Skip to content

fix: app cannot fetch updates because of GitHub rate limits#243

Merged
vitonsky merged 7 commits into
masterfrom
203-app-cannot-fetch-updates-due-to-a-rate-limits-of-github-releases-public-api
Mar 7, 2026
Merged

fix: app cannot fetch updates because of GitHub rate limits#243
vitonsky merged 7 commits into
masterfrom
203-app-cannot-fetch-updates-due-to-a-rate-limits-of-github-releases-public-api

Conversation

@vitonsky

@vitonsky vitonsky commented Mar 7, 2026

Copy link
Copy Markdown
Member

Fix #203

Now we use a static page on our site to check for updates.

Summary by CodeRabbit

  • New Features

    • Added a configurable app update checker that queries a custom update host and returns version + URL info.
  • Refactor

    • Replaced the previous GitHub-based update implementation with the new host-based update service.
    • Update flow now uses a canonical download URL surfaced to the UI.
  • Tests

    • Added comprehensive tests and fixtures covering release and prerelease update detection.

@coderabbitai

coderabbitai Bot commented Mar 7, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Replaces the GitHub releases updater with a host-configurable updater. Removes GitHubReleaseUpdatesChecker and its tests, adds AppUpdatesChecker that fetches and validates versions.json from a configured host, and updates consumers and tests to use the new host-based flow.

Changes

Cohort / File(s) Summary
New AppUpdatesChecker Implementation
src/electron/updates/AppUpdatesChecker.ts
Adds AppUpdatesChecker with a zod VersionObjectScheme, exported types (VersionObject, AppVersionInfo) and getUpdate({ version }) that fetches versions.json from a configurable host, validates entries, selects the latest semver (stripping leading v), compares with current version using semver.gt, and returns { version, url } or null on no update or errors.
Removed GitHub-based Update Checker
src/electron/updates/GitHubReleaseUpdatesChecker.ts, src/electron/updates/GitHubReleaseUpdatesChecker.test.ts
Deletes the GitHub releases updater and its test suite, removing ReleaseObjectScheme, GitHubReleaseUpdatesChecker, and the previous AppVersionInfo declaration.
Test Suite & Fixture for Host-based Updater
src/electron/updates/__tests__/AppUpdatesChecker.test.ts, src/electron/updates/__tests__/AppUpdatesChecker.unit.test.ts, src/electron/updates/__tests__/versions.json
Adds unit/integration tests that mock fetch and assert getUpdate behavior across normal and prerelease versions, error cases, and empty responses; includes a versions.json fixture with multiple prerelease entries and platform assets.
Consumer Updates
src/features/App/Settings/sections/GeneralSettings.tsx, src/features/App/useGetAppUpdates.tsx
Replaces imports of the removed GitHub checker with AppUpdatesChecker and AppVersionInfo, instantiates with { host: 'https://deepink.io' }, replaces .checkForUpdates(...) with .getUpdate(...), and maps the returned info to a fixed download URL (https://deepink.io/download) for UI actions.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped from GitHub to a friendly host,
Fetching versions I like the most.
Semver trimmed, prereleases checked,
No rate-limit scare, my build's protected.
Hooray — I delivered updates, toasted! 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main change: replacing GitHub Releases API with a static site-based update check to bypass rate limiting issues.
Linked Issues check ✅ Passed The PR successfully addresses issue #203 by replacing GitHub Releases API calls with a static versioning service, implementing valid version filtering logic.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the update-checking mechanism and its consuming components; no unrelated modifications detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 203-app-cannot-fetch-updates-due-to-a-rate-limits-of-github-releases-public-api

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (5)
src/electron/updates/__tests__/AppUpdatesChecker.unit.test.ts (2)

86-109: Good error handling coverage.

The "No updates cases" block effectively covers empty releases and server errors. Consider adding a test for network-level failures (fetch rejection) to ensure complete error path coverage.

Optional: Add network failure test
+	test('Returns null for network failures', async () => {
+		mockFetch.mockRejectedValue(new Error('Network error'));
+
+		await expect(
+			updatesChecker.getUpdate({
+				version: '0.0.1',
+			}),
+		).resolves.toEqual(null);
+	});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/electron/updates/__tests__/AppUpdatesChecker.unit.test.ts` around lines
86 - 109, Add a test to cover network-level failures by mocking the fetch used
by AppUpdatesChecker to reject (e.g., mockFetch.mockRejectedValueOnce(new
Error('Network error'))) and verifying AppUpdatesChecker.getUpdate(...) resolves
to null; place this new test inside the existing "No updates cases" describe
alongside the tests using mockVersions and the server error mock so it exercises
the network rejection path for getUpdate.

1-16: Consider adding explicit vitest imports.

Similar to the other test file, explicit imports would improve clarity.

Suggested imports
 import { AppUpdatesChecker, VersionObject } from '../AppUpdatesChecker';
+import { vi, describe, beforeEach, test, expect } from 'vitest';

 const mockFetch = vi.fn();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/electron/updates/__tests__/AppUpdatesChecker.unit.test.ts` around lines 1
- 16, Add explicit vitest imports at the top of the test file so the test
helpers are clear: import the vi mock helper and beforeEach from 'vitest' (used
by mockFetch and beforeEach), then remove reliance on implicit globals; update
the top of the file where AppUpdatesChecker and VersionObject are imported to
also import { vi, beforeEach } from 'vitest' so symbols like vi.fn and
beforeEach resolve explicitly.
src/electron/updates/AppUpdatesChecker.ts (1)

4-8: The prerelease field is validated but unused.

VersionObjectScheme requires a prerelease boolean field, but getUpdate doesn't use it in the update detection logic. This is fine if planned for future use (e.g., allowing users to opt-out of prerelease updates), but could be removed if not needed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/electron/updates/AppUpdatesChecker.ts` around lines 4 - 8, The
VersionObjectScheme currently declares a prerelease boolean that's never used by
the update detection logic in getUpdate; either remove the unused field from
VersionObjectScheme (and any code that expects versionObj.prerelease) or wire it
into getUpdate (e.g., respect prerelease when comparing/choosing updates). Pick
one: if you don't support prerelease yet, delete the prerelease entry from
VersionObjectScheme to keep the schema accurate; if you do want prerelease
support, update getUpdate to inspect versionObj.prerelease and incorporate it
into update selection logic.
src/features/App/useGetAppUpdates.tsx (1)

72-73: URL override creates inconsistency with API response.

The code mutates newVersion.url to a hardcoded download page URL, discarding the release-specific URL returned by AppUpdatesChecker. While this aligns with the PR objective to direct users to the site's download page, consider:

  1. The mutation pattern is fragile—if AppVersionInfo becomes immutable (e.g., readonly properties), this will break.
  2. The AppUpdatesChecker.getUpdate() already returns a url field that's now being ignored.

A cleaner approach would be to either:

  • Configure the download URL in AppUpdatesChecker or its config
  • Or create a new object instead of mutating
Option: Create new object instead of mutating
 			.then((newVersion) => {
 				if (!newVersion) return null;

-				newVersion.url = 'https://deepink.io/download';
-
+				const versionWithDownloadUrl = {
+					...newVersion,
+					url: 'https://deepink.io/download',
+				};
+
 				showToast({
 					duration: null,
 					position: 'top-right',
 					containerStyle: {
 						maxW: '350px',
 					},
 					render(props) {
 						return (
 							<AppToast
 								title="New version is available"
 								body="Update app to a latest version to get new features and improvements"
 								actions={
 									<>
 										<Button
 											size="sm"
 											variant="accent"
 											onClick={() => {
-												window.open(newVersion.url);
+												window.open(versionWithDownloadUrl.url);
 												localStorage.removeItem(
 													ignoreFlagKey,
 												);
 												props.onClose();
 											}}
 										>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/App/useGetAppUpdates.tsx` around lines 72 - 73, The current code
mutates newVersion.url to a hardcoded download page which discards the
release-specific URL from AppUpdatesChecker.getUpdate() and is fragile if
AppVersionInfo becomes readonly; instead, stop mutating the returned object and
either (A) have AppUpdatesChecker accept a configured downloadUrl so getUpdate()
returns the desired URL, or (B) create a new object in useGetAppUpdates (e.g.,
const displayVersion = { ...newVersion, url: 'https://deepink.io/download' })
and use displayVersion for UI, leaving AppUpdatesChecker.getUpdate() and the
original newVersion untouched.
src/electron/updates/__tests__/AppUpdatesChecker.test.ts (1)

1-5: Consider adding explicit vitest imports for clarity.

The test uses vi and beforeEach without explicit imports. While Vitest supports globals mode, explicit imports improve code clarity and IDE support.

Suggested imports
 import { AppUpdatesChecker } from '../AppUpdatesChecker';
 import versions from './versions.json';
+import { vi, beforeEach, test, expect } from 'vitest';

 const mockFetch = vi.fn();
 globalThis.fetch = mockFetch;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/electron/updates/__tests__/AppUpdatesChecker.test.ts` around lines 1 - 5,
The test currently relies on Vitest globals (uses vi and beforeEach) without
explicit imports; add explicit imports from 'vitest' (e.g., import { vi,
beforeEach, afterEach, it, expect } from 'vitest') at the top of the test file
so IDEs and readers can resolve symbols like vi, beforeEach and test helpers
used alongside AppUpdatesChecker and the versions fixture; keep only the helpers
you actually use to avoid unused-import lint warnings.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/electron/updates/AppUpdatesChecker.ts`:
- Around line 37-38: Validate the version strings before calling semver.gt in
AppUpdatesChecker.ts: check semver.valid(latestVersion) and
semver.valid(context.version) (the variables from the diff) and handle invalid
cases explicitly (e.g., log a clear error or skip comparison) instead of relying
on the try-catch; only call semver.gt(latestVersion, context.version) when both
are valid to avoid TypeError and preserve clearer error messages.

---

Nitpick comments:
In `@src/electron/updates/__tests__/AppUpdatesChecker.test.ts`:
- Around line 1-5: The test currently relies on Vitest globals (uses vi and
beforeEach) without explicit imports; add explicit imports from 'vitest' (e.g.,
import { vi, beforeEach, afterEach, it, expect } from 'vitest') at the top of
the test file so IDEs and readers can resolve symbols like vi, beforeEach and
test helpers used alongside AppUpdatesChecker and the versions fixture; keep
only the helpers you actually use to avoid unused-import lint warnings.

In `@src/electron/updates/__tests__/AppUpdatesChecker.unit.test.ts`:
- Around line 86-109: Add a test to cover network-level failures by mocking the
fetch used by AppUpdatesChecker to reject (e.g.,
mockFetch.mockRejectedValueOnce(new Error('Network error'))) and verifying
AppUpdatesChecker.getUpdate(...) resolves to null; place this new test inside
the existing "No updates cases" describe alongside the tests using mockVersions
and the server error mock so it exercises the network rejection path for
getUpdate.
- Around line 1-16: Add explicit vitest imports at the top of the test file so
the test helpers are clear: import the vi mock helper and beforeEach from
'vitest' (used by mockFetch and beforeEach), then remove reliance on implicit
globals; update the top of the file where AppUpdatesChecker and VersionObject
are imported to also import { vi, beforeEach } from 'vitest' so symbols like
vi.fn and beforeEach resolve explicitly.

In `@src/electron/updates/AppUpdatesChecker.ts`:
- Around line 4-8: The VersionObjectScheme currently declares a prerelease
boolean that's never used by the update detection logic in getUpdate; either
remove the unused field from VersionObjectScheme (and any code that expects
versionObj.prerelease) or wire it into getUpdate (e.g., respect prerelease when
comparing/choosing updates). Pick one: if you don't support prerelease yet,
delete the prerelease entry from VersionObjectScheme to keep the schema
accurate; if you do want prerelease support, update getUpdate to inspect
versionObj.prerelease and incorporate it into update selection logic.

In `@src/features/App/useGetAppUpdates.tsx`:
- Around line 72-73: The current code mutates newVersion.url to a hardcoded
download page which discards the release-specific URL from
AppUpdatesChecker.getUpdate() and is fragile if AppVersionInfo becomes readonly;
instead, stop mutating the returned object and either (A) have AppUpdatesChecker
accept a configured downloadUrl so getUpdate() returns the desired URL, or (B)
create a new object in useGetAppUpdates (e.g., const displayVersion = {
...newVersion, url: 'https://deepink.io/download' }) and use displayVersion for
UI, leaving AppUpdatesChecker.getUpdate() and the original newVersion untouched.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bf4d1483-9462-42e8-9776-f16de76d845b

📥 Commits

Reviewing files that changed from the base of the PR and between 65d058d and 2220aad.

📒 Files selected for processing (8)
  • src/electron/updates/AppUpdatesChecker.ts
  • src/electron/updates/GitHubReleaseUpdatesChecker.test.ts
  • src/electron/updates/GitHubReleaseUpdatesChecker.ts
  • src/electron/updates/__tests__/AppUpdatesChecker.test.ts
  • src/electron/updates/__tests__/AppUpdatesChecker.unit.test.ts
  • src/electron/updates/__tests__/versions.json
  • src/features/App/Settings/sections/GeneralSettings.tsx
  • src/features/App/useGetAppUpdates.tsx
💤 Files with no reviewable changes (2)
  • src/electron/updates/GitHubReleaseUpdatesChecker.test.ts
  • src/electron/updates/GitHubReleaseUpdatesChecker.ts

Comment thread src/electron/updates/AppUpdatesChecker.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/features/App/useGetAppUpdates.tsx (1)

72-119: Keep the update CTA aligned with the fetched release metadata.

AppUpdatesChecker.getUpdate() already returns the canonical url from /versions.json, but this hook drops it and always opens /download. That gives you two sources of truth for the same update and can send users somewhere different from the release metadata the checker just resolved. Prefer reusing newVersion.url, or make /download the only URL emitted by versions.json.

🔗 Suggested simplification
-					const updateUrl = 'https://deepink.io/download';
+					const updateUrl = newVersion.url;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/App/useGetAppUpdates.tsx` around lines 72 - 119, The hook
currently hardcodes updateUrl = 'https://deepink.io/download' and uses it in the
toast and return value; replace that with the canonical URL from the release
metadata (use newVersion.url) so the CTA and returned AppVersionInfo align with
AppUpdatesChecker.getUpdate(), and update the window.open call, toast actions,
and the returned object (version and url) to use newVersion.url instead of the
hardcoded updateUrl; keep existing ignoreUpdate and ignoreFlagKey behavior
unchanged.
src/electron/updates/__tests__/AppUpdatesChecker.test.ts (1)

26-30: Assert the proxied endpoint in this regression test.

This covers the version-comparison behavior, but it never proves that the checker actually requested the new site endpoint. A typo in host or /versions.json would still leave this test green, which weakens the main regression this PR is meant to protect.

📌 Suggested assertion
 	await expect(
 		updatesChecker.getUpdate({
 			version: '0.0.1',
 		}),
 	).resolves.toEqual(latestVersion);
+	expect(mockFetch).toHaveBeenNthCalledWith(
+		1,
+		'https://site.com/versions.json',
+	);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/electron/updates/__tests__/AppUpdatesChecker.test.ts` around lines 26 -
30, The test currently verifies version comparison but doesn't assert the actual
network call; update the test around updatesChecker.getUpdate to also assert
that the proxied endpoint was requested (e.g., that the HTTP client/mocker was
called with the configured host + '/versions.json' or that the test server
received a request to '/versions.json'). Reference the same setup values used in
the test (the configured host/URL and updatesChecker.getUpdate) and add an
assertion that the fetch/mock call includes the exact path '/versions.json' to
catch typos in the host or path.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/electron/updates/__tests__/AppUpdatesChecker.test.ts`:
- Around line 26-30: The test currently verifies version comparison but doesn't
assert the actual network call; update the test around updatesChecker.getUpdate
to also assert that the proxied endpoint was requested (e.g., that the HTTP
client/mocker was called with the configured host + '/versions.json' or that the
test server received a request to '/versions.json'). Reference the same setup
values used in the test (the configured host/URL and updatesChecker.getUpdate)
and add an assertion that the fetch/mock call includes the exact path
'/versions.json' to catch typos in the host or path.

In `@src/features/App/useGetAppUpdates.tsx`:
- Around line 72-119: The hook currently hardcodes updateUrl =
'https://deepink.io/download' and uses it in the toast and return value; replace
that with the canonical URL from the release metadata (use newVersion.url) so
the CTA and returned AppVersionInfo align with AppUpdatesChecker.getUpdate(),
and update the window.open call, toast actions, and the returned object (version
and url) to use newVersion.url instead of the hardcoded updateUrl; keep existing
ignoreUpdate and ignoreFlagKey behavior unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 81dff6cb-9d1c-4092-9c65-74da6f8b5621

📥 Commits

Reviewing files that changed from the base of the PR and between 2220aad and 125dc3d.

📒 Files selected for processing (2)
  • src/electron/updates/__tests__/AppUpdatesChecker.test.ts
  • src/features/App/useGetAppUpdates.tsx

@vitonsky vitonsky changed the title fix: app cannot fetch updates due to a rate limits of GitHub releases public api fix: app cannot fetch updates because of GitHub rate limits Mar 7, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/electron/updates/AppUpdatesChecker.ts (1)

4-8: Unused prerelease field.

The prerelease field is parsed but never used in the update selection logic. If prereleases should be excluded from stable update checks, consider filtering them out in getUpdate. If this is intentional (e.g., for future use or showing all releases), this can be ignored.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/electron/updates/AppUpdatesChecker.ts` around lines 4 - 8, The
VersionObjectScheme currently includes a prerelease boolean that's never used;
either remove prerelease from VersionObjectScheme if you don't intend to handle
it, or update the update-selection logic in getUpdate to respect it (e.g.,
filter out items where parsedVersion.prerelease === true when performing stable
update checks). Locate VersionObjectScheme and the getUpdate function and either
drop the prerelease property from the schema or add a filter step in getUpdate
that excludes prerelease releases before selecting the latest update.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/electron/updates/AppUpdatesChecker.ts`:
- Around line 4-8: The VersionObjectScheme currently includes a prerelease
boolean that's never used; either remove prerelease from VersionObjectScheme if
you don't intend to handle it, or update the update-selection logic in getUpdate
to respect it (e.g., filter out items where parsedVersion.prerelease === true
when performing stable update checks). Locate VersionObjectScheme and the
getUpdate function and either drop the prerelease property from the schema or
add a filter step in getUpdate that excludes prerelease releases before
selecting the latest update.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bf3f2abd-6833-4149-beeb-dcaa1b20d823

📥 Commits

Reviewing files that changed from the base of the PR and between 125dc3d and addc87b.

📒 Files selected for processing (2)
  • src/electron/updates/AppUpdatesChecker.ts
  • src/electron/updates/__tests__/AppUpdatesChecker.unit.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/electron/updates/tests/AppUpdatesChecker.unit.test.ts

@vitonsky vitonsky merged commit a7186f3 into master Mar 7, 2026
10 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.

App cannot fetch updates due to a rate limits of GitHub releases public API

1 participant