Layer 2 Fix

This commit is contained in:
ayyo42069
2026-02-19 00:06:37 +01:00
parent 5ce891c6cf
commit 45add35c8a
3 changed files with 84 additions and 46 deletions
+9
View File
@@ -131,6 +131,15 @@ const patchApp = async () => {
process.exit(1) process.exit(1)
} }
// Remove @prisma/instrumentation - it has a broken nested node_modules reference
// to @opentelemetry/instrumentation that causes a crash after repacking.
// It's only telemetry and not needed for the app to function.
const prismaInstrPath = path.join(tempPath, 'node_modules', '@prisma', 'instrumentation')
if (fs.existsSync(prismaInstrPath)) {
rm(prismaInstrPath)
console.log(chalk.yellowBright`Removed broken @prisma/instrumentation package`)
}
const indexPath = path.join(tempPath, 'build', 'index.js') const indexPath = path.join(tempPath, 'build', 'index.js')
if (!fs.existsSync(indexPath)) { if (!fs.existsSync(indexPath)) {
console.error(chalk.redBright`Couldn't find index.js file`) console.error(chalk.redBright`Couldn't find index.js file`)
+7 -7
View File
@@ -1,11 +1,11 @@
{ {
"name": "httptoolkit-pro-patcher", "name": "httptoolkit-patcher",
"version": "1.0.0", "version": "1.0.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "httptoolkit-pro-patcher", "name": "httptoolkit-patcher",
"version": "1.0.0", "version": "1.0.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -53,9 +53,9 @@
} }
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "20.19.28", "version": "20.19.33",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.28.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz",
"integrity": "sha512-VyKBr25BuFDzBFCK5sUM6ZXiWfqgCTwTAOK8qzGV/m9FCirXYDlmczJ+d5dXBAQALGCdRRdbteKYfJ84NGEusw==", "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -251,7 +251,7 @@
"version": "7.2.3", "version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Glob versions prior to v9 are no longer supported", "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"fs.realpath": "^1.0.0", "fs.realpath": "^1.0.0",
@@ -511,4 +511,4 @@
} }
} }
} }
} }
+68 -39
View File
@@ -1,16 +1,24 @@
// Patcher imports - need these for ESM compatibility // Patcher imports - dynamic import works in both ESM and CJS
import { createRequire as __patcherCreateRequire } from 'module'; const __patcherHttpsProxyAgent = (await import('https-proxy-agent')).HttpsProxyAgent;
import { fileURLToPath as __patcherFileURLToPath } from 'url'; const __patcherExpress = (await import('express')).default;
import { dirname as __patcherDirname, join as __patcherJoin } from 'path'; const __patcherHttps = (await import('https')).default;
const __patcherFs = (await import('fs')).default;
const __patcherOs = (await import('os')).default;
const __patcherPath = (await import('path')).default;
// Load modules using require (works better with the app's setup) // The fake pro user we inject everywhere
const __patcherRequire = __patcherCreateRequire(import.meta.url); const __patcherProUser = {
const __patcherHttpsProxyAgent = __patcherRequire('https-proxy-agent').HttpsProxyAgent; email,
const __patcherExpress = __patcherRequire('express'); subscription: {
const __patcherHttps = __patcherRequire('https'); status: 'active',
const __patcherFs = __patcherRequire('fs'); expiry: new Date('6767-06-07').toISOString(),
const __patcherOs = __patcherRequire('os'); sku: 'pro-annual',
const __patcherPath = __patcherRequire('path'); },
userId: 'patcher',
banned: false,
featureFlags: [],
teamSubscription: null,
};
// Simple HTTP request helper with redirect support // Simple HTTP request helper with redirect support
const __patcherRequest = (method, url, redirectCount = 0) => new Promise((resolve, reject) => { const __patcherRequest = (method, url, redirectCount = 0) => new Promise((resolve, reject) => {
@@ -46,6 +54,17 @@ console.log(`[Patcher] Temp path: ${__patcherTempPath}`);
const __patcherApp = __patcherExpress(); const __patcherApp = __patcherExpress();
__patcherApp.disable('x-powered-by'); __patcherApp.disable('x-powered-by');
// LAYER 2: Intercept account API calls directly
// Handles any endpoint the account store calls, so even if JS regex fails, app still gets pro user
__patcherApp.all(/^\/__patcher_api\/(.*)/, (req, res) => {
console.log(`[Patcher] API intercept: ${req.url}`);
const user = JSON.parse(JSON.stringify(__patcherProUser));
user.subscription.expiry = new Date(user.subscription.expiry);
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Headers', '*');
res.json(user);
});
// Catch all requests and proxy them through our server // Catch all requests and proxy them through our server
__patcherApp.all(/(.*)/, async (req, res) => { __patcherApp.all(/(.*)/, async (req, res) => {
console.log(`[Patcher] ${req.url}`); console.log(`[Patcher] ${req.url}`);
@@ -100,44 +119,39 @@ __patcherApp.all(/(.*)/, async (req, res) => {
let data = remoteFile.data; let data = remoteFile.data;
// This is where the magic happens - patch main.js to inject pro subscription // LAYER 1: Patch main.js JS directly (with multiple fallback patterns)
if (new URL(req.url, process.env.APP_URL).pathname === '/main.js') { if (new URL(req.url, process.env.APP_URL).pathname === '/main.js') {
console.log(`[Patcher] Patching main.js...`); console.log(`[Patcher] Patching main.js...`);
res.setHeader('Cache-Control', 'no-store'); res.setHeader('Cache-Control', 'no-store');
data = data.toString(); data = data.toString();
// Find the account store class and module names (they're minified so we gotta search for them) // Try multiple regex patterns for different minification versions
const accStoreName = data.match(/class ([0-9A-Za-z_]+){constructor\(e\){this\.goToSettings=e/)?.[1]; const accStoreName =
const modName = data.match(/([0-9A-Za-z_]+).(getLatestUserData|getLastUserData)/)?.[1]; data.match(/class ([0-9A-Za-z_$]+)\s*\{\s*constructor\s*\(\s*e\s*\)\s*\{\s*this\.goToSettings\s*=\s*e/)?.[1] ||
data.match(/class ([0-9A-Za-z_$]+)[^{]*\{[^}]{0,200}goToSettings/)?.[1];
if (!accStoreName) console.error(`[Patcher] Couldn't find account store class`); const modName =
else if (!modName) console.error(`[Patcher] Couldn't find user data module`); data.match(/([0-9A-Za-z_$]+)\.(getLatestUserData|getLastUserData)/)?.[1];
else {
// Override the user data functions to return our fake pro user const userJson = JSON.stringify(__patcherProUser);
const userInit = `const __pUser=${userJson};__pUser.subscription.expiry=new Date(__pUser.subscription.expiry);`;
if (!accStoreName) {
console.error(`[Patcher] Couldn't find account store class - relying on API interception`);
} else if (!modName) {
console.error(`[Patcher] Couldn't find user data module - relying on API interception`);
} else {
const classPattern = new RegExp(`class ${accStoreName}\\s*\\{`);
let patched = data.replace( let patched = data.replace(
`class ${accStoreName}{`, classPattern,
`["getLatestUserData","getLastUserData"].forEach(p=>Object.defineProperty(${modName},p,{value:()=>user}));class ${accStoreName}{` `["getLatestUserData","getLastUserData"].forEach(p=>Object.defineProperty(${modName},p,{value:()=>__pUser}));class ${accStoreName}{`
); );
if (patched === data) { if (patched === data) {
console.error(`[Patcher] Patch failed - couldn't find injection point`); console.error(`[Patcher] JS inject failed - relying on API interception`);
} else { } else {
// Inject our pro user at the top data = userInit + patched;
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!`); console.log(`[Patcher] main.js patched successfully!`);
} }
} }
@@ -153,7 +167,7 @@ __patcherApp.all(/(.*)/, async (req, res) => {
__patcherApp.listen(__patcherPort, () => console.log(`[Patcher] Running on port ${__patcherPort}`)); __patcherApp.listen(__patcherPort, () => console.log(`[Patcher] Running on port ${__patcherPort}`));
// Fix CORS so the app can talk to our local server // Fix CORS and intercept account API at the Electron session level
app.on('ready', () => { app.on('ready', () => {
session.defaultSession.webRequest.onBeforeSendHeaders((details, callback) => { session.defaultSession.webRequest.onBeforeSendHeaders((details, callback) => {
// Block telemetry // Block telemetry
@@ -169,6 +183,21 @@ app.on('ready', () => {
callback({ requestHeaders: details.requestHeaders }); callback({ requestHeaders: details.requestHeaders });
}); });
// LAYER 2: Redirect any account/auth API calls to our fake endpoint
session.defaultSession.webRequest.onBeforeRequest(
{ urls: ['https://api.httptoolkit.tech/*', 'https://accounts.httptoolkit.tech/*'] },
(details, callback) => {
try {
const url = new URL(details.url);
const redirectURL = `http://localhost:${__patcherPort}/__patcher_api${url.pathname}${url.search}`;
console.log(`[Patcher] Redirecting API: ${details.url} -> ${redirectURL}`);
callback({ redirectURL });
} catch (e) {
callback({});
}
}
);
session.defaultSession.webRequest.onHeadersReceived((details, callback) => { session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
if (details.responseHeaders) { if (details.responseHeaders) {
details.responseHeaders['Access-Control-Allow-Origin'] = [`http://localhost:${__patcherPort}`]; details.responseHeaders['Access-Control-Allow-Origin'] = [`http://localhost:${__patcherPort}`];