Skip to content

Commit

Permalink
[deep link] Add ios path from AASA file check result. (flutter#8285)
Browse files Browse the repository at this point in the history
* Add tests

* add key

* 1

* add aasa path

* 1

* lint

* lint

* lint

* resolve comments
  • Loading branch information
hannah-hyj authored Sep 6, 2024
1 parent f4cb8be commit 4eae64d
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -497,10 +497,8 @@ class DeepLinksController extends DisposableController
}
}

Future<List<LinkData>> _validateDomain(
List<LinkData> linkdatas,
) async {
final domains = linkdatas
Future<List<LinkData>> _validateDomain(List<LinkData> rawLinkdatas) async {
final domains = rawLinkdatas
.where(
(linkdata) => linkdata.domain != null,
)
Expand All @@ -511,6 +509,8 @@ class DeepLinksController extends DisposableController
late final Map<String, List<DomainError>> androidDomainErrors;
Map<String, List<DomainError>> iosDomainErrors =
<String, List<DomainError>>{};

late final Map<String, List<String>> iosDomainPaths;
try {
final androidResult = await deepLinksService.validateAndroidDomain(
domains: domains,
Expand All @@ -527,40 +527,68 @@ class DeepLinksController extends DisposableController
domains: domains,
);
iosDomainErrors = iosResult.domainErrors;
iosDomainPaths = iosResult.paths;
}
} catch (_) {
// TODO(hangyujin): Add more error handling for cases like RPC error and invalid json.
pagePhase.value = PagePhase.validationErrorPage;
return linkdatas;
return rawLinkdatas;
}

return linkdatas.map((linkdata) {
final validatedLinkDatas = <LinkData>[];

for (final linkdata in rawLinkdatas) {
final errors = <DomainError>[
if (linkdata.os.contains(PlatformOS.android))
...(androidDomainErrors[linkdata.domain] ?? []),
if (linkdata.os.contains(PlatformOS.ios))
...(iosDomainErrors[linkdata.domain] ?? []),
];
if (errors.isNotEmpty) {
return LinkData(
domain: linkdata.domain,
domainErrors: errors,
path: linkdata.path,
pathErrors: linkdata.pathErrors,
os: linkdata.os,
scheme: linkdata.scheme,
associatedDomains: linkdata.associatedDomains,
associatedPath: linkdata.associatedPath,
hasAndroidAssetLinksFile: !(androidDomainErrors[linkdata.domain]
?.contains(AndroidDomainError.existence) ??
false),
hasIosAasaFile: !(iosDomainErrors[linkdata.domain]
?.contains(IosDomainError.existence) ??
false),
final hasAndroidAssetLinksFile = !(androidDomainErrors[linkdata.domain]
?.contains(AndroidDomainError.existence) ??
false);
final hasIosAasaFile = !(iosDomainErrors[linkdata.domain]
?.contains(IosDomainError.existence) ??
false);

if (linkdata.os.contains(PlatformOS.ios)) {
final List<String> iosPaths = iosDomainPaths[linkdata.domain] ?? [];

// If no path is provided, we will still show the domain just with domain errors.
if (iosPaths.isEmpty) {
validatedLinkDatas.add(
linkdata.copyWith(
domainErrors: errors,
hasAndroidAssetLinksFile: hasAndroidAssetLinksFile,
hasIosAasaFile: hasIosAasaFile,
),
);
} else {
// If there are multiple paths for the same domain, we will show the domain with each path.
for (final iosPath in iosPaths) {
validatedLinkDatas.add(
linkdata.copyWith(
path: iosPath,
domainErrors: errors,
hasAndroidAssetLinksFile: hasAndroidAssetLinksFile,
hasIosAasaFile: hasIosAasaFile,
),
);
}
}
}

if (linkdata.os.contains(PlatformOS.android)) {
validatedLinkDatas.add(
linkdata.copyWith(
domainErrors: errors,
hasAndroidAssetLinksFile: hasAndroidAssetLinksFile,
hasIosAasaFile: hasIosAasaFile,
),
);
}
return linkdata;
}).toList();
}
return validatedLinkDatas;
}

Future<List<LinkData>> _validatePath(List<LinkData> linkdatas) async {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,10 +278,31 @@ class LinkData with SearchableDataMixin {
}

@override
String toString() => 'LinkData($domain $path)';
String toString() => 'LinkData($domain $path $os)';

String get safePath => path ?? '';
String get safeDomain => domain ?? '';

LinkData copyWith({
String? path,
List<DomainError>? domainErrors,
bool? hasAndroidAssetLinksFile,
bool? hasIosAasaFile,
}) {
return LinkData(
domain: domain,
path: path ?? this.path,
os: os,
scheme: scheme,
domainErrors: domainErrors ?? this.domainErrors,
pathErrors: pathErrors,
associatedPath: associatedPath,
associatedDomains: associatedDomains,
hasAndroidAssetLinksFile:
hasAndroidAssetLinksFile ?? this.hasAndroidAssetLinksFile,
hasIosAasaFile: hasIosAasaFile ?? this.hasIosAasaFile,
);
}
}

class _ErrorAwareText extends StatelessWidget {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ const _teamIdKey = 'team_id';
const _universalLinkDomainsKey = 'universal_link_domains';
const _iosDomainNameKey = 'domain_name';
const _iosValidationResultsKey = 'validationResults';
const _aasaAppPathsKey = 'aasaAppPaths';
const _aasaPathsKey = 'aasaPaths';
const _pathKey = 'path';

const iosCheckNameToDomainError = <String, DomainError>{
'EXISTENCE': IosDomainError.existence,
Expand All @@ -69,9 +72,10 @@ const iosCheckNameToDomainError = <String, DomainError>{
};

class ValidateIosDomainResult {
ValidateIosDomainResult(this.errorCode, this.domainErrors);
ValidateIosDomainResult(this.errorCode, this.domainErrors, this.paths);
final String errorCode;
final Map<String, List<DomainError>> domainErrors;
final Map<String, List<String>> paths;
}

class GenerateAssetLinksResult {
Expand Down Expand Up @@ -153,9 +157,8 @@ class DeepLinksService {
required String teamId,
required List<String> domains,
}) async {
final domainErrors = <String, List<DomainError>>{
for (final domain in domains) domain: <DomainError>[],
};
final domainErrors = <String, List<DomainError>>{};
final paths = <String, List<String>>{};
// TODO(hangyujin): Add error code to the result.
const errorCode = '';

Expand Down Expand Up @@ -188,17 +191,35 @@ class DeepLinksService {
final checkName = failedCheck[_checkNameKey] as String;
final domainError = iosCheckNameToDomainError[checkName];
if (domainError != null) {
domainErrors[domainName]!.add(domainError);
domainErrors
.putIfAbsent(domainName, () => <DomainError>[])
.add(domainError);
}
}
}
final aasaAppPaths = (domainResult[_aasaAppPathsKey] as List?)
?.cast<Map<String, Object?>>();
if (aasaAppPaths != null) {
for (final aasaAppPath in aasaAppPaths) {
final aasaPaths = (aasaAppPath[_aasaPathsKey] as List?)
?.cast<Map<String, Object?>>();
if (aasaPaths != null) {
for (final aasaPath in aasaPaths) {
paths
.putIfAbsent(domainName, () => <String>[])
.add(aasaPath[_pathKey] as String);
}
continue;
}
}
}
}
// TODO(hangyujin): Add path from AASA file check result.
}
}
return ValidateIosDomainResult(
errorCode,
domainErrors,
paths,
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,8 @@ void main() {
hasPathError: true,
),
androidDeepLinkJson('www.domain3.com', path: '/path3'),
];
]
..fakeIosDomains = [defaultDomain];

