mirror of
https://github.com/xenos1337/httptoolkit-patcher.git
synced 2026-07-16 00:24:24 +02:00
Compare commits
21
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3490b01d80 | ||
|
|
432f9a8e70 | ||
|
|
7d437d16e2 | ||
|
|
3ed4d62d6c | ||
|
|
1adcd3c5cb | ||
|
|
86c35f079a | ||
|
|
24c8828e71 | ||
|
|
bbc7918678 | ||
|
|
68f7e1c1c0 | ||
|
|
0685a1e727 | ||
|
|
fa9dcf5361 | ||
|
|
efcc89bab4 | ||
|
|
8215f4348b | ||
|
|
359b707b44 | ||
|
|
7537b33593 | ||
|
|
fe97c803f5 | ||
|
|
61b3bdeb5e | ||
|
|
91333c1a3e | ||
|
|
d1a64774da | ||
|
|
fe7cae9dd9 | ||
|
|
cb8fd663b7 |
@@ -20,10 +20,6 @@ The patcher intercepts HTTP Toolkit's authentication functions:
|
|||||||
|
|
||||||
By hooking these functions, we bypass the subscription checks entirely.
|
By hooking these functions, we bypass the subscription checks entirely.
|
||||||
|
|
||||||
### Can They Fix It?
|
|
||||||
|
|
||||||
Yes, but they most likely won't. Fixing this would require changing their entire codebase architecture. And if they do? I'll just update the patcher.
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
1. Install Node.js (if not already installed)
|
1. Install Node.js (if not already installed)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// @ts-check
|
// @ts-check
|
||||||
import { spawn, execSync } from "child_process";
|
import { spawn, execSync } from "child_process";
|
||||||
import asar from "@electron/asar";
|
import * as asar from "@electron/asar";
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
@@ -23,6 +23,18 @@ const __filename = (() => {
|
|||||||
return fileURLToPath(import.meta.url);
|
return fileURLToPath(import.meta.url);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
// Version info - read from package.json
|
||||||
|
const GITHUB_REPO = "xenos1337/httptoolkit-patcher";
|
||||||
|
const LOCAL_VERSION = (() => {
|
||||||
|
try {
|
||||||
|
const packageJsonPath = path.join(path.dirname(__filename), "package.json");
|
||||||
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
||||||
|
return packageJson.version || "0.0.0";
|
||||||
|
} catch {
|
||||||
|
return "0.0.0";
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
// Check if running with elevated privileges
|
// Check if running with elevated privileges
|
||||||
function isElevated() {
|
function isElevated() {
|
||||||
if (isWin) {
|
if (isWin) {
|
||||||
@@ -51,19 +63,79 @@ function prompt(question) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to fetch content from URL
|
/**
|
||||||
function fetchUrl(url) {
|
* Fetch JSON from a URL
|
||||||
|
* @param {string} url
|
||||||
|
* @returns {Promise<Array<{name: string}>>}
|
||||||
|
*/
|
||||||
|
function fetchJson(url) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
const options = {
|
||||||
|
headers: {
|
||||||
|
"User-Agent": "httptoolkit-patcher",
|
||||||
|
Accept: "application/vnd.github.v3+json",
|
||||||
|
},
|
||||||
|
};
|
||||||
https
|
https
|
||||||
.get(url, res => {
|
.get(url, options, res => {
|
||||||
|
if (res.statusCode !== 200) {
|
||||||
|
reject(new Error(`HTTP ${res.statusCode}`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
let data = "";
|
let data = "";
|
||||||
res.on("data", chunk => (data += chunk));
|
res.on("data", chunk => (data += chunk));
|
||||||
res.on("end", () => resolve(data));
|
res.on("end", () => {
|
||||||
|
try {
|
||||||
|
resolve(JSON.parse(data));
|
||||||
|
} catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.on("error", reject);
|
.on("error", reject);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function compareVersions(v1, v2) {
|
||||||
|
const normalize = (/** @type {string} */ v) => v.replace(/^v/, "").split(".").map(Number);
|
||||||
|
const parts1 = normalize(v1);
|
||||||
|
const parts2 = normalize(v2);
|
||||||
|
|
||||||
|
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
|
||||||
|
const num1 = parts1[i] || 0;
|
||||||
|
const num2 = parts2[i] || 0;
|
||||||
|
if (num1 > num2) return 1;
|
||||||
|
if (num1 < num2) return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for updates from GitHub
|
||||||
|
*/
|
||||||
|
async function checkForUpdates() {
|
||||||
|
try {
|
||||||
|
const tags = await fetchJson(`https://api.github.com/repos/${GITHUB_REPO}/tags`);
|
||||||
|
|
||||||
|
if (!tags || tags.length === 0) {
|
||||||
|
return; // No tags found, skip update check
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tags are returned in order, first one is the latest
|
||||||
|
const latestTag = tags[0].name;
|
||||||
|
const latestVersion = latestTag.replace(/^v/, "");
|
||||||
|
|
||||||
|
if (compareVersions(latestVersion, LOCAL_VERSION) > 0) {
|
||||||
|
console.log(chalk.yellowBright('\n╔════════════════════════════════════════════════════════════╗'));
|
||||||
|
console.log(chalk.yellowBright('║') + chalk.white(' A new version is available: ') + chalk.greenBright(`v${latestVersion}`) + chalk.white(' (current: ') + chalk.gray(`v${LOCAL_VERSION}`) + chalk.white(') ') + chalk.yellowBright(' ║'));
|
||||||
|
console.log(chalk.yellowBright('║') + chalk.white(' Update: ') + chalk.cyanBright(`https://github.com/${GITHUB_REPO}`) + chalk.white(' ') + chalk.yellowBright('║'));
|
||||||
|
console.log(chalk.yellowBright('╚════════════════════════════════════════════════════════════╝\n'));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Silently ignore update check errors (network issues, etc.)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Helper function to remove directory recursively
|
// Helper function to remove directory recursively
|
||||||
function rm(dirPath) {
|
function rm(dirPath) {
|
||||||
if (!fs.existsSync(dirPath)) return;
|
if (!fs.existsSync(dirPath)) return;
|
||||||
@@ -78,7 +150,11 @@ function rm(dirPath) {
|
|||||||
|
|
||||||
// Find HTTP Toolkit installation path
|
// Find HTTP Toolkit installation path
|
||||||
async function findAppPath() {
|
async function findAppPath() {
|
||||||
const possiblePaths = isWin ? [path.join("C:", "Program Files", "HTTP Toolkit", "resources"), path.join("C:", "Program Files (x86)", "HTTP Toolkit", "resources")] : isMac ? ["/Applications/HTTP Toolkit.app/Contents/Resources"] : ["/opt/HTTP Toolkit/resources", "/opt/httptoolkit/resources"];
|
const possiblePaths = isWin
|
||||||
|
? [path.join("C:", "Program Files", "HTTP Toolkit", "resources"), path.join("C:", "Program Files (x86)", "HTTP Toolkit", "resources"), path.join(process.env.LOCALAPPDATA || path.join(process.env.USERPROFILE || "", "AppData", "Local"), "Programs", "HTTP Toolkit", "resources")]
|
||||||
|
: isMac
|
||||||
|
? ["/Applications/HTTP Toolkit.app/Contents/Resources"]
|
||||||
|
: ["/opt/HTTP Toolkit/resources", "/opt/httptoolkit/resources"];
|
||||||
|
|
||||||
for (const p of possiblePaths) {
|
for (const p of possiblePaths) {
|
||||||
if (fs.existsSync(path.join(p, "app.asar"))) {
|
if (fs.existsSync(path.join(p, "app.asar"))) {
|
||||||
@@ -86,11 +162,11 @@ async function findAppPath() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(chalk.yellowBright`[!] HTTP Toolkit not found in default locations`);
|
console.log(chalk.yellowBright('[!] HTTP Toolkit not found in default locations'));
|
||||||
const userPath = await prompt("Please enter the path to HTTP Toolkit executable/app: ");
|
const userPath = await prompt("Please enter the path to HTTP Toolkit executable/app: ");
|
||||||
|
|
||||||
if (!userPath) {
|
if (!userPath) {
|
||||||
console.error(chalk.redBright`[-] No path provided`);
|
console.error(chalk.redBright('[-] No path provided'));
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +177,7 @@ async function findAppPath() {
|
|||||||
if (!resourcesPath.endsWith("resources")) resourcesPath = path.join(resourcesPath, "resources");
|
if (!resourcesPath.endsWith("resources")) resourcesPath = path.join(resourcesPath, "resources");
|
||||||
|
|
||||||
if (!fs.existsSync(path.join(resourcesPath, "app.asar"))) {
|
if (!fs.existsSync(path.join(resourcesPath, "app.asar"))) {
|
||||||
console.error(chalk.redBright`[-] app.asar not found at ${resourcesPath}`);
|
console.error(chalk.redBright(`[-] app.asar not found at ${resourcesPath}`));
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +186,7 @@ async function findAppPath() {
|
|||||||
|
|
||||||
// Request elevated permissions
|
// Request elevated permissions
|
||||||
async function requestElevation() {
|
async function requestElevation() {
|
||||||
console.log(chalk.yellowBright`[!] Requesting elevated permissions...`);
|
console.log(chalk.yellowBright('[!] Requesting elevated permissions...'));
|
||||||
|
|
||||||
// @ts-ignore - pkg adds this property at runtime
|
// @ts-ignore - pkg adds this property at runtime
|
||||||
const isBundled = process.pkg;
|
const isBundled = process.pkg;
|
||||||
@@ -125,28 +201,28 @@ async function requestElevation() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log(chalk.greenBright`[+] Spawning PowerShell with script: ${script}`);
|
console.log(chalk.greenBright(`[+] Spawning PowerShell with script: ${script}`));
|
||||||
execSync(`powershell -Command "${script}"`, { stdio: "inherit" });
|
execSync(`powershell -Command "${script}"`, { stdio: "inherit" });
|
||||||
console.log(chalk.blueBright`[+] Restarting with administrator privileges...`);
|
console.log(chalk.blueBright('[+] Restarting with administrator privileges...'));
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(chalk.redBright`[-] Failed to elevate permissions: ${e.message}`);
|
console.error(chalk.redBright(`[-] Failed to elevate permissions: ${e.message}`));
|
||||||
console.error(chalk.redBright`[-] Please run as administrator manually`);
|
console.error(chalk.redBright('[-] Please run as administrator manually'));
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
} else if (isLinux) {
|
} else if (isLinux) {
|
||||||
// Linux: Cannot auto-elevate with sudo, show instructions instead
|
// Linux: Cannot auto-elevate with sudo, show instructions instead
|
||||||
console.log(chalk.yellowBright`[!] Elevated permissions are required for patching on Linux`);
|
console.log(chalk.yellowBright('[!] Elevated permissions are required for patching on Linux'));
|
||||||
console.log(chalk.yellowBright`[!] Please re-run this script with sudo:`);
|
console.log(chalk.yellowBright('[!] Please re-run this script with sudo:'));
|
||||||
if (isBundled) {
|
if (isBundled) {
|
||||||
console.log(chalk.blueBright` sudo ${__filename}`);
|
console.log(chalk.blueBright(` sudo ${__filename}`));
|
||||||
} else {
|
} else {
|
||||||
console.log(chalk.blueBright` sudo node ${__filename}`);
|
console.log(chalk.blueBright(` sudo node ${__filename}`));
|
||||||
}
|
}
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
} else {
|
} else {
|
||||||
// macOS: Try to elevate with sudo
|
// macOS: Try to elevate with sudo
|
||||||
console.log(chalk.blueBright`[+] Restarting with sudo...`);
|
console.log(chalk.blueBright('[+] Restarting with sudo...'));
|
||||||
try {
|
try {
|
||||||
let child;
|
let child;
|
||||||
if (isBundled) {
|
if (isBundled) {
|
||||||
@@ -160,8 +236,8 @@ async function requestElevation() {
|
|||||||
}
|
}
|
||||||
child.on("exit", code => process.exit(code || 0));
|
child.on("exit", code => process.exit(code || 0));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(chalk.redBright`[-] Failed to elevate permissions: ${e.message}`);
|
console.error(chalk.redBright(`[-] Failed to elevate permissions: ${e.message}`));
|
||||||
console.error(chalk.redBright`[-] Please run with sudo manually`);
|
console.error(chalk.redBright('[-] Please run with sudo manually'));
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -179,76 +255,207 @@ function checkPermissions(filePath) {
|
|||||||
fs.mkdirSync(testDirPath, { recursive: true });
|
fs.mkdirSync(testDirPath, { recursive: true });
|
||||||
fs.rmdirSync(testDirPath);
|
fs.rmdirSync(testDirPath);
|
||||||
} catch (dirError) {
|
} catch (dirError) {
|
||||||
console.error(chalk.redBright`[-] Cannot create directories in ${path.dirname(filePath)}: ${dirError.message}`);
|
console.error(chalk.redBright(`[-] Cannot create directories in ${path.dirname(filePath)}: ${dirError.message}`));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(chalk.greenBright`[+] Permissions check passed for ${filePath}`);
|
console.log(chalk.greenBright(`[+] Permissions check passed for ${filePath}`));
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(chalk.redBright`[-] Permissions check failed for ${filePath}: ${e.message}`);
|
console.error(chalk.redBright(`[-] Permissions check failed for ${filePath}: ${e.message}`));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kill HTTP Toolkit processes
|
// Kill HTTP Toolkit processes
|
||||||
async function killHttpToolkit() {
|
async function killHttpToolkit() {
|
||||||
console.log(chalk.yellowBright`[+] Checking for running HTTP Toolkit processes...`);
|
console.log(chalk.yellowBright('[+] Checking for running HTTP Toolkit processes...'));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (isWin) {
|
if (isWin) {
|
||||||
// Windows: Use tasklist to find and taskkill to terminate
|
// Windows: Use tasklist to find and taskkill to terminate
|
||||||
const output = execSync('tasklist /FI "IMAGENAME eq HTTP Toolkit.exe" /FO CSV /NH', { encoding: "utf-8" });
|
const output = execSync('tasklist /FI "IMAGENAME eq HTTP Toolkit.exe" /FO CSV /NH', { encoding: "utf-8" });
|
||||||
if (output.includes("HTTP Toolkit.exe")) {
|
if (output.includes("HTTP Toolkit.exe")) {
|
||||||
console.log(chalk.yellowBright`[!] HTTP Toolkit is running, attempting to close it...`);
|
console.log(chalk.yellowBright('[!] HTTP Toolkit is running, attempting to close it...'));
|
||||||
execSync('taskkill /F /IM "HTTP Toolkit.exe" /T', { stdio: "ignore" });
|
execSync('taskkill /F /IM "HTTP Toolkit.exe" /T', { stdio: "ignore" });
|
||||||
console.log(chalk.greenBright`[+] HTTP Toolkit processes terminated`);
|
console.log(chalk.greenBright('[+] HTTP Toolkit processes terminated'));
|
||||||
// Wait a moment for the process to fully close
|
// Wait a moment for the process to fully close
|
||||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||||
} else {
|
} else {
|
||||||
console.log(chalk.greenBright`[+] HTTP Toolkit is not running`);
|
console.log(chalk.greenBright('[+] HTTP Toolkit is not running'));
|
||||||
}
|
}
|
||||||
} else if (isMac) {
|
} else if (isMac) {
|
||||||
// macOS: Use pgrep and pkill
|
// macOS: Use pgrep and pkill
|
||||||
try {
|
try {
|
||||||
execSync('pgrep -f "HTTP Toolkit"', { stdio: "ignore" });
|
execSync('pgrep -f "HTTP Toolkit"', { stdio: "ignore" });
|
||||||
console.log(chalk.yellowBright`[!] HTTP Toolkit is running, attempting to close it...`);
|
console.log(chalk.yellowBright('[!] HTTP Toolkit is running, attempting to close it...'));
|
||||||
execSync('pkill -f "HTTP Toolkit"', { stdio: "ignore" });
|
execSync('pkill -f "HTTP Toolkit"', { stdio: "ignore" });
|
||||||
console.log(chalk.greenBright`[+] HTTP Toolkit processes terminated`);
|
console.log(chalk.greenBright('[+] HTTP Toolkit processes terminated'));
|
||||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(chalk.greenBright`[+] HTTP Toolkit is not running`);
|
console.log(chalk.greenBright('[+] HTTP Toolkit is not running'));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Linux: Use pgrep and pkill
|
// Linux: Use pgrep and pkill
|
||||||
try {
|
try {
|
||||||
execSync('pgrep -f "HTTP Toolkit"', { stdio: "ignore" });
|
execSync('pgrep -f "HTTP Toolkit"', { stdio: "ignore" });
|
||||||
console.log(chalk.yellowBright`[!] HTTP Toolkit is running, attempting to close it...`);
|
console.log(chalk.yellowBright('[!] HTTP Toolkit is running, attempting to close it...'));
|
||||||
execSync('pkill -f "HTTP Toolkit"', { stdio: "ignore" });
|
execSync('pkill -f "HTTP Toolkit"', { stdio: "ignore" });
|
||||||
console.log(chalk.greenBright`[+] HTTP Toolkit processes terminated`);
|
console.log(chalk.greenBright('[+] HTTP Toolkit processes terminated'));
|
||||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(chalk.greenBright`[+] HTTP Toolkit is not running`);
|
console.log(chalk.greenBright('[+] HTTP Toolkit is not running'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(chalk.yellowBright`[!] Could not check/kill processes: ${e.message}`);
|
console.log(chalk.yellowBright(`[!] Could not check/kill processes: ${e.message}`));
|
||||||
console.log(chalk.yellowBright`[!] If HTTP Toolkit is running, please close it manually`);
|
console.log(chalk.yellowBright('[!] If HTTP Toolkit is running, please close it manually'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve the base installation directory (without the trailing resources folder)
|
||||||
|
function getBinaryBasePath(resourcesPath) {
|
||||||
|
const normalized = resourcesPath.replace(/[\\/]+$/, "");
|
||||||
|
if (normalized.toLowerCase().endsWith("resources")) {
|
||||||
|
return path.dirname(normalized);
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the executable path for HTTP Toolkit based on platform
|
||||||
|
function getExecutablePath(resourcesPath) {
|
||||||
|
const basePath = getBinaryBasePath(resourcesPath);
|
||||||
|
const candidates = isWin ? [path.join(basePath, "HTTP Toolkit.exe"), path.join(basePath, "httptoolkit.exe")] : isMac ? [path.join(basePath, "MacOS", "HTTP Toolkit"), path.join(basePath, "MacOS", "HTTP Toolkit Preview")] : [path.join(basePath, "httptoolkit"), path.join(basePath, "HTTP Toolkit")];
|
||||||
|
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
if (fs.existsSync(candidate)) {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Could not locate HTTP Toolkit executable near ${resourcesPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract hashes from integrity check output
|
||||||
|
function extractIntegrityHashes(output) {
|
||||||
|
const regex = /Integrity check failed for asar archive[\s\S]*?\(\s*([0-9a-f]{64})\s*vs\s*([0-9a-f]{64})\s*\)/i;
|
||||||
|
const match = output.match(regex);
|
||||||
|
if (!match) return null;
|
||||||
|
return {
|
||||||
|
originalHash: match[1],
|
||||||
|
newHash: match[2],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Launch the app once to grab integrity hashes from its crash output
|
||||||
|
async function captureIntegrityHashes(executablePath) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let output = "";
|
||||||
|
let finished = false;
|
||||||
|
const child = spawn(executablePath, {
|
||||||
|
cwd: path.dirname(executablePath),
|
||||||
|
stdio: ["ignore", "pipe", "pipe"],
|
||||||
|
windowsHide: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
if (!finished) {
|
||||||
|
finished = true;
|
||||||
|
child.kill();
|
||||||
|
reject(new Error("Timed out waiting for integrity check output"));
|
||||||
|
}
|
||||||
|
}, 20000);
|
||||||
|
|
||||||
|
const handleData = data => {
|
||||||
|
output += data.toString();
|
||||||
|
const hashes = extractIntegrityHashes(output);
|
||||||
|
if (hashes && !finished) {
|
||||||
|
finished = true;
|
||||||
|
clearTimeout(timeout);
|
||||||
|
child.kill();
|
||||||
|
resolve(hashes);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
child.stdout.on("data", handleData);
|
||||||
|
child.stderr.on("data", handleData);
|
||||||
|
|
||||||
|
child.on("error", err => {
|
||||||
|
if (!finished) {
|
||||||
|
finished = true;
|
||||||
|
clearTimeout(timeout);
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
child.on("exit", () => {
|
||||||
|
if (!finished) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
const hashes = extractIntegrityHashes(output);
|
||||||
|
if (hashes) {
|
||||||
|
resolve(hashes);
|
||||||
|
} else {
|
||||||
|
reject(new Error("Could not find integrity hashes in HTTP Toolkit output"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace all occurrences of the original hash with the new hash inside the binary
|
||||||
|
function patchExecutableHash(executablePath, originalHash, newHash) {
|
||||||
|
if (originalHash.length !== newHash.length) {
|
||||||
|
throw new Error("Hash lengths do not match; cannot safely patch binary");
|
||||||
|
}
|
||||||
|
|
||||||
|
const binary = fs.readFileSync(executablePath);
|
||||||
|
const originalBuf = Buffer.from(originalHash, "utf-8");
|
||||||
|
const newBuf = Buffer.from(newHash, "utf-8");
|
||||||
|
|
||||||
|
let occurrences = 0;
|
||||||
|
let idx = binary.indexOf(originalBuf);
|
||||||
|
while (idx !== -1) {
|
||||||
|
newBuf.copy(binary, idx);
|
||||||
|
occurrences += 1;
|
||||||
|
idx = binary.indexOf(originalBuf, idx + originalBuf.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (occurrences === 0) {
|
||||||
|
throw new Error("Original hash not found in binary");
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(executablePath, binary);
|
||||||
|
return occurrences;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace the asar integrity hash inside the macOS Info.plist
|
||||||
|
function patchInfoPlistHash(resourcesPath, originalHash, newHash) {
|
||||||
|
const basePath = getBinaryBasePath(resourcesPath);
|
||||||
|
const plistPath = path.join(basePath, "Info.plist");
|
||||||
|
|
||||||
|
if (!fs.existsSync(plistPath)) {
|
||||||
|
throw new Error(`Info.plist not found at ${plistPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let content = fs.readFileSync(plistPath, "utf-8");
|
||||||
|
|
||||||
|
if (!content.includes(originalHash)) {
|
||||||
|
throw new Error("Original hash not found in Info.plist");
|
||||||
|
}
|
||||||
|
|
||||||
|
content = content.replaceAll(originalHash, newHash);
|
||||||
|
fs.writeFileSync(plistPath, content, "utf-8");
|
||||||
|
}
|
||||||
|
|
||||||
// Unpatch/restore function
|
// Unpatch/restore function
|
||||||
async function unpatchApp() {
|
async function unpatchApp() {
|
||||||
console.log(chalk.blueBright`[+] HTTP Toolkit Unpatcher Started`);
|
console.log(chalk.blueBright('[+] HTTP Toolkit Unpatcher Started'));
|
||||||
|
|
||||||
// Step 1: Find app path
|
|
||||||
const appPath = await findAppPath();
|
const appPath = await findAppPath();
|
||||||
console.log(chalk.greenBright`[+] HTTP Toolkit found at ${appPath}`);
|
console.log(chalk.greenBright(`[+] HTTP Toolkit found at ${appPath}`));
|
||||||
|
|
||||||
// Step 2: Kill HTTP Toolkit if running
|
|
||||||
await killHttpToolkit();
|
await killHttpToolkit();
|
||||||
|
|
||||||
// Step 3: Check permissions
|
|
||||||
const asarPath = path.join(appPath, "app.asar");
|
const asarPath = path.join(appPath, "app.asar");
|
||||||
const backupPath = path.join(appPath, "app.asar.bak");
|
const backupPath = path.join(appPath, "app.asar.bak");
|
||||||
const extractPath = path.join(appPath, "app.asar_extracted");
|
const extractPath = path.join(appPath, "app.asar_extracted");
|
||||||
@@ -257,196 +464,249 @@ async function unpatchApp() {
|
|||||||
const hasPermissions = checkPermissions(appPath) && checkPermissions(asarPath);
|
const hasPermissions = checkPermissions(appPath) && checkPermissions(asarPath);
|
||||||
|
|
||||||
if (!hasPermissions) {
|
if (!hasPermissions) {
|
||||||
console.log(chalk.yellowBright`[!] No write permissions for ${appPath}`);
|
console.log(chalk.yellowBright(`[!] No write permissions for ${appPath}`));
|
||||||
|
|
||||||
if (isElevated()) {
|
if (isElevated()) {
|
||||||
console.error(chalk.redBright`[-] Still no permissions even with elevated privileges`);
|
console.error(chalk.redBright('[-] Still no permissions even with elevated privileges'));
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(chalk.yellowBright`[!] Administrator/sudo privileges required for unpatching`);
|
console.log(chalk.yellowBright('[!] Administrator/sudo privileges required for unpatching'));
|
||||||
await requestElevation();
|
await requestElevation();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 4: Check if backup exists
|
|
||||||
if (!fs.existsSync(backupPath)) {
|
if (!fs.existsSync(backupPath)) {
|
||||||
console.error(chalk.redBright`[-] Backup file not found at ${backupPath}`);
|
console.error(chalk.redBright(`[-] Backup file not found at ${backupPath}`));
|
||||||
console.error(chalk.redBright`[-] Cannot unpatch without backup file`);
|
console.error(chalk.redBright('[-] Cannot unpatch without backup file'));
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 5: Restore from backup
|
console.log(chalk.yellowBright('[+] Restoring from backup...'));
|
||||||
console.log(chalk.yellowBright`[+] Restoring from backup...`);
|
|
||||||
try {
|
try {
|
||||||
fs.copyFileSync(backupPath, asarPath);
|
fs.copyFileSync(backupPath, asarPath);
|
||||||
console.log(chalk.greenBright`[+] Restored app.asar from backup`);
|
console.log(chalk.greenBright('[+] Restored app.asar from backup'));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(chalk.redBright`[-] Failed to restore backup: ${e.message}`);
|
console.error(chalk.redBright(`[-] Failed to restore backup: ${e.message}`));
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 6: Clean up extracted files if they exist
|
|
||||||
if (fs.existsSync(extractPath)) {
|
if (fs.existsSync(extractPath)) {
|
||||||
console.log(chalk.yellowBright`[+] Removing extracted files...`);
|
console.log(chalk.yellowBright('[+] Removing extracted files...'));
|
||||||
rm(extractPath);
|
rm(extractPath);
|
||||||
console.log(chalk.greenBright`[+] Cleaned up extracted files`);
|
console.log(chalk.greenBright('[+] Cleaned up extracted files'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 7: Optionally remove backup
|
|
||||||
const removeBackup = await prompt("Do you want to remove the backup file? (y/n): ");
|
const removeBackup = await prompt("Do you want to remove the backup file? (y/n): ");
|
||||||
if (removeBackup.toLowerCase() === "y" || removeBackup.toLowerCase() === "yes") {
|
if (removeBackup.toLowerCase() === "y" || removeBackup.toLowerCase() === "yes") {
|
||||||
fs.rmSync(backupPath, { force: true });
|
fs.rmSync(backupPath, { force: true });
|
||||||
console.log(chalk.greenBright`[+] Backup file removed`);
|
console.log(chalk.greenBright('[+] Backup file removed'));
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(chalk.greenBright`[+] Successfully unpatched!`);
|
console.log(chalk.greenBright('[+] Successfully unpatched!'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main patching function
|
// Main patching function
|
||||||
async function patchApp() {
|
async function patchApp() {
|
||||||
console.log(chalk.blueBright`[+] HTTP Toolkit Patcher Started`);
|
console.log(chalk.blueBright('[+] HTTP Toolkit Patcher Started'));
|
||||||
|
|
||||||
// Step 1: Find app path
|
|
||||||
const appPath = await findAppPath();
|
const appPath = await findAppPath();
|
||||||
console.log(chalk.greenBright`[+] HTTP Toolkit found at ${appPath}`);
|
console.log(chalk.greenBright(`[+] HTTP Toolkit found at ${appPath}`));
|
||||||
|
|
||||||
// Step 2: Kill HTTP Toolkit if running
|
|
||||||
await killHttpToolkit();
|
await killHttpToolkit();
|
||||||
|
|
||||||
// Step 3: Check permissions
|
|
||||||
const asarPath = path.join(appPath, "app.asar");
|
const asarPath = path.join(appPath, "app.asar");
|
||||||
|
|
||||||
// Check if we have write permissions on both the directory and the file
|
// Check if we have write permissions on both the directory and the file
|
||||||
const hasPermissions = checkPermissions(appPath) && checkPermissions(asarPath);
|
const hasPermissions = checkPermissions(appPath) && checkPermissions(asarPath);
|
||||||
|
|
||||||
if (!hasPermissions) {
|
if (!hasPermissions) {
|
||||||
console.log(chalk.yellowBright`[!] No write permissions for ${appPath}`);
|
console.log(chalk.yellowBright(`[!] No write permissions for ${appPath}`));
|
||||||
|
|
||||||
if (isElevated()) {
|
if (isElevated()) {
|
||||||
console.error(chalk.redBright`[-] Still no permissions even with elevated privileges`);
|
console.error(chalk.redBright('[-] Still no permissions even with elevated privileges'));
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(chalk.yellowBright`[!] Administrator/sudo privileges required for patching`);
|
console.log(chalk.yellowBright('[!] Administrator/sudo privileges required for patching'));
|
||||||
await requestElevation();
|
await requestElevation();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 4: Backup app.asar
|
|
||||||
const backupPath = path.join(appPath, "app.asar.bak");
|
const backupPath = path.join(appPath, "app.asar.bak");
|
||||||
if (!fs.existsSync(backupPath)) {
|
if (!fs.existsSync(backupPath)) {
|
||||||
console.log(chalk.yellowBright`[+] Creating backup...`);
|
console.log(chalk.yellowBright('[+] Creating backup...'));
|
||||||
fs.copyFileSync(asarPath, backupPath);
|
fs.copyFileSync(asarPath, backupPath);
|
||||||
console.log(chalk.greenBright`[+] Backup created at ${backupPath}`);
|
console.log(chalk.greenBright(`[+] Backup created at ${backupPath}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 5: Extract app.asar
|
|
||||||
const extractPath = path.join(appPath, "app.asar_extracted");
|
const extractPath = path.join(appPath, "app.asar_extracted");
|
||||||
console.log(chalk.yellowBright`[+] Extracting app.asar...`);
|
console.log(chalk.yellowBright('[+] Extracting app.asar...'));
|
||||||
rm(extractPath);
|
rm(extractPath);
|
||||||
asar.extractAll(asarPath, extractPath);
|
asar.extractAll(asarPath, extractPath);
|
||||||
console.log(chalk.greenBright`[+] Extracted to ${extractPath}`);
|
console.log(chalk.greenBright(`[+] Extracted to ${extractPath}`));
|
||||||
|
|
||||||
// Step 6: Check if preload.cjs or preload.js exists
|
const preloadPath = path.join(extractPath, "build", "preload.cjs");
|
||||||
let preloadPath = path.join(extractPath, "build", "preload.cjs");
|
|
||||||
if (!fs.existsSync(preloadPath)) {
|
if (!fs.existsSync(preloadPath)) {
|
||||||
console.log(chalk.yellowBright`[!] preload.cjs not found, checking for preload.js...`);
|
console.error(chalk.redBright(`[-] preload.cjs not found in ${path.join(extractPath, "build")}`));
|
||||||
preloadPath = path.join(extractPath, "build", "preload.js");
|
console.error(chalk.yellowBright('[!] Please download the latest version of HTTP Toolkit from https://httptoolkit.com/'));
|
||||||
if (!fs.existsSync(preloadPath)) {
|
|
||||||
console.error(chalk.redBright`[-] Neither preload.cjs nor preload.js found in ${path.join(extractPath, "build")}`);
|
|
||||||
rm(extractPath);
|
rm(extractPath);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
console.log(chalk.greenBright`[+] Found preload.js`);
|
console.log(chalk.greenBright('[+] Found preload.cjs'));
|
||||||
} else {
|
|
||||||
console.log(chalk.greenBright`[+] Found preload.cjs`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 7: Fetch inject.js from GitHub
|
console.log(chalk.yellowBright('[+] Reading inject code from local file...'));
|
||||||
console.log(chalk.yellowBright`[+] Fetching inject code from GitHub...`);
|
const injectJsPath = path.join(path.dirname(__filename), "inject.js");
|
||||||
const injectCode = await fetchUrl("https://raw.githubusercontent.com/xenos1337/httptoolkit-patcher/refs/heads/master/inject.js");
|
if (!fs.existsSync(injectJsPath)) {
|
||||||
if (!injectCode || !injectCode.includes("injectPageContextHooks")) {
|
console.error(chalk.redBright(`[-] inject.js not found at ${injectJsPath}`));
|
||||||
console.error(chalk.redBright`[-] Failed to fetch inject.js from GitHub`);
|
|
||||||
rm(extractPath);
|
rm(extractPath);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
console.log(chalk.greenBright`[+] Inject code fetched successfully`);
|
const injectCode = fs.readFileSync(injectJsPath, "utf-8");
|
||||||
|
if (!injectCode || !injectCode.includes("PAGE-INJECT")) {
|
||||||
|
console.error(chalk.redBright('[-] Invalid inject.js file'));
|
||||||
|
rm(extractPath);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
console.log(chalk.greenBright('[+] Inject code loaded successfully'));
|
||||||
|
|
||||||
// Step 8: Read preload file and check if already patched
|
|
||||||
let preloadContent = fs.readFileSync(preloadPath, "utf-8");
|
let preloadContent = fs.readFileSync(preloadPath, "utf-8");
|
||||||
const isPatched = preloadContent.includes("injectPageContextHooks");
|
|
||||||
|
|
||||||
if (isPatched) {
|
const electronVarName = preloadContent.includes("electron_1") ? "electron_1" : "electron";
|
||||||
console.log(chalk.yellowBright`[!] File already patched`);
|
console.log(chalk.greenBright(`[+] Detected electron variable: ${electronVarName}`));
|
||||||
|
|
||||||
|
const preloadPatchCode = `
|
||||||
|
(function loadInjectScript() {
|
||||||
|
const injectCode = ${JSON.stringify(injectCode)};
|
||||||
|
|
||||||
|
function injectViaWebFrame() {
|
||||||
|
try {
|
||||||
|
const { webFrame } = ${electronVarName};
|
||||||
|
if (webFrame && webFrame.executeJavaScript) {
|
||||||
|
webFrame.executeJavaScript(injectCode).then(() => console.log("[PRELOAD] Injected via webFrame.executeJavaScript")).catch(err => console.error("[PRELOAD] webFrame injection failed:", err));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[PRELOAD] webFrame not available:", e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!injectViaWebFrame()) {
|
||||||
|
const tryInject = () => {
|
||||||
|
if (!injectViaWebFrame()) {
|
||||||
|
console.error("[PRELOAD] All injection methods failed");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
||||||
|
tryInject();
|
||||||
|
} else {
|
||||||
|
document.addEventListener('DOMContentLoaded', tryInject, { once: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
`;
|
||||||
|
const isPreloadPatched = preloadContent.includes("loadInjectScript");
|
||||||
|
|
||||||
|
if (isPreloadPatched) {
|
||||||
|
console.log(chalk.yellowBright('[!] Files already patched'));
|
||||||
const answer = await prompt("Do you want to repatch? (y/n): ");
|
const answer = await prompt("Do you want to repatch? (y/n): ");
|
||||||
|
|
||||||
if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
|
if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
|
||||||
console.log(chalk.blueBright`[+] Patching cancelled`);
|
console.log(chalk.blueBright('[+] Patching cancelled'));
|
||||||
rm(extractPath);
|
rm(extractPath);
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace the existing injectPageContextHooks function
|
// Remove existing patches
|
||||||
console.log(chalk.yellowBright`[+] Replacing existing patch...`);
|
console.log(chalk.yellowBright('[+] Replacing existing patches...'));
|
||||||
const functionRegex = /\(function injectPageContextHooks\(\) \{[\s\S]*?\}\)\(\);/;
|
|
||||||
preloadContent = preloadContent.replace(functionRegex, injectCode);
|
|
||||||
} else {
|
|
||||||
// Find line with electron_1 and insert inject code below it
|
|
||||||
console.log(chalk.yellowBright`[+] Applying patch...`);
|
|
||||||
const lines = preloadContent.split("\n");
|
|
||||||
let insertIndex = -1;
|
|
||||||
|
|
||||||
for (let i = 0; i < lines.length; i++) {
|
// Remove preload patch
|
||||||
if (lines[i].includes("electron_1")) {
|
const preloadPatchRegex = /\n?\(function loadInjectScript\(\) \{[\s\S]*?\}\)\(\);/;
|
||||||
insertIndex = i + 1;
|
preloadContent = preloadContent.replace(preloadPatchRegex, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(chalk.yellowBright('[+] Applying preload patch...'));
|
||||||
|
const preloadLines = preloadContent.split("\n");
|
||||||
|
let preloadInsertIndex = -1;
|
||||||
|
|
||||||
|
for (let i = 0; i < preloadLines.length; i++) {
|
||||||
|
const line = preloadLines[i];
|
||||||
|
if (line.includes('require("electron")') || line.includes("require('electron')") || line.includes("electron_1")) {
|
||||||
|
preloadInsertIndex = i + 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (insertIndex === -1) {
|
if (preloadInsertIndex === -1) {
|
||||||
console.error(chalk.redBright`[-] Could not find insertion point (electron_1) in ${path.basename(preloadPath)}`);
|
console.error(chalk.redBright(`[-] Could not find insertion point (electron import) in ${path.basename(preloadPath)}`));
|
||||||
rm(extractPath);
|
rm(extractPath);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
lines.splice(insertIndex, 0, injectCode);
|
preloadLines.splice(preloadInsertIndex, 0, preloadPatchCode);
|
||||||
preloadContent = lines.join("\n");
|
preloadContent = preloadLines.join("\n");
|
||||||
|
|
||||||
|
// Write patched preload file
|
||||||
|
fs.writeFileSync(preloadPath, preloadContent, "utf-8");
|
||||||
|
console.log(chalk.greenBright(`[+] ${path.basename(preloadPath)} patched successfully`));
|
||||||
|
|
||||||
|
console.log(chalk.yellowBright('[+] Repackaging app.asar...'));
|
||||||
|
await asar.createPackage(extractPath, asarPath);
|
||||||
|
console.log(chalk.greenBright('[+] app.asar repackaged successfully'));
|
||||||
|
|
||||||
|
let executablePath;
|
||||||
|
try {
|
||||||
|
executablePath = getExecutablePath(appPath);
|
||||||
|
} catch (e) {
|
||||||
|
rm(extractPath);
|
||||||
|
console.error(chalk.redBright(`[-] ${e.message}`));
|
||||||
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 9: Write patched preload file
|
console.log(chalk.yellowBright('[+] Launching HTTP Toolkit to read integrity hashes...'));
|
||||||
fs.writeFileSync(preloadPath, preloadContent, "utf-8");
|
let hashes;
|
||||||
console.log(chalk.greenBright`[+] ${path.basename(preloadPath)} patched successfully`);
|
|
||||||
|
|
||||||
// Step 10: Repackage app.asar
|
|
||||||
console.log(chalk.yellowBright`[+] Repackaging app.asar...`);
|
|
||||||
await asar.createPackage(extractPath, asarPath);
|
|
||||||
console.log(chalk.greenBright`[+] app.asar repackaged successfully`);
|
|
||||||
|
|
||||||
// Step 11: Clean up
|
|
||||||
console.log(chalk.yellowBright`[+] Cleaning up temporary files...`);
|
|
||||||
rm(extractPath);
|
|
||||||
console.log(chalk.greenBright`[+] Successfully patched!`);
|
|
||||||
|
|
||||||
// Step 12: Open HTTP Toolkit as detached process
|
|
||||||
console.log(chalk.blueBright`[+] Opening HTTP Toolkit...`);
|
|
||||||
try {
|
try {
|
||||||
if (isLinux) {
|
hashes = await captureIntegrityHashes(executablePath);
|
||||||
// Linux: Cannot auto-launch reliably, show manual instructions
|
} catch (e) {
|
||||||
console.log(chalk.yellowBright`[!] HTTP Toolkit has been successfully patched`);
|
rm(extractPath);
|
||||||
console.log(chalk.yellowBright`[!] Please manually launch HTTP Toolkit from your applications menu or using the command:`);
|
console.error(chalk.redBright(`[-] Failed to capture integrity hashes: ${e.message}`));
|
||||||
console.log(chalk.blueBright` httptoolkit`);
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(chalk.greenBright('[+] Integrity hashes captured'));
|
||||||
|
console.log(chalk.white(` Original hash: ${hashes.originalHash}`));
|
||||||
|
console.log(chalk.white(` New asar hash: ${hashes.newHash}`));
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (isMac) {
|
||||||
|
patchInfoPlistHash(appPath, hashes.originalHash, hashes.newHash);
|
||||||
|
console.log(chalk.greenBright('[+] Patched Info.plist hash'));
|
||||||
} else {
|
} else {
|
||||||
const command = isWin ? `"${path.resolve(appPath, "..", "HTTP Toolkit.exe")}"` : isMac ? 'open -a "HTTP Toolkit"' : "httptoolkit";
|
const replacements = patchExecutableHash(executablePath, hashes.originalHash, hashes.newHash);
|
||||||
const child = spawn(command, {
|
console.log(chalk.greenBright(`[+] Patched binary hash (${replacements} replacement${replacements === 1 ? "" : "s"})`));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
rm(extractPath);
|
||||||
|
console.error(chalk.redBright(`[-] Failed to patch hash: ${e.message}`));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(chalk.yellowBright('[+] Cleaning up temporary files...'));
|
||||||
|
rm(extractPath);
|
||||||
|
console.log(chalk.greenBright('[+] Successfully patched!'));
|
||||||
|
|
||||||
|
console.log(chalk.blueBright('[+] Opening HTTP Toolkit...'));
|
||||||
|
try {
|
||||||
|
const child = spawn(executablePath, {
|
||||||
stdio: "ignore",
|
stdio: "ignore",
|
||||||
shell: true,
|
shell: false,
|
||||||
detached: true,
|
detached: true,
|
||||||
});
|
});
|
||||||
child.unref();
|
child.unref();
|
||||||
console.log(chalk.greenBright`[+] HTTP Toolkit launched successfully`);
|
console.log(chalk.greenBright('[+] HTTP Toolkit launched successfully'));
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(chalk.yellowBright`[!] Could not auto-start HTTP Toolkit: ${e.message}`);
|
console.error(chalk.yellowBright(`[!] Could not auto-start HTTP Toolkit: ${e.message}`));
|
||||||
console.log(chalk.blueBright`[+] Please start HTTP Toolkit manually`);
|
console.log(chalk.blueBright('[+] Please start HTTP Toolkit manually'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -465,33 +725,36 @@ const commandName = (() => {
|
|||||||
// Run the appropriate command
|
// Run the appropriate command
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
|
// Check for updates from GitHub
|
||||||
|
await checkForUpdates();
|
||||||
|
|
||||||
if (command === "unpatch" || command === "restore") {
|
if (command === "unpatch" || command === "restore") {
|
||||||
await unpatchApp();
|
await unpatchApp();
|
||||||
} else if (command === "help" || command === "-h" || command === "--help") {
|
} else if (command === "help" || command === "-h" || command === "--help") {
|
||||||
console.log(chalk.blueBright`HTTP Toolkit Patcher`);
|
console.log(chalk.blueBright('HTTP Toolkit Patcher'));
|
||||||
console.log(chalk.white`\nUsage:`);
|
console.log(chalk.white('\nUsage:'));
|
||||||
console.log(chalk.white` ${commandName} [command]`);
|
console.log(chalk.white(` ${commandName} [command]`));
|
||||||
console.log(chalk.white`\nCommands:`);
|
console.log(chalk.white('\nCommands:'));
|
||||||
console.log(chalk.white` patch ${chalk.gray`- Patch HTTP Toolkit (default)`}`);
|
console.log(chalk.white(` patch ${chalk.gray('- Patch HTTP Toolkit (default)')}`));
|
||||||
console.log(chalk.white` unpatch ${chalk.gray`- Restore original HTTP Toolkit from backup`}`);
|
console.log(chalk.white(` unpatch ${chalk.gray('- Restore original HTTP Toolkit from backup')}`));
|
||||||
console.log(chalk.white` restore ${chalk.gray`- Alias for unpatch`}`);
|
console.log(chalk.white(` restore ${chalk.gray('- Alias for unpatch')}`));
|
||||||
console.log(chalk.white` help ${chalk.gray`- Show this help message`}`);
|
console.log(chalk.white(` help ${chalk.gray('- Show this help message')}`));
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
} else if (!command || command === "patch") {
|
} else if (!command || command === "patch") {
|
||||||
// Ask for confirmation before patching
|
// Ask for confirmation before patching
|
||||||
const answer = await prompt("Do you want to patch HTTP Toolkit? [Y/n]: ");
|
const answer = await prompt("Do you want to patch HTTP Toolkit? [Y/n]: ");
|
||||||
if (answer.toLowerCase() === "n" || answer.toLowerCase() === "no") {
|
if (answer.toLowerCase() === "n" || answer.toLowerCase() === "no") {
|
||||||
console.log(chalk.blueBright`[+] Patching cancelled`);
|
console.log(chalk.blueBright('[+] Patching cancelled'));
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
await patchApp();
|
await patchApp();
|
||||||
} else {
|
} else {
|
||||||
console.error(chalk.redBright`[-] Unknown command: ${command}`);
|
console.error(chalk.redBright(`[-] Unknown command: ${command}`));
|
||||||
console.log(chalk.yellowBright`[!] Use 'help' to see available commands`);
|
console.log(chalk.yellowBright('[!] Use \'help\' to see available commands'));
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(chalk.redBright`[-] Error: ${error.message}`);
|
console.error(chalk.redBright(`[-] Error: ${error.message}`));
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -1,42 +1,62 @@
|
|||||||
(function injectPageContextHooks() {
|
(function () {
|
||||||
const injectionCode = `
|
|
||||||
(function() {
|
|
||||||
console.log("[PAGE-INJECT] Installing hooks in page context");
|
console.log("[PAGE-INJECT] Installing hooks in page context");
|
||||||
|
|
||||||
|
// Properties to hook directly on accountStore (still exist as computed properties)
|
||||||
const propertyHooks = {
|
const propertyHooks = {
|
||||||
isPaidUser: true,
|
|
||||||
isLoggedIn: true,
|
isLoggedIn: true,
|
||||||
userHasSubscription: true,
|
|
||||||
userEmail: "hi@httptoolkit.com",
|
userEmail: "hi@httptoolkit.com",
|
||||||
mightBePaidUser: true,
|
mightBePaidUser: true,
|
||||||
isPastDueUser: false,
|
|
||||||
userSubscription: {
|
userSubscription: {
|
||||||
|
state: "fulfilled",
|
||||||
status: "active",
|
status: "active",
|
||||||
plan: "pro",
|
plan: "pro",
|
||||||
sku: "sku",
|
sku: "sku",
|
||||||
tierCode: "pro",
|
tierCode: "pro",
|
||||||
interval: "monthly",
|
interval: "monthly",
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
expiry: new Date("2999-01-01"),
|
expiry: new Date(new Date().setFullYear(new Date().getFullYear() + 10)),
|
||||||
updateBillingDetailsUrl: "https://httptoolkit.com/",
|
updateBillingDetailsUrl: "https://httptoolkit.com/",
|
||||||
cancelSubscriptionUrl: "https://httptoolkit.com/",
|
cancelSubscriptionUrl: "https://httptoolkit.com/",
|
||||||
lastReceiptUrl: "https://httptoolkit.com/",
|
lastReceiptUrl: "https://httptoolkit.com/",
|
||||||
canManageSubscription: true
|
canManageSubscription: true,
|
||||||
}
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Methods to hook on the user object (moved from accountStore properties to user methods in v3.1.0)
|
||||||
|
const userMethodHooks = {
|
||||||
|
isPaidUser: true,
|
||||||
|
isPastDueUser: false,
|
||||||
|
userHasSubscription: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const hookedObjects = new WeakSet();
|
const hookedObjects = new WeakSet();
|
||||||
|
const hookedUsers = new WeakSet();
|
||||||
|
|
||||||
|
// Patch user object methods
|
||||||
|
function patchUserObject(user) {
|
||||||
|
if (!user || typeof user !== "object" || hookedUsers.has(user)) return;
|
||||||
|
|
||||||
|
for (const methodName of Object.keys(userMethodHooks)) {
|
||||||
|
if (typeof user[methodName] === "function") {
|
||||||
|
console.log("[PAGE-INJECT] Patching user." + methodName + "()");
|
||||||
|
user[methodName] = function () {
|
||||||
|
return userMethodHooks[methodName];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hookedUsers.add(user);
|
||||||
|
}
|
||||||
|
|
||||||
// Override Object.defineProperty to intercept all property definitions
|
// Override Object.defineProperty to intercept all property definitions
|
||||||
const originalDefineProperty = Object.defineProperty;
|
const originalDefineProperty = Object.defineProperty;
|
||||||
Object.defineProperty = function(target, prop, descriptor) {
|
Object.defineProperty = function (target, prop, descriptor) {
|
||||||
// Intercept our target properties
|
|
||||||
if (prop in propertyHooks) {
|
if (prop in propertyHooks) {
|
||||||
console.log("[PAGE-INJECT] Intercepting defineProperty for: " + prop);
|
console.log("[PAGE-INJECT] Intercepting defineProperty for: " + prop);
|
||||||
|
|
||||||
if (descriptor && descriptor.get) {
|
if (descriptor && descriptor.get) {
|
||||||
const originalGetter = descriptor.get;
|
const originalGetter = descriptor.get;
|
||||||
descriptor.get = function() {
|
descriptor.get = function () {
|
||||||
const originalValue = originalGetter.call(this);
|
const originalValue = originalGetter.call(this);
|
||||||
console.log("[PAGE-INJECT] " + prop + " getter called, original=" + originalValue + ", returning=" + JSON.stringify(propertyHooks[prop]));
|
console.log("[PAGE-INJECT] " + prop + " getter called, original=" + originalValue + ", returning=" + JSON.stringify(propertyHooks[prop]));
|
||||||
return propertyHooks[prop];
|
return propertyHooks[prop];
|
||||||
@@ -47,18 +67,34 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Intercept the 'user' property to patch its methods when it's set/accessed
|
||||||
|
if (prop === "user") {
|
||||||
|
console.log("[PAGE-INJECT] Intercepting defineProperty for: user");
|
||||||
|
|
||||||
|
if (descriptor && descriptor.get) {
|
||||||
|
const originalGetter = descriptor.get;
|
||||||
|
descriptor.get = function () {
|
||||||
|
const user = originalGetter.call(this);
|
||||||
|
if (user) patchUserObject(user);
|
||||||
|
return user;
|
||||||
|
};
|
||||||
|
} else if (descriptor && descriptor.value !== undefined && descriptor.value) {
|
||||||
|
patchUserObject(descriptor.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return originalDefineProperty.call(this, target, prop, descriptor);
|
return originalDefineProperty.call(this, target, prop, descriptor);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Hook Object.defineProperties too
|
// Hook Object.defineProperties too
|
||||||
const originalDefineProperties = Object.defineProperties;
|
const originalDefineProperties = Object.defineProperties;
|
||||||
Object.defineProperties = function(target, props) {
|
Object.defineProperties = function (target, props) {
|
||||||
for (let prop in props) {
|
for (let prop in props) {
|
||||||
if (prop in propertyHooks) {
|
if (prop in propertyHooks) {
|
||||||
console.log("[PAGE-INJECT] Intercepting defineProperties for: " + prop);
|
console.log("[PAGE-INJECT] Intercepting defineProperties for: " + prop);
|
||||||
if (props[prop].get) {
|
if (props[prop].get) {
|
||||||
const originalGetter = props[prop].get;
|
const originalGetter = props[prop].get;
|
||||||
props[prop].get = function() {
|
props[prop].get = function () {
|
||||||
const originalValue = originalGetter.call(this);
|
const originalValue = originalGetter.call(this);
|
||||||
console.log("[PAGE-INJECT] " + prop + " getter called, original=" + originalValue + ", returning=" + JSON.stringify(propertyHooks[prop]));
|
console.log("[PAGE-INJECT] " + prop + " getter called, original=" + originalValue + ", returning=" + JSON.stringify(propertyHooks[prop]));
|
||||||
return propertyHooks[prop];
|
return propertyHooks[prop];
|
||||||
@@ -67,6 +103,20 @@
|
|||||||
props[prop].value = propertyHooks[prop];
|
props[prop].value = propertyHooks[prop];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (prop === "user") {
|
||||||
|
console.log("[PAGE-INJECT] Intercepting defineProperties for: user");
|
||||||
|
if (props[prop].get) {
|
||||||
|
const originalGetter = props[prop].get;
|
||||||
|
props[prop].get = function () {
|
||||||
|
const user = originalGetter.call(this);
|
||||||
|
if (user) patchUserObject(user);
|
||||||
|
return user;
|
||||||
|
};
|
||||||
|
} else if (props[prop].value !== undefined && props[prop].value) {
|
||||||
|
patchUserObject(props[prop].value);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return originalDefineProperties.call(this, target, props);
|
return originalDefineProperties.call(this, target, props);
|
||||||
};
|
};
|
||||||
@@ -74,17 +124,13 @@
|
|||||||
// Periodically scan and patch existing objects
|
// Periodically scan and patch existing objects
|
||||||
function scanAndPatch() {
|
function scanAndPatch() {
|
||||||
// Search through window and common store locations
|
// Search through window and common store locations
|
||||||
const searchPaths = [
|
const searchPaths = [window, window.accountStore, window.stores && window.stores.accountStore, window.appState && window.appState.accountStore];
|
||||||
window,
|
|
||||||
window.accountStore,
|
|
||||||
window.stores && window.stores.accountStore,
|
|
||||||
window.appState && window.appState.accountStore,
|
|
||||||
];
|
|
||||||
|
|
||||||
searchPaths.forEach((obj, idx) => {
|
searchPaths.forEach((obj, idx) => {
|
||||||
if (!obj || hookedObjects.has(obj)) return;
|
if (!obj || hookedObjects.has(obj)) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Patch direct property hooks
|
||||||
Object.keys(propertyHooks).forEach(prop => {
|
Object.keys(propertyHooks).forEach(prop => {
|
||||||
try {
|
try {
|
||||||
const desc = Object.getOwnPropertyDescriptor(obj, prop);
|
const desc = Object.getOwnPropertyDescriptor(obj, prop);
|
||||||
@@ -93,15 +139,15 @@
|
|||||||
|
|
||||||
if (desc.get) {
|
if (desc.get) {
|
||||||
const originalGetter = desc.get;
|
const originalGetter = desc.get;
|
||||||
Object.defineProperty(obj, prop, {
|
originalDefineProperty(obj, prop, {
|
||||||
get: function() {
|
get: function () {
|
||||||
const originalValue = originalGetter.call(this);
|
const originalValue = originalGetter.call(this);
|
||||||
console.log("[PAGE-INJECT] " + prop + " getter intercepted, original=" + originalValue + ", returning=" + JSON.stringify(propertyHooks[prop]));
|
console.log("[PAGE-INJECT] " + prop + " getter intercepted, original=" + originalValue + ", returning=" + JSON.stringify(propertyHooks[prop]));
|
||||||
return propertyHooks[prop];
|
return propertyHooks[prop];
|
||||||
},
|
},
|
||||||
set: desc.set,
|
set: desc.set,
|
||||||
configurable: true,
|
configurable: true,
|
||||||
enumerable: desc.enumerable
|
enumerable: desc.enumerable,
|
||||||
});
|
});
|
||||||
} else if (desc.writable) {
|
} else if (desc.writable) {
|
||||||
obj[prop] = propertyHooks[prop];
|
obj[prop] = propertyHooks[prop];
|
||||||
@@ -113,6 +159,14 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Patch user object if present
|
||||||
|
try {
|
||||||
|
if (obj.user && typeof obj.user === "object") {
|
||||||
|
console.log("[PAGE-INJECT] Found user object on object #" + idx + ", patching methods...");
|
||||||
|
patchUserObject(obj.user);
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
hookedObjects.add(obj);
|
hookedObjects.add(obj);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Ignore object access errors
|
// Ignore object access errors
|
||||||
@@ -124,7 +178,7 @@
|
|||||||
for (let key in window) {
|
for (let key in window) {
|
||||||
try {
|
try {
|
||||||
const obj = window[key];
|
const obj = window[key];
|
||||||
if (obj && typeof obj === 'object' && 'accountStore' in obj) {
|
if (obj && typeof obj === "object" && "accountStore" in obj) {
|
||||||
console.log("[PAGE-INJECT] Found accountStore in window." + key);
|
console.log("[PAGE-INJECT] Found accountStore in window." + key);
|
||||||
const store = obj.accountStore;
|
const store = obj.accountStore;
|
||||||
if (store && !hookedObjects.has(store)) {
|
if (store && !hookedObjects.has(store)) {
|
||||||
@@ -133,19 +187,28 @@
|
|||||||
const desc = Object.getOwnPropertyDescriptor(store, prop);
|
const desc = Object.getOwnPropertyDescriptor(store, prop);
|
||||||
if (desc && desc.configurable && desc.get) {
|
if (desc && desc.configurable && desc.get) {
|
||||||
const originalGetter = desc.get;
|
const originalGetter = desc.get;
|
||||||
Object.defineProperty(store, prop, {
|
originalDefineProperty(store, prop, {
|
||||||
get: function() {
|
get: function () {
|
||||||
const originalValue = originalGetter.call(this);
|
const originalValue = originalGetter.call(this);
|
||||||
console.log("[PAGE-INJECT] accountStore." + prop + " intercepted, original=" + originalValue + ", returning=" + JSON.stringify(propertyHooks[prop]));
|
console.log("[PAGE-INJECT] accountStore." + prop + " intercepted, original=" + originalValue + ", returning=" + JSON.stringify(propertyHooks[prop]));
|
||||||
return propertyHooks[prop];
|
return propertyHooks[prop];
|
||||||
},
|
},
|
||||||
set: desc.set,
|
set: desc.set,
|
||||||
configurable: true,
|
configurable: true,
|
||||||
enumerable: desc.enumerable
|
enumerable: desc.enumerable,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Patch user methods on the store
|
||||||
|
try {
|
||||||
|
if (store.user && typeof store.user === "object") {
|
||||||
|
console.log("[PAGE-INJECT] Found user object in accountStore, patching methods...");
|
||||||
|
patchUserObject(store.user);
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
hookedObjects.add(store);
|
hookedObjects.add(store);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -163,44 +226,11 @@
|
|||||||
scanCount++;
|
scanCount++;
|
||||||
scanAndPatch();
|
scanAndPatch();
|
||||||
|
|
||||||
if (scanCount >= 10) {
|
if (scanCount >= 50) {
|
||||||
clearInterval(scanInterval);
|
clearInterval(scanInterval);
|
||||||
console.log("[PAGE-INJECT] Stopped periodic scanning after 10 attempts");
|
console.log("[PAGE-INJECT] Stopped periodic scanning after 50 attempts");
|
||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
console.log("[PAGE-INJECT] Hooks installed successfully");
|
console.log("[PAGE-INJECT] Hooks installed successfully");
|
||||||
})();
|
|
||||||
`;
|
|
||||||
|
|
||||||
function injectScript() {
|
|
||||||
try {
|
|
||||||
const script = document.createElement('script');
|
|
||||||
script.textContent = injectionCode;
|
|
||||||
(document.head || document.documentElement).appendChild(script);
|
|
||||||
script.remove(); // Clean up the script tag
|
|
||||||
console.log("[PRELOAD] Injected page context hooks");
|
|
||||||
} catch (e) {
|
|
||||||
console.error("[PRELOAD] Failed to inject hooks:", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to inject immediately if DOM is ready, otherwise wait
|
|
||||||
if (document.documentElement) {
|
|
||||||
injectScript();
|
|
||||||
} else {
|
|
||||||
// Wait for document to be created
|
|
||||||
const observer = new MutationObserver(() => {
|
|
||||||
if (document.documentElement) {
|
|
||||||
observer.disconnect();
|
|
||||||
injectScript();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
observer.observe(document, { childList: true, subtree: true });
|
|
||||||
|
|
||||||
// Also try on DOMContentLoaded as backup
|
|
||||||
if (document.addEventListener) {
|
|
||||||
document.addEventListener('DOMContentLoaded', injectScript, { once: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})();
|
})();
|
||||||
Generated
+128
-148
@@ -1,238 +1,218 @@
|
|||||||
{
|
{
|
||||||
"name": "httptoolkit-patcher",
|
"name": "httptoolkit-patcher",
|
||||||
"version": "2.0.5",
|
"version": "2.0.11",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "httptoolkit-patcher",
|
"name": "httptoolkit-patcher",
|
||||||
"version": "2.0.5",
|
"version": "2.0.11",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron/asar": "^3.2.9",
|
"@electron/asar": "^4.1.0",
|
||||||
"chalk": "4"
|
"chalk": "5.6.2"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"httptoolkit-patcher": "index.js"
|
"httptoolkit-patcher": "index.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.14.9"
|
"@types/node": "^25.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@electron/asar": {
|
"node_modules/@electron/asar": {
|
||||||
"version": "3.4.1",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/@electron/asar/-/asar-4.1.0.tgz",
|
||||||
"integrity": "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==",
|
"integrity": "sha512-DflfXtTFuTYHcyupbhnCWi+hBFdhsWl878NuA1UmW7YVFlqPlY0SHdREVnigEO3zjS2vh4hp1A4dEl3I6mgd9w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"commander": "^5.0.0",
|
"commander": "^13.1.0",
|
||||||
"glob": "^7.1.6",
|
"glob": "^13.0.2",
|
||||||
"minimatch": "^3.0.4"
|
"minimatch": "^10.0.1",
|
||||||
|
"plist": "^3.1.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"asar": "bin/asar.js"
|
"asar": "bin/asar.mjs"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.12.0"
|
"node": ">=22.12.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.19.23",
|
"version": "25.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.23.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz",
|
||||||
"integrity": "sha512-yIdlVVVHXpmqRhtyovZAcSy0MiPcYWGkoO4CGe/+jpP0hmNuihm4XhHbADpK++MsiLHP5MVlv+bcgdF99kSiFQ==",
|
"integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.21.0"
|
"undici-types": "~7.18.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ansi-styles": {
|
"node_modules/@xmldom/xmldom": {
|
||||||
"version": "4.3.0",
|
"version": "0.8.13",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.13.tgz",
|
||||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
"integrity": "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
|
||||||
"color-convert": "^2.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=10.0.0"
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/balanced-match": {
|
"node_modules/balanced-match": {
|
||||||
"version": "1.0.2",
|
"version": "4.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
|
||||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "18 || 20 || >=22"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/base64-js": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||||
|
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/brace-expansion": {
|
"node_modules/brace-expansion": {
|
||||||
"version": "1.1.12",
|
"version": "5.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
|
||||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"balanced-match": "^1.0.0",
|
"balanced-match": "^4.0.2"
|
||||||
"concat-map": "0.0.1"
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "18 || 20 || >=22"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/chalk": {
|
"node_modules/chalk": {
|
||||||
"version": "4.1.2",
|
"version": "5.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
|
||||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
"integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
|
||||||
"ansi-styles": "^4.1.0",
|
|
||||||
"supports-color": "^7.1.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": "^12.17.0 || ^14.13 || >=16.0.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/color-convert": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
|
||||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"color-name": "~1.1.4"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=7.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/color-name": {
|
|
||||||
"version": "1.1.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
|
||||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/commander": {
|
"node_modules/commander": {
|
||||||
"version": "5.1.0",
|
"version": "13.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz",
|
||||||
"integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==",
|
"integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/concat-map": {
|
|
||||||
"version": "0.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
|
||||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/fs.realpath": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
|
||||||
"license": "ISC"
|
|
||||||
},
|
|
||||||
"node_modules/glob": {
|
"node_modules/glob": {
|
||||||
"version": "7.2.3",
|
"version": "13.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz",
|
||||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
"integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==",
|
||||||
"deprecated": "Glob versions prior to v9 are no longer supported",
|
"license": "BlueOak-1.0.0",
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fs.realpath": "^1.0.0",
|
"minimatch": "^10.2.2",
|
||||||
"inflight": "^1.0.4",
|
"minipass": "^7.1.3",
|
||||||
"inherits": "2",
|
"path-scurry": "^2.0.2"
|
||||||
"minimatch": "^3.1.1",
|
|
||||||
"once": "^1.3.0",
|
|
||||||
"path-is-absolute": "^1.0.0"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "*"
|
"node": "18 || 20 || >=22"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/has-flag": {
|
"node_modules/lru-cache": {
|
||||||
"version": "4.0.0",
|
"version": "11.2.7",
|
||||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz",
|
||||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
"integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==",
|
||||||
"license": "MIT",
|
"license": "BlueOak-1.0.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": "20 || >=22"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/inflight": {
|
|
||||||
"version": "1.0.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
|
||||||
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
|
||||||
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"once": "^1.3.0",
|
|
||||||
"wrappy": "1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/inherits": {
|
|
||||||
"version": "2.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
|
||||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
|
||||||
"license": "ISC"
|
|
||||||
},
|
|
||||||
"node_modules/minimatch": {
|
"node_modules/minimatch": {
|
||||||
"version": "3.1.2",
|
"version": "10.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
|
||||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
"integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
|
||||||
"license": "ISC",
|
"license": "BlueOak-1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^5.0.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "*"
|
"node": "18 || 20 || >=22"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/once": {
|
"node_modules/minipass": {
|
||||||
"version": "1.4.0",
|
"version": "7.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
|
||||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
"integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
|
||||||
"license": "ISC",
|
"license": "BlueOak-1.0.0",
|
||||||
"dependencies": {
|
|
||||||
"wrappy": "1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/path-is-absolute": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=16 || 14 >=14.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/supports-color": {
|
"node_modules/path-scurry": {
|
||||||
"version": "7.2.0",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
|
||||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
"integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
|
||||||
|
"license": "BlueOak-1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"lru-cache": "^11.0.0",
|
||||||
|
"minipass": "^7.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "18 || 20 || >=22"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/plist": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"has-flag": "^4.0.0"
|
"@xmldom/xmldom": "^0.8.8",
|
||||||
|
"base64-js": "^1.5.1",
|
||||||
|
"xmlbuilder": "^15.1.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=10.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "6.21.0",
|
"version": "7.18.2",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
|
||||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/wrappy": {
|
"node_modules/xmlbuilder": {
|
||||||
"version": "1.0.2",
|
"version": "15.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",
|
||||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
"integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==",
|
||||||
"license": "ISC"
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-4
@@ -1,10 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "httptoolkit-patcher",
|
"name": "httptoolkit-patcher",
|
||||||
"version": "2.0.5",
|
"version": "2.0.11",
|
||||||
"description": "HTTP Toolkit Pro Patcher",
|
"description": "HTTP Toolkit Pro Patcher",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": "index.js",
|
"bin": "index.js",
|
||||||
|
"author": "xenos1337",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node index.js",
|
"start": "node index.js",
|
||||||
"patch": "node index.js patch",
|
"patch": "node index.js patch",
|
||||||
@@ -13,10 +14,10 @@
|
|||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron/asar": "^3.2.9",
|
"@electron/asar": "^4.1.0",
|
||||||
"chalk": "4"
|
"chalk": "5.6.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.14.9"
|
"@types/node": "^25.5.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user