Skip to content

Commit 2545a6a

Browse files
committed
Split Edge & Chrome into per-variant (dev, beta, ...) interceptors
This also refactors them to use a shared core, so that we can fix them all together easily, and add more chromium-based browsers (hey there Brave) easily on top too.
1 parent 68afc9f commit 2545a6a

File tree

5 files changed

+265
-264
lines changed

5 files changed

+265
-264
lines changed
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
import * as _ from 'lodash';
2+
import { generateSPKIFingerprint } from 'mockttp';
3+
4+
import { HtkConfig } from '../config';
5+
6+
import { getAvailableBrowsers, launchBrowser, BrowserInstance, Browser } from '../browsers';
7+
import { delay, readFile, deleteFolder } from '../util';
8+
import { HideWarningServer } from '../hide-warning-server';
9+
import { Interceptor } from '.';
10+
import { reportError } from '../error-tracking';
11+
12+
const getBrowserDetails = async (config: HtkConfig, variant: string): Promise<Browser | undefined> => {
13+
const browsers = await getAvailableBrowsers(config.configPath);
14+
15+
// Get the details for the first of these browsers that is installed.
16+
return _.find(browsers, b => b.name === variant);
17+
};
18+
19+
abstract class ChromiumBasedInterceptor implements Interceptor {
20+
21+
readonly abstract id: string;
22+
readonly abstract version: string;
23+
24+
private readonly activeBrowsers: _.Dictionary<BrowserInstance> = {};
25+
26+
constructor(
27+
private config: HtkConfig,
28+
private variantName: string
29+
) { }
30+
31+
32+
isActive(proxyPort: number | string) {
33+
const browser = this.activeBrowsers[proxyPort];
34+
return !!browser && !!browser.pid;
35+
}
36+
37+
async isActivable() {
38+
return !!(await getBrowserDetails(this.config, this.variantName));
39+
}
40+
41+
async activate(proxyPort: number) {
42+
if (this.isActive(proxyPort)) return;
43+
44+
const certificatePem = await readFile(this.config.https.certPath, 'utf8');
45+
const spkiFingerprint = generateSPKIFingerprint(certificatePem);
46+
47+
const hideWarningServer = new HideWarningServer(this.config);
48+
await hideWarningServer.start('https://amiusing.httptoolkit.tech');
49+
50+
const browserDetails = await getBrowserDetails(this.config, this.variantName);
51+
52+
const browser = await launchBrowser(hideWarningServer.hideWarningUrl, {
53+
browser: browserDetails ? browserDetails.name : this.variantName,
54+
proxy: `https://127.0.0.1:${proxyPort}`,
55+
noProxy: [
56+
// Force even localhost requests to go through the proxy
57+
// See https://bugs.chromium.org/p/chromium/issues/detail?id=899126#c17
58+
'<-loopback>',
59+
// Don't intercept our warning hiding requests. Note that this must be
60+
// the 2nd rule here, or <-loopback> would override it.
61+
hideWarningServer.host
62+
],
63+
options: [
64+
// Trust our CA certificate's fingerprint:
65+
`--ignore-certificate-errors-spki-list=${spkiFingerprint}`
66+
]
67+
}, this.config.configPath);
68+
69+
if (browser.process.stdout) browser.process.stdout.pipe(process.stdout);
70+
if (browser.process.stderr) browser.process.stderr.pipe(process.stderr);
71+
72+
await hideWarningServer.completedPromise;
73+
await hideWarningServer.stop();
74+
75+
this.activeBrowsers[proxyPort] = browser;
76+
browser.process.once('close', () => {
77+
delete this.activeBrowsers[proxyPort];
78+
79+
if (Object.keys(this.activeBrowsers).length === 0 && browserDetails && _.isString(browserDetails.profile)) {
80+
// If we were the last browser, and we have a profile path, and it's in our config
81+
// (just in case something's gone wrong) -> delete the profile to reset everything.
82+
83+
const profilePath = browserDetails.profile;
84+
if (!profilePath.startsWith(this.config.configPath)) {
85+
reportError(
86+
`Unexpected ${this.variantName} profile location, not deleting: ${profilePath}`
87+
);
88+
} else {
89+
deleteFolder(browserDetails.profile).catch(reportError);
90+
}
91+
}
92+
});
93+
94+
// Delay the approx amount of time it normally takes the browser to really open, just to be sure
95+
await delay(500);
96+
}
97+
98+
async deactivate(proxyPort: number | string) {
99+
if (this.isActive(proxyPort)) {
100+
const browser = this.activeBrowsers[proxyPort];
101+
const exitPromise = new Promise((resolve) => browser!.process.once('close', resolve));
102+
browser!.stop();
103+
await exitPromise;
104+
}
105+
}
106+
107+
async deactivateAll(): Promise<void> {
108+
await Promise.all(
109+
Object.keys(this.activeBrowsers).map((proxyPort) => this.deactivate(proxyPort))
110+
);
111+
}
112+
};
113+
114+
export class FreshChrome extends ChromiumBasedInterceptor {
115+
116+
id = 'fresh-chrome';
117+
version = '1.0.0';
118+
119+
constructor(config: HtkConfig) {
120+
super(config, 'chrome');
121+
}
122+
123+
};
124+
125+
export class FreshChromeBeta extends ChromiumBasedInterceptor {
126+
127+
id = 'fresh-chrome-beta';
128+
version = '1.0.0';
129+
130+
constructor(config: HtkConfig) {
131+
super(config, 'chrome-beta');
132+
}
133+
134+
};
135+
136+
export class FreshChromeDev extends ChromiumBasedInterceptor {
137+
138+
id = 'fresh-chrome-dev';
139+
version = '1.0.0';
140+
141+
constructor(config: HtkConfig) {
142+
super(config, 'chrome-dev');
143+
}
144+
145+
};
146+
147+
export class FreshChromeCanary extends ChromiumBasedInterceptor {
148+
149+
id = 'fresh-chrome-canary';
150+
version = '1.0.0';
151+
152+
constructor(config: HtkConfig) {
153+
super(config, 'chrome-canary');
154+
}
155+
156+
};
157+
158+
export class FreshChromium extends ChromiumBasedInterceptor {
159+
160+
id = 'fresh-chromium';
161+
version = '1.0.0';
162+
163+
constructor(config: HtkConfig) {
164+
super(config, 'chromium');
165+
}
166+
167+
};
168+
169+
export class FreshChromiumDev extends ChromiumBasedInterceptor {
170+
171+
id = 'fresh-chromium-dev';
172+
version = '1.0.0';
173+
174+
constructor(config: HtkConfig) {
175+
super(config, 'chromium-dev');
176+
}
177+
178+
};
179+
180+
export class FreshEdge extends ChromiumBasedInterceptor {
181+
182+
id = 'fresh-edge';
183+
version = '1.0.0';
184+
185+
constructor(config: HtkConfig) {
186+
super(config, 'msedge');
187+
}
188+
189+
};
190+
191+
export class FreshEdgeBeta extends ChromiumBasedInterceptor {
192+
193+
id = 'fresh-edge-beta';
194+
version = '1.0.0';
195+
196+
constructor(config: HtkConfig) {
197+
super(config, 'msedge-beta');
198+
}
199+
200+
};
201+
202+
export class FreshEdgeCanary extends ChromiumBasedInterceptor {
203+
204+
id = 'fresh-edge-canary';
205+
version = '1.0.0';
206+
207+
constructor(config: HtkConfig) {
208+
super(config, 'msedge-canary');
209+
}
210+
211+
};

src/interceptors/fresh-chrome.ts

Lines changed: 0 additions & 109 deletions
This file was deleted.

0 commit comments

Comments
 (0)