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

[deep link] Add ios path from AASA file check result. #8285

Merged
merged 9 commits into from
Sep 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
Comment on lines +547 to +552
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is defaulting to false what we want here? The way I am understanding this is that if we have no domain errors, we assume the asset links or aasa files do not exist.

Copy link
Member Author

@hannah-hyj hannah-hyj Sep 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we have no domain errors, hasAndroidAssetLinksFile =! ( null ?? false ) = true .

if the domain doesn't have asset links or aasa files. the domain errors should return AndroidDomainError.existence or IosDomainError.existence.

If we have no domain errors, we assume the domain has asset links or aasa files.


if (linkdata.os.contains(PlatformOS.ios)) {
final List<String> iosPaths = iosDomainPaths[linkdata.domain] ?? [];
hannah-hyj marked this conversation as resolved.
Show resolved Hide resolved

// 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);
}
hannah-hyj marked this conversation as resolved.
Show resolved Hide resolved
}
}
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