8 Commits
5 changed files with 479 additions and 281 deletions
-4
View File
@@ -20,10 +20,6 @@ The patcher intercepts HTTP Toolkit's authentication functions:
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
1. Install Node.js (if not already installed)
+306 -67
View File
@@ -23,6 +23,18 @@ const __filename = (() => {
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
function isElevated() {
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) => {
const options = {
headers: {
"User-Agent": "httptoolkit-patcher",
Accept: "application/vnd.github.v3+json",
},
};
https
.get(url, res => {
.get(url, options, res => {
if (res.statusCode !== 200) {
reject(new Error(`HTTP ${res.statusCode}`));
return;
}
let data = "";
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);
});
}
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
function rm(dirPath) {
if (!fs.existsSync(dirPath)) return;
@@ -78,7 +150,11 @@ function rm(dirPath) {
// Find HTTP Toolkit installation path
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) {
if (fs.existsSync(path.join(p, "app.asar"))) {
@@ -237,18 +313,130 @@ async function killHttpToolkit() {
}
}
// 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;
}
// Unpatch/restore function
async function unpatchApp() {
console.log(chalk.blueBright`[+] HTTP Toolkit Unpatcher Started`);
// Step 1: Find app path
const appPath = await findAppPath();
console.log(chalk.greenBright`[+] HTTP Toolkit found at ${appPath}`);
// Step 2: Kill HTTP Toolkit if running
await killHttpToolkit();
// Step 3: Check permissions
const asarPath = path.join(appPath, "app.asar");
const backupPath = path.join(appPath, "app.asar.bak");
const extractPath = path.join(appPath, "app.asar_extracted");
@@ -268,14 +456,12 @@ async function unpatchApp() {
await requestElevation();
}
// Step 4: Check if backup exists
if (!fs.existsSync(backupPath)) {
console.error(chalk.redBright`[-] Backup file not found at ${backupPath}`);
console.error(chalk.redBright`[-] Cannot unpatch without backup file`);
process.exit(1);
}
// Step 5: Restore from backup
console.log(chalk.yellowBright`[+] Restoring from backup...`);
try {
fs.copyFileSync(backupPath, asarPath);
@@ -285,14 +471,12 @@ async function unpatchApp() {
process.exit(1);
}
// Step 6: Clean up extracted files if they exist
if (fs.existsSync(extractPath)) {
console.log(chalk.yellowBright`[+] Removing extracted files...`);
rm(extractPath);
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): ");
if (removeBackup.toLowerCase() === "y" || removeBackup.toLowerCase() === "yes") {
fs.rmSync(backupPath, { force: true });
@@ -306,14 +490,11 @@ async function unpatchApp() {
async function patchApp() {
console.log(chalk.blueBright`[+] HTTP Toolkit Patcher Started`);
// Step 1: Find app path
const appPath = await findAppPath();
console.log(chalk.greenBright`[+] HTTP Toolkit found at ${appPath}`);
// Step 2: Kill HTTP Toolkit if running
await killHttpToolkit();
// Step 3: Check permissions
const asarPath = path.join(appPath, "app.asar");
// Check if we have write permissions on both the directory and the file
@@ -331,7 +512,6 @@ async function patchApp() {
await requestElevation();
}
// Step 4: Backup app.asar
const backupPath = path.join(appPath, "app.asar.bak");
if (!fs.existsSync(backupPath)) {
console.log(chalk.yellowBright`[+] Creating backup...`);
@@ -339,44 +519,77 @@ async function patchApp() {
console.log(chalk.greenBright`[+] Backup created at ${backupPath}`);
}
// Step 5: Extract app.asar
const extractPath = path.join(appPath, "app.asar_extracted");
console.log(chalk.yellowBright`[+] Extracting app.asar...`);
rm(extractPath);
asar.extractAll(asarPath, extractPath);
console.log(chalk.greenBright`[+] Extracted to ${extractPath}`);
// Step 6: Check if preload.cjs or preload.js exists
let preloadPath = path.join(extractPath, "build", "preload.cjs");
const preloadPath = path.join(extractPath, "build", "preload.cjs");
if (!fs.existsSync(preloadPath)) {
console.log(chalk.yellowBright`[!] preload.cjs not found, checking for preload.js...`);
preloadPath = path.join(extractPath, "build", "preload.js");
if (!fs.existsSync(preloadPath)) {
console.error(chalk.redBright`[-] Neither preload.cjs nor preload.js found in ${path.join(extractPath, "build")}`);
console.error(chalk.redBright`[-] preload.cjs not found in ${path.join(extractPath, "build")}`);
console.error(chalk.yellowBright`[!] Please download the latest version of HTTP Toolkit from https://httptoolkit.com/`);
rm(extractPath);
process.exit(1);
}
console.log(chalk.greenBright`[+] Found preload.js`);
} else {
console.log(chalk.greenBright`[+] Found preload.cjs`);
}
// Step 7: Fetch inject.js from GitHub
console.log(chalk.yellowBright`[+] Fetching inject code from GitHub...`);
const injectCode = await fetchUrl("https://raw.githubusercontent.com/xenos1337/httptoolkit-patcher/refs/heads/master/inject.js");
if (!injectCode || !injectCode.includes("injectPageContextHooks")) {
console.error(chalk.redBright`[-] Failed to fetch inject.js from GitHub`);
console.log(chalk.yellowBright`[+] Reading inject code from local file...`);
const injectJsPath = path.join(path.dirname(__filename), "inject.js");
if (!fs.existsSync(injectJsPath)) {
console.error(chalk.redBright`[-] inject.js not found at ${injectJsPath}`);
rm(extractPath);
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");
const isPatched = preloadContent.includes("injectPageContextHooks");
if (isPatched) {
console.log(chalk.yellowBright`[!] File already patched`);
const electronVarName = preloadContent.includes("electron_1") ? "electron_1" : "electron";
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): ");
if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
@@ -385,65 +598,88 @@ async function patchApp() {
process.exit(0);
}
// Replace the existing injectPageContextHooks function
console.log(chalk.yellowBright`[+] Replacing existing patch...`);
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;
// Remove existing patches
console.log(chalk.yellowBright`[+] Replacing existing patches...`);
for (let i = 0; i < lines.length; i++) {
if (lines[i].includes("electron_1")) {
insertIndex = i + 1;
// Remove preload patch
const preloadPatchRegex = /\n?\(function loadInjectScript\(\) \{[\s\S]*?\}\)\(\);/;
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;
}
}
if (insertIndex === -1) {
console.error(chalk.redBright`[-] Could not find insertion point (electron_1) in ${path.basename(preloadPath)}`);
if (preloadInsertIndex === -1) {
console.error(chalk.redBright`[-] Could not find insertion point (electron import) in ${path.basename(preloadPath)}`);
rm(extractPath);
process.exit(1);
}
lines.splice(insertIndex, 0, injectCode);
preloadContent = lines.join("\n");
}
preloadLines.splice(preloadInsertIndex, 0, preloadPatchCode);
preloadContent = preloadLines.join("\n");
// Step 9: Write patched preload file
// Write patched preload file
fs.writeFileSync(preloadPath, preloadContent, "utf-8");
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
let executablePath;
try {
executablePath = getExecutablePath(appPath);
} catch (e) {
rm(extractPath);
console.error(chalk.redBright`[-] ${e.message}`);
process.exit(1);
}
console.log(chalk.yellowBright`[+] Launching HTTP Toolkit to read integrity hashes...`);
let hashes;
try {
hashes = await captureIntegrityHashes(executablePath);
} catch (e) {
rm(extractPath);
console.error(chalk.redBright`[-] Failed to capture integrity hashes: ${e.message}`);
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 {
const replacements = patchExecutableHash(executablePath, hashes.originalHash, hashes.newHash);
console.log(chalk.greenBright`[+] Patched binary hash (${replacements} replacement${replacements === 1 ? "" : "s"})`);
} catch (e) {
rm(extractPath);
console.error(chalk.redBright`[-] Failed to patch binary hash: ${e.message}`);
process.exit(1);
}
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 {
if (isLinux) {
// Linux: Cannot auto-launch reliably, show manual instructions
console.log(chalk.yellowBright`[!] HTTP Toolkit has been successfully patched`);
console.log(chalk.yellowBright`[!] Please manually launch HTTP Toolkit from your applications menu or using the command:`);
console.log(chalk.blueBright` httptoolkit`);
} else {
const command = isWin ? `"${path.resolve(appPath, "..", "HTTP Toolkit.exe")}"` : isMac ? 'open -a "HTTP Toolkit"' : "httptoolkit";
const child = spawn(command, {
const child = spawn(executablePath, {
stdio: "ignore",
shell: true,
shell: false,
detached: true,
});
child.unref();
console.log(chalk.greenBright`[+] HTTP Toolkit launched successfully`);
}
} catch (e) {
console.error(chalk.yellowBright`[!] Could not auto-start HTTP Toolkit: ${e.message}`);
console.log(chalk.blueBright`[+] Please start HTTP Toolkit manually`);
@@ -465,6 +701,9 @@ const commandName = (() => {
// Run the appropriate command
(async () => {
try {
// Check for updates from GitHub
await checkForUpdates();
if (command === "unpatch" || command === "restore") {
await unpatchApp();
} else if (command === "help" || command === "-h" || command === "--help") {
+17 -55
View File
@@ -1,6 +1,4 @@
(function injectPageContextHooks() {
const injectionCode = `
(function() {
(function () {
console.log("[PAGE-INJECT] Installing hooks in page context");
const propertyHooks = {
@@ -10,33 +8,35 @@
userEmail: "hi@httptoolkit.com",
mightBePaidUser: true,
isPastDueUser: false,
isStatusUnexpired: true,
userSubscription: {
state: "fulfilled",
status: "active",
plan: "pro",
sku: "sku",
tierCode: "pro",
interval: "monthly",
quantity: 1,
expiry: new Date("2999-01-01"),
expiry: new Date(new Date().setFullYear(new Date().getFullYear() + 10)),
updateBillingDetailsUrl: "https://httptoolkit.com/",
cancelSubscriptionUrl: "https://httptoolkit.com/",
lastReceiptUrl: "https://httptoolkit.com/",
canManageSubscription: true
}
canManageSubscription: true,
},
};
const hookedObjects = new WeakSet();
// Override Object.defineProperty to intercept all property definitions
const originalDefineProperty = Object.defineProperty;
Object.defineProperty = function(target, prop, descriptor) {
Object.defineProperty = function (target, prop, descriptor) {
// Intercept our target properties
if (prop in propertyHooks) {
console.log("[PAGE-INJECT] Intercepting defineProperty for: " + prop);
if (descriptor && descriptor.get) {
const originalGetter = descriptor.get;
descriptor.get = function() {
descriptor.get = function () {
const originalValue = originalGetter.call(this);
console.log("[PAGE-INJECT] " + prop + " getter called, original=" + originalValue + ", returning=" + JSON.stringify(propertyHooks[prop]));
return propertyHooks[prop];
@@ -52,13 +52,13 @@
// Hook Object.defineProperties too
const originalDefineProperties = Object.defineProperties;
Object.defineProperties = function(target, props) {
Object.defineProperties = function (target, props) {
for (let prop in props) {
if (prop in propertyHooks) {
console.log("[PAGE-INJECT] Intercepting defineProperties for: " + prop);
if (props[prop].get) {
const originalGetter = props[prop].get;
props[prop].get = function() {
props[prop].get = function () {
const originalValue = originalGetter.call(this);
console.log("[PAGE-INJECT] " + prop + " getter called, original=" + originalValue + ", returning=" + JSON.stringify(propertyHooks[prop]));
return propertyHooks[prop];
@@ -74,12 +74,7 @@
// Periodically scan and patch existing objects
function scanAndPatch() {
// Search through window and common store locations
const searchPaths = [
window,
window.accountStore,
window.stores && window.stores.accountStore,
window.appState && window.appState.accountStore,
];
const searchPaths = [window, window.accountStore, window.stores && window.stores.accountStore, window.appState && window.appState.accountStore];
searchPaths.forEach((obj, idx) => {
if (!obj || hookedObjects.has(obj)) return;
@@ -94,14 +89,14 @@
if (desc.get) {
const originalGetter = desc.get;
Object.defineProperty(obj, prop, {
get: function() {
get: function () {
const originalValue = originalGetter.call(this);
console.log("[PAGE-INJECT] " + prop + " getter intercepted, original=" + originalValue + ", returning=" + JSON.stringify(propertyHooks[prop]));
return propertyHooks[prop];
},
set: desc.set,
configurable: true,
enumerable: desc.enumerable
enumerable: desc.enumerable,
});
} else if (desc.writable) {
obj[prop] = propertyHooks[prop];
@@ -124,7 +119,7 @@
for (let key in window) {
try {
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);
const store = obj.accountStore;
if (store && !hookedObjects.has(store)) {
@@ -134,14 +129,14 @@
if (desc && desc.configurable && desc.get) {
const originalGetter = desc.get;
Object.defineProperty(store, prop, {
get: function() {
get: function () {
const originalValue = originalGetter.call(this);
console.log("[PAGE-INJECT] accountStore." + prop + " intercepted, original=" + originalValue + ", returning=" + JSON.stringify(propertyHooks[prop]));
return propertyHooks[prop];
},
set: desc.set,
configurable: true,
enumerable: desc.enumerable
enumerable: desc.enumerable,
});
}
} catch (e) {}
@@ -163,44 +158,11 @@
scanCount++;
scanAndPatch();
if (scanCount >= 10) {
if (scanCount >= 50) {
clearInterval(scanInterval);
console.log("[PAGE-INJECT] Stopped periodic scanning after 10 attempts");
}
}, 100);
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 });
}
}
})();
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "httptoolkit-patcher",
"version": "2.0.5",
"version": "2.0.10",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "httptoolkit-patcher",
"version": "2.0.5",
"version": "2.0.10",
"license": "MIT",
"dependencies": {
"@electron/asar": "^3.2.9",
+2 -1
View File
@@ -1,10 +1,11 @@
{
"name": "httptoolkit-patcher",
"version": "2.0.5",
"version": "2.0.10",
"description": "HTTP Toolkit Pro Patcher",
"main": "index.js",
"type": "module",
"bin": "index.js",
"author": "xenos1337",
"scripts": {
"start": "node index.js",
"patch": "node index.js patch",