Cross-platform headers
How web and Electron identify Linux, macOS, and Windows requests with X-Client-Platform and X-App-Version, including the Linux cloud rollout boundary.
Requests made by apps/web carry two client-identification headers:
| Header | Browser value | Electron value | Why |
|---|---|---|---|
X-Client-Platform | web | linux-client, macos-client, or windows-client | Selects the client platform for routing and telemetry |
X-App-Version | 2.0.0 | The packaged desktop application version | Lets cloud gates target specific client versions |
The browser defaults live in
packages/api-client/src/platform-constants.ts:
export const APP_VERSION_HEADER_VALUE = '2.0.0'
export const CLIENT_PLATFORM_HEADER_VALUE = 'web'Inside the Electron app shell, the validated document-start runtime descriptor overrides those defaults. The mapping is explicit:
| Bridge platform | API header | Unified Node Socket registration |
|---|---|---|
linux | linux-client | linux / linux-connector / linux |
macos | macos-client | macos / macos-connector / darwin |
windows | windows-client | windows / windows-connector / win32 |
The registration column is clientPlatform / clientType / Node platform.
Connector clientType values make the unified desktop connection visible in
the device registry; they are not X-Client-Platform values. Registration
sends the CPI System installationId unchanged as clientMachineId, then uses
the server-issued d_* device ID for the Socket handshake, tool-catalog sync,
and push registration. The app-owned web/API header remains platform-specific;
the direct Classic Socket uses that same *-client identity and packaged app
version for registration and ticket requests. Only the browser Classic Socket
keeps the web / 2.0.0 defaults. These setup headers remain distinct from
the registration body fields above.
Why the contract is symmetric
Web, native mobile clients, and the desktop app all use the same two headers with platform-specific values. Cloud services can use them for:
- Auth-context platform routing — downstream services receive a normalized first-party platform when the API middleware recognizes it.
- Version-gated features — routes behind a minimum-client-version gate
compare
X-App-Versionwith the configured minimum. - Telemetry segmentation — platform and version are explicit rather than inferred from user-agent strings.
New browser routes inherit web; a new platform identifier still requires a
coordinated cloud rollout.
Where the headers are injected
The shared browser-side context lives in:
| File | What it does |
|---|---|
apps/web/src/lib/client-runtime.ts | Resolves web vs Electron platform, app version, and device ID |
apps/web/src/lib/client-context-headers.ts | Applies the shared browser/API request context |
apps/web/src/hooks/use-api-client.ts | Configures the generated API client |
apps/web/src/hooks/use-session.ts | Applies the same context to session/token requests |
Electron also sets the authoritative platform/version headers in the main
process for trusted Today web/API requests. This prevents page code from
downgrading a Linux package to the generic web identity. The unified Node
Socket uses the same platform/version headers for its setup requests and the
registration identity described above for its body instead of opening a second
Native-owned connection.
The normal browser path is:
const headers = new Headers()
applyClientContextHeaders(headers)
// Browser:
// X-Client-Platform: web
// X-App-Version: 2.0.0
// Linux Electron:
// X-Client-Platform: linux-client
// X-App-Version: <packaged application version>Linux cloud rollout boundary
The client-side Linux contract is implemented, but it does not imply production cloud enablement:
- The current API platform normalizer recognizes
ios,android,web,web-client,macos,windows, andweb-bff. Suffix-bearing values such asmacos-clientandwindows-clientnormalize through their base platform.linux-clientcurrently has no recognizedlinuxbase, so the API omits the platform from downstream auth context. It does not reject an otherwise valid bearer token solely because of that unknown platform value. - The cloud device registry has
linuxandlinux-connectorschemas, but accepts that pair only in thedevenvironment whenLINUX_DEVICE_REGISTRATION_ENABLEDis enabled. The production flag remains disabled.
Before production Linux remote tools are enabled, cloud must add Linux to API platform normalization and deliberately open the device-registration gate. Until then, Linux packages and the local Electron bridge can be built and tested, while production platform-specific routing, telemetry, and remote connector/tool registration remain unavailable.
Version-bump policy
The browser fallback X-App-Version is currently '2.0.0'. Desktop packages
use their packaged version instead.
Bump APP_VERSION_HEADER_VALUE only when a future browser-facing cloud gate
requires it. A typical coordinated change is:
- Cloud raises a new gate, for example minimum version
2.1.0. - Web bumps
APP_VERSION_HEADER_VALUEto2.1.0in the same rollout. - The PR makes the cloud-gate dependency explicit so the server side ships first.
Desktop package versions come from their packaging entry points and do not require changing the browser constant.
Missing or unrecognized values
A missing or unrecognized X-Client-Platform is omitted from normalized
downstream context. Authentication still depends on the route's bearer/session
contract, but platform-specific routing and telemetry cannot rely on the value.
For routes with an active version gate, a missing or below-minimum
X-App-Version can return 426 Upgrade Required. Inspect the captured response
for the exact gate and minimum version rather than assuming every route enforces
the same policy.
Related
- Auth interaction architecture — the auth layer that
consumes
X-Client-Platform - API codegen — how the generated
@hey-api/client-fetchclient is configured - WebView Bridge environment detection
— the synchronous
linux/macos/windowsruntime platform contract
OpalShaderShell
Three auth surfaces (`/login`, `/oauth/select-account`, `/oauth/consent`) share one shell that bundles the static auth background, the centered content column, and the beacon plumbing the brand mark needs to paint.
macOS notification authorization
Which macOS mechanism decides notification authorization, why a helper must carry the app signing identity to read it, and what is measured versus still assumed.