Skip to content

Redesign push testing pages and store APNs .p8 keys in secret providers - #261

Merged
Redth merged 1 commit into
aritchie/pub-profilesfrom
aritchie/push-refresh
Sep 23, 2026
Merged

Redth merged 1 commit into
aritchie/pub-profilesfrom
aritchie/push-refresh

Conversation

@aritchie

Copy link
Copy Markdown
Collaborator

Summary

Redesigns the Apple (APNs) and Firebase (FCM) push testing pages to match the rest of the app, and adds the ability to store APNs .p8 auth keys in secret providers.

Push testing redesign

  • Page headers follow the .NET SDK page pattern (title, description, status pill: Sandbox/Production for APNs, Firebase project or "No service account" for FCM).
  • Sections use the Settings visual language: icon chip, title, description, rounded card.
  • Consistent input sizing on theme tokens; segmented controls replace two-option dropdowns (key source, environment, priority, Form/JSON, token/topic). This also removes the Environment <select> that rendered blank.
  • Sticky right column with a live notification preview (iOS lock screen / Android shade, parsed from the form or JSON) and a send panel with a summary and readiness checklist explaining why Send is disabled.
  • JSON editor component with valid/invalid state, Format, and an APNs payload size counter (4 KB, 5 KB for VoIP).
  • Shared send history list with status icons, status codes, and highlighted errors.
  • Restyled project bar with an "Unsaved" indicator.
  • Shared styles live in wwwroot/css/pushTesting.css; new components: PushJsonEditor, PushNotificationPreview, PushHistoryList.

APNs .p8 keys in secret providers

  • New Secret provider key source on Push Testing.
  • Import .p8… stores the key as a managed File secret at apns/<file name> with apnsKeyId/apnsTeamId metadata; it goes to the active provider and is synced to default providers via ISecretSyncCoordinator, so it also appears on the Secrets page.
  • Choose reuses SecretPickerPage (now takes an optional title/button text; defaults unchanged) filtered to .p8 keys, and fills in Key ID/Team ID.
  • The existing SecretProviderPickerHost controls which providers hold the key.
  • Store in provider moves a file-based key into providers in one step.
  • At send time the key is read with IManagedSecretsService.GetValueAsync, which checks every provider.
  • ApnsPushProjectConfig gains a nullable P8SecretKey; existing projects load unchanged.

Build fix

main didn't compile: XcodeManagement.razor (from #247) calls IOperationModalService.RequestCancellation(), which only existed on the concrete service. The first commit adds it to the interface; OperationModalService is the only implementer.

Testing

  • dotnet build src/MauiSherpa.MacOS -f net10.0-macos succeeds.
  • dotnet test tests/MauiSherpa.Core.Tests: 592 passed, 0 failed.
  • Both push pages checked in the running macOS app in light mode. Dark mode was checked by forcing the app theme while macOS stayed light: cards, inputs, preview and editor look right, but the page background and headings still need a look in real dark mode.

Not verified yet:

  • The secret-provider flow end to end (import, choose, send) in the running app.
  • The send history list on screen (the test projects had no history).
  • Windows and Linux heads.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LMQybcsC24APNz8Vfhbbhw

Copilot AI lite review requested due to automatic review settings September 10, 2026 23:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Unresolved send validation, dirty-state, secret-readiness, metadata, and provider-synchronization issues block approval.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Redesigns APNs and FCM push-testing pages and adds managed APNs .p8 secret-provider support.

Changes:

  • Adds shared previews, JSON editors, history, and project controls.
  • Supports APNs key import, selection, metadata, and synchronization.
  • Adds configurable secret-picker actions and operation cancellation.
File summaries
File Description
src/MauiSherpa/wwwroot/index.html Loads shared push-testing styles.
src/MauiSherpa/wwwroot/css/pushTesting.css Defines shared push UI styling.
src/MauiSherpa/Pages/PushTesting.razor Redesigns APNs testing and secret handling.
src/MauiSherpa/Pages/Modals/SecretPickerPage.cs Supports customizable picker titles and buttons.
src/MauiSherpa/Pages/FirebasePush.razor Redesigns FCM testing and previews.
src/MauiSherpa/Components/PushProjectBar.razor Adds redesigned project controls.
src/MauiSherpa/Components/PushNotificationPreview.razor Adds device notification previews.
src/MauiSherpa/Components/PushJsonEditor.razor Adds JSON validation and formatting.
src/MauiSherpa/Components/PushHistoryList.razor Adds shared send-history rendering.
src/MauiSherpa.Core/Interfaces.cs Adds cancellation API and APNs secret configuration.
Review details

Suppressed comments (3)

src/MauiSherpa/Components/PushJsonEditor.razor:30

  • This textarea has no associated label or aria-label; the visible filename in the toolbar is not programmatically associated with it. Both push pages therefore expose an unnamed multiline editor to screen readers. Add an accessible label to the editor.
    <textarea class="push-json-input" rows="@Rows" spellcheck="false" placeholder="@Placeholder"
              value="@Value" @oninput="OnInput"></textarea>

src/MauiSherpa/Pages/PushTesting.razor:363

  • In the new secret mode this only checks the configured secret name and Key ID. A deleted or unreadable provider copy still marks the signing-key checklist as complete and enables Send; SendPush then discovers the missing value in GetValueAsync and fails only after the click. Track secret availability or make readiness asynchronous so the checklist and button reflect whether the key can actually be read.
    bool HasSigningKey => authMode switch
    {
        "identity" => !string.IsNullOrWhiteSpace(selectedIdentityId),
        "secret" => !string.IsNullOrWhiteSpace(p8SecretKey) && !string.IsNullOrWhiteSpace(p8KeyId),
        _ => !string.IsNullOrWhiteSpace(p8FilePath) && !string.IsNullOrWhiteSpace(p8KeyId)
    };

src/MauiSherpa/Pages/PushTesting.razor:644

  • When the selected secret has neither APNs metadata nor an AuthKey_... filename, these branches leave the previous p8KeyId and teamId unchanged. Switching from key A to such a secret can then pair the new key material with stale credentials, which APNs rejects. Clear each field when it cannot be derived so the checklist forces the user to supply matching values.
            p8KeyId = storedKeyId;
        else if (KeyIdFromFileName(selected.OriginalFileName ?? selected.Key) is { } parsedKeyId)
            p8KeyId = parsedKeyId;
        if (selected.Metadata?.TryGetValue(ApnsTeamIdMetadata, out var storedTeamId) == true && !string.IsNullOrWhiteSpace(storedTeamId))
            teamId = storedTeamId;
  • Files reviewed: 10/10 changed files
  • Comments generated: 4
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +26 to +30
@if (IsDirty)
{
<span class="push-unsaved"><span class="push-unsaved-dot"></span> Unsaved</span>
}
<button class="btn btn-sm @(IsDirty ? "btn-primary" : "btn-secondary")" @onclick="HandleSave" title="Save project">
Comment on lines +241 to +244
bool HasRawJson => !string.IsNullOrWhiteSpace(rawJson);

bool CanSend => hasCredentials && HasTarget && HasContent;
bool CanSendCurrent => rawJsonMode ? hasCredentials && HasRawJson : CanSend;
bool HasTeamId => !string.IsNullOrWhiteSpace(teamId);
bool HasBundleId => !string.IsNullOrWhiteSpace(bundleId);
bool HasDeviceToken => !string.IsNullOrWhiteSpace(deviceToken);
bool HasPayload => !string.IsNullOrWhiteSpace(jsonPayload);
Comment on lines +703 to +707
stored = await SecretsService.CreateAsync(secretKey, bytes, ManagedSecretType.File, description, fileName, metadata);
if (stored)
await SecretSyncCoordinator.SetDefaultProvidersAsync(
new SecretItemRef(SecretItemKind.ManagedSecret, secretKey, fileName));
}
@Redth
Redth force-pushed the aritchie/push-refresh branch from 0b0f783 to 51d1ac4 Compare September 21, 2026 12:38
Restyle the APNs and Firebase push pages to match the rest of the app:
Settings-style sections, segmented controls, a sticky notification preview
and send checklist, a JSON editor with validation and payload size, and a
shared send history list. Shared styles move to pushTesting.css.

Add a "Secret provider" key source for APNs: import a .p8 as a managed
File secret (synced to default providers), choose an existing one, or move
a file-based key into providers. The key is read from providers at send
time and the project stores its secret key in P8SecretKey.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LMQybcsC24APNz8Vfhbbhw
@Redth
Redth force-pushed the aritchie/push-refresh branch from 51d1ac4 to efbe5d4 Compare September 22, 2026 19:12
@Redth
Redth changed the base branch from main to aritchie/pub-profiles September 22, 2026 19:13
@Redth
Redth added this pull request to stack #268 September 22, 2026 20:18
@Redth
Redth merged commit b1ed939 into main Sep 23, 2026
9 checks passed
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