Skip to content

fix: route share.geolibre.app through native HTTP on desktop - #1109

Merged
giswqs merged 1 commit into
mainfrom
fix/desktop-share-cors-native-fetch
Jul 7, 2026
Merged

fix: route share.geolibre.app through native HTTP on desktop#1109
giswqs merged 1 commit into
mainfrom
fix/desktop-share-cors-native-fetch

Conversation

@giswqs

@giswqs giswqs commented Jul 7, 2026

Copy link
Copy Markdown
Member

Summary

  • On the Tauri desktop build, the project Share action fails with "Could not reach share.geolibre.app. Check your internet connection." This is a CORS block, not a network or CSP issue: the Share client uses the WebView browser fetch, and the share server's CORS policy allows the web origin but not the Tauri WebView origin, so the cross-origin request throws.
  • Route the share host through Tauri's native HTTP client (@tauri-apps/plugin-http), which is not bound by CORS, exactly as the existing geocoding fix does. Added a centralized, overridable share fetch (lib/share-fetch.ts); installNativeShareFetch() runs desktop-only and lazily, so web and embedded bundles never import the plugin.
  • Applied to the whole share client, not just upload: the gallery / "My Projects" reads hit the same host and had the same latent failure on desktop. Added https://share.geolibre.app/* to the http:default capability scope.

Test plan

  • tests/share-fetch.test.ts (new): default/override/reset of the share fetch, and that upload plus both gallery reads route through the installed fetch (with bearer token) - 43 share tests pass
  • Desktop app type-check clean; pre-commit (eslint + build) passes
  • Web build unaffected: the default share fetch is the browser fetch; only the desktop build swaps in the native path
  • In a packaged desktop build, sign in with a share API token and confirm Share uploads succeed with no CORS/network error in the console
  • In the packaged desktop build, open the project gallery and "My Projects" and confirm listings load

Summary by CodeRabbit

  • New Features

    • Desktop app now uses a dedicated share connection path, helping share-related actions work more reliably in the app.
    • Share requests can now be handled separately from the browser default, with support for desktop-specific routing.
  • Bug Fixes

    • Improved handling of share uploads and project listings to avoid browser security blocking in desktop mode.
    • Share host access now includes the app’s share domain for smoother desktop sharing.

The Share action and project gallery use the WebView fetch, which the
share server's CORS policy blocks from the Tauri origin, so desktop users
hit "Could not reach share.geolibre.app." Route the share host through the
native HTTP client (as geocoding already does) to bypass WebView CORS.
Copilot AI review requested due to automatic review settings July 7, 2026 14:08
@netlify

netlify Bot commented Jul 7, 2026

Copy link
Copy Markdown

Deploy Preview for geolibre-app ready!

Name Link
🔨 Latest commit 0264593
🔍 Latest deploy log https://app.netlify.com/projects/geolibre-app/deploys/6a4d084e7efe87000810590c
😎 Deploy Preview https://deploy-preview-1109--geolibre-app.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a share-fetch module for the Geolibre desktop app that defaults to browser fetch but can install a native Tauri HTTP client scoped to the configured share host. Wires this into upload/gallery fetch calls and desktop startup, extends the Tauri capability allowlist, and adds corresponding tests.

Changes

Native share fetch integration