await pumpDeepLinkScreen(
tester,
Expand All @@ -572,6 +573,8 @@ void main() {
expect(find.text('/path1'), findsOneWidget);
expect(find.text('/path2'), findsOneWidget);
expect(find.text('/path3'), findsOneWidget);
expect(find.text('/ios-path1'), findsOneWidget);
expect(find.text('/ios-path2'), findsOneWidget);

// Only show links with path error.
deepLinksController.updateDisplayOptions(
Expand All @@ -583,6 +586,8 @@ void main() {
expect(find.text('/path1'), findsNothing);
expect(find.text('/path2'), findsOneWidget);
expect(find.text('/path3'), findsNothing);
expect(find.text('/ios-path1'), findsNothing);
expect(find.text('/ios-path2'), findsNothing);

// Only show links with no issue.
deepLinksController.updateDisplayOptions(
Expand All @@ -597,6 +602,8 @@ void main() {
expect(find.text('/path1'), findsOneWidget);
expect(find.text('/path2'), findsNothing);
expect(find.text('/path3'), findsOneWidget);
expect(find.text('/ios-path1'), findsOneWidget);
expect(find.text('/ios-path2'), findsOneWidget);
},
);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,39 @@ const iosValidationResponseWithNoError = '''
"severityLevel": "ERROR"
}
],
"status": "VALIDATION_COMPLETE"
"status": "VALIDATION_COMPLETE",
"aasaAppPaths": [
{
"aasaAppId": {
"bundleId": "bundle.id",
"teamId": "AAABBB"
},
"aasaPaths": [
{
"path": "/ios-path1",
"queryParams": [
{
"key": "dplnk",
"value": "?*"
}
],
"isCaseSensitive": true,
"isPercentEncoded": true
},
{
"path": "/ios-path2",
"queryParams": [
{
"key": "dplnk",
"value": "?*"
}
],
"isCaseSensitive": true,
"isPercentEncoded": true
}
]
}
]
}
]
}
Expand Down Expand Up @@ -177,7 +209,39 @@ const iosValidationResponseWithError = '''
"severityLevel": "ERROR"
}
],
"status": "VALIDATION_COMPLETE"
"status": "VALIDATION_COMPLETE",
"aasaAppPaths": [
{
"aasaAppId": {
"bundleId": "bundle.id",
"teamId": "AAABBB"
},
"aasaPaths": [
{
"path": "/ios-path1",
"queryParams": [
{
"key": "dplnk",
"value": "?*"
}
],
"isCaseSensitive": true,
"isPercentEncoded": true
},
{
"path": "/ios-path2",
"queryParams": [
{
"key": "dplnk",
"value": "?*"
}
],
"isCaseSensitive": true,
"isPercentEncoded": true
}
]
}
]
}
]
}
Expand Down

0 comments on commit 4eae64d

Please sign in to comment.