Launch OpenGPEX

Cloud Integration

How OpenGPEX v2 connects to GPEX Cloud for file storage and sharing, and how the optional anonymous telemetry loop (update checks + usage statistics) is wired through a local BFF into the cloud.


Cloud Storage & Sharing

The editor integrates with GPEX Cloud through a small, stateless API surface and an explicit, user-driven sync model (carried over from v1):

  • Authentication — email + one-time password, with OAuth popup as secondary; a short-lived access JWT held in memory and a refresh token persisted in localStorage, sent as Bearer headers and refreshed transparently on expiry.
  • File operations — save, list, download, remove, share / unshare, and quota check, executed by the editor's cloud client against Cloudflare Workers with an R2 object store behind them.
  • Sync model — the editor always works locally (IndexedDB). Cloud save is an explicit user action; there is no background sync or silent conflict resolution. Dirty detection is history-based: the menu derives a per-frame status (OFFLINE / NEVER_SAVED / LOCAL_AHEAD / SYNCED) from the undo-history length recorded at last save, and skips redundant saves when already synced. Opening a cloud file that would replace an existing local frame requires explicit overwrite confirmation.

.gpex Payload Packaging

Cloud save/receive reuse the project container directly: the cloud menu commands build a ZIP payload from state.json + assets-manifest.json + the three asset classes (assets/, raw/, dec/), and receiving side unpacks it through the zero-decode, manifest-driven path described in File Format & Storage. A project uploaded to the cloud therefore round-trips its full color identity and high-precision pixels, not just an 8-bit flattening.


Telemetry & Update Checks

v2 adds a single outbound telemetry channel that doubles as the version-update service: checking for updates is what users want, and the same request yields an anonymous activity heartbeat.

Topology

┌───────────────────────────────────────────────┐
│ OpenGPEX local runtime (Next.js)              │
│                                               │
│  Browser ──GET /api/system/check-update──►  Local BFF
│  • client_id (localStorage, header)           │  • validates opt-out state
│  • 12h throttle + cached response             │  • collects platform self-report
│  • renders update toast                       │  • appends environment fields
│                                               │  • stateless, 3.5s timeout,
└───────────────────────────────────────────────┘    fails silent ("no update")
                                                       │
                                                       ▼
                                     gpex-cloud (Cloudflare Workers + Supabase)
                                     • IP → /24 mask, then discarded
                                     • single stored-procedure aggregate write
                                     • GitHub Releases version match

The browser never talks to the cloud directly: the same-origin BFF relay is immune to ad blockers, lets the server contribute environment fields, and degrades silently on any failure.

Identity and Privacy

  • Statistical entity = browser. A random UUID (crypto.randomUUID()) is generated once per browser and sent via a request header — never in URLs or query strings.
  • Pseudonymization at the edge. The cloud stores only SHA-256(client_id + server salt); the raw ID never leaves the browser. IPs are masked to /24 for abuse guarding and then dropped in memory; country comes from the edge header.
  • Low-cardinality dimensions only — client OS/arch (browser self-report via Client Hints), deployment kind (cloud vs. self-hosted, server-determined). No hostnames, file paths, document content, or high-cardinality fields. A negative list forbids them explicitly.
  • Opt-out, two levels — an in-app preference stops all automatic checks; the server environment variable DO_NOT_TRACK=1 hard-disables outbound calls entirely (the BFF relay never performs the request).

Data Model

Three Supabase tables, maintained by a daily cron:

Table Contents Retention
tb_stats_daily_active Daily activity detail (pseudonymous client hash + dimensions) 90 days
tb_stats_ip_guards /24-subnet abuse counters 2 days
tb_stats_daily_rollup Day-grain aggregates, no identifier columns permanent

Daily/monthly active browsers and version distribution are computed off the rollup; cross-day deduplication queries the detail table. A per-subnet daily cap on new clients bounds statistical pollution (over-cap requests still get version answers, just no writes).

Version Source

Latest versions come from GitHub Releases (cached 1h server-side, with stale-cache fallback when GitHub is unavailable), so release publishing is the only operational step. Update checks are locked to the user's current major version — v1 users are never prompted into v2; major migration is announced on the website, not through the update toast. A release titled with [security] flags a critical update; responses always degrade to "no update" rather than failing.


Last updated: 2026-10-12