Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(package_info_plus)!: Support multiple version.json locations #2733

Merged
merged 9 commits into from
Mar 23, 2024
Merged
29 changes: 22 additions & 7 deletions packages/package_info_plus/package_info_plus/example/.metadata
Original file line number Diff line number Diff line change
@@ -1,23 +1,38 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled.
# This file should be version controlled and should not be manually edited.

version:
revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
channel: stable
revision: "ba393198430278b6595976de84fe170f553cc728"
channel: "stable"

project_type: app

# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
create_revision: ba393198430278b6595976de84fe170f553cc728
base_revision: ba393198430278b6595976de84fe170f553cc728
- platform: android
create_revision: ba393198430278b6595976de84fe170f553cc728
base_revision: ba393198430278b6595976de84fe170f553cc728
- platform: ios
create_revision: ba393198430278b6595976de84fe170f553cc728
base_revision: ba393198430278b6595976de84fe170f553cc728
- platform: linux
create_revision: ba393198430278b6595976de84fe170f553cc728
base_revision: ba393198430278b6595976de84fe170f553cc728
- platform: macos
create_revision: ba393198430278b6595976de84fe170f553cc728
base_revision: ba393198430278b6595976de84fe170f553cc728
- platform: web
create_revision: ba393198430278b6595976de84fe170f553cc728
base_revision: ba393198430278b6595976de84fe170f553cc728
- platform: windows
create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
create_revision: ba393198430278b6595976de84fe170f553cc728
base_revision: ba393198430278b6595976de84fe170f553cc728

# User provided section

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ library package_info_plus_web_test;

import 'dart:convert';

import 'package:clock/clock.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:integration_test/integration_test.dart';
Expand Down Expand Up @@ -78,6 +79,35 @@ void main() {
},
);

testWidgets(
'Get correct values when using a custom base URL',
(tester) async {
const String baseUrl = 'https://www.example.com/';
final DateTime now = DateTime.now();
final Clock fakeClock = Clock(() => now);

await withClock(fakeClock, () async {
final int cache = now.millisecondsSinceEpoch;

when(client.get(
Uri.parse('${baseUrl}version.json?cachebuster=$cache'),
)).thenAnswer(
(_) => Future.value(
http.Response(jsonEncode(VERSION_JSON), 200),
),
);

final versionMap = await plugin.getAll(baseUrl: baseUrl);

expect(versionMap.appName, VERSION_JSON['app_name']);
expect(versionMap.version, VERSION_JSON['version']);
expect(versionMap.buildNumber, VERSION_JSON['build_number']);
expect(versionMap.packageName, VERSION_JSON['package_name']);
expect(versionMap.buildSignature, VERSION_JSON['build_signature']);
});
},
);

