From 78d151881edd1c9aea7f727ebb94b1ff611cc28e Mon Sep 17 00:00:00 2001 From: Miduo666 Date: Tue, 3 Mar 2026 00:12:06 +1100 Subject: [PATCH 1/6] miduo/224_perf: Optimize login latency and reduce redundant I/O in solidui/pod/auth --- lib/solid_auth_issuer.dart | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/lib/solid_auth_issuer.dart b/lib/solid_auth_issuer.dart index 4105772..0eb2326 100644 --- a/lib/solid_auth_issuer.dart +++ b/lib/solid_auth_issuer.dart @@ -32,11 +32,42 @@ import 'dart:async'; import 'package:http/http.dart' as http; -/// Get POD issuer URI +/// In-memory cache: maps a server URL / WebID to its resolved OIDC issuer URI. +/// The issuer URI for a given Solid server never changes at runtime, so a +/// simple process-lifetime cache is safe and avoids repeated HTTP round-trips. +final Map _issuerCache = {}; + +/// Profile-body cache: maps the plain profile card URL (fragment stripped) to +/// the Turtle body fetched during [getIssuer]. +/// +/// When the user enters a WebID URL such as `https://example.org/profile/card#me`, +/// [getIssuer] must fetch the profile document to extract the OIDC issuer URI. +/// The same document is needed again after authentication to populate profile +/// data. Caching it here avoids a redundant (second) HTTP round-trip. +final Map _profileBodyCache = {}; + +/// Returns the profile card body that was fetched during [getIssuer], or `null` +/// if the profile has not been fetched yet (e.g. the server URL is a plain +/// issuer URI rather than a WebID URL). +String? getCachedIssuerProfileBody(String profUrl) => + _profileBodyCache[profUrl]; + +/// Get POD issuer URI. +/// +/// Results are cached in memory so that repeated calls for the same [textUrl] +/// (e.g. when the user logs out and back in) skip the network look-up. Future getIssuer(String textUrl) async { + // Return cached result immediately when available. + if (_issuerCache.containsKey(textUrl)) { + return _issuerCache[textUrl]!; + } + String issuerUri = ''; if (textUrl.contains('profile/card#me')) { String pubProf = await fetchProfileData(textUrl); + // Cache the profile body under the plain URL (without #me fragment). + // authenticate.dart can reuse this to skip a second HTTP GET. + _profileBodyCache[textUrl.replaceAll('#me', '')] = pubProf; issuerUri = getIssuerUri(pubProf); } @@ -48,6 +79,11 @@ Future getIssuer(String textUrl) async { issuerUri = textUrl.substring(match.start, match.end); } } + + if (issuerUri.isNotEmpty) { + _issuerCache[textUrl] = issuerUri; + } + return issuerUri; } From 7dea35a4e80bf90b531254787c98315c2928de14 Mon Sep 17 00:00:00 2001 From: Miduo666 Date: Tue, 10 Mar 2026 09:47:49 +1100 Subject: [PATCH 2/6] run comment_fixer.py and add blank line after comments --- lib/solid_auth_issuer.dart | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/solid_auth_issuer.dart b/lib/solid_auth_issuer.dart index 0eb2326..705f6f4 100644 --- a/lib/solid_auth_issuer.dart +++ b/lib/solid_auth_issuer.dart @@ -35,6 +35,7 @@ import 'package:http/http.dart' as http; /// In-memory cache: maps a server URL / WebID to its resolved OIDC issuer URI. /// The issuer URI for a given Solid server never changes at runtime, so a /// simple process-lifetime cache is safe and avoids repeated HTTP round-trips. + final Map _issuerCache = {}; /// Profile-body cache: maps the plain profile card URL (fragment stripped) to @@ -44,11 +45,13 @@ final Map _issuerCache = {}; /// [getIssuer] must fetch the profile document to extract the OIDC issuer URI. /// The same document is needed again after authentication to populate profile /// data. Caching it here avoids a redundant (second) HTTP round-trip. + final Map _profileBodyCache = {}; /// Returns the profile card body that was fetched during [getIssuer], or `null` /// if the profile has not been fetched yet (e.g. the server URL is a plain /// issuer URI rather than a WebID URL). + String? getCachedIssuerProfileBody(String profUrl) => _profileBodyCache[profUrl]; @@ -56,6 +59,7 @@ String? getCachedIssuerProfileBody(String profUrl) => /// /// Results are cached in memory so that repeated calls for the same [textUrl] /// (e.g. when the user logs out and back in) skip the network look-up. + Future getIssuer(String textUrl) async { // Return cached result immediately when available. if (_issuerCache.containsKey(textUrl)) { @@ -65,14 +69,16 @@ Future getIssuer(String textUrl) async { String issuerUri = ''; if (textUrl.contains('profile/card#me')) { String pubProf = await fetchProfileData(textUrl); + // Cache the profile body under the plain URL (without #me fragment). // authenticate.dart can reuse this to skip a second HTTP GET. + _profileBodyCache[textUrl.replaceAll('#me', '')] = pubProf; issuerUri = getIssuerUri(pubProf); } if (issuerUri == '') { - /// This reg expression works with localhost and other urls + /// This reg expression works with localhost and other urls. RegExp exp = RegExp(r'(?:(?:https?|ftp):\/\/)?[\w/\-?=%.]+(\.|\:)[\w\.]+'); Iterable matches = exp.allMatches(textUrl); for (var match in matches) { @@ -87,7 +93,8 @@ Future getIssuer(String textUrl) async { return issuerUri; } -/// Get public profile information from webId +/// Get public profile information from webId. + Future fetchProfileData(String profUrl) async { final response = await http.get( Uri.parse(profUrl), @@ -107,7 +114,8 @@ Future fetchProfileData(String profUrl) async { } } -/// Read public profile RDF file and get the issuer URI +/// Read public profile RDF file and get the issuer URI. + String getIssuerUri(String profileRdfStr) { String issuerUri = ''; var profileDataList = profileRdfStr.split('\n'); From 2692981aa65398f145e95310bdd1af2c4bf3296d Mon Sep 17 00:00:00 2001 From: Miduo666 Date: Tue, 10 Mar 2026 10:05:56 +1100 Subject: [PATCH 3/6] fix: normalize issuer cache keys and profile URL fragments per Copilot review --- lib/solid_auth_issuer.dart | 39 ++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/lib/solid_auth_issuer.dart b/lib/solid_auth_issuer.dart index 705f6f4..ab531cb 100644 --- a/lib/solid_auth_issuer.dart +++ b/lib/solid_auth_issuer.dart @@ -48,12 +48,31 @@ final Map _issuerCache = {}; final Map _profileBodyCache = {}; +/// Strips any URI fragment (e.g. `#me`) from [profUrl] so that cache keys are +/// canonical regardless of whether the caller includes a fragment. + +String _normalizeProfileUrl(String profUrl) { + final int hashIndex = profUrl.indexOf('#'); + return hashIndex == -1 ? profUrl : profUrl.substring(0, hashIndex); +} + /// Returns the profile card body that was fetched during [getIssuer], or `null` /// if the profile has not been fetched yet (e.g. the server URL is a plain /// issuer URI rather than a WebID URL). +/// The [profUrl] fragment (if any) is stripped before the look-up so callers +/// may pass either the bare document URL or a full WebID URL. + String? getCachedIssuerProfileBody(String profUrl) => - _profileBodyCache[profUrl]; + _profileBodyCache[_normalizeProfileUrl(profUrl)]; + +/// Clears both in-memory caches. +/// Useful in tests or whenever server configuration may have changed. + +void clearIssuerCaches() { + _issuerCache.clear(); + _profileBodyCache.clear(); +} /// Get POD issuer URI. /// @@ -61,19 +80,23 @@ String? getCachedIssuerProfileBody(String profUrl) => /// (e.g. when the user logs out and back in) skip the network look-up. Future getIssuer(String textUrl) async { + // Normalize the key so that trivially different representations of the same + // URL (e.g. percent-encoding variants) share a single cache entry. + final String cacheKey = Uri.parse(textUrl).toString(); + // Return cached result immediately when available. - if (_issuerCache.containsKey(textUrl)) { - return _issuerCache[textUrl]!; + if (_issuerCache.containsKey(cacheKey)) { + return _issuerCache[cacheKey]!; } String issuerUri = ''; if (textUrl.contains('profile/card#me')) { String pubProf = await fetchProfileData(textUrl); - // Cache the profile body under the plain URL (without #me fragment). - // authenticate.dart can reuse this to skip a second HTTP GET. - - _profileBodyCache[textUrl.replaceAll('#me', '')] = pubProf; + // Cache the profile body under the plain profile document URL (fragment + // stripped). solidpod/authenticate.dart can reuse this to skip a second + // HTTP GET. + _profileBodyCache[_normalizeProfileUrl(textUrl)] = pubProf; issuerUri = getIssuerUri(pubProf); } @@ -87,7 +110,7 @@ Future getIssuer(String textUrl) async { } if (issuerUri.isNotEmpty) { - _issuerCache[textUrl] = issuerUri; + _issuerCache[cacheKey] = issuerUri; } return issuerUri; From 7ae42fb872ddd87b3b6ba4e07d90e6e13dd1fc45 Mon Sep 17 00:00:00 2001 From: Miduo666 Date: Tue, 10 Mar 2026 10:08:09 +1100 Subject: [PATCH 4/6] small format change --- lib/solid_auth_issuer.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/solid_auth_issuer.dart b/lib/solid_auth_issuer.dart index ab531cb..6982f63 100644 --- a/lib/solid_auth_issuer.dart +++ b/lib/solid_auth_issuer.dart @@ -85,6 +85,7 @@ Future getIssuer(String textUrl) async { final String cacheKey = Uri.parse(textUrl).toString(); // Return cached result immediately when available. + if (_issuerCache.containsKey(cacheKey)) { return _issuerCache[cacheKey]!; } @@ -96,6 +97,7 @@ Future getIssuer(String textUrl) async { // Cache the profile body under the plain profile document URL (fragment // stripped). solidpod/authenticate.dart can reuse this to skip a second // HTTP GET. + _profileBodyCache[_normalizeProfileUrl(textUrl)] = pubProf; issuerUri = getIssuerUri(pubProf); } From ed5d833f6472f39af36b26cf3090edc5a3bc06c0 Mon Sep 17 00:00:00 2001 From: Miduo666 Date: Thu, 12 Mar 2026 00:25:22 +1100 Subject: [PATCH 5/6] fix: trim textUrl in getIssuer to handle leading/trailing whitespace from any caller --- lib/solid_auth_issuer.dart | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/solid_auth_issuer.dart b/lib/solid_auth_issuer.dart index 6982f63..81cf7c3 100644 --- a/lib/solid_auth_issuer.dart +++ b/lib/solid_auth_issuer.dart @@ -80,9 +80,15 @@ void clearIssuerCaches() { /// (e.g. when the user logs out and back in) skip the network look-up. Future getIssuer(String textUrl) async { + // Trim leading/trailing whitespace defensively so that solid_auth behaves + // correctly regardless of whether the caller has already sanitised the input. + final String trimmed = textUrl.trim(); + // Normalize the key so that trivially different representations of the same // URL (e.g. percent-encoding variants) share a single cache entry. - final String cacheKey = Uri.parse(textUrl).toString(); + // Uri.tryParse is used instead of Uri.parse to avoid a FormatException for + // malformed inputs; fall back to the trimmed string if parsing fails. + final String cacheKey = Uri.tryParse(trimmed)?.toString() ?? trimmed; // Return cached result immediately when available. @@ -91,23 +97,23 @@ Future getIssuer(String textUrl) async { } String issuerUri = ''; - if (textUrl.contains('profile/card#me')) { - String pubProf = await fetchProfileData(textUrl); + if (trimmed.contains('profile/card#me')) { + String pubProf = await fetchProfileData(trimmed); // Cache the profile body under the plain profile document URL (fragment // stripped). solidpod/authenticate.dart can reuse this to skip a second // HTTP GET. - _profileBodyCache[_normalizeProfileUrl(textUrl)] = pubProf; + _profileBodyCache[_normalizeProfileUrl(trimmed)] = pubProf; issuerUri = getIssuerUri(pubProf); } if (issuerUri == '') { /// This reg expression works with localhost and other urls. RegExp exp = RegExp(r'(?:(?:https?|ftp):\/\/)?[\w/\-?=%.]+(\.|\:)[\w\.]+'); - Iterable matches = exp.allMatches(textUrl); + Iterable matches = exp.allMatches(trimmed); for (var match in matches) { - issuerUri = textUrl.substring(match.start, match.end); + issuerUri = trimmed.substring(match.start, match.end); } } From a252c37ba67ae8e951029b6551850fa627199a8f Mon Sep 17 00:00:00 2001 From: Miduo666 Date: Thu, 12 Mar 2026 10:20:06 +1100 Subject: [PATCH 6/6] lint --- lib/solid_auth_issuer.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solid_auth_issuer.dart b/lib/solid_auth_issuer.dart index 81cf7c3..0a7b087 100644 --- a/lib/solid_auth_issuer.dart +++ b/lib/solid_auth_issuer.dart @@ -59,7 +59,7 @@ String _normalizeProfileUrl(String profUrl) { /// Returns the profile card body that was fetched during [getIssuer], or `null` /// if the profile has not been fetched yet (e.g. the server URL is a plain /// issuer URI rather than a WebID URL). - +/// /// The [profUrl] fragment (if any) is stripped before the look-up so callers /// may pass either the bare document URL or a full WebID URL.