Layer / File(s) Summary
Tauri capability allowlist
apps/geolibre-desktop/src-tauri/capabilities/default.json
Extends http:default allow list and comment to permit https://share.geolibre.app/*.
share-fetch module
apps/geolibre-desktop/src/lib/share-fetch.ts
Adds getShareFetch, setShareFetch, resetShareFetch, requestHost, and installNativeShareFetch, which routes matching-host requests through Tauri's native HTTP plugin while leaving others to browser fetch.
Wire upload and gallery fetch calls
apps/geolibre-desktop/src/lib/share-geolibre.ts, apps/geolibre-desktop/src/lib/share-gallery.ts
Changes default fetchImpl in uploadProjectToShare, fetchSharedProjects, and fetchMyProjects from global fetch to getShareFetch(), with updated comments/docs.
Startup wiring and tests
apps/geolibre-desktop/src/main.tsx, tests/share-fetch.test.ts
Lazily installs native share fetch on Tauri startup with error logging fallback; adds tests for override/reset behavior and fetch usage in upload/gallery flows.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MainTsx as main.tsx
  participant ShareFetch as share-fetch.ts
  participant TauriHttp as "Tauri plugin-http"
  participant Browser as "browser fetch"
  participant ShareApp as "share.geolibre.app"

  MainTsx->>ShareFetch: installNativeShareFetch() (if isTauri)
  ShareFetch->>ShareFetch: resolveShareBaseUrl()
  ShareFetch->>TauriHttp: dynamic import plugin-http
  ShareFetch->>ShareFetch: setShareFetch(wrapped fetch)
  Note over ShareFetch: request made via uploadProjectToShare / fetchSharedProjects / fetchMyProjects
  ShareFetch->>ShareFetch: requestHost(request)
  alt host matches share host
    ShareFetch->>TauriHttp: native fetch(request)
    TauriHttp->>ShareApp: HTTP request (bypasses WebView CORS)
  else other host
    ShareFetch->>Browser: fetch(request)
  end
Loading

Possibly related PRs

  • opengeos/GeoLibre#190: Both PRs touch uploadProjectToShare in share-geolibre.ts, with this PR changing its default fetch to the new Tauri-scoped getShareFetch().
  • opengeos/GeoLibre#825: Both PRs modify share-gallery fetching logic in share-gallery.ts, with this PR routing fetchSharedProjects/fetchMyProjects through getShareFetch().
  • opengeos/GeoLibre#1058: Both PRs modify the http:default permission in src-tauri/capabilities/default.json for the native HTTP plugin allowlist.

Poem

A rabbit hops through WebView walls,
Where CORS once blocked my share-y calls.
Now native fetch, so swift and sure,
Through Tauri's door, requests endure. 🐇
Hop, hop, upload — the burrow's clear!

🚥 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 title clearly summarizes the main desktop change: routing share.geolibre.app requests through native HTTP.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/desktop-share-cors-native-fetch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

⚡ Cloudflare Pages preview

Item Value
Preview https://7990be33.geolibre-preview.pages.dev
Demo app https://7990be33.geolibre-preview.pages.dev/demo/
Commit 93b28ff

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.

Pull request overview

This PR fixes the desktop (Tauri) Share + gallery CORS failure by routing requests to share.geolibre.app through Tauri’s native HTTP client (@tauri-apps/plugin-http) instead of the WebView fetch, while keeping the default web behavior unchanged.

Changes:

  • Added a centralized, overridable share fetch layer (lib/share-fetch.ts) with a desktop-only installer that swaps in the native HTTP client only for the resolved share host.
  • Updated the share upload and gallery listing clients to use the centralized share fetch by default (getShareFetch()), ensuring the desktop override is actually exercised.
  • Expanded the Tauri HTTP capability scope to allow https://share.geolibre.app/*, and added new unit tests covering override/reset and routing through the installed share fetch.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
File Description
tests/share-fetch.test.ts Adds tests ensuring share upload and gallery calls go through the installed share fetch (and that auth is preserved).
apps/geolibre-desktop/src/main.tsx Desktop-only lazy import to install the native share fetch at startup, mirroring the geocoding native-fetch pattern.
apps/geolibre-desktop/src/lib/share-geolibre.ts Routes Share upload through getShareFetch() by default (instead of direct browser fetch).
apps/geolibre-desktop/src/lib/share-gallery.ts Routes gallery reads (shared + my projects) through getShareFetch() by default, preserving same-origin token gating.
apps/geolibre-desktop/src/lib/share-fetch.ts Introduces the centralized share fetch + native installer with host scoping to limit native HTTP usage to the share host.
apps/geolibre-desktop/src-tauri/capabilities/default.json Adds https://share.geolibre.app/* to the http:default allowlist and updates the rationale comment accordingly.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +60 to +70
* The host is resolved from {@link resolveShareBaseUrl} (the configured or
* production share URL) at install time, so a `VITE_GEOLIBRE_SHARE_URL` override
* is honored.
*
* Loaded lazily and only in the desktop build so the web/embedded bundles never
* pull in `@tauri-apps/plugin-http`.
*/
export async function installNativeShareFetch(): Promise<void> {
let shareHost: string | null;
try {
shareHost = new URL(resolveShareBaseUrl()).host;

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.

The routed host is derived from resolveShareBaseUrl(), which honors the runtime-configurable VITE_GEOLIBRE_SHARE_URL env var (including http://localhost/127.0.0.1 overrides for local dev). But the http:default capability scope in default.json hard-codes only https://share.geolibre.app/*. If that env var is ever set to anything else, shareHost will match a host that Tauri's native HTTP plugin isn't permitted to reach, so tauriFetch(...) will fail with a permission error — a regression versus today's behavior, where a plain browser fetch would at least attempt the request (subject to the dev server's own CORS config). The doc comment's claim that the override "is honored" is misleading in the desktop build; consider scoping native routing to the production host only (mirroring how geocoding-fetch.ts fixes NATIVE_FETCH_HOSTS to the known provider list) or noting that overriding the share URL for a Tauri build also requires updating the capability file.

Confidence: medium — this only manifests for non-default builds (self-hosted/dev share servers), which may not be a supported/exercised configuration today.

Comment on lines +38 to +39
/** The request URL's host, or null when it cannot be parsed. */
function requestHost(input: RequestInfo | URL): string | null {

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.

Minor duplication: requestHost is byte-for-byte identical to the helper of the same name in geocoding-fetch.ts. Not a functional issue, but if a third native-fetch host gets added later this logic will likely be copy-pasted a third time — could be worth hoisting into a small shared util alongside the two lazily-installed fetch overrides.

Confidence: low — pure style nit.

Comment thread tests/share-fetch.test.ts
@@ -0,0 +1,115 @@
import assert from "node:assert/strict";

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.

Coverage gap: these tests cover getShareFetch/setShareFetch/resetShareFetch and that the share client functions call getShareFetch(), but none exercise installNativeShareFetch() itself — the host-matching logic that decides whether a request goes through tauriFetch or falls back to the browser fetch (share-fetch.ts:76-83). A regression there (e.g. a host mismatch due to a resolveShareBaseUrl() change) wouldn't be caught by this suite. (Note: installNativeGeocodingFetch has the same gap already, so this isn't a new pattern — just flagging since it's easy to miss.)

Confidence: low-medium — not blocking, but the core new CORS-bypass logic is currently untested.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Code review

Bugs

  • Opening a shared project from the Gallery still isn't routed through the native fetch, so it likely still hits the same CORS wall on desktop. The PR fixes the Share upload and the gallery listing calls (fetchSharedProjects/fetchMyProjects), but the actual "open this project" flow triggered from a gallery card does not go through getShareFetch():

    • apps/geolibre-desktop/src/lib/tauri-io.ts:1388 (openRecentProjectFile) calls the raw fetch unconditionally for any http(s) URL, and is used for the gallery's non-authenticated open path (ProjectGalleryDialogTopToolbar.tsx:1112useProjectFileActions.ts openProjectFromShareUrlopenRecentProjectFile).
    • apps/geolibre-desktop/src/hooks/useProjectFileActions.ts:234-237 builds the authenticated (private/unlisted project) open path via shareAuthorizedFetch(options.authToken, resolveShareBaseUrl()), but omits the third baseFetch argument, so shareAuthorizedFetch's default (fetch = fetch, global browser fetch) is used instead of getShareFetch().

    Since neither file is touched by this diff, these can't be flagged inline, but they mean clicking a project in the gallery to actually open it (as opposed to just listing it) would still throw "Could not reach share.geolibre.app"-style CORS errors on desktop, for both public and token-authenticated projects. Worth confirming against the manual test plan item "open the project gallery... confirm listings load" — that only covers the listing, not opening a card. Confidence: medium-high (traced the call chain from TopToolbar.tsx through to the raw fetch calls, but couldn't run the packaged desktop app to confirm the CORS failure reproduces there).

Security

  • None found. The native-fetch routing stays scoped to the resolved share host (falls back to browser fetch for any other host, e.g. third-party thumbnails/project URLs), and the Bearer token attachment logic (shareAuthorizedFetch) is unchanged same-origin gating.

Performance

  • None found.

Quality

  • apps/geolibre-desktop/src/lib/share-fetch.ts:60-70 (inline): installNativeShareFetch derives the native-routed host from resolveShareBaseUrl(), which honors the build-time-configurable VITE_GEOLIBRE_SHARE_URL override (including local dev hosts) — but the Tauri http:default capability scope hard-codes only https://share.geolibre.app/*. If that env var is ever set to a non-default host, native fetch calls to it will fail on Tauri's own permission check rather than falling back to the browser fetch, which is a regression versus pre-PR behavior for that (admittedly non-default) configuration. Confidence: medium.
  • apps/geolibre-desktop/src/lib/share-gallery.ts:79: stale JSDoc — FetchSharedProjectsOptions.fetchImpl still says "defaults to the global fetch," but the implementation was changed in this PR to default to getShareFetch(). (Not inline-commentable since the interface itself is outside the diff hunk; ShareUploadOptions.fetchImpl's equivalent doc in share-geolibre.ts was correctly updated.) Confidence: low, cosmetic.
  • apps/geolibre-desktop/src/lib/share-fetch.ts:39 (inline): requestHost() duplicates the identical helper in geocoding-fetch.ts verbatim; minor, could be hoisted into a shared util. Confidence: low.
  • tests/share-fetch.test.ts:1 (inline): new tests cover the getter/setter/reset and that the share client functions call getShareFetch(), but don't exercise installNativeShareFetch()'s own host-matching logic (the core of the CORS bypass). Matches the existing gap for installNativeGeocodingFetch, so not a new pattern, but worth noting. Confidence: low-medium.

CLAUDE.md

  • No violations found: no new user-facing strings needing t(), capability/CSP host changes were made in the right place (src-tauri/capabilities/default.json), and the change follows the existing native-fetch-bypass pattern from the geocoding fix.

@coderabbitai coderabbitai Bot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/geolibre-desktop/src/lib/share-gallery.ts (1)

68-80: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Stale doc comment: fetchImpl no longer defaults to the global fetch.

FetchSharedProjectsOptions.fetchImpl's doc still says "defaults to the global fetch," but Line 196 now defaults to getShareFetch(). The sibling ShareUploadOptions.fetchImpl doc in share-geolibre.ts was correctly updated in this same PR to reflect this.

📝 Proposed fix
-  /** Injected for testing; defaults to the global fetch. */
+  /** Injected for testing; defaults to the share fetch (see share-fetch.ts). */
   fetchImpl?: typeof fetch;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/geolibre-desktop/src/lib/share-gallery.ts` around lines 68 - 80, Update
the stale doc comment for FetchSharedProjectsOptions.fetchImpl in
share-gallery.ts so it matches the actual behavior in fetchSharedProjects and no
longer says it defaults to the global fetch; change it to reflect the
getShareFetch() default used when no fetchImpl is provided, consistent with
ShareUploadOptions.fetchImpl in share-geolibre.ts.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@apps/geolibre-desktop/src/lib/share-gallery.ts`:
- Around line 68-80: Update the stale doc comment for
FetchSharedProjectsOptions.fetchImpl in share-gallery.ts so it matches the
actual behavior in fetchSharedProjects and no longer says it defaults to the
global fetch; change it to reflect the getShareFetch() default used when no
fetchImpl is provided, consistent with ShareUploadOptions.fetchImpl in
share-geolibre.ts.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 16c12967-e00d-49bf-8960-1eb8fcb21559

📥 Commits

Reviewing files that changed from the base of the PR and between 0c7ef57 and 0264593.

📒 Files selected for processing (6)
  • apps/geolibre-desktop/src-tauri/capabilities/default.json
  • apps/geolibre-desktop/src/lib/share-fetch.ts
  • apps/geolibre-desktop/src/lib/share-gallery.ts
  • apps/geolibre-desktop/src/lib/share-geolibre.ts
  • apps/geolibre-desktop/src/main.tsx
  • tests/share-fetch.test.ts

@giswqs
giswqs merged commit 1155728 into main Jul 7, 2026
25 checks passed
@giswqs
giswqs deleted the fix/desktop-share-cors-native-fetch branch July 7, 2026 14:19
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.

2 participants