Skip to content

feat: Cloudflare Workers SSR support — createServerClient + worker-safe analytics#221

Open
netanelgilad wants to merge 1 commit into
mainfrom
claude/session-m669xy
Open

feat: Cloudflare Workers SSR support — createServerClient + worker-safe analytics#221
netanelgilad wants to merge 1 commit into
mainfrom
claude/session-m669xy

Conversation

@netanelgilad

Copy link
Copy Markdown
Collaborator

Cloudflare Workers SSR support

Motivation

Full-stack hosting POC: running app SSR on Cloudflare Workers for Platforms. Today the SDK cannot be safely instantiated inside a Worker request handler:

  • createClient() unconditionally constructs the analytics module, whose heartbeat processor runs a raw setInterval. In Workers, setInterval at global scope throws, and inside a request it leaks past the request lifetime — and there was no config option to disable analytics.
  • axios 1.x adapter auto-detection (xhr → http → fetch) can pick the wrong adapter under workerd.
  • Auth tokens live only in localStorage, so SSR document requests carry no auth at all.

Changes

  1. Worker-safe analytics (src/modules/analytics.ts): the analytics module is now a no-op outside a browser (typeof window === 'undefined') — no heartbeat interval, no processing timers, track() queues nothing. A new disableAnalytics?: boolean on CreateClientConfig skips analytics explicitly; the server client sets it automatically.
  2. Axios adapter passthrough (src/utils/axios-client.ts, src/client.ts): createAxiosClient accepts an optional adapter, exposed as an adapter field on CreateClientConfig and forwarded to all four internal axios clients. The server client forces adapter: 'fetch'.
  3. New createServerClient(options) (src/server.ts, exported from the main entry): builds a per-request client for SSR/edge runtimes. Each config value resolves in order: explicit option → env (BASE44_APP_ID, BASE44_API_URL, BASE44_SERVICE_TOKEN, BASE44_FUNCTIONS_VERSION) → the same Base44-* request headers createClientFromRequest reads (so it degrades gracefully behind the existing proxy); the user token resolves option → Authorization: Bearer header → base44_access_token cookie (internal dependency-free cookie parser handles quoted + URL-encoded values). Throws a clear error when no appId resolves or when the serverUrl isn't absolute; forces analytics off, the fetch adapter, and requiresAuth: false — no window/localStorage access anywhere in the path. Also propagates Base44-State like createClientFromRequest.
  4. Cookie mirror for browser token storage (src/utils/auth-utils.ts, src/modules/auth.ts): saveAccessToken now also sets a base44_access_token cookie (path=/; SameSite=Lax, plus Secure on https) so subsequent document requests carry the token to SSR; removeAccessToken clears it. auth.setToken() and auth.logout() (which write localStorage inline) mirror/clear the same cookie so login and logout flows stay consistent with SSR. All cookie writes are guarded on typeof document !== 'undefined'; localStorage behavior is byte-for-byte unchanged.

Compatibility guarantees

  • Deno backend-functions contract unchanged: createClientFromRequest and its Authorization / Base44-Service-Authorization / Base44-App-Id / Base44-Api-Url / Base44-Functions-Version / Base44-State header handling are untouched.
  • No exports map added to package.json — consumers that deep-import (e.g. @base44/sdk/dist/utils/axios-client) keep working. createServerClient is exported from the main entry only.
  • Browser behavior unchanged when analytics is enabled: same heartbeat, batching, throttling, visibility handling, and initialization event; the no-op path only activates without window or when disableAnalytics is set.

Test evidence

  • npm test (typecheck via tsc --noEmit + vitest): 19 files, 197 tests passed (was 15 files / ~171 before this PR).
  • npm run lint (eslint): clean. npm run build (tsc): clean.
  • New coverage:
    • tests/unit/server-client.test.ts: full resolution precedence (options → env → headers), cookie fallback incl. quoted/URL-encoded values, Authorization-over-cookie priority, malformed-header handling, appId-missing throws, non-absolute serverUrl throws, fetch adapter forced on all four axios clients, Base44-State propagation, and a worker simulation proving no timers start and track() is a safe no-op without window.
    • tests/unit/auth-utils-cookie.test.ts: cookie-string builders (attributes, Secure, encoding, custom name, clearing) plus save/remove mirror behavior in a simulated browser and SSR no-op guards.
    • tests/unit/analytics.test.ts: updated to simulate browser globals (analytics is browser-only now) and extended with heartbeat-preserved-in-browser and disableAnalytics no-op tests, plus a no-window worker-safety test.

Note: this PR triggers an automatic @base44-preview/sdk npm preview publish for easy testing in the Workers POC.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DvhQfqxACcq25XAQRpoSh9


Generated by Claude Code

…fe analytics

Make the SDK safe and usable inside Cloudflare Workers SSR:

- Analytics is now a no-op outside a browser (no heartbeat setInterval or
  processing timers without window), and a new disableAnalytics client
  config option skips it entirely. Browser behavior is unchanged when
  analytics is enabled.
- createAxiosClient accepts an optional axios adapter, exposed as an
  adapter field on CreateClientConfig, so edge runtimes can force the
  fetch adapter instead of relying on axios adapter auto-detection.
- New createServerClient({ request, env, ... }) entry point resolves
  config from explicit options -> env (BASE44_*) -> Base44-* request
  headers (same contract as createClientFromRequest) -> Authorization
  header / base44_access_token cookie for the user token, and forces
  disabled analytics, the fetch adapter, and requiresAuth: false.
- saveAccessToken/removeAccessToken (and auth.setToken/logout) now mirror
  the token into a base44_access_token cookie (path=/; SameSite=Lax;
  Secure on https) so SSR document requests carry auth; localStorage
  behavior is unchanged.

The Deno backend-functions contract (createClientFromRequest headers) is
untouched, and no package.json exports map was added so existing deep
imports keep working.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DvhQfqxACcq25XAQRpoSh9
@netanelgilad netanelgilad self-assigned this Jul 6, 2026
@claude

claude Bot commented Jul 6, 2026

Copy link
Copy Markdown

Claude encountered an error —— View job


I'll analyze this and get back to you.

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

🚀 Package Preview Available!


Install this PR's preview build with npm:

npm i @base44-preview/sdk@0.8.35-pr.221.5933e35

Prefer not to change any import paths? Install using npm alias so your code still imports @base44/sdk:

npm i "@base44/sdk@npm:@base44-preview/sdk@0.8.35-pr.221.5933e35"

Or add it to your package.json dependencies:

{
  "dependencies": {
    "@base44/sdk": "npm:@base44-preview/sdk@0.8.35-pr.221.5933e35"
  }
}

Preview published to npm registry — try new features instantly!

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