testWidgets(
'Get correct versionJsonUrl for http and https',
(tester) async {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ environment:
sdk: '>=2.18.0 <4.0.0'

dependencies:
clock: ^1.1.1
flutter:
sdk: flutter
http: ">=0.13.5 <2.0.0"
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<!--
If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from.

The path provided below has to start and end with a slash "/" in order for
it to work correctly.

For more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base

This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`.
-->
<base href="$FLUTTER_BASE_HREF">

<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">
Expand All @@ -16,18 +31,29 @@

<title>example</title>
<link rel="manifest" href="manifest.json">

<script>
// The value below is injected by flutter build, do not touch.
const serviceWorkerVersion = null;
</script>
<!-- This script adds the flutter initialization JS code -->
<script src="flutter.js" defer></script>
</head>
<body>
<!-- This script installs service_worker.js to provide PWA functionality to
application. For more information, see:
https://developers.google.com/web/fundamentals/primers/service-workers -->
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('flutter_service_worker.js');
window.addEventListener('load', function(ev) {
// Download main.dart.js
_flutter.loader.loadEntrypoint({
serviceWorker: {
serviceWorkerVersion: serviceWorkerVersion,
},
onEntrypointLoaded: function(engineInitializer) {
engineInitializer.initializeEngine().then(function(appRunner) {
appRunner.runApp();
});
}
});
}
});
</script>
<script src="main.dart.js" type="application/javascript"></script>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,54 @@ class PackageInfo {

/// Retrieves package information from the platform.
/// The result is cached.
static Future<PackageInfo> fromPlatform() async {
///
/// The [baseUrl] parameter is for web use only and the other platforms will
/// ignore it.
///
/// ## Web platform
///
/// In a web environment, the package uses the `version.json` file that it is
/// generated in the build process.
///
/// The package will try to locate this file in 3 ways:
///
/// * If you provide the optional custom [baseUrl] parameter, it will be
/// used as the first option where to search. Example:
///
/// ```dart
/// await PackageInfo.fromPlatform(baseUrl: 'https://cdn.domain.com/with/some/path/');
/// ```
///
/// With this, the package will try to search the file in `https://cdn.domain.com/with/some/path/version.json`
///
/// * The second option where it will search is the [assetBase] parameter
/// that you can pass to the Flutter Web Engine when you initialize it.
///
/// ```javascript
/// _flutter.loader.loadEntrypoint({
/// onEntrypointLoaded: async function(engineInitializer) {
/// let appRunner = await engineInitializer.initializeEngine({
/// assetBase: "https://cdn.domain.com/with/some/path/"
/// });
/// appRunner.runApp();
/// }
/// });
/// ```
///
/// For more information about the Flutter Web Engine initialization see here:
/// https://docs.flutter.dev/platform-integration/web/initialization#initializing-the-engine
///
/// * Finally, if none of the previous locations return the `version.json` file,
/// the package will use the browser window base URL to resolve its location.
static Future<PackageInfo> fromPlatform({String? baseUrl}) async {
miquelbeltran marked this conversation as resolved.
Show resolved Hide resolved
if (_fromPlatform != null) {
return _fromPlatform!;
}

final platformData = await PackageInfoPlatform.instance.getAll();
final platformData = await PackageInfoPlatform.instance.getAll(
baseUrl: baseUrl,
);

_fromPlatform = PackageInfo(
appName: platformData.appName,
packageName: platformData.packageName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class PackageInfoPlusLinuxPlugin extends PackageInfoPlatform {
/// Returns a map with the following keys:
/// appName, packageName, version, buildNumber
@override
Future<PackageInfoData> getAll() async {
Future<PackageInfoData> getAll({String? baseUrl}) async {
final versionJson = await _getVersionJson();
return PackageInfoData(
appName: versionJson['app_name'] ?? '',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import 'dart:convert';
import 'dart:ui_web';

import 'package:clock/clock.dart';
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
import 'package:http/http.dart';
import 'package:package_info_plus_platform_interface/package_info_data.dart';
Expand Down Expand Up @@ -49,11 +51,13 @@ class PackageInfoPlusWebPlugin extends PackageInfoPlatform {
}

@override
Future<PackageInfoData> getAll() async {
final cacheBuster = DateTime.now().millisecondsSinceEpoch;
final url = versionJsonUrl(web.window.document.baseURI, cacheBuster);
final response = _client == null ? await get(url) : await _client.get(url);
final versionMap = _getVersionMap(response);
Future<PackageInfoData> getAll({String? baseUrl}) async {
final int cacheBuster = clock.now().millisecondsSinceEpoch;
final Map<String, dynamic> versionMap =
await _getVersionMap(baseUrl, cacheBuster) ??
await _getVersionMap(assetManager.baseUrl, cacheBuster) ??
await _getVersionMap(web.window.document.baseURI, cacheBuster) ??
{};

return PackageInfoData(
appName: versionMap['app_name'] ?? '',
Expand All @@ -65,19 +69,50 @@ class PackageInfoPlusWebPlugin extends PackageInfoPlatform {
);
}

Map<String, dynamic> _getVersionMap(Response response) {
Future<Map<String, dynamic>?> _getVersionMap(
String? baseUrl,
int cacheBuster,
) async {
if (baseUrl?.isNotEmpty == true) {
final Uri url = versionJsonUrl(baseUrl!, cacheBuster);
final Response response = await _getResponse(url);

return _decodeVersionMap(response);
}

return null;
}

Future<Response> _getResponse(Uri uri) async {
return _client == null ? await get(uri) : await _client.get(uri);
}

Map<String, dynamic>? _decodeVersionMap(Response response) {
if (response.statusCode == 200) {
try {
return jsonDecode(response.body);
} catch (_) {
return <String, dynamic>{};
return null;
}
} else {
return <String, dynamic>{};
return null;
}
}
}

extension _AssetManager on AssetManager {
/// Get the base URL configured in the Flutter Web Engine initialization
///
/// The AssetManager has the base URL as private ([AssetManager._baseUrl] property),
/// so we need to do some little hack to get it. If AssetManager adds in some
/// moment a public API to get the base URL, this extension can be replaced by that API.
///
/// @see https://docs.flutter.dev/platform-integration/web/initialization#initializing-the-engine
String get baseUrl {
return getAssetUrl('').replaceAll('$assetsDir/', '');
}
}

extension _UriOrigin on Uri {
/// Get origin.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class PackageInfoPlusWindowsPlugin extends PackageInfoPlatform {
/// Returns a map with the following keys:
/// appName, packageName, version, buildNumber
@override
Future<PackageInfoData> getAll() {
Future<PackageInfoData> getAll({String? baseUrl}) {
String resolvedExecutable = Platform.resolvedExecutable;

/// Workaround for https://github.com/dart-lang/sdk/issues/52309
Expand Down
1 change: 1 addition & 0 deletions packages/package_info_plus/package_info_plus/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ dependencies:

# win32 is compatible across v4 and v5 for Win32 only (not COM)
win32: ">=4.0.0 <6.0.0"
clock: ^1.1.1

dev_dependencies:
flutter_lints: ">=2.0.1 <4.0.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const MethodChannel _channel =
/// An implementation of [PackageInfoPlatform] that uses method channels.
class MethodChannelPackageInfo extends PackageInfoPlatform {
@override
Future<PackageInfoData> getAll() async {
Future<PackageInfoData> getAll({String? baseUrl}) async {
final map = await _channel.invokeMapMethod<String, dynamic>('getAll');
return PackageInfoData(
appName: map!['appName'] ?? '',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ abstract class PackageInfoPlatform extends PlatformInterface {
}

///Returns a map with the following keys : appName,packageName,version,buildNumber
Future<PackageInfoData> getAll() {
Future<PackageInfoData> getAll({String? baseUrl}) {
throw UnimplementedError('getAll() has not been implemented.');
}
}
Loading