Skip to content

Commit a1f0b0c

Browse files
committed
Make APK download more resilient to GH issues & rate limit
1 parent 9c4ac4f commit a1f0b0c

File tree

1 file changed

+41
-7
lines changed

1 file changed

+41
-7
lines changed

src/interceptors/android/fetch-apk.ts

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,26 @@ import * as fs from '../../util/fs';
77
import { HtkConfig } from '../../config';
88
import { logError } from '../../error-tracking';
99

10-
async function getLatestRelease(): Promise<{ version: string, url: string } | undefined> {
10+
const APK_REPO_RELEASE = 'httptoolkit/httptoolkit-android/releases/latest';
11+
12+
async function getLatestRelease(): Promise<
13+
| { version: string, url: string }
14+
| 'unknown-latest'
15+
| undefined
16+
> {
1117
try {
12-
const response = await fetch(
13-
"https://api.github.com/repos/httptoolkit/httptoolkit-android/releases/latest"
14-
);
18+
const response = await fetch(`https://api.github.com/repos/${APK_REPO_RELEASE}`);
19+
20+
const result = await response.json().catch(() => undefined)
21+
22+
if (response.status === 403 && response.headers.get('x-ratelimit-remaining') === '0') {
23+
// Likely connectable but we can't check the version (API rate limit). Used only if
24+
// we don't have any other APK available.
25+
return 'unknown-latest';
26+
} else if (!response.ok) {
27+
throw new Error(`Checking latest Android app release failed with ${response.status}`);
28+
}
29+
1530
const release = await response.json();
1631
const apkAsset = release.assets.filter((a: any) => a.name === "httptoolkit.apk")[0];
1732
const releaseName = release.name || release.tag_name;
@@ -119,8 +134,18 @@ export async function streamLatestApk(config: HtkConfig): Promise<stream.Readabl
119134
if (!latestApkRelease) {
120135
throw new Error("Couldn't find an Android APK locally or remotely");
121136
} else {
137+
// No APK locally, but we can get one remotely:
138+
122139
console.log('Streaming remote APK directly');
123-
const apkStream = (await fetch(latestApkRelease.url)).body;
140+
const apkUrl = latestApkRelease === 'unknown-latest'
141+
? `https://github.com/${APK_REPO_RELEASE}/download/httptoolkit.apk`
142+
: latestApkRelease.url;
143+
144+
const apkResponse = await fetch(apkUrl);
145+
if (!apkResponse.ok) {
146+
throw new Error(`APK download failed with ${apkResponse.status}`);
147+
}
148+
const apkStream = apkResponse.body;
124149

125150
// We buffer output into two passthrough streams, so both file & install
126151
// stream usage can be set up async independently. Buffers are 10MB, to
@@ -130,12 +155,21 @@ export async function streamLatestApk(config: HtkConfig): Promise<stream.Readabl
130155
const apkOutputStream = new stream.PassThrough({ highWaterMark: 10485760 });
131156
apkStream.pipe(apkOutputStream);
132157

133-
updateLocalApk(latestApkRelease.version, apkFileStream, config).catch(logError);
158+
if (latestApkRelease !== 'unknown-latest') {
159+
updateLocalApk(latestApkRelease.version, apkFileStream, config).catch(logError);
160+
}
161+
134162
return apkOutputStream;
135163
}
136164
}
137165

138-
if (!latestApkRelease || semver.gte(localApk.version, latestApkRelease.version, true)) {
166+
// So, we now have a local APK. Do we want to pull the remote one anyway?
167+
168+
if (
169+
!latestApkRelease || // Can't get releases at all
170+
latestApkRelease === 'unknown-latest' || // Can get, but don't know the version
171+
semver.gte(localApk.version, latestApkRelease.version, true) // Already up to date
172+
) {
139173
console.log('Streaming local APK');
140174
// If we have an APK locally and it's up to date, or we can't tell, just use it
141175
return fs.createReadStream(localApk.path);

0 commit comments

Comments
 (0)