Skip to content

feat(update): Add update notifications (GitHub / release manifest feed) - #98

Merged
keyldev merged 2 commits into
mainfrom
feat/update-notify
Oct 29, 2026
Merged

keyldev merged 2 commits into
mainfrom
feat/update-notify

Conversation

@keyldev

@keyldev keyldev commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator
  • Check the build's UpdateFeed on launch and daily; banner offers newer versions
  • Download the platform archive into Downloads with sha256 check; APK link on mobile
  • UpdateFeed MSBuild property: github:, https latest.json, or none
  • Settings > About: auto-check toggle, manual check, skip-version

Summary

Related

Type

  • Bug fix
  • Feature
  • Refactor / cleanup
  • Docs / CI
  • Other:

Checklist

  • Builds with 0 warnings (TreatWarningsAsErrors=true).
  • Tests pass (dotnet test); new Core logic has unit tests.
  • No layering violation — App references Core only (Infrastructure / Video / Devices wired via DI in a head).
  • Scope stays within one phase (didn't pull work from a later phase's "Не входит").
  • README / docs updated if public commands, options, or setup changed.

Platforms tested

  • Windows
  • Linux
  • macOS
  • Android
  • iOS
  • CI build only

Screenshots / notes

- Check the build's UpdateFeed on launch and daily; banner offers newer versions
- Download the platform archive into Downloads with sha256 check; APK link on mobile
- UpdateFeed MSBuild property: github:<repo>, https latest.json, or none
- Settings > About: auto-check toggle, manual check, skip-version
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Add configurable release notifications and platform downloads

✨ Enhancement ⚙️ Configuration changes 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Check the build-selected release feed on launch and daily; offer newer platform builds in a
 banner.
• Save desktop archives to Downloads with SHA-256 verification when a hash is available; open mobile
 APK links in the browser.
• Add About-page check preferences and manual checks; document the notification-only update
 workflow.
Diagram

graph TD
  Build["Build feed"] --> Factory["Feed factory"] --> Feeds["GitHub or manifest"] --> Service["Update service"] --> Banner["Update banner"] --> Downloader["Archive downloader"] --> Downloads[("Downloads folder")]
  Settings["About preferences"] --> Service
  Service --> Downloader
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Link to the release page only
  • ➕ Avoids in-app archive transfer, local file handling, and integrity-check logic.
  • ➖ Loses platform-specific downloads, progress, and the requested Downloads workflow.
2. Adopt a self-updating installer
  • ➕ Could eventually handle installation and replacement rather than only notification.
  • ➖ Requires packaging and platform-specific installation work beyond this PR's notification-only scope.

Recommendation: Keep the feed abstraction and notification-only approach: they support multiple distribution channels without committing to an installer. Before relying on the download as hash-checked, decide whether desktop assets without a published SHA-256 should be rejected, since the current downloader accepts them.

Files changed (24) +1288 / -5

Enhancement (18) +1049 / -1
Localizer.csLocalize update controls and statuses +38/-0

Localize update controls and statuses

• Adds English and Russian strings for About-page update controls, check results, banner actions, download progress, and failures.

src/OpenIPC.Viewer.App/Services/Localizer.cs

UpdateService.csCoordinate periodic and manual release checks +163/-0

Coordinate periodic and manual release checks

• Checks the configured feed after launch and daily, compares versions and platform assets, and publishes banner availability. Handles disabled feeds, failures, skipped versions, and manual rechecks.

src/OpenIPC.Viewer.App/Services/UpdateService.cs

UserSettings.csPersist update-check and skipped-version preferences +6/-1

Persist update-check and skipped-version preferences

• Adds an enabled-by-default automatic-check setting and a version identifier for dismissing a release until a newer one appears.

src/OpenIPC.Viewer.App/Services/UserSettings.cs

Theme.axamlAdd an update-banner download icon +2/-0

Add an update-banner download icon

• Defines a download geometry for the new banner.

src/OpenIPC.Viewer.App/Themes/Theme.axaml

MainWindowViewModel.csExpose the update banner to the main view +3/-0

Expose the update banner to the main view

• Injects and exposes UpdateBannerViewModel alongside the existing page view models.

src/OpenIPC.Viewer.App/ViewModels/MainWindowViewModel.cs

SettingsPageViewModel.csAdd About-page update controls +44/-0

Add About-page update controls

• Loads and persists the automatic-check preference. Adds a manual check with status feedback and hides update controls when the build has no feed.

