Initial Commit

Signed-off-by: xenos <66328734+xenos1337@users.noreply.github.com>
This commit is contained in:
xenos
2025-10-23 20:31:50 +02:00
commit 5327edc7fe
8 changed files with 3178 additions and 0 deletions
+120
View File
@@ -0,0 +1,120 @@
name: Release Build
on:
push:
tags:
- 'v*'
workflow_dispatch:
permissions:
contents: write
jobs:
build:
name: Build ${{ matrix.os }}
runs-on: ${{ matrix.runner }}
strategy:
matrix:
include:
- os: linux
runner: ubuntu-latest
targets: node18-linux-x64,node18-linux-arm64
- os: macos
runner: macos-latest
targets: node18-macos-x64,node18-macos-arm64
- os: windows
runner: windows-latest
targets: node18-win-x64
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Create dist directory
run: mkdir -p dist
- name: Build for ${{ matrix.os }}
run: |
npm run build
npx pkg dist/bundle.cjs --targets ${{ matrix.targets }} --compress GZip --out-path dist/
- name: List built files (debug - Unix)
if: matrix.os != 'windows'
run: ls -lah dist/
- name: List built files (debug - Windows)
if: matrix.os == 'windows'
shell: pwsh
run: Get-ChildItem -Path dist/ | Format-Table -AutoSize
- name: Rename executables (Linux)
if: matrix.os == 'linux'
run: |
[ -f dist/bundle-x64 ] && mv dist/bundle-x64 dist/httptoolkit-patcher-linux-x64
[ -f dist/bundle-arm64 ] && mv dist/bundle-arm64 dist/httptoolkit-patcher-linux-arm64
rm -f dist/bundle dist/bundle.cjs 2>/dev/null || true
- name: Rename executables (macOS)
if: matrix.os == 'macos'
run: |
[ -f dist/bundle-x64 ] && mv dist/bundle-x64 dist/httptoolkit-patcher-macos-x64
[ -f dist/bundle-arm64 ] && mv dist/bundle-arm64 dist/httptoolkit-patcher-macos-arm64
rm -f dist/bundle dist/bundle.cjs 2>/dev/null || true
- name: Rename executables (Windows)
if: matrix.os == 'windows'
shell: pwsh
run: |
if (Test-Path dist/bundle.exe) { Move-Item -Force dist/bundle.exe dist/httptoolkit-patcher.exe }
if (Test-Path dist/bundle-x64.exe) { Move-Item -Force dist/bundle-x64.exe dist/httptoolkit-patcher-win-x64.exe }
Remove-Item -Force dist/bundle.cjs -ErrorAction SilentlyContinue
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: binaries-${{ matrix.os }}
path: dist/*
release:
name: Create Release
needs: build
runs-on: ubuntu-latest
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
pattern: binaries-*
path: artifacts
merge-multiple: true
- name: Prepare release files
run: |
mkdir -p release
cp artifacts/* release/ 2>/dev/null || find artifacts -type f -exec cp {} release/ \;
ls -lah release/
- name: Extract version from tag
id: get_version
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
- name: Create Release
uses: softprops/action-gh-release@v1
with:
files: release/*
draft: false
prerelease: false
generate_release_notes: true
name: Release ${{ steps.get_version.outputs.VERSION }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+6
View File
@@ -0,0 +1,6 @@
yarn.lock
node_modules
.yarn
.yarnrc.yml
dist/
release/
+22
View File
@@ -0,0 +1,22 @@
MIT License
Copyright (c) 2025 xenos1337
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+135
View File
@@ -0,0 +1,135 @@
# HTTP Toolkit Patcher
A minimal, cross-platform patcher for HTTP Toolkit that removes subscription requirements.
![WindowsTerminal_BsiVsdyUoj](https://github.com/user-attachments/assets/f21dda33-9334-4b6d-b8ae-6cd51937b3cd)
## Why?
I don't feel like paying a **monthly subscription** for an HTTP proxy/interceptor. A lifetime license? Sure. But subscription-based for a dev tool? No thanks.
## How It Works
The patcher intercepts HTTP Toolkit's authentication functions:
- `isPaidUser`
- `isLoggedIn`
- `userHasSubscription`
- `userEmail`
- `mightBePaidUser`
- `isPastDueUser`
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 dependencies:
```bash
npm install
```
## Building
Build standalone executables for all platforms and architectures:
```bash
npm run build
```
This creates self-contained executables in the `dist/` directory:
**Windows:**
- `httptoolkit-patcher-win-x64.exe` (64-bit)
**Linux:**
- `httptoolkit-patcher-linux-x64` (x86_64/AMD64)
- `httptoolkit-patcher-linux-arm64` (ARM64/AArch64)
**macOS:**
- `httptoolkit-patcher-macos-x64` (Intel)
- `httptoolkit-patcher-macos-arm64` (Apple Silicon M Chip)
**Note:** These are standalone executables created with `pkg` that include Node.js runtime and all dependencies. No need to install Node.js separately!
## Usage
### From Source
**Patch:**
```bash
npm start
```
**Unpatch:**
```bash
npm run unpatch
```
### Using Prebuilt Executables
Download the appropriate executable for your platform from [Releases](https://github.com/xenos1337/httptoolkit-patcher/releases), then:
**Windows:**
```cmd
httptoolkit-patcher-win-x64.exe
```
**Linux/macOS:**
```bash
chmod +x httptoolkit-patcher-linux-x64 # or your architecture
./httptoolkit-patcher-linux-x64
```
That's it. The patcher handles everything automatically.
## Technical Details
1. Finds HTTP Toolkit installation
2. Kills running processes
3. Requests elevation if needed
4. Backs up `app.asar`
5. Extracts and patches `preload.js`
6. Repackages and launches
## Troubleshooting
**Permission errors?** Run as admin/sudo or let the patcher request elevation.
**Already patched?** The patcher will ask if you want to repatch.
**Want to restore?** Run `npm run unpatch` to restore from backup.
**Anything else?** Open an issue on the [GitHub repository](https://github.com/xenos1337/httptoolkit-patcher/issues).
## GitHub Release Workflow
This project includes an automated GitHub Actions workflow that builds and releases the patcher for all platforms.
### Creating a Release
1. Create and push a version tag:
```bash
git tag v2.0.1
git push origin v2.0.1
```
2. The GitHub Actions workflow will automatically:
- Build for all platforms and architectures (Windows x64, Linux x64/ARM64, macOS Intel/Apple Silicon)
- Create standalone native executables using pkg
- Create a GitHub release with all binaries as downloadable artifacts
### Manual Workflow Trigger
You can also manually trigger the release workflow from the GitHub Actions tab.
## Disclaimer
This tool is provided as-is. Use at your own risk. For educational purposes only.
## License
MIT License - see [LICENSE](LICENSE) file.
+454
View File
@@ -0,0 +1,454 @@
// @ts-check
import { spawn, execSync } from "child_process";
import asar from "@electron/asar";
import chalk from "chalk";
import path from "path";
import fs from "fs";
import readline from "readline";
import https from "https";
import { fileURLToPath } from "url";
const isWin = process.platform === "win32";
const isMac = process.platform === "darwin";
const isLinux = process.platform === "linux";
// Handle both ESM and pkg-bundled scenarios
const __filename = (() => {
// Check if running as pkg bundle
if (process.pkg) {
return process.execPath;
}
// Check if import.meta.url exists (ESM)
if (typeof import.meta !== 'undefined' && import.meta.url) {
return fileURLToPath(import.meta.url);
}
// Fallback to __filename if in CommonJS
return typeof __filename !== 'undefined' ? __filename : process.argv[1];
})();
// Check if running with elevated privileges
const isElevated = () => {
if (isWin) {
try {
execSync("net session", { stdio: "ignore" });
return true;
} catch {
return false;
}
} else {
return process.getuid && process.getuid() === 0;
}
};
// Helper function to prompt user for input
const prompt = question => {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
return new Promise(resolve => {
rl.question(question, answer => {
rl.close();
resolve(answer);
});
});
};
// Helper function to fetch content from URL
const fetchUrl = url => {
return new Promise((resolve, reject) => {
https
.get(url, res => {
let data = "";
res.on("data", chunk => (data += chunk));
res.on("end", () => resolve(data));
})
.on("error", reject);
});
};
// Helper function to remove directory recursively
const rm = dirPath => {
if (!fs.existsSync(dirPath)) return;
if (!fs.lstatSync(dirPath).isDirectory()) return fs.rmSync(dirPath, { force: true });
for (const entry of fs.readdirSync(dirPath)) {
const entryPath = path.join(dirPath, entry);
if (fs.lstatSync(entryPath).isDirectory()) rm(entryPath);
else fs.rmSync(entryPath, { force: true });
}
fs.rmdirSync(dirPath);
};
// Find HTTP Toolkit installation path
const findAppPath = async () => {
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"];
for (const p of possiblePaths) {
if (fs.existsSync(path.join(p, "app.asar"))) {
return p;
}
}
console.log(chalk.yellowBright`[!] HTTP Toolkit not found in default locations`);
const userPath = await prompt("Please enter the path to HTTP Toolkit executable/app: ");
if (!userPath) {
console.error(chalk.redBright`[-] No path provided`);
process.exit(1);
}
// Extract resources path from user input
let resourcesPath = userPath.trim().replace(/['"]/g, "");
if (resourcesPath.endsWith(".exe") || resourcesPath.endsWith(".app")) resourcesPath = path.dirname(resourcesPath);
if (!resourcesPath.endsWith("resources")) resourcesPath = path.join(resourcesPath, "resources");
if (!fs.existsSync(path.join(resourcesPath, "app.asar"))) {
console.error(chalk.redBright`[-] app.asar not found at ${resourcesPath}`);
process.exit(1);
}
return resourcesPath;
};
// Request elevated permissions
const requestElevation = async () => {
console.log(chalk.yellowBright`[!] Requesting elevated permissions...`);
if (isWin) {
// Windows: Use PowerShell to run as administrator
const script = `Start-Process -FilePath "node" -ArgumentList '"${__filename}"' -Verb RunAs`;
try {
spawn("powershell", ["-Command", script], {
stdio: "inherit",
shell: true,
});
console.log(chalk.blueBright`[+] Restarting with administrator privileges...`);
process.exit(0);
} catch (e) {
console.error(chalk.redBright`[-] Failed to elevate permissions: ${e.message}`);
console.error(chalk.redBright`[-] Please run as administrator manually`);
process.exit(1);
}
} else {
console.log(chalk.blueBright`[+] Restarting with sudo...`);
try {
const child = spawn("sudo", ["node", __filename], {
stdio: "inherit",
});
child.on("exit", code => process.exit(code || 0));
} catch (e) {
console.error(chalk.redBright`[-] Failed to elevate permissions: ${e.message}`);
console.error(chalk.redBright`[-] Please run with sudo manually`);
process.exit(1);
}
}
};
// Check if we have write permissions
const checkPermissions = filePath => {
try {
fs.accessSync(filePath, fs.constants.W_OK);
return true;
} catch (e) {
return false;
}
};
// Kill HTTP Toolkit processes
const killHttpToolkit = async () => {
console.log(chalk.yellowBright`[+] Checking for running HTTP Toolkit processes...`);
try {
if (isWin) {
// 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" });
if (output.includes("HTTP Toolkit.exe")) {
console.log(chalk.yellowBright`[!] HTTP Toolkit is running, attempting to close it...`);
execSync('taskkill /F /IM "HTTP Toolkit.exe" /T', { stdio: "ignore" });
console.log(chalk.greenBright`[+] HTTP Toolkit processes terminated`);
// Wait a moment for the process to fully close
await new Promise(resolve => setTimeout(resolve, 2000));
} else {
console.log(chalk.greenBright`[+] HTTP Toolkit is not running`);
}
} else if (isMac) {
// macOS: Use pgrep and pkill
try {
execSync('pgrep -f "HTTP Toolkit"', { stdio: "ignore" });
console.log(chalk.yellowBright`[!] HTTP Toolkit is running, attempting to close it...`);
execSync('pkill -f "HTTP Toolkit"', { stdio: "ignore" });
console.log(chalk.greenBright`[+] HTTP Toolkit processes terminated`);
await new Promise(resolve => setTimeout(resolve, 2000));
} catch (e) {
console.log(chalk.greenBright`[+] HTTP Toolkit is not running`);
}
} else {
// Linux: Use pgrep and pkill
try {
execSync('pgrep -f "HTTP Toolkit"', { stdio: "ignore" });
console.log(chalk.yellowBright`[!] HTTP Toolkit is running, attempting to close it...`);
execSync('pkill -f "HTTP Toolkit"', { stdio: "ignore" });
console.log(chalk.greenBright`[+] HTTP Toolkit processes terminated`);
await new Promise(resolve => setTimeout(resolve, 2000));
} catch (e) {
console.log(chalk.greenBright`[+] HTTP Toolkit is not running`);
}
}
} catch (e) {
console.log(chalk.yellowBright`[!] Could not check/kill processes: ${e.message}`);
console.log(chalk.yellowBright`[!] If HTTP Toolkit is running, please close it manually`);
}
};
// Unpatch/restore function
const unpatchApp = async () => {
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");
// Check if we have write permissions on both the directory and the file
const hasPermissions = checkPermissions(appPath) && checkPermissions(asarPath);
if (!hasPermissions) {
console.log(chalk.yellowBright`[!] No write permissions for ${appPath}`);
if (isElevated()) {
console.error(chalk.redBright`[-] Still no permissions even with elevated privileges`);
process.exit(1);
}
console.log(chalk.yellowBright`[!] Administrator/sudo privileges required for unpatching`);
const answer = await prompt("Do you want to request elevated permissions? [Y/n]: ");
if (!answer || answer.toLowerCase() === "y" || answer.toLowerCase() === "yes") {
await requestElevation();
} else {
console.log(chalk.redBright`[-] Cannot proceed without write permissions`);
process.exit(1);
}
}
// 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);
console.log(chalk.greenBright`[+] Restored app.asar from backup`);
} catch (e) {
console.error(chalk.redBright`[-] Failed to restore backup: ${e.message}`);
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 });
console.log(chalk.greenBright`[+] Backup file removed`);
}
console.log(chalk.greenBright`[+] Successfully unpatched!`);
};
// Main patching function
const patchApp = async () => {
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
const hasPermissions = checkPermissions(appPath) && checkPermissions(asarPath);
if (!hasPermissions) {
console.log(chalk.yellowBright`[!] No write permissions for ${appPath}`);
if (isElevated()) {
console.error(chalk.redBright`[-] Still no permissions even with elevated privileges`);
process.exit(1);
}
console.log(chalk.yellowBright`[!] Administrator/sudo privileges required for patching`);
const answer = await prompt("Do you want to request elevated permissions? [Y/n]: ");
if (!answer || answer.toLowerCase() === "y" || answer.toLowerCase() === "yes") {
await requestElevation();
} else {
console.log(chalk.redBright`[-] Cannot proceed without write permissions`);
process.exit(1);
}
}
// Step 4: Backup app.asar
const backupPath = path.join(appPath, "app.asar.bak");
if (!fs.existsSync(backupPath)) {
console.log(chalk.yellowBright`[+] Creating backup...`);
fs.copyFileSync(asarPath, backupPath);
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.js exists
const preloadPath = path.join(extractPath, "build", "preload.js");
if (!fs.existsSync(preloadPath)) {
console.error(chalk.redBright`[-] preload.js not found at ${preloadPath}`);
rm(extractPath);
process.exit(1);
}
// 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`);
rm(extractPath);
process.exit(1);
}
console.log(chalk.greenBright`[+] Inject code fetched successfully`);
// Step 8: Read preload.js 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 answer = await prompt("Do you want to repatch? (y/n): ");
if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
console.log(chalk.blueBright`[+] Patching cancelled`);
rm(extractPath);
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;
for (let i = 0; i < lines.length; i++) {
if (lines[i].includes("electron_1")) {
insertIndex = i + 1;
break;
}
}
if (insertIndex === -1) {
console.error(chalk.redBright`[-] Could not find insertion point (electron_1) in preload.js`);
rm(extractPath);
process.exit(1);
}
lines.splice(insertIndex, 0, injectCode);
preloadContent = lines.join("\n");
}
// Step 9: Write patched preload.js
fs.writeFileSync(preloadPath, preloadContent, "utf-8");
console.log(chalk.greenBright`[+] preload.js 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 {
const command = isWin ? `"${path.resolve(appPath, "..", "HTTP Toolkit.exe")}"` : isMac ? 'open -a "HTTP Toolkit"' : "httptoolkit";
const child = spawn(command, {
stdio: "ignore",
shell: true,
detached: true,
});
child.unref(); // Completely detach the child process
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`);
}
};
// Parse command line arguments
const args = process.argv.slice(2);
const command = args[0];
// Get the command name for display
const commandName = process.pkg ? path.basename(__filename) : 'node index.js';
// Run the appropriate command
(async () => {
try {
if (command === "unpatch" || command === "restore") {
await unpatchApp();
} else if (command === "help" || command === "-h" || command === "--help") {
console.log(chalk.blueBright`HTTP Toolkit Patcher`);
console.log(chalk.white`\nUsage:`);
console.log(chalk.white` ${commandName} [command]`);
console.log(chalk.white`\nCommands:`);
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` restore ${chalk.gray`- Alias for unpatch`}`);
console.log(chalk.white` help ${chalk.gray`- Show this help message`}`);
process.exit(0);
} else if (!command || command === "patch") {
// Ask for confirmation before patching
const answer = await prompt("Do you want to patch HTTP Toolkit? [Y/n]: ");
if (answer.toLowerCase() === "n" || answer.toLowerCase() === "no") {
console.log(chalk.blueBright`[+] Patching cancelled`);
process.exit(0);
}
await patchApp();
} else {
console.error(chalk.redBright`[-] Unknown command: ${command}`);
console.log(chalk.yellowBright`[!] Use 'help' to see available commands`);
process.exit(1);
}
} catch (error) {
console.error(chalk.redBright`[-] Error: ${error.message}`);
process.exit(1);
}
})();
+206
View File
@@ -0,0 +1,206 @@
(function injectPageContextHooks() {
const injectionCode = `
(function() {
console.log("[PAGE-INJECT] Installing hooks in page context");
const propertyHooks = {
isPaidUser: true,
isLoggedIn: true,
userHasSubscription: true,
userEmail: "hi@httptoolkit.com",
mightBePaidUser: true,
isPastDueUser: false,
userSubscription: {
status: "active",
plan: "pro",
sku: "sku",
tierCode: "pro",
interval: "monthly",
quantity: 1,
expiry: new Date("2999-01-01"),
updateBillingDetailsUrl: "https://httptoolkit.com/",
cancelSubscriptionUrl: "https://httptoolkit.com/",
lastReceiptUrl: "https://httptoolkit.com/",
canManageSubscription: true
}
};
const hookedObjects = new WeakSet();
// Override Object.defineProperty to intercept all property definitions
const originalDefineProperty = Object.defineProperty;
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() {
const originalValue = originalGetter.call(this);
console.log("[PAGE-INJECT] " + prop + " getter called, original=" + originalValue + ", returning=" + JSON.stringify(propertyHooks[prop]));
return propertyHooks[prop];
};
} else if (descriptor && descriptor.value !== undefined) {
console.log("[PAGE-INJECT] " + prop + " value being defined, overriding to " + JSON.stringify(propertyHooks[prop]));
descriptor.value = propertyHooks[prop];
}
}
return originalDefineProperty.call(this, target, prop, descriptor);
};
// Hook Object.defineProperties too
const originalDefineProperties = Object.defineProperties;
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() {
const originalValue = originalGetter.call(this);
console.log("[PAGE-INJECT] " + prop + " getter called, original=" + originalValue + ", returning=" + JSON.stringify(propertyHooks[prop]));
return propertyHooks[prop];
};
} else if (props[prop].value !== undefined) {
props[prop].value = propertyHooks[prop];
}
}
}
return originalDefineProperties.call(this, target, props);
};
// 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,
];
searchPaths.forEach((obj, idx) => {
if (!obj || hookedObjects.has(obj)) return;
try {
Object.keys(propertyHooks).forEach(prop => {
try {
const desc = Object.getOwnPropertyDescriptor(obj, prop);
if (desc && desc.configurable) {
console.log("[PAGE-INJECT] Found " + prop + " on object #" + idx + ", patching...");
if (desc.get) {
const originalGetter = desc.get;
Object.defineProperty(obj, prop, {
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
});
} else if (desc.writable) {
obj[prop] = propertyHooks[prop];
console.log("[PAGE-INJECT] " + prop + " value set to " + JSON.stringify(propertyHooks[prop]));
}
}
} catch (e) {
// Ignore individual property errors
}
});
hookedObjects.add(obj);
} catch (e) {
// Ignore object access errors
}
});
// Also try to find accountStore by scanning window properties
try {
for (let key in window) {
try {
const obj = window[key];
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)) {
Object.keys(propertyHooks).forEach(prop => {
try {
const desc = Object.getOwnPropertyDescriptor(store, prop);
if (desc && desc.configurable && desc.get) {
const originalGetter = desc.get;
Object.defineProperty(store, prop, {
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
});
}
} catch (e) {}
});
hookedObjects.add(store);
}
}
} catch (e) {}
}
} catch (e) {}
}
// Run initial scan
scanAndPatch();
// Scan periodically for late-initialized stores
let scanCount = 0;
const scanInterval = setInterval(() => {
scanCount++;
scanAndPatch();
if (scanCount >= 10) {
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 });
}
}
})();
+2200
View File
File diff suppressed because it is too large Load Diff
+35
View File
@@ -0,0 +1,35 @@
{
"name": "httptoolkit-patcher",
"version": "2.0.5",
"description": "HTTP Toolkit Pro Patcher",
"main": "index.js",
"type": "module",
"bin": "index.js",
"scripts": {
"start": "node index.js",
"patch": "node index.js patch",
"unpatch": "node index.js unpatch",
"restore": "node index.js restore",
"build": "esbuild index.js --bundle --platform=node --format=cjs --target=node18 --outfile=dist/bundle.cjs --external:@electron/asar --external:chalk"
},
"pkg": {
"targets": [
"node18-win-x64",
"node18-linux-x64",
"node18-linux-arm64",
"node18-macos-x64",
"node18-macos-arm64"
],
"outputPath": "dist"
},
"license": "MIT",
"dependencies": {
"@electron/asar": "^3.2.9",
"chalk": "4"
},
"devDependencies": {
"@types/node": "^20.14.9",
"esbuild": "^0.25.11",
"pkg": "^5.8.1"
}
}