Skip to content

Commit

Permalink
Make APK download more resilient to GH issues & rate limit
Browse files Browse the repository at this point in the history
  • Loading branch information
pimterry committed Aug 30, 2024
1 parent 9c4ac4f commit a1f0b0c
Showing 1 changed file with 41 additions and 7 deletions.
48 changes: 41 additions & 7 deletions src/interceptors/android/fetch-apk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,26 @@ import * as fs from '../../util/fs';
import { HtkConfig } from '../../config';
import { logError } from '../../error-tracking';

async function getLatestRelease(): Promise<{ version: string, url: string } | undefined> {
const APK_REPO_RELEASE = 'httptoolkit/httptoolkit-android/releases/latest';

async function getLatestRelease(): Promise<
| { version: string, url: string }
| 'unknown-latest'
| undefined
> {
try {
const response = await fetch(
"https://api.github.com/repos/httptoolkit/httptoolkit-android/releases/latest"
);
const response = await fetch(`https://api.github.com/repos/${APK_REPO_RELEASE}`);

const result = await response.json().catch(() => undefined)

if (response.status === 403 && response.headers.get('x-ratelimit-remaining') === '0') {
// Likely connectable but we can't check the version (API rate limit). Used only if
// we don't have any other APK available.
return 'unknown-latest';
} else if (!response.ok) {
throw new Error(`Checking latest Android app release failed with ${response.status}`);
}

const release = await response.json();
const apkAsset = release.assets.filter((a: any) => a.name === "httptoolkit.apk")[0];
const releaseName = release.name || release.tag_name;
Expand Down Expand Up @@ -119,8 +134,18 @@ export async function streamLatestApk(config: HtkConfig): Promise<stream.Readabl
if (!latestApkRelease) {
throw new Error("Couldn't find an Android APK locally or remotely");
} else {
// No APK locally, but we can get one remotely:

console.log('Streaming remote APK directly');
const apkStream = (await fetch(latestApkRelease.url)).body;
const apkUrl = latestApkRelease === 'unknown-latest'
? `https://github.com/${APK_REPO_RELEASE}/download/httptoolkit.apk`
: latestApkRelease.url;

const apkResponse = await fetch(apkUrl);
if (!apkResponse.ok) {
throw new Error(`APK download failed with ${apkResponse.status}`);
}
const apkStream = apkResponse.body;

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

updateLocalApk(latestApkRelease.version, apkFileStream, config).catch(logError);
if (latestApkRelease !== 'unknown-latest') {
updateLocalApk(latestApkRelease.version, apkFileStream, config).catch(logError);
}

return apkOutputStream;
}
}

if (!latestApkRelease || semver.gte(localApk.version, latestApkRelease.version, true)) {
// So, we now have a local APK. Do we want to pull the remote one anyway?

if (
!latestApkRelease || // Can't get releases at all
latestApkRelease === 'unknown-latest' || // Can get, but don't know the version
semver.gte(localApk.version, latestApkRelease.version, true) // Already up to date
) {
console.log('Streaming local APK');
// If we have an APK locally and it's up to date, or we can't tell, just use it
return fs.createReadStream(localApk.path);
Expand Down

0 comments on commit a1f0b0c

Please sign in to comment.