@@ -7,11 +7,26 @@ import * as fs from '../../util/fs';
7
7
import { HtkConfig } from '../../config' ;
8
8
import { logError } from '../../error-tracking' ;
9
9
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
+ > {
11
17
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
+
15
30
const release = await response . json ( ) ;
16
31
const apkAsset = release . assets . filter ( ( a : any ) => a . name === "httptoolkit.apk" ) [ 0 ] ;
17
32
const releaseName = release . name || release . tag_name ;
@@ -119,8 +134,18 @@ export async function streamLatestApk(config: HtkConfig): Promise<stream.Readabl
119
134
if ( ! latestApkRelease ) {
120
135
throw new Error ( "Couldn't find an Android APK locally or remotely" ) ;
121
136
} else {
137
+ // No APK locally, but we can get one remotely:
138
+
122
139
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 ;
124
149
125
150
// We buffer output into two passthrough streams, so both file & install
126
151
// 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
130
155
const apkOutputStream = new stream . PassThrough ( { highWaterMark : 10485760 } ) ;
131
156
apkStream . pipe ( apkOutputStream ) ;
132
157
133
- updateLocalApk ( latestApkRelease . version , apkFileStream , config ) . catch ( logError ) ;
158
+ if ( latestApkRelease !== 'unknown-latest' ) {
159
+ updateLocalApk ( latestApkRelease . version , apkFileStream , config ) . catch ( logError ) ;
160
+ }
161
+
134
162
return apkOutputStream ;
135
163
}
136
164
}
137
165
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
+ ) {
139
173
console . log ( 'Streaming local APK' ) ;
140
174
// If we have an APK locally and it's up to date, or we can't tell, just use it
141
175
return fs . createReadStream ( localApk . path ) ;
0 commit comments