Skip to content

Deep Link Testing & AASA Validator#126

Merged
Redth merged 2 commits intomainfrom
dev/aritchie/deep-linking
Apr 17, 2026
Merged

Deep Link Testing & AASA Validator#126
Redth merged 2 commits intomainfrom
dev/aritchie/deep-linking

Conversation

@aritchie
Copy link
Copy Markdown
Collaborator

This pull request adds deep link validation tools for both iOS (AASA) and Android (assetlinks.json) to the device and simulator tools tabs. It introduces a new IDeepLinkValidationService for validating domain association files, updates the UI to allow users to validate these files directly from the app, and displays detailed validation results with user-friendly feedback.

Key changes include:

Core Service Implementation:

  • Added a new interface IDeepLinkValidationService and corresponding result record types (AasaValidationResult, AssetLinksValidationResult, etc.) to define methods for validating Apple App Site Association (AASA) and Android assetlinks.json files. (src/MauiSherpa.Core/Interfaces.cs)
  • Implemented DeepLinkValidationService with logic to fetch, parse, and validate AASA and assetlinks.json files, handling both modern and legacy formats, and providing structured results. (src/MauiSherpa.Core/Services/DeepLinkValidationService.cs)
  • Registered DeepLinkValidationService as a singleton in the DI container for MacOS. (src/MauiSherpa.MacOS/MacOSMauiProgram.cs)

UI Integration – Device Tools Tab:

  • Injected IDeepLinkValidationService and added a new button to validate assetlinks.json from the device tools tab, showing a styled result section with summary, entry details, and raw JSON. (src/MauiSherpa/Components/DeviceToolsTab.razor) [1] [2] [3] [4] [5]

UI Integration – Simulator Tools Tab:

  • Injected IDeepLinkValidationService and added a new deep links section to the simulator tools tab, allowing users to launch deep links and validate AASA files with detailed feedback and raw JSON display. (src/MauiSherpa/Components/SimToolsTab.razor) [1] [2] [3] [4] [5]Initial commit

aritchie and others added 2 commits March 25, 2026 17:13
Initial commit
- DeepLinkValidationService now detects CMS/PKCS7-signed AASA blobs
  (application/pkcs7-mime or DER 0x30) and extracts the inner JSON via
  SignedCms before parsing, with fallback to raw JSON.
- AasaValidationResult gains a Signed flag; SimToolsTab shows '(signed)'
  on success when the AASA was served as a signed CMS blob.
- Dispose JsonDocument instances in the validator.
- SimToolsTab and DeviceToolsTab display an inline warning when the
  deep-link URL is a non-http(s) custom scheme, explaining that
  AASA / assetlinks.json validation requires an https URL while keeping
  Launch enabled so users can still try the scheme.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 17, 2026 21:18
@Redth Redth merged commit 381fd6c into main Apr 17, 2026
9 checks passed
@Redth Redth deleted the dev/aritchie/deep-linking branch April 17, 2026 21:20
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds in-app deep link validation utilities for iOS (AASA) and Android (assetlinks.json), surfaced in the simulator and device tools UI, backed by a new Core validation service.

Changes:

  • Introduced IDeepLinkValidationService plus result record types for AASA and assetlinks validation.
  • Implemented DeepLinkValidationService to fetch and parse AASA (including signed CMS/PKCS7) and assetlinks.json.
  • Updated SimToolsTab and DeviceToolsTab UI to launch deep links and display validation results (including raw JSON).

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/MauiSherpa/Components/SimToolsTab.razor Adds a Deep Links section for launching URLs and validating AASA with detailed result rendering.
src/MauiSherpa/Components/DeviceToolsTab.razor Adds assetlinks.json validation UI and result rendering for device tools.
src/MauiSherpa.MacOS/MacOSMauiProgram.cs Registers the new deep link validation service in the macOS head DI container.
src/MauiSherpa.Core/Services/DeepLinkValidationService.cs New service that downloads and validates AASA and assetlinks association files.
src/MauiSherpa.Core/Interfaces.cs Adds IDeepLinkValidationService and related result record types.

Comment on lines +332 to +347
.validation-result { margin-top: 0.375rem; padding: 0.5rem; border-radius: 0.375rem; font-size: 0.6875rem; border: 1px solid var(--border-color); }
.validation-result.success { background: rgba(34,197,94,0.08); border-color: rgba(34,197,94,0.3); }
.validation-result.warning { background: rgba(234,179,8,0.08); border-color: rgba(234,179,8,0.3); }
.validation-result.error { background: rgba(239,68,68,0.08); border-color: rgba(239,68,68,0.3); }
.validation-status { display: flex; align-items: center; gap: 0.375rem; font-weight: 600; }
.validation-status .fa-check-circle { color: #22c55e; }
.validation-status .fa-exclamation-triangle { color: #eab308; }
.validation-status .fa-times-circle { color: #ef4444; }
.validation-entries { margin-top: 0.375rem; display: flex; flex-direction: column; gap: 0.25rem; }
.validation-entry { padding: 0.25rem 0.375rem; background: var(--bg-tertiary); border-radius: 0.25rem; }
.validation-entry strong { color: var(--text-primary); }
.validation-fingerprint { color: var(--text-secondary); margin-left: 0.375rem; font-family: 'Consolas', 'Monaco', monospace; font-size: 0.625rem; }
.validation-raw { margin-top: 0.375rem; }
.validation-raw summary { cursor: pointer; color: var(--text-secondary); font-size: 0.625rem; }
.validation-raw pre { margin: 0.25rem 0 0; padding: 0.375rem; background: var(--bg-tertiary); border-radius: 0.25rem; font-size: 0.625rem; overflow-x: auto; max-height: 200px; overflow-y: auto; white-space: pre-wrap; word-break: break-all; }
.deep-link-warning { margin-top: 0.375rem; padding: 0.375rem 0.5rem; border-radius: 0.375rem; font-size: 0.6875rem; background: rgba(234,179,8,0.08); border: 1px solid rgba(234,179,8,0.3); color: var(--text-primary); display: flex; gap: 0.375rem; align-items: flex-start; }
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

These validation styles (.validation-result, .validation-status, .validation-raw, .deep-link-warning, etc.) are duplicated between DeviceToolsTab and SimToolsTab. Consider moving them into a shared stylesheet (e.g., a common component CSS block or wwwroot CSS) to avoid future drift and reduce maintenance overhead.

Copilot uses AI. Check for mistakes.
Comment on lines +595 to +618
private static string? ExtractDomain(string url)
{
if (Uri.TryCreate(url, UriKind.Absolute, out var uri) &&
(uri.Scheme == "https" || uri.Scheme == "http"))
return uri.Host;
return null;
}

private static bool IsNonWebScheme(string url)
{
if (string.IsNullOrWhiteSpace(url)) return false;
if (!Uri.TryCreate(url, UriKind.Absolute, out var uri)) return false;
return uri.Scheme != "https" && uri.Scheme != "http";
}

private static string FormatJson(string json)
{
try
{
using var doc = System.Text.Json.JsonDocument.Parse(json);
return System.Text.Json.JsonSerializer.Serialize(doc, new System.Text.Json.JsonSerializerOptions { WriteIndented = true });
}
catch { return json; }
}
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

ExtractDomain, IsNonWebScheme, and FormatJson are duplicated in both DeviceToolsTab and SimToolsTab. Consider factoring these into a shared helper (e.g., a small static utility in the UI project) so fixes/behavior changes (scheme handling, formatting options, etc.) only need to be made once.

Copilot uses AI. Check for mistakes.
Comment on lines +15 to +21
public async Task<AasaValidationResult> ValidateAppleAppSiteAssociationAsync(string domain)
{
try
{
var url = $"https://{domain}/.well-known/apple-app-site-association";
var response = await Http.GetAsync(url).ConfigureAwait(false);

Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

domain is interpolated directly into the URL string. If a caller passes unexpected input (e.g., containing @ userinfo or /), the request can be sent to an unintended host (e.g., https://example.com@evil.com/... resolves to evil.com). Validate that domain is a hostname (e.g., via Uri.CheckHostName and rejecting characters like @, /, :), or build the URI via UriBuilder to avoid URL-injection edge cases.

Suggested change
public async Task<AasaValidationResult> ValidateAppleAppSiteAssociationAsync(string domain)
{
try
{
var url = $"https://{domain}/.well-known/apple-app-site-association";
var response = await Http.GetAsync(url).ConfigureAwait(false);
static bool IsValidHostName(string domain)
{
if (string.IsNullOrWhiteSpace(domain))
return false;
if (domain.IndexOfAny(['@', '/', '\\', ':', '?', '#']) >= 0)
return false;
return Uri.CheckHostName(domain) != UriHostNameType.Unknown;
}
public async Task<AasaValidationResult> ValidateAppleAppSiteAssociationAsync(string domain)
{
try
{
if (!IsValidHostName(domain))
{
return new AasaValidationResult(
false,
false,
null,
Array.Empty<AasaAppEntry>(),
"Invalid domain name",
false);
}
var requestUri = new UriBuilder("https", domain)
{
Path = ".well-known/apple-app-site-association"
}.Uri;
var response = await Http.GetAsync(requestUri).ConfigureAwait(false);

Copilot uses AI. Check for mistakes.
Comment on lines +10 to +20
static readonly HttpClient Http = new()
{
Timeout = TimeSpan.FromSeconds(10)
};

public async Task<AasaValidationResult> ValidateAppleAppSiteAssociationAsync(string domain)
{
try
{
var url = $"https://{domain}/.well-known/apple-app-site-association";
var response = await Http.GetAsync(url).ConfigureAwait(false);
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

This new service adds non-trivial parsing/validation logic (AASA modern/legacy formats, signed-vs-raw content, assetlinks relation filtering) but there are no accompanying unit tests under tests/MauiSherpa.Core.Tests/Services. Adding tests for success, missing/invalid JSON, non-2xx responses, and signed AASA decoding would help prevent regressions. Consider also making the HTTP dependency injectable (instead of a static HttpClient) to enable deterministic tests.

Suggested change
static readonly HttpClient Http = new()
{
Timeout = TimeSpan.FromSeconds(10)
};
public async Task<AasaValidationResult> ValidateAppleAppSiteAssociationAsync(string domain)
{
try
{
var url = $"https://{domain}/.well-known/apple-app-site-association";
var response = await Http.GetAsync(url).ConfigureAwait(false);
readonly HttpClient http;
static HttpClient CreateDefaultHttpClient()
{
return new HttpClient
{
Timeout = TimeSpan.FromSeconds(10)
};
}
public DeepLinkValidationService()
: this(CreateDefaultHttpClient())
{
}
public DeepLinkValidationService(HttpClient httpClient)
{
http = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
}
public async Task<AasaValidationResult> ValidateAppleAppSiteAssociationAsync(string domain)
{
try
{
var url = $"https://{domain}/.well-known/apple-app-site-association";
var response = await http.GetAsync(url).ConfigureAwait(false);

Copilot uses AI. Check for mistakes.
Comment on lines 5 to +8
@inject SimInspectorService Inspector
@inject IAlertService AlertService
@inject IDialogService DialogService
@inject IDeepLinkValidationService DeepLinkValidator
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

IDeepLinkValidationService is injected here, but the main MAUI app host (src/MauiSherpa/MauiProgram.cs) does not register it in the DI container. This will throw at runtime when this component is rendered on Mac Catalyst/Windows. Register IDeepLinkValidationService (and its implementation) in the shared host (and any other heads that use these components), or guard the UI behind a feature check so it isn’t constructed when the service is unavailable.

Copilot uses AI. Check for mistakes.
@inject DeviceInspectorService Inspector
@inject IAlertService AlertService
@inject IDialogService DialogService
@inject IDeepLinkValidationService DeepLinkValidator
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

IDeepLinkValidationService is injected here, but it isn’t registered in the main app DI container (src/MauiSherpa/MauiProgram.cs). Rendering this component will fail on Mac Catalyst/Windows unless the service is added to the shared registrations (and any other relevant heads).

Suggested change
@inject IDeepLinkValidationService DeepLinkValidator

Copilot uses AI. Check for mistakes.
Comment on lines +567 to +572
private static string? ExtractDomain(string url)
{
if (Uri.TryCreate(url, UriKind.Absolute, out var uri) &&
(uri.Scheme == "https" || uri.Scheme == "http"))
return uri.Host;
return null;
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

The toast/error messaging says users must enter an https:// URL, but ExtractDomain currently accepts both http and https. Either restrict this to https only, or update the messaging/warning text so it matches the accepted input.

Copilot uses AI. Check for mistakes.
private static string? ExtractDomain(string url)
{
if (Uri.TryCreate(url, UriKind.Absolute, out var uri) &&
(uri.Scheme == "https" || uri.Scheme == "http"))
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

The UI messaging indicates assetlinks.json validation requires an https:// URL, but ExtractDomain accepts both http and https. Consider either enforcing https here, or adjusting the user-facing message to match the actual accepted schemes.

Suggested change
(uri.Scheme == "https" || uri.Scheme == "http"))
uri.Scheme == Uri.UriSchemeHttps)

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants