mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-05 21:39:54 +02:00
* feat: identify clients via X-Client-Platform/Version/OS
Adds client identification headers (and matching WS query params) across
all first-party clients so the server can split logs/metrics/gating by
caller without parsing User-Agent.
- HTTP: X-Client-Platform, X-Client-Version, X-Client-OS
- WS: client_platform, client_version, client_os query params
- Platform ∈ {web, desktop, cli, daemon}; OS ∈ {macos, windows, linux}
Wired through the shared TS ApiClient/WSClient via a new identity option
on CoreProvider. Web reads its version from package.json/env; Desktop
captures version + OS synchronously in preload via sendSync IPC. Go CLI
and daemon clients populate the same headers using runtime.GOOS
(normalized darwin → macos).
Server-side adds a ClientMetadata middleware that stashes the headers in
request context; the request logger and logger.RequestAttrs surface them
on every access log and handler-level log. Realtime hub logs the same
fields on websocket connect.
CORS allowlist extended for the new headers.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* test: address client-identity PR nits
- Memoize the CoreProvider identity object on Web and Desktop, and key
WSProvider's effect on identity primitives instead of the object
reference, so unrelated parent re-renders no longer tear down and
reconnect the WebSocket.
- Add direct header-injection tests for the CLI and daemon Go HTTP
clients (X-Client-Platform/Version/OS) and a normalizeGOOS unit test
on both packages.
- Add a TS test for WSClient that asserts client_platform/client_version/
client_os land on the upgrade URL and never leak the auth token.
- Add a hub test that dials the WS endpoint with client_* query params
and asserts the "websocket connected" log entry surfaces them as
structured attributes.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
55 lines
1.7 KiB
Go
55 lines
1.7 KiB
Go
package middleware
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
)
|
|
|
|
func TestClientMetadataExtractsHeaders(t *testing.T) {
|
|
var gotPlatform, gotVersion, gotOS string
|
|
handler := ClientMetadata(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
|
|
gotPlatform, gotVersion, gotOS = ClientMetadataFromContext(r.Context())
|
|
}))
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
|
req.Header.Set(HeaderClientPlatform, "desktop")
|
|
req.Header.Set(HeaderClientVersion, "1.2.3")
|
|
req.Header.Set(HeaderClientOS, "macos")
|
|
|
|
handler.ServeHTTP(httptest.NewRecorder(), req)
|
|
|
|
if gotPlatform != "desktop" {
|
|
t.Errorf("platform: got %q, want desktop", gotPlatform)
|
|
}
|
|
if gotVersion != "1.2.3" {
|
|
t.Errorf("version: got %q, want 1.2.3", gotVersion)
|
|
}
|
|
if gotOS != "macos" {
|
|
t.Errorf("os: got %q, want macos", gotOS)
|
|
}
|
|
}
|
|
|
|
func TestClientMetadataMissingHeadersReturnEmpty(t *testing.T) {
|
|
var gotPlatform, gotVersion, gotOS string
|
|
handler := ClientMetadata(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
|
|
gotPlatform, gotVersion, gotOS = ClientMetadataFromContext(r.Context())
|
|
}))
|
|
|
|
handler.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "/", nil))
|
|
|
|
if gotPlatform != "" || gotVersion != "" || gotOS != "" {
|
|
t.Errorf("expected empty metadata, got (%q,%q,%q)", gotPlatform, gotVersion, gotOS)
|
|
}
|
|
}
|
|
|
|
func TestSetClientMetadataAttachesValues(t *testing.T) {
|
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
|
ctx := SetClientMetadata(req.Context(), "cli", "0.5.1", "linux")
|
|
|
|
platform, version, os := ClientMetadataFromContext(ctx)
|
|
if platform != "cli" || version != "0.5.1" || os != "linux" {
|
|
t.Errorf("got (%q,%q,%q), want (cli,0.5.1,linux)", platform, version, os)
|
|
}
|
|
}
|