Skip to content

Add CORS support so browser-based KOSync clients can talk to the server directly #9

Description

@johanedlund21

Context

I'm building a browser-based reader (React + Vite, runs in a WebView on iOS/Even Realities G2) that talks to KOSync-compatible sync servers. The KOSync API surface at /users/create, /syncs/progress, etc. is exactly what I need — but today the hosted sync.crosspointreader.com doesn't send CORS response headers, which means browsers (including iOS WKWebView) block JavaScript from reading any response.

What I observe

$ curl -si -X POST https://sync.crosspointreader.com/users/create \
-H 'Content-Type: application/json' \
-H 'Origin: http://localhost:5173'
-d '{"username":"corstest","password":"test"}'
HTTP/2 201
content-type: application/json

← no Access-Control-Allow-Origin, no Access-Control-Allow-Headers, no Vary: Origin

$ curl -si -X OPTIONS https://sync.crosspointreader.com/users/create
-H 'Access-Control-Request-Method: POST' \
-H 'Access-Control-Request-Headers: content-type,x-auth-user,x-auth-key' \
-H 'Origin: http://localhost:5173'
HTTP/2 404 ← no CORS preflight handler

The server accepts the requests fine (great!), but the browser refuses to expose the response body to JS because the preflight fails and the actual response lacks Access-Control-Allow-Origin. Web-based clients today therefore need a Cloudflare Worker or similar CORS proxy — same as we've been doing for sync.koreader.rocks historically.

Suggested fix

The server uses Hono, which ships with a cors() middleware. It's a ~5-line change in src/app.ts:

import { cors } from 'hono/cors';

export function createApp(db: DB, config: Config, opts: AppOptions = {}): Hono {
const app = new Hono();

app.use('*', cors({                                                                                                                                   
  origin: '*', // or a configurable allowlist via env
  allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
  allowHeaders: ['Content-Type', 'Accept', 'x-auth-user', 'x-auth-key'],                                                                                
  exposeHeaders: [],
  maxAge: 86400,                                                                                                                                        
}));                                                                                                                                                    

// …existing routes…                                                                                                                                    

}

Auth is header-based (x-auth-user / x-auth-key) so credentials: 'include' isn't required — the safest default is origin: '*' which avoids the whole cookie/credentials attack surface. If a stricter allowlist is preferred, exposing it via a CORS_ORIGINS env variable (comma-separated) works well.

Why it's worth it

  • Removes the CF-Worker-proxy requirement for any web-based KOSync client (PWAs, browser extensions, WebView-based reader companion apps like the Even Realities G2 companion I'm building).
  • Makes the "just change the sync-server URL" story that the README already advertises true for browsers too.
  • Zero impact on existing device firmware clients (KOReader, CrossPoint) — they don't do preflight.
  • Makes it possible for browser-based clients to use the extended /api/v1 API (bookmarks / clippings / stats) without proxying every endpoint.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions