Skip to content

Commit f650839

Browse files
author
Ivan Yuryevich
committed
Fix Android updater GitHub download reliability
1 parent 6b0bb75 commit f650839

5 files changed

Lines changed: 145 additions & 94 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# Changelog
22

3+
## 1.0.80 - 2026-06-20
4+
5+
- Increased GitHub Releases updater network timeouts and retry backoff for
6+
slow or filtered mobile networks.
7+
- Added support for the current versioned APK asset name
8+
`Yurich-Connect-Android-vX.Y.Z.apk`.
9+
- The manual APK fallback now opens the exact APK found by the update check
10+
instead of an older static release filename.
11+
- Kept VPN protocols, Smart Route, and routing behavior unchanged.
12+
313
## 1.0.79 - 2026-06-20
414

515
- Strengthened the native VPN keeper for night idle and mobile network changes.

lib/src/screens/home_screen.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2652,7 +2652,11 @@ class _HomeScreenState extends State<HomeScreen>
26522652
action: SnackBarAction(
26532653
label: s.downloadApk,
26542654
onPressed: () => unawaited(
2655-
_openUrl(AppUpdateService.latestApkDownloadUri.toString()),
2655+
_openUrl(
2656+
(_availableUpdate?.downloadUrl ??
2657+
AppUpdateService.latestApkDownloadUri)
2658+
.toString(),
2659+
),
26562660
),
26572661
),
26582662
);

lib/src/services/app_update_service.dart

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@ const _releaseApiUrls = [
1111
const _githubRepository = 'ivan-yurich/Yurich-Connect-Android';
1212
const _githubReleaseAssetName = 'YurichConnect-android-release.apk';
1313
const _updaterUserAgent = 'YurichConnect-Updater';
14+
const _updateConnectTimeout = Duration(seconds: 30);
15+
const _updateMetadataTimeout = Duration(seconds: 25);
16+
const _updateDownloadOpenTimeout = Duration(seconds: 60);
1417
const _updateRetryDelays = [
15-
Duration(seconds: 1),
16-
Duration(seconds: 3),
17-
Duration(seconds: 6),
18+
Duration(seconds: 2),
19+
Duration(seconds: 5),
20+
Duration(seconds: 10),
21+
Duration(seconds: 20),
1822
];
1923

2024
class AppUpdateInfo {
@@ -55,7 +59,7 @@ class AppUpdateService {
5559
_releaseApiUris =
5660
releaseApiUris ??
5761
_releaseApiUrls.map(Uri.parse).toList(growable: false) {
58-
_client.connectionTimeout = const Duration(seconds: 7);
62+
_client.connectionTimeout = _updateConnectTimeout;
5963
}
6064

6165
static const _channel = MethodChannel('online.dnsai.ivanvpn/updater');
@@ -180,7 +184,7 @@ class AppUpdateService {
180184
'application/vnd.android.package-archive, application/octet-stream, */*',
181185
);
182186
request.followRedirects = true;
183-
final response = await request.close().timeout(const Duration(seconds: 30));
187+
final response = await request.close().timeout(_updateDownloadOpenTimeout);
184188
if (response.statusCode < 200 || response.statusCode >= 300) {
185189
await response.drain<void>();
186190
throw _UpdateHttpException(response.statusCode);
@@ -297,9 +301,7 @@ class AppUpdateService {
297301
request.headers.set(HttpHeaders.acceptHeader, 'application/json');
298302
request.headers.set(HttpHeaders.userAgentHeader, _updaterUserAgent);
299303
request.followRedirects = true;
300-
final response = await request.close().timeout(
301-
const Duration(seconds: 14),
302-
);
304+
final response = await request.close().timeout(_updateMetadataTimeout);
303305
if (response.statusCode == HttpStatus.notFound) {
304306
await response.drain<void>();
305307
return null;
@@ -360,9 +362,7 @@ class AppUpdateService {
360362
final request = await _client.getUrl(uri);
361363
request.headers.set(HttpHeaders.userAgentHeader, _updaterUserAgent);
362364
request.followRedirects = false;
363-
final response = await request.close().timeout(
364-
const Duration(seconds: 14),
365-
);
365+
final response = await request.close().timeout(_updateMetadataTimeout);
366366
final location = response.headers.value(HttpHeaders.locationHeader);
367367
await response.drain<void>();
368368

@@ -403,9 +403,7 @@ class AppUpdateService {
403403
final request = await _client.headUrl(uri);
404404
request.headers.set(HttpHeaders.userAgentHeader, _updaterUserAgent);
405405
request.followRedirects = true;
406-
final response = await request.close().timeout(
407-
const Duration(seconds: 14),
408-
);
406+
final response = await request.close().timeout(_updateMetadataTimeout);
409407
final length = response.contentLength;
410408
await response.drain<void>();
411409
return length > 0 ? length : null;
@@ -439,6 +437,7 @@ class AppUpdateService {
439437
'YurichConnect-android-armeabi-v7a-v$normalized.apk',
440438
if (supportedAbis.contains('x86_64'))
441439
'YurichConnect-android-x86_64-v$normalized.apk',
440+
'Yurich-Connect-Android-v$normalized.apk',
442441
_githubReleaseAssetName,
443442
];
444443
}

pubspec.yaml

Lines changed: 79 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,60 @@
11
name: aurum_vpn
22
description: "Yurich Connect: Flutter VPN client powered by sing-box."
3-
# The following line prevents the package from being accidentally published to
4-
# pub.dev using `flutter pub publish`. This is preferred for private packages.
5-
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
6-
7-
# The following defines the version and build number for your application.
8-
# A version number is three numbers separated by dots, like 1.2.43
9-
# followed by an optional build number separated by a +.
10-
# Both the version and the builder number may be overridden in flutter
11-
# build by specifying --build-name and --build-number, respectively.
12-
# In Android, build-name is used as versionName while build-number used as versionCode.
13-
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
14-
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
15-
# Read more about iOS versioning at
16-
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
17-
version: 1.0.79+80
18-
19-
environment:
20-
sdk: ^3.11.5
21-
22-
# Dependencies specify other packages that your package needs in order to work.
23-
# To automatically upgrade your package dependencies to the latest versions
24-
# consider running `flutter pub upgrade --major-versions`. Alternatively,
25-
# dependencies can be manually updated by changing the version numbers below to
26-
# the latest version available on pub.dev. To see which dependencies have newer
27-
# versions available, run `flutter pub outdated`.
28-
dependencies:
29-
flutter:
30-
sdk: flutter
31-
32-
# The following adds the Cupertino Icons font to your application.
33-
# Use with the CupertinoIcons class for iOS style icons.
34-
cupertino_icons: ^1.0.8
3+
# The following line prevents the package from being accidentally published to
4+
# pub.dev using `flutter pub publish`. This is preferred for private packages.
5+
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
6+
7+
# The following defines the version and build number for your application.
8+
# A version number is three numbers separated by dots, like 1.2.43
9+
# followed by an optional build number separated by a +.
10+
# Both the version and the builder number may be overridden in flutter
11+
# build by specifying --build-name and --build-number, respectively.
12+
# In Android, build-name is used as versionName while build-number used as versionCode.
13+
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
14+
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
15+
# Read more about iOS versioning at
16+
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
17+
version: 1.0.80+81
18+
19+
environment:
20+
sdk: ^3.11.5
21+
22+
# Dependencies specify other packages that your package needs in order to work.
23+
# To automatically upgrade your package dependencies to the latest versions
24+
# consider running `flutter pub upgrade --major-versions`. Alternatively,
25+
# dependencies can be manually updated by changing the version numbers below to
26+
# the latest version available on pub.dev. To see which dependencies have newer
27+
# versions available, run `flutter pub outdated`.
28+
dependencies:
29+
flutter:
30+
sdk: flutter
31+
32+
# The following adds the Cupertino Icons font to your application.
33+
# Use with the CupertinoIcons class for iOS style icons.
34+
cupertino_icons: ^1.0.8
3535
flutter_singbox_vpn:
3636
path: plugins/flutter_singbox_vpn
37-
mobile_scanner: ^7.2.0
37+
mobile_scanner: ^7.2.0
3838
shared_preferences: ^2.5.5
3939
qr_flutter: ^4.1.0
4040
url_launcher: ^6.3.2
41-
package_info_plus: ^10.1.0
42-
43-
dev_dependencies:
44-
flutter_test:
45-
sdk: flutter
46-
47-
# The "flutter_lints" package below contains a set of recommended lints to
48-
# encourage good coding practices. The lint set provided by the package is
49-
# activated in the `analysis_options.yaml` file located at the root of your
50-
# package. See that file for information about deactivating specific lint
51-
# rules and activating additional ones.
52-
flutter_lints: ^6.0.0
53-
54-
# For information on the generic Dart part of this file, see the
55-
# following page: https://dart.dev/tools/pub/pubspec
56-
57-
# The following section is specific to Flutter packages.
41+
package_info_plus: ^10.1.0
42+
43+
dev_dependencies:
44+
flutter_test:
45+
sdk: flutter
46+
47+
# The "flutter_lints" package below contains a set of recommended lints to
48+
# encourage good coding practices. The lint set provided by the package is
49+
# activated in the `analysis_options.yaml` file located at the root of your
50+
# package. See that file for information about deactivating specific lint
51+
# rules and activating additional ones.
52+
flutter_lints: ^6.0.0
53+
54+
# For information on the generic Dart part of this file, see the
55+
# following page: https://dart.dev/tools/pub/pubspec
56+
57+
# The following section is specific to Flutter packages.
5858
flutter:
5959

6060
# The following line ensures that the Material Icons font is
@@ -65,32 +65,32 @@ flutter:
6565
- assets/images/app_icon.png
6666

6767
# To add assets to your application, add an assets section, like this:
68-
# assets:
69-
# - images/a_dot_burr.jpeg
70-
# - images/a_dot_ham.jpeg
71-
72-
# An image asset can refer to one or more resolution-specific "variants", see
73-
# https://flutter.dev/to/resolution-aware-images
74-
75-
# For details regarding adding assets from package dependencies, see
76-
# https://flutter.dev/to/asset-from-package
77-
78-
# To add custom fonts to your application, add a fonts section here,
79-
# in this "flutter" section. Each entry in this list should have a
80-
# "family" key with the font family name, and a "fonts" key with a
81-
# list giving the asset and other descriptors for the font. For
82-
# example:
83-
# fonts:
84-
# - family: Schyler
85-
# fonts:
86-
# - asset: fonts/Schyler-Regular.ttf
87-
# - asset: fonts/Schyler-Italic.ttf
88-
# style: italic
89-
# - family: Trajan Pro
90-
# fonts:
91-
# - asset: fonts/TrajanPro.ttf
92-
# - asset: fonts/TrajanPro_Bold.ttf
93-
# weight: 700
94-
#
95-
# For details regarding fonts from package dependencies,
96-
# see https://flutter.dev/to/font-from-package
68+
# assets:
69+
# - images/a_dot_burr.jpeg
70+
# - images/a_dot_ham.jpeg
71+
72+
# An image asset can refer to one or more resolution-specific "variants", see
73+
# https://flutter.dev/to/resolution-aware-images
74+
75+
# For details regarding adding assets from package dependencies, see
76+
# https://flutter.dev/to/asset-from-package
77+
78+
# To add custom fonts to your application, add a fonts section here,
79+
# in this "flutter" section. Each entry in this list should have a
80+
# "family" key with the font family name, and a "fonts" key with a
81+
# list giving the asset and other descriptors for the font. For
82+
# example:
83+
# fonts:
84+
# - family: Schyler
85+
# fonts:
86+
# - asset: fonts/Schyler-Regular.ttf
87+
# - asset: fonts/Schyler-Italic.ttf
88+
# style: italic
89+
# - family: Trajan Pro
90+
# fonts:
91+
# - asset: fonts/TrajanPro.ttf
92+
# - asset: fonts/TrajanPro_Bold.ttf
93+
# weight: 700
94+
#
95+
# For details regarding fonts from package dependencies,
96+
# see https://flutter.dev/to/font-from-package

test/app_update_service_test.dart

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,44 @@ void main() {
9191
expect(update.assetName, 'YurichConnect-android-arm64-v8a-v1.0.62.apk');
9292
});
9393

94+
test('accepts versioned GitHub release APK name', () async {
95+
final server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);
96+
addTearDown(() => server.close(force: true));
97+
98+
server.listen((request) async {
99+
final payload = {
100+
'tag_name': 'v1.0.80',
101+
'assets': [
102+
{
103+
'name': 'Yurich-Connect-Android-v1.0.80.apk',
104+
'browser_download_url':
105+
'http://127.0.0.1:${server.port}/versioned.apk',
106+
'size': 199738363,
107+
},
108+
],
109+
};
110+
request.response
111+
..statusCode = HttpStatus.ok
112+
..headers.contentType = ContentType.json
113+
..write(jsonEncode(payload));
114+
await request.response.close();
115+
});
116+
117+
final service = AppUpdateService(
118+
releaseApiUris: [Uri.parse('http://127.0.0.1:${server.port}/latest')],
119+
);
120+
121+
final update = await service.findLatest(
122+
currentVersion: '1.0.79',
123+
supportedAbis: const ['arm64-v8a'],
124+
);
125+
126+
expect(update, isNotNull);
127+
expect(update!.version, '1.0.80');
128+
expect(update.assetName, 'Yurich-Connect-Android-v1.0.80.apk');
129+
expect(update.downloadUrl.path, '/versioned.apk');
130+
});
131+
94132
test(
95133
'reuses a complete downloaded APK instead of downloading again',
96134
() async {

0 commit comments

Comments
 (0)