Skip to content

Commit

Permalink
[deep link] Add sub checks for ios domain error - format error (#8309)
Browse files Browse the repository at this point in the history
* add subchecks

resolve comments

lint

lint

add tests

Update deep_links_services.dart

Update deep_links_services.dart

UI

Update deep_links_model.dart

add subchecks

Update deep_links_model.dart

1

* resolve comments

* Update deep_links_screen_test.dart

* Update deep_links_services.dart

* resolve sync conflicts

* update typos in AASA and apply small fixes
  • Loading branch information
hannah-hyj authored Oct 1, 2024
1 parent acb2889 commit 6b6397d
Show file tree
Hide file tree
Showing 6 changed files with 392 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,8 @@ class DeepLinksController extends DisposableController

AppLinkSettings? get currentAppLinkSettings =>
androidAppLinks[selectedAndroidVariantIndex.value];

UniversalLinkSettings? get currentUniversalLinkSettings =>
iosLinks[selectedIosConfigurationIndex.value];
@visibleForTesting
final androidAppLinks = <int, AppLinkSettings>{};

Expand Down Expand Up @@ -301,6 +302,9 @@ class DeepLinksController extends DisposableController
}

Future<void> _loadAndroidAppLinks() async {
if (selectedProject.value!.androidVariants.isEmpty) {
return;
}
final variant = selectedProject
.value!.androidVariants[selectedAndroidVariantIndex.value];
await ga.timeAsync(
Expand All @@ -327,6 +331,9 @@ class DeepLinksController extends DisposableController

Future<void> _loadIosLinks() async {
final iosBuildOptions = selectedProject.value!.iosBuildOptions;
if (iosBuildOptions.configurations.isEmpty) {
return;
}
final configuration =
iosBuildOptions.configurations[selectedIosConfigurationIndex.value];
final target = iosBuildOptions.targets[selectedIosTargetIndex.value];
Expand Down Expand Up @@ -507,21 +514,22 @@ class DeepLinksController extends DisposableController
.toSet()
.toList();

late final Map<String, List<DomainError>> androidDomainErrors;
Map<String, List<DomainError>> iosDomainErrors =
<String, List<DomainError>>{};

late final Map<String, List<Path>> iosDomainPaths;
Map<String, List<DomainError>> androidDomainErrors = {};
Map<String, List<DomainError>> iosDomainErrors = {};
Map<String, List<Path>> iosDomainPaths = {};
try {
final androidResult = await deepLinksService.validateAndroidDomain(
domains: domains,
applicationId: applicationId,
localFingerprint: localFingerprint.value,
);
androidDomainErrors = androidResult.domainErrors;
googlePlayFingerprintsAvailability.value =
androidResult.googlePlayFingerprintsAvailability;
if (FeatureFlags.deepLinkIosCheck) {
if (currentAppLinkSettings != null) {
final androidResult = await deepLinksService.validateAndroidDomain(
domains: domains,
applicationId: applicationId,
localFingerprint: localFingerprint.value,
);
androidDomainErrors = androidResult.domainErrors;
googlePlayFingerprintsAvailability.value =
androidResult.googlePlayFingerprintsAvailability;
}
if (FeatureFlags.deepLinkIosCheck &&
currentUniversalLinkSettings != null) {
final iosResult = await deepLinksService.validateIosDomain(
bundleId: bundleId,
teamId: teamId,
Expand Down Expand Up @@ -607,11 +615,7 @@ class DeepLinksController extends DisposableController

Future<void> validateLinks() async {
final appLinkSettings = currentAppLinkSettings;
if (appLinkSettings == null) {
pagePhase.value = PagePhase.noLinks;
return;
}
if (appLinkSettings.error != null) {
if (appLinkSettings?.error != null) {
pagePhase.value = PagePhase.analyzeErrorPage;
ga.select(
gac.deeplink,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,12 @@ class AndroidDomainError extends DomainError {
}

class IosDomainError extends DomainError {
const IosDomainError(super.title, super.explanation, super.fixDetails);
const IosDomainError(
super.title,
super.explanation,
super.fixDetails, {
this.subcheckErrors = const [],
});
// TODO(hangyujin): Finalize strings for these domain errors.

/// Existence of an Apple-App-Site-Association file.
Expand Down Expand Up @@ -163,13 +168,159 @@ class IosDomainError extends DomainError {
'Ensure your domain is accessible without any redirects.',
);

/// TODO(hangyujin): There are sub checkes of this check, add them and add links when finalized.
/// AASA file format follows guidelines.
static const fileFormat = IosDomainError(
'Apple-App-Site-Association file format is incorrect',
'This test checks that your Apple-App-Site-Association file '
'follows the correct format guidelines.',
'Ensure your Apple-App-Site-Association file follows the correct format guidelines.',
static IosDomainError iosFileFormatDomainError({
required List<AASAfileFormatSubCheck> subcheckErrors,
}) =>
IosDomainError(
'Apple-App-Site-Association file format is incorrect',
'This test checks that your Apple-App-Site-Association file '
'follows the correct format guidelines.',
'Ensure your Apple-App-Site-Association file follows the correct format guidelines.',
subcheckErrors: subcheckErrors,
);

final List<AASAfileFormatSubCheck> subcheckErrors;
}

String propertyTypeMessage({
required String property,
required String expectedType,
}) {
return 'This test checks that the `$property` property only holds a $expectedType.';
}

class AASAfileFormatSubCheck extends CommonError {
const AASAfileFormatSubCheck(String title, String explanation)
: super(title, explanation, '');

static final appLinksFormat = AASAfileFormatSubCheck(
'Applinks format',
propertyTypeMessage(property: 'applinks', expectedType: 'object'),
);

static const appLinksSubstitutionVariablesFormat = AASAfileFormatSubCheck(
'Applinks substitution variables format',
'This test checks that the `applinks.SubstitutionVariables` property has a valid '
'substitution variable format. Ref - '
'https://developer.apple.com/documentation/bundleresources/applinks/substitutionvariables',
);

static const defaultsFormat = AASAfileFormatSubCheck(
'Applinks defaults format',
'This test checks that the `applinks.defaults` property only holds '
'the keys caseSensitive and percentEncoded. Ref - '
'https://developer.apple.com/documentation/bundleresources/applinks/defaults',
);

static final defaultsPercentEncodedFormat = AASAfileFormatSubCheck(
'Applinks defaults percent encoded format',
propertyTypeMessage(
property: 'applinks.defaults.percentEncoded',
expectedType: 'boolean',
),
);

static final defaultsCaseSensitiveFormat = AASAfileFormatSubCheck(
'Applinks defaults case sensitive format',
propertyTypeMessage(
property: 'applinks.defaults.caseSensitive',
expectedType: 'boolean',
),
);

static const detailsFormat = AASAfileFormatSubCheck(
'Applinks details format',
'This test checks that the `applinks.details` property is formatted properly. Ref - '
'https://developer.apple.com/documentation/bundleresources/applinks/details',
);

static const detailsAppIdFormat = AASAfileFormatSubCheck(
'Applinks details appIDs format',
'This test checks that the `applinks.details.appID` property is an array of strings.',
);

static const detailsPathsFormat = AASAfileFormatSubCheck(
'Applinks details paths format',
'This test checks that the `applinks.details.paths` property is an array of strings.',
);

static const detailsDefaultsFormat = AASAfileFormatSubCheck(
'Applinks details default format',
'This test checks that the `applinks.details.defaults` property only holds '
'the keys caseSensitive and percentEncoded. Ref - '
'https://developer.apple.com/documentation/bundleresources/applinks/details/default',
);

static final detailsDefaultsPercentEncodedFormat = AASAfileFormatSubCheck(
'Applinks defaults percent encoded format',
propertyTypeMessage(
property: 'applinks.details.defaults.percentEncoded',
expectedType: 'boolean',
),
);

static final detailsDefaultsCaseSensitiveFormat = AASAfileFormatSubCheck(
'Applinks defaults case sensitive format',
propertyTypeMessage(
property: 'applinks.details.defaults.caseSensitive',
expectedType: 'boolean',
),
);

static const componentFormat = AASAfileFormatSubCheck(
'Applinks details components format',
'This test checks that the `applinks.details.components` property is formatted properly. Ref - '
'https://developer.apple.com/documentation/bundleresources/applinks/details/components',
);

static final componentPathFormat = AASAfileFormatSubCheck(
'Applinks details components path format',
propertyTypeMessage(
property: 'applinks.details.components.path',
expectedType: 'string',
),
);

static const componentQueryFormat = AASAfileFormatSubCheck(
'Applinks details components query format',
'This test checks that the `applinks.details.components.query` property only holds either a string or object of type `applinks.Details.Components.Query`.',
);

static final componentFragmentFormat = AASAfileFormatSubCheck(
'Applinks details components fragment format',
propertyTypeMessage(
property: 'applinks.details.components.fragment',
expectedType: 'string',
),
);

static final componentExcludeFormat = AASAfileFormatSubCheck(
'Applinks details components exclude format',
propertyTypeMessage(
property: 'applinks.details.components.exclude',
expectedType: 'boolean',
),
);
static final componentPercentEncodedFormat = AASAfileFormatSubCheck(
'Applinks details components percent encoded format',
propertyTypeMessage(
property: 'applinks.details.components.percentEncoded',
expectedType: 'boolean',
),
);
static final componentCaseSensitiveFormat = AASAfileFormatSubCheck(
'Applinks details components case sensitive format',
propertyTypeMessage(
property: 'applinks.details.components.caseSensitive',
expectedType: 'boolean',
),
);
static final componentCommentFormat = AASAfileFormatSubCheck(
'Applinks details components comment format',
propertyTypeMessage(
property: 'applinks.details.components.comment',
expectedType: 'string',
),
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ const _checkNameKey = 'checkName';
const _severityLevelKey = 'severityLevel';
const _severityLevelError = 'ERROR';
const _failedChecksKey = 'failedChecks';
const _subCheckResultsKey = 'subCheckResults';
const _resultTypeKey = 'resultType';
const _passedKey = 'PASSED';
const _domainBatchSize = 500;

// The keys for the Android domain validation API.
Expand Down Expand Up @@ -70,12 +73,38 @@ const _queryParamsKey = 'queryParams';
const _keyKey = 'key';
const _valueKey = 'value';

const iosCheckNameToDomainError = <String, DomainError>{
const _fileFormatKey = 'FILE_FORMAT';
final iosCheckNameToDomainError = <String, DomainError>{
'EXISTENCE': IosDomainError.existence,
'APP_IDENTIFIER': IosDomainError.appIdentifier,
'HTTPS_ACCESSIBILITY': IosDomainError.httpsAccessibility,
'NON_REDIRECT': IosDomainError.nonRedirect,
'FILE_FORMAT': IosDomainError.fileFormat,
};
final aasaFileFormatSubCheck = {
'APPLINKS_FORMAT': AASAfileFormatSubCheck.appLinksFormat,
'APPLINKS_SUBSTITUTION_VARIABLES_FORMAT':
AASAfileFormatSubCheck.appLinksSubstitutionVariablesFormat,
'DEFAULTS_FORMAT': AASAfileFormatSubCheck.defaultsFormat,
'DEFAULTS_PERCENT_ENCODED_FORMAT':
AASAfileFormatSubCheck.defaultsPercentEncodedFormat,
'DETAIL_FORMAT': AASAfileFormatSubCheck.detailsFormat,
'DETAIL_APP_ID_FORMAT': AASAfileFormatSubCheck.detailsAppIdFormat,
'DETAIL_PATHS_FORMAT': AASAfileFormatSubCheck.detailsPathsFormat,
'DETAIL_DEFAULTS_FORMAT': AASAfileFormatSubCheck.detailsDefaultsFormat,
'DETAIL_DEFAULTS_PERCENT_ENCODED_FORMAT':
AASAfileFormatSubCheck.detailsDefaultsPercentEncodedFormat,
'DETAIL_DEFAULTS_CASE_SENSITIVE_FORMAT ':
AASAfileFormatSubCheck.detailsDefaultsCaseSensitiveFormat,
'COMPONENT_FORMAT': AASAfileFormatSubCheck.componentFormat,
'COMPONENT_PATH_FORMAT': AASAfileFormatSubCheck.componentPathFormat,
'COMPONENT_QUERY_FORMAT': AASAfileFormatSubCheck.componentQueryFormat,
'COMPONENT_FRAGMENT_FORMAT': AASAfileFormatSubCheck.componentFragmentFormat,
'COMPONENT_EXCLUDE_FORMAT': AASAfileFormatSubCheck.componentExcludeFormat,
'COMPONENT_PERCENT_ENCODED_FORMAT':
AASAfileFormatSubCheck.componentPercentEncodedFormat,
'COMPONENT_CASE_SENSITIVE_FORMAT':
AASAfileFormatSubCheck.componentCaseSensitiveFormat,
'COMPONENT_COMMENT_FORMAT': AASAfileFormatSubCheck.componentCommentFormat,
};

class ValidateIosDomainResult {
Expand Down Expand Up @@ -198,13 +227,40 @@ class DeepLinksService {
final checkName = failedCheck[_checkNameKey] as String;
final domainError = iosCheckNameToDomainError[checkName];
final severityLevel = failedCheck[_severityLevelKey] as String;
if (domainError != null && severityLevel == _severityLevelError) {
domainErrors
.putIfAbsent(domainName, () => <DomainError>[])
.add(domainError);
if (severityLevel == _severityLevelError) {
if (checkName == _fileFormatKey) {
final failedAasaFileFormatSubCheck =
<AASAfileFormatSubCheck>[];

// Adds sub checks for file format error.
final subChecks = (failedCheck[_subCheckResultsKey] as List?)
?.cast<Map<String, Object?>>();
for (final subCheck in (subChecks ?? <Map>[])) {
final subCheckName = subCheck[_checkNameKey] as String;
final subCheckResultType =
subCheck[_resultTypeKey] as String;
if (subCheckResultType != _passedKey) {
failedAasaFileFormatSubCheck
.add(aasaFileFormatSubCheck[subCheckName]!);
}
}

domainErrors
.putIfAbsent(domainName, () => <DomainError>[])
.add(
IosDomainError.iosFileFormatDomainError(
subcheckErrors: failedAasaFileFormatSubCheck,
),
);
} else if (domainError != null) {
domainErrors
.putIfAbsent(domainName, () => <DomainError>[])
.add(domainError);
}
}
}
}

final aasaAppPaths = (domainResult[_aasaAppPathsKey] as List?)
?.cast<Map<String, Object?>>();
if (aasaAppPaths != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,27 +187,32 @@ class _DomainCheckTable extends StatelessWidget {
errors: [error],
oneFixGuideForAll:
'To fix this issue, add an Apple-App-Site-Association file at the following location: '
'https://${controller.selectedLink.value!.domain}/.well-known/assetlinks.json.',
'https://${controller.selectedLink.value!.domain}/.well-known/apple-app-site-association',
),
const SizedBox(height: denseSpacing),
_CodeCard(
content: '''{
"applinks": {
"details": [
{
"appIDs": [ "${controller.teamId}.${controller.bundleId}" ],
"appIDs": [
"${controller.teamId}.${controller.bundleId}"
],
"components": [
{
"/": "*"
}
]
}
]
}''',
}
}''',
),
]
: [
_FailureDetails(errors: [error]),
_FailureDetails(
errors: [error, ...error.subcheckErrors],
),
],
),
],
Expand Down
Loading

0 comments on commit 6b6397d

Please sign in to comment.