From 57bebddbf834cda873119a9fd9252699822b1fa0 Mon Sep 17 00:00:00 2001 From: nmloc Date: Sun, 18 Oct 2026 19:08:30 +1300 Subject: [PATCH] fix(package_info_plus): resolve relative web base URLs A relative engine assetBase (e.g. '/static/companion/flutter/', which the engine documents for embedded apps) or a relative custom baseUrl made versionJsonUrl call Uri.origin on a URI without a scheme. The StateError escaped getAll, so fromPlatform threw instead of trying the next location. Resolve the base URL against document.baseURI first, the same base the browser uses for the engine's relative asset requests. Fixes #4005 --- .../package_info_plus_web_test.dart | 55 +++++++++++++++++++ .../lib/src/package_info_plus_web.dart | 4 +- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/packages/package_info_plus/package_info_plus/example/integration_test/package_info_plus_web_test.dart b/packages/package_info_plus/package_info_plus/example/integration_test/package_info_plus_web_test.dart index 1ba5fa7b48..cdb5e6a58f 100644 --- a/packages/package_info_plus/package_info_plus/example/integration_test/package_info_plus_web_test.dart +++ b/packages/package_info_plus/package_info_plus/example/integration_test/package_info_plus_web_test.dart @@ -234,6 +234,28 @@ void main() { Uri.parse('file:///a/b/c/version.json?cachebuster=1'), ); }); + + testWidgets('Get correct versionJsonUrl for relative base URLs', ( + tester, + ) async { + final Uri rootRelative = plugin.versionJsonUrl( + '/static/companion/flutter/', + 1, + ); + expect( + rootRelative.isScheme('http') || rootRelative.isScheme('https'), + isTrue, + ); + expect(rootRelative.path, '/static/companion/flutter/version.json'); + expect(rootRelative.query, 'cachebuster=1'); + + final Uri pathRelative = plugin.versionJsonUrl('flutter/', 1); + expect( + pathRelative.isScheme('http') || pathRelative.isScheme('https'), + isTrue, + ); + expect(pathRelative.path, endsWith('/flutter/version.json')); + }); }); group('Package Info Web (using MockAssetManager)', () { @@ -273,6 +295,39 @@ void main() { }); }); + testWidgets( + 'Get correct values when the AssetManager baseUrl is relative', + (tester) async { + // A relative engine `assetBase`, e.g. '/static/companion/flutter/', is + // supported by the engine for apps embedded in another page. + const String assetBase = '/static/companion/flutter/'; + const String assetsDir = 'assets'; + + when(assetManagerMock.assetsDir).thenReturn(assetsDir); + when( + assetManagerMock.getAssetUrl(''), + ).thenReturn('$assetBase$assetsDir/'); + when( + client.get( + argThat( + predicate( + (uri) => uri.path == '/static/companion/flutter/version.json', + ), + ), + ), + ).thenAnswer( + (_) => Future.value(http.Response(jsonEncode(VERSION_JSON), 200)), + ); + + final versionMap = await plugin.getAll(); + + 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']); + }, + ); + testWidgets( 'Has preference for the custom base URL over the other 2 locations', (tester) async { diff --git a/packages/package_info_plus/package_info_plus/lib/src/package_info_plus_web.dart b/packages/package_info_plus/package_info_plus/lib/src/package_info_plus_web.dart index 3e884cae36..b7018781bd 100644 --- a/packages/package_info_plus/package_info_plus/lib/src/package_info_plus_web.dart +++ b/packages/package_info_plus/package_info_plus/lib/src/package_info_plus_web.dart @@ -26,7 +26,9 @@ class PackageInfoPlusWebPlugin extends PackageInfoPlatform { /// Get version.json full url. Uri versionJsonUrl(String baseUrl, int cacheBuster) { - final baseUri = Uri.parse(baseUrl); + // A relative base URL (e.g. an engine `assetBase` of '/app/') has no + // origin, so resolve it against the document like the browser does. + final baseUri = Uri.parse(web.window.document.baseURI).resolve(baseUrl); final regExp = RegExp(r'[^/]+\.html.*'); final String originPath = '${baseUri._origin}${baseUri.path.replaceAll(regExp, '')}';