src/OpenIPC.Viewer.App/ViewModels/SettingsPageViewModel.cs

UpdateBannerViewModel.csManage update offer and download actions +171/-0

Manage update offer and download actions

• Starts background checks and tracks banner visibility, progress, saved files, and errors. Supports download or browser handoff, cancellation, release notes, Later, and Skip.

src/OpenIPC.Viewer.App/ViewModels/UpdateBannerViewModel.cs

MainView.axamlPlace the update banner above page content +4/-0

Place the update banner above page content

• Adds the banner at the top of the main content area and hides it with the rest of the chrome in fullscreen mode.

src/OpenIPC.Viewer.App/Views/MainView.axaml

SettingsPage.axamlShow update options in Settings About +17/-0

Show update options in Settings About

• Adds the automatic-check checkbox, manual-check button, result text, and configured feed name when updates are supported.

src/OpenIPC.Viewer.App/Views/Pages/SettingsPage.axaml

UpdateBanner.axamlRender the release offer and its actions +78/-0

Render the release offer and its actions

• Introduces a responsive banner with version and status text, download progress, and context-dependent actions.

src/OpenIPC.Viewer.App/Views/UpdateBanner.axaml

UpdateBanner.axaml.csInitialize the update banner control +11/-0

Initialize the update banner control

• Adds the Avalonia UserControl code-behind required to load the banner markup.

src/OpenIPC.Viewer.App/Views/UpdateBanner.axaml.cs

SharedComposition.csWire update feeds, downloader, and view model +17/-0

Wire update feeds, downloader, and view model

• Registers the downloader, build-selected feed through UpdateService, and banner view model in shared DI. Uses separate HTTP clients and timeouts for checks and archive downloads.

src/OpenIPC.Viewer.Composition/SharedComposition.cs

AppVersion.csCompare release and git-derived versions +108/-0

Compare release and git-derived versions

• Adds parsing and ordering for release tags, prereleases, vendor labels, and git-describe builds so checks can distinguish newer releases.

src/OpenIPC.Viewer.Core/Updates/AppVersion.cs

IUpdateFeed.csDefine feed and downloader contracts +37/-0

Define feed and downloader contracts

• Introduces Core interfaces for obtaining the latest release and downloading a platform asset with progress and optional hash verification.

src/OpenIPC.Viewer.Core/Updates/IUpdateFeed.cs

UpdateRelease.csModel releases and platform assets +41/-0

Model releases and platform assets

• Defines platform identifiers, release and asset records, and exact platform-and-architecture asset lookup.

src/OpenIPC.Viewer.Core/Updates/UpdateRelease.cs

GithubReleaseFeed.csRead latest GitHub release assets +95/-0

Read latest GitHub release assets

• Fetches GitHub’s latest published release, maps known archive and APK names to targets, and reads asset digests when present.

src/OpenIPC.Viewer.Infrastructure/Updates/GithubReleaseFeed.cs

ManifestUpdateFeed.csRead hosted latest.json manifests +76/-0

Read hosted latest.json manifests

• Parses versions, assets, hashes, and optional release pages from a hosted manifest, resolving asset paths relative to its URL.

src/OpenIPC.Viewer.Infrastructure/Updates/ManifestUpdateFeed.cs

UpdateDownloader.csDownload desktop archives into Downloads +138/-0

Download desktop archives into Downloads

• Detects the running platform, streams assets to a temporary file, verifies SHA-256 when supplied, and moves completed archives into Downloads. Reuses an existing file only when its supplied hash matches.

src/OpenIPC.Viewer.Infrastructure/Updates/UpdateDownloader.cs

Tests (2) +180 / -0
AppVersionTests.csTest release-version parsing and ordering +54/-0

Test release-version parsing and ordering

• Covers tags, prereleases, vendor versions, git-describe builds, invalid strings, and equality.

tests/OpenIPC.Viewer.Core.Tests/Updates/AppVersionTests.cs

UpdateFeedTests.csTest feed parsing and selection +126/-0

Test feed parsing and selection

• Checks GitHub asset classification and digest parsing, manifest URL resolution, feed-factory choices, and stamped build configuration.

tests/OpenIPC.Viewer.Infrastructure.Tests/UpdateFeedTests.cs

Documentation (2) +13 / -4
README.mdExplain the release notification workflow +8/-3

Explain the release notification workflow

• Describes the banner, desktop archive download and manual replacement, Android browser behavior, and About-page toggle.

README.md

ROADMAP.mdSeparate notifications from self-replacing updates +5/-1

Separate notifications from self-replacing updates

• Marks update notifications complete while leaving native installers and self-replacing updates as future work.

docs/ROADMAP.md

Other (2) +46 / -0
Directory.Build.propsStamp the selected update feed into builds +10/-0

Stamp the selected update feed into builds

• Defaults UpdateFeed to the project’s GitHub repository and embeds its value as assembly metadata. Builds can instead specify an HTTPS manifest or none.

Directory.Build.props

UpdateFeedFactory.csSelect a feed from assembly metadata +36/-0

Select a feed from assembly metadata

• Creates a GitHub or HTTPS manifest feed from the build’s UpdateFeed value, or disables checks for none and invalid specifications.

src/OpenIPC.Viewer.Infrastructure/Updates/UpdateFeedFactory.cs

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Store builds fail the update-feed test ✓ Resolved
Description
Build_is_stamped_with_a_feed asserts that the configured feed always creates a non-null feed.
Builds configured with the documented UpdateFeed=none instead return null, so their test run
fails.
Code

tests/OpenIPC.Viewer.Infrastructure.Tests/UpdateFeedTests.cs[124]

+        Assert.NotNull(UpdateFeedFactory.Create(UpdateFeedFactory.ConfiguredSpec, http));
Evidence
The new test requires a feed, while the new factory deliberately returns null for none.

tests/OpenIPC.Viewer.Infrastructure.Tests/UpdateFeedTests.cs[119-125]
src/OpenIPC.Viewer.Infrastructure/Updates/UpdateFeedFactory.cs[20-24]
Directory.Build.props[23-30]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The build-metadata test fails for the supported `UpdateFeed=none` configuration.
## Fix Focus Areas
- tests/OpenIPC.Viewer.Infrastructure.Tests/UpdateFeedTests.cs[119-125]
## Recommended Fix
Make the assertion conditional on the embedded specification, and add an explicit test for `none` returning null.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Older checks can restore skipped offers ✓ Resolved
Description
CheckAsync allows daily and manual feed requests to overlap, and Publish accepts whichever
distinct result completes last without checking whether it has been superseded. An earlier request
can therefore replace a newer offer with an older one, or republish an offer while
SkipAvailableAsync is still persisting the skipped version.
Code

src/OpenIPC.Viewer.App/Services/UpdateService.cs[R127-129]

+        var skipped = !manual && string.Equals(_settings.Current.SkippedUpdateVersion, latest.Version.ToString(), StringComparison.Ordinal);
+        Publish(skipped ? null : latest, skipped ? null : asset, force: manual);
+        return new UpdateCheckOutcome(UpdateCheckStatus.Available, latest);
Evidence
The background and manual paths both invoke the unsynchronized check, while publishing replaces the
current offer on any version difference and skip persists after clearing it.

src/OpenIPC.Viewer.App/Services/UpdateService.cs[78-86]
src/OpenIPC.Viewer.App/ViewModels/SettingsPageViewModel.cs[479-485]
src/OpenIPC.Viewer.App/Services/UpdateService.cs[125-139]
src/OpenIPC.Viewer.App/Services/UpdateService.cs[148-161]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Overlapping checks can publish stale releases or restore an offer while it is being skipped.
## Fix Focus Areas
- src/OpenIPC.Viewer.App/Services/UpdateService.cs[93-129]
- src/OpenIPC.Viewer.App/Services/UpdateService.cs[132-161]
## Recommended Fix
Serialize checks or assign request generations and discard superseded results. Coordinate skip persistence with publication so a check cannot republish the version being skipped.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Release links can launch other schemes ✓ Resolved
Description
ManifestUpdateFeed.Parse accepts any absolute pageUrl and lets an absolute file URI override
the HTTPS manifest URL. Clicking “What's new” or mobile “Download” passes those values to
OpenUrlAsync, which launches absolute URIs without restricting their scheme, so a supplied
manifest can invoke a local or custom-protocol handler instead of a web page.
Code

src/OpenIPC.Viewer.Infrastructure/Updates/ManifestUpdateFeed.cs[54]

+        var page = Uri.TryCreate(Str(root, "pageUrl"), UriKind.Absolute, out var p) ? p : null;
Evidence
The feed accepts unrestricted absolute URIs, and the existing launcher validates absoluteness but
not the scheme before opening them.

src/OpenIPC.Viewer.Infrastructure/Updates/ManifestUpdateFeed.cs[44-54]
src/OpenIPC.Viewer.App/ViewModels/UpdateBannerViewModel.cs[111-115]
src/OpenIPC.Viewer.App/ViewModels/UpdateBannerViewModel.cs[155-160]
src/OpenIPC.Viewer.App/Services/DialogService.cs[340-349]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Feed-supplied release and asset links can invoke non-web URI handlers when clicked.
## Fix Focus Areas
- src/OpenIPC.Viewer.Infrastructure/Updates/ManifestUpdateFeed.cs[44-54]
- src/OpenIPC.Viewer.App/ViewModels/UpdateBannerViewModel.cs[111-115]
- src/OpenIPC.Viewer.App/ViewModels/UpdateBannerViewModel.cs[155-160]
## Recommended Fix
Accept only HTTPS release-page and asset URLs from feeds, including absolute `file` values after resolution; reject or omit links with other schemes before exposing the actions.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Preview releases reach stable users ✓ Resolved
Description
AppVersion newly classifies preview as a pre-release, but the release workflow does not mark
-preview tags as GitHub pre-releases. Publishing such a tag makes it eligible for the
/releases/latest endpoint used by stable builds, which can then offer that preview as an update.
Code

src/OpenIPC.Viewer.Core/Updates/AppVersion.cs[R101-102]

+        "preview" => -2,
+        "rc" => -1,
Evidence
The parser explicitly recognizes preview as a pre-release, while the workflow's release flag omits
it and the new feed relies on GitHub's latest-release filtering.

src/OpenIPC.Viewer.Core/Updates/AppVersion.cs[97-105]
.github/workflows/build.yml[324-326]
src/OpenIPC.Viewer.Infrastructure/Updates/GithubReleaseFeed.cs[7-9]
src/OpenIPC.Viewer.Infrastructure/Updates/GithubReleaseFeed.cs[23-26]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new preview version type is not classified as a pre-release by the release workflow.
## Fix Focus Areas
- src/OpenIPC.Viewer.Core/Updates/AppVersion.cs[97-105]
- .github/workflows/build.yml[324-326]
## Recommended Fix
Include `-preview` in the workflow's GitHub pre-release condition and test the classification against the labels accepted by the version parser.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View review recommended (2)
5. Banner keeps showing an older downloaded archive ✓ Resolved
Description
OnAvailableChanged clears DownloadedPath only when the saved file name differs from the new
asset's FileName, even though successive releases reuse names such as openipc-viewer-.zip or
.tar.gz and org.openipc.viewer-Signed.apk. If a newer release appears while the app is running,
the banner continues to show the older archive as “Saved to …” and hides the Download button; an
in-flight download for the older offer can also set completed state after the offer changes.
Code

src/OpenIPC.Viewer.App/ViewModels/UpdateBannerViewModel.cs[R91-93]

+        if (DownloadedPath is not null && _updates.AvailableAsset is { } asset
+            && !string.Equals(Path.GetFileName(DownloadedPath), asset.FileName, StringComparison.Ordinal))
+            DownloadedPath = null;
Evidence
build.yml names archives openipc-viewer-${{ matrix.rid }}.zip/.tar.gz without a version, and
GithubReleaseFeed.ClassifyAsset relies on those names. Publish replaces Available and
AvailableAsset and raises Changed when the version changes, but the banner's filename comparison
cannot distinguish the old download from the new offer, so it retains the stale path.

.github/workflows/build.yml[100-109]
src/OpenIPC.Viewer.App/Services/UpdateService.cs[148-162]
src/OpenIPC.Viewer.App/Services/UpdateService.cs[148-161]
src/OpenIPC.Viewer.App/ViewModels/UpdateBannerViewModel.cs[79-80]
src/OpenIPC.Viewer.App/ViewModels/UpdateBannerViewModel.cs[87-98]
src/OpenIPC.Viewer.Infrastructure/Updates/UpdateDownloader.cs[28-40]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The update banner identifies a completed download by filename even though successive releases reuse asset filenames. A saved path, or a download still running for an older offer, must not become completed state for a newer offer.
## Fix Focus Areas
- src/OpenIPC.Viewer.App/ViewModels/UpdateBannerViewModel.cs[87-98]
- src/OpenIPC.Viewer.App/ViewModels/UpdateBannerViewModel.cs[106-143]
## Recommended Fix
Associate a completed download with the release `AppVersion` and asset identity captured when the download starts. In `OnAvailableChanged`, clear `DownloadedPath` when the available offer no longer matches that identity. Cancel downloads running for an older offer, and guard download completion so an old download cannot set completed state for a new offer.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Skipping a version can undo a settings change ✓ Resolved
Description
SkipAvailableAsync calls `_settings.UpdateAsync(_settings.Current with { SkippedUpdateVersion =
version }), which reads Current` before it takes the save gate. If another save is in flight at
the same time (for example, Settings persisting a toggle through the mutate overload), one write
overwrites the other with a stale snapshot, so either that setting change or the skipped version is
lost.
Code

src/OpenIPC.Viewer.App/Services/UpdateService.cs[138]

+        await _settings.UpdateAsync(_settings.Current with { SkippedUpdateVersion = version }).ConfigureAwait(true);
Evidence
UserSettingsService has a Func overload, and its comment warns that UpdateAsync(Current with …)
reads Current before the gate and can overwrite other pages' changes. SettingsPageViewModel.Persist
already uses the mutate form.

src/OpenIPC.Viewer.App/Services/UserSettingsService.cs[70-101]
src/OpenIPC.Viewer.App/ViewModels/SettingsPageViewModel.cs[353-361]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
SkipAvailableAsync writes a snapshot of settings taken before the save gate, which can overwrite concurrent changes.
## Fix Focus Areas
- src/OpenIPC.Viewer.App/Services/UpdateService.cs[133-139]
## Recommended Fix
Replace it with `await _settings.UpdateAsync(s => s with { SkippedUpdateVersion = version }).ConfigureAwait(true);`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

7. One unexpected error stops daily checks for good ✓ Resolved
Description
UpdateService.CheckAsync catches only HttpRequestException, TaskCanceledException and
JsonException, while malformed manifest elements can throw during property access and
AppVersion.TryParse can throw when int.Parse receives oversized digit groups. These and other
uncaught exceptions escape the fire-and-forget BackgroundLoopAsync, stopping future checks without
an observed error; during a manual check, they reach the command unhandled and can leave its status
at “Checking…”.
Code

src/OpenIPC.Viewer.App/Services/UpdateService.cs[R75-87]

+        _ = BackgroundLoopAsync();
+    }
+
+    private async Task BackgroundLoopAsync()
+    {
+        await Task.Delay(FirstCheckDelay).ConfigureAwait(true);
+        while (true)
+        {
+            // Re-read each round: the toggle in Settings applies without a restart.
+            if (_settings.Current.CheckForUpdates)
+                await CheckAsync(manual: false, CancellationToken.None).ConfigureAwait(true);
+            await Task.Delay(CheckInterval).ConfigureAwait(true);
+        }
Evidence
The background loop has no try/catch and discards its task, while CheckAsync uses a narrow
exception filter. The manifest parser reads properties from array elements without checking their
JSON kinds, so a non-object element can throw outside that filter. Both feeds pass remote version
strings to AppVersion.TryParse, whose regex accepts digit groups larger than int.Parse can
represent.

src/OpenIPC.Viewer.App/Services/UpdateService.cs[78-107]
src/OpenIPC.Viewer.Infrastructure/Updates/ManifestUpdateFeed.cs[40-50]
src/OpenIPC.Viewer.Infrastructure/Updates/ManifestUpdateFeed.cs[74-75]
src/OpenIPC.Viewer.App/Services/UpdateService.cs[78-87]
src/OpenIPC.Viewer.App/Services/UpdateService.cs[97-107]
src/OpenIPC.Viewer.App/ViewModels/SettingsPageViewModel.cs[479-499]
src/OpenIPC.Viewer.Core/Updates/AppVersion.cs[19-24]
src/OpenIPC.Viewer.Core/Updates/AppVersion.cs[49-64]
src/OpenIPC.Viewer.Core/Updates/AppVersion.cs[107-107]
src/OpenIPC.Viewer.Infrastructure/Updates/ManifestUpdateFeed.cs[33-37]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Unexpected exceptions from HTTP operations or feed parsing can escape `CheckAsync`, silently end the background update loop, and reach manual checks unhandled. Examples include non-object manifest entries and oversized remote version components.
## Fix Focus Areas
- src/OpenIPC.Viewer.App/Services/UpdateService.cs[78-107]
- src/OpenIPC.Viewer.Infrastructure/Updates/ManifestUpdateFeed.cs[40-50]
- src/OpenIPC.Viewer.Infrastructure/Updates/ManifestUpdateFeed.cs[74-75]
- src/OpenIPC.Viewer.Core/Updates/AppVersion.cs[49-64]
- src/OpenIPC.Viewer.Core/Updates/AppVersion.cs[107-107]
## Recommended Fix
Validate each manifest array element’s JSON kind before reading its properties. Use non-throwing numeric conversions for every version component, returning false when a component exceeds the supported range, and test oversized feed versions. Convert malformed-feed failures into failed checks; protect the background loop by catching and logging exceptions around `CheckAsync`, or widen `CheckAsync`’s catch to cover non-fatal exceptions and return `Failed`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


8. Manifest downloads can skip HTTPS and the hash check ✓ Resolved
Description
ManifestUpdateFeed.Parse accepts resolved asset URLs with non-HTTPS schemes and permits a missing
sha256, while UpdateDownloader.DownloadAsync verifies SHA-256 only when asset.Sha256 is
present. A hashless manifest entry pointing to plain HTTP can therefore save an unverified archive
into Downloads, and GitHub assets without a digest—including the macOS asset in the test fixture—can
likewise be presented as completed downloads without the integrity check promised by the UI and
README.
Code

src/OpenIPC.Viewer.Infrastructure/Updates/ManifestUpdateFeed.cs[R47-50]

+                if (!Uri.TryCreate(manifestUrl, file, out var url)) continue;
+                var size = f.TryGetProperty("size", out var s) && s.TryGetInt64(out var n) ? n : 0;
+                var sha = Str(f, "sha256")?.ToLowerInvariant();
+                assets.Add(new UpdateAsset(platform, ArchOf(file, Str(f, "label")), file, url, size, sha));
Evidence
UpdateFeedFactory requires HTTPS for the manifest URL, but ManifestUpdateFeed.Parse does not check
the resolved asset URL’s scheme or require sha256. UpdateDownloader.DownloadAsync makes its sole
integrity check conditional on a digest being present before moving the download to its final
archive path. GithubReleaseFeed can also supply an asset without a digest, as the macOS test
fixture demonstrates; assets uploaded before GitHub added the digest field can have the same
condition.

src/OpenIPC.Viewer.Infrastructure/Updates/UpdateDownloader.cs[64-69]
src/OpenIPC.Viewer.Infrastructure/Updates/UpdateFeedFactory.cs[32-34]
src/OpenIPC.Viewer.Infrastructure/Updates/UpdateDownloader.cs[64-71]
src/OpenIPC.Viewer.Infrastructure/Updates/GithubReleaseFeed.cs[55-60]
src/OpenIPC.Viewer.Infrastructure/Updates/ManifestUpdateFeed.cs[44-50]
tests/OpenIPC.Viewer.Infrastructure.Tests/UpdateFeedTests.cs[18-19]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Desktop archives from either feed can be offered and saved without an integrity check; manifest assets can also use plain HTTP.
## Fix Focus Areas
- src/OpenIPC.Viewer.Infrastructure/Updates/ManifestUpdateFeed.cs[44-50]
- src/OpenIPC.Viewer.Infrastructure/Updates/GithubReleaseFeed.cs[55-60]
- src/OpenIPC.Viewer.Infrastructure/Updates/UpdateDownloader.cs[64-69]
## Recommended Fix
Skip manifest entries whose resolved `url.Scheme != Uri.UriSchemeHttps`. Require a valid SHA-256 digest before offering an in-app desktop download from either feed, fail visibly when a feed lacks one, and always verify the digest before moving the temporary file to its final archive path.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can add REVIEW.md to your repo root and Qodo follows it on every PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

@keyldev keyldev changed the title Add update notifications (GitHub / release manifest feed) feat(update): Add update notifications (GitHub / release manifest feed) Oct 29, 2026
- Only the newest check publishes; Skip uses the mutate overload and wins over racing checks
- Background loop survives unexpected errors; version and manifest parsing no longer throw
- Banner ties a saved archive to its release; hashless assets open in the browser
- https-only feed links and downloads; -preview tags marked pre-release and skipped
@keyldev
keyldev merged commit 8053656 into main Oct 29, 2026
6 checks passed
@keyldev
keyldev deleted the feat/update-notify branch October 29, 2026 15:35
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.

1 participant