This commit is contained in:
ayyo42069
2026-01-12 13:40:16 +01:00
commit 4171ec2196
6 changed files with 1034 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
node_modules
+45
View File
@@ -0,0 +1,45 @@
# HTTP Toolkit Pro Patcher
A simple patcher to enable Pro features in HTTP Toolkit.
**Updated by [kristof.best](https://kristof.best) ([@ayyo42069](https://github.com/ayyo42069))**
**Original by ([@XielQ](https://github.com/XielQs))**
## Requirements
- Node.js 15 or higher
- HTTP Toolkit installed
## Usage
```bash
# Install dependencies
npm install
# Apply the patch
node . patch
# Start HTTP Toolkit
node . start
# Restore original (if needed)
node . restore
```
## How it works
1. Extracts the app.asar from HTTP Toolkit
2. Injects a local proxy server that intercepts UI requests
3. Patches the subscription data on-the-fly to enable Pro features
4. Flips Electron fuses to bypass ASAR integrity checks
5. Repacks the app
## Notes
- Creates a backup at `app.asar.bak` before patching
- You can set a custom proxy with the `PROXY` environment variable
- Works offline after first run (caches UI files locally)
## License
MIT
+271
View File
@@ -0,0 +1,271 @@
// @ts-check
import { spawn } from 'child_process'
import { flipFuses, FuseVersion, FuseV1Options } from '@electron/fuses'
import asar from '@electron/asar'
import prompts from 'prompts'
import yargs from 'yargs'
import chalk from 'chalk'
import path from 'path'
import fs from 'fs'
import os from 'os'
const argv = await yargs(process.argv.slice(2))
.usage(`Usage: ${process.argv0} . <command> [options]`)
.command('patch', 'Patch HTTP Toolkit using the specified script')
.command('restore', 'Restore HTTP Toolkit files to their original state')
.command('start', 'Start HTTP Toolkit')
.demandCommand(1, 'You need at least one command before moving on')
.alias('h', 'help')
.parse()
const isWin = process.platform === 'win32'
const isMac = process.platform === 'darwin'
// Try to find where HTTP Toolkit is installed
const appPath =
isWin ? path.join(process.env.LOCALAPPDATA ?? '', 'Programs', 'HTTP Toolkit', 'resources')
: isMac ? '/Applications/HTTP Toolkit.app/Contents/Resources'
: fs.existsSync('/opt/HTTP Toolkit/resources') ? '/opt/HTTP Toolkit/resources'
: '/opt/httptoolkit/resources'
const exePath =
isWin ? path.join(process.env.LOCALAPPDATA ?? '', 'Programs', 'HTTP Toolkit', 'HTTP Toolkit.exe')
: isMac ? '/Applications/HTTP Toolkit.app/Contents/MacOS/HTTP Toolkit'
: fs.existsSync('/opt/HTTP Toolkit/httptoolkit') ? '/opt/HTTP Toolkit/httptoolkit'
: '/opt/httptoolkit/httptoolkit'
const isSudo = !isWin && (process.getuid || (() => process.env.SUDO_UID ? 0 : null))() === 0
if (+(process.versions.node.split('.')[0]) < 15) {
console.error(chalk.redBright`Node.js version 15 or higher is recommended, you are using version {bold ${process.versions.node}}`)
}
if (!fs.existsSync(path.join(appPath, 'app.asar'))) {
console.error(chalk.redBright`Couldn't find HTTP Toolkit! Make sure it's installed.`)
process.exit(1)
}
console.log(chalk.blueBright`Found HTTP Toolkit at {bold ${path.dirname(appPath)}}`)
// Helper to recursively delete directories
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 })
}
}
/** @type {Array<import('child_process').ChildProcess>} */
const activeProcesses = []
let isCancelled = false
const cleanUp = async () => {
isCancelled = true
console.log(chalk.redBright`Cancelled! Cleaning up...`)
if (activeProcesses.length) {
console.log(chalk.yellowBright`Stopping active processes...`)
for (const proc of activeProcesses) {
proc.kill('SIGINT')
console.log(chalk.yellowBright`Process {bold ${proc.pid ? process.pid + ' ' : ''}}stopped`)
}
await new Promise(resolve => setTimeout(resolve, 1000))
}
const paths = [
path.resolve(os.tmpdir(), 'httptoolkit-patch'),
path.resolve(appPath, 'app')
]
try {
for (const p of paths) {
if (fs.existsSync(p)) {
console.log(chalk.yellowBright`Removing {bold ${p}}`)
rm(p)
}
}
} catch (e) {
console.error(chalk.redBright`Error while cleaning up`, e)
}
process.exit(1)
}
const patchApp = async () => {
const filePath = path.join(appPath, 'app.asar')
const tempPath = path.join(appPath, 'app')
if (fs.readFileSync(filePath).includes('Injected by HTTP Toolkit Patcher')) {
console.log(chalk.greenBright`App is already patched!`)
return
}
const globalProxy = process.env.PROXY
console.log(chalk.blueBright`Starting the patch process...`)
if (globalProxy) {
if (!globalProxy.match(/^https?:/)) {
console.error(chalk.redBright`Global proxy must start with http:// or https://`)
process.exit(1)
}
console.log(chalk.yellowBright`Using custom proxy: {bold ${globalProxy}}`)
}
console.log(chalk.yellowBright`Extracting app files...`)
;['SIGINT', 'SIGTERM'].forEach(signal => process.on(signal, cleanUp))
try {
rm(tempPath)
asar.extractAll(filePath, tempPath)
} catch (e) {
if (!isSudo && /** @type {any} */ (e).errno === -13) { // Permission denied
console.error(chalk.redBright`Permission denied${!isWin ? ', try running with sudo' : ', try running node as administrator'}`)
process.exit(1)
}
console.error(chalk.redBright`Error extracting app`, e)
process.exit(1)
}
const indexPath = path.join(tempPath, 'build', 'index.js')
if (!fs.existsSync(indexPath)) {
console.error(chalk.redBright`Couldn't find index.js file`)
cleanUp()
}
const data = fs.readFileSync(indexPath, 'utf-8')
;['SIGINT', 'SIGTERM'].forEach(signal => process.off(signal, cleanUp))
const { email } = await prompts({
type: 'text',
name: 'email',
message: 'Enter an email for the pro plan',
validate: value => value.includes('@') || 'Invalid email'
})
if (!email || typeof email !== 'string') {
console.error(chalk.redBright`No email provided`)
cleanUp()
}
;['SIGINT', 'SIGTERM'].forEach(signal => process.on(signal, cleanUp))
const patchContent = fs.readFileSync('patch.js', 'utf-8')
// Separate imports from the rest of the patch code
const patchImportsMatch = patchContent.match(/\/\/ --- Patcher Imports[\s\S]*?\/\/ --- End Patcher Imports ---\n?/)
const patchImports = patchImportsMatch ? patchImportsMatch[0] : ''
const patchCode = patchContent.replace(patchImports, '')
// Find where to put our imports (right after the last existing import)
const lastImportMatch = data.match(/^import .+$/gm)
const lastImport = lastImportMatch ? lastImportMatch[lastImportMatch.length - 1] : null
let patchedData = data
// Inject imports at the top
if (patchImports && lastImport) {
patchedData = patchedData.replace(lastImport, `${lastImport}\n// ------- Patcher Imports -------\n${patchImports.trim()}\n// ------- End Patcher Imports -------`)
}
// Inject the main patch code where APP_URL is defined
patchedData = patchedData
.replace('const APP_URL =', `// ------- Injected by HTTP Toolkit Patcher -------\nconst email = \`${email.replace(/`/g, '\\`')}\`\nconst globalProxy = process.env.PROXY ?? \`${globalProxy ? globalProxy.replace(/`/g, '\\`') : ''}\`\n${patchCode}\n// ------- End patched content -------\nconst APP_URL =`)
if (data === patchedData || !patchedData) {
console.error(chalk.redBright`Patch failed - couldn't find injection point`)
cleanUp()
}
fs.writeFileSync(indexPath, patchedData, 'utf-8')
console.log(chalk.greenBright`Patched index.js successfully`)
console.log(chalk.yellowBright`Installing dependencies...`)
try {
const proc = spawn('npm install express https-proxy-agent', { cwd: tempPath, stdio: 'inherit', shell: true })
activeProcesses.push(proc)
await new Promise(resolve =>
proc.on('close', resolve)
)
activeProcesses.splice(activeProcesses.indexOf(proc), 1)
if (isCancelled) return
} catch (e) {
console.error(chalk.redBright`Error installing dependencies`, e)
cleanUp()
}
rm(path.join(tempPath, 'package-lock.json'))
fs.copyFileSync(filePath, `${filePath}.bak`)
console.log(chalk.greenBright`Created backup at {bold ${filePath}.bak}`)
console.log(chalk.yellowBright`Repacking the app...`)
await asar.createPackage(tempPath, filePath)
rm(tempPath)
console.log(chalk.greenBright`App patched!`)
// Disable ASAR integrity checks so Electron doesn't complain
await flipElectronFuses()
}
const flipElectronFuses = async () => {
console.log(chalk.yellowBright`Disabling ASAR integrity checks...`)
if (!fs.existsSync(exePath)) {
console.log(chalk.yellowBright`Executable not found at ${exePath}, skipping fuse flipping`)
return
}
try {
await flipFuses(exePath, {
version: FuseVersion.V1,
[FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: false,
[FuseV1Options.OnlyLoadAppFromAsar]: false,
})
console.log(chalk.greenBright`Integrity checks disabled successfully`)
} catch (e) {
console.error(chalk.yellowBright`Failed to flip fuses (might already be done or not supported): ${/** @type {Error} */ (e).message}`)
}
}
switch (argv._[0]) {
case 'patch':
await patchApp()
break
case 'restore':
try {
console.log(chalk.blueBright`Restoring app...`)
if (!fs.existsSync(path.join(appPath, 'app.asar.bak')))
console.error(chalk.redBright`Backup file not found - maybe the app wasn't patched?`)
else {
fs.copyFileSync(path.join(appPath, 'app.asar.bak'), path.join(appPath, 'app.asar'))
console.log(chalk.greenBright`App restored successfully`)
}
rm(path.join(os.tmpdir(), 'httptoolkit-patch'))
} catch (e) {
if (!isSudo && /** @type {any} */ (e).errno === -13) { // Permission denied
console.error(chalk.redBright`Permission denied${!isWin ? ', try running with sudo' : ', try running node as administrator'}`)
process.exit(1)
}
console.error(chalk.redBright`Error restoring app`, e)
process.exit(1)
}
break
case 'start':
console.log(chalk.blueBright`Starting HTTP Toolkit...`)
try {
const command =
isWin ? `"${path.resolve(appPath, '..', 'HTTP Toolkit.exe')}"`
: isMac ? 'open -a "HTTP Toolkit"'
: 'httptoolkit'
const proc = spawn(command, { stdio: 'inherit', shell: true })
proc.on('close', code => process.exit(code))
} catch (e) {
console.error(chalk.redBright`Error starting app`, e)
if (isSudo) console.error(chalk.redBright`Try running without sudo`)
process.exit(1)
}
break
default:
console.error(chalk.redBright`Unknown command`)
process.exit(1)
}
if (!isCancelled) console.log(chalk.greenBright`All done!`)
+514
View File
@@ -0,0 +1,514 @@
{
"name": "httptoolkit-pro-patcher",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "httptoolkit-pro-patcher",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"@electron/asar": "^3.2.9",
"@electron/fuses": "^1.8.0",
"chalk": "4",
"prompts": "^2.4.2",
"yargs": "^17.7.2"
},
"devDependencies": {
"@types/node": "^20.14.9",
"@types/prompts": "^2.4.9",
"@types/yargs": "^17.0.32"
}
},
"node_modules/@electron/asar": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz",
"integrity": "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==",
"license": "MIT",
"dependencies": {
"commander": "^5.0.0",
"glob": "^7.1.6",
"minimatch": "^3.0.4"
},
"bin": {
"asar": "bin/asar.js"
},
"engines": {
"node": ">=10.12.0"
}
},
"node_modules/@electron/fuses": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@electron/fuses/-/fuses-1.8.0.tgz",
"integrity": "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw==",
"license": "MIT",
"dependencies": {
"chalk": "^4.1.1",
"fs-extra": "^9.0.1",
"minimist": "^1.2.5"
},
"bin": {
"electron-fuses": "dist/bin.js"
}
},
"node_modules/@types/node": {
"version": "20.19.28",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.28.tgz",
"integrity": "sha512-VyKBr25BuFDzBFCK5sUM6ZXiWfqgCTwTAOK8qzGV/m9FCirXYDlmczJ+d5dXBAQALGCdRRdbteKYfJ84NGEusw==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
}
},
"node_modules/@types/prompts": {
"version": "2.4.9",
"resolved": "https://registry.npmjs.org/@types/prompts/-/prompts-2.4.9.tgz",
"integrity": "sha512-qTxFi6Buiu8+50/+3DGIWLHM6QuWsEKugJnnP6iv2Mc4ncxE4A/OJkjuVOA+5X0X1S/nq5VJRa8Lu+nwcvbrKA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*",
"kleur": "^3.0.3"
}
},
"node_modules/@types/yargs": {
"version": "17.0.35",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz",
"integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/yargs-parser": "*"
}
},
"node_modules/@types/yargs-parser": {
"version": "21.0.3",
"resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
"integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
"dev": true,
"license": "MIT"
},
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/at-least-node": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
"license": "ISC",
"engines": {
"node": ">= 4.0.0"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
"node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"license": "ISC",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.1",
"wrap-ansi": "^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"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": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz",
"integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==",
"license": "MIT",
"engines": {
"node": ">= 6"
}
},
"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/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
"node_modules/escalade": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/fs-extra": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
"integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
"license": "MIT",
"dependencies": {
"at-least-node": "^1.0.0",
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=10"
}
},
"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/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"license": "ISC",
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Glob versions prior to v9 are no longer supported",
"license": "ISC",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"license": "ISC"
},
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"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/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/jsonfile": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
"integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
"license": "MIT",
"dependencies": {
"universalify": "^2.0.0"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/kleur": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
"integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"license": "ISC",
"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": {
"node": ">=0.10.0"
}
},
"node_modules/prompts": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
"integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==",
"license": "MIT",
"dependencies": {
"kleur": "^3.0.3",
"sisteransi": "^1.0.5"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/sisteransi": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
"license": "MIT"
},
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true,
"license": "MIT"
},
"node_modules/universalify": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
"license": "MIT",
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
},
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"license": "ISC",
"engines": {
"node": ">=10"
}
},
"node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
"license": "MIT",
"dependencies": {
"cliui": "^8.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.3",
"y18n": "^5.0.5",
"yargs-parser": "^21.1.1"
},
"engines": {
"node": ">=12"
}
},
"node_modules/yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"license": "ISC",
"engines": {
"node": ">=12"
}
}
}
}
+24
View File
@@ -0,0 +1,24 @@
{
"name": "httptoolkit-patcher",
"version": "1.0.0",
"description": "Patches HTTP Tool Kit.",
"main": "index.js",
"type": "module",
"scripts": {
"start": "node . patch"
},
"author": "ayyo42069",
"license": "MIT",
"dependencies": {
"@electron/asar": "^3.2.9",
"@electron/fuses": "^1.8.0",
"chalk": "4",
"prompts": "^2.4.2",
"yargs": "^17.7.2"
},
"devDependencies": {
"@types/node": "^20.14.9",
"@types/prompts": "^2.4.9",
"@types/yargs": "^17.0.32"
}
}
+179
View File
@@ -0,0 +1,179 @@
// Patcher imports - need these for ESM compatibility
import { createRequire as __patcherCreateRequire } from 'module';
import { fileURLToPath as __patcherFileURLToPath } from 'url';
import { dirname as __patcherDirname, join as __patcherJoin } from 'path';
// Load modules using require (works better with the app's setup)
const __patcherRequire = __patcherCreateRequire(import.meta.url);
const __patcherHttpsProxyAgent = __patcherRequire('https-proxy-agent').HttpsProxyAgent;
const __patcherExpress = __patcherRequire('express');
const __patcherHttps = __patcherRequire('https');
const __patcherFs = __patcherRequire('fs');
const __patcherOs = __patcherRequire('os');
const __patcherPath = __patcherRequire('path');
// Simple HTTP request helper with redirect support
const __patcherRequest = (method, url, redirectCount = 0) => new Promise((resolve, reject) => {
const agent = globalProxy ? new __patcherHttpsProxyAgent(globalProxy.startsWith('http') ? globalProxy.replace(/^http:/, 'https:') : 'https://' + globalProxy) : undefined;
const req = __patcherHttps.request(url, { method, agent }, res => {
let data = Buffer.alloc(0);
res.on('data', chunk => data = Buffer.concat([data, chunk]));
res.on('end', () => {
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
if (redirectCount >= 5) return reject(new Error('Too many redirects'));
return resolve(__patcherRequest(method, res.headers.location, redirectCount + 1));
}
resolve({ data, statusCode: res.statusCode, headers: res.headers });
});
});
req.on('error', reject);
req.end();
});
// Quick check if we can reach the HTTP Toolkit servers
const __patcherHasInternet = () => __patcherRequest('HEAD', 'https://app.httptoolkit.tech')
.then(r => r.statusCode >= 200 && r.statusCode < 400)
.catch(() => false);
const __patcherPort = process.env.PATCHER_PORT || 5067;
const __patcherTempPath = __patcherPath.join(__patcherOs.tmpdir(), 'httptoolkit-patch');
// Point the app to our local server instead of the real one
process.env.APP_URL = `http://localhost:${__patcherPort}`;
console.log(`[Patcher] Temp path: ${__patcherTempPath}`);
const __patcherApp = __patcherExpress();
__patcherApp.disable('x-powered-by');
// Catch all requests and proxy them through our server
__patcherApp.all(/(.*)/, async (req, res) => {
console.log(`[Patcher] ${req.url}`);
let filePath = __patcherPath.join(__patcherTempPath, new URL(req.url, process.env.APP_URL).pathname === '/' ? 'index.html' : new URL(req.url, process.env.APP_URL).pathname);
// Some routes need .html extension
if (['/view', '/intercept', '/settings', '/mock'].includes(new URL(req.url, process.env.APP_URL).pathname)) {
filePath += '.html';
}
// Block service worker - it causes caching headaches
if (new URL(req.url, process.env.APP_URL).pathname === '/ui-update-worker.js') {
return res.status(404).send('Not found');
}
if (!__patcherFs.existsSync(__patcherTempPath)) {
__patcherFs.mkdirSync(__patcherTempPath);
}
// Offline mode - serve from cache if no internet
if (!(await __patcherHasInternet())) {
if (__patcherFs.existsSync(filePath)) {
return res.sendFile(filePath);
}
return res.status(404).send('No internet and file not cached');
}
try {
// Check if we have a cached copy that's still fresh
if (__patcherFs.existsSync(filePath)) {
try {
const remoteDate = await __patcherRequest('HEAD', `https://app.httptoolkit.tech${req.url}`).then(r => new Date(r.headers['last-modified']));
if (remoteDate < new Date(__patcherFs.statSync(filePath).mtime)) {
return res.sendFile(filePath);
}
} catch (e) { /* ignore, just redownload */ }
}
// Download from the real server
const remoteFile = await __patcherRequest('GET', `https://app.httptoolkit.tech${req.url}`);
for (const [key, value] of Object.entries(remoteFile.headers)) res.setHeader(key, value);
// Create directories if needed
const mkdirp = dir => {
if (!__patcherFs.existsSync(dir)) {
mkdirp(__patcherPath.dirname(dir));
__patcherFs.mkdirSync(dir);
}
};
mkdirp(__patcherPath.dirname(filePath));
let data = remoteFile.data;
// This is where the magic happens - patch main.js to inject pro subscription
if (new URL(req.url, process.env.APP_URL).pathname === '/main.js') {
console.log(`[Patcher] Patching main.js...`);
res.setHeader('Cache-Control', 'no-store');
data = data.toString();
// Find the account store class and module names (they're minified so we gotta search for them)
const accStoreName = data.match(/class ([0-9A-Za-z_]+){constructor\(e\){this\.goToSettings=e/)?.[1];
const modName = data.match(/([0-9A-Za-z_]+).(getLatestUserData|getLastUserData)/)?.[1];
if (!accStoreName) console.error(`[Patcher] Couldn't find account store class`);
else if (!modName) console.error(`[Patcher] Couldn't find user data module`);
else {
// Override the user data functions to return our fake pro user
let patched = data.replace(
`class ${accStoreName}{`,
`["getLatestUserData","getLastUserData"].forEach(p=>Object.defineProperty(${modName},p,{value:()=>user}));class ${accStoreName}{`
);
if (patched === data) {
console.error(`[Patcher] Patch failed - couldn't find injection point`);
} else {
// Inject our pro user at the top
patched = `const user=${JSON.stringify({
email,
subscription: {
status: 'active',
expiry: new Date('6767-06-07').toISOString(),
sku: 'pro-annual',
},
userId: 'patcher',
banned: false,
featureFlags: [],
teamSubscription: null,
})};user.subscription.expiry=new Date(user.subscription.expiry);` + patched;
data = patched;
console.log(`[Patcher] main.js patched successfully!`);
}
}
}
__patcherFs.writeFileSync(filePath, data);
res.sendFile(filePath);
} catch (e) {
console.error(`[Patcher] Error: ${e.message}`);
res.status(500).send('Something went wrong');
}
});
__patcherApp.listen(__patcherPort, () => console.log(`[Patcher] Running on port ${__patcherPort}`));
// Fix CORS so the app can talk to our local server
app.on('ready', () => {
session.defaultSession.webRequest.onBeforeSendHeaders((details, callback) => {
// Block telemetry
const blocked = ['events.httptoolkit.tech'];
try {
const host = new URL(details.url).hostname;
if (blocked.includes(host) || details.url?.includes('sentry')) {
return callback({ cancel: true });
}
} catch (e) { }
details.requestHeaders.Origin = 'https://app.httptoolkit.tech';
callback({ requestHeaders: details.requestHeaders });
});
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
if (details.responseHeaders) {
details.responseHeaders['Access-Control-Allow-Origin'] = [`http://localhost:${__patcherPort}`];
delete details.responseHeaders['access-control-allow-origin'];
}
callback({ responseHeaders: details.responseHeaders });
});
});