From 711cf1c0f6c396e77a25b5ac5edfaab29beeb827 Mon Sep 17 00:00:00 2001 From: Archan Datta Date: Thu, 14 May 2026 08:19:02 -0300 Subject: [PATCH 01/28] feat: rename image API routes and schemas from capture_session to telemetry --- server/lib/oapi/oapi.go | 4158 +++++++++++++++++++-------------------- server/openapi.yaml | 160 +- 2 files changed, 2048 insertions(+), 2270 deletions(-) diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index 525b2c47..bffeb4fc 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -26,57 +26,6 @@ import ( openapi_types "github.com/oapi-codegen/runtime/types" ) -// Defines values for CaptureConfigCategories. -const ( - CaptureConfigCategoriesCaptcha CaptureConfigCategories = "captcha" - CaptureConfigCategoriesConsole CaptureConfigCategories = "console" - CaptureConfigCategoriesInteraction CaptureConfigCategories = "interaction" - CaptureConfigCategoriesLiveview CaptureConfigCategories = "liveview" - CaptureConfigCategoriesNetwork CaptureConfigCategories = "network" - CaptureConfigCategoriesPage CaptureConfigCategories = "page" - CaptureConfigCategoriesSystem CaptureConfigCategories = "system" -) - -// Valid indicates whether the value is a known member of the CaptureConfigCategories enum. -func (e CaptureConfigCategories) Valid() bool { - switch e { - case CaptureConfigCategoriesCaptcha: - return true - case CaptureConfigCategoriesConsole: - return true - case CaptureConfigCategoriesInteraction: - return true - case CaptureConfigCategoriesLiveview: - return true - case CaptureConfigCategoriesNetwork: - return true - case CaptureConfigCategoriesPage: - return true - case CaptureConfigCategoriesSystem: - return true - default: - return false - } -} - -// Defines values for CaptureSessionStatus. -const ( - CaptureSessionStatusRunning CaptureSessionStatus = "running" - CaptureSessionStatusStopped CaptureSessionStatus = "stopped" -) - -// Valid indicates whether the value is a known member of the CaptureSessionStatus enum. -func (e CaptureSessionStatus) Valid() bool { - switch e { - case CaptureSessionStatusRunning: - return true - case CaptureSessionStatusStopped: - return true - default: - return false - } -} - // Defines values for ClickMouseRequestButton. const ( ClickMouseRequestButtonBack ClickMouseRequestButton = "back" @@ -364,31 +313,49 @@ func (e ProcessStreamEventStream) Valid() bool { // Defines values for PublishEventRequestCategory. const ( - Captcha PublishEventRequestCategory = "captcha" - Console PublishEventRequestCategory = "console" - Interaction PublishEventRequestCategory = "interaction" - Liveview PublishEventRequestCategory = "liveview" - Network PublishEventRequestCategory = "network" - Page PublishEventRequestCategory = "page" - System PublishEventRequestCategory = "system" + PublishEventRequestCategoryCaptcha PublishEventRequestCategory = "captcha" + PublishEventRequestCategoryConsole PublishEventRequestCategory = "console" + PublishEventRequestCategoryInteraction PublishEventRequestCategory = "interaction" + PublishEventRequestCategoryLiveview PublishEventRequestCategory = "liveview" + PublishEventRequestCategoryNetwork PublishEventRequestCategory = "network" + PublishEventRequestCategoryPage PublishEventRequestCategory = "page" + PublishEventRequestCategorySystem PublishEventRequestCategory = "system" ) // Valid indicates whether the value is a known member of the PublishEventRequestCategory enum. func (e PublishEventRequestCategory) Valid() bool { switch e { - case Captcha: + case PublishEventRequestCategoryCaptcha: + return true + case PublishEventRequestCategoryConsole: return true - case Console: + case PublishEventRequestCategoryInteraction: return true - case Interaction: + case PublishEventRequestCategoryLiveview: return true - case Liveview: + case PublishEventRequestCategoryNetwork: return true - case Network: + case PublishEventRequestCategoryPage: return true - case Page: + case PublishEventRequestCategorySystem: + return true + default: + return false + } +} + +// Defines values for TelemetryStateStatus. +const ( + TelemetryStateStatusRunning TelemetryStateStatus = "running" + TelemetryStateStatusStopped TelemetryStateStatus = "stopped" +) + +// Valid indicates whether the value is a known member of the TelemetryStateStatus enum. +func (e TelemetryStateStatus) Valid() bool { + switch e { + case TelemetryStateStatusRunning: return true - case System: + case TelemetryStateStatusStopped: return true default: return false @@ -443,29 +410,32 @@ type BatchComputerActionRequest struct { Actions []ComputerAction `json:"actions"` } -// CaptureConfig Capture filtering preferences. -type CaptureConfig struct { - // Categories Event categories to capture. When omitted or empty, all categories are captured. - Categories *[]CaptureConfigCategories `json:"categories,omitempty"` -} +// BrowserTelemetryCategoriesConfig Per-category telemetry capture settings for browser events. +type BrowserTelemetryCategoriesConfig struct { + // Console Configuration for a single telemetry category. + Console *BrowserTelemetryCategoryConfig `json:"console,omitempty"` -// CaptureConfigCategories defines model for CaptureConfig.Categories. -type CaptureConfigCategories string + // Interaction Configuration for a single telemetry category. + Interaction *BrowserTelemetryCategoryConfig `json:"interaction,omitempty"` -// CaptureSession A capture session resource. -type CaptureSession struct { - // Config Capture filtering preferences. - Config CaptureConfig `json:"config"` - CreatedAt time.Time `json:"created_at"` - Id string `json:"id"` + // Network Configuration for a single telemetry category. + Network *BrowserTelemetryCategoryConfig `json:"network,omitempty"` - // Seq Process-monotonic sequence number of the last published event. Does not reset between sessions. - Seq int64 `json:"seq"` - Status CaptureSessionStatus `json:"status"` + // Page Configuration for a single telemetry category. + Page *BrowserTelemetryCategoryConfig `json:"page,omitempty"` } -// CaptureSessionStatus defines model for CaptureSession.Status. -type CaptureSessionStatus string +// BrowserTelemetryCategoryConfig Configuration for a single telemetry category. +type BrowserTelemetryCategoryConfig struct { + // Enabled Whether this category is captured. Defaults to true if omitted. + Enabled *bool `json:"enabled,omitempty"` +} + +// BrowserTelemetryConfig Telemetry configuration for a browser session. Per-category capture settings. Omit a category or set enabled: true to capture it. Set enabled: false to exclude it. Omit the browser key entirely to capture all categories. Set all four categories to enabled: false to stop telemetry. +type BrowserTelemetryConfig struct { + // Browser Per-category telemetry capture settings for browser events. + Browser *BrowserTelemetryCategoriesConfig `json:"browser,omitempty"` +} // ClickMouseRequest defines model for ClickMouseRequest. type ClickMouseRequest struct { @@ -1038,12 +1008,6 @@ type SleepAction struct { DurationMs int `json:"duration_ms"` } -// StartCaptureSessionRequest Optional capture configuration. All fields default to the server-defined profile when omitted or when no body is sent. -type StartCaptureSessionRequest struct { - // Config Capture filtering preferences. - Config *CaptureConfig `json:"config,omitempty"` -} - // StartFsWatchRequest defines model for StartFsWatchRequest. type StartFsWatchRequest struct { // Path Directory to watch. @@ -1077,6 +1041,21 @@ type StopRecordingRequest struct { Id *string `json:"id,omitempty"` } +// TelemetryState Current telemetry session state and configuration. +type TelemetryState struct { + // Config Telemetry configuration for a browser session. Per-category capture settings. Omit a category or set enabled: true to capture it. Set enabled: false to exclude it. Omit the browser key entirely to capture all categories. Set all four categories to enabled: false to stop telemetry. + Config BrowserTelemetryConfig `json:"config"` + CreatedAt time.Time `json:"created_at"` + Id string `json:"id"` + + // Seq Process-monotonic sequence number of the last published event. Does not reset between telemetry sessions. + Seq int64 `json:"seq"` + Status TelemetryStateStatus `json:"status"` +} + +// TelemetryStateStatus defines model for TelemetryState.Status. +type TelemetryStateStatus string + // TypeTextRequest defines model for TypeTextRequest. type TypeTextRequest struct { // Delay Delay in milliseconds between keystrokes. Ignored when smooth is true. @@ -1098,12 +1077,6 @@ type TypeTextRequest struct { TypoChance *float32 `json:"typo_chance,omitempty"` } -// UpdateCaptureSessionRequest Fields to update on the capture session. -type UpdateCaptureSessionRequest struct { - // Config Capture filtering preferences. - Config *CaptureConfig `json:"config,omitempty"` -} - // WriteClipboardRequest defines model for WriteClipboardRequest. type WriteClipboardRequest struct { // Text Text to write to the system clipboard @@ -1143,12 +1116,6 @@ type UploadExtensionsAndRestartMultipartBody struct { } `json:"extensions"` } -// StreamEventsParams defines parameters for StreamEvents. -type StreamEventsParams struct { - // LastEventID Resume after this sequence number. Omit or send 0 to start from the current position. Sequence numbers are process-monotonic, so a value from a previous session resumes correctly from that point. - LastEventID *string `json:"Last-Event-ID,omitempty"` -} - // DownloadDirZipParams defines parameters for DownloadDirZip. type DownloadDirZipParams struct { // Path Absolute directory path to archive and download. @@ -1247,6 +1214,12 @@ type DownloadRecordingParams struct { Id *string `form:"id,omitempty" json:"id,omitempty"` } +// StreamEventsParams defines parameters for StreamEvents. +type StreamEventsParams struct { + // LastEventID Resume after this sequence number. Omit or send 0 to start from the current position. Sequence numbers are process-monotonic, so a value from a previous telemetry session resumes correctly from that point. + LastEventID *string `json:"Last-Event-ID,omitempty"` +} + // PatchChromiumFlagsJSONRequestBody defines body for PatchChromiumFlags for application/json ContentType. type PatchChromiumFlagsJSONRequestBody PatchChromiumFlagsJSONBody @@ -1289,15 +1262,6 @@ type TypeTextJSONRequestBody = TypeTextRequest // PatchDisplayJSONRequestBody defines body for PatchDisplay for application/json ContentType. type PatchDisplayJSONRequestBody = PatchDisplayRequest -// UpdateCaptureSessionJSONRequestBody defines body for UpdateCaptureSession for application/json ContentType. -type UpdateCaptureSessionJSONRequestBody = UpdateCaptureSessionRequest - -// StartCaptureSessionJSONRequestBody defines body for StartCaptureSession for application/json ContentType. -type StartCaptureSessionJSONRequestBody = StartCaptureSessionRequest - -// PublishEventJSONRequestBody defines body for PublishEvent for application/json ContentType. -type PublishEventJSONRequestBody = PublishEventRequest - // CreateDirectoryJSONRequestBody defines body for CreateDirectory for application/json ContentType. type CreateDirectoryJSONRequestBody = CreateDirectoryRequest @@ -1352,6 +1316,15 @@ type StartRecordingJSONRequestBody = StartRecordingRequest // StopRecordingJSONRequestBody defines body for StopRecording for application/json ContentType. type StopRecordingJSONRequestBody = StopRecordingRequest +// PatchTelemetryJSONRequestBody defines body for PatchTelemetry for application/json ContentType. +type PatchTelemetryJSONRequestBody = BrowserTelemetryConfig + +// PutTelemetryJSONRequestBody defines body for PutTelemetry for application/json ContentType. +type PutTelemetryJSONRequestBody = BrowserTelemetryConfig + +// PublishEventJSONRequestBody defines body for PublishEvent for application/json ContentType. +type PublishEventJSONRequestBody = PublishEventRequest + // RequestEditorFn is the function signature for the RequestEditor callback function type RequestEditorFn func(ctx context.Context, req *http.Request) error @@ -1499,30 +1472,6 @@ type ClientInterface interface { PatchDisplay(ctx context.Context, body PatchDisplayJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - // StopCaptureSession request - StopCaptureSession(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) - - // GetCaptureSession request - GetCaptureSession(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) - - // UpdateCaptureSessionWithBody request with any body - UpdateCaptureSessionWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - UpdateCaptureSession(ctx context.Context, body UpdateCaptureSessionJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - // StartCaptureSessionWithBody request with any body - StartCaptureSessionWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - StartCaptureSession(ctx context.Context, body StartCaptureSessionJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - // PublishEventWithBody request with any body - PublishEventWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - PublishEvent(ctx context.Context, body PublishEventJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - // StreamEvents request - StreamEvents(ctx context.Context, params *StreamEventsParams, reqEditors ...RequestEditorFn) (*http.Response, error) - // CreateDirectoryWithBody request with any body CreateDirectoryWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -1651,6 +1600,27 @@ type ClientInterface interface { // EnableScaleToZero request EnableScaleToZero(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetTelemetry request + GetTelemetry(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + + // PatchTelemetryWithBody request with any body + PatchTelemetryWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + PatchTelemetry(ctx context.Context, body PatchTelemetryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // PutTelemetryWithBody request with any body + PutTelemetryWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + PutTelemetry(ctx context.Context, body PutTelemetryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // PublishEventWithBody request with any body + PublishEventWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + PublishEvent(ctx context.Context, body PublishEventJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // StreamEvents request + StreamEvents(ctx context.Context, params *StreamEventsParams, reqEditors ...RequestEditorFn) (*http.Response, error) } func (c *Client) PatchChromiumFlagsWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { @@ -2001,114 +1971,6 @@ func (c *Client) PatchDisplay(ctx context.Context, body PatchDisplayJSONRequestB return c.Client.Do(req) } -func (c *Client) StopCaptureSession(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewStopCaptureSessionRequest(c.Server) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) GetCaptureSession(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetCaptureSessionRequest(c.Server) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) UpdateCaptureSessionWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewUpdateCaptureSessionRequestWithBody(c.Server, contentType, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) UpdateCaptureSession(ctx context.Context, body UpdateCaptureSessionJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewUpdateCaptureSessionRequest(c.Server, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) StartCaptureSessionWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewStartCaptureSessionRequestWithBody(c.Server, contentType, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) StartCaptureSession(ctx context.Context, body StartCaptureSessionJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewStartCaptureSessionRequest(c.Server, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) PublishEventWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPublishEventRequestWithBody(c.Server, contentType, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) PublishEvent(ctx context.Context, body PublishEventJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPublishEventRequest(c.Server, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) StreamEvents(ctx context.Context, params *StreamEventsParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewStreamEventsRequest(c.Server, params) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - func (c *Client) CreateDirectoryWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewCreateDirectoryRequestWithBody(c.Server, contentType, body) if err != nil { @@ -2685,69 +2547,165 @@ func (c *Client) EnableScaleToZero(ctx context.Context, reqEditors ...RequestEdi return c.Client.Do(req) } -// NewPatchChromiumFlagsRequest calls the generic PatchChromiumFlags builder with application/json body -func NewPatchChromiumFlagsRequest(server string, body PatchChromiumFlagsJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) +func (c *Client) GetTelemetry(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetTelemetryRequest(c.Server) if err != nil { return nil, err } - bodyReader = bytes.NewReader(buf) - return NewPatchChromiumFlagsRequestWithBody(server, "application/json", bodyReader) + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) } -// NewPatchChromiumFlagsRequestWithBody generates requests for PatchChromiumFlags with any type of body -func NewPatchChromiumFlagsRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) +func (c *Client) PatchTelemetryWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPatchTelemetryRequestWithBody(c.Server, contentType, body) if err != nil { return nil, err } - - operationPath := fmt.Sprintf("/chromium/flags") - if operationPath[0] == '/' { - operationPath = "." + operationPath + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err } + return c.Client.Do(req) +} - queryURL, err := serverURL.Parse(operationPath) +func (c *Client) PatchTelemetry(ctx context.Context, body PatchTelemetryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPatchTelemetryRequest(c.Server, body) if err != nil { return nil, err } - - req, err := http.NewRequest("PATCH", queryURL.String(), body) - if err != nil { + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { return nil, err } - - req.Header.Add("Content-Type", contentType) - - return req, nil + return c.Client.Do(req) } -// NewPatchChromiumPoliciesRequest calls the generic PatchChromiumPolicies builder with application/json body -func NewPatchChromiumPoliciesRequest(server string, body PatchChromiumPoliciesJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) +func (c *Client) PutTelemetryWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPutTelemetryRequestWithBody(c.Server, contentType, body) if err != nil { return nil, err } - bodyReader = bytes.NewReader(buf) - return NewPatchChromiumPoliciesRequestWithBody(server, "application/json", bodyReader) + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) } -// NewPatchChromiumPoliciesRequestWithBody generates requests for PatchChromiumPolicies with any type of body -func NewPatchChromiumPoliciesRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) +func (c *Client) PutTelemetry(ctx context.Context, body PutTelemetryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPutTelemetryRequest(c.Server, body) if err != nil { return nil, err } - - operationPath := fmt.Sprintf("/chromium/policies") - if operationPath[0] == '/' { - operationPath = "." + operationPath + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) PublishEventWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPublishEventRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) PublishEvent(ctx context.Context, body PublishEventJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPublishEventRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) StreamEvents(ctx context.Context, params *StreamEventsParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewStreamEventsRequest(c.Server, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewPatchChromiumFlagsRequest calls the generic PatchChromiumFlags builder with application/json body +func NewPatchChromiumFlagsRequest(server string, body PatchChromiumFlagsJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewPatchChromiumFlagsRequestWithBody(server, "application/json", bodyReader) +} + +// NewPatchChromiumFlagsRequestWithBody generates requests for PatchChromiumFlags with any type of body +func NewPatchChromiumFlagsRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/chromium/flags") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("PATCH", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewPatchChromiumPoliciesRequest calls the generic PatchChromiumPolicies builder with application/json body +func NewPatchChromiumPoliciesRequest(server string, body PatchChromiumPoliciesJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewPatchChromiumPoliciesRequestWithBody(server, "application/json", bodyReader) +} + +// NewPatchChromiumPoliciesRequestWithBody generates requests for PatchChromiumPolicies with any type of body +func NewPatchChromiumPoliciesRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/chromium/policies") + if operationPath[0] == '/' { + operationPath = "." + operationPath } queryURL, err := serverURL.Parse(operationPath) @@ -3288,35 +3246,19 @@ func NewPatchDisplayRequestWithBody(server string, contentType string, body io.R return req, nil } -// NewStopCaptureSessionRequest generates requests for StopCaptureSession -func NewStopCaptureSessionRequest(server string) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/events/capture_session") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("DELETE", queryURL.String(), nil) +// NewCreateDirectoryRequest calls the generic CreateDirectory builder with application/json body +func NewCreateDirectoryRequest(server string, body CreateDirectoryJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) if err != nil { return nil, err } - - return req, nil + bodyReader = bytes.NewReader(buf) + return NewCreateDirectoryRequestWithBody(server, "application/json", bodyReader) } -// NewGetCaptureSessionRequest generates requests for GetCaptureSession -func NewGetCaptureSessionRequest(server string) (*http.Request, error) { +// NewCreateDirectoryRequestWithBody generates requests for CreateDirectory with any type of body +func NewCreateDirectoryRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -3324,7 +3266,7 @@ func NewGetCaptureSessionRequest(server string) (*http.Request, error) { return nil, err } - operationPath := fmt.Sprintf("/events/capture_session") + operationPath := fmt.Sprintf("/fs/create_directory") if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -3334,27 +3276,29 @@ func NewGetCaptureSessionRequest(server string) (*http.Request, error) { return nil, err } - req, err := http.NewRequest("GET", queryURL.String(), nil) + req, err := http.NewRequest("PUT", queryURL.String(), body) if err != nil { return nil, err } + req.Header.Add("Content-Type", contentType) + return req, nil } -// NewUpdateCaptureSessionRequest calls the generic UpdateCaptureSession builder with application/json body -func NewUpdateCaptureSessionRequest(server string, body UpdateCaptureSessionJSONRequestBody) (*http.Request, error) { +// NewDeleteDirectoryRequest calls the generic DeleteDirectory builder with application/json body +func NewDeleteDirectoryRequest(server string, body DeleteDirectoryJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewUpdateCaptureSessionRequestWithBody(server, "application/json", bodyReader) + return NewDeleteDirectoryRequestWithBody(server, "application/json", bodyReader) } -// NewUpdateCaptureSessionRequestWithBody generates requests for UpdateCaptureSession with any type of body -func NewUpdateCaptureSessionRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { +// NewDeleteDirectoryRequestWithBody generates requests for DeleteDirectory with any type of body +func NewDeleteDirectoryRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -3362,7 +3306,7 @@ func NewUpdateCaptureSessionRequestWithBody(server string, contentType string, b return nil, err } - operationPath := fmt.Sprintf("/events/capture_session") + operationPath := fmt.Sprintf("/fs/delete_directory") if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -3372,7 +3316,7 @@ func NewUpdateCaptureSessionRequestWithBody(server string, contentType string, b return nil, err } - req, err := http.NewRequest("PATCH", queryURL.String(), body) + req, err := http.NewRequest("PUT", queryURL.String(), body) if err != nil { return nil, err } @@ -3382,19 +3326,19 @@ func NewUpdateCaptureSessionRequestWithBody(server string, contentType string, b return req, nil } -// NewStartCaptureSessionRequest calls the generic StartCaptureSession builder with application/json body -func NewStartCaptureSessionRequest(server string, body StartCaptureSessionJSONRequestBody) (*http.Request, error) { +// NewDeleteFileRequest calls the generic DeleteFile builder with application/json body +func NewDeleteFileRequest(server string, body DeleteFileJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewStartCaptureSessionRequestWithBody(server, "application/json", bodyReader) + return NewDeleteFileRequestWithBody(server, "application/json", bodyReader) } -// NewStartCaptureSessionRequestWithBody generates requests for StartCaptureSession with any type of body -func NewStartCaptureSessionRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { +// NewDeleteFileRequestWithBody generates requests for DeleteFile with any type of body +func NewDeleteFileRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -3402,7 +3346,7 @@ func NewStartCaptureSessionRequestWithBody(server string, contentType string, bo return nil, err } - operationPath := fmt.Sprintf("/events/capture_session") + operationPath := fmt.Sprintf("/fs/delete_file") if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -3412,7 +3356,7 @@ func NewStartCaptureSessionRequestWithBody(server string, contentType string, bo return nil, err } - req, err := http.NewRequest("POST", queryURL.String(), body) + req, err := http.NewRequest("PUT", queryURL.String(), body) if err != nil { return nil, err } @@ -3422,19 +3366,8 @@ func NewStartCaptureSessionRequestWithBody(server string, contentType string, bo return req, nil } -// NewPublishEventRequest calls the generic PublishEvent builder with application/json body -func NewPublishEventRequest(server string, body PublishEventJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewPublishEventRequestWithBody(server, "application/json", bodyReader) -} - -// NewPublishEventRequestWithBody generates requests for PublishEvent with any type of body -func NewPublishEventRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { +// NewDownloadDirZipRequest generates requests for DownloadDirZip +func NewDownloadDirZipRequest(server string, params *DownloadDirZipParams) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -3442,7 +3375,7 @@ func NewPublishEventRequestWithBody(server string, contentType string, body io.R return nil, err } - operationPath := fmt.Sprintf("/events/publish") + operationPath := fmt.Sprintf("/fs/download_dir_zip") if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -3452,18 +3385,34 @@ func NewPublishEventRequestWithBody(server string, contentType string, body io.R return nil, err } - req, err := http.NewRequest("POST", queryURL.String(), body) + if params != nil { + queryValues := queryURL.Query() + + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "path", params.Path, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err } - req.Header.Add("Content-Type", contentType) - return req, nil } -// NewStreamEventsRequest generates requests for StreamEvents -func NewStreamEventsRequest(server string, params *StreamEventsParams) (*http.Request, error) { +// NewDownloadDirZstdRequest generates requests for DownloadDirZstd +func NewDownloadDirZstdRequest(server string, params *DownloadDirZstdParams) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -3471,7 +3420,7 @@ func NewStreamEventsRequest(server string, params *StreamEventsParams) (*http.Re return nil, err } - operationPath := fmt.Sprintf("/events/stream") + operationPath := fmt.Sprintf("/fs/download_dir_zstd") if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -3481,42 +3430,50 @@ func NewStreamEventsRequest(server string, params *StreamEventsParams) (*http.Re return nil, err } - req, err := http.NewRequest("GET", queryURL.String(), nil) - if err != nil { - return nil, err - } - if params != nil { + queryValues := queryURL.Query() - if params.LastEventID != nil { - var headerParam0 string + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "path", params.Path, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } - headerParam0, err = runtime.StyleParamWithOptions("simple", false, "Last-Event-ID", *params.LastEventID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) - if err != nil { + if params.CompressionLevel != nil { + + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "compression_level", *params.CompressionLevel, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } } - req.Header.Set("Last-Event-ID", headerParam0) } + queryURL.RawQuery = queryValues.Encode() } - return req, nil -} - -// NewCreateDirectoryRequest calls the generic CreateDirectory builder with application/json body -func NewCreateDirectoryRequest(server string, body CreateDirectoryJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err } - bodyReader = bytes.NewReader(buf) - return NewCreateDirectoryRequestWithBody(server, "application/json", bodyReader) + + return req, nil } -// NewCreateDirectoryRequestWithBody generates requests for CreateDirectory with any type of body -func NewCreateDirectoryRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { +// NewFileInfoRequest generates requests for FileInfo +func NewFileInfoRequest(server string, params *FileInfoParams) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -3524,7 +3481,7 @@ func NewCreateDirectoryRequestWithBody(server string, contentType string, body i return nil, err } - operationPath := fmt.Sprintf("/fs/create_directory") + operationPath := fmt.Sprintf("/fs/file_info") if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -3534,223 +3491,8 @@ func NewCreateDirectoryRequestWithBody(server string, contentType string, body i return nil, err } - req, err := http.NewRequest("PUT", queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - return req, nil -} - -// NewDeleteDirectoryRequest calls the generic DeleteDirectory builder with application/json body -func NewDeleteDirectoryRequest(server string, body DeleteDirectoryJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewDeleteDirectoryRequestWithBody(server, "application/json", bodyReader) -} - -// NewDeleteDirectoryRequestWithBody generates requests for DeleteDirectory with any type of body -func NewDeleteDirectoryRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/fs/delete_directory") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("PUT", queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - return req, nil -} - -// NewDeleteFileRequest calls the generic DeleteFile builder with application/json body -func NewDeleteFileRequest(server string, body DeleteFileJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewDeleteFileRequestWithBody(server, "application/json", bodyReader) -} - -// NewDeleteFileRequestWithBody generates requests for DeleteFile with any type of body -func NewDeleteFileRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/fs/delete_file") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("PUT", queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - return req, nil -} - -// NewDownloadDirZipRequest generates requests for DownloadDirZip -func NewDownloadDirZipRequest(server string, params *DownloadDirZipParams) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/fs/download_dir_zip") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - if params != nil { - queryValues := queryURL.Query() - - if queryFrag, err := runtime.StyleParamWithOptions("form", true, "path", params.Path, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - queryURL.RawQuery = queryValues.Encode() - } - - req, err := http.NewRequest("GET", queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewDownloadDirZstdRequest generates requests for DownloadDirZstd -func NewDownloadDirZstdRequest(server string, params *DownloadDirZstdParams) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/fs/download_dir_zstd") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - if params != nil { - queryValues := queryURL.Query() - - if queryFrag, err := runtime.StyleParamWithOptions("form", true, "path", params.Path, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - if params.CompressionLevel != nil { - - if queryFrag, err := runtime.StyleParamWithOptions("form", true, "compression_level", *params.CompressionLevel, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - } - - queryURL.RawQuery = queryValues.Encode() - } - - req, err := http.NewRequest("GET", queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewFileInfoRequest generates requests for FileInfo -func NewFileInfoRequest(server string, params *FileInfoParams) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/fs/file_info") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - if params != nil { - queryValues := queryURL.Query() + if params != nil { + queryValues := queryURL.Query() if queryFrag, err := runtime.StyleParamWithOptions("form", true, "path", params.Path, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { return nil, err @@ -4875,44 +4617,233 @@ func NewEnableScaleToZeroRequest(server string) (*http.Request, error) { return req, nil } -func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { - for _, r := range c.RequestEditors { - if err := r(ctx, req); err != nil { - return err - } +// NewGetTelemetryRequest generates requests for GetTelemetry +func NewGetTelemetryRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err } - for _, r := range additionalEditors { - if err := r(ctx, req); err != nil { - return err - } + + operationPath := fmt.Sprintf("/telemetry") + if operationPath[0] == '/' { + operationPath = "." + operationPath } - return nil -} -// ClientWithResponses builds on ClientInterface to offer response payloads -type ClientWithResponses struct { - ClientInterface -} + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } -// NewClientWithResponses creates a new ClientWithResponses, which wraps -// Client with return type handling -func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { - client, err := NewClient(server, opts...) + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err } - return &ClientWithResponses{client}, nil + + return req, nil } -// WithBaseURL overrides the baseURL. -func WithBaseURL(baseURL string) ClientOption { - return func(c *Client) error { - newBaseURL, err := url.Parse(baseURL) - if err != nil { - return err - } - c.Server = newBaseURL.String() - return nil +// NewPatchTelemetryRequest calls the generic PatchTelemetry builder with application/json body +func NewPatchTelemetryRequest(server string, body PatchTelemetryJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewPatchTelemetryRequestWithBody(server, "application/json", bodyReader) +} + +// NewPatchTelemetryRequestWithBody generates requests for PatchTelemetry with any type of body +func NewPatchTelemetryRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/telemetry") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("PATCH", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewPutTelemetryRequest calls the generic PutTelemetry builder with application/json body +func NewPutTelemetryRequest(server string, body PutTelemetryJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewPutTelemetryRequestWithBody(server, "application/json", bodyReader) +} + +// NewPutTelemetryRequestWithBody generates requests for PutTelemetry with any type of body +func NewPutTelemetryRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/telemetry") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("PUT", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewPublishEventRequest calls the generic PublishEvent builder with application/json body +func NewPublishEventRequest(server string, body PublishEventJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewPublishEventRequestWithBody(server, "application/json", bodyReader) +} + +// NewPublishEventRequestWithBody generates requests for PublishEvent with any type of body +func NewPublishEventRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/telemetry/events") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewStreamEventsRequest generates requests for StreamEvents +func NewStreamEventsRequest(server string, params *StreamEventsParams) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/telemetry/stream") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + if params != nil { + + if params.LastEventID != nil { + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "Last-Event-ID", *params.LastEventID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("Last-Event-ID", headerParam0) + } + + } + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil } } @@ -4992,30 +4923,6 @@ type ClientWithResponsesInterface interface { PatchDisplayWithResponse(ctx context.Context, body PatchDisplayJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchDisplayResponse, error) - // StopCaptureSessionWithResponse request - StopCaptureSessionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*StopCaptureSessionResponse, error) - - // GetCaptureSessionWithResponse request - GetCaptureSessionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetCaptureSessionResponse, error) - - // UpdateCaptureSessionWithBodyWithResponse request with any body - UpdateCaptureSessionWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateCaptureSessionResponse, error) - - UpdateCaptureSessionWithResponse(ctx context.Context, body UpdateCaptureSessionJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateCaptureSessionResponse, error) - - // StartCaptureSessionWithBodyWithResponse request with any body - StartCaptureSessionWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*StartCaptureSessionResponse, error) - - StartCaptureSessionWithResponse(ctx context.Context, body StartCaptureSessionJSONRequestBody, reqEditors ...RequestEditorFn) (*StartCaptureSessionResponse, error) - - // PublishEventWithBodyWithResponse request with any body - PublishEventWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PublishEventResponse, error) - - PublishEventWithResponse(ctx context.Context, body PublishEventJSONRequestBody, reqEditors ...RequestEditorFn) (*PublishEventResponse, error) - - // StreamEventsWithResponse request - StreamEventsWithResponse(ctx context.Context, params *StreamEventsParams, reqEditors ...RequestEditorFn) (*StreamEventsResponse, error) - // CreateDirectoryWithBodyWithResponse request with any body CreateDirectoryWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateDirectoryResponse, error) @@ -5144,6 +5051,27 @@ type ClientWithResponsesInterface interface { // EnableScaleToZeroWithResponse request EnableScaleToZeroWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*EnableScaleToZeroResponse, error) + + // GetTelemetryWithResponse request + GetTelemetryWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetTelemetryResponse, error) + + // PatchTelemetryWithBodyWithResponse request with any body + PatchTelemetryWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchTelemetryResponse, error) + + PatchTelemetryWithResponse(ctx context.Context, body PatchTelemetryJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchTelemetryResponse, error) + + // PutTelemetryWithBodyWithResponse request with any body + PutTelemetryWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PutTelemetryResponse, error) + + PutTelemetryWithResponse(ctx context.Context, body PutTelemetryJSONRequestBody, reqEditors ...RequestEditorFn) (*PutTelemetryResponse, error) + + // PublishEventWithBodyWithResponse request with any body + PublishEventWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PublishEventResponse, error) + + PublishEventWithResponse(ctx context.Context, body PublishEventJSONRequestBody, reqEditors ...RequestEditorFn) (*PublishEventResponse, error) + + // StreamEventsWithResponse request + StreamEventsWithResponse(ctx context.Context, params *StreamEventsParams, reqEditors ...RequestEditorFn) (*StreamEventsResponse, error) } type PatchChromiumFlagsResponse struct { @@ -5517,15 +5445,15 @@ func (r PatchDisplayResponse) StatusCode() int { return 0 } -type StopCaptureSessionResponse struct { +type CreateDirectoryResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *CaptureSession - JSON404 *NotFoundError + JSON400 *BadRequestError + JSON500 *InternalError } // Status returns HTTPResponse.Status -func (r StopCaptureSessionResponse) Status() string { +func (r CreateDirectoryResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -5533,22 +5461,23 @@ func (r StopCaptureSessionResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r StopCaptureSessionResponse) StatusCode() int { +func (r CreateDirectoryResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type GetCaptureSessionResponse struct { +type DeleteDirectoryResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *CaptureSession + JSON400 *BadRequestError JSON404 *NotFoundError + JSON500 *InternalError } // Status returns HTTPResponse.Status -func (r GetCaptureSessionResponse) Status() string { +func (r DeleteDirectoryResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -5556,23 +5485,23 @@ func (r GetCaptureSessionResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetCaptureSessionResponse) StatusCode() int { +func (r DeleteDirectoryResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type UpdateCaptureSessionResponse struct { +type DeleteFileResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *CaptureSession JSON400 *BadRequestError JSON404 *NotFoundError + JSON500 *InternalError } // Status returns HTTPResponse.Status -func (r UpdateCaptureSessionResponse) Status() string { +func (r DeleteFileResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -5580,24 +5509,23 @@ func (r UpdateCaptureSessionResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r UpdateCaptureSessionResponse) StatusCode() int { +func (r DeleteFileResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type StartCaptureSessionResponse struct { +type DownloadDirZipResponse struct { Body []byte HTTPResponse *http.Response - JSON201 *CaptureSession JSON400 *BadRequestError - JSON409 *ConflictError + JSON404 *NotFoundError JSON500 *InternalError } // Status returns HTTPResponse.Status -func (r StartCaptureSessionResponse) Status() string { +func (r DownloadDirZipResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -5605,22 +5533,23 @@ func (r StartCaptureSessionResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r StartCaptureSessionResponse) StatusCode() int { +func (r DownloadDirZipResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type PublishEventResponse struct { +type DownloadDirZstdResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *PublishedEnvelope JSON400 *BadRequestError + JSON404 *NotFoundError + JSON500 *InternalError } // Status returns HTTPResponse.Status -func (r PublishEventResponse) Status() string { +func (r DownloadDirZstdResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -5628,20 +5557,24 @@ func (r PublishEventResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r PublishEventResponse) StatusCode() int { +func (r DownloadDirZstdResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type StreamEventsResponse struct { +type FileInfoResponse struct { Body []byte HTTPResponse *http.Response + JSON200 *FileInfo + JSON400 *BadRequestError + JSON404 *NotFoundError + JSON500 *InternalError } // Status returns HTTPResponse.Status -func (r StreamEventsResponse) Status() string { +func (r FileInfoResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -5649,22 +5582,24 @@ func (r StreamEventsResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r StreamEventsResponse) StatusCode() int { +func (r FileInfoResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type CreateDirectoryResponse struct { +type ListFilesResponse struct { Body []byte HTTPResponse *http.Response + JSON200 *ListFiles JSON400 *BadRequestError + JSON404 *NotFoundError JSON500 *InternalError } // Status returns HTTPResponse.Status -func (r CreateDirectoryResponse) Status() string { +func (r ListFilesResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -5672,153 +5607,7 @@ func (r CreateDirectoryResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r CreateDirectoryResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -type DeleteDirectoryResponse struct { - Body []byte - HTTPResponse *http.Response - JSON400 *BadRequestError - JSON404 *NotFoundError - JSON500 *InternalError -} - -// Status returns HTTPResponse.Status -func (r DeleteDirectoryResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r DeleteDirectoryResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -type DeleteFileResponse struct { - Body []byte - HTTPResponse *http.Response - JSON400 *BadRequestError - JSON404 *NotFoundError - JSON500 *InternalError -} - -// Status returns HTTPResponse.Status -func (r DeleteFileResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r DeleteFileResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -type DownloadDirZipResponse struct { - Body []byte - HTTPResponse *http.Response - JSON400 *BadRequestError - JSON404 *NotFoundError - JSON500 *InternalError -} - -// Status returns HTTPResponse.Status -func (r DownloadDirZipResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r DownloadDirZipResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -type DownloadDirZstdResponse struct { - Body []byte - HTTPResponse *http.Response - JSON400 *BadRequestError - JSON404 *NotFoundError - JSON500 *InternalError -} - -// Status returns HTTPResponse.Status -func (r DownloadDirZstdResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r DownloadDirZstdResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -type FileInfoResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *FileInfo - JSON400 *BadRequestError - JSON404 *NotFoundError - JSON500 *InternalError -} - -// Status returns HTTPResponse.Status -func (r FileInfoResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r FileInfoResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -type ListFilesResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *ListFiles - JSON400 *BadRequestError - JSON404 *NotFoundError - JSON500 *InternalError -} - -// Status returns HTTPResponse.Status -func (r ListFilesResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r ListFilesResponse) StatusCode() int { +func (r ListFilesResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -6449,6 +6238,122 @@ func (r EnableScaleToZeroResponse) StatusCode() int { return 0 } +type GetTelemetryResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *TelemetryState + JSON404 *NotFoundError +} + +// Status returns HTTPResponse.Status +func (r GetTelemetryResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetTelemetryResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type PatchTelemetryResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *TelemetryState + JSON400 *BadRequestError + JSON404 *NotFoundError +} + +// Status returns HTTPResponse.Status +func (r PatchTelemetryResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r PatchTelemetryResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type PutTelemetryResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *TelemetryState + JSON201 *TelemetryState + JSON400 *BadRequestError + JSON500 *InternalError +} + +// Status returns HTTPResponse.Status +func (r PutTelemetryResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r PutTelemetryResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type PublishEventResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *PublishedEnvelope + JSON400 *BadRequestError +} + +// Status returns HTTPResponse.Status +func (r PublishEventResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r PublishEventResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type StreamEventsResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r StreamEventsResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r StreamEventsResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + // PatchChromiumFlagsWithBodyWithResponse request with arbitrary body returning *PatchChromiumFlagsResponse func (c *ClientWithResponses) PatchChromiumFlagsWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchChromiumFlagsResponse, error) { rsp, err := c.PatchChromiumFlagsWithBody(ctx, contentType, body, reqEditors...) @@ -6697,99 +6602,21 @@ func (c *ClientWithResponses) PatchDisplayWithResponse(ctx context.Context, body return ParsePatchDisplayResponse(rsp) } -// StopCaptureSessionWithResponse request returning *StopCaptureSessionResponse -func (c *ClientWithResponses) StopCaptureSessionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*StopCaptureSessionResponse, error) { - rsp, err := c.StopCaptureSession(ctx, reqEditors...) +// CreateDirectoryWithBodyWithResponse request with arbitrary body returning *CreateDirectoryResponse +func (c *ClientWithResponses) CreateDirectoryWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateDirectoryResponse, error) { + rsp, err := c.CreateDirectoryWithBody(ctx, contentType, body, reqEditors...) if err != nil { return nil, err } - return ParseStopCaptureSessionResponse(rsp) + return ParseCreateDirectoryResponse(rsp) } -// GetCaptureSessionWithResponse request returning *GetCaptureSessionResponse -func (c *ClientWithResponses) GetCaptureSessionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetCaptureSessionResponse, error) { - rsp, err := c.GetCaptureSession(ctx, reqEditors...) +func (c *ClientWithResponses) CreateDirectoryWithResponse(ctx context.Context, body CreateDirectoryJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateDirectoryResponse, error) { + rsp, err := c.CreateDirectory(ctx, body, reqEditors...) if err != nil { return nil, err } - return ParseGetCaptureSessionResponse(rsp) -} - -// UpdateCaptureSessionWithBodyWithResponse request with arbitrary body returning *UpdateCaptureSessionResponse -func (c *ClientWithResponses) UpdateCaptureSessionWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateCaptureSessionResponse, error) { - rsp, err := c.UpdateCaptureSessionWithBody(ctx, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseUpdateCaptureSessionResponse(rsp) -} - -func (c *ClientWithResponses) UpdateCaptureSessionWithResponse(ctx context.Context, body UpdateCaptureSessionJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateCaptureSessionResponse, error) { - rsp, err := c.UpdateCaptureSession(ctx, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseUpdateCaptureSessionResponse(rsp) -} - -// StartCaptureSessionWithBodyWithResponse request with arbitrary body returning *StartCaptureSessionResponse -func (c *ClientWithResponses) StartCaptureSessionWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*StartCaptureSessionResponse, error) { - rsp, err := c.StartCaptureSessionWithBody(ctx, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseStartCaptureSessionResponse(rsp) -} - -func (c *ClientWithResponses) StartCaptureSessionWithResponse(ctx context.Context, body StartCaptureSessionJSONRequestBody, reqEditors ...RequestEditorFn) (*StartCaptureSessionResponse, error) { - rsp, err := c.StartCaptureSession(ctx, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseStartCaptureSessionResponse(rsp) -} - -// PublishEventWithBodyWithResponse request with arbitrary body returning *PublishEventResponse -func (c *ClientWithResponses) PublishEventWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PublishEventResponse, error) { - rsp, err := c.PublishEventWithBody(ctx, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParsePublishEventResponse(rsp) -} - -func (c *ClientWithResponses) PublishEventWithResponse(ctx context.Context, body PublishEventJSONRequestBody, reqEditors ...RequestEditorFn) (*PublishEventResponse, error) { - rsp, err := c.PublishEvent(ctx, body, reqEditors...) - if err != nil { - return nil, err - } - return ParsePublishEventResponse(rsp) -} - -// StreamEventsWithResponse request returning *StreamEventsResponse -func (c *ClientWithResponses) StreamEventsWithResponse(ctx context.Context, params *StreamEventsParams, reqEditors ...RequestEditorFn) (*StreamEventsResponse, error) { - rsp, err := c.StreamEvents(ctx, params, reqEditors...) - if err != nil { - return nil, err - } - return ParseStreamEventsResponse(rsp) -} - -// CreateDirectoryWithBodyWithResponse request with arbitrary body returning *CreateDirectoryResponse -func (c *ClientWithResponses) CreateDirectoryWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateDirectoryResponse, error) { - rsp, err := c.CreateDirectoryWithBody(ctx, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseCreateDirectoryResponse(rsp) -} - -func (c *ClientWithResponses) CreateDirectoryWithResponse(ctx context.Context, body CreateDirectoryJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateDirectoryResponse, error) { - rsp, err := c.CreateDirectory(ctx, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseCreateDirectoryResponse(rsp) + return ParseCreateDirectoryResponse(rsp) } // DeleteDirectoryWithBodyWithResponse request with arbitrary body returning *DeleteDirectoryResponse @@ -7192,6 +7019,75 @@ func (c *ClientWithResponses) EnableScaleToZeroWithResponse(ctx context.Context, return ParseEnableScaleToZeroResponse(rsp) } +// GetTelemetryWithResponse request returning *GetTelemetryResponse +func (c *ClientWithResponses) GetTelemetryWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetTelemetryResponse, error) { + rsp, err := c.GetTelemetry(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetTelemetryResponse(rsp) +} + +// PatchTelemetryWithBodyWithResponse request with arbitrary body returning *PatchTelemetryResponse +func (c *ClientWithResponses) PatchTelemetryWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchTelemetryResponse, error) { + rsp, err := c.PatchTelemetryWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePatchTelemetryResponse(rsp) +} + +func (c *ClientWithResponses) PatchTelemetryWithResponse(ctx context.Context, body PatchTelemetryJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchTelemetryResponse, error) { + rsp, err := c.PatchTelemetry(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePatchTelemetryResponse(rsp) +} + +// PutTelemetryWithBodyWithResponse request with arbitrary body returning *PutTelemetryResponse +func (c *ClientWithResponses) PutTelemetryWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PutTelemetryResponse, error) { + rsp, err := c.PutTelemetryWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePutTelemetryResponse(rsp) +} + +func (c *ClientWithResponses) PutTelemetryWithResponse(ctx context.Context, body PutTelemetryJSONRequestBody, reqEditors ...RequestEditorFn) (*PutTelemetryResponse, error) { + rsp, err := c.PutTelemetry(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePutTelemetryResponse(rsp) +} + +// PublishEventWithBodyWithResponse request with arbitrary body returning *PublishEventResponse +func (c *ClientWithResponses) PublishEventWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PublishEventResponse, error) { + rsp, err := c.PublishEventWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePublishEventResponse(rsp) +} + +func (c *ClientWithResponses) PublishEventWithResponse(ctx context.Context, body PublishEventJSONRequestBody, reqEditors ...RequestEditorFn) (*PublishEventResponse, error) { + rsp, err := c.PublishEvent(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePublishEventResponse(rsp) +} + +// StreamEventsWithResponse request returning *StreamEventsResponse +func (c *ClientWithResponses) StreamEventsWithResponse(ctx context.Context, params *StreamEventsParams, reqEditors ...RequestEditorFn) (*StreamEventsResponse, error) { + rsp, err := c.StreamEvents(ctx, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseStreamEventsResponse(rsp) +} + // ParsePatchChromiumFlagsResponse parses an HTTP response from a PatchChromiumFlagsWithResponse call func ParsePatchChromiumFlagsResponse(rsp *http.Response) (*PatchChromiumFlagsResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -7741,59 +7637,59 @@ func ParsePatchDisplayResponse(rsp *http.Response) (*PatchDisplayResponse, error return response, nil } -// ParseStopCaptureSessionResponse parses an HTTP response from a StopCaptureSessionWithResponse call -func ParseStopCaptureSessionResponse(rsp *http.Response) (*StopCaptureSessionResponse, error) { +// ParseCreateDirectoryResponse parses an HTTP response from a CreateDirectoryWithResponse call +func ParseCreateDirectoryResponse(rsp *http.Response) (*CreateDirectoryResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &StopCaptureSessionResponse{ + response := &CreateDirectoryResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest CaptureSession + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest BadRequestError if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON200 = &dest + response.JSON400 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest NotFoundError + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalError if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON404 = &dest + response.JSON500 = &dest } return response, nil } -// ParseGetCaptureSessionResponse parses an HTTP response from a GetCaptureSessionWithResponse call -func ParseGetCaptureSessionResponse(rsp *http.Response) (*GetCaptureSessionResponse, error) { +// ParseDeleteDirectoryResponse parses an HTTP response from a DeleteDirectoryWithResponse call +func ParseDeleteDirectoryResponse(rsp *http.Response) (*DeleteDirectoryResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetCaptureSessionResponse{ + response := &DeleteDirectoryResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest CaptureSession + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest BadRequestError if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON200 = &dest + response.JSON400 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: var dest NotFoundError @@ -7802,32 +7698,32 @@ func ParseGetCaptureSessionResponse(rsp *http.Response) (*GetCaptureSessionRespo } response.JSON404 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + } return response, nil } -// ParseUpdateCaptureSessionResponse parses an HTTP response from a UpdateCaptureSessionWithResponse call -func ParseUpdateCaptureSessionResponse(rsp *http.Response) (*UpdateCaptureSessionResponse, error) { +// ParseDeleteFileResponse parses an HTTP response from a DeleteFileWithResponse call +func ParseDeleteFileResponse(rsp *http.Response) (*DeleteFileResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &UpdateCaptureSessionResponse{ + response := &DeleteFileResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest CaptureSession - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: var dest BadRequestError if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -7842,32 +7738,32 @@ func ParseUpdateCaptureSessionResponse(rsp *http.Response) (*UpdateCaptureSessio } response.JSON404 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + } return response, nil } -// ParseStartCaptureSessionResponse parses an HTTP response from a StartCaptureSessionWithResponse call -func ParseStartCaptureSessionResponse(rsp *http.Response) (*StartCaptureSessionResponse, error) { +// ParseDownloadDirZipResponse parses an HTTP response from a DownloadDirZipWithResponse call +func ParseDownloadDirZipResponse(rsp *http.Response) (*DownloadDirZipResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &StartCaptureSessionResponse{ + response := &DownloadDirZipResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: - var dest CaptureSession - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON201 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: var dest BadRequestError if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -7875,12 +7771,12 @@ func ParseStartCaptureSessionResponse(rsp *http.Response) (*StartCaptureSessionR } response.JSON400 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 409: - var dest ConflictError + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest NotFoundError if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON409 = &dest + response.JSON404 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: var dest InternalError @@ -7894,27 +7790,20 @@ func ParseStartCaptureSessionResponse(rsp *http.Response) (*StartCaptureSessionR return response, nil } -// ParsePublishEventResponse parses an HTTP response from a PublishEventWithResponse call -func ParsePublishEventResponse(rsp *http.Response) (*PublishEventResponse, error) { +// ParseDownloadDirZstdResponse parses an HTTP response from a DownloadDirZstdWithResponse call +func ParseDownloadDirZstdResponse(rsp *http.Response) (*DownloadDirZstdResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &PublishEventResponse{ + response := &DownloadDirZstdResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest PublishedEnvelope - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: var dest BadRequestError if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -7922,41 +7811,46 @@ func ParsePublishEventResponse(rsp *http.Response) (*PublishEventResponse, error } response.JSON400 = &dest - } - - return response, nil -} + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest NotFoundError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest -// ParseStreamEventsResponse parses an HTTP response from a StreamEventsWithResponse call -func ParseStreamEventsResponse(rsp *http.Response) (*StreamEventsResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest - response := &StreamEventsResponse{ - Body: bodyBytes, - HTTPResponse: rsp, } return response, nil } -// ParseCreateDirectoryResponse parses an HTTP response from a CreateDirectoryWithResponse call -func ParseCreateDirectoryResponse(rsp *http.Response) (*CreateDirectoryResponse, error) { +// ParseFileInfoResponse parses an HTTP response from a FileInfoWithResponse call +func ParseFileInfoResponse(rsp *http.Response) (*FileInfoResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &CreateDirectoryResponse{ + response := &FileInfoResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest FileInfo + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: var dest BadRequestError if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -7964,6 +7858,13 @@ func ParseCreateDirectoryResponse(rsp *http.Response) (*CreateDirectoryResponse, } response.JSON400 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest NotFoundError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: var dest InternalError if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -7976,20 +7877,27 @@ func ParseCreateDirectoryResponse(rsp *http.Response) (*CreateDirectoryResponse, return response, nil } -// ParseDeleteDirectoryResponse parses an HTTP response from a DeleteDirectoryWithResponse call -func ParseDeleteDirectoryResponse(rsp *http.Response) (*DeleteDirectoryResponse, error) { +// ParseListFilesResponse parses an HTTP response from a ListFilesWithResponse call +func ParseListFilesResponse(rsp *http.Response) (*ListFilesResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &DeleteDirectoryResponse{ + response := &ListFilesResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest ListFiles + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: var dest BadRequestError if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -8016,15 +7924,15 @@ func ParseDeleteDirectoryResponse(rsp *http.Response) (*DeleteDirectoryResponse, return response, nil } -// ParseDeleteFileResponse parses an HTTP response from a DeleteFileWithResponse call -func ParseDeleteFileResponse(rsp *http.Response) (*DeleteFileResponse, error) { +// ParseMovePathResponse parses an HTTP response from a MovePathWithResponse call +func ParseMovePathResponse(rsp *http.Response) (*MovePathResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &DeleteFileResponse{ + response := &MovePathResponse{ Body: bodyBytes, HTTPResponse: rsp, } @@ -8056,222 +7964,8 @@ func ParseDeleteFileResponse(rsp *http.Response) (*DeleteFileResponse, error) { return response, nil } -// ParseDownloadDirZipResponse parses an HTTP response from a DownloadDirZipWithResponse call -func ParseDownloadDirZipResponse(rsp *http.Response) (*DownloadDirZipResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &DownloadDirZipResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest BadRequestError - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON400 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest NotFoundError - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON404 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest InternalError - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON500 = &dest - - } - - return response, nil -} - -// ParseDownloadDirZstdResponse parses an HTTP response from a DownloadDirZstdWithResponse call -func ParseDownloadDirZstdResponse(rsp *http.Response) (*DownloadDirZstdResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &DownloadDirZstdResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest BadRequestError - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON400 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest NotFoundError - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON404 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest InternalError - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON500 = &dest - - } - - return response, nil -} - -// ParseFileInfoResponse parses an HTTP response from a FileInfoWithResponse call -func ParseFileInfoResponse(rsp *http.Response) (*FileInfoResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &FileInfoResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest FileInfo - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest BadRequestError - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON400 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest NotFoundError - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON404 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest InternalError - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON500 = &dest - - } - - return response, nil -} - -// ParseListFilesResponse parses an HTTP response from a ListFilesWithResponse call -func ParseListFilesResponse(rsp *http.Response) (*ListFilesResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &ListFilesResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest ListFiles - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest BadRequestError - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON400 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest NotFoundError - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON404 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest InternalError - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON500 = &dest - - } - - return response, nil -} - -// ParseMovePathResponse parses an HTTP response from a MovePathWithResponse call -func ParseMovePathResponse(rsp *http.Response) (*MovePathResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &MovePathResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest BadRequestError - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON400 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest NotFoundError - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON404 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest InternalError - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON500 = &dest - - } - - return response, nil -} - -// ParseReadFileResponse parses an HTTP response from a ReadFileWithResponse call -func ParseReadFileResponse(rsp *http.Response) (*ReadFileResponse, error) { +// ParseReadFileResponse parses an HTTP response from a ReadFileWithResponse call +func ParseReadFileResponse(rsp *http.Response) (*ReadFileResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { @@ -9249,49 +8943,218 @@ func ParseEnableScaleToZeroResponse(rsp *http.Response) (*EnableScaleToZeroRespo return response, nil } -// ServerInterface represents all server handlers. -type ServerInterface interface { - // Update Chromium launch flags and restart - // (PATCH /chromium/flags) - PatchChromiumFlags(w http.ResponseWriter, r *http.Request) - // Update Chromium enterprise policies and restart - // (PATCH /chromium/policies) - PatchChromiumPolicies(w http.ResponseWriter, r *http.Request) - // Upload one or more unpacked extensions (as zips) and restart Chromium - // (POST /chromium/upload-extensions-and-restart) - UploadExtensionsAndRestart(w http.ResponseWriter, r *http.Request) - // Execute a batch of computer actions sequentially - // (POST /computer/batch) - BatchComputerAction(w http.ResponseWriter, r *http.Request) - // Simulate a mouse click action on the host computer - // (POST /computer/click_mouse) - ClickMouse(w http.ResponseWriter, r *http.Request) - // Read text from the system clipboard - // (POST /computer/clipboard/read) - ReadClipboard(w http.ResponseWriter, r *http.Request) - // Write text to the system clipboard - // (POST /computer/clipboard/write) - WriteClipboard(w http.ResponseWriter, r *http.Request) - // Hide or show the cursor - // (POST /computer/cursor) - SetCursor(w http.ResponseWriter, r *http.Request) - // Drag the mouse along a path - // (POST /computer/drag_mouse) - DragMouse(w http.ResponseWriter, r *http.Request) - // Get the current mouse cursor position on the host computer - // (POST /computer/get_mouse_position) - GetMousePosition(w http.ResponseWriter, r *http.Request) - // Move the mouse cursor to the specified coordinates on the host computer - // (POST /computer/move_mouse) - MoveMouse(w http.ResponseWriter, r *http.Request) - // Press one or more keys on the host computer - // (POST /computer/press_key) - PressKey(w http.ResponseWriter, r *http.Request) - // Capture a screenshot of the host computer - // (POST /computer/screenshot) - TakeScreenshot(w http.ResponseWriter, r *http.Request) - // Scroll the mouse wheel at a position on the host computer - // (POST /computer/scroll) +// ParseGetTelemetryResponse parses an HTTP response from a GetTelemetryWithResponse call +func ParseGetTelemetryResponse(rsp *http.Response) (*GetTelemetryResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetTelemetryResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest TelemetryState + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest NotFoundError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + } + + return response, nil +} + +// ParsePatchTelemetryResponse parses an HTTP response from a PatchTelemetryWithResponse call +func ParsePatchTelemetryResponse(rsp *http.Response) (*PatchTelemetryResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &PatchTelemetryResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest TelemetryState + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest BadRequestError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest NotFoundError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + } + + return response, nil +} + +// ParsePutTelemetryResponse parses an HTTP response from a PutTelemetryWithResponse call +func ParsePutTelemetryResponse(rsp *http.Response) (*PutTelemetryResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &PutTelemetryResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest TelemetryState + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest TelemetryState + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest BadRequestError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParsePublishEventResponse parses an HTTP response from a PublishEventWithResponse call +func ParsePublishEventResponse(rsp *http.Response) (*PublishEventResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &PublishEventResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest PublishedEnvelope + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest BadRequestError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + } + + return response, nil +} + +// ParseStreamEventsResponse parses an HTTP response from a StreamEventsWithResponse call +func ParseStreamEventsResponse(rsp *http.Response) (*StreamEventsResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &StreamEventsResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + // Update Chromium launch flags and restart + // (PATCH /chromium/flags) + PatchChromiumFlags(w http.ResponseWriter, r *http.Request) + // Update Chromium enterprise policies and restart + // (PATCH /chromium/policies) + PatchChromiumPolicies(w http.ResponseWriter, r *http.Request) + // Upload one or more unpacked extensions (as zips) and restart Chromium + // (POST /chromium/upload-extensions-and-restart) + UploadExtensionsAndRestart(w http.ResponseWriter, r *http.Request) + // Execute a batch of computer actions sequentially + // (POST /computer/batch) + BatchComputerAction(w http.ResponseWriter, r *http.Request) + // Simulate a mouse click action on the host computer + // (POST /computer/click_mouse) + ClickMouse(w http.ResponseWriter, r *http.Request) + // Read text from the system clipboard + // (POST /computer/clipboard/read) + ReadClipboard(w http.ResponseWriter, r *http.Request) + // Write text to the system clipboard + // (POST /computer/clipboard/write) + WriteClipboard(w http.ResponseWriter, r *http.Request) + // Hide or show the cursor + // (POST /computer/cursor) + SetCursor(w http.ResponseWriter, r *http.Request) + // Drag the mouse along a path + // (POST /computer/drag_mouse) + DragMouse(w http.ResponseWriter, r *http.Request) + // Get the current mouse cursor position on the host computer + // (POST /computer/get_mouse_position) + GetMousePosition(w http.ResponseWriter, r *http.Request) + // Move the mouse cursor to the specified coordinates on the host computer + // (POST /computer/move_mouse) + MoveMouse(w http.ResponseWriter, r *http.Request) + // Press one or more keys on the host computer + // (POST /computer/press_key) + PressKey(w http.ResponseWriter, r *http.Request) + // Capture a screenshot of the host computer + // (POST /computer/screenshot) + TakeScreenshot(w http.ResponseWriter, r *http.Request) + // Scroll the mouse wheel at a position on the host computer + // (POST /computer/scroll) Scroll(w http.ResponseWriter, r *http.Request) // Type text on the host computer // (POST /computer/type) @@ -9299,24 +9162,6 @@ type ServerInterface interface { // Update display configuration // (PATCH /display) PatchDisplay(w http.ResponseWriter, r *http.Request) - // Stop the capture session - // (DELETE /events/capture_session) - StopCaptureSession(w http.ResponseWriter, r *http.Request) - // Get the capture session - // (GET /events/capture_session) - GetCaptureSession(w http.ResponseWriter, r *http.Request) - // Update the capture session - // (PATCH /events/capture_session) - UpdateCaptureSession(w http.ResponseWriter, r *http.Request) - // Start the capture session - // (POST /events/capture_session) - StartCaptureSession(w http.ResponseWriter, r *http.Request) - // Publish an event into the event bus - // (POST /events/publish) - PublishEvent(w http.ResponseWriter, r *http.Request) - // Stream events as Server-Sent Events - // (GET /events/stream) - StreamEvents(w http.ResponseWriter, r *http.Request, params StreamEventsParams) // Create a new directory // (PUT /fs/create_directory) CreateDirectory(w http.ResponseWriter, r *http.Request) @@ -9416,6 +9261,21 @@ type ServerInterface interface { // Idempotently enable scale to zero on this VM. // (POST /scaletozero/enable) EnableScaleToZero(w http.ResponseWriter, r *http.Request) + // Get current telemetry state + // (GET /telemetry) + GetTelemetry(w http.ResponseWriter, r *http.Request) + // Update active telemetry configuration + // (PATCH /telemetry) + PatchTelemetry(w http.ResponseWriter, r *http.Request) + // Set telemetry configuration + // (PUT /telemetry) + PutTelemetry(w http.ResponseWriter, r *http.Request) + // Publish an event into the telemetry bus + // (POST /telemetry/events) + PublishEvent(w http.ResponseWriter, r *http.Request) + // Stream telemetry events as Server-Sent Events + // (GET /telemetry/stream) + StreamEvents(w http.ResponseWriter, r *http.Request, params StreamEventsParams) } // Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint. @@ -9518,42 +9378,6 @@ func (_ Unimplemented) PatchDisplay(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } -// Stop the capture session -// (DELETE /events/capture_session) -func (_ Unimplemented) StopCaptureSession(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) -} - -// Get the capture session -// (GET /events/capture_session) -func (_ Unimplemented) GetCaptureSession(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) -} - -// Update the capture session -// (PATCH /events/capture_session) -func (_ Unimplemented) UpdateCaptureSession(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) -} - -// Start the capture session -// (POST /events/capture_session) -func (_ Unimplemented) StartCaptureSession(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) -} - -// Publish an event into the event bus -// (POST /events/publish) -func (_ Unimplemented) PublishEvent(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) -} - -// Stream events as Server-Sent Events -// (GET /events/stream) -func (_ Unimplemented) StreamEvents(w http.ResponseWriter, r *http.Request, params StreamEventsParams) { - w.WriteHeader(http.StatusNotImplemented) -} - // Create a new directory // (PUT /fs/create_directory) func (_ Unimplemented) CreateDirectory(w http.ResponseWriter, r *http.Request) { @@ -9752,6 +9576,36 @@ func (_ Unimplemented) EnableScaleToZero(w http.ResponseWriter, r *http.Request) w.WriteHeader(http.StatusNotImplemented) } +// Get current telemetry state +// (GET /telemetry) +func (_ Unimplemented) GetTelemetry(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Update active telemetry configuration +// (PATCH /telemetry) +func (_ Unimplemented) PatchTelemetry(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Set telemetry configuration +// (PUT /telemetry) +func (_ Unimplemented) PutTelemetry(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Publish an event into the telemetry bus +// (POST /telemetry/events) +func (_ Unimplemented) PublishEvent(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Stream telemetry events as Server-Sent Events +// (GET /telemetry/stream) +func (_ Unimplemented) StreamEvents(w http.ResponseWriter, r *http.Request, params StreamEventsParams) { + w.WriteHeader(http.StatusNotImplemented) +} + // ServerInterfaceWrapper converts contexts to parameters. type ServerInterfaceWrapper struct { Handler ServerInterface @@ -9985,11 +9839,11 @@ func (siw *ServerInterfaceWrapper) PatchDisplay(w http.ResponseWriter, r *http.R handler.ServeHTTP(w, r) } -// StopCaptureSession operation middleware -func (siw *ServerInterfaceWrapper) StopCaptureSession(w http.ResponseWriter, r *http.Request) { +// CreateDirectory operation middleware +func (siw *ServerInterfaceWrapper) CreateDirectory(w http.ResponseWriter, r *http.Request) { handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.StopCaptureSession(w, r) + siw.Handler.CreateDirectory(w, r) })) for _, middleware := range siw.HandlerMiddlewares { @@ -9999,11 +9853,11 @@ func (siw *ServerInterfaceWrapper) StopCaptureSession(w http.ResponseWriter, r * handler.ServeHTTP(w, r) } -// GetCaptureSession operation middleware -func (siw *ServerInterfaceWrapper) GetCaptureSession(w http.ResponseWriter, r *http.Request) { +// DeleteDirectory operation middleware +func (siw *ServerInterfaceWrapper) DeleteDirectory(w http.ResponseWriter, r *http.Request) { handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetCaptureSession(w, r) + siw.Handler.DeleteDirectory(w, r) })) for _, middleware := range siw.HandlerMiddlewares { @@ -10013,11 +9867,11 @@ func (siw *ServerInterfaceWrapper) GetCaptureSession(w http.ResponseWriter, r *h handler.ServeHTTP(w, r) } -// UpdateCaptureSession operation middleware -func (siw *ServerInterfaceWrapper) UpdateCaptureSession(w http.ResponseWriter, r *http.Request) { +// DeleteFile operation middleware +func (siw *ServerInterfaceWrapper) DeleteFile(w http.ResponseWriter, r *http.Request) { handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.UpdateCaptureSession(w, r) + siw.Handler.DeleteFile(w, r) })) for _, middleware := range siw.HandlerMiddlewares { @@ -10027,120 +9881,10 @@ func (siw *ServerInterfaceWrapper) UpdateCaptureSession(w http.ResponseWriter, r handler.ServeHTTP(w, r) } -// StartCaptureSession operation middleware -func (siw *ServerInterfaceWrapper) StartCaptureSession(w http.ResponseWriter, r *http.Request) { +// DownloadDirZip operation middleware +func (siw *ServerInterfaceWrapper) DownloadDirZip(w http.ResponseWriter, r *http.Request) { - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.StartCaptureSession(w, r) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r) -} - -// PublishEvent operation middleware -func (siw *ServerInterfaceWrapper) PublishEvent(w http.ResponseWriter, r *http.Request) { - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.PublishEvent(w, r) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r) -} - -// StreamEvents operation middleware -func (siw *ServerInterfaceWrapper) StreamEvents(w http.ResponseWriter, r *http.Request) { - - var err error - - // Parameter object where we will unmarshal all parameters from the context - var params StreamEventsParams - - headers := r.Header - - // ------------- Optional header parameter "Last-Event-ID" ------------- - if valueList, found := headers[http.CanonicalHeaderKey("Last-Event-ID")]; found { - var LastEventID string - n := len(valueList) - if n != 1 { - siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "Last-Event-ID", Count: n}) - return - } - - err = runtime.BindStyledParameterWithOptions("simple", "Last-Event-ID", valueList[0], &LastEventID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false, Type: "string", Format: ""}) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "Last-Event-ID", Err: err}) - return - } - - params.LastEventID = &LastEventID - - } - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.StreamEvents(w, r, params) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r) -} - -// CreateDirectory operation middleware -func (siw *ServerInterfaceWrapper) CreateDirectory(w http.ResponseWriter, r *http.Request) { - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.CreateDirectory(w, r) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r) -} - -// DeleteDirectory operation middleware -func (siw *ServerInterfaceWrapper) DeleteDirectory(w http.ResponseWriter, r *http.Request) { - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.DeleteDirectory(w, r) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r) -} - -// DeleteFile operation middleware -func (siw *ServerInterfaceWrapper) DeleteFile(w http.ResponseWriter, r *http.Request) { - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.DeleteFile(w, r) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r) -} - -// DownloadDirZip operation middleware -func (siw *ServerInterfaceWrapper) DownloadDirZip(w http.ResponseWriter, r *http.Request) { - - var err error + var err error // Parameter object where we will unmarshal all parameters from the context var params DownloadDirZipParams @@ -10827,6 +10571,102 @@ func (siw *ServerInterfaceWrapper) EnableScaleToZero(w http.ResponseWriter, r *h handler.ServeHTTP(w, r) } +// GetTelemetry operation middleware +func (siw *ServerInterfaceWrapper) GetTelemetry(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetTelemetry(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// PatchTelemetry operation middleware +func (siw *ServerInterfaceWrapper) PatchTelemetry(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.PatchTelemetry(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// PutTelemetry operation middleware +func (siw *ServerInterfaceWrapper) PutTelemetry(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.PutTelemetry(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// PublishEvent operation middleware +func (siw *ServerInterfaceWrapper) PublishEvent(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.PublishEvent(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// StreamEvents operation middleware +func (siw *ServerInterfaceWrapper) StreamEvents(w http.ResponseWriter, r *http.Request) { + + var err error + + // Parameter object where we will unmarshal all parameters from the context + var params StreamEventsParams + + headers := r.Header + + // ------------- Optional header parameter "Last-Event-ID" ------------- + if valueList, found := headers[http.CanonicalHeaderKey("Last-Event-ID")]; found { + var LastEventID string + n := len(valueList) + if n != 1 { + siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "Last-Event-ID", Count: n}) + return + } + + err = runtime.BindStyledParameterWithOptions("simple", "Last-Event-ID", valueList[0], &LastEventID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: false, Required: false, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "Last-Event-ID", Err: err}) + return + } + + params.LastEventID = &LastEventID + + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.StreamEvents(w, r, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + type UnescapedCookieParamError struct { ParamName string Err error @@ -10988,24 +10828,6 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Patch(options.BaseURL+"/display", wrapper.PatchDisplay) }) - r.Group(func(r chi.Router) { - r.Delete(options.BaseURL+"/events/capture_session", wrapper.StopCaptureSession) - }) - r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/events/capture_session", wrapper.GetCaptureSession) - }) - r.Group(func(r chi.Router) { - r.Patch(options.BaseURL+"/events/capture_session", wrapper.UpdateCaptureSession) - }) - r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/events/capture_session", wrapper.StartCaptureSession) - }) - r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/events/publish", wrapper.PublishEvent) - }) - r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/events/stream", wrapper.StreamEvents) - }) r.Group(func(r chi.Router) { r.Put(options.BaseURL+"/fs/create_directory", wrapper.CreateDirectory) }) @@ -11105,6 +10927,21 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/scaletozero/enable", wrapper.EnableScaleToZero) }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/telemetry", wrapper.GetTelemetry) + }) + r.Group(func(r chi.Router) { + r.Patch(options.BaseURL+"/telemetry", wrapper.PatchTelemetry) + }) + r.Group(func(r chi.Router) { + r.Put(options.BaseURL+"/telemetry", wrapper.PutTelemetry) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/telemetry/events", wrapper.PublishEvent) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/telemetry/stream", wrapper.StreamEvents) + }) return r } @@ -11665,287 +11502,75 @@ func (response PatchDisplay500JSONResponse) VisitPatchDisplayResponse(w http.Res return json.NewEncoder(w).Encode(response) } -type StopCaptureSessionRequestObject struct { +type CreateDirectoryRequestObject struct { + Body *CreateDirectoryJSONRequestBody +} + +type CreateDirectoryResponseObject interface { + VisitCreateDirectoryResponse(w http.ResponseWriter) error +} + +type CreateDirectory201Response struct { } -type StopCaptureSessionResponseObject interface { - VisitStopCaptureSessionResponse(w http.ResponseWriter) error +func (response CreateDirectory201Response) VisitCreateDirectoryResponse(w http.ResponseWriter) error { + w.WriteHeader(201) + return nil } -type StopCaptureSession200JSONResponse CaptureSession +type CreateDirectory400JSONResponse struct{ BadRequestErrorJSONResponse } -func (response StopCaptureSession200JSONResponse) VisitStopCaptureSessionResponse(w http.ResponseWriter) error { +func (response CreateDirectory400JSONResponse) VisitCreateDirectoryResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") - w.WriteHeader(200) + w.WriteHeader(400) return json.NewEncoder(w).Encode(response) } -type StopCaptureSession404JSONResponse struct{ NotFoundErrorJSONResponse } +type CreateDirectory500JSONResponse struct{ InternalErrorJSONResponse } -func (response StopCaptureSession404JSONResponse) VisitStopCaptureSessionResponse(w http.ResponseWriter) error { +func (response CreateDirectory500JSONResponse) VisitCreateDirectoryResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") - w.WriteHeader(404) + w.WriteHeader(500) return json.NewEncoder(w).Encode(response) } -type GetCaptureSessionRequestObject struct { +type DeleteDirectoryRequestObject struct { + Body *DeleteDirectoryJSONRequestBody +} + +type DeleteDirectoryResponseObject interface { + VisitDeleteDirectoryResponse(w http.ResponseWriter) error +} + +type DeleteDirectory200Response struct { } -type GetCaptureSessionResponseObject interface { - VisitGetCaptureSessionResponse(w http.ResponseWriter) error +func (response DeleteDirectory200Response) VisitDeleteDirectoryResponse(w http.ResponseWriter) error { + w.WriteHeader(200) + return nil } -type GetCaptureSession200JSONResponse CaptureSession +type DeleteDirectory400JSONResponse struct{ BadRequestErrorJSONResponse } -func (response GetCaptureSession200JSONResponse) VisitGetCaptureSessionResponse(w http.ResponseWriter) error { +func (response DeleteDirectory400JSONResponse) VisitDeleteDirectoryResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") - w.WriteHeader(200) + w.WriteHeader(400) return json.NewEncoder(w).Encode(response) } -type GetCaptureSession404JSONResponse struct{ NotFoundErrorJSONResponse } +type DeleteDirectory404JSONResponse struct{ NotFoundErrorJSONResponse } -func (response GetCaptureSession404JSONResponse) VisitGetCaptureSessionResponse(w http.ResponseWriter) error { +func (response DeleteDirectory404JSONResponse) VisitDeleteDirectoryResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(404) return json.NewEncoder(w).Encode(response) } -type UpdateCaptureSessionRequestObject struct { - Body *UpdateCaptureSessionJSONRequestBody -} - -type UpdateCaptureSessionResponseObject interface { - VisitUpdateCaptureSessionResponse(w http.ResponseWriter) error -} - -type UpdateCaptureSession200JSONResponse CaptureSession - -func (response UpdateCaptureSession200JSONResponse) VisitUpdateCaptureSessionResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(200) - - return json.NewEncoder(w).Encode(response) -} - -type UpdateCaptureSession400JSONResponse struct{ BadRequestErrorJSONResponse } - -func (response UpdateCaptureSession400JSONResponse) VisitUpdateCaptureSessionResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(400) - - return json.NewEncoder(w).Encode(response) -} - -type UpdateCaptureSession404JSONResponse struct{ NotFoundErrorJSONResponse } - -func (response UpdateCaptureSession404JSONResponse) VisitUpdateCaptureSessionResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(404) - - return json.NewEncoder(w).Encode(response) -} - -type StartCaptureSessionRequestObject struct { - Body *StartCaptureSessionJSONRequestBody -} - -type StartCaptureSessionResponseObject interface { - VisitStartCaptureSessionResponse(w http.ResponseWriter) error -} - -type StartCaptureSession201JSONResponse CaptureSession - -func (response StartCaptureSession201JSONResponse) VisitStartCaptureSessionResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(201) - - return json.NewEncoder(w).Encode(response) -} - -type StartCaptureSession400JSONResponse struct{ BadRequestErrorJSONResponse } - -func (response StartCaptureSession400JSONResponse) VisitStartCaptureSessionResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(400) - - return json.NewEncoder(w).Encode(response) -} - -type StartCaptureSession409JSONResponse struct{ ConflictErrorJSONResponse } - -func (response StartCaptureSession409JSONResponse) VisitStartCaptureSessionResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(409) - - return json.NewEncoder(w).Encode(response) -} - -type StartCaptureSession500JSONResponse struct{ InternalErrorJSONResponse } - -func (response StartCaptureSession500JSONResponse) VisitStartCaptureSessionResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(500) - - return json.NewEncoder(w).Encode(response) -} - -type PublishEventRequestObject struct { - Body *PublishEventJSONRequestBody -} - -type PublishEventResponseObject interface { - VisitPublishEventResponse(w http.ResponseWriter) error -} - -type PublishEvent200JSONResponse PublishedEnvelope - -func (response PublishEvent200JSONResponse) VisitPublishEventResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(200) - - return json.NewEncoder(w).Encode(response) -} - -type PublishEvent400JSONResponse struct{ BadRequestErrorJSONResponse } - -func (response PublishEvent400JSONResponse) VisitPublishEventResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(400) - - return json.NewEncoder(w).Encode(response) -} - -type StreamEventsRequestObject struct { - Params StreamEventsParams -} - -type StreamEventsResponseObject interface { - VisitStreamEventsResponse(w http.ResponseWriter) error -} - -type StreamEvents200ResponseHeaders struct { - XSSEContentType string -} - -type StreamEvents200TexteventStreamResponse struct { - Body io.Reader - Headers StreamEvents200ResponseHeaders - ContentLength int64 -} - -func (response StreamEvents200TexteventStreamResponse) VisitStreamEventsResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "text/event-stream") - if response.ContentLength != 0 { - w.Header().Set("Content-Length", fmt.Sprint(response.ContentLength)) - } - w.Header().Set("X-SSE-Content-Type", fmt.Sprint(response.Headers.XSSEContentType)) - w.Header().Set("Cache-Control", "no-cache") - w.Header().Set("X-Accel-Buffering", "no") - w.WriteHeader(200) - - if closer, ok := response.Body.(io.ReadCloser); ok { - defer closer.Close() - } - flusher, ok := w.(http.Flusher) - if !ok { - // If w doesn't support flushing, might as well use io.Copy - _, err := io.Copy(w, response.Body) - return err - } - - // Use a buffer for efficient copying and flushing - buf := make([]byte, 4096) // text/event-stream are usually very small messages - for { - n, err := response.Body.Read(buf) - if n > 0 { - if _, werr := w.Write(buf[:n]); werr != nil { - return werr - } - flusher.Flush() // Flush after each write - } - if err != nil { - if err == io.EOF { - return nil // End of file, no error - } - return err - } - } -} - -type CreateDirectoryRequestObject struct { - Body *CreateDirectoryJSONRequestBody -} - -type CreateDirectoryResponseObject interface { - VisitCreateDirectoryResponse(w http.ResponseWriter) error -} - -type CreateDirectory201Response struct { -} - -func (response CreateDirectory201Response) VisitCreateDirectoryResponse(w http.ResponseWriter) error { - w.WriteHeader(201) - return nil -} - -type CreateDirectory400JSONResponse struct{ BadRequestErrorJSONResponse } - -func (response CreateDirectory400JSONResponse) VisitCreateDirectoryResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(400) - - return json.NewEncoder(w).Encode(response) -} - -type CreateDirectory500JSONResponse struct{ InternalErrorJSONResponse } - -func (response CreateDirectory500JSONResponse) VisitCreateDirectoryResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(500) - - return json.NewEncoder(w).Encode(response) -} - -type DeleteDirectoryRequestObject struct { - Body *DeleteDirectoryJSONRequestBody -} - -type DeleteDirectoryResponseObject interface { - VisitDeleteDirectoryResponse(w http.ResponseWriter) error -} - -type DeleteDirectory200Response struct { -} - -func (response DeleteDirectory200Response) VisitDeleteDirectoryResponse(w http.ResponseWriter) error { - w.WriteHeader(200) - return nil -} - -type DeleteDirectory400JSONResponse struct{ BadRequestErrorJSONResponse } - -func (response DeleteDirectory400JSONResponse) VisitDeleteDirectoryResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(400) - - return json.NewEncoder(w).Encode(response) -} - -type DeleteDirectory404JSONResponse struct{ NotFoundErrorJSONResponse } - -func (response DeleteDirectory404JSONResponse) VisitDeleteDirectoryResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(404) - - return json.NewEncoder(w).Encode(response) -} - -type DeleteDirectory500JSONResponse struct{ InternalErrorJSONResponse } +type DeleteDirectory500JSONResponse struct{ InternalErrorJSONResponse } func (response DeleteDirectory500JSONResponse) VisitDeleteDirectoryResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") @@ -13383,74 +13008,243 @@ func (response EnableScaleToZero500JSONResponse) VisitEnableScaleToZeroResponse( return json.NewEncoder(w).Encode(response) } -// StrictServerInterface represents all server handlers. -type StrictServerInterface interface { - // Update Chromium launch flags and restart - // (PATCH /chromium/flags) - PatchChromiumFlags(ctx context.Context, request PatchChromiumFlagsRequestObject) (PatchChromiumFlagsResponseObject, error) - // Update Chromium enterprise policies and restart - // (PATCH /chromium/policies) - PatchChromiumPolicies(ctx context.Context, request PatchChromiumPoliciesRequestObject) (PatchChromiumPoliciesResponseObject, error) - // Upload one or more unpacked extensions (as zips) and restart Chromium - // (POST /chromium/upload-extensions-and-restart) - UploadExtensionsAndRestart(ctx context.Context, request UploadExtensionsAndRestartRequestObject) (UploadExtensionsAndRestartResponseObject, error) - // Execute a batch of computer actions sequentially - // (POST /computer/batch) - BatchComputerAction(ctx context.Context, request BatchComputerActionRequestObject) (BatchComputerActionResponseObject, error) - // Simulate a mouse click action on the host computer - // (POST /computer/click_mouse) - ClickMouse(ctx context.Context, request ClickMouseRequestObject) (ClickMouseResponseObject, error) - // Read text from the system clipboard - // (POST /computer/clipboard/read) - ReadClipboard(ctx context.Context, request ReadClipboardRequestObject) (ReadClipboardResponseObject, error) - // Write text to the system clipboard - // (POST /computer/clipboard/write) - WriteClipboard(ctx context.Context, request WriteClipboardRequestObject) (WriteClipboardResponseObject, error) - // Hide or show the cursor - // (POST /computer/cursor) - SetCursor(ctx context.Context, request SetCursorRequestObject) (SetCursorResponseObject, error) - // Drag the mouse along a path - // (POST /computer/drag_mouse) - DragMouse(ctx context.Context, request DragMouseRequestObject) (DragMouseResponseObject, error) - // Get the current mouse cursor position on the host computer - // (POST /computer/get_mouse_position) - GetMousePosition(ctx context.Context, request GetMousePositionRequestObject) (GetMousePositionResponseObject, error) - // Move the mouse cursor to the specified coordinates on the host computer - // (POST /computer/move_mouse) - MoveMouse(ctx context.Context, request MoveMouseRequestObject) (MoveMouseResponseObject, error) - // Press one or more keys on the host computer - // (POST /computer/press_key) - PressKey(ctx context.Context, request PressKeyRequestObject) (PressKeyResponseObject, error) - // Capture a screenshot of the host computer - // (POST /computer/screenshot) - TakeScreenshot(ctx context.Context, request TakeScreenshotRequestObject) (TakeScreenshotResponseObject, error) - // Scroll the mouse wheel at a position on the host computer - // (POST /computer/scroll) - Scroll(ctx context.Context, request ScrollRequestObject) (ScrollResponseObject, error) - // Type text on the host computer - // (POST /computer/type) - TypeText(ctx context.Context, request TypeTextRequestObject) (TypeTextResponseObject, error) - // Update display configuration - // (PATCH /display) +type GetTelemetryRequestObject struct { +} + +type GetTelemetryResponseObject interface { + VisitGetTelemetryResponse(w http.ResponseWriter) error +} + +type GetTelemetry200JSONResponse TelemetryState + +func (response GetTelemetry200JSONResponse) VisitGetTelemetryResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type GetTelemetry404JSONResponse struct{ NotFoundErrorJSONResponse } + +func (response GetTelemetry404JSONResponse) VisitGetTelemetryResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(404) + + return json.NewEncoder(w).Encode(response) +} + +type PatchTelemetryRequestObject struct { + Body *PatchTelemetryJSONRequestBody +} + +type PatchTelemetryResponseObject interface { + VisitPatchTelemetryResponse(w http.ResponseWriter) error +} + +type PatchTelemetry200JSONResponse TelemetryState + +func (response PatchTelemetry200JSONResponse) VisitPatchTelemetryResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type PatchTelemetry400JSONResponse struct{ BadRequestErrorJSONResponse } + +func (response PatchTelemetry400JSONResponse) VisitPatchTelemetryResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type PatchTelemetry404JSONResponse struct{ NotFoundErrorJSONResponse } + +func (response PatchTelemetry404JSONResponse) VisitPatchTelemetryResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(404) + + return json.NewEncoder(w).Encode(response) +} + +type PutTelemetryRequestObject struct { + Body *PutTelemetryJSONRequestBody +} + +type PutTelemetryResponseObject interface { + VisitPutTelemetryResponse(w http.ResponseWriter) error +} + +type PutTelemetry200JSONResponse TelemetryState + +func (response PutTelemetry200JSONResponse) VisitPutTelemetryResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type PutTelemetry201JSONResponse TelemetryState + +func (response PutTelemetry201JSONResponse) VisitPutTelemetryResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(201) + + return json.NewEncoder(w).Encode(response) +} + +type PutTelemetry400JSONResponse struct{ BadRequestErrorJSONResponse } + +func (response PutTelemetry400JSONResponse) VisitPutTelemetryResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type PutTelemetry500JSONResponse struct{ InternalErrorJSONResponse } + +func (response PutTelemetry500JSONResponse) VisitPutTelemetryResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + +type PublishEventRequestObject struct { + Body *PublishEventJSONRequestBody +} + +type PublishEventResponseObject interface { + VisitPublishEventResponse(w http.ResponseWriter) error +} + +type PublishEvent200JSONResponse PublishedEnvelope + +func (response PublishEvent200JSONResponse) VisitPublishEventResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type PublishEvent400JSONResponse struct{ BadRequestErrorJSONResponse } + +func (response PublishEvent400JSONResponse) VisitPublishEventResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type StreamEventsRequestObject struct { + Params StreamEventsParams +} + +type StreamEventsResponseObject interface { + VisitStreamEventsResponse(w http.ResponseWriter) error +} + +type StreamEvents200ResponseHeaders struct { + XSSEContentType string +} + +type StreamEvents200TexteventStreamResponse struct { + Body io.Reader + Headers StreamEvents200ResponseHeaders + ContentLength int64 +} + +func (response StreamEvents200TexteventStreamResponse) VisitStreamEventsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "text/event-stream") + if response.ContentLength != 0 { + w.Header().Set("Content-Length", fmt.Sprint(response.ContentLength)) + } + w.Header().Set("X-SSE-Content-Type", fmt.Sprint(response.Headers.XSSEContentType)) + w.Header().Set("Cache-Control", "no-cache") + w.Header().Set("X-Accel-Buffering", "no") + w.WriteHeader(200) + + if closer, ok := response.Body.(io.ReadCloser); ok { + defer closer.Close() + } + flusher, ok := w.(http.Flusher) + if !ok { + // If w doesn't support flushing, might as well use io.Copy + _, err := io.Copy(w, response.Body) + return err + } + + // Use a buffer for efficient copying and flushing + buf := make([]byte, 4096) // text/event-stream are usually very small messages + for { + n, err := response.Body.Read(buf) + if n > 0 { + if _, werr := w.Write(buf[:n]); werr != nil { + return werr + } + flusher.Flush() // Flush after each write + } + if err != nil { + if err == io.EOF { + return nil // End of file, no error + } + return err + } + } +} + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { + // Update Chromium launch flags and restart + // (PATCH /chromium/flags) + PatchChromiumFlags(ctx context.Context, request PatchChromiumFlagsRequestObject) (PatchChromiumFlagsResponseObject, error) + // Update Chromium enterprise policies and restart + // (PATCH /chromium/policies) + PatchChromiumPolicies(ctx context.Context, request PatchChromiumPoliciesRequestObject) (PatchChromiumPoliciesResponseObject, error) + // Upload one or more unpacked extensions (as zips) and restart Chromium + // (POST /chromium/upload-extensions-and-restart) + UploadExtensionsAndRestart(ctx context.Context, request UploadExtensionsAndRestartRequestObject) (UploadExtensionsAndRestartResponseObject, error) + // Execute a batch of computer actions sequentially + // (POST /computer/batch) + BatchComputerAction(ctx context.Context, request BatchComputerActionRequestObject) (BatchComputerActionResponseObject, error) + // Simulate a mouse click action on the host computer + // (POST /computer/click_mouse) + ClickMouse(ctx context.Context, request ClickMouseRequestObject) (ClickMouseResponseObject, error) + // Read text from the system clipboard + // (POST /computer/clipboard/read) + ReadClipboard(ctx context.Context, request ReadClipboardRequestObject) (ReadClipboardResponseObject, error) + // Write text to the system clipboard + // (POST /computer/clipboard/write) + WriteClipboard(ctx context.Context, request WriteClipboardRequestObject) (WriteClipboardResponseObject, error) + // Hide or show the cursor + // (POST /computer/cursor) + SetCursor(ctx context.Context, request SetCursorRequestObject) (SetCursorResponseObject, error) + // Drag the mouse along a path + // (POST /computer/drag_mouse) + DragMouse(ctx context.Context, request DragMouseRequestObject) (DragMouseResponseObject, error) + // Get the current mouse cursor position on the host computer + // (POST /computer/get_mouse_position) + GetMousePosition(ctx context.Context, request GetMousePositionRequestObject) (GetMousePositionResponseObject, error) + // Move the mouse cursor to the specified coordinates on the host computer + // (POST /computer/move_mouse) + MoveMouse(ctx context.Context, request MoveMouseRequestObject) (MoveMouseResponseObject, error) + // Press one or more keys on the host computer + // (POST /computer/press_key) + PressKey(ctx context.Context, request PressKeyRequestObject) (PressKeyResponseObject, error) + // Capture a screenshot of the host computer + // (POST /computer/screenshot) + TakeScreenshot(ctx context.Context, request TakeScreenshotRequestObject) (TakeScreenshotResponseObject, error) + // Scroll the mouse wheel at a position on the host computer + // (POST /computer/scroll) + Scroll(ctx context.Context, request ScrollRequestObject) (ScrollResponseObject, error) + // Type text on the host computer + // (POST /computer/type) + TypeText(ctx context.Context, request TypeTextRequestObject) (TypeTextResponseObject, error) + // Update display configuration + // (PATCH /display) PatchDisplay(ctx context.Context, request PatchDisplayRequestObject) (PatchDisplayResponseObject, error) - // Stop the capture session - // (DELETE /events/capture_session) - StopCaptureSession(ctx context.Context, request StopCaptureSessionRequestObject) (StopCaptureSessionResponseObject, error) - // Get the capture session - // (GET /events/capture_session) - GetCaptureSession(ctx context.Context, request GetCaptureSessionRequestObject) (GetCaptureSessionResponseObject, error) - // Update the capture session - // (PATCH /events/capture_session) - UpdateCaptureSession(ctx context.Context, request UpdateCaptureSessionRequestObject) (UpdateCaptureSessionResponseObject, error) - // Start the capture session - // (POST /events/capture_session) - StartCaptureSession(ctx context.Context, request StartCaptureSessionRequestObject) (StartCaptureSessionResponseObject, error) - // Publish an event into the event bus - // (POST /events/publish) - PublishEvent(ctx context.Context, request PublishEventRequestObject) (PublishEventResponseObject, error) - // Stream events as Server-Sent Events - // (GET /events/stream) - StreamEvents(ctx context.Context, request StreamEventsRequestObject) (StreamEventsResponseObject, error) // Create a new directory // (PUT /fs/create_directory) CreateDirectory(ctx context.Context, request CreateDirectoryRequestObject) (CreateDirectoryResponseObject, error) @@ -13550,6 +13344,21 @@ type StrictServerInterface interface { // Idempotently enable scale to zero on this VM. // (POST /scaletozero/enable) EnableScaleToZero(ctx context.Context, request EnableScaleToZeroRequestObject) (EnableScaleToZeroResponseObject, error) + // Get current telemetry state + // (GET /telemetry) + GetTelemetry(ctx context.Context, request GetTelemetryRequestObject) (GetTelemetryResponseObject, error) + // Update active telemetry configuration + // (PATCH /telemetry) + PatchTelemetry(ctx context.Context, request PatchTelemetryRequestObject) (PatchTelemetryResponseObject, error) + // Set telemetry configuration + // (PUT /telemetry) + PutTelemetry(ctx context.Context, request PutTelemetryRequestObject) (PutTelemetryResponseObject, error) + // Publish an event into the telemetry bus + // (POST /telemetry/events) + PublishEvent(ctx context.Context, request PublishEventRequestObject) (PublishEventResponseObject, error) + // Stream telemetry events as Server-Sent Events + // (GET /telemetry/stream) + StreamEvents(ctx context.Context, request StreamEventsRequestObject) (StreamEventsResponseObject, error) } type StrictHandlerFunc = strictnethttp.StrictHTTPHandlerFunc @@ -13792,223 +13601,10 @@ func (sh *strictHandler) WriteClipboard(w http.ResponseWriter, r *http.Request) } // SetCursor operation middleware -func (sh *strictHandler) SetCursor(w http.ResponseWriter, r *http.Request) { - var request SetCursorRequestObject - - var body SetCursorJSONRequestBody - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) - return - } - request.Body = &body - - handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.SetCursor(ctx, request.(SetCursorRequestObject)) - } - for _, middleware := range sh.middlewares { - handler = middleware(handler, "SetCursor") - } - - response, err := handler(r.Context(), w, r, request) - - if err != nil { - sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(SetCursorResponseObject); ok { - if err := validResponse.VisitSetCursorResponse(w); err != nil { - sh.options.ResponseErrorHandlerFunc(w, r, err) - } - } else if response != nil { - sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) - } -} - -// DragMouse operation middleware -func (sh *strictHandler) DragMouse(w http.ResponseWriter, r *http.Request) { - var request DragMouseRequestObject - - var body DragMouseJSONRequestBody - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) - return - } - request.Body = &body - - handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.DragMouse(ctx, request.(DragMouseRequestObject)) - } - for _, middleware := range sh.middlewares { - handler = middleware(handler, "DragMouse") - } - - response, err := handler(r.Context(), w, r, request) - - if err != nil { - sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(DragMouseResponseObject); ok { - if err := validResponse.VisitDragMouseResponse(w); err != nil { - sh.options.ResponseErrorHandlerFunc(w, r, err) - } - } else if response != nil { - sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) - } -} - -// GetMousePosition operation middleware -func (sh *strictHandler) GetMousePosition(w http.ResponseWriter, r *http.Request) { - var request GetMousePositionRequestObject - - handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.GetMousePosition(ctx, request.(GetMousePositionRequestObject)) - } - for _, middleware := range sh.middlewares { - handler = middleware(handler, "GetMousePosition") - } - - response, err := handler(r.Context(), w, r, request) - - if err != nil { - sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(GetMousePositionResponseObject); ok { - if err := validResponse.VisitGetMousePositionResponse(w); err != nil { - sh.options.ResponseErrorHandlerFunc(w, r, err) - } - } else if response != nil { - sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) - } -} - -// MoveMouse operation middleware -func (sh *strictHandler) MoveMouse(w http.ResponseWriter, r *http.Request) { - var request MoveMouseRequestObject - - var body MoveMouseJSONRequestBody - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) - return - } - request.Body = &body - - handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.MoveMouse(ctx, request.(MoveMouseRequestObject)) - } - for _, middleware := range sh.middlewares { - handler = middleware(handler, "MoveMouse") - } - - response, err := handler(r.Context(), w, r, request) - - if err != nil { - sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(MoveMouseResponseObject); ok { - if err := validResponse.VisitMoveMouseResponse(w); err != nil { - sh.options.ResponseErrorHandlerFunc(w, r, err) - } - } else if response != nil { - sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) - } -} - -// PressKey operation middleware -func (sh *strictHandler) PressKey(w http.ResponseWriter, r *http.Request) { - var request PressKeyRequestObject - - var body PressKeyJSONRequestBody - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) - return - } - request.Body = &body - - handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.PressKey(ctx, request.(PressKeyRequestObject)) - } - for _, middleware := range sh.middlewares { - handler = middleware(handler, "PressKey") - } - - response, err := handler(r.Context(), w, r, request) - - if err != nil { - sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(PressKeyResponseObject); ok { - if err := validResponse.VisitPressKeyResponse(w); err != nil { - sh.options.ResponseErrorHandlerFunc(w, r, err) - } - } else if response != nil { - sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) - } -} - -// TakeScreenshot operation middleware -func (sh *strictHandler) TakeScreenshot(w http.ResponseWriter, r *http.Request) { - var request TakeScreenshotRequestObject - - var body TakeScreenshotJSONRequestBody - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - if !errors.Is(err, io.EOF) { - sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) - return - } - } else { - request.Body = &body - } - - handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.TakeScreenshot(ctx, request.(TakeScreenshotRequestObject)) - } - for _, middleware := range sh.middlewares { - handler = middleware(handler, "TakeScreenshot") - } - - response, err := handler(r.Context(), w, r, request) - - if err != nil { - sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(TakeScreenshotResponseObject); ok { - if err := validResponse.VisitTakeScreenshotResponse(w); err != nil { - sh.options.ResponseErrorHandlerFunc(w, r, err) - } - } else if response != nil { - sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) - } -} - -// Scroll operation middleware -func (sh *strictHandler) Scroll(w http.ResponseWriter, r *http.Request) { - var request ScrollRequestObject - - var body ScrollJSONRequestBody - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) - return - } - request.Body = &body - - handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.Scroll(ctx, request.(ScrollRequestObject)) - } - for _, middleware := range sh.middlewares { - handler = middleware(handler, "Scroll") - } - - response, err := handler(r.Context(), w, r, request) - - if err != nil { - sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(ScrollResponseObject); ok { - if err := validResponse.VisitScrollResponse(w); err != nil { - sh.options.ResponseErrorHandlerFunc(w, r, err) - } - } else if response != nil { - sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) - } -} - -// TypeText operation middleware -func (sh *strictHandler) TypeText(w http.ResponseWriter, r *http.Request) { - var request TypeTextRequestObject +func (sh *strictHandler) SetCursor(w http.ResponseWriter, r *http.Request) { + var request SetCursorRequestObject - var body TypeTextJSONRequestBody + var body SetCursorJSONRequestBody if err := json.NewDecoder(r.Body).Decode(&body); err != nil { sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) return @@ -14016,18 +13612,18 @@ func (sh *strictHandler) TypeText(w http.ResponseWriter, r *http.Request) { request.Body = &body handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.TypeText(ctx, request.(TypeTextRequestObject)) + return sh.ssi.SetCursor(ctx, request.(SetCursorRequestObject)) } for _, middleware := range sh.middlewares { - handler = middleware(handler, "TypeText") + handler = middleware(handler, "SetCursor") } response, err := handler(r.Context(), w, r, request) if err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(TypeTextResponseObject); ok { - if err := validResponse.VisitTypeTextResponse(w); err != nil { + } else if validResponse, ok := response.(SetCursorResponseObject); ok { + if err := validResponse.VisitSetCursorResponse(w); err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { @@ -14035,11 +13631,11 @@ func (sh *strictHandler) TypeText(w http.ResponseWriter, r *http.Request) { } } -// PatchDisplay operation middleware -func (sh *strictHandler) PatchDisplay(w http.ResponseWriter, r *http.Request) { - var request PatchDisplayRequestObject +// DragMouse operation middleware +func (sh *strictHandler) DragMouse(w http.ResponseWriter, r *http.Request) { + var request DragMouseRequestObject - var body PatchDisplayJSONRequestBody + var body DragMouseJSONRequestBody if err := json.NewDecoder(r.Body).Decode(&body); err != nil { sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) return @@ -14047,18 +13643,18 @@ func (sh *strictHandler) PatchDisplay(w http.ResponseWriter, r *http.Request) { request.Body = &body handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.PatchDisplay(ctx, request.(PatchDisplayRequestObject)) + return sh.ssi.DragMouse(ctx, request.(DragMouseRequestObject)) } for _, middleware := range sh.middlewares { - handler = middleware(handler, "PatchDisplay") + handler = middleware(handler, "DragMouse") } response, err := handler(r.Context(), w, r, request) if err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(PatchDisplayResponseObject); ok { - if err := validResponse.VisitPatchDisplayResponse(w); err != nil { + } else if validResponse, ok := response.(DragMouseResponseObject); ok { + if err := validResponse.VisitDragMouseResponse(w); err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { @@ -14066,23 +13662,23 @@ func (sh *strictHandler) PatchDisplay(w http.ResponseWriter, r *http.Request) { } } -// StopCaptureSession operation middleware -func (sh *strictHandler) StopCaptureSession(w http.ResponseWriter, r *http.Request) { - var request StopCaptureSessionRequestObject +// GetMousePosition operation middleware +func (sh *strictHandler) GetMousePosition(w http.ResponseWriter, r *http.Request) { + var request GetMousePositionRequestObject handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.StopCaptureSession(ctx, request.(StopCaptureSessionRequestObject)) + return sh.ssi.GetMousePosition(ctx, request.(GetMousePositionRequestObject)) } for _, middleware := range sh.middlewares { - handler = middleware(handler, "StopCaptureSession") + handler = middleware(handler, "GetMousePosition") } response, err := handler(r.Context(), w, r, request) if err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(StopCaptureSessionResponseObject); ok { - if err := validResponse.VisitStopCaptureSessionResponse(w); err != nil { + } else if validResponse, ok := response.(GetMousePositionResponseObject); ok { + if err := validResponse.VisitGetMousePositionResponse(w); err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { @@ -14090,23 +13686,30 @@ func (sh *strictHandler) StopCaptureSession(w http.ResponseWriter, r *http.Reque } } -// GetCaptureSession operation middleware -func (sh *strictHandler) GetCaptureSession(w http.ResponseWriter, r *http.Request) { - var request GetCaptureSessionRequestObject +// MoveMouse operation middleware +func (sh *strictHandler) MoveMouse(w http.ResponseWriter, r *http.Request) { + var request MoveMouseRequestObject + + var body MoveMouseJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + request.Body = &body handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.GetCaptureSession(ctx, request.(GetCaptureSessionRequestObject)) + return sh.ssi.MoveMouse(ctx, request.(MoveMouseRequestObject)) } for _, middleware := range sh.middlewares { - handler = middleware(handler, "GetCaptureSession") + handler = middleware(handler, "MoveMouse") } response, err := handler(r.Context(), w, r, request) if err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(GetCaptureSessionResponseObject); ok { - if err := validResponse.VisitGetCaptureSessionResponse(w); err != nil { + } else if validResponse, ok := response.(MoveMouseResponseObject); ok { + if err := validResponse.VisitMoveMouseResponse(w); err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { @@ -14114,11 +13717,11 @@ func (sh *strictHandler) GetCaptureSession(w http.ResponseWriter, r *http.Reques } } -// UpdateCaptureSession operation middleware -func (sh *strictHandler) UpdateCaptureSession(w http.ResponseWriter, r *http.Request) { - var request UpdateCaptureSessionRequestObject +// PressKey operation middleware +func (sh *strictHandler) PressKey(w http.ResponseWriter, r *http.Request) { + var request PressKeyRequestObject - var body UpdateCaptureSessionJSONRequestBody + var body PressKeyJSONRequestBody if err := json.NewDecoder(r.Body).Decode(&body); err != nil { sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) return @@ -14126,18 +13729,18 @@ func (sh *strictHandler) UpdateCaptureSession(w http.ResponseWriter, r *http.Req request.Body = &body handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.UpdateCaptureSession(ctx, request.(UpdateCaptureSessionRequestObject)) + return sh.ssi.PressKey(ctx, request.(PressKeyRequestObject)) } for _, middleware := range sh.middlewares { - handler = middleware(handler, "UpdateCaptureSession") + handler = middleware(handler, "PressKey") } response, err := handler(r.Context(), w, r, request) if err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(UpdateCaptureSessionResponseObject); ok { - if err := validResponse.VisitUpdateCaptureSessionResponse(w); err != nil { + } else if validResponse, ok := response.(PressKeyResponseObject); ok { + if err := validResponse.VisitPressKeyResponse(w); err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { @@ -14145,11 +13748,11 @@ func (sh *strictHandler) UpdateCaptureSession(w http.ResponseWriter, r *http.Req } } -// StartCaptureSession operation middleware -func (sh *strictHandler) StartCaptureSession(w http.ResponseWriter, r *http.Request) { - var request StartCaptureSessionRequestObject +// TakeScreenshot operation middleware +func (sh *strictHandler) TakeScreenshot(w http.ResponseWriter, r *http.Request) { + var request TakeScreenshotRequestObject - var body StartCaptureSessionJSONRequestBody + var body TakeScreenshotJSONRequestBody if err := json.NewDecoder(r.Body).Decode(&body); err != nil { if !errors.Is(err, io.EOF) { sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) @@ -14160,18 +13763,18 @@ func (sh *strictHandler) StartCaptureSession(w http.ResponseWriter, r *http.Requ } handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.StartCaptureSession(ctx, request.(StartCaptureSessionRequestObject)) + return sh.ssi.TakeScreenshot(ctx, request.(TakeScreenshotRequestObject)) } for _, middleware := range sh.middlewares { - handler = middleware(handler, "StartCaptureSession") + handler = middleware(handler, "TakeScreenshot") } response, err := handler(r.Context(), w, r, request) if err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(StartCaptureSessionResponseObject); ok { - if err := validResponse.VisitStartCaptureSessionResponse(w); err != nil { + } else if validResponse, ok := response.(TakeScreenshotResponseObject); ok { + if err := validResponse.VisitTakeScreenshotResponse(w); err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { @@ -14179,11 +13782,11 @@ func (sh *strictHandler) StartCaptureSession(w http.ResponseWriter, r *http.Requ } } -// PublishEvent operation middleware -func (sh *strictHandler) PublishEvent(w http.ResponseWriter, r *http.Request) { - var request PublishEventRequestObject +// Scroll operation middleware +func (sh *strictHandler) Scroll(w http.ResponseWriter, r *http.Request) { + var request ScrollRequestObject - var body PublishEventJSONRequestBody + var body ScrollJSONRequestBody if err := json.NewDecoder(r.Body).Decode(&body); err != nil { sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) return @@ -14191,18 +13794,18 @@ func (sh *strictHandler) PublishEvent(w http.ResponseWriter, r *http.Request) { request.Body = &body handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.PublishEvent(ctx, request.(PublishEventRequestObject)) + return sh.ssi.Scroll(ctx, request.(ScrollRequestObject)) } for _, middleware := range sh.middlewares { - handler = middleware(handler, "PublishEvent") + handler = middleware(handler, "Scroll") } response, err := handler(r.Context(), w, r, request) if err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(PublishEventResponseObject); ok { - if err := validResponse.VisitPublishEventResponse(w); err != nil { + } else if validResponse, ok := response.(ScrollResponseObject); ok { + if err := validResponse.VisitScrollResponse(w); err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { @@ -14210,25 +13813,61 @@ func (sh *strictHandler) PublishEvent(w http.ResponseWriter, r *http.Request) { } } -// StreamEvents operation middleware -func (sh *strictHandler) StreamEvents(w http.ResponseWriter, r *http.Request, params StreamEventsParams) { - var request StreamEventsRequestObject +// TypeText operation middleware +func (sh *strictHandler) TypeText(w http.ResponseWriter, r *http.Request) { + var request TypeTextRequestObject - request.Params = params + var body TypeTextJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + request.Body = &body handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.StreamEvents(ctx, request.(StreamEventsRequestObject)) + return sh.ssi.TypeText(ctx, request.(TypeTextRequestObject)) } for _, middleware := range sh.middlewares { - handler = middleware(handler, "StreamEvents") + handler = middleware(handler, "TypeText") } response, err := handler(r.Context(), w, r, request) if err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(StreamEventsResponseObject); ok { - if err := validResponse.VisitStreamEventsResponse(w); err != nil { + } else if validResponse, ok := response.(TypeTextResponseObject); ok { + if err := validResponse.VisitTypeTextResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// PatchDisplay operation middleware +func (sh *strictHandler) PatchDisplay(w http.ResponseWriter, r *http.Request) { + var request PatchDisplayRequestObject + + var body PatchDisplayJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.PatchDisplay(ctx, request.(PatchDisplayRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "PatchDisplay") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(PatchDisplayResponseObject); ok { + if err := validResponse.VisitPatchDisplayResponse(w); err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { @@ -15195,191 +14834,338 @@ func (sh *strictHandler) EnableScaleToZero(w http.ResponseWriter, r *http.Reques } } +// GetTelemetry operation middleware +func (sh *strictHandler) GetTelemetry(w http.ResponseWriter, r *http.Request) { + var request GetTelemetryRequestObject + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.GetTelemetry(ctx, request.(GetTelemetryRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "GetTelemetry") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(GetTelemetryResponseObject); ok { + if err := validResponse.VisitGetTelemetryResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// PatchTelemetry operation middleware +func (sh *strictHandler) PatchTelemetry(w http.ResponseWriter, r *http.Request) { + var request PatchTelemetryRequestObject + + var body PatchTelemetryJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.PatchTelemetry(ctx, request.(PatchTelemetryRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "PatchTelemetry") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(PatchTelemetryResponseObject); ok { + if err := validResponse.VisitPatchTelemetryResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// PutTelemetry operation middleware +func (sh *strictHandler) PutTelemetry(w http.ResponseWriter, r *http.Request) { + var request PutTelemetryRequestObject + + var body PutTelemetryJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + if !errors.Is(err, io.EOF) { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + } else { + request.Body = &body + } + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.PutTelemetry(ctx, request.(PutTelemetryRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "PutTelemetry") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(PutTelemetryResponseObject); ok { + if err := validResponse.VisitPutTelemetryResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// PublishEvent operation middleware +func (sh *strictHandler) PublishEvent(w http.ResponseWriter, r *http.Request) { + var request PublishEventRequestObject + + var body PublishEventJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.PublishEvent(ctx, request.(PublishEventRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "PublishEvent") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(PublishEventResponseObject); ok { + if err := validResponse.VisitPublishEventResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// StreamEvents operation middleware +func (sh *strictHandler) StreamEvents(w http.ResponseWriter, r *http.Request, params StreamEventsParams) { + var request StreamEventsRequestObject + + request.Params = params + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.StreamEvents(ctx, request.(StreamEventsRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "StreamEvents") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(StreamEventsResponseObject); ok { + if err := validResponse.VisitStreamEventsResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/openapi.yaml b/server/openapi.yaml index c29fd98b..363f1abb 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -1194,89 +1194,80 @@ paths: $ref: "#/components/responses/BadRequestError" "500": $ref: "#/components/responses/InternalError" - /events/capture_session: + /telemetry: get: - summary: Get the capture session + summary: Get current telemetry state description: > - Returns the current state of the capture session. Returns 404 if - no session is active. - operationId: getCaptureSession + Returns the current telemetry state and configuration. Returns 404 if + telemetry is not active. + operationId: getTelemetry responses: "200": - description: Session state + description: Telemetry state content: application/json: schema: - $ref: "#/components/schemas/CaptureSession" + $ref: "#/components/schemas/TelemetryState" "404": $ref: "#/components/responses/NotFoundError" - post: - summary: Start the capture session + put: + summary: Set telemetry configuration description: > - Starts the capture session. At most one session may exist at a - time; returns 409 if a session is already active. The caller must - DELETE the existing session before starting a new one. - operationId: startCaptureSession + Creates or replaces the telemetry configuration. Starts telemetry if not + already active (returns 201); replaces the configuration if active (returns 200). + Setting all four categories to enabled: false stops telemetry (returns 200 + with status stopped). + operationId: putTelemetry requestBody: required: false content: application/json: schema: - $ref: "#/components/schemas/StartCaptureSessionRequest" + $ref: "#/components/schemas/BrowserTelemetryConfig" responses: + "200": + description: Telemetry configuration replaced + content: + application/json: + schema: + $ref: "#/components/schemas/TelemetryState" "201": - description: Session started + description: Telemetry started content: application/json: schema: - $ref: "#/components/schemas/CaptureSession" + $ref: "#/components/schemas/TelemetryState" "400": $ref: "#/components/responses/BadRequestError" - "409": - $ref: "#/components/responses/ConflictError" "500": $ref: "#/components/responses/InternalError" patch: - summary: Update the capture session + summary: Update active telemetry configuration description: > - Updates the active capture session. Returns 404 if no session is - active. - operationId: updateCaptureSession + Updates the configuration of the active telemetry session. Returns 404 if + telemetry is not active. Setting all four categories to enabled: false + stops telemetry (returns 200 with status stopped). + operationId: patchTelemetry requestBody: required: true content: application/json: schema: - $ref: "#/components/schemas/UpdateCaptureSessionRequest" + $ref: "#/components/schemas/BrowserTelemetryConfig" responses: "200": - description: Session updated + description: Telemetry configuration updated content: application/json: schema: - $ref: "#/components/schemas/CaptureSession" + $ref: "#/components/schemas/TelemetryState" "400": $ref: "#/components/responses/BadRequestError" "404": $ref: "#/components/responses/NotFoundError" - delete: - summary: Stop the capture session - description: > - Stops the capture session and its CDP monitor, returning the final - session state. Returns 404 if no session is active. - operationId: stopCaptureSession - responses: - "200": - description: Session stopped - content: - application/json: - schema: - $ref: "#/components/schemas/CaptureSession" - "404": - $ref: "#/components/responses/NotFoundError" - /events/publish: + /telemetry/events: post: - summary: Publish an event into the event bus + summary: Publish an event into the telemetry bus description: > Injects an event into the global event bus. The event is assigned a monotonically increasing sequence number and @@ -1297,11 +1288,11 @@ paths: $ref: "#/components/schemas/PublishedEnvelope" "400": $ref: "#/components/responses/BadRequestError" - /events/stream: + /telemetry/stream: get: - summary: Stream events as Server-Sent Events + summary: Stream telemetry events as Server-Sent Events description: > - Opens a live SSE stream of events from the global event bus. + Opens a live SSE stream of telemetry events from the global event bus. Each frame has the form `id: {seq}\ndata: {envelope-json}\n\n`. Sequence numbers are process-monotonic and do not reset between sessions; a Last-Event-ID from any previous session is valid for resuming the stream. @@ -1320,10 +1311,10 @@ paths: description: > Resume after this sequence number. Omit or send 0 to start from the current position. Sequence numbers are process-monotonic, so - a value from a previous session resumes correctly from that point. + a value from a previous telemetry session resumes correctly from that point. responses: "200": - description: Live SSE stream of events. + description: Live SSE stream of telemetry events. headers: X-SSE-Content-Type: description: Media type of SSE data events (application/json) @@ -1455,29 +1446,46 @@ components: event: $ref: "#/components/schemas/Event" additionalProperties: false - CaptureConfig: + BrowserTelemetryConfig: type: object - description: Capture filtering preferences. + description: > + Telemetry configuration for a browser session. Per-category capture settings. + Omit a category or set enabled: true to capture it. + Set enabled: false to exclude it. + Omit the browser key entirely to capture all categories. + Set all four categories to enabled: false to stop telemetry. properties: - categories: - type: array - description: > - Event categories to capture. When omitted or empty, all - categories are captured. - items: - type: string - enum: - - console - - network - - page - - interaction - - liveview - - captcha - - system + browser: + $ref: "#/components/schemas/BrowserTelemetryCategoriesConfig" + additionalProperties: false + BrowserTelemetryCategoriesConfig: + type: object + description: Per-category telemetry capture settings for browser events. + properties: + console: + $ref: "#/components/schemas/BrowserTelemetryCategoryConfig" + description: Console output (log, warn, error) and uncaught exceptions. + network: + $ref: "#/components/schemas/BrowserTelemetryCategoryConfig" + description: HTTP request/response metadata. + page: + $ref: "#/components/schemas/BrowserTelemetryCategoryConfig" + description: Page lifecycle events (navigation, load, layout shifts, LCP). + interaction: + $ref: "#/components/schemas/BrowserTelemetryCategoryConfig" + description: User interaction events (clicks, keydowns, scroll). + additionalProperties: false + BrowserTelemetryCategoryConfig: + type: object + description: Configuration for a single telemetry category. + properties: + enabled: + type: boolean + description: Whether this category is captured. Defaults to true if omitted. additionalProperties: false - CaptureSession: + TelemetryState: type: object - description: A capture session resource. + description: Current telemetry session state and configuration. required: - id - status @@ -1493,34 +1501,18 @@ components: - running - stopped config: - $ref: "#/components/schemas/CaptureConfig" + $ref: "#/components/schemas/BrowserTelemetryConfig" seq: type: integer format: int64 description: >- Process-monotonic sequence number of the last published event. - Does not reset between sessions. + Does not reset between telemetry sessions. minimum: 0 created_at: type: string format: date-time additionalProperties: false - StartCaptureSessionRequest: - type: object - description: > - Optional capture configuration. All fields default to the - server-defined profile when omitted or when no body is sent. - properties: - config: - $ref: "#/components/schemas/CaptureConfig" - additionalProperties: false - UpdateCaptureSessionRequest: - type: object - description: Fields to update on the capture session. - properties: - config: - $ref: "#/components/schemas/CaptureConfig" - additionalProperties: false StartRecordingRequest: type: object properties: From d90bbff0077174876303302b55cef82e7fad843f Mon Sep 17 00:00:00 2001 From: Archan Datta Date: Thu, 14 May 2026 08:21:09 -0300 Subject: [PATCH 02/28] feat: drop liveview and captcha event categories --- server/lib/events/event.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/server/lib/events/event.go b/server/lib/events/event.go index aa0e6b77..1ef8fc04 100644 --- a/server/lib/events/event.go +++ b/server/lib/events/event.go @@ -16,15 +16,14 @@ const ( CategoryNetwork EventCategory = "network" CategoryPage EventCategory = "page" CategoryInteraction EventCategory = "interaction" - CategoryLiveview EventCategory = "liveview" - CategoryCaptcha EventCategory = "captcha" CategorySystem EventCategory = "system" ) -// AllCategories is the canonical list of all known event categories. +// AllCategories is the canonical list of all configurable event categories. +// CategorySystem events are always captured regardless of telemetry config. var AllCategories = []EventCategory{ CategoryConsole, CategoryNetwork, CategoryPage, CategoryInteraction, - CategoryLiveview, CategoryCaptcha, CategorySystem, + CategorySystem, } var validCategories = func() map[EventCategory]struct{} { From 3f423521c075222e9184c675b6b6968ddb255961 Mon Sep 17 00:00:00 2001 From: Archan Datta Date: Thu, 14 May 2026 08:21:12 -0300 Subject: [PATCH 03/28] feat: introduce telemetry package, replacing capturesession --- server/lib/telemetry/telemetry.go | 161 ++++++++++++++++++ server/lib/telemetry/telemetry_test.go | 221 +++++++++++++++++++++++++ 2 files changed, 382 insertions(+) create mode 100644 server/lib/telemetry/telemetry.go create mode 100644 server/lib/telemetry/telemetry_test.go diff --git a/server/lib/telemetry/telemetry.go b/server/lib/telemetry/telemetry.go new file mode 100644 index 00000000..9ed8458d --- /dev/null +++ b/server/lib/telemetry/telemetry.go @@ -0,0 +1,161 @@ +package telemetry + +import ( + "sync" + "time" + + "github.com/kernel/kernel-images/server/lib/events" +) + +// TelemetryConfig holds caller-supplied telemetry preferences. All fields are +// optional; zero values mean "use server defaults" (all user-facing categories +// plus system events). +type TelemetryConfig struct { + // Categories limits which event categories are captured. + // nil or empty captures all user-facing categories plus system events. + Categories []events.EventCategory +} + +// TelemetrySession manages a telemetry session against a shared EventStream. +// It is responsible for (a) category-filtering Publish calls, (b) tracking +// session-scoped metadata (ID, config, timestamps), and (c) embedding +// telemetry_session_id into Event.Source.Metadata before forwarding to the bus. +type TelemetrySession struct { + es *events.EventStream + mu sync.Mutex + telemetrySessionID string + sessionStartSeq uint64 + categories map[events.EventCategory]struct{} + createdAt time.Time +} + +func NewTelemetrySession(es *events.EventStream) *TelemetrySession { + cats := make(map[events.EventCategory]struct{}, len(events.AllCategories)) + for _, c := range events.AllCategories { + cats[c] = struct{}{} + } + return &TelemetrySession{es: es, categories: cats} +} + +// Start begins a new telemetry session with the given ID and config. Sequence +// numbers are process-monotonic and do not reset between sessions; a +// Last-Event-ID from any previous session is valid for resuming the SSE stream. +func (s *TelemetrySession) Start(telemetrySessionID string, cfg TelemetryConfig) { + s.mu.Lock() + defer s.mu.Unlock() + s.telemetrySessionID = telemetrySessionID + s.sessionStartSeq = s.es.Seq() + s.createdAt = time.Now() + + // Build the category filter. CategorySystem is always included so + // kernel_api events (e.g. monitor_disconnected) are never dropped. + cats := cfg.Categories + if len(cats) == 0 { + cats = events.AllCategories + } + s.categories = make(map[events.EventCategory]struct{}, len(cats)+1) + for _, c := range cats { + s.categories[c] = struct{}{} + } + s.categories[events.CategorySystem] = struct{}{} +} + +// publishLocked stamps telemetry_session_id into ev.Source.Metadata and forwards to the bus. +// Requires s.mu to be held. +func (s *TelemetrySession) publishLocked(ev events.Event) events.Envelope { + if ev.Ts == 0 { + ev.Ts = time.Now().UnixMicro() + } + if ev.Source.Metadata == nil { + ev.Source.Metadata = make(map[string]string) + } + ev.Source.Metadata["telemetry_session_id"] = s.telemetrySessionID + return s.es.Publish(events.Envelope{Event: ev}) +} + +// Publish applies the category filter then forwards ev to the EventStream. +func (s *TelemetrySession) Publish(ev events.Event) { + s.mu.Lock() + defer s.mu.Unlock() + if s.telemetrySessionID == "" { + return + } + if _, ok := s.categories[ev.Category]; !ok { + return + } + s.publishLocked(ev) +} + +// NewReader returns a Reader from the EventStream positioned after afterSeq. +func (s *TelemetrySession) NewReader(afterSeq uint64) *events.Reader { + return s.es.NewReader(afterSeq) +} + +// ID returns the current telemetry session ID, or "" if no session is active. +func (s *TelemetrySession) ID() string { + s.mu.Lock() + defer s.mu.Unlock() + return s.telemetrySessionID +} + +// Seq returns the sequence number of the last published event. +func (s *TelemetrySession) Seq() uint64 { + return s.es.Seq() +} + +// SessionStartSeq returns the sequence number at which the current session +// started. Fresh SSE connections with no Last-Event-ID should begin here. +func (s *TelemetrySession) SessionStartSeq() uint64 { + s.mu.Lock() + defer s.mu.Unlock() + return s.sessionStartSeq +} + +// Config returns the current telemetry configuration. +func (s *TelemetrySession) Config() TelemetryConfig { + s.mu.Lock() + defer s.mu.Unlock() + cats := make([]events.EventCategory, 0, len(s.categories)) + for c := range s.categories { + cats = append(cats, c) + } + return TelemetryConfig{Categories: cats} +} + +// CreatedAt returns when the current session was started. +func (s *TelemetrySession) CreatedAt() time.Time { + s.mu.Lock() + defer s.mu.Unlock() + return s.createdAt +} + +// UpdateConfig applies a new TelemetryConfig to the running session. +func (s *TelemetrySession) UpdateConfig(cfg TelemetryConfig) { + s.mu.Lock() + defer s.mu.Unlock() + // CategorySystem is always included so kernel_api events are never dropped. + cats := cfg.Categories + if len(cats) == 0 { + cats = events.AllCategories + } + s.categories = make(map[events.EventCategory]struct{}, len(cats)+1) + for _, c := range cats { + s.categories[c] = struct{}{} + } + s.categories[events.CategorySystem] = struct{}{} +} + +// Active reports whether a telemetry session is currently running. +func (s *TelemetrySession) Active() bool { + s.mu.Lock() + defer s.mu.Unlock() + return s.telemetrySessionID != "" +} + +// Stop ends the current telemetry session. The ring buffer is left intact so +// existing readers can finish draining. +func (s *TelemetrySession) Stop() { + s.mu.Lock() + defer s.mu.Unlock() + s.telemetrySessionID = "" +} diff --git a/server/lib/telemetry/telemetry_test.go b/server/lib/telemetry/telemetry_test.go new file mode 100644 index 00000000..29010282 --- /dev/null +++ b/server/lib/telemetry/telemetry_test.go @@ -0,0 +1,221 @@ +package telemetry + +import ( + "context" + "encoding/json" + "strings" + "sync" + "testing" + "time" + + "github.com/kernel/kernel-images/server/lib/events" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func newTestEventStream(t *testing.T, capacity int) *events.EventStream { + t.Helper() + es, err := events.NewEventStream(events.EventStreamConfig{RingCapacity: capacity}) + require.NoError(t, err) + return es +} + +func newTestTelemetrySession(t *testing.T) *TelemetrySession { + t.Helper() + ts := NewTelemetrySession(newTestEventStream(t, 100)) + ts.Start("test-session", TelemetryConfig{}) + return ts +} + +func readEnvelope(t *testing.T, r *events.Reader, ctx context.Context) events.Envelope { + t.Helper() + res, err := r.Read(ctx) + require.NoError(t, err) + require.NotNil(t, res.Envelope, "expected envelope, got drop") + return *res.Envelope +} + +func cdpEvent(typ string, cat events.EventCategory) events.Event { + return events.Event{Type: typ, Category: cat, Source: events.Source{Kind: events.KindCDP}} +} + +func telemetrySessionIDFromMetadata(t *testing.T, src events.Source) string { + t.Helper() + id, ok := src.Metadata["telemetry_session_id"] + require.True(t, ok, "telemetry_session_id not found in source.metadata") + return id +} + +func TestTelemetrySession(t *testing.T) { + t.Run("concurrent_publish_seq_order", func(t *testing.T) { + const goroutines = 8 + const eventsEach = 50 + const total = goroutines * eventsEach + + ts := NewTelemetrySession(newTestEventStream(t, total)) + ts.Start("test-concurrent", TelemetryConfig{}) + reader := ts.NewReader(0) + + var wg sync.WaitGroup + for i := 0; i < goroutines; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for j := 0; j < eventsEach; j++ { + ts.Publish(cdpEvent("console.log", events.CategoryConsole)) + } + }() + } + wg.Wait() + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + for want := uint64(1); want <= total; want++ { + env := readEnvelope(t, reader, ctx) + assert.Equal(t, want, env.Seq, "events must arrive in seq order") + } + }) + + t.Run("seq_continues_across_sessions", func(t *testing.T) { + ts := NewTelemetrySession(newTestEventStream(t, 100)) + ts.Start("session-1", TelemetryConfig{}) + ts.Publish(cdpEvent("ev.one", events.CategorySystem)) + ts.Publish(cdpEvent("ev.two", events.CategorySystem)) + + ts.Start("session-2", TelemetryConfig{}) + ts.Publish(cdpEvent("ev.three", events.CategorySystem)) + + assert.Equal(t, uint64(2), ts.SessionStartSeq(), "session-2 starts after seq 2") + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + reader := ts.NewReader(2) + env := readEnvelope(t, reader, ctx) + assert.Equal(t, uint64(3), env.Seq) + assert.Equal(t, "session-2", telemetrySessionIDFromMetadata(t, env.Event.Source)) + assert.Equal(t, "ev.three", env.Event.Type) + }) + + t.Run("publish_increments_seq", func(t *testing.T) { + ts := newTestTelemetrySession(t) + reader := ts.NewReader(0) + + for i := 0; i < 3; i++ { + ts.Publish(events.Event{Type: "page.navigation", Category: events.CategoryPage, Source: events.Source{Kind: events.KindCDP}, Ts: 1}) + } + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + for want := uint64(1); want <= 3; want++ { + env := readEnvelope(t, reader, ctx) + assert.Equal(t, want, env.Seq, "expected seq %d got %d", want, env.Seq) + } + }) + + t.Run("publish_sets_ts", func(t *testing.T) { + ts := newTestTelemetrySession(t) + reader := ts.NewReader(0) + + before := time.Now().UnixMicro() + ts.Publish(events.Event{Type: "page.navigation", Category: events.CategoryPage, Source: events.Source{Kind: events.KindCDP}}) + after := time.Now().UnixMicro() + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + env := readEnvelope(t, reader, ctx) + assert.GreaterOrEqual(t, env.Event.Ts, before) + assert.LessOrEqual(t, env.Event.Ts, after) + }) + + t.Run("publish_writes_ring", func(t *testing.T) { + ts := newTestTelemetrySession(t) + + reader := ts.NewReader(0) + ts.Publish(events.Event{Type: "page.navigation", Category: events.CategoryPage, Source: events.Source{Kind: events.KindCDP}, Ts: 1}) + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + env := readEnvelope(t, reader, ctx) + assert.Equal(t, "page.navigation", env.Event.Type) + assert.Equal(t, events.CategoryPage, env.Event.Category) + }) + + t.Run("start_sets_telemetry_session_id_in_source_metadata", func(t *testing.T) { + ts := newTestTelemetrySession(t) + ts.Start("test-uuid", TelemetryConfig{}) + + reader := ts.NewReader(0) + ts.Publish(events.Event{Type: "page.navigation", Category: events.CategoryPage, Source: events.Source{Kind: events.KindCDP}, Ts: 1}) + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + env := readEnvelope(t, reader, ctx) + assert.Equal(t, "test-uuid", telemetrySessionIDFromMetadata(t, env.Event.Source)) + }) + + t.Run("data_unchanged_when_telemetry_session_id_in_metadata", func(t *testing.T) { + ts := newTestTelemetrySession(t) + ts.Start("merge-session", TelemetryConfig{}) + + reader := ts.NewReader(0) + ts.Publish(events.Event{ + Type: "page.navigation", + Category: events.CategoryPage, + Source: events.Source{Kind: events.KindCDP}, + Ts: 1, + Data: json.RawMessage(`{"url":"https://example.com"}`), + }) + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + env := readEnvelope(t, reader, ctx) + assert.Equal(t, "merge-session", telemetrySessionIDFromMetadata(t, env.Event.Source)) + assert.JSONEq(t, `{"url":"https://example.com"}`, string(env.Event.Data)) + }) + + t.Run("system_events_always_captured_regardless_of_config", func(t *testing.T) { + ts := NewTelemetrySession(newTestEventStream(t, 10)) + // Start with only console category — system should still pass through. + ts.Start("sys-test", TelemetryConfig{Categories: []events.EventCategory{events.CategoryConsole}}) + reader := ts.NewReader(0) + + ts.Publish(events.Event{Type: "monitor.disconnected", Category: events.CategorySystem, Source: events.Source{Kind: events.KindKernelAPI}, Ts: 1}) + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + env := readEnvelope(t, reader, ctx) + assert.Equal(t, events.CategorySystem, env.Event.Category) + }) + + t.Run("truncation_applied", func(t *testing.T) { + ts := newTestTelemetrySession(t) + reader := ts.NewReader(0) + + largeData := strings.Repeat("x", 1_100_000) + rawData, err := json.Marshal(map[string]string{"payload": largeData}) + require.NoError(t, err) + + ts.Publish(events.Event{ + Type: "page.navigation", + Category: events.CategoryPage, + Source: events.Source{Kind: events.KindCDP}, + Ts: 1, + Data: json.RawMessage(rawData), + }) + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + env := readEnvelope(t, reader, ctx) + assert.True(t, env.Event.Truncated) + assert.True(t, json.Valid(env.Event.Data)) + }) +} From ccb50e606634973b00618293f40e87931c41bdd8 Mon Sep 17 00:00:00 2001 From: Archan Datta Date: Thu, 14 May 2026 08:26:11 -0300 Subject: [PATCH 04/28] feat: drop liveview and captcha from event category enums in openapi spec --- server/lib/oapi/oapi.go | 384 +++++++++++++++++++--------------------- server/openapi.yaml | 8 +- 2 files changed, 188 insertions(+), 204 deletions(-) diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index bffeb4fc..e69e2d14 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -133,10 +133,8 @@ func (e DragMouseRequestButton) Valid() bool { // Defines values for EventCategory. const ( - EventCategoryCaptcha EventCategory = "captcha" EventCategoryConsole EventCategory = "console" EventCategoryInteraction EventCategory = "interaction" - EventCategoryLiveview EventCategory = "liveview" EventCategoryNetwork EventCategory = "network" EventCategoryPage EventCategory = "page" EventCategorySystem EventCategory = "system" @@ -145,14 +143,10 @@ const ( // Valid indicates whether the value is a known member of the EventCategory enum. func (e EventCategory) Valid() bool { switch e { - case EventCategoryCaptcha: - return true case EventCategoryConsole: return true case EventCategoryInteraction: return true - case EventCategoryLiveview: - return true case EventCategoryNetwork: return true case EventCategoryPage: @@ -313,10 +307,8 @@ func (e ProcessStreamEventStream) Valid() bool { // Defines values for PublishEventRequestCategory. const ( - PublishEventRequestCategoryCaptcha PublishEventRequestCategory = "captcha" PublishEventRequestCategoryConsole PublishEventRequestCategory = "console" PublishEventRequestCategoryInteraction PublishEventRequestCategory = "interaction" - PublishEventRequestCategoryLiveview PublishEventRequestCategory = "liveview" PublishEventRequestCategoryNetwork PublishEventRequestCategory = "network" PublishEventRequestCategoryPage PublishEventRequestCategory = "page" PublishEventRequestCategorySystem PublishEventRequestCategory = "system" @@ -325,14 +317,10 @@ const ( // Valid indicates whether the value is a known member of the PublishEventRequestCategory enum. func (e PublishEventRequestCategory) Valid() bool { switch e { - case PublishEventRequestCategoryCaptcha: - return true case PublishEventRequestCategoryConsole: return true case PublishEventRequestCategoryInteraction: return true - case PublishEventRequestCategoryLiveview: - return true case PublishEventRequestCategoryNetwork: return true case PublishEventRequestCategoryPage: @@ -560,7 +548,7 @@ type Error struct { Message string `json:"message"` } -// Event A capture event. +// Event A telemetry event. type Event struct { // Category Event category. Category *EventCategory `json:"category,omitempty"` @@ -910,7 +898,7 @@ type PublishEventRequest struct { // Category Event category. Category *PublishEventRequestCategory `json:"category,omitempty"` - // Data Capture Session Event Payload + // Data Telemetry event payload Data interface{} `json:"data,omitempty"` // Source Provenance of the event. @@ -925,7 +913,7 @@ type PublishEventRequestCategory string // PublishedEnvelope The envelope assigned to a successfully published event. type PublishedEnvelope struct { - // Event A capture event. + // Event A telemetry event. Event Event `json:"event"` // Seq Process-monotonic sequence number assigned across the lifetime of the server. Use with Last-Event-ID to resume the SSE stream from this point. @@ -14983,189 +14971,189 @@ func (sh *strictHandler) StreamEvents(w http.ResponseWriter, r *http.Request, pa // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "ecdzq337/wYAAP//J6eTQj3jAAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/openapi.yaml b/server/openapi.yaml index 363f1abb..eb2b3596 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -1353,7 +1353,7 @@ components: schemas: Event: type: object - description: A capture event. + description: A telemetry event. required: - type properties: @@ -1372,8 +1372,6 @@ components: - network - page - interaction - - liveview - - captcha - system source: $ref: "#/components/schemas/EventSource" @@ -1419,14 +1417,12 @@ components: - network - page - interaction - - liveview - - captcha - system default: system source: $ref: "#/components/schemas/EventSource" data: - description: Capture Session Event Payload + description: Telemetry event payload additionalProperties: false PublishedEnvelope: type: object From 87efe18419603d2e4534149edf5bb1fad948a61c Mon Sep 17 00:00:00 2001 From: Archan Datta Date: Thu, 14 May 2026 08:26:14 -0300 Subject: [PATCH 05/28] feat: replace capture_session handlers with telemetry handlers, rewire ApiService --- server/cmd/api/api/api.go | 34 ++--- server/cmd/api/api/capture_session.go | 147 -------------------- server/cmd/api/api/events.go | 6 +- server/cmd/api/api/telemetry.go | 188 ++++++++++++++++++++++++++ server/cmd/api/main.go | 6 +- 5 files changed, 211 insertions(+), 170 deletions(-) delete mode 100644 server/cmd/api/api/capture_session.go create mode 100644 server/cmd/api/api/telemetry.go diff --git a/server/cmd/api/api/api.go b/server/cmd/api/api/api.go index d28fedd9..ffea2836 100644 --- a/server/cmd/api/api/api.go +++ b/server/cmd/api/api/api.go @@ -10,8 +10,8 @@ import ( "sync" "time" - "github.com/kernel/kernel-images/server/lib/capturesession" "github.com/kernel/kernel-images/server/lib/cdpmonitor" + "github.com/kernel/kernel-images/server/lib/telemetry" "github.com/kernel/kernel-images/server/lib/devtoolsproxy" "github.com/kernel/kernel-images/server/lib/events" "github.com/kernel/kernel-images/server/lib/logger" @@ -81,13 +81,13 @@ type ApiService struct { // when multiple CDP fast-path resizes fire in quick succession. xvfbResizeMu sync.Mutex - // CDP event pipeline and cdpMonitor. - eventStream *events.EventStream - captureSession *capturesession.CaptureSession - cdpMonitor cdpMonitorController - monitorMu sync.Mutex - lifecycleCtx context.Context - lifecycleCancel context.CancelFunc + // Telemetry event pipeline and CDP monitor. + eventStream *events.EventStream + telemetrySession *telemetry.TelemetrySession + cdpMonitor cdpMonitorController + monitorMu sync.Mutex + lifecycleCtx context.Context + lifecycleCancel context.CancelFunc } var _ oapi.StrictServerInterface = (*ApiService)(nil) @@ -98,8 +98,8 @@ func New( upstreamMgr *devtoolsproxy.UpstreamManager, stz scaletozero.PinnedController, nekoAuthClient *nekoclient.AuthClient, - captureSession *capturesession.CaptureSession, - eventStream *events.EventStream, + telemetrySession *telemetry.TelemetrySession, + eventStream *events.EventStream, displayNum int, ) (*ApiService, error) { switch { @@ -111,18 +111,18 @@ func New( return nil, fmt.Errorf("upstreamMgr cannot be nil") case nekoAuthClient == nil: return nil, fmt.Errorf("nekoAuthClient cannot be nil") - case captureSession == nil: - return nil, fmt.Errorf("captureSession cannot be nil") + case telemetrySession == nil: + return nil, fmt.Errorf("telemetrySession cannot be nil") case eventStream == nil: return nil, fmt.Errorf("eventStream cannot be nil") } - mon := cdpmonitor.New(upstreamMgr, captureSession.Publish, displayNum, slog.Default()) + mon := cdpmonitor.New(upstreamMgr, telemetrySession.Publish, displayNum, slog.Default()) ctx, cancel := context.WithCancel(context.Background()) return &ApiService{ - recordManager: recordManager, - factory: factory, + recordManager: recordManager, + factory: factory, defaultRecorderID: "default", watches: make(map[string]*fsWatch), procs: make(map[string]*processHandle), @@ -131,7 +131,7 @@ func New( nekoAuthClient: nekoAuthClient, policy: &policy.Policy{}, eventStream: eventStream, - captureSession: captureSession, + telemetrySession: telemetrySession, cdpMonitor: mon, lifecycleCtx: ctx, lifecycleCancel: cancel, @@ -357,7 +357,7 @@ func (s *ApiService) Shutdown(ctx context.Context) error { s.monitorMu.Lock() s.lifecycleCancel() s.cdpMonitor.Stop() - s.captureSession.Stop() + s.telemetrySession.Stop() s.monitorMu.Unlock() return s.recordManager.StopAll(ctx) } diff --git a/server/cmd/api/api/capture_session.go b/server/cmd/api/api/capture_session.go deleted file mode 100644 index b69ed84a..00000000 --- a/server/cmd/api/api/capture_session.go +++ /dev/null @@ -1,147 +0,0 @@ -package api - -import ( - "context" - "fmt" - "sort" - - "github.com/nrednav/cuid2" - oapi "github.com/kernel/kernel-images/server/lib/oapi" - - "github.com/kernel/kernel-images/server/lib/capturesession" - "github.com/kernel/kernel-images/server/lib/events" - "github.com/kernel/kernel-images/server/lib/logger" -) - -// StartCaptureSession handles POST /events/capture_session. -// Returns 409 if a session is already active. -func (s *ApiService) StartCaptureSession(ctx context.Context, req oapi.StartCaptureSessionRequestObject) (oapi.StartCaptureSessionResponseObject, error) { - s.monitorMu.Lock() - defer s.monitorMu.Unlock() - - if s.captureSession.ID() != "" { - return oapi.StartCaptureSession409JSONResponse{ConflictErrorJSONResponse: oapi.ConflictErrorJSONResponse{Message: "a capture session is already active"}}, nil - } - - cfg, err := captureConfigFrom(req.Body) - if err != nil { - return oapi.StartCaptureSession400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: err.Error()}}, nil - } - - id := cuid2.Generate() - s.captureSession.Start(id, cfg) - - if err := s.cdpMonitor.Start(s.lifecycleCtx); err != nil { - // Roll back: clear the session so a retry can succeed. - s.captureSession.Stop() - logger.FromContext(ctx).Error("failed to start capture monitor", "err", err) - return oapi.StartCaptureSession500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "failed to start capture"}}, nil - } - - return oapi.StartCaptureSession201JSONResponse(s.buildSessionResponse()), nil -} - -// GetCaptureSession handles GET /events/capture_session. -func (s *ApiService) GetCaptureSession(_ context.Context, _ oapi.GetCaptureSessionRequestObject) (oapi.GetCaptureSessionResponseObject, error) { - s.monitorMu.Lock() - defer s.monitorMu.Unlock() - - if s.captureSession.ID() == "" { - return oapi.GetCaptureSession404JSONResponse{NotFoundErrorJSONResponse: oapi.NotFoundErrorJSONResponse{Message: "no active capture session"}}, nil - } - return oapi.GetCaptureSession200JSONResponse(s.buildSessionResponse()), nil -} - -// UpdateCaptureSession handles PATCH /events/capture_session. -func (s *ApiService) UpdateCaptureSession(_ context.Context, req oapi.UpdateCaptureSessionRequestObject) (oapi.UpdateCaptureSessionResponseObject, error) { - s.monitorMu.Lock() - defer s.monitorMu.Unlock() - - if s.captureSession.ID() == "" { - return oapi.UpdateCaptureSession404JSONResponse{NotFoundErrorJSONResponse: oapi.NotFoundErrorJSONResponse{Message: "no active capture session"}}, nil - } - - if req.Body != nil && req.Body.Config != nil { - cfg, err := captureConfigFromOAPI(req.Body.Config) - if err != nil { - return oapi.UpdateCaptureSession400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: err.Error()}}, nil - } - s.captureSession.UpdateConfig(cfg) - } - - return oapi.UpdateCaptureSession200JSONResponse(s.buildSessionResponse()), nil -} - -// StopCaptureSession handles DELETE /events/capture_session. -// Stops the capture session and clears it so a new one can be started. -func (s *ApiService) StopCaptureSession(_ context.Context, _ oapi.StopCaptureSessionRequestObject) (oapi.StopCaptureSessionResponseObject, error) { - s.monitorMu.Lock() - defer s.monitorMu.Unlock() - - if s.captureSession.ID() == "" { - return oapi.StopCaptureSession404JSONResponse{NotFoundErrorJSONResponse: oapi.NotFoundErrorJSONResponse{Message: "no active capture session"}}, nil - } - - s.cdpMonitor.Stop() - // Snapshot the final state before clearing the session ID so buildSessionResponse - // can still parse it. Force the status to Stopped because cdpMonitor.Stop may - // tear down asynchronously, leaving IsRunning briefly true. - resp := s.buildSessionResponse() - resp.Status = oapi.CaptureSessionStatusStopped - s.captureSession.Stop() - - return oapi.StopCaptureSession200JSONResponse(resp), nil -} - -// buildSessionResponse constructs the CaptureSession response from current state. -func (s *ApiService) buildSessionResponse() oapi.CaptureSession { - cfg := s.captureSession.Config() - - cats := make([]oapi.CaptureConfigCategories, len(cfg.Categories)) - for i, c := range cfg.Categories { - cats[i] = oapi.CaptureConfigCategories(c) - } - sort.Slice(cats, func(i, j int) bool { return cats[i] < cats[j] }) - - status := oapi.CaptureSessionStatusStopped - if s.cdpMonitor.IsRunning() { - status = oapi.CaptureSessionStatusRunning - } - - return oapi.CaptureSession{ - Id: s.captureSession.ID(), - Status: status, - Config: oapi.CaptureConfig{ - Categories: &cats, - }, - Seq: int64(s.captureSession.Seq()), - CreatedAt: s.captureSession.CreatedAt(), - } -} - -// captureConfigFrom converts the optional StartCaptureSessionRequest body -// into a capturesession.CaptureConfig. -func captureConfigFrom(body *oapi.StartCaptureSessionRequest) (capturesession.CaptureConfig, error) { - if body == nil { - return capturesession.CaptureConfig{}, nil - } - return captureConfigFromOAPI(body.Config) -} - -// captureConfigFromOAPI converts an oapi.CaptureConfig to capturesession.CaptureConfig. -func captureConfigFromOAPI(cfg *oapi.CaptureConfig) (capturesession.CaptureConfig, error) { - if cfg == nil || cfg.Categories == nil { - return capturesession.CaptureConfig{}, nil - } - out := capturesession.CaptureConfig{ - Categories: make([]events.EventCategory, 0, len(*cfg.Categories)), - } - for _, c := range *cfg.Categories { - cat := events.EventCategory(c) - if !events.ValidCategory(cat) { - return capturesession.CaptureConfig{}, fmt.Errorf("unknown category: %q", c) - } - out.Categories = append(out.Categories, cat) - } - return out, nil -} diff --git a/server/cmd/api/api/events.go b/server/cmd/api/api/events.go index c72ba756..20f25bce 100644 --- a/server/cmd/api/api/events.go +++ b/server/cmd/api/api/events.go @@ -15,7 +15,7 @@ import ( oapi "github.com/kernel/kernel-images/server/lib/oapi" ) -// PublishEvent handles POST /events/publish. +// PublishEvent handles POST /telemetry/events. // Injects a caller-supplied event into the event bus. Returns 400 if the event // fails validation. func (s *ApiService) PublishEvent(_ context.Context, req oapi.PublishEventRequestObject) (oapi.PublishEventResponseObject, error) { @@ -64,8 +64,8 @@ func (s *ApiService) PublishEvent(_ context.Context, req oapi.PublishEventReques return publishEventOKResponse{env}, nil } -// StreamEvents handles GET /events/stream. -// Opens an SSE stream of envelopes from the event bus ring buffer. +// StreamEvents handles GET /telemetry/stream. +// Opens an SSE stream of telemetry event envelopes from the event bus ring buffer. // Supports reconnection via the Last-Event-ID header. Emits a keepalive comment // frame every 15 s when no event arrives. func (s *ApiService) StreamEvents(ctx context.Context, req oapi.StreamEventsRequestObject) (oapi.StreamEventsResponseObject, error) { diff --git a/server/cmd/api/api/telemetry.go b/server/cmd/api/api/telemetry.go new file mode 100644 index 00000000..961f2f7e --- /dev/null +++ b/server/cmd/api/api/telemetry.go @@ -0,0 +1,188 @@ +package api + +import ( + "context" + "sort" + + "github.com/nrednav/cuid2" + oapi "github.com/kernel/kernel-images/server/lib/oapi" + + "github.com/kernel/kernel-images/server/lib/events" + "github.com/kernel/kernel-images/server/lib/logger" + "github.com/kernel/kernel-images/server/lib/telemetry" +) + +// GetTelemetry handles GET /telemetry. +// Returns the current telemetry state. Returns 404 if telemetry is not active. +func (s *ApiService) GetTelemetry(_ context.Context, _ oapi.GetTelemetryRequestObject) (oapi.GetTelemetryResponseObject, error) { + s.monitorMu.Lock() + defer s.monitorMu.Unlock() + + if s.telemetrySession.ID() == "" { + return oapi.GetTelemetry404JSONResponse{NotFoundErrorJSONResponse: oapi.NotFoundErrorJSONResponse{Message: "telemetry is not active"}}, nil + } + return oapi.GetTelemetry200JSONResponse(s.buildTelemetryResponse()), nil +} + +// PutTelemetry handles PUT /telemetry. +// Creates (201) or replaces (200) the telemetry session with the given config. +// Setting all four categories to enabled:false stops an active session (200, status stopped). +func (s *ApiService) PutTelemetry(ctx context.Context, req oapi.PutTelemetryRequestObject) (oapi.PutTelemetryResponseObject, error) { + s.monitorMu.Lock() + defer s.monitorMu.Unlock() + + cfg, allDisabled, err := telemetryConfigFromOAPI(req.Body) + if err != nil { + return oapi.PutTelemetry400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: err.Error()}}, nil + } + + wasActive := s.telemetrySession.ID() != "" + + if allDisabled { + // All categories disabled: stop any running session. + if wasActive { + s.cdpMonitor.Stop() + s.telemetrySession.Stop() + } + return oapi.PutTelemetry200JSONResponse(s.buildTelemetryResponse()), nil + } + + if wasActive { + // Replace config on the running session. + s.telemetrySession.UpdateConfig(cfg) + return oapi.PutTelemetry200JSONResponse(s.buildTelemetryResponse()), nil + } + + // Start a new telemetry session. + id := cuid2.Generate() + s.telemetrySession.Start(id, cfg) + + if err := s.cdpMonitor.Start(s.lifecycleCtx); err != nil { + // Roll back: clear the session so a retry can succeed. + s.telemetrySession.Stop() + logger.FromContext(ctx).Error("failed to start telemetry monitor", "err", err) + return oapi.PutTelemetry500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "failed to start telemetry"}}, nil + } + + return oapi.PutTelemetry201JSONResponse(s.buildTelemetryResponse()), nil +} + +// PatchTelemetry handles PATCH /telemetry. +// Updates the configuration of the active telemetry session. Returns 404 if not active. +// Setting all four categories to enabled:false stops the session (200, status stopped). +func (s *ApiService) PatchTelemetry(_ context.Context, req oapi.PatchTelemetryRequestObject) (oapi.PatchTelemetryResponseObject, error) { + s.monitorMu.Lock() + defer s.monitorMu.Unlock() + + if s.telemetrySession.ID() == "" { + return oapi.PatchTelemetry404JSONResponse{NotFoundErrorJSONResponse: oapi.NotFoundErrorJSONResponse{Message: "telemetry is not active"}}, nil + } + + if req.Body != nil { + cfg, allDisabled, err := telemetryConfigFromOAPI(req.Body) + if err != nil { + return oapi.PatchTelemetry400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: err.Error()}}, nil + } + if allDisabled { + // All categories disabled: stop the session. + s.cdpMonitor.Stop() + // Snapshot the final state before clearing the session ID so buildTelemetryResponse + // can still read it. Force status to stopped because cdpMonitor.Stop may + // tear down asynchronously, leaving IsRunning briefly true. + resp := s.buildTelemetryResponse() + resp.Status = oapi.TelemetryStateStatusStopped + s.telemetrySession.Stop() + return oapi.PatchTelemetry200JSONResponse(resp), nil + } + s.telemetrySession.UpdateConfig(cfg) + } + + return oapi.PatchTelemetry200JSONResponse(s.buildTelemetryResponse()), nil +} + +// buildTelemetryResponse constructs a TelemetryState response from the current session state. +func (s *ApiService) buildTelemetryResponse() oapi.TelemetryState { + cfg := s.telemetrySession.Config() + + status := oapi.TelemetryStateStatusStopped + if s.cdpMonitor.IsRunning() { + status = oapi.TelemetryStateStatusRunning + } + + return oapi.TelemetryState{ + Id: s.telemetrySession.ID(), + Status: status, + Config: telemetryConfigToOAPI(cfg), + Seq: int64(s.telemetrySession.Seq()), + CreatedAt: s.telemetrySession.CreatedAt(), + } +} + +// telemetryConfigFromOAPI converts an *oapi.BrowserTelemetryConfig to a telemetry.TelemetryConfig. +// Returns the config, a boolean indicating whether all user-facing categories are explicitly +// disabled (stop signal), and any validation error. +func telemetryConfigFromOAPI(cfg *oapi.BrowserTelemetryConfig) (telemetry.TelemetryConfig, bool, error) { + if cfg == nil || cfg.Browser == nil { + // No config provided: capture all categories. + return telemetry.TelemetryConfig{}, false, nil + } + + b := cfg.Browser + // A nil or omitted Enabled field defaults to true (capture the category). + isEnabled := func(c *oapi.BrowserTelemetryCategoryConfig) bool { + return c == nil || c.Enabled == nil || *c.Enabled + } + + consoleOn := isEnabled(b.Console) + networkOn := isEnabled(b.Network) + pageOn := isEnabled(b.Page) + interactionOn := isEnabled(b.Interaction) + + allDisabled := !consoleOn && !networkOn && !pageOn && !interactionOn + if allDisabled { + return telemetry.TelemetryConfig{}, true, nil + } + + cats := make([]events.EventCategory, 0, 5) + if consoleOn { + cats = append(cats, events.CategoryConsole) + } + if networkOn { + cats = append(cats, events.CategoryNetwork) + } + if pageOn { + cats = append(cats, events.CategoryPage) + } + if interactionOn { + cats = append(cats, events.CategoryInteraction) + } + // CategorySystem is always appended by TelemetrySession.Start/UpdateConfig; + // no need to include it here. + return telemetry.TelemetryConfig{Categories: cats}, false, nil +} + +// telemetryConfigToOAPI converts a telemetry.TelemetryConfig to an oapi.BrowserTelemetryConfig +// suitable for API responses. +func telemetryConfigToOAPI(cfg telemetry.TelemetryConfig) oapi.BrowserTelemetryConfig { + // Build a set of active categories for O(1) lookup. + active := make(map[events.EventCategory]struct{}, len(cfg.Categories)) + for _, c := range cfg.Categories { + active[c] = struct{}{} + } + sort.Slice(cfg.Categories, func(i, j int) bool { return cfg.Categories[i] < cfg.Categories[j] }) + + enabled := func(cat events.EventCategory) *oapi.BrowserTelemetryCategoryConfig { + _, on := active[cat] + return &oapi.BrowserTelemetryCategoryConfig{Enabled: &on} + } + + return oapi.BrowserTelemetryConfig{ + Browser: &oapi.BrowserTelemetryCategoriesConfig{ + Console: enabled(events.CategoryConsole), + Network: enabled(events.CategoryNetwork), + Page: enabled(events.CategoryPage), + Interaction: enabled(events.CategoryInteraction), + }, + } +} + diff --git a/server/cmd/api/main.go b/server/cmd/api/main.go index dfaa05b6..90bc636b 100644 --- a/server/cmd/api/main.go +++ b/server/cmd/api/main.go @@ -22,8 +22,8 @@ import ( serverpkg "github.com/kernel/kernel-images/server" "github.com/kernel/kernel-images/server/cmd/api/api" "github.com/kernel/kernel-images/server/cmd/config" - "github.com/kernel/kernel-images/server/lib/capturesession" "github.com/kernel/kernel-images/server/lib/chromedriverproxy" + "github.com/kernel/kernel-images/server/lib/telemetry" "github.com/kernel/kernel-images/server/lib/devtoolsproxy" "github.com/kernel/kernel-images/server/lib/events" "github.com/kernel/kernel-images/server/lib/logger" @@ -100,7 +100,7 @@ func main() { slogger.Error("failed to create event stream", "err", err) os.Exit(1) } - captureSession := capturesession.NewCaptureSession(eventStream) + telemetrySession := telemetry.NewTelemetrySession(eventStream) // Optional S2 storage sink. var s2Writer *events.S2StorageWriter @@ -119,7 +119,7 @@ func main() { upstreamMgr, stz, nekoAuthClient, - captureSession, + telemetrySession, eventStream, config.DisplayNum, ) From ad68b4b1683670027a6112301ad5900e54f503ac Mon Sep 17 00:00:00 2001 From: Archan Datta Date: Thu, 14 May 2026 08:40:26 -0300 Subject: [PATCH 06/28] feat: update all tests for telemetry rename and delete capturesession package --- server/cmd/api/api/api_test.go | 12 +- server/cmd/api/api/capture_session_test.go | 260 ---------------- server/cmd/api/api/display_test.go | 4 +- server/cmd/api/api/events_test.go | 22 +- server/cmd/api/api/telemetry_test.go | 292 ++++++++++++++++++ server/e2e/e2e_s2_storage_test.go | 29 +- server/lib/capturesession/capturesession.go | 154 --------- .../lib/capturesession/capturesession_test.go | 207 ------------- server/lib/events/events_test.go | 2 +- 9 files changed, 335 insertions(+), 647 deletions(-) delete mode 100644 server/cmd/api/api/capture_session_test.go create mode 100644 server/cmd/api/api/telemetry_test.go delete mode 100644 server/lib/capturesession/capturesession.go delete mode 100644 server/lib/capturesession/capturesession_test.go diff --git a/server/cmd/api/api/api_test.go b/server/cmd/api/api/api_test.go index 02835ab1..72afd164 100644 --- a/server/cmd/api/api/api_test.go +++ b/server/cmd/api/api/api_test.go @@ -11,8 +11,8 @@ import ( "log/slog" - "github.com/kernel/kernel-images/server/lib/capturesession" "github.com/kernel/kernel-images/server/lib/devtoolsproxy" + "github.com/kernel/kernel-images/server/lib/telemetry" "github.com/kernel/kernel-images/server/lib/events" "github.com/kernel/kernel-images/server/lib/nekoclient" oapi "github.com/kernel/kernel-images/server/lib/oapi" @@ -305,20 +305,20 @@ func newMockNekoClient(t *testing.T) *nekoclient.AuthClient { return client } -func newCaptureSession(t *testing.T) (*capturesession.CaptureSession, *events.EventStream) { +func newTelemetrySession(t *testing.T) (*telemetry.TelemetrySession, *events.EventStream) { t.Helper() es, err := events.NewEventStream(events.EventStreamConfig{RingCapacity: 64}) if err != nil { t.Fatal(err) } - return capturesession.NewCaptureSession(es), es + return telemetry.NewTelemetrySession(es), es } -// newSvc constructs an ApiService with a fresh capture session and event stream. +// newSvc constructs an ApiService with a fresh telemetry session and event stream. func newSvc(t *testing.T, mgr recorder.RecordManager) (*ApiService, error) { t.Helper() - cs, es := newCaptureSession(t) - return New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController(), newMockNekoClient(t), cs, es, 0) + ts, es := newTelemetrySession(t) + return New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController(), newMockNekoClient(t), ts, es, 0) } func TestApiService_PatchChromiumFlags(t *testing.T) { diff --git a/server/cmd/api/api/capture_session_test.go b/server/cmd/api/api/capture_session_test.go deleted file mode 100644 index 032cbb77..00000000 --- a/server/cmd/api/api/capture_session_test.go +++ /dev/null @@ -1,260 +0,0 @@ -package api - -import ( - "context" - "testing" - - oapi "github.com/kernel/kernel-images/server/lib/oapi" - "github.com/kernel/kernel-images/server/lib/recorder" - "github.com/kernel/kernel-images/server/lib/scaletozero" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestCaptureConfigFrom(t *testing.T) { - t.Run("nil body returns defaults", func(t *testing.T) { - cfg, err := captureConfigFrom(nil) - require.NoError(t, err) - assert.Empty(t, cfg.Categories) - }) - - t.Run("valid categories", func(t *testing.T) { - cats := []oapi.CaptureConfigCategories{oapi.CaptureConfigCategoriesConsole, oapi.CaptureConfigCategoriesNetwork} - body := &oapi.StartCaptureSessionRequest{ - Config: &oapi.CaptureConfig{Categories: &cats}, - } - cfg, err := captureConfigFrom(body) - require.NoError(t, err) - assert.Len(t, cfg.Categories, 2) - }) - - t.Run("invalid category returns error", func(t *testing.T) { - cats := []oapi.CaptureConfigCategories{"bogus"} - body := &oapi.StartCaptureSessionRequest{ - Config: &oapi.CaptureConfig{Categories: &cats}, - } - _, err := captureConfigFrom(body) - require.Error(t, err) - assert.Contains(t, err.Error(), "unknown category") - }) - - t.Run("nil config returns defaults", func(t *testing.T) { - body := &oapi.StartCaptureSessionRequest{} - cfg, err := captureConfigFrom(body) - require.NoError(t, err) - assert.Empty(t, cfg.Categories) - }) -} - -func TestStartCaptureSession(t *testing.T) { - ctx := context.Background() - - t.Run("success with no body", func(t *testing.T) { - svc := newTestService(t, newMockRecordManager()) - resp, err := svc.StartCaptureSession(ctx, oapi.StartCaptureSessionRequestObject{}) - require.NoError(t, err) - r201, ok := resp.(oapi.StartCaptureSession201JSONResponse) - require.True(t, ok) - assert.NotEmpty(t, r201.Id) - assert.NotZero(t, r201.CreatedAt) - // Status depends on cdpMonitor.IsRunning(); the stub monitor doesn't - // track state, so we only verify the field is populated. - assert.NotEmpty(t, r201.Status) - }) - - t.Run("success with config", func(t *testing.T) { - svc := newTestService(t, newMockRecordManager()) - cats := []oapi.CaptureConfigCategories{oapi.CaptureConfigCategoriesConsole} - resp, err := svc.StartCaptureSession(ctx, oapi.StartCaptureSessionRequestObject{ - Body: &oapi.StartCaptureSessionRequest{ - Config: &oapi.CaptureConfig{Categories: &cats}, - }, - }) - require.NoError(t, err) - r201, ok := resp.(oapi.StartCaptureSession201JSONResponse) - require.True(t, ok) - assert.NotEmpty(t, r201.Id) - }) - - t.Run("invalid category returns 400", func(t *testing.T) { - svc := newTestService(t, newMockRecordManager()) - cats := []oapi.CaptureConfigCategories{"badcat"} - resp, err := svc.StartCaptureSession(ctx, oapi.StartCaptureSessionRequestObject{ - Body: &oapi.StartCaptureSessionRequest{ - Config: &oapi.CaptureConfig{Categories: &cats}, - }, - }) - require.NoError(t, err) - assert.IsType(t, oapi.StartCaptureSession400JSONResponse{}, resp) - }) - - t.Run("duplicate returns 409", func(t *testing.T) { - svc := newTestService(t, newMockRecordManager()) - _, err := svc.StartCaptureSession(ctx, oapi.StartCaptureSessionRequestObject{}) - require.NoError(t, err) - - resp, err := svc.StartCaptureSession(ctx, oapi.StartCaptureSessionRequestObject{}) - require.NoError(t, err) - assert.IsType(t, oapi.StartCaptureSession409JSONResponse{}, resp) - }) -} - -func TestGetCaptureSession(t *testing.T) { - ctx := context.Background() - - t.Run("no session returns 404", func(t *testing.T) { - svc := newTestService(t, newMockRecordManager()) - resp, err := svc.GetCaptureSession(ctx, oapi.GetCaptureSessionRequestObject{}) - require.NoError(t, err) - assert.IsType(t, oapi.GetCaptureSession404JSONResponse{}, resp) - }) - - t.Run("active session returns 200", func(t *testing.T) { - svc := newTestService(t, newMockRecordManager()) - startResp, err := svc.StartCaptureSession(ctx, oapi.StartCaptureSessionRequestObject{}) - require.NoError(t, err) - started := startResp.(oapi.StartCaptureSession201JSONResponse) - - resp, err := svc.GetCaptureSession(ctx, oapi.GetCaptureSessionRequestObject{}) - require.NoError(t, err) - r200, ok := resp.(oapi.GetCaptureSession200JSONResponse) - require.True(t, ok) - assert.Equal(t, started.Id, r200.Id) - assert.Equal(t, started.CreatedAt, r200.CreatedAt) - }) -} - -func TestUpdateCaptureSession(t *testing.T) { - ctx := context.Background() - - t.Run("no session returns 404", func(t *testing.T) { - svc := newTestService(t, newMockRecordManager()) - resp, err := svc.UpdateCaptureSession(ctx, oapi.UpdateCaptureSessionRequestObject{ - Body: &oapi.UpdateCaptureSessionRequest{}, - }) - require.NoError(t, err) - assert.IsType(t, oapi.UpdateCaptureSession404JSONResponse{}, resp) - }) - - t.Run("update config", func(t *testing.T) { - svc := newTestService(t, newMockRecordManager()) - _, err := svc.StartCaptureSession(ctx, oapi.StartCaptureSessionRequestObject{}) - require.NoError(t, err) - - cats := []oapi.CaptureConfigCategories{oapi.CaptureConfigCategoriesConsole} - resp, err := svc.UpdateCaptureSession(ctx, oapi.UpdateCaptureSessionRequestObject{ - Body: &oapi.UpdateCaptureSessionRequest{ - Config: &oapi.CaptureConfig{Categories: &cats}, - }, - }) - require.NoError(t, err) - r200, ok := resp.(oapi.UpdateCaptureSession200JSONResponse) - require.True(t, ok) - require.NotNil(t, r200.Config.Categories) - assert.Len(t, *r200.Config.Categories, 1) - assert.Equal(t, oapi.CaptureConfigCategoriesConsole, (*r200.Config.Categories)[0]) - }) - - t.Run("empty body is no-op", func(t *testing.T) { - svc := newTestService(t, newMockRecordManager()) - startResp, err := svc.StartCaptureSession(ctx, oapi.StartCaptureSessionRequestObject{}) - require.NoError(t, err) - started := startResp.(oapi.StartCaptureSession201JSONResponse) - - resp, err := svc.UpdateCaptureSession(ctx, oapi.UpdateCaptureSessionRequestObject{ - Body: &oapi.UpdateCaptureSessionRequest{}, - }) - require.NoError(t, err) - r200, ok := resp.(oapi.UpdateCaptureSession200JSONResponse) - require.True(t, ok) - assert.Equal(t, started.Id, r200.Id) - }) - - t.Run("invalid category returns 400", func(t *testing.T) { - svc := newTestService(t, newMockRecordManager()) - _, err := svc.StartCaptureSession(ctx, oapi.StartCaptureSessionRequestObject{}) - require.NoError(t, err) - - cats := []oapi.CaptureConfigCategories{"invalid"} - resp, err := svc.UpdateCaptureSession(ctx, oapi.UpdateCaptureSessionRequestObject{ - Body: &oapi.UpdateCaptureSessionRequest{ - Config: &oapi.CaptureConfig{Categories: &cats}, - }, - }) - require.NoError(t, err) - assert.IsType(t, oapi.UpdateCaptureSession400JSONResponse{}, resp) - }) -} - -func TestStopCaptureSession(t *testing.T) { - ctx := context.Background() - - t.Run("no session returns 404", func(t *testing.T) { - svc := newTestService(t, newMockRecordManager()) - resp, err := svc.StopCaptureSession(ctx, oapi.StopCaptureSessionRequestObject{}) - require.NoError(t, err) - assert.IsType(t, oapi.StopCaptureSession404JSONResponse{}, resp) - }) - - t.Run("stop active session", func(t *testing.T) { - svc := newTestService(t, newMockRecordManager()) - startResp, err := svc.StartCaptureSession(ctx, oapi.StartCaptureSessionRequestObject{}) - require.NoError(t, err) - started := startResp.(oapi.StartCaptureSession201JSONResponse) - - resp, err := svc.StopCaptureSession(ctx, oapi.StopCaptureSessionRequestObject{}) - require.NoError(t, err) - r200, ok := resp.(oapi.StopCaptureSession200JSONResponse) - require.True(t, ok) - assert.Equal(t, started.Id, r200.Id) - assert.Equal(t, oapi.CaptureSessionStatusStopped, r200.Status) - }) - - t.Run("start succeeds after stop", func(t *testing.T) { - svc := newTestService(t, newMockRecordManager()) - startResp, err := svc.StartCaptureSession(ctx, oapi.StartCaptureSessionRequestObject{}) - require.NoError(t, err) - started := startResp.(oapi.StartCaptureSession201JSONResponse) - - _, err = svc.StopCaptureSession(ctx, oapi.StopCaptureSessionRequestObject{}) - require.NoError(t, err) - - resp, err := svc.StartCaptureSession(ctx, oapi.StartCaptureSessionRequestObject{}) - require.NoError(t, err) - r201, ok := resp.(oapi.StartCaptureSession201JSONResponse) - require.True(t, ok) - assert.NotEqual(t, started.Id, r201.Id) - }) -} - -// newMockRecordManager returns a minimal record manager for tests that don't -// exercise recording. -func newMockRecordManager() *mockRecordManager { - return &mockRecordManager{} -} - -type mockRecordManager struct{} - -func (m *mockRecordManager) RegisterRecorder(_ context.Context, _ recorder.Recorder) error { return nil } -func (m *mockRecordManager) DeregisterRecorder(_ context.Context, _ recorder.Recorder) error { - return nil -} -func (m *mockRecordManager) GetRecorder(_ string) (recorder.Recorder, bool) { return nil, false } -func (m *mockRecordManager) ListActiveRecorders(_ context.Context) []recorder.Recorder { return nil } -func (m *mockRecordManager) StopAll(_ context.Context) error { return nil } - -// newTestService builds an ApiService with minimal dependencies for capture session tests. -func newTestService(t *testing.T, mgr recorder.RecordManager) *ApiService { - t.Helper() - cs, es := newCaptureSession(t) - svc, err := New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController(), newMockNekoClient(t), cs, es, 0) - require.NoError(t, err) - svc.cdpMonitor = &stubCdpMonitor{} - return svc -} - -type stubCdpMonitor struct{} - -func (s *stubCdpMonitor) Start(_ context.Context) error { return nil } -func (s *stubCdpMonitor) Stop() {} -func (s *stubCdpMonitor) IsRunning() bool { return false } diff --git a/server/cmd/api/api/display_test.go b/server/cmd/api/api/display_test.go index 1ecefa3c..3a9d919c 100644 --- a/server/cmd/api/api/display_test.go +++ b/server/cmd/api/api/display_test.go @@ -34,8 +34,8 @@ func testFFmpegFactory(t *testing.T, tempDir string) recorder.FFmpegRecorderFact func newTestServiceWithFactory(t *testing.T, mgr recorder.RecordManager, factory recorder.FFmpegRecorderFactory) *ApiService { t.Helper() - cs, es := newCaptureSession(t) - svc, err := New(mgr, factory, newTestUpstreamManager(), scaletozero.NewNoopController(), newMockNekoClient(t), cs, es, 0) + ts, es := newTelemetrySession(t) + svc, err := New(mgr, factory, newTestUpstreamManager(), scaletozero.NewNoopController(), newMockNekoClient(t), ts, es, 0) require.NoError(t, err) return svc } diff --git a/server/cmd/api/api/events_test.go b/server/cmd/api/api/events_test.go index b47cdffc..d438c9b6 100644 --- a/server/cmd/api/api/events_test.go +++ b/server/cmd/api/api/events_test.go @@ -19,10 +19,10 @@ func TestEventLifecycle(t *testing.T) { ctx := context.Background() svc := newTestService(t, newMockRecordManager()) - // Start a capture session. - startResp, err := svc.StartCaptureSession(ctx, oapi.StartCaptureSessionRequestObject{}) + // Start a telemetry session. + startResp, err := svc.PutTelemetry(ctx, oapi.PutTelemetryRequestObject{}) require.NoError(t, err) - require.IsType(t, oapi.StartCaptureSession201JSONResponse{}, startResp) + require.IsType(t, oapi.PutTelemetry201JSONResponse{}, startResp) // Open an SSE stream (5s budget covers the three 2s selects below). streamCtx, streamCancel := context.WithTimeout(ctx, 5*time.Second) @@ -73,8 +73,18 @@ func TestEventLifecycle(t *testing.T) { t.Fatal("timed out waiting for test.event") } - // Stop the session. - stopResp, err := svc.StopCaptureSession(ctx, oapi.StopCaptureSessionRequestObject{}) + // Stop telemetry by disabling all categories. + f := false + stopResp, err := svc.PatchTelemetry(ctx, oapi.PatchTelemetryRequestObject{ + Body: &oapi.BrowserTelemetryConfig{ + Browser: &oapi.BrowserTelemetryCategoriesConfig{ + Console: &oapi.BrowserTelemetryCategoryConfig{Enabled: &f}, + Network: &oapi.BrowserTelemetryCategoryConfig{Enabled: &f}, + Page: &oapi.BrowserTelemetryCategoryConfig{Enabled: &f}, + Interaction: &oapi.BrowserTelemetryCategoryConfig{Enabled: &f}, + }, + }, + }) require.NoError(t, err) - assert.IsType(t, oapi.StopCaptureSession200JSONResponse{}, stopResp) + assert.IsType(t, oapi.PatchTelemetry200JSONResponse{}, stopResp) } diff --git a/server/cmd/api/api/telemetry_test.go b/server/cmd/api/api/telemetry_test.go new file mode 100644 index 00000000..87a6c0fc --- /dev/null +++ b/server/cmd/api/api/telemetry_test.go @@ -0,0 +1,292 @@ +package api + +import ( + "context" + "testing" + + "github.com/kernel/kernel-images/server/lib/events" + oapi "github.com/kernel/kernel-images/server/lib/oapi" + "github.com/kernel/kernel-images/server/lib/recorder" + "github.com/kernel/kernel-images/server/lib/scaletozero" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestTelemetryConfigFromOAPI(t *testing.T) { + t.Run("nil body returns defaults (all categories)", func(t *testing.T) { + cfg, allDisabled, err := telemetryConfigFromOAPI(nil) + require.NoError(t, err) + assert.False(t, allDisabled) + assert.Empty(t, cfg.Categories) + }) + + t.Run("nil browser key returns defaults", func(t *testing.T) { + cfg, allDisabled, err := telemetryConfigFromOAPI(&oapi.BrowserTelemetryConfig{}) + require.NoError(t, err) + assert.False(t, allDisabled) + assert.Empty(t, cfg.Categories) + }) + + t.Run("omitted enabled defaults to true", func(t *testing.T) { + cfg, allDisabled, err := telemetryConfigFromOAPI(&oapi.BrowserTelemetryConfig{ + Browser: &oapi.BrowserTelemetryCategoriesConfig{ + Console: &oapi.BrowserTelemetryCategoryConfig{}, // Enabled is nil → defaults to true + }, + }) + require.NoError(t, err) + assert.False(t, allDisabled) + assert.Contains(t, cfg.Categories, events.CategoryConsole) + }) + + t.Run("all false returns allDisabled=true", func(t *testing.T) { + f := false + _, allDisabled, err := telemetryConfigFromOAPI(&oapi.BrowserTelemetryConfig{ + Browser: &oapi.BrowserTelemetryCategoriesConfig{ + Console: &oapi.BrowserTelemetryCategoryConfig{Enabled: &f}, + Network: &oapi.BrowserTelemetryCategoryConfig{Enabled: &f}, + Page: &oapi.BrowserTelemetryCategoryConfig{Enabled: &f}, + Interaction: &oapi.BrowserTelemetryCategoryConfig{Enabled: &f}, + }, + }) + require.NoError(t, err) + assert.True(t, allDisabled) + }) + + t.Run("mixed enabled flags", func(t *testing.T) { + tr, f := true, false + cfg, allDisabled, err := telemetryConfigFromOAPI(&oapi.BrowserTelemetryConfig{ + Browser: &oapi.BrowserTelemetryCategoriesConfig{ + Console: &oapi.BrowserTelemetryCategoryConfig{Enabled: &tr}, + Network: &oapi.BrowserTelemetryCategoryConfig{Enabled: &f}, + }, + }) + require.NoError(t, err) + assert.False(t, allDisabled) + assert.Len(t, cfg.Categories, 3) // console + page + interaction (network=false, others default true) + }) +} + +func TestPutTelemetry(t *testing.T) { + ctx := context.Background() + + t.Run("creates session with no body (201)", func(t *testing.T) { + svc := newTestService(t, newMockRecordManager()) + resp, err := svc.PutTelemetry(ctx, oapi.PutTelemetryRequestObject{}) + require.NoError(t, err) + r201, ok := resp.(oapi.PutTelemetry201JSONResponse) + require.True(t, ok, "expected 201, got %T", resp) + assert.NotEmpty(t, r201.Id) + assert.NotZero(t, r201.CreatedAt) + assert.NotEmpty(t, r201.Status) + }) + + t.Run("creates session with config (201)", func(t *testing.T) { + svc := newTestService(t, newMockRecordManager()) + tr := true + resp, err := svc.PutTelemetry(ctx, oapi.PutTelemetryRequestObject{ + Body: &oapi.BrowserTelemetryConfig{ + Browser: &oapi.BrowserTelemetryCategoriesConfig{ + Console: &oapi.BrowserTelemetryCategoryConfig{Enabled: &tr}, + }, + }, + }) + require.NoError(t, err) + _, ok := resp.(oapi.PutTelemetry201JSONResponse) + require.True(t, ok, "expected 201, got %T", resp) + }) + + t.Run("replaces config on active session (200)", func(t *testing.T) { + svc := newTestService(t, newMockRecordManager()) + _, err := svc.PutTelemetry(ctx, oapi.PutTelemetryRequestObject{}) + require.NoError(t, err) + + tr := true + resp, err := svc.PutTelemetry(ctx, oapi.PutTelemetryRequestObject{ + Body: &oapi.BrowserTelemetryConfig{ + Browser: &oapi.BrowserTelemetryCategoriesConfig{ + Console: &oapi.BrowserTelemetryCategoryConfig{Enabled: &tr}, + }, + }, + }) + require.NoError(t, err) + _, ok := resp.(oapi.PutTelemetry200JSONResponse) + assert.True(t, ok, "expected 200 on replace, got %T", resp) + }) + + t.Run("all-false stops active session (200, status stopped)", func(t *testing.T) { + svc := newTestService(t, newMockRecordManager()) + _, err := svc.PutTelemetry(ctx, oapi.PutTelemetryRequestObject{}) + require.NoError(t, err) + + f := false + resp, err := svc.PutTelemetry(ctx, oapi.PutTelemetryRequestObject{ + Body: &oapi.BrowserTelemetryConfig{ + Browser: &oapi.BrowserTelemetryCategoriesConfig{ + Console: &oapi.BrowserTelemetryCategoryConfig{Enabled: &f}, + Network: &oapi.BrowserTelemetryCategoryConfig{Enabled: &f}, + Page: &oapi.BrowserTelemetryCategoryConfig{Enabled: &f}, + Interaction: &oapi.BrowserTelemetryCategoryConfig{Enabled: &f}, + }, + }, + }) + require.NoError(t, err) + r200, ok := resp.(oapi.PutTelemetry200JSONResponse) + require.True(t, ok, "expected 200, got %T", resp) + assert.Equal(t, oapi.TelemetryStateStatusStopped, r200.Status) + }) +} + +func TestGetTelemetry(t *testing.T) { + ctx := context.Background() + + t.Run("no session returns 404", func(t *testing.T) { + svc := newTestService(t, newMockRecordManager()) + resp, err := svc.GetTelemetry(ctx, oapi.GetTelemetryRequestObject{}) + require.NoError(t, err) + assert.IsType(t, oapi.GetTelemetry404JSONResponse{}, resp) + }) + + t.Run("active session returns 200", func(t *testing.T) { + svc := newTestService(t, newMockRecordManager()) + startResp, err := svc.PutTelemetry(ctx, oapi.PutTelemetryRequestObject{}) + require.NoError(t, err) + started := startResp.(oapi.PutTelemetry201JSONResponse) + + resp, err := svc.GetTelemetry(ctx, oapi.GetTelemetryRequestObject{}) + require.NoError(t, err) + r200, ok := resp.(oapi.GetTelemetry200JSONResponse) + require.True(t, ok) + assert.Equal(t, started.Id, r200.Id) + assert.Equal(t, started.CreatedAt, r200.CreatedAt) + }) +} + +func TestPatchTelemetry(t *testing.T) { + ctx := context.Background() + + t.Run("no session returns 404", func(t *testing.T) { + svc := newTestService(t, newMockRecordManager()) + resp, err := svc.PatchTelemetry(ctx, oapi.PatchTelemetryRequestObject{ + Body: &oapi.BrowserTelemetryConfig{}, + }) + require.NoError(t, err) + assert.IsType(t, oapi.PatchTelemetry404JSONResponse{}, resp) + }) + + t.Run("update config", func(t *testing.T) { + svc := newTestService(t, newMockRecordManager()) + _, err := svc.PutTelemetry(ctx, oapi.PutTelemetryRequestObject{}) + require.NoError(t, err) + + tr, f := true, false + resp, err := svc.PatchTelemetry(ctx, oapi.PatchTelemetryRequestObject{ + Body: &oapi.BrowserTelemetryConfig{ + Browser: &oapi.BrowserTelemetryCategoriesConfig{ + Console: &oapi.BrowserTelemetryCategoryConfig{Enabled: &tr}, + Network: &oapi.BrowserTelemetryCategoryConfig{Enabled: &f}, + Page: &oapi.BrowserTelemetryCategoryConfig{Enabled: &f}, + Interaction: &oapi.BrowserTelemetryCategoryConfig{Enabled: &f}, + }, + }, + }) + require.NoError(t, err) + r200, ok := resp.(oapi.PatchTelemetry200JSONResponse) + require.True(t, ok) + require.NotNil(t, r200.Config.Browser) + require.NotNil(t, r200.Config.Browser.Console) + assert.True(t, *r200.Config.Browser.Console.Enabled) + }) + + t.Run("nil body is no-op", func(t *testing.T) { + svc := newTestService(t, newMockRecordManager()) + startResp, err := svc.PutTelemetry(ctx, oapi.PutTelemetryRequestObject{}) + require.NoError(t, err) + started := startResp.(oapi.PutTelemetry201JSONResponse) + + resp, err := svc.PatchTelemetry(ctx, oapi.PatchTelemetryRequestObject{}) + require.NoError(t, err) + r200, ok := resp.(oapi.PatchTelemetry200JSONResponse) + require.True(t, ok) + assert.Equal(t, started.Id, r200.Id) + }) + + t.Run("all-false stops session", func(t *testing.T) { + svc := newTestService(t, newMockRecordManager()) + _, err := svc.PutTelemetry(ctx, oapi.PutTelemetryRequestObject{}) + require.NoError(t, err) + + f := false + resp, err := svc.PatchTelemetry(ctx, oapi.PatchTelemetryRequestObject{ + Body: &oapi.BrowserTelemetryConfig{ + Browser: &oapi.BrowserTelemetryCategoriesConfig{ + Console: &oapi.BrowserTelemetryCategoryConfig{Enabled: &f}, + Network: &oapi.BrowserTelemetryCategoryConfig{Enabled: &f}, + Page: &oapi.BrowserTelemetryCategoryConfig{Enabled: &f}, + Interaction: &oapi.BrowserTelemetryCategoryConfig{Enabled: &f}, + }, + }, + }) + require.NoError(t, err) + r200, ok := resp.(oapi.PatchTelemetry200JSONResponse) + require.True(t, ok, "expected 200, got %T", resp) + assert.Equal(t, oapi.TelemetryStateStatusStopped, r200.Status) + }) + + t.Run("put succeeds after patch-stop", func(t *testing.T) { + svc := newTestService(t, newMockRecordManager()) + startResp, err := svc.PutTelemetry(ctx, oapi.PutTelemetryRequestObject{}) + require.NoError(t, err) + firstID := startResp.(oapi.PutTelemetry201JSONResponse).Id + + f := false + _, err = svc.PatchTelemetry(ctx, oapi.PatchTelemetryRequestObject{ + Body: &oapi.BrowserTelemetryConfig{ + Browser: &oapi.BrowserTelemetryCategoriesConfig{ + Console: &oapi.BrowserTelemetryCategoryConfig{Enabled: &f}, + Network: &oapi.BrowserTelemetryCategoryConfig{Enabled: &f}, + Page: &oapi.BrowserTelemetryCategoryConfig{Enabled: &f}, + Interaction: &oapi.BrowserTelemetryCategoryConfig{Enabled: &f}, + }, + }, + }) + require.NoError(t, err) + + resp, err := svc.PutTelemetry(ctx, oapi.PutTelemetryRequestObject{}) + require.NoError(t, err) + r201, ok := resp.(oapi.PutTelemetry201JSONResponse) + require.True(t, ok) + assert.NotEqual(t, firstID, r201.Id) + }) +} + +// newMockRecordManager returns a minimal record manager for tests that don't +// exercise recording. +func newMockRecordManager() *mockRecordManager { + return &mockRecordManager{} +} + +type mockRecordManager struct{} + +func (m *mockRecordManager) RegisterRecorder(_ context.Context, _ recorder.Recorder) error { return nil } +func (m *mockRecordManager) DeregisterRecorder(_ context.Context, _ recorder.Recorder) error { + return nil +} +func (m *mockRecordManager) GetRecorder(_ string) (recorder.Recorder, bool) { return nil, false } +func (m *mockRecordManager) ListActiveRecorders(_ context.Context) []recorder.Recorder { return nil } +func (m *mockRecordManager) StopAll(_ context.Context) error { return nil } + +// newTestService builds an ApiService with minimal dependencies for telemetry tests. +func newTestService(t *testing.T, mgr recorder.RecordManager) *ApiService { + t.Helper() + ts, es := newTelemetrySession(t) + svc, err := New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController(), newMockNekoClient(t), ts, es, 0) + require.NoError(t, err) + svc.cdpMonitor = &stubCdpMonitor{} + return svc +} + +type stubCdpMonitor struct{} + +func (s *stubCdpMonitor) Start(_ context.Context) error { return nil } +func (s *stubCdpMonitor) Stop() {} +func (s *stubCdpMonitor) IsRunning() bool { return false } diff --git a/server/e2e/e2e_s2_storage_test.go b/server/e2e/e2e_s2_storage_test.go index 64d8c9a7..2ccf6e40 100644 --- a/server/e2e/e2e_s2_storage_test.go +++ b/server/e2e/e2e_s2_storage_test.go @@ -16,7 +16,7 @@ import ( ) // TestS2StorageWriter starts a headless container with S2 credentials, runs a -// capture session, and verifies that events land in the configured S2 stream. +// telemetry session, and verifies that events land in the configured S2 stream. // // Skips automatically when S2_BASIN, S2_ACCESS_TOKEN, or S2_STREAM are unset. func TestS2StorageWriter(t *testing.T) { @@ -58,23 +58,30 @@ func TestS2StorageWriter(t *testing.T) { require.NoError(t, err, "check tail before test") startSeq := checkResp.Tail.SeqNum - // Start a capture session. - startResp, err := client.StartCaptureSessionWithResponse(ctx, instanceoapi.StartCaptureSessionJSONRequestBody{}) + // Start a telemetry session. + startResp, err := client.PutTelemetryWithResponse(ctx, instanceoapi.PutTelemetryJSONRequestBody{}) require.NoError(t, err) - require.Equal(t, http.StatusCreated, startResp.StatusCode(), "start capture session: %s", string(startResp.Body)) + require.Equal(t, http.StatusCreated, startResp.StatusCode(), "put telemetry: %s", string(startResp.Body)) require.NotNil(t, startResp.JSON201) sessionID := startResp.JSON201.Id - t.Logf("capture session started: %s", sessionID) + t.Logf("telemetry session started: %s", sessionID) - // Let the session run briefly so at least one event is published (the - // session_started system event is emitted on session start). + // Let the session run briefly so at least one event is published. time.Sleep(500 * time.Millisecond) - // Stop the capture session. - stopResp, err := client.StopCaptureSessionWithResponse(ctx) + // Stop telemetry by disabling all categories. + f := false + stopResp, err := client.PatchTelemetryWithResponse(ctx, instanceoapi.PatchTelemetryJSONRequestBody{ + Browser: &instanceoapi.BrowserTelemetryCategoriesConfig{ + Console: &instanceoapi.BrowserTelemetryCategoryConfig{Enabled: &f}, + Network: &instanceoapi.BrowserTelemetryCategoryConfig{Enabled: &f}, + Page: &instanceoapi.BrowserTelemetryCategoryConfig{Enabled: &f}, + Interaction: &instanceoapi.BrowserTelemetryCategoryConfig{Enabled: &f}, + }, + }) require.NoError(t, err) - require.Equal(t, http.StatusOK, stopResp.StatusCode(), "stop capture session: %s", string(stopResp.Body)) - t.Log("capture session stopped") + require.Equal(t, http.StatusOK, stopResp.StatusCode(), "patch telemetry: %s", string(stopResp.Body)) + t.Log("telemetry session stopped") // Give the storage writer time to flush to S2 (batcher linger + network). time.Sleep(2 * time.Second) diff --git a/server/lib/capturesession/capturesession.go b/server/lib/capturesession/capturesession.go deleted file mode 100644 index 9a7fa106..00000000 --- a/server/lib/capturesession/capturesession.go +++ /dev/null @@ -1,154 +0,0 @@ -package capturesession - -import ( - "sync" - "time" - - "github.com/kernel/kernel-images/server/lib/events" -) - -// CaptureConfig holds caller-supplied capture preferences. All fields are -// optional; zero values mean "use server defaults" (all categories). -type CaptureConfig struct { - // Categories limits which event categories are captured. - // nil or empty includes all categories. - Categories []events.EventCategory -} - -// CaptureSession manages a capture session against a shared EventStream. -// It is responsible for (a) category-filtering Publish calls, (b) tracking -// session-scoped metadata (ID, config, timestamps), and (c) embedding -// capture_session_id into Event.Data before forwarding to the bus. -type CaptureSession struct { - es *events.EventStream - mu sync.Mutex - captureSessionID string - sessionStartSeq uint64 - categories map[events.EventCategory]struct{} - createdAt time.Time -} - -func NewCaptureSession(es *events.EventStream) *CaptureSession { - cats := make(map[events.EventCategory]struct{}, len(events.AllCategories)) - for _, c := range events.AllCategories { - cats[c] = struct{}{} - } - return &CaptureSession{es: es, categories: cats} -} - -// Start begins a new capture session with the given ID and config. Sequence -// numbers are process-monotonic and do not reset between sessions; a -// Last-Event-ID from any previous session is valid for resuming the SSE stream. -func (s *CaptureSession) Start(captureSessionID string, cfg CaptureConfig) { - s.mu.Lock() - defer s.mu.Unlock() - s.captureSessionID = captureSessionID - s.sessionStartSeq = s.es.Seq() - s.createdAt = time.Now() - cats := cfg.Categories - if len(cats) == 0 { - cats = events.AllCategories - } - s.categories = make(map[events.EventCategory]struct{}, len(cats)) - for _, c := range cats { - s.categories[c] = struct{}{} - } -} - -// publishLocked stamps capture_session_id into ev.Source.Metadata and forwards to the bus. -// Requires s.mu to be held. -func (s *CaptureSession) publishLocked(ev events.Event) events.Envelope { - if ev.Ts == 0 { - ev.Ts = time.Now().UnixMicro() - } - if ev.Source.Metadata == nil { - ev.Source.Metadata = make(map[string]string) - } - ev.Source.Metadata["capture_session_id"] = s.captureSessionID - return s.es.Publish(events.Envelope{Event: ev}) -} - -// Publish applies the category filter then forwards ev to the EventStream. -func (s *CaptureSession) Publish(ev events.Event) { - s.mu.Lock() - defer s.mu.Unlock() - if s.captureSessionID == "" { - return - } - if _, ok := s.categories[ev.Category]; !ok { - return - } - s.publishLocked(ev) -} - -// NewReader returns a Reader from the EventStream positioned after afterSeq. -func (s *CaptureSession) NewReader(afterSeq uint64) *events.Reader { - return s.es.NewReader(afterSeq) -} - -// ID returns the current capture session ID, or "" if no session is active. -func (s *CaptureSession) ID() string { - s.mu.Lock() - defer s.mu.Unlock() - return s.captureSessionID -} - -// Seq returns the sequence number of the last published event. -func (s *CaptureSession) Seq() uint64 { - return s.es.Seq() -} - -// SessionStartSeq returns the sequence number at which the current session -// started. Fresh SSE connections with no Last-Event-ID should begin here. -func (s *CaptureSession) SessionStartSeq() uint64 { - s.mu.Lock() - defer s.mu.Unlock() - return s.sessionStartSeq -} - -// Config returns the current capture configuration. -func (s *CaptureSession) Config() CaptureConfig { - s.mu.Lock() - defer s.mu.Unlock() - cats := make([]events.EventCategory, 0, len(s.categories)) - for c := range s.categories { - cats = append(cats, c) - } - return CaptureConfig{Categories: cats} -} - -// CreatedAt returns when the current session was started. -func (s *CaptureSession) CreatedAt() time.Time { - s.mu.Lock() - defer s.mu.Unlock() - return s.createdAt -} - -// UpdateConfig applies a new CaptureConfig to the running session. -func (s *CaptureSession) UpdateConfig(cfg CaptureConfig) { - s.mu.Lock() - defer s.mu.Unlock() - cats := cfg.Categories - if len(cats) == 0 { - cats = events.AllCategories - } - s.categories = make(map[events.EventCategory]struct{}, len(cats)) - for _, c := range cats { - s.categories[c] = struct{}{} - } -} - -// Active reports whether a capture session is currently running. -func (s *CaptureSession) Active() bool { - s.mu.Lock() - defer s.mu.Unlock() - return s.captureSessionID != "" -} - -// Stop ends the current session. The ring buffer is left intact so existing -// readers can finish draining. -func (s *CaptureSession) Stop() { - s.mu.Lock() - defer s.mu.Unlock() - s.captureSessionID = "" -} diff --git a/server/lib/capturesession/capturesession_test.go b/server/lib/capturesession/capturesession_test.go deleted file mode 100644 index 8f424e23..00000000 --- a/server/lib/capturesession/capturesession_test.go +++ /dev/null @@ -1,207 +0,0 @@ -package capturesession - -import ( - "context" - "encoding/json" - "strings" - "sync" - "testing" - "time" - - "github.com/kernel/kernel-images/server/lib/events" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func newTestEventStream(t *testing.T, capacity int) *events.EventStream { - t.Helper() - es, err := events.NewEventStream(events.EventStreamConfig{RingCapacity: capacity}) - require.NoError(t, err) - return es -} - -func newTestCaptureSession(t *testing.T) *CaptureSession { - t.Helper() - p := NewCaptureSession(newTestEventStream(t, 100)) - p.Start("test-session", CaptureConfig{}) - return p -} - -func readEnvelope(t *testing.T, r *events.Reader, ctx context.Context) events.Envelope { - t.Helper() - res, err := r.Read(ctx) - require.NoError(t, err) - require.NotNil(t, res.Envelope, "expected envelope, got drop") - return *res.Envelope -} - -func cdpEvent(typ string, cat events.EventCategory) events.Event { - return events.Event{Type: typ, Category: cat, Source: events.Source{Kind: events.KindCDP}} -} - -func sessionIDFromMetadata(t *testing.T, src events.Source) string { - t.Helper() - id, ok := src.Metadata["capture_session_id"] - require.True(t, ok, "capture_session_id not found in source.metadata") - return id -} - -func TestCaptureSession(t *testing.T) { - t.Run("concurrent_publish_seq_order", func(t *testing.T) { - const goroutines = 8 - const eventsEach = 50 - const total = goroutines * eventsEach - - p := NewCaptureSession(newTestEventStream(t, total)) - p.Start("test-concurrent", CaptureConfig{}) - reader := p.NewReader(0) - - var wg sync.WaitGroup - for i := 0; i < goroutines; i++ { - wg.Add(1) - go func() { - defer wg.Done() - for j := 0; j < eventsEach; j++ { - p.Publish(cdpEvent("console.log", events.CategoryConsole)) - } - }() - } - wg.Wait() - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - for want := uint64(1); want <= total; want++ { - env := readEnvelope(t, reader, ctx) - assert.Equal(t, want, env.Seq, "events must arrive in seq order") - } - }) - - t.Run("seq_continues_across_sessions", func(t *testing.T) { - p := NewCaptureSession(newTestEventStream(t, 100)) - p.Start("session-1", CaptureConfig{}) - p.Publish(cdpEvent("ev.one", events.CategorySystem)) - p.Publish(cdpEvent("ev.two", events.CategorySystem)) - - p.Start("session-2", CaptureConfig{}) - p.Publish(cdpEvent("ev.three", events.CategorySystem)) - - assert.Equal(t, uint64(2), p.SessionStartSeq(), "session-2 starts after seq 2") - - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) - defer cancel() - - reader := p.NewReader(2) - env := readEnvelope(t, reader, ctx) - assert.Equal(t, uint64(3), env.Seq) - assert.Equal(t, "session-2", sessionIDFromMetadata(t, env.Event.Source)) - assert.Equal(t, "ev.three", env.Event.Type) - }) - - t.Run("publish_increments_seq", func(t *testing.T) { - p := newTestCaptureSession(t) - reader := p.NewReader(0) - - for i := 0; i < 3; i++ { - p.Publish(events.Event{Type: "page.navigation", Category: events.CategoryPage, Source: events.Source{Kind: events.KindCDP}, Ts: 1}) - } - - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) - defer cancel() - - for want := uint64(1); want <= 3; want++ { - env := readEnvelope(t, reader, ctx) - assert.Equal(t, want, env.Seq, "expected seq %d got %d", want, env.Seq) - } - }) - - t.Run("publish_sets_ts", func(t *testing.T) { - p := newTestCaptureSession(t) - reader := p.NewReader(0) - - before := time.Now().UnixMicro() - p.Publish(events.Event{Type: "page.navigation", Category: events.CategoryPage, Source: events.Source{Kind: events.KindCDP}}) - after := time.Now().UnixMicro() - - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) - defer cancel() - - env := readEnvelope(t, reader, ctx) - assert.GreaterOrEqual(t, env.Event.Ts, before) - assert.LessOrEqual(t, env.Event.Ts, after) - }) - - t.Run("publish_writes_ring", func(t *testing.T) { - p := newTestCaptureSession(t) - - reader := p.NewReader(0) - p.Publish(events.Event{Type: "page.navigation", Category: events.CategoryPage, Source: events.Source{Kind: events.KindCDP}, Ts: 1}) - - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) - defer cancel() - - env := readEnvelope(t, reader, ctx) - assert.Equal(t, "page.navigation", env.Event.Type) - assert.Equal(t, events.CategoryPage, env.Event.Category) - }) - - t.Run("start_sets_capture_session_id_in_source_metadata", func(t *testing.T) { - p := newTestCaptureSession(t) - p.Start("test-uuid", CaptureConfig{}) - - reader := p.NewReader(0) - p.Publish(events.Event{Type: "page.navigation", Category: events.CategoryPage, Source: events.Source{Kind: events.KindCDP}, Ts: 1}) - - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) - defer cancel() - - env := readEnvelope(t, reader, ctx) - assert.Equal(t, "test-uuid", sessionIDFromMetadata(t, env.Event.Source)) - }) - - t.Run("data_unchanged_when_session_id_in_metadata", func(t *testing.T) { - p := newTestCaptureSession(t) - p.Start("merge-session", CaptureConfig{}) - - reader := p.NewReader(0) - p.Publish(events.Event{ - Type: "page.navigation", - Category: events.CategoryPage, - Source: events.Source{Kind: events.KindCDP}, - Ts: 1, - Data: json.RawMessage(`{"url":"https://example.com"}`), - }) - - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) - defer cancel() - - env := readEnvelope(t, reader, ctx) - assert.Equal(t, "merge-session", sessionIDFromMetadata(t, env.Event.Source)) - assert.JSONEq(t, `{"url":"https://example.com"}`, string(env.Event.Data)) - }) - - t.Run("truncation_applied", func(t *testing.T) { - p := newTestCaptureSession(t) - reader := p.NewReader(0) - - largeData := strings.Repeat("x", 1_100_000) - rawData, err := json.Marshal(map[string]string{"payload": largeData}) - require.NoError(t, err) - - p.Publish(events.Event{ - Type: "page.navigation", - Category: events.CategoryPage, - Source: events.Source{Kind: events.KindCDP}, - Ts: 1, - Data: json.RawMessage(rawData), - }) - - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) - defer cancel() - - env := readEnvelope(t, reader, ctx) - assert.True(t, env.Event.Truncated) - assert.True(t, json.Valid(env.Event.Data)) - }) -} - diff --git a/server/lib/events/events_test.go b/server/lib/events/events_test.go index a7eea87b..3280e7da 100644 --- a/server/lib/events/events_test.go +++ b/server/lib/events/events_test.go @@ -75,7 +75,7 @@ func TestEnvelopeSerialization(t *testing.T) { require.NoError(t, json.Unmarshal(b, &decoded)) assert.Equal(t, float64(1), decoded["seq"]) - assert.NotContains(t, decoded, "capture_session_id") + assert.NotContains(t, decoded, "telemetry_session_id") inner, ok := decoded["event"].(map[string]any) require.True(t, ok) assert.Equal(t, "console.log", inner["type"]) From e89a85f37df1d0a88c2f9bd0b935c5402d16ff20 Mon Sep 17 00:00:00 2001 From: Archan Datta Date: Thu, 14 May 2026 11:20:07 -0300 Subject: [PATCH 07/28] feat: rename PublishEvent and StreamEvents to PublishTelemetryEvent and StreamTelemetryEvents --- server/cmd/api/api/events.go | 32 +- server/lib/oapi/oapi.go | 579 ++++++++++++++++++----------------- server/openapi.yaml | 34 +- 3 files changed, 327 insertions(+), 318 deletions(-) diff --git a/server/cmd/api/api/events.go b/server/cmd/api/api/events.go index 20f25bce..50d11722 100644 --- a/server/cmd/api/api/events.go +++ b/server/cmd/api/api/events.go @@ -15,13 +15,13 @@ import ( oapi "github.com/kernel/kernel-images/server/lib/oapi" ) -// PublishEvent handles POST /telemetry/events. -// Injects a caller-supplied event into the event bus. Returns 400 if the event +// PublishTelemetryEvent handles POST /telemetry/events. +// Injects a caller-supplied event into the telemetry stream. Returns 400 if the event // fails validation. -func (s *ApiService) PublishEvent(_ context.Context, req oapi.PublishEventRequestObject) (oapi.PublishEventResponseObject, error) { +func (s *ApiService) PublishTelemetryEvent(_ context.Context, req oapi.PublishTelemetryEventRequestObject) (oapi.PublishTelemetryEventResponseObject, error) { body := req.Body if body == nil || body.Type == "" { - return oapi.PublishEvent400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "type is required"}}, nil + return oapi.PublishTelemetryEvent400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "type is required"}}, nil } ev := events.Event{Type: body.Type} @@ -29,7 +29,7 @@ func (s *ApiService) PublishEvent(_ context.Context, req oapi.PublishEventReques if body.Category != nil { cat := events.EventCategory(*body.Category) if !events.ValidCategory(cat) { - return oapi.PublishEvent400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "invalid category"}}, nil + return oapi.PublishTelemetryEvent400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "invalid category"}}, nil } ev.Category = cat } else { @@ -39,7 +39,7 @@ func (s *ApiService) PublishEvent(_ context.Context, req oapi.PublishEventReques if body.Source != nil { if body.Source.Kind != nil { if *body.Source.Kind == oapi.KernelApi { - return oapi.PublishEvent400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "source.kind kernel_api is reserved for server-generated events"}}, nil + return oapi.PublishTelemetryEvent400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "source.kind kernel_api is reserved for server-generated events"}}, nil } ev.Source.Kind = events.SourceKind(*body.Source.Kind) } @@ -55,20 +55,20 @@ func (s *ApiService) PublishEvent(_ context.Context, req oapi.PublishEventReques // re-marshal body.Data to normalize it into a canonical RawMessage byte slice. data, err := json.Marshal(body.Data) if err != nil { - return oapi.PublishEvent400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "invalid data"}}, nil + return oapi.PublishTelemetryEvent400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "invalid data"}}, nil } ev.Data = json.RawMessage(data) } env := s.eventStream.Publish(events.Envelope{Event: ev}) - return publishEventOKResponse{env}, nil + return publishTelemetryEventOKResponse{env}, nil } -// StreamEvents handles GET /telemetry/stream. -// Opens an SSE stream of telemetry event envelopes from the event bus ring buffer. +// StreamTelemetryEvents handles GET /telemetry/stream. +// Opens an SSE stream of telemetry event envelopes from the telemetry stream ring buffer. // Supports reconnection via the Last-Event-ID header. Emits a keepalive comment // frame every 15 s when no event arrives. -func (s *ApiService) StreamEvents(ctx context.Context, req oapi.StreamEventsRequestObject) (oapi.StreamEventsResponseObject, error) { +func (s *ApiService) StreamTelemetryEvents(ctx context.Context, req oapi.StreamTelemetryEventsRequestObject) (oapi.StreamTelemetryEventsResponseObject, error) { // Default to the current seq so fresh connections only see new events. // Seqs are process-monotonic; a Last-Event-ID from any prior session resumes correctly. afterSeq := s.eventStream.Seq() @@ -114,15 +114,15 @@ func (s *ApiService) StreamEvents(ctx context.Context, req oapi.StreamEventsRequ } }() - headers := oapi.StreamEvents200ResponseHeaders{XSSEContentType: "application/json"} - return oapi.StreamEvents200TexteventStreamResponse{Body: pr, Headers: headers}, nil + headers := oapi.StreamTelemetryEvents200ResponseHeaders{XSSEContentType: "application/json"} + return oapi.StreamTelemetryEvents200TexteventStreamResponse{Body: pr, Headers: headers}, nil } -// publishEventOKResponse serializes events.Envelope directly so the response +// publishTelemetryEventOKResponse serializes events.Envelope directly so the response // is identical in shape to the SSE stream frames. -type publishEventOKResponse struct{ env events.Envelope } +type publishTelemetryEventOKResponse struct{ env events.Envelope } -func (r publishEventOKResponse) VisitPublishEventResponse(w http.ResponseWriter) error { +func (r publishTelemetryEventOKResponse) VisitPublishTelemetryEventResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) return json.NewEncoder(w).Encode(r.env) diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index e69e2d14..78da5305 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -553,7 +553,7 @@ type Event struct { // Category Event category. Category *EventCategory `json:"category,omitempty"` - // Data Arbitrary event payload. + // Data Telemetry event payload. Data interface{} `json:"data,omitempty"` // Source Provenance of the event. @@ -893,7 +893,7 @@ type ProcessStreamEventEvent string // ProcessStreamEventStream Source stream of the data chunk. type ProcessStreamEventStream string -// PublishEventRequest Request body for publishing an event into the event bus. +// PublishEventRequest Request body for publishing an event into the telemetry stream. type PublishEventRequest struct { // Category Event category. Category *PublishEventRequestCategory `json:"category,omitempty"` @@ -1032,9 +1032,13 @@ type StopRecordingRequest struct { // TelemetryState Current telemetry session state and configuration. type TelemetryState struct { // Config Telemetry configuration for a browser session. Per-category capture settings. Omit a category or set enabled: true to capture it. Set enabled: false to exclude it. Omit the browser key entirely to capture all categories. Set all four categories to enabled: false to stop telemetry. - Config BrowserTelemetryConfig `json:"config"` - CreatedAt time.Time `json:"created_at"` - Id string `json:"id"` + Config BrowserTelemetryConfig `json:"config"` + + // CreatedAt When the telemetry session was started. + CreatedAt time.Time `json:"created_at"` + + // Id Unique identifier for the telemetry session. + Id string `json:"id"` // Seq Process-monotonic sequence number of the last published event. Does not reset between telemetry sessions. Seq int64 `json:"seq"` @@ -1202,8 +1206,8 @@ type DownloadRecordingParams struct { Id *string `form:"id,omitempty" json:"id,omitempty"` } -// StreamEventsParams defines parameters for StreamEvents. -type StreamEventsParams struct { +// StreamTelemetryEventsParams defines parameters for StreamTelemetryEvents. +type StreamTelemetryEventsParams struct { // LastEventID Resume after this sequence number. Omit or send 0 to start from the current position. Sequence numbers are process-monotonic, so a value from a previous telemetry session resumes correctly from that point. LastEventID *string `json:"Last-Event-ID,omitempty"` } @@ -1310,8 +1314,8 @@ type PatchTelemetryJSONRequestBody = BrowserTelemetryConfig // PutTelemetryJSONRequestBody defines body for PutTelemetry for application/json ContentType. type PutTelemetryJSONRequestBody = BrowserTelemetryConfig -// PublishEventJSONRequestBody defines body for PublishEvent for application/json ContentType. -type PublishEventJSONRequestBody = PublishEventRequest +// PublishTelemetryEventJSONRequestBody defines body for PublishTelemetryEvent for application/json ContentType. +type PublishTelemetryEventJSONRequestBody = PublishEventRequest // RequestEditorFn is the function signature for the RequestEditor callback function type RequestEditorFn func(ctx context.Context, req *http.Request) error @@ -1602,13 +1606,13 @@ type ClientInterface interface { PutTelemetry(ctx context.Context, body PutTelemetryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - // PublishEventWithBody request with any body - PublishEventWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + // PublishTelemetryEventWithBody request with any body + PublishTelemetryEventWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - PublishEvent(ctx context.Context, body PublishEventJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + PublishTelemetryEvent(ctx context.Context, body PublishTelemetryEventJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - // StreamEvents request - StreamEvents(ctx context.Context, params *StreamEventsParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // StreamTelemetryEvents request + StreamTelemetryEvents(ctx context.Context, params *StreamTelemetryEventsParams, reqEditors ...RequestEditorFn) (*http.Response, error) } func (c *Client) PatchChromiumFlagsWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { @@ -2595,8 +2599,8 @@ func (c *Client) PutTelemetry(ctx context.Context, body PutTelemetryJSONRequestB return c.Client.Do(req) } -func (c *Client) PublishEventWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPublishEventRequestWithBody(c.Server, contentType, body) +func (c *Client) PublishTelemetryEventWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPublishTelemetryEventRequestWithBody(c.Server, contentType, body) if err != nil { return nil, err } @@ -2607,8 +2611,8 @@ func (c *Client) PublishEventWithBody(ctx context.Context, contentType string, b return c.Client.Do(req) } -func (c *Client) PublishEvent(ctx context.Context, body PublishEventJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPublishEventRequest(c.Server, body) +func (c *Client) PublishTelemetryEvent(ctx context.Context, body PublishTelemetryEventJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPublishTelemetryEventRequest(c.Server, body) if err != nil { return nil, err } @@ -2619,8 +2623,8 @@ func (c *Client) PublishEvent(ctx context.Context, body PublishEventJSONRequestB return c.Client.Do(req) } -func (c *Client) StreamEvents(ctx context.Context, params *StreamEventsParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewStreamEventsRequest(c.Server, params) +func (c *Client) StreamTelemetryEvents(ctx context.Context, params *StreamTelemetryEventsParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewStreamTelemetryEventsRequest(c.Server, params) if err != nil { return nil, err } @@ -4712,19 +4716,19 @@ func NewPutTelemetryRequestWithBody(server string, contentType string, body io.R return req, nil } -// NewPublishEventRequest calls the generic PublishEvent builder with application/json body -func NewPublishEventRequest(server string, body PublishEventJSONRequestBody) (*http.Request, error) { +// NewPublishTelemetryEventRequest calls the generic PublishTelemetryEvent builder with application/json body +func NewPublishTelemetryEventRequest(server string, body PublishTelemetryEventJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewPublishEventRequestWithBody(server, "application/json", bodyReader) + return NewPublishTelemetryEventRequestWithBody(server, "application/json", bodyReader) } -// NewPublishEventRequestWithBody generates requests for PublishEvent with any type of body -func NewPublishEventRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { +// NewPublishTelemetryEventRequestWithBody generates requests for PublishTelemetryEvent with any type of body +func NewPublishTelemetryEventRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -4752,8 +4756,8 @@ func NewPublishEventRequestWithBody(server string, contentType string, body io.R return req, nil } -// NewStreamEventsRequest generates requests for StreamEvents -func NewStreamEventsRequest(server string, params *StreamEventsParams) (*http.Request, error) { +// NewStreamTelemetryEventsRequest generates requests for StreamTelemetryEvents +func NewStreamTelemetryEventsRequest(server string, params *StreamTelemetryEventsParams) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -5053,13 +5057,13 @@ type ClientWithResponsesInterface interface { PutTelemetryWithResponse(ctx context.Context, body PutTelemetryJSONRequestBody, reqEditors ...RequestEditorFn) (*PutTelemetryResponse, error) - // PublishEventWithBodyWithResponse request with any body - PublishEventWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PublishEventResponse, error) + // PublishTelemetryEventWithBodyWithResponse request with any body + PublishTelemetryEventWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PublishTelemetryEventResponse, error) - PublishEventWithResponse(ctx context.Context, body PublishEventJSONRequestBody, reqEditors ...RequestEditorFn) (*PublishEventResponse, error) + PublishTelemetryEventWithResponse(ctx context.Context, body PublishTelemetryEventJSONRequestBody, reqEditors ...RequestEditorFn) (*PublishTelemetryEventResponse, error) - // StreamEventsWithResponse request - StreamEventsWithResponse(ctx context.Context, params *StreamEventsParams, reqEditors ...RequestEditorFn) (*StreamEventsResponse, error) + // StreamTelemetryEventsWithResponse request + StreamTelemetryEventsWithResponse(ctx context.Context, params *StreamTelemetryEventsParams, reqEditors ...RequestEditorFn) (*StreamTelemetryEventsResponse, error) } type PatchChromiumFlagsResponse struct { @@ -6298,7 +6302,7 @@ func (r PutTelemetryResponse) StatusCode() int { return 0 } -type PublishEventResponse struct { +type PublishTelemetryEventResponse struct { Body []byte HTTPResponse *http.Response JSON200 *PublishedEnvelope @@ -6306,7 +6310,7 @@ type PublishEventResponse struct { } // Status returns HTTPResponse.Status -func (r PublishEventResponse) Status() string { +func (r PublishTelemetryEventResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -6314,20 +6318,20 @@ func (r PublishEventResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r PublishEventResponse) StatusCode() int { +func (r PublishTelemetryEventResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type StreamEventsResponse struct { +type StreamTelemetryEventsResponse struct { Body []byte HTTPResponse *http.Response } // Status returns HTTPResponse.Status -func (r StreamEventsResponse) Status() string { +func (r StreamTelemetryEventsResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -6335,7 +6339,7 @@ func (r StreamEventsResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r StreamEventsResponse) StatusCode() int { +func (r StreamTelemetryEventsResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -7050,30 +7054,30 @@ func (c *ClientWithResponses) PutTelemetryWithResponse(ctx context.Context, body return ParsePutTelemetryResponse(rsp) } -// PublishEventWithBodyWithResponse request with arbitrary body returning *PublishEventResponse -func (c *ClientWithResponses) PublishEventWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PublishEventResponse, error) { - rsp, err := c.PublishEventWithBody(ctx, contentType, body, reqEditors...) +// PublishTelemetryEventWithBodyWithResponse request with arbitrary body returning *PublishTelemetryEventResponse +func (c *ClientWithResponses) PublishTelemetryEventWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PublishTelemetryEventResponse, error) { + rsp, err := c.PublishTelemetryEventWithBody(ctx, contentType, body, reqEditors...) if err != nil { return nil, err } - return ParsePublishEventResponse(rsp) + return ParsePublishTelemetryEventResponse(rsp) } -func (c *ClientWithResponses) PublishEventWithResponse(ctx context.Context, body PublishEventJSONRequestBody, reqEditors ...RequestEditorFn) (*PublishEventResponse, error) { - rsp, err := c.PublishEvent(ctx, body, reqEditors...) +func (c *ClientWithResponses) PublishTelemetryEventWithResponse(ctx context.Context, body PublishTelemetryEventJSONRequestBody, reqEditors ...RequestEditorFn) (*PublishTelemetryEventResponse, error) { + rsp, err := c.PublishTelemetryEvent(ctx, body, reqEditors...) if err != nil { return nil, err } - return ParsePublishEventResponse(rsp) + return ParsePublishTelemetryEventResponse(rsp) } -// StreamEventsWithResponse request returning *StreamEventsResponse -func (c *ClientWithResponses) StreamEventsWithResponse(ctx context.Context, params *StreamEventsParams, reqEditors ...RequestEditorFn) (*StreamEventsResponse, error) { - rsp, err := c.StreamEvents(ctx, params, reqEditors...) +// StreamTelemetryEventsWithResponse request returning *StreamTelemetryEventsResponse +func (c *ClientWithResponses) StreamTelemetryEventsWithResponse(ctx context.Context, params *StreamTelemetryEventsParams, reqEditors ...RequestEditorFn) (*StreamTelemetryEventsResponse, error) { + rsp, err := c.StreamTelemetryEvents(ctx, params, reqEditors...) if err != nil { return nil, err } - return ParseStreamEventsResponse(rsp) + return ParseStreamTelemetryEventsResponse(rsp) } // ParsePatchChromiumFlagsResponse parses an HTTP response from a PatchChromiumFlagsWithResponse call @@ -9051,15 +9055,15 @@ func ParsePutTelemetryResponse(rsp *http.Response) (*PutTelemetryResponse, error return response, nil } -// ParsePublishEventResponse parses an HTTP response from a PublishEventWithResponse call -func ParsePublishEventResponse(rsp *http.Response) (*PublishEventResponse, error) { +// ParsePublishTelemetryEventResponse parses an HTTP response from a PublishTelemetryEventWithResponse call +func ParsePublishTelemetryEventResponse(rsp *http.Response) (*PublishTelemetryEventResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &PublishEventResponse{ + response := &PublishTelemetryEventResponse{ Body: bodyBytes, HTTPResponse: rsp, } @@ -9084,15 +9088,15 @@ func ParsePublishEventResponse(rsp *http.Response) (*PublishEventResponse, error return response, nil } -// ParseStreamEventsResponse parses an HTTP response from a StreamEventsWithResponse call -func ParseStreamEventsResponse(rsp *http.Response) (*StreamEventsResponse, error) { +// ParseStreamTelemetryEventsResponse parses an HTTP response from a StreamTelemetryEventsWithResponse call +func ParseStreamTelemetryEventsResponse(rsp *http.Response) (*StreamTelemetryEventsResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &StreamEventsResponse{ + response := &StreamTelemetryEventsResponse{ Body: bodyBytes, HTTPResponse: rsp, } @@ -9258,12 +9262,12 @@ type ServerInterface interface { // Set telemetry configuration // (PUT /telemetry) PutTelemetry(w http.ResponseWriter, r *http.Request) - // Publish an event into the telemetry bus + // Publish an event into the telemetry stream // (POST /telemetry/events) - PublishEvent(w http.ResponseWriter, r *http.Request) + PublishTelemetryEvent(w http.ResponseWriter, r *http.Request) // Stream telemetry events as Server-Sent Events // (GET /telemetry/stream) - StreamEvents(w http.ResponseWriter, r *http.Request, params StreamEventsParams) + StreamTelemetryEvents(w http.ResponseWriter, r *http.Request, params StreamTelemetryEventsParams) } // Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint. @@ -9582,15 +9586,15 @@ func (_ Unimplemented) PutTelemetry(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } -// Publish an event into the telemetry bus +// Publish an event into the telemetry stream // (POST /telemetry/events) -func (_ Unimplemented) PublishEvent(w http.ResponseWriter, r *http.Request) { +func (_ Unimplemented) PublishTelemetryEvent(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } // Stream telemetry events as Server-Sent Events // (GET /telemetry/stream) -func (_ Unimplemented) StreamEvents(w http.ResponseWriter, r *http.Request, params StreamEventsParams) { +func (_ Unimplemented) StreamTelemetryEvents(w http.ResponseWriter, r *http.Request, params StreamTelemetryEventsParams) { w.WriteHeader(http.StatusNotImplemented) } @@ -10601,11 +10605,11 @@ func (siw *ServerInterfaceWrapper) PutTelemetry(w http.ResponseWriter, r *http.R handler.ServeHTTP(w, r) } -// PublishEvent operation middleware -func (siw *ServerInterfaceWrapper) PublishEvent(w http.ResponseWriter, r *http.Request) { +// PublishTelemetryEvent operation middleware +func (siw *ServerInterfaceWrapper) PublishTelemetryEvent(w http.ResponseWriter, r *http.Request) { handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.PublishEvent(w, r) + siw.Handler.PublishTelemetryEvent(w, r) })) for _, middleware := range siw.HandlerMiddlewares { @@ -10615,13 +10619,13 @@ func (siw *ServerInterfaceWrapper) PublishEvent(w http.ResponseWriter, r *http.R handler.ServeHTTP(w, r) } -// StreamEvents operation middleware -func (siw *ServerInterfaceWrapper) StreamEvents(w http.ResponseWriter, r *http.Request) { +// StreamTelemetryEvents operation middleware +func (siw *ServerInterfaceWrapper) StreamTelemetryEvents(w http.ResponseWriter, r *http.Request) { var err error // Parameter object where we will unmarshal all parameters from the context - var params StreamEventsParams + var params StreamTelemetryEventsParams headers := r.Header @@ -10645,7 +10649,7 @@ func (siw *ServerInterfaceWrapper) StreamEvents(w http.ResponseWriter, r *http.R } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.StreamEvents(w, r, params) + siw.Handler.StreamTelemetryEvents(w, r, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -10925,10 +10929,10 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Put(options.BaseURL+"/telemetry", wrapper.PutTelemetry) }) r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/telemetry/events", wrapper.PublishEvent) + r.Post(options.BaseURL+"/telemetry/events", wrapper.PublishTelemetryEvent) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/telemetry/stream", wrapper.StreamEvents) + r.Get(options.BaseURL+"/telemetry/stream", wrapper.StreamTelemetryEvents) }) return r @@ -13100,51 +13104,51 @@ func (response PutTelemetry500JSONResponse) VisitPutTelemetryResponse(w http.Res return json.NewEncoder(w).Encode(response) } -type PublishEventRequestObject struct { - Body *PublishEventJSONRequestBody +type PublishTelemetryEventRequestObject struct { + Body *PublishTelemetryEventJSONRequestBody } -type PublishEventResponseObject interface { - VisitPublishEventResponse(w http.ResponseWriter) error +type PublishTelemetryEventResponseObject interface { + VisitPublishTelemetryEventResponse(w http.ResponseWriter) error } -type PublishEvent200JSONResponse PublishedEnvelope +type PublishTelemetryEvent200JSONResponse PublishedEnvelope -func (response PublishEvent200JSONResponse) VisitPublishEventResponse(w http.ResponseWriter) error { +func (response PublishTelemetryEvent200JSONResponse) VisitPublishTelemetryEventResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) return json.NewEncoder(w).Encode(response) } -type PublishEvent400JSONResponse struct{ BadRequestErrorJSONResponse } +type PublishTelemetryEvent400JSONResponse struct{ BadRequestErrorJSONResponse } -func (response PublishEvent400JSONResponse) VisitPublishEventResponse(w http.ResponseWriter) error { +func (response PublishTelemetryEvent400JSONResponse) VisitPublishTelemetryEventResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(400) return json.NewEncoder(w).Encode(response) } -type StreamEventsRequestObject struct { - Params StreamEventsParams +type StreamTelemetryEventsRequestObject struct { + Params StreamTelemetryEventsParams } -type StreamEventsResponseObject interface { - VisitStreamEventsResponse(w http.ResponseWriter) error +type StreamTelemetryEventsResponseObject interface { + VisitStreamTelemetryEventsResponse(w http.ResponseWriter) error } -type StreamEvents200ResponseHeaders struct { +type StreamTelemetryEvents200ResponseHeaders struct { XSSEContentType string } -type StreamEvents200TexteventStreamResponse struct { +type StreamTelemetryEvents200TexteventStreamResponse struct { Body io.Reader - Headers StreamEvents200ResponseHeaders + Headers StreamTelemetryEvents200ResponseHeaders ContentLength int64 } -func (response StreamEvents200TexteventStreamResponse) VisitStreamEventsResponse(w http.ResponseWriter) error { +func (response StreamTelemetryEvents200TexteventStreamResponse) VisitStreamTelemetryEventsResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "text/event-stream") if response.ContentLength != 0 { w.Header().Set("Content-Length", fmt.Sprint(response.ContentLength)) @@ -13341,12 +13345,12 @@ type StrictServerInterface interface { // Set telemetry configuration // (PUT /telemetry) PutTelemetry(ctx context.Context, request PutTelemetryRequestObject) (PutTelemetryResponseObject, error) - // Publish an event into the telemetry bus + // Publish an event into the telemetry stream // (POST /telemetry/events) - PublishEvent(ctx context.Context, request PublishEventRequestObject) (PublishEventResponseObject, error) + PublishTelemetryEvent(ctx context.Context, request PublishTelemetryEventRequestObject) (PublishTelemetryEventResponseObject, error) // Stream telemetry events as Server-Sent Events // (GET /telemetry/stream) - StreamEvents(ctx context.Context, request StreamEventsRequestObject) (StreamEventsResponseObject, error) + StreamTelemetryEvents(ctx context.Context, request StreamTelemetryEventsRequestObject) (StreamTelemetryEventsResponseObject, error) } type StrictHandlerFunc = strictnethttp.StrictHTTPHandlerFunc @@ -14911,11 +14915,11 @@ func (sh *strictHandler) PutTelemetry(w http.ResponseWriter, r *http.Request) { } } -// PublishEvent operation middleware -func (sh *strictHandler) PublishEvent(w http.ResponseWriter, r *http.Request) { - var request PublishEventRequestObject +// PublishTelemetryEvent operation middleware +func (sh *strictHandler) PublishTelemetryEvent(w http.ResponseWriter, r *http.Request) { + var request PublishTelemetryEventRequestObject - var body PublishEventJSONRequestBody + var body PublishTelemetryEventJSONRequestBody if err := json.NewDecoder(r.Body).Decode(&body); err != nil { sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) return @@ -14923,18 +14927,18 @@ func (sh *strictHandler) PublishEvent(w http.ResponseWriter, r *http.Request) { request.Body = &body handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.PublishEvent(ctx, request.(PublishEventRequestObject)) + return sh.ssi.PublishTelemetryEvent(ctx, request.(PublishTelemetryEventRequestObject)) } for _, middleware := range sh.middlewares { - handler = middleware(handler, "PublishEvent") + handler = middleware(handler, "PublishTelemetryEvent") } response, err := handler(r.Context(), w, r, request) if err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(PublishEventResponseObject); ok { - if err := validResponse.VisitPublishEventResponse(w); err != nil { + } else if validResponse, ok := response.(PublishTelemetryEventResponseObject); ok { + if err := validResponse.VisitPublishTelemetryEventResponse(w); err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { @@ -14942,25 +14946,25 @@ func (sh *strictHandler) PublishEvent(w http.ResponseWriter, r *http.Request) { } } -// StreamEvents operation middleware -func (sh *strictHandler) StreamEvents(w http.ResponseWriter, r *http.Request, params StreamEventsParams) { - var request StreamEventsRequestObject +// StreamTelemetryEvents operation middleware +func (sh *strictHandler) StreamTelemetryEvents(w http.ResponseWriter, r *http.Request, params StreamTelemetryEventsParams) { + var request StreamTelemetryEventsRequestObject request.Params = params handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.StreamEvents(ctx, request.(StreamEventsRequestObject)) + return sh.ssi.StreamTelemetryEvents(ctx, request.(StreamTelemetryEventsRequestObject)) } for _, middleware := range sh.middlewares { - handler = middleware(handler, "StreamEvents") + handler = middleware(handler, "StreamTelemetryEvents") } response, err := handler(r.Context(), w, r, request) if err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(StreamEventsResponseObject); ok { - if err := validResponse.VisitStreamEventsResponse(w); err != nil { + } else if validResponse, ok := response.(StreamTelemetryEventsResponseObject); ok { + if err := validResponse.VisitStreamTelemetryEventsResponse(w); err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { @@ -14971,189 +14975,190 @@ func (sh *strictHandler) StreamEvents(w http.ResponseWriter, r *http.Request, pa // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "ecdzq337/wYAAP//J6eTQj3jAAA=", + "H4sIAAAAAAAC/+y9eXMbN7Yo/lVQ/N0qS78hKXrLvHHq/uHIcqIXO1ZZ8mQmoZ8Cdh+SGKGBDoCmRLs8", + "n/0VDoBe2GhukrzMu1VTE1lqbGfDwVk/9hKZ5VKAMLr37GNPgc6l0ID/+IGmb+HPArQ5UUoq+6tECgPC", + "2B9pnnOWUMOkOPqXlsL+TidzyKj96b8UTHvPev/fUTX/kfurPnKzffr0qd9LQSeK5XaS3jO7IPEr9j71", + "e8dSTDlLPtfqYTm79KkwoATln2npsBw5B7UARfyH/d4v0ryUhUg/0z5+kYbgej37N/+5IwWTzI9llhcG", + "1PPEfh4QZXeSpsz+ivIzJXNQhlkCmlKuYXWF52RipyJyShI/HaE4nyZGEriBpDBAtJ1cGEY5Xw57/V5e", + "m/djzw+wPzZnf6NSUJASzrSxS7RnHpIT/IFJQbSRuSZSEDMHMmVKGwIWMnZBZiDTm+DYBIjFV8bEqRv5", + "sN8zyxx6z3pUKbpEgCr4s2AK0t6z38szvC+/k5N/gaO+H5S81qAugEMGRi2PqYGZVAy0pVE22xHmZ6AG", + "iZtiSUyYlCQ0N4WysDaGiZkmU6nIxC1NYGFP2gZ9IoWWHDZBpuMES7//T/0esyTvgHD7yQSYa6mubj9R", + "Tme3Ptun7TG63AufblChkPURa5RoJmYcGth1K7RRCIJOuCXCVe75dQ5mDoqYOdPleII/I6WkQ/ICprTg", + "BjnKqAIImxKZMWMgtQv5c0+k5EDFlqDYBwQX1TkjwAhErEFrJsWQNDhgle6H5E3GDKHVkaUdaogH1DN3", + "UiPLkcwMyXn9A9yjkzIJL1L3Bc5qRUvYzhUsiRVqCviyPh3lPKzNQLup7e+mslC1P+D8rRWtEKvQPhyL", + "Fr798nuSdSV0otg85iy5ei0LDdveByubK4xxEqCJYJySuL/aYwZxQa6Zmff6PRBFZsUoh6np9XuKzeb2", + "vxlLUw69fm9Ck6tevzeV6pqqtCZltVFMIK8nduuX7tery18sc8A7yn7jr5Haqqm8tv8s8p6fJrrAXPL0", + "8gqWOna8lE2ZowlErP2WpIUdijTjZq3dQ63Zm7dLvyeK7BJH+eWQUfEeWrnji2wCyh7OsMxRlYIcqGms", + "62e3YJ8BqiI37VP8gyRSqpQJahBa5QQkl5p5mLVnWrZn+uc+M63cqDc9O/X7OJHmE0lVelzTnranUQM3", + "pr3l40IpEMZu001O7HckKGgteljZLU4a3WxTqdhVvfIXwYpyVdetqCY5VU4/ctrYkFzMgfxht/IHmTLg", + "KdHAITGaXM9ZMh+LapYc1FSqrE+oSB2apHKvhtTSrhttgUCZVbzmEHaQU0UzMKD0cCxObmhi+JJIUf7d", + "jczsfgIT2A2RrNCGTIDkSi5YCmlMxDlWzqzM2KiztQSW1YIVnW03/IWis9XRmVzAdqNfywWsjs4VaG3F", + "xKbBZ/bDn2FZG6sTJTnfNPAcv6oPA3OZFErLjZfCOZhj/LA+mgPkGwfajyq9uEPKBhyXqnqNwoY1eVvH", + "bwPebuZLZKY6KEvQNHDbOHk4SExyV5NuOKa9Jy7gxpTgWeVyO3OUyxVQAy+YgsRItdzv8sxkGoHqm9wN", + "J2mYndgPyYFMDOXEnbJPYDgbkr8+fXrY1Or++vQpqozU2Cdp71nv//w+Gvz1/cfH/Sef/qsXgVVOzby9", + "iecTLbmVNtUm7Ieo9uDRVxY5Gv7/G0UmrhQD5gvgYOCMmvl+cNxwhLDxFJe5+42/hQTvvtl+u2cRVf40", + "tYomahj+NlVhkdpJyHOez6koMlAssVrvfJnPQazinw4+PB/8Nhr8bfD+L/8VPWz7YEznnG6r2zfPMwdU", + "5jov3NTNTdx3hAmSsxvgOqprKJgq0PNLRQ1sntJ/TezXduKfPpCDjC7t9SMKzu1rR0hDUjCQGKuJH0YX", + "vWZpjKBWV8PP1u4/CtrVG+h+FG4rNjuU7VLJdlp3TICmwOmyoYeOVlWVF/YTe/qMcc40JFKkmkzAXAOI", + "sBGraKOmoQ1VxlOvlf+Ecum1BMtdQ9yWYJnd6CiGk9Q/ES+ziDp+QdUMDDHSCsjwZWtv9nFpF3RvOAch", + "u5fMIvV6DoLoTEoz/2/7XvTvP3yQFkZm1LDEatz2DBOqIUXDEy6I8oWDmPlz0Bt3joej0WhUO9fT6MFu", + "88qwR9jpkRGXlKtmt99v+mT5vq7S55QpXeLOzJUsZnOrXHK3iRkTsyF5bVU9rzsSaggHqg15RHLJvDGq", + "3OnqlmsAyeiNt8E9qhvkHrVPs/aPDpcNGrZ4XSXjdxrIvMioGHB2BeQH+GABnhRqARU1I4av6dIdhDCh", + "DdDUgoozAVS5520uORLekPxqiQlXI9pAri9zUJcaZkhpjh0gv0Qmu8w0oQoImwmp4laYfq/xeeNIT3fk", + "SwV2jwtw+2ph8NTtos0NG/mzdc7mK3bU/Ywtt4S05faVo/XHwYuJSkx0b5C8dtsjDxt7fbjx2dl5uZc2", + "+xWlDbT2Vsb1WkP4MDr3ooRQ8/lXGQDRghsx4HojV3swztmwG5bqtzf6VoZWbyltmnH7Pb3UBrL4jUAN", + "jQje5nZJTpdc0nSIFCELlWx8UuGuz92nVmCpQtgjRHSiczBkskRa0M7NgkSAcpAa6h+g11STchIrsabe", + "iqfZByCcZczEecxEBPA7wW6cmcXQLHdMlSjpmWrFnGrf0l5DsEPc7mrGVfsoosZR4ndPoopH/JnlEIsP", + "LVbqhcPNFoqut0sd5Dt6IpRcgKAiKY08HVQKgb5bdHTFRAy3uBti/zgk494VKAH8kuZs3CNMEwWI8RSv", + "Y4f8wQwEKESy93WM6xa+JM17/do89k83BoR2hM5lQvllrmQCWkfpPQNDA83HIdR93QZQx9Q/576CM06X", + "16h/7eeH86PqdqFqSpLYh2Xb9ZN2WErP8d9H/5suqPsRJ2h43S7QUpQCmVNNaGLBZv/+wIqRB33yAM1m", + "N+aBsys98DbrB2RBFbOKtjcaZTmHZ2Tco9eUWWkxg+FMGnnwYG5Mrp8dHYH7ZpjI7MHh90SBKZQgtc8N", + "MxwODr8f98Yi9py1rCcLc6khadxA37VuoNdOT/NnROOFZVsmyCp/WxL8btTQ7R43NLvNtwwC//129KBx", + "wzuSgx1kmXKFCqrTtXk03G8r0sb+mvjLy76YKvhMKeOQxqCuyk23LUQLygvwmIQ0iHDc3AGbEiqWh06B", + "SEFF9nNuqEipSp1vl0yVzJzkqR2stR9tUlmYNZPJwuSF2Xa2Agl+nccN6tCGlPgh04LzZdyxVqeOsECM", + "QF4yDqdiKtuaCNOXKVPrd4WaK9OEVoac+PWXyfTS0n97uldWic/wKeKiFZBPGjdaSg0McHRMkkbljj2W", + "s2tNmNHkAIazYZ+Me6m6vlED+79xj0hFxr2Buh6ogf3fuHc4jK0gaGzfP1ANxP4p3FVTu6RUUUhsbQ8L", + "r9U2kbAPcDlZGojQybnVPZgg+OchGeE9FrbBQA83e0fwjH53jcX6gQ5qOPRA7yKnc1TzOnRQ+4HTA0ky", + "p2LWecdvQ350OoXE8sPWdLgvLsul9kXqblSySVOrq9/Hb0+eX5z0+r1f357if1+cvDrBH96e/PL89UlE", + "/4ipcv3up8orpg3iLXJG+x62Z2tDjAnHwJalQZhAiFuFzpRSKWJkeCVnne8bLme41rISvbU4qDaR1V5b", + "K1JJzspLymoewy5lAFX3yM1k73q7fLUj+3TIlUyLZEVhXyPeOt589aVjCENr3Zl3jb71QXttCb+tzzZ4", + "RPb31XbNsLWPtuUa282seYfmPfQV3dKwlzJt7DOnofM9vW9znt3zTua829u4vGCuDFr2RyrMChTjsnoT", + "eVb2wkBhxMi9yHTbmXYi1/0dTiloc7nJcQba2M0737lTGjb5nfo9rZJNEzu7ytZzrqqaYYF+7RQxCL25", + "qsulHd4iP9qHOUvIm59JCEduy3V5tZFqT0VqrwXQQZkeblak5VX0LGfUJHPv09oP411OrRfdzqxSUDx6", + "MtrdtfWi06U1JKdl0F6fFBpcmMaczeagDaELyrh9crshQSoqQPLxl6xXTb4b9R+P+o+e9h+O3se3iKC9", + "ZCmHzfiaepO3gmnhgtsUoNUNRTBnCyALBtdWCSm9mUcK8JhWNUwMW0Bc0ihAB9JlMlcyY3bvH7tXx0/J", + "sf+U0KkBVTt/UGsxHE+7eEBCU5o7O56Aa7QVNl7/LlzPwnIONJ0WvO+CCsNveAd5dvoSX3T6EEuyefxo", + "tJ1HcTWwZL+bd4O3L9y64dqyNIX3GLr4Vu7iOoladI/67luqgBia506/Wu9QWHORlhES2aYb9QqWBKNK", + "fES6u9G3v2Dj67/yfjI7u15mE8lxcVxoSE5oMid2CaLnsuApmQChtW+JLvJcKuNsITepNFLysTjQAOQf", + "Dx/iWZYZSWHKBCJRHw6Jt51pwoSLUB333qJFZdyzr+bzOZsa9+OxUdz99Jz7X718Ou4Nx85X5twpTDtn", + "X4IbpFxLu8tEZhN/ZWkfYOLm+4sJj3H8F672lws6wWl3AOiKtEboRuW1M8ye3EByZ+ZRao+XofNtKawc", + "EbLQ0ewENWv62H5/347gdjNRNSsyWPVtbqQqqi+VlE0PWfwYhfd9OXigP5/YoSRXbME4zKBD7FB9Wfig", + "4fVTUu3IwX5tpxIFx9sjyPh22K07e+Txi4DGm0cqoufAeQlyexcUIvpGS65joexSXVkerh6rB7T+WD/0", + "M3rLm1uEidgBNutcIBY7WflLnH1sJeCciAVTUuDDozR9Y5g3mPIq9qCvQaOi/Jb5ejeLdTcCuw3TDp0b", + "2fBWVmlaZ7oSYeU52ky49j1YpQB1PQaH0VcG3DBzGXeD+KMS+wmacuMzOCP15eS7J3Eb1XdPBiDs8JS4", + "T8mkmE6j3rpgpN52MlmY7sk+dWPvZ1bFju6GvnM2s5csUq/j4RXqbaJM4+cNoda7OHn7urd+3rqlzH/+", + "8+mrV71+7/SXi16/99O7s80GMr/2GiJ+i6rovrcJqrGUnF38czChyRWk3WBIJI+Q7C9wTQyojNmTJ5IX", + "mdCbAhX6PSWvN81lP9kx4gFn7buNroHYeU6vG1mCnL+Z9p79vinKuXV1f+qv2rUo59I+7S6NWW6+BZ/7", + "rwkluYYilYPy9AdnF/88XBWsVaJNmXaCES/2Ruq4LuNIO7X6l6XUFcS5B039EPaN0IqT2QGlrZXsZ/sv", + "0xYH71t43UOen9YMxnRiBRIl2s62jh/yWHzrm/MSWacv4qLW//2SRUNBMASAasv3kNbCImKXbGnHLQqW", + "xgUxter4JTVxO7GL/iijTcLO/bAdTMWdrGaoKfSuaYQ+2ETjYHfLdkulvLjMk8j5TrRhGYZRHJ+9IwXa", + "03NQCQhDZ/VbUGDA1oZr9CRcn4RNG7CaU3e3OnBt0lH6vQyyLmdatWMFGjFPMsisjuh2X/rZOm7wqLnl", + "rMKpaThvVCGERZ87NqTxu6gbsSnbM9P6BTXUSrJrxZwBdIX0nB+bibyI+OZSauhWikVaX2VzTFE57/uN", + "Z76Vvmi346OFtZ2ufUL7hQHRRSRVeCF+QPznw962JhV/FAW0cpTuojudn4RgOKIgV6CthBKzEoM+AEEq", + "wtkUkmXCoTNpezdslo61iljsKaIqKMT9dK+aW2p5NC0rRKOmthINpSB1kzNNxjhw3OtiWbv/zqAx9+fg", + "yUIQJPNCXNU37ONByiiTLZm4mHCm54j/29khJjJd4tWUuyktJVARACA8d1fBnxXK1kV/Bi3bh232v96A", + "0P3jQT9LRKRHNKQnYgFc5ru6PC4wq8ANJaVWYqTVj2qBQAH7IWZxTdzkRhC5PMA/O2+yQSaFNFKwpDR9", + "EneFVxukiZLaeQ+sEEJjgmcjF2Q5JO80OKvTK6rNAFcenL7wtv3Cu9CtsPNc6IUP0y7o35kHW5GvOzxW", + "7BmDnIqhziVfgYqHSE2ZQHhvo9pVGVZhVJdit9FG5nTW9q91mSpW+3sjzn9rRbTarR+052ZXwI0Kcn2f", + "MZifJwpA6Lk0b2G2TZLzdr60n5wPrUx4m3nDzpr0sA7vyq/oVdlloi0jLdxcD+wTMx9wmNobTQm4VezF", + "DnNG3dsBCv0A2E0o28dLpEpEb8hUbhJG9Fpt5jPv6nnnhl7erHdW/SQV+yAFZsviWoRmshBmSFzIzQL8", + "7zXBSNk+ETCjjd9bPMS1EbeDDclxf7c7TrZYP5XXIrJ8kccXv010SZlRvb2jYhNXUOMKDNTSvptL7c4U", + "O0+5dchHKxd+R6nF0hTEhhhgF5pS+f38oI1xC/67jm2/ZBzOQGUMy+Ho/fY/U7LI48ZE/JMPr1Tkx4ZF", + "Ztc43kiS+ndPnhzulpMur0XMd2X3in9Cb1XY77uO/W4T83k9lxrtHQG2zkXtvKEYJpDumy++Jga3Xlxh", + "xwQcWmioR+S74lE5JJb309IfsqNDpe7dx6oKMX9KPfehEQg32siU9cWjALEqzEv9KzXJnZYAKOszoIkD", + "S6XEsxcs47IFbLZFl9zu5yPlWL7cIj6pM9oKIXDLQgJTRTOIRxO9rXTb8JFF8TS3HLsApVgKOqTVeQgc", + "1nH+aLTJsB0184ZAjYiBtqbAhjJfd1LOADcdCPpUnDsC7namVvuoOxNDUOl66KwFSEZvMNiefYBT8fqH", + "7h1gZLb2KQKvf9gSI6vZ5Q+3jBY6NzK/LaFJlYCdZzO/nGYZpIwa4EtX6sw+JmVhyEzRBKYFJ3peGKsF", + "DcmFfTZmGPOGdkAmMGhDqSI3kJIFS0EisOI+nF3qaDgOthu6xyIapS3kPNic97Du12xCjk2ciRrT7Bq1", + "86K1Hn31jp3qxpXFFF2hl/hr9Ndgw2vv75ru7BqJI++dYH8WEJMdrUXj/vX9zCOeUDjVpmWwIS8kaKwh", + "okCDKdPqWxvSw7V2j1GXa6LwJR5XnQ+WVPMu70PrAe9n6gcS6HszSg2jsUtotRrSzk+z29UMsQ8Xo+QV", + "6Gief9T5GQfkXmHxIV6n2kdIC6iFx1MyZTdW0bInGY5Fq6DmgbZqKdUYr44JEUdpqPhy6GpDGlnFk46F", + "DwAkZpnbtdDYRgWR4eqsrdeAFDnA3/33yMLFR+0fDseiVnsCC9pZqC1zSC3Yr6VKB/amSZ2J3EeUlSdn", + "wig6sF+5BfVYWDkjqCmUvcWFAeX+nFtFVLu8YLc3l35v97IGdWMRz72PVuizpIhwxRJjTtzMJUYtuuJ4", + "HYlZ8tKq7gmsp0WsKTqniibG3gbLXBImLCdYGWYVpO9JxrShV+AeBVgnD3PZEGYTmlzpnCZQEQEZDckb", + "wZcuowl0DALkQDMOwvBlA05jUX2GtHHoQFXe9qPhwyjVB6/sttUJf1XMQFlPcT9GX4+thr8yJBKGBfct", + "q/gJKw47Cy9mgPee9X7G5H5ymtEZaPL87LTX7y1Aabed0fDhcIRvyRwEzVnvWe/xcDR87NPo8CBHIZz8", + "aMrpLLwjkshD4jWoGWBoOH7pSABumEafnhSg+6TI7Q1HViaNBKQvGCW6yEEtmJYq7TsmwxT3QhjGEXLl", + "1y9gcSEl12Tc40wbsBfCuIdpa5wJsBQjJ748wgSmUoVca1StfeYEEpPFodOKU3xKmmQeVnmJ53eoAG1+", + "kOlyp2rlK/phgOaKShOO5GBoJMkQrD739/dxbzC4YlJfuajlwSBl2oqlwSwvxr33h/sHGrsNxcmq+s7y", + "p8s1qGroPxqNIjYP3L/Dd4qaWHk0j+zVDPBP/d4TN1NMFStXPFot2f+p33u6zbhmvXss/l5kGVVLe805", + "uiy3yGkhkrlHgt283zMOq6g3l5wl1eu6mysKDWoQqntWywCWRFJMA8GplqR61pQezwkt/zy0VNUfi43s", + "QnbnlrHYlV2OQWEVqwAFklFBZy5k31UVIUxMFdVGFQlWgUYqJiehyMi5r07dH4tcyZvlAMscQVrO6M5R", + "zh/IEHXc4xdnRyE5UYpDvH8mXCZXkI4FutgCLDdy9llA4/7MHb8aYhrVNsgfkp9DKoj/k6AZ6LE48AkH", + "/jY9lvKKgfZwHPcOEV5YTMJb6eblDO63w7E4ByChlAhSMlQ7Gc6knHEoCfvIWc/KdKnwe19IxiVcuOYJ", + "miXPCzN/swD1kzH5SajA7mAQ3TAaBezH+l0+UzQFXY7yl+prenMshXAahz4DdWbppPfs8aN+70zmRa6f", + "cy6vIX0p1TvFNdqJ22VSeu8/3ZVcC7TyzYq2VbKzZ+mWcEXOJU0HZV0gPaAiHYRvrdiTOqLovMNhWPpY", + "KpJZCVJOQT6wnFCVzNnCcjjcGCx6buaQkUKkoMjRXGZw5ETIUbX00bgYjR4nlhXwJ+iPhX1gKivjsvoK", + "Tm4zsYeiUUrOsfiMioaDVykY9XORvvUwXieTsoIbllNljuwzehDCTbp0jgqU3fla1TdW+XDoR5hghDA1", + "jeTr5vTxshQvJbc4RU+EkSTnNAFfTiagazesrxidng9+o4MPo8HfhpeD9x8f9h89fRp3mHxg+eWU8cgW", + "f6sIMpRm9MFHhchdKHvFPuWuD7Bqd8g1y6hgU9AGr+jDumFjwoTlxE1afbk9X98j9jJZq8DVsLufFvcw", + "FpBWUoMjBUj7EWnnuKZkDqxGRtMvLfdaIqjEZo3ID6i2Akkf1oVgeUQvDf1b+mgSdLy41DsJaXSCyJVy", + "oa1GQGi49YXsn5+dkoRyPiTP/V/x5neeXavO1FsF+XqUc8nTECF3k/BCW+K16k+faEmEJBJ9MBj7Skph", + "o0lChbNRcKALwIpjm3oFlWXwA+AJK9POnR86lLfH2lfDsUArtUuYmxYcdYhk7rkqBRfAb9+FlWERY7Nd", + "PQW72hUsXb8BD66xCDbxnC7tLD4mjyhZiHRgFMuJVR1F4kIIAfNLRcoWLC0o99PEJG+k69Mt1MC1RuTu", + "/lL7KiM4ZUdBrS/JeyUjrOmEVafpFTZbaXUQmK2JuKrJwT3hK9JFYU80ubrToUdEYOsviqFzlhXc5Qs5", + "rqt3gYkbEls4cuaqIyvqu9H0Fmh6XDNtxaB1V+hqNkCJtb8r+5j4JfGeavHNraFrD+0sy2WgecvK1wVO", + "tA12w7NpnLwn0o9bQPclf7R6+uQCbI5QYuGrEVi/OoNsMKZvga+ytUgcTWUg1T1hqN20ZGvk3Mn6tco3", + "MT5zMV4LptmEcWaW5Wv5q8H4Tyz1Ofjyul7eq4nmZtOcuNaHpUVQa8FowiBQXXX/fumkspobDUW17LLK", + "OK9Q3y4vViv+z9giFFV3iikHqgF1q3rFyg3l6GMaT9lc4Z5Is90+aE+5YSf6Sq5L3EpVOM2hiSIeVihm", + "BsYRzGXZ1atTSPwIplHk7j6vx3g1vTjvYlCFO2l5iLuA4o9gGiXAvebhhEVYaRvlo9mNKg7cstjePZF5", + "u8/VrbRDDwV7si9L6q9DDbkGdsKtWEZRVpJGb4OxRgewNXLUF+qq1kE3PsrMmr+/DOF0dvIqlrhWbWgs", + "YjWEhuQlyl+7MQVzEO7d3C5W1CcaYCzsZuIFhwg1lRl9xsxwqgBS0FdG5kOpZkc39v9yJY08unn40P2Q", + "c8rEkZsshelw7uS5D2GbSyGVrgd+DDgsoDqvfVH7AMXEgwJDUbU3oTksyDTq8fAVsO6JHVqd2/bkBkQo", + "UsvXpC24O75uS0K63ILwdZnu0S2qLugVVGkh96UxtrJbPnkcrb1xWEZncJS7bKxqpc3WzdbFUm2A4KRf", + "FKHHoS8tqRAUoto2oNN3I4wLMZe3QxY+t4UvrfZ2JC1vh3wb+ztT0/FqkrSpLTbsfI0ybl4NbCTO+BY5", + "gnA5w7Qaw5IrTQ6END6py5k4axREJjCnC2ZJmi7Jgqrl98QUaKXzHcECA4eYqYk089pRnLsx5PFg1o+3", + "XXpXd7/eiiSE/KCnp2HSPCjnQFW4WuDQxX2gFckFCwH3KeBeFP4RYsOcAWMw8I1efyGDgQu6GhHnQXAK", + "ufMh/BGTkOchfeae2K/eoHJP6ejJ6yuxIbnNVLqCQw81VjPeQZsL+csdwtEHXN4TXtrdLW9h5HBBhF/N", + "rYUdntGo0Y0F36ivEcESCZXwtTjvS3mI1J79zAaNZjfHyPX1zlswQmfDRmD5bdD8ZPS3zePsvjhL7j4u", + "oOM4ljSm+sgFQ1+WJQaRTIqYNb7Z6/W+TPLxjrL7ejer1Ccf9P31sK47KaEYT1mBP+DFNTfdAi+u++p9", + "46XdnHZvm0+JEnfE9Hac9WTzuF+keSkLkd6hsQh3Xm/jsYq3EIawBmUvXSjA140tTGz9D0AU4qPEkbwW", + "XNLUctflB4YJXDMwsYRBUyihCSW/nZ65DLVa9Iiruoro0mXFkCoJtd45ZQX/fv0XTP3Gcox2CV3lsbLg", + "1k2oQ0iL1aDDobAIrx33ZwEoDlzQTkjHbdJAvx5JtCm99/1Ol7OH660elBbq4Yxl5hoSVh3A3yJdemTV", + "RQihgdD8kTvoVZt0C4I1VA0/aEMODFW10KcsGF4wdt/OdbiWrsdiDWGT37RJiZxOQWmi2UxgcyxM65hS", + "bUCVC2KtRJGORQr1X9mfqXJFgj6w3D+IaTJnsMBOJGBWZ0E2ins9alxlYfStsFX/Y7uudnlctA4OyU9s", + "Ngfl/lW25yE6o5xDiV5NJoUhhl4B4VLMQA3HYuAwoc0z8m+LbTcFedgnPqnGIhZScvDvx6PR4OloRF7/", + "cKQP7UCfNNQc+LhPJpRTkVhVyo48QgyQg38/fFob6xDXHPrXfsBnGPJ0NPhfjUGtbT7s42/LEY9Ggyfl", + "iA6M1KjlEqfp1dFR1QsLP1UFwTyoev3a39yW8Qcdq/a2q1T03HsrsXjhefv/MdFomscuxaOVX5chL8qL", + "xaZoKPt0bSsTNrZC+xpu2N10wqpXWZugUMurNUL7BsnmRzCNVm6hMm8LeyXZcKYN6um6k26qjnL7XSbf", + "JqVUp46QSvV84y7v7xukFYyER8y7IN02bWAPsq7nW+iadY9u57t4uqGbtzJ3fIN4whNgnyTMLVjHzApo", + "Wj66o7z8Fmjqn9zbsTIuFlRCO//Xws0yMWAGVT3YW+kSKPqjMZLfGLFgRGb5lLEDS+LQ4AT9Za3EVSd3", + "tyuN3V+AX0dJs70z12oVvHw43jeIyHMwkTatNdQdYfUzPWd5iWGXutLttMUcwpDhgplaLi9DKuIyrDj4", + "C8GHwSjIpJcBLk502JHRFdSDO0vhKjWSjhysfbou1ioSeIV2uz6MQaDumunks5zWt1Zcn6uOULizLCfE", + "Upng9K2Lukji09Tra3V2CKbNtQmcFA0vyG+uGZHL1WRGV7bNVmhYrKtnjDmcdfPOWGNX0k/rhe9qWajl", + "w9nI7fignlh4i6y/dfywJ2H/xvKKrGsI/I8hclpPJl4h0Ra9e+PKBoLf1TTaxRdjsZkxNptIGxbRsVgx", + "iXanEnsb550xV7CqtOMe5rBqeimvkI3M0P9yTGt/yi8rultfCKlqlcHBqQh4cVbDXak8xfJQTdjvDROF", + "sXSWJafBAL8ZVOMON1XqWpEXAQ/3Ii6eexj+h4uMVXLtEBvXq8m+Ky+BWj3W+3oDREq+bo/bPQsT4bEv", + "d6s1WHHltQfHxtKP7bcmHpPcdf2ML0Rs7jB1I7VPghazmiaG0Dr6GED+yZcIBJcAuEpvMq/IbcVIgYYH", + "b2nwdocSj+tsD5tNDZGGOgFRWG3xW0fUORZctSfCbPqI8WgVSUcu/rTTlOQaIr3UJ+6zz4irVbOQgRvj", + "dhu1B23yB5zj09Z3UonEc1cdTeS09hb28bnYa4GmeOqPvX8Mzs9PBj41d3AR7VjzGlJGfSXDKTZMwUZF", + "Ptz3YFWIHTY8d8FL1xJ1Eafcp2+RTF3rmFUo+3RCJ3ZLirWP+fVBRpjwuo3B80VN+aIt4+dn9Hu/qYp9", + "hmr6nYX0Sb3k6HdPnnRtE6vPd2xrbfl9x3zb3Pi3NMfuac0o062/9WsUzVL25gzxkFWoFpczfVQBNu6i", + "kzPfoK5DDq8QhO+/tY5yg6DxJF7VjorWHY4vM5Wcy+t45EGjA1GtRv4qmqXgy6oiHpsSt3fCNPFbW8OY", + "3bfKLuvUzh5frfrg0jfa632xG+2VnG15lVnC+qpvr9jNYDeNBQTt0o5Bck6X19i858iXiNmidJGaMKOo", + "WpKzcrRvVios9ynQ81pvDUTNjSF0RpnQ7iU+ccXSiS/MPRZSEC4TyudSm2d/e/TokSuJjLPOqSY0CR2J", + "H+R0Bg/65IGf94ErLPXAT/mg6hfvM6BU2Q3ThBmrzWEZKlMo4Qo71ysYxQwnHgTVuY/d7XAfL7vWWl8o", + "6yGyD+xJGssLr4D7NZYaqo6AKT3nuHNHERHi9AziZBJyR/dDv9at+95yZ9v9wD8vHTR20EUBVaUw5b/5", + "KkpMJTLLrJTQS5HMlRSy0KGiVEAwNuDeiGFs+n2/KG70i/8yOK63No9dha5X+VeGW7oGuR+rLuifjq5Y", + "Mzs3iuifGaZ5bn6X1/qrr1MJNzRP3/6xsBdC7Wm+yipAb37+JuMLrChhM/vSNDL0h15DcQo0+wAbae6t", + "++w/hurcef6H7u4uQAm7W1FydvHPwcSVKd1MfFU7nOjzt2zR7lvdfF7au+d7zB0qdoX5v3yTUcpVQ3p/", + "vG7Up2wLnQa/+o+ROnicL6w/uS106U8/LLEsrjO/fbMWt+rmI47O1tKhLMwmQ1wFPFmYtRa5LySPbmFZ", + "Ks9mh21pYwrQlYXJC4NWDs6mkCwTDv/jQLk/B0qNqmVhVgxmZZvLo8oJG5euLnO46gB/n4narUaU3XWb", + "uhqafrEU7S9U26JM7M4VLBi+GUNTy3qPzBbWfXJZpxQL2Wd1xK/1npVOq7KlZhU9MSRYUklm9qpoVkoq", + "Qh087xUoh3c5slxHw6gba1NTzs2iEQF2lOVPbp1OUGux61yPDQFX/nXwkgnsKDl4HmuixjLQhma5FXLX", + "ocemqk3tBg/JjwVVVBhw8XITIG9fHj9+/Phvw/UekMZWzl08yl47CR0+99yI3cqj0aN1jM2sJGOcEyas", + "aJsp0LpPcqwVS4xaOtsnlsZXTXC/BaOWg+dTE2sifl7MZi5XFEvWYneVWsPfqrOJWvrWm+Uh1vX7/Rbv", + "jTLh1JW50siLrjnhFhKFM3d7dOYPvvWMrW9b+7XMB1h3oYTVXKZnK8i+xa+hKYwqd3lnCXaU8/q0TbC1", + "ugtFQu/u+/KNdxuP3r0P17GoFwLfYIUohEBZIbGSa76DpxR1WZeDIqcvsL0I1g2cMW2wAwqWg7MSZNjG", + "sszXIbnWg/vecBzp8727ehUaD3/RYnxG5s3rx4FbJ5SDkR9AySPfK3JtCV73VrAT/f21615gZ8DCH5LY", + "WfoWuVSlHJ8vU/LTxcUZMYpOpywhUhBmhuSYch5qhTw/O3Xl55i2U17b2+qaXgFhhkwgoYUG8k6wK0Wn", + "xv01dPVLfNH0K/AFgJehiEHIOfn762ipD3fMc3vyC/kbKNnbJqwRvx8YObCnJB5W6Z0g5zSFLJfGXRt+", + "ZoQrBKjWQDRsIw7Eery9BW2kwq7bKqPcTV0epazyWa3Rt/JXXqMKgdBsbsZpDajRsJSDQ6gbW6o5f39N", + "hPSlRIgASLXXbebAU0It2qJednF73IC4J9S4iTdhpmxkvrHQTr3Yea39eUdzehKGPRk9IWxaG8FcP3Vs", + "wRaF6o9gyub091lFfqVdf6z6SPOY+2puLbtoBxjt/B2dV98C9ppzWLCPLgUkoQZmWPCvDvkQP+LgG+mc", + "T47dOOYb21cpPWVciZNLE5kuy0ebKSl1a9QS3xkT1aOpLFTYMXPJp36+Z745uuvYVU12oPwyj0Yjl6jk", + "zMjhhjrsbIXapJ57aH7l4i7KZep1Nj+fCXcX8m1SyBdKmI5V7GxRaasSqY/4XYnfwQhK7Som1DijY54h", + "QfWvTl1s6kiVY3O/sI8azT08/L45dxOGbBoZMzrcj+ip6GTXLdigxo8j3Bfn9UWpglIBcN3vVvnViyO+", + "XCOUzwrz5djq62QjTx3IR7umKd3hvXTbB9mdVDHoZOCGjlHLNonrfqfiX5AYZAj8tGocXr8qFdDMhV36", + "jzShWrOZANexSEgjhde4mUgUUKyuHtozEuESIC0vTKmwo2SBN5zlHJmDCL6NpGrXHOeJCWe6um6cu+Se", + "fIZuLVziS/kM3RYgPREL4DKPkiZuEKNg89BQOg/j9qfSZv8KN98WRLJKfi2/3qqBG4TrQ7UA0nRxVTM7", + "Eh6SE5rMyVTRzMX9YrUJqTLyB0ufkY8a/vw0HouUGvqMfAQPsIEFuP39eCz+sFdFgyDLbgMJaD0oydjX", + "ZkRJrUCDfWmaa7BU6q4I/T2h5BXVZoDAH5y+cG9d+8YMPoPyOmGaLChnru28Al1k4W0b+OqFwitJgY8c", + "ci1nZjTXQUH8g6V/kCkDnj5D/c891IEtIHV/Y9qVajBzKshDQudA06CXcrtRbbdvP+0Hh941KMvODJNz", + "y0aDk2I6BTUkx5zhV745jlE0uYrMZnk4BQOJwf0OyUsM8a6xsbtBhVyBl2uUWy4b9PNSYlgUYO6ABsAq", + "1p4KYo0XEIxNmbCxqNpbiwggdGqw8Q3Tq8JqSN5kzGCPNxApGbkU7eimQ7eCbekLe+xS11LfE05FNm19", + "BGkGNEmkUpBgdr/bAbVLM2GGVblKZ6KvPDsNoH+57ImtBNmrLYTAN5dYsXoCQjU5Rzfd4NzSjqdWO/r/", + "BgAA//97m+fzauQAAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/openapi.yaml b/server/openapi.yaml index eb2b3596..720ade4f 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -1215,8 +1215,9 @@ paths: description: > Creates or replaces the telemetry configuration. Starts telemetry if not already active (returns 201); replaces the configuration if active (returns 200). - Setting all four categories to enabled: false stops telemetry (returns 200 - with status stopped). + Setting all four categories to enabled: false stops an active telemetry session + (returns 200 with status stopped). Returns 400 if all categories are disabled + and telemetry is not currently active. operationId: putTelemetry requestBody: required: false @@ -1244,9 +1245,10 @@ paths: patch: summary: Update active telemetry configuration description: > - Updates the configuration of the active telemetry session. Returns 404 if - telemetry is not active. Setting all four categories to enabled: false - stops telemetry (returns 200 with status stopped). + Replaces the entire category configuration of the active telemetry session. + Categories not specified in the request body default to enabled. + Returns 404 if telemetry is not active. Setting all four categories to + enabled: false stops telemetry (returns 200 with status stopped). operationId: patchTelemetry requestBody: required: true @@ -1267,12 +1269,12 @@ paths: $ref: "#/components/responses/NotFoundError" /telemetry/events: post: - summary: Publish an event into the telemetry bus + summary: Publish an event into the telemetry stream description: > - Injects an event into the global event bus. + Injects an event into the telemetry stream. The event is assigned a monotonically increasing sequence number and fanned out to all open stream connections. - operationId: publishEvent + operationId: publishTelemetryEvent requestBody: required: true content: @@ -1292,7 +1294,7 @@ paths: get: summary: Stream telemetry events as Server-Sent Events description: > - Opens a live SSE stream of telemetry events from the global event bus. + Opens a live SSE stream of telemetry events. Each frame has the form `id: {seq}\ndata: {envelope-json}\n\n`. Sequence numbers are process-monotonic and do not reset between sessions; a Last-Event-ID from any previous session is valid for resuming the stream. @@ -1301,7 +1303,7 @@ paths: Clients should track the last-seen `id` to detect gaps. Fresh connections with no Last-Event-ID start from the current sequence and only see new events. - operationId: streamEvents + operationId: streamTelemetryEvents parameters: - in: header name: Last-Event-ID @@ -1376,7 +1378,7 @@ components: source: $ref: "#/components/schemas/EventSource" data: - description: Arbitrary event payload. + description: Telemetry event payload. truncated: type: boolean description: Set by the server when the data field was truncated to fit the size limit. @@ -1402,7 +1404,7 @@ components: additionalProperties: false PublishEventRequest: type: object - description: Request body for publishing an event into the event bus. + description: Request body for publishing an event into the telemetry stream. required: - type properties: @@ -1461,15 +1463,15 @@ components: console: $ref: "#/components/schemas/BrowserTelemetryCategoryConfig" description: Console output (log, warn, error) and uncaught exceptions. - network: - $ref: "#/components/schemas/BrowserTelemetryCategoryConfig" - description: HTTP request/response metadata. page: $ref: "#/components/schemas/BrowserTelemetryCategoryConfig" description: Page lifecycle events (navigation, load, layout shifts, LCP). interaction: $ref: "#/components/schemas/BrowserTelemetryCategoryConfig" description: User interaction events (clicks, keydowns, scroll). + network: + $ref: "#/components/schemas/BrowserTelemetryCategoryConfig" + description: HTTP request/response metadata. additionalProperties: false BrowserTelemetryCategoryConfig: type: object @@ -1491,6 +1493,7 @@ components: properties: id: type: string + description: Unique identifier for the telemetry session. status: type: string enum: @@ -1508,6 +1511,7 @@ components: created_at: type: string format: date-time + description: When the telemetry session was started. additionalProperties: false StartRecordingRequest: type: object From 38f5aa13b00934e81b4b0a0613579ae07361ba6e Mon Sep 17 00:00:00 2001 From: Archan Datta Date: Thu, 14 May 2026 11:20:09 -0300 Subject: [PATCH 08/28] fix: return 400 when starting telemetry with all categories disabled --- server/cmd/api/api/telemetry.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/server/cmd/api/api/telemetry.go b/server/cmd/api/api/telemetry.go index 961f2f7e..da0d099a 100644 --- a/server/cmd/api/api/telemetry.go +++ b/server/cmd/api/api/telemetry.go @@ -2,7 +2,6 @@ package api import ( "context" - "sort" "github.com/nrednav/cuid2" oapi "github.com/kernel/kernel-images/server/lib/oapi" @@ -39,12 +38,15 @@ func (s *ApiService) PutTelemetry(ctx context.Context, req oapi.PutTelemetryRequ wasActive := s.telemetrySession.ID() != "" if allDisabled { - // All categories disabled: stop any running session. - if wasActive { - s.cdpMonitor.Stop() - s.telemetrySession.Stop() + if !wasActive { + return oapi.PutTelemetry400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "cannot start telemetry with all categories disabled"}}, nil } - return oapi.PutTelemetry200JSONResponse(s.buildTelemetryResponse()), nil + // All categories disabled: stop the running session. + s.cdpMonitor.Stop() + resp := s.buildTelemetryResponse() + resp.Status = oapi.TelemetryStateStatusStopped + s.telemetrySession.Stop() + return oapi.PutTelemetry200JSONResponse(resp), nil } if wasActive { @@ -169,7 +171,6 @@ func telemetryConfigToOAPI(cfg telemetry.TelemetryConfig) oapi.BrowserTelemetryC for _, c := range cfg.Categories { active[c] = struct{}{} } - sort.Slice(cfg.Categories, func(i, j int) bool { return cfg.Categories[i] < cfg.Categories[j] }) enabled := func(cat events.EventCategory) *oapi.BrowserTelemetryCategoryConfig { _, on := active[cat] From e3642a278109586dd57c7f4ba665dfd456c403af Mon Sep 17 00:00:00 2001 From: Archan Datta Date: Thu, 14 May 2026 11:30:33 -0300 Subject: [PATCH 09/28] review: address bot feedback --- server/cmd/api/api/telemetry.go | 2 +- server/lib/oapi/oapi.go | 376 ++++++++++++++++---------------- server/openapi.yaml | 3 +- 3 files changed, 192 insertions(+), 189 deletions(-) diff --git a/server/cmd/api/api/telemetry.go b/server/cmd/api/api/telemetry.go index da0d099a..328820b3 100644 --- a/server/cmd/api/api/telemetry.go +++ b/server/cmd/api/api/telemetry.go @@ -80,7 +80,7 @@ func (s *ApiService) PatchTelemetry(_ context.Context, req oapi.PatchTelemetryRe return oapi.PatchTelemetry404JSONResponse{NotFoundErrorJSONResponse: oapi.NotFoundErrorJSONResponse{Message: "telemetry is not active"}}, nil } - if req.Body != nil { + if req.Body != nil && req.Body.Browser != nil { cfg, allDisabled, err := telemetryConfigFromOAPI(req.Body) if err != nil { return oapi.PatchTelemetry400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: err.Error()}}, nil diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index 78da5305..c863014c 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -898,7 +898,7 @@ type PublishEventRequest struct { // Category Event category. Category *PublishEventRequestCategory `json:"category,omitempty"` - // Data Telemetry event payload + // Data Telemetry event payload. Data interface{} `json:"data,omitempty"` // Source Provenance of the event. @@ -1041,11 +1041,13 @@ type TelemetryState struct { Id string `json:"id"` // Seq Process-monotonic sequence number of the last published event. Does not reset between telemetry sessions. - Seq int64 `json:"seq"` + Seq int64 `json:"seq"` + + // Status Current status of the telemetry session. Status TelemetryStateStatus `json:"status"` } -// TelemetryStateStatus defines model for TelemetryState.Status. +// TelemetryStateStatus Current status of the telemetry session. type TelemetryStateStatus string // TypeTextRequest defines model for TypeTextRequest. @@ -14975,190 +14977,190 @@ func (sh *strictHandler) StreamTelemetryEvents(w http.ResponseWriter, r *http.Re // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "BgAA//97m+fzauQAAA==", + "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", + "/x8AAP//D+KNdqTkAAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/openapi.yaml b/server/openapi.yaml index 720ade4f..0bd3a86e 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -1424,7 +1424,7 @@ components: source: $ref: "#/components/schemas/EventSource" data: - description: Telemetry event payload + description: Telemetry event payload. additionalProperties: false PublishedEnvelope: type: object @@ -1496,6 +1496,7 @@ components: description: Unique identifier for the telemetry session. status: type: string + description: Current status of the telemetry session. enum: - running - stopped From a7de080b47d626d74c04fed31815cbe94d82aec7 Mon Sep 17 00:00:00 2001 From: Archan Datta Date: Thu, 14 May 2026 11:46:43 -0300 Subject: [PATCH 10/28] review: rename telemetrySessionID, fix import order, patch merge semantics --- server/cmd/api/api/api.go | 2 +- server/cmd/api/api/events_test.go | 8 ++-- server/cmd/api/api/telemetry.go | 67 +++++++++++++++++++++++++++---- server/lib/telemetry/telemetry.go | 24 +++++------ 4 files changed, 77 insertions(+), 24 deletions(-) diff --git a/server/cmd/api/api/api.go b/server/cmd/api/api/api.go index ffea2836..5d49f2bc 100644 --- a/server/cmd/api/api/api.go +++ b/server/cmd/api/api/api.go @@ -11,8 +11,8 @@ import ( "time" "github.com/kernel/kernel-images/server/lib/cdpmonitor" - "github.com/kernel/kernel-images/server/lib/telemetry" "github.com/kernel/kernel-images/server/lib/devtoolsproxy" + "github.com/kernel/kernel-images/server/lib/telemetry" "github.com/kernel/kernel-images/server/lib/events" "github.com/kernel/kernel-images/server/lib/logger" "github.com/kernel/kernel-images/server/lib/nekoclient" diff --git a/server/cmd/api/api/events_test.go b/server/cmd/api/api/events_test.go index d438c9b6..24129844 100644 --- a/server/cmd/api/api/events_test.go +++ b/server/cmd/api/api/events_test.go @@ -27,9 +27,9 @@ func TestEventLifecycle(t *testing.T) { // Open an SSE stream (5s budget covers the three 2s selects below). streamCtx, streamCancel := context.WithTimeout(ctx, 5*time.Second) defer streamCancel() - streamResp, err := svc.StreamEvents(streamCtx, oapi.StreamEventsRequestObject{}) + streamResp, err := svc.StreamTelemetryEvents(streamCtx, oapi.StreamTelemetryEventsRequestObject{}) require.NoError(t, err) - r200, ok := streamResp.(oapi.StreamEvents200TexteventStreamResponse) + r200, ok := streamResp.(oapi.StreamTelemetryEvents200TexteventStreamResponse) require.True(t, ok) // Drain SSE frames into a channel. @@ -55,11 +55,11 @@ func TestEventLifecycle(t *testing.T) { }() // Publish an event. - resp, err := svc.PublishEvent(ctx, oapi.PublishEventRequestObject{ + resp, err := svc.PublishTelemetryEvent(ctx, oapi.PublishTelemetryEventRequestObject{ Body: &oapi.PublishEventRequest{Type: "test.event"}, }) require.NoError(t, err) - r200pub, ok := resp.(publishEventOKResponse) + r200pub, ok := resp.(publishTelemetryEventOKResponse) require.True(t, ok, "expected 200 response") assert.Equal(t, "test.event", r200pub.env.Event.Type) assert.Greater(t, r200pub.env.Seq, uint64(0)) diff --git a/server/cmd/api/api/telemetry.go b/server/cmd/api/api/telemetry.go index 328820b3..e9b174a5 100644 --- a/server/cmd/api/api/telemetry.go +++ b/server/cmd/api/api/telemetry.go @@ -17,7 +17,7 @@ func (s *ApiService) GetTelemetry(_ context.Context, _ oapi.GetTelemetryRequestO s.monitorMu.Lock() defer s.monitorMu.Unlock() - if s.telemetrySession.ID() == "" { + if !s.telemetrySession.Active() { return oapi.GetTelemetry404JSONResponse{NotFoundErrorJSONResponse: oapi.NotFoundErrorJSONResponse{Message: "telemetry is not active"}}, nil } return oapi.GetTelemetry200JSONResponse(s.buildTelemetryResponse()), nil @@ -35,7 +35,7 @@ func (s *ApiService) PutTelemetry(ctx context.Context, req oapi.PutTelemetryRequ return oapi.PutTelemetry400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: err.Error()}}, nil } - wasActive := s.telemetrySession.ID() != "" + wasActive := s.telemetrySession.Active() if allDisabled { if !wasActive { @@ -76,15 +76,15 @@ func (s *ApiService) PatchTelemetry(_ context.Context, req oapi.PatchTelemetryRe s.monitorMu.Lock() defer s.monitorMu.Unlock() - if s.telemetrySession.ID() == "" { + if !s.telemetrySession.Active() { return oapi.PatchTelemetry404JSONResponse{NotFoundErrorJSONResponse: oapi.NotFoundErrorJSONResponse{Message: "telemetry is not active"}}, nil } if req.Body != nil && req.Body.Browser != nil { - cfg, allDisabled, err := telemetryConfigFromOAPI(req.Body) - if err != nil { - return oapi.PatchTelemetry400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: err.Error()}}, nil - } + // PATCH merges: only categories explicitly set in the request are updated; + // omitted categories retain their current enabled/disabled state. + current := s.telemetrySession.Config() + cfg, allDisabled := mergeTelemetryConfig(current, req.Body.Browser) if allDisabled { // All categories disabled: stop the session. s.cdpMonitor.Stop() @@ -163,6 +163,59 @@ func telemetryConfigFromOAPI(cfg *oapi.BrowserTelemetryConfig) (telemetry.Teleme return telemetry.TelemetryConfig{Categories: cats}, false, nil } +// mergeTelemetryConfig applies patch overrides onto current, returning the merged config and +// whether all user-facing categories ended up disabled (stop signal). Only categories with an +// explicit Enabled field in patch are changed; omitted categories keep their current state. +func mergeTelemetryConfig(current telemetry.TelemetryConfig, patch *oapi.BrowserTelemetryCategoriesConfig) (telemetry.TelemetryConfig, bool) { + active := make(map[events.EventCategory]struct{}, len(current.Categories)) + for _, c := range current.Categories { + if c != events.CategorySystem { // system is managed internally by TelemetrySession + active[c] = struct{}{} + } + } + + override := func(cat events.EventCategory, field *oapi.BrowserTelemetryCategoryConfig) { + if field == nil || field.Enabled == nil { + return // not mentioned in patch — keep current state + } + if *field.Enabled { + active[cat] = struct{}{} + } else { + delete(active, cat) + } + } + + override(events.CategoryConsole, patch.Console) + override(events.CategoryNetwork, patch.Network) + override(events.CategoryPage, patch.Page) + override(events.CategoryInteraction, patch.Interaction) + + // CategorySystem is managed internally by TelemetrySession; exclude from the + // user-facing allDisabled check. + userCats := []events.EventCategory{ + events.CategoryConsole, + events.CategoryNetwork, + events.CategoryPage, + events.CategoryInteraction, + } + allDisabled := true + for _, c := range userCats { + if _, ok := active[c]; ok { + allDisabled = false + break + } + } + if allDisabled { + return telemetry.TelemetryConfig{}, true + } + + cats := make([]events.EventCategory, 0, len(active)) + for c := range active { + cats = append(cats, c) + } + return telemetry.TelemetryConfig{Categories: cats}, false +} + // telemetryConfigToOAPI converts a telemetry.TelemetryConfig to an oapi.BrowserTelemetryConfig // suitable for API responses. func telemetryConfigToOAPI(cfg telemetry.TelemetryConfig) oapi.BrowserTelemetryConfig { diff --git a/server/lib/telemetry/telemetry.go b/server/lib/telemetry/telemetry.go index 9ed8458d..2920ee97 100644 --- a/server/lib/telemetry/telemetry.go +++ b/server/lib/telemetry/telemetry.go @@ -21,12 +21,12 @@ type TelemetryConfig struct { // session-scoped metadata (ID, config, timestamps), and (c) embedding // telemetry_session_id into Event.Source.Metadata before forwarding to the bus. type TelemetrySession struct { - es *events.EventStream - mu sync.Mutex - telemetrySessionID string - sessionStartSeq uint64 - categories map[events.EventCategory]struct{} - createdAt time.Time + es *events.EventStream + mu sync.Mutex + id string + sessionStartSeq uint64 + categories map[events.EventCategory]struct{} + createdAt time.Time } func NewTelemetrySession(es *events.EventStream) *TelemetrySession { @@ -43,7 +43,7 @@ func NewTelemetrySession(es *events.EventStream) *TelemetrySession { func (s *TelemetrySession) Start(telemetrySessionID string, cfg TelemetryConfig) { s.mu.Lock() defer s.mu.Unlock() - s.telemetrySessionID = telemetrySessionID + s.id = telemetrySessionID s.sessionStartSeq = s.es.Seq() s.createdAt = time.Now() @@ -69,7 +69,7 @@ func (s *TelemetrySession) publishLocked(ev events.Event) events.Envelope { if ev.Source.Metadata == nil { ev.Source.Metadata = make(map[string]string) } - ev.Source.Metadata["telemetry_session_id"] = s.telemetrySessionID + ev.Source.Metadata["telemetry_session_id"] = s.id return s.es.Publish(events.Envelope{Event: ev}) } @@ -77,7 +77,7 @@ func (s *TelemetrySession) publishLocked(ev events.Event) events.Envelope { func (s *TelemetrySession) Publish(ev events.Event) { s.mu.Lock() defer s.mu.Unlock() - if s.telemetrySessionID == "" { + if s.id == "" { return } if _, ok := s.categories[ev.Category]; !ok { @@ -95,7 +95,7 @@ func (s *TelemetrySession) NewReader(afterSeq uint64) *events.Reader { func (s *TelemetrySession) ID() string { s.mu.Lock() defer s.mu.Unlock() - return s.telemetrySessionID + return s.id } // Seq returns the sequence number of the last published event. @@ -149,7 +149,7 @@ func (s *TelemetrySession) UpdateConfig(cfg TelemetryConfig) { func (s *TelemetrySession) Active() bool { s.mu.Lock() defer s.mu.Unlock() - return s.telemetrySessionID != "" + return s.id != "" } // Stop ends the current telemetry session. The ring buffer is left intact so @@ -157,5 +157,5 @@ func (s *TelemetrySession) Active() bool { func (s *TelemetrySession) Stop() { s.mu.Lock() defer s.mu.Unlock() - s.telemetrySessionID = "" + s.id = "" } From c99f2b644b771561505bd242257b541f8415b498 Mon Sep 17 00:00:00 2001 From: Archan Datta Date: Thu, 14 May 2026 11:58:42 -0300 Subject: [PATCH 11/28] review: update oapi to match code --- server/lib/oapi/oapi.go | 28 ++++++++++++++-------------- server/openapi.yaml | 5 +++-- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index c863014c..120513c9 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -15147,20 +15147,20 @@ var swaggerSpec = []string{ "U+P+Grr6Jb5o+hX4AsDLUMQg5Jz843W01Ic75rk9+YX8BZTsbRPWiN8PjBzYUxIPq/ROkHOaQpZL464N", "PzPCFQJUayAathEHYj3e3oI2UmG7bpVR7qYuj1JW+azW6Fv5K69RhUBoNjfjtAbUaFjKwSHUjS3VnH+8", "JkL6UiJEAKTa6zZz4CmhFm1RL7u4PW5A3BNq3MSbMFO2I99YaKde7LzWxLyjqz0Jw56MnhA2rY1grhE7", - "tmCLQvV7MGVX+/usIr/S5z9WfaR5zH01t5ZdtAOMdv6OzqtvAXvNOSzYR5cCklADMyz4V4d8iB9x8I20", - "myfHbhzzHfGrlJ4yrsTJpYlMl+WjzZSUujVqie+MierRVBYq7Ji55FM/3zPfHN117KomO1B+mUejkUtU", - "8o30/Q112NkKtUk999D8ysVdlMvU62z+cSbcXci3SSGfKWE6VrGzRaWtSqQ+4nclfgcjKLWrmFDjjI55", - "hgTVvzp1sakjVY7N/cI+ajT38PDb5txNGLJpZMzocD+ip6KTXbdggxo/jnBfnNcXpQpKBcB1v1vlVy+O", - "+HKNUD4rzOdjqy+TjTx1IB/tmqZ0h/fSbR9kd1LFoJOBGzpGLdskrvudin9DYpAh8NOqcXj9qlRAMxd2", - "6T/ShGrNZgJcxyIhjRRe42YiUUCxunpoz0iES4C0vDClwo6SBd5wlnNkDiL4NpKqXXOcJyac6eq6ce6S", - "e/IZurVwic/lM3RbgPRELIDLPEqauEGMgs1DQ+k8jNufSpv9K9x8WxDJKvm1/HqrBm4Qrg/VAkjTxVXN", - "7Eh4SE5oMidTRTMX94vVJqTKyG8sfUY+avj903gsUmroM/IRPMAGFuD29+Ox+M1eFQ2CLLsNJKD1oCRj", - "X5sRJbUCDfalaa7BUqm7IvS3hJJXVJsBAn9w+sK9de0bM/gMyuuEabKgnLm28wp0kYW3beCrFwqvJAU+", - "csi1nJnRXAcF8TeW/kamDHj6DPU/91AHtoDU/Y1pV6rBzKkgDwmdA02DXsrtRrXdvv20Hxx616AsOzNM", - "zi0bDU6K6RTUkBxzhl/55jhG0eQqMpvl4RQMJAb3OyQvMcS7xsbuBhVyBV6uUW65bNDPS4lhUYC5AxoA", - "q1h7Kog1XkAwNmXCxqJqby0igNCpwcY3TK8KqyF5kzGDPd5ApGTkUrSjmw7dCralL+yxS11LfU84Fdm0", - "9RGkGdAkkUpBgtn9bgfULs2EGVblKp2JvvLsNID++bInthJkr7YQAl9dYsXqCQjV5BzddINzSzueWu3o", - "/x8AAP//D+KNdqTkAAA=", + "tmCLQvV7MGVX+/usIr/S5z9WfaR5zH01t5ZdtAOMdv6OzqtnVPnysi7b1aODGphhyb867EMEiYNwpOG8", + "u/L8YAaawI2FIbNUo7G4RL3ICpnIdOk6YmPEd/pteObVp1CA7VPNHJgqz+dpO8jq1B1za9ogvrUm6ldT", + "Waj6gqZknWe+u7pr+VVNdqD8Mo9GI5fp5Dvx+yvusLOXapP87qF7lgvcKJepF+r842zAu9B/k8A+U8Z1", + "rORni8hbpUx9yPBKABCGYGpXcgHbODqO6phnSFB/rFMXmzpS5dgdMOyjRnMPD79tzt2EIZtGxowO9yN6", + "Kjq5fQs2qPHjCPfFeX1Ry/olB2P7vFV+9fzOl2uk+llhPh9bfZls5KkD+WjXPKc7vNhu+6K7kzIInQzc", + "UFJq6Spx5fFU/BsSgwyBn1adx+t3rQKaubhN/5EmVGs2E+BaHglppPAqOxOJAorl2UN/RyJcBqXlhSkV", + "dpQsUPO0nCNzEME5klT9nuM8MeFMV9eN87fck9PRrYVLfC6no9sCpCdiAVzmUdLEDWIYbR46Uudh3P5U", + "2myA4ebbgkhWya/lGFy1kINwjawWQJo+smpmR8JDckKTOZkqmrnAYSxXIVVGfmPpM/JRw++fxmORUkOf", + "kY/gATawALe/H4/Fb/aqaBBk2a4gAa0HJRn74o4oqRVYBW8C5hoslborQn9LKHlFtRkg8AenL9xj2T5S", + "g9OhvE6YJgvKmetbr0AXWXgcB756ofBKUuBDj1zPmhnNddAsf2Ppb2TKgKfPUP9zL31gC0jd35h2tR7M", + "nArykNA50DSotdxuVNvt20/7wSN4DcqyM8Ps3rJT4aSYTkENyTFn+JXvrmMUTa4is1keTsFAYnC/Q/IS", + "Y8RrbOxuUCFX4OU67ZbLBgW4lBgWBZh8oAGwDLangljnBgRjUyZsrMr21iICCJ0a7JzD9KqwGpI3GTPY", + "JA5ESkYuxzu66dDuYFv6wia91PXk94RTkU1bH0GaAU0SqRQkWB7A7YDapZkww6repbPxV66hBtA/X/rF", + "VoLs1RZC4KvLzFg9AaGanKOfb3BuacdTqx39/wMAAP//RwH+1uXkAAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/openapi.yaml b/server/openapi.yaml index 0bd3a86e..6696c60a 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -1245,8 +1245,9 @@ paths: patch: summary: Update active telemetry configuration description: > - Replaces the entire category configuration of the active telemetry session. - Categories not specified in the request body default to enabled. + Partially updates the category configuration of the active telemetry session. + Only categories explicitly set in the request body are changed; omitted categories + retain their current enabled/disabled state. Returns 404 if telemetry is not active. Setting all four categories to enabled: false stops telemetry (returns 200 with status stopped). operationId: patchTelemetry From e3985a470b7d8fd5f79b0377c5185970500fdddd Mon Sep 17 00:00:00 2001 From: Archan Datta Date: Thu, 14 May 2026 13:59:07 -0300 Subject: [PATCH 12/28] review: idempotent all-disabled PUT/PATCH, fix stop response config, docs cleanup --- server/cmd/api/api/telemetry.go | 28 ++- server/lib/cdpmonitor/README.md | 2 +- server/lib/oapi/oapi.go | 372 ++++++++++++++++---------------- server/openapi.yaml | 7 +- 4 files changed, 212 insertions(+), 197 deletions(-) diff --git a/server/cmd/api/api/telemetry.go b/server/cmd/api/api/telemetry.go index e9b174a5..17ebdbdf 100644 --- a/server/cmd/api/api/telemetry.go +++ b/server/cmd/api/api/telemetry.go @@ -39,14 +39,13 @@ func (s *ApiService) PutTelemetry(ctx context.Context, req oapi.PutTelemetryRequ if allDisabled { if !wasActive { - return oapi.PutTelemetry400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "cannot start telemetry with all categories disabled"}}, nil + // Already stopped; all-disabled is idempotent. + return oapi.PutTelemetry200JSONResponse(oapi.TelemetryState{Status: oapi.TelemetryStateStatusStopped}), nil } // All categories disabled: stop the running session. s.cdpMonitor.Stop() - resp := s.buildTelemetryResponse() - resp.Status = oapi.TelemetryStateStatusStopped s.telemetrySession.Stop() - return oapi.PutTelemetry200JSONResponse(resp), nil + return oapi.PutTelemetry200JSONResponse(oapi.TelemetryState{Status: oapi.TelemetryStateStatusStopped, Config: disabledConfig()}), nil } if wasActive { @@ -88,13 +87,8 @@ func (s *ApiService) PatchTelemetry(_ context.Context, req oapi.PatchTelemetryRe if allDisabled { // All categories disabled: stop the session. s.cdpMonitor.Stop() - // Snapshot the final state before clearing the session ID so buildTelemetryResponse - // can still read it. Force status to stopped because cdpMonitor.Stop may - // tear down asynchronously, leaving IsRunning briefly true. - resp := s.buildTelemetryResponse() - resp.Status = oapi.TelemetryStateStatusStopped s.telemetrySession.Stop() - return oapi.PatchTelemetry200JSONResponse(resp), nil + return oapi.PatchTelemetry200JSONResponse(oapi.TelemetryState{Status: oapi.TelemetryStateStatusStopped, Config: disabledConfig()}), nil } s.telemetrySession.UpdateConfig(cfg) } @@ -216,6 +210,20 @@ func mergeTelemetryConfig(current telemetry.TelemetryConfig, patch *oapi.Browser return telemetry.TelemetryConfig{Categories: cats}, false } +// disabledConfig returns a BrowserTelemetryConfig with all four user-facing categories explicitly disabled. +func disabledConfig() oapi.BrowserTelemetryConfig { + f := false + cat := func() *oapi.BrowserTelemetryCategoryConfig { return &oapi.BrowserTelemetryCategoryConfig{Enabled: &f} } + return oapi.BrowserTelemetryConfig{ + Browser: &oapi.BrowserTelemetryCategoriesConfig{ + Console: cat(), + Network: cat(), + Page: cat(), + Interaction: cat(), + }, + } +} + // telemetryConfigToOAPI converts a telemetry.TelemetryConfig to an oapi.BrowserTelemetryConfig // suitable for API responses. func telemetryConfigToOAPI(cfg telemetry.TelemetryConfig) oapi.BrowserTelemetryConfig { diff --git a/server/lib/cdpmonitor/README.md b/server/lib/cdpmonitor/README.md index 9bf3a86f..0dc4437a 100644 --- a/server/lib/cdpmonitor/README.md +++ b/server/lib/cdpmonitor/README.md @@ -89,7 +89,7 @@ Every event arrives as an `Envelope`: ```json { - "capture_session_id": "cs_abc123", + "telemetry_session_id": "cs_abc123", "seq": 42, "event": { "ts": 1746123456789000, diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index 120513c9..d19fbd2a 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -415,7 +415,7 @@ type BrowserTelemetryCategoriesConfig struct { // BrowserTelemetryCategoryConfig Configuration for a single telemetry category. type BrowserTelemetryCategoryConfig struct { - // Enabled Whether this category is captured. Defaults to true if omitted. + // Enabled Whether this category is captured. In PUT requests, omitting this field defaults to true (category enabled). In PATCH requests, omitting this field (or sending an empty object `{}`) is a no-op; the category retains its current state. To enable or disable a category via PATCH, you must send an explicit `true` or `false`. Enabled *bool `json:"enabled,omitempty"` } @@ -14977,190 +14977,192 @@ func (sh *strictHandler) StreamTelemetryEvents(w http.ResponseWriter, r *http.Re // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/openapi.yaml b/server/openapi.yaml index 6696c60a..1034fae9 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -1480,7 +1480,12 @@ components: properties: enabled: type: boolean - description: Whether this category is captured. Defaults to true if omitted. + description: > + Whether this category is captured. In PUT requests, omitting this field + defaults to true (category enabled). In PATCH requests, omitting this field + (or sending an empty object `{}`) is a no-op; the category retains its + current state. To enable or disable a category via PATCH, you must send + an explicit `true` or `false`. additionalProperties: false TelemetryState: type: object From 3db300882e0c695233b25cbe8c37117dbb0ea90e Mon Sep 17 00:00:00 2001 From: Archan Datta Date: Thu, 14 May 2026 14:02:23 -0300 Subject: [PATCH 13/28] chore: simplify disabledConfig helper --- server/cmd/api/api/telemetry.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/server/cmd/api/api/telemetry.go b/server/cmd/api/api/telemetry.go index 17ebdbdf..f304df7e 100644 --- a/server/cmd/api/api/telemetry.go +++ b/server/cmd/api/api/telemetry.go @@ -213,13 +213,13 @@ func mergeTelemetryConfig(current telemetry.TelemetryConfig, patch *oapi.Browser // disabledConfig returns a BrowserTelemetryConfig with all four user-facing categories explicitly disabled. func disabledConfig() oapi.BrowserTelemetryConfig { f := false - cat := func() *oapi.BrowserTelemetryCategoryConfig { return &oapi.BrowserTelemetryCategoryConfig{Enabled: &f} } + cat := &oapi.BrowserTelemetryCategoryConfig{Enabled: &f} return oapi.BrowserTelemetryConfig{ Browser: &oapi.BrowserTelemetryCategoriesConfig{ - Console: cat(), - Network: cat(), - Page: cat(), - Interaction: cat(), + Console: cat, + Network: cat, + Page: cat, + Interaction: cat, }, } } From 9450ad18bc87434f2fa5b45629e76d4ede902171 Mon Sep 17 00:00:00 2001 From: Archan Datta Date: Thu, 14 May 2026 15:40:28 -0300 Subject: [PATCH 14/28] feat: add session, target, and nav_seq context to network events --- server/lib/cdpmonitor/computed.go | 7 ++++ server/lib/cdpmonitor/handlers.go | 54 +++++++++++++++++++++++-------- 2 files changed, 47 insertions(+), 14 deletions(-) diff --git a/server/lib/cdpmonitor/computed.go b/server/lib/cdpmonitor/computed.go index f18b8bb6..fd869d20 100644 --- a/server/lib/cdpmonitor/computed.go +++ b/server/lib/cdpmonitor/computed.go @@ -76,6 +76,13 @@ func (s *computedState) navSnapshot() (json.RawMessage, map[string]string) { return s.navData, s.navMeta } +// currentNavSeq returns the current navigation sequence number under mu. +func (s *computedState) currentNavSeq() int { + s.mu.Lock() + defer s.mu.Unlock() + return s.navSeq +} + // navDataWith merges extra fields into the current nav payload. // Nav context fields (session_id, target_id, etc.) always take precedence over // caller-supplied extra so a page-controlled payload cannot forge nav identity. diff --git a/server/lib/cdpmonitor/handlers.go b/server/lib/cdpmonitor/handlers.go index 15bca908..c334ddc6 100644 --- a/server/lib/cdpmonitor/handlers.go +++ b/server/lib/cdpmonitor/handlers.go @@ -292,7 +292,13 @@ func (m *Monitor) handleNetworkRequest(p cdpNetworkRequestWillBeSentParams, sess addedAt: addedAt, } m.pendReqMu.Unlock() + m.sessionsMu.RLock() + info := m.sessions[sessionID] + m.sessionsMu.RUnlock() ev := map[string]any{ + "session_id": sessionID, + "target_id": info.targetID, + "target_type": info.targetType, "request_id": p.RequestID, "loader_id": p.LoaderID, "frame_id": p.FrameID, @@ -312,12 +318,14 @@ func (m *Monitor) handleNetworkRequest(p cdpNetworkRequestWillBeSentParams, sess ev["is_redirect"] = true ev["redirect_url"] = existing.url } + cs := m.computedFor(sessionID) + if cs != nil { + ev["nav_seq"] = cs.currentNavSeq() + } data, _ := json.Marshal(ev) m.publishEvent(EventNetworkRequest, events.CategoryNetwork, events.Source{Kind: events.KindCDP}, "Network.requestWillBeSent", data, sessionID) - if !isRedirect { - if cs := m.computedFor(sessionID); cs != nil { - cs.onRequest() - } + if !isRedirect && cs != nil { + cs.onRequest() } } @@ -343,20 +351,29 @@ func (m *Monitor) handleLoadingFinished(ctx context.Context, p cdpNetworkLoading if !ok { return } + m.sessionsMu.RLock() + info := m.sessions[sessionID] + m.sessionsMu.RUnlock() + var navSeq int if cs := m.computedFor(state.sessionID); cs != nil { cs.onLoadingFinished() + navSeq = cs.currentNavSeq() } // Fetch response body async to avoid blocking readLoop; binary types are skipped. m.asyncWg.Go(func() { body := m.fetchResponseBody(ctx, p.RequestID, sessionID, state) ev := map[string]any{ - "request_id": p.RequestID, - "loader_id": state.loaderID, - "frame_id": state.frameID, - "method": state.method, - "url": state.url, - "status": state.status, - "headers": state.resHeaders, + "session_id": sessionID, + "target_id": info.targetID, + "target_type": info.targetType, + "request_id": p.RequestID, + "loader_id": state.loaderID, + "frame_id": state.frameID, + "method": state.method, + "url": state.url, + "status": state.status, + "headers": state.resHeaders, + "nav_seq": navSeq, } if state.statusText != "" { ev["status_text"] = state.statusText @@ -412,10 +429,16 @@ func (m *Monitor) handleLoadingFailed(p cdpNetworkLoadingFailedParams, sessionID } m.pendReqMu.Unlock() + m.sessionsMu.RLock() + info := m.sessions[sessionID] + m.sessionsMu.RUnlock() ev := map[string]any{ - "request_id": p.RequestID, - "error_text": p.ErrorText, - "canceled": p.Canceled, + "session_id": sessionID, + "target_id": info.targetID, + "target_type": info.targetType, + "request_id": p.RequestID, + "error_text": p.ErrorText, + "canceled": p.Canceled, } if ok { ev["url"] = state.url @@ -423,6 +446,9 @@ func (m *Monitor) handleLoadingFailed(p cdpNetworkLoadingFailedParams, sessionID ev["frame_id"] = state.frameID ev["resource_type"] = state.resourceType } + if cs := m.computedFor(sessionID); cs != nil { + ev["nav_seq"] = cs.currentNavSeq() + } data, _ := json.Marshal(ev) m.publishEvent(EventNetworkLoadingFailed, events.CategoryNetwork, events.Source{Kind: events.KindCDP}, "Network.loadingFailed", data, sessionID) if ok { From b0ee0ff37a483e5326823427a818d5a34de5dd54 Mon Sep 17 00:00:00 2001 From: Archan Datta Date: Thu, 14 May 2026 15:40:28 -0300 Subject: [PATCH 15/28] review: return disabled config on idempotent all-disabled PUT --- server/cmd/api/api/telemetry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/cmd/api/api/telemetry.go b/server/cmd/api/api/telemetry.go index f304df7e..12c3d7a7 100644 --- a/server/cmd/api/api/telemetry.go +++ b/server/cmd/api/api/telemetry.go @@ -40,7 +40,7 @@ func (s *ApiService) PutTelemetry(ctx context.Context, req oapi.PutTelemetryRequ if allDisabled { if !wasActive { // Already stopped; all-disabled is idempotent. - return oapi.PutTelemetry200JSONResponse(oapi.TelemetryState{Status: oapi.TelemetryStateStatusStopped}), nil + return oapi.PutTelemetry200JSONResponse(oapi.TelemetryState{Status: oapi.TelemetryStateStatusStopped, Config: disabledConfig()}), nil } // All categories disabled: stop the running session. s.cdpMonitor.Stop() From 489e25b75359337ab2d2c0208a0ff40a2af6395c Mon Sep 17 00:00:00 2001 From: Archan Datta Date: Thu, 14 May 2026 15:40:28 -0300 Subject: [PATCH 16/28] review: scrub lifecycle language from telemetry openapi and readme --- server/lib/cdpmonitor/README.md | 4 +- server/lib/oapi/oapi.go | 401 ++++++++++++++++---------------- server/openapi.yaml | 53 +++-- 3 files changed, 228 insertions(+), 230 deletions(-) diff --git a/server/lib/cdpmonitor/README.md b/server/lib/cdpmonitor/README.md index 0dc4437a..c62bc3c5 100644 --- a/server/lib/cdpmonitor/README.md +++ b/server/lib/cdpmonitor/README.md @@ -104,8 +104,8 @@ Every event arrives as an `Envelope`: | Field | Type | Description | | --- | --- | --- | -| `capture_session_id` | string | Pipeline-assigned ID for the capture session (not a CDP concept). | -| `seq` | uint64 | Monotonically increasing per-capture-session sequence number. | +| `telemetry_session_id` | string | Pipeline-assigned ID for the telemetry session (not a CDP concept). | +| `seq` | uint64 | Monotonically increasing per-telemetry-session sequence number. | | `event.ts` | int64 | Wall-clock time the monitor emitted the event, as **Unix microseconds** (µs since epoch). | | `event.type` | string | See [Event taxonomy](#event-taxonomy). | | `event.category` | string | One of: `console`, `network`, `page`, `interaction`, `system`. | diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index d19fbd2a..d673c6ba 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -419,7 +419,7 @@ type BrowserTelemetryCategoryConfig struct { Enabled *bool `json:"enabled,omitempty"` } -// BrowserTelemetryConfig Telemetry configuration for a browser session. Per-category capture settings. Omit a category or set enabled: true to capture it. Set enabled: false to exclude it. Omit the browser key entirely to capture all categories. Set all four categories to enabled: false to stop telemetry. +// BrowserTelemetryConfig Telemetry configuration for a browser. Per-category capture settings. Omit a category or set enabled: true to capture it. Set enabled: false to exclude it. Omit the browser key entirely to capture all categories. Set all four categories to enabled: false to clear the telemetry configuration. type BrowserTelemetryConfig struct { // Browser Per-category telemetry capture settings for browser events. Browser *BrowserTelemetryCategoriesConfig `json:"browser,omitempty"` @@ -1029,25 +1029,25 @@ type StopRecordingRequest struct { Id *string `json:"id,omitempty"` } -// TelemetryState Current telemetry session state and configuration. +// TelemetryState Current telemetry configuration and status. type TelemetryState struct { - // Config Telemetry configuration for a browser session. Per-category capture settings. Omit a category or set enabled: true to capture it. Set enabled: false to exclude it. Omit the browser key entirely to capture all categories. Set all four categories to enabled: false to stop telemetry. + // Config Telemetry configuration for a browser. Per-category capture settings. Omit a category or set enabled: true to capture it. Set enabled: false to exclude it. Omit the browser key entirely to capture all categories. Set all four categories to enabled: false to clear the telemetry configuration. Config BrowserTelemetryConfig `json:"config"` - // CreatedAt When the telemetry session was started. + // CreatedAt When the current telemetry configuration was applied. CreatedAt time.Time `json:"created_at"` - // Id Unique identifier for the telemetry session. + // Id Unique identifier for the current telemetry configuration. Id string `json:"id"` - // Seq Process-monotonic sequence number of the last published event. Does not reset between telemetry sessions. + // Seq Process-monotonic sequence number of the last published event. Does not reset across configuration changes. Seq int64 `json:"seq"` - // Status Current status of the telemetry session. + // Status Current telemetry status. Status TelemetryStateStatus `json:"status"` } -// TelemetryStateStatus Current status of the telemetry session. +// TelemetryStateStatus Current telemetry status. type TelemetryStateStatus string // TypeTextRequest defines model for TypeTextRequest. @@ -1210,7 +1210,7 @@ type DownloadRecordingParams struct { // StreamTelemetryEventsParams defines parameters for StreamTelemetryEvents. type StreamTelemetryEventsParams struct { - // LastEventID Resume after this sequence number. Omit or send 0 to start from the current position. Sequence numbers are process-monotonic, so a value from a previous telemetry session resumes correctly from that point. + // LastEventID Resume after this sequence number. Omit or send 0 to start from the current position. Sequence numbers are process-monotonic, so any previously seen value resumes correctly from that point. LastEventID *string `json:"Last-Event-ID,omitempty"` } @@ -9255,10 +9255,10 @@ type ServerInterface interface { // Idempotently enable scale to zero on this VM. // (POST /scaletozero/enable) EnableScaleToZero(w http.ResponseWriter, r *http.Request) - // Get current telemetry state + // Get telemetry configuration // (GET /telemetry) GetTelemetry(w http.ResponseWriter, r *http.Request) - // Update active telemetry configuration + // Update telemetry configuration // (PATCH /telemetry) PatchTelemetry(w http.ResponseWriter, r *http.Request) // Set telemetry configuration @@ -9570,13 +9570,13 @@ func (_ Unimplemented) EnableScaleToZero(w http.ResponseWriter, r *http.Request) w.WriteHeader(http.StatusNotImplemented) } -// Get current telemetry state +// Get telemetry configuration // (GET /telemetry) func (_ Unimplemented) GetTelemetry(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } -// Update active telemetry configuration +// Update telemetry configuration // (PATCH /telemetry) func (_ Unimplemented) PatchTelemetry(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) @@ -13338,10 +13338,10 @@ type StrictServerInterface interface { // Idempotently enable scale to zero on this VM. // (POST /scaletozero/enable) EnableScaleToZero(ctx context.Context, request EnableScaleToZeroRequestObject) (EnableScaleToZeroResponseObject, error) - // Get current telemetry state + // Get telemetry configuration // (GET /telemetry) GetTelemetry(ctx context.Context, request GetTelemetryRequestObject) (GetTelemetryResponseObject, error) - // Update active telemetry configuration + // Update telemetry configuration // (PATCH /telemetry) PatchTelemetry(ctx context.Context, request PatchTelemetryRequestObject) (PatchTelemetryResponseObject, error) // Set telemetry configuration @@ -14977,192 +14977,191 @@ func (sh *strictHandler) StreamTelemetryEvents(w http.ResponseWriter, r *http.Re // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "B4Qqco5+t+G5oRVHnebt/x8AAP//3gxADIzlAAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/openapi.yaml b/server/openapi.yaml index 1034fae9..0aaaa9c5 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -1196,14 +1196,14 @@ paths: $ref: "#/components/responses/InternalError" /telemetry: get: - summary: Get current telemetry state + summary: Get telemetry configuration description: > - Returns the current telemetry state and configuration. Returns 404 if - telemetry is not active. + Returns the current telemetry configuration. Returns 404 if telemetry + is not configured. operationId: getTelemetry responses: "200": - description: Telemetry state + description: Telemetry configuration content: application/json: schema: @@ -1213,11 +1213,10 @@ paths: put: summary: Set telemetry configuration description: > - Creates or replaces the telemetry configuration. Starts telemetry if not - already active (returns 201); replaces the configuration if active (returns 200). - Setting all four categories to enabled: false stops an active telemetry session - (returns 200 with status stopped). Returns 400 if all categories are disabled - and telemetry is not currently active. + Sets the telemetry configuration. Returns 201 if telemetry was not + previously configured; returns 200 if it was. Setting all four categories + to enabled: false clears the configuration (returns 200 with + status: stopped); this is idempotent when telemetry is not configured. operationId: putTelemetry requestBody: required: false @@ -1227,13 +1226,13 @@ paths: $ref: "#/components/schemas/BrowserTelemetryConfig" responses: "200": - description: Telemetry configuration replaced + description: Telemetry configuration updated content: application/json: schema: $ref: "#/components/schemas/TelemetryState" "201": - description: Telemetry started + description: Telemetry configuration set content: application/json: schema: @@ -1243,13 +1242,13 @@ paths: "500": $ref: "#/components/responses/InternalError" patch: - summary: Update active telemetry configuration + summary: Update telemetry configuration description: > - Partially updates the category configuration of the active telemetry session. - Only categories explicitly set in the request body are changed; omitted categories - retain their current enabled/disabled state. - Returns 404 if telemetry is not active. Setting all four categories to - enabled: false stops telemetry (returns 200 with status stopped). + Partially updates the telemetry configuration. Only categories explicitly + set in the request body are changed; omitted categories retain their + current settings. Returns 404 if telemetry is not configured. Setting + all four categories to enabled: false clears the configuration (returns + 200 with status: stopped). operationId: patchTelemetry requestBody: required: true @@ -1297,8 +1296,8 @@ paths: description: > Opens a live SSE stream of telemetry events. Each frame has the form `id: {seq}\ndata: {envelope-json}\n\n`. - Sequence numbers are process-monotonic and do not reset between sessions; - a Last-Event-ID from any previous session is valid for resuming the stream. + Sequence numbers are process-monotonic and persist across configuration + changes; a Last-Event-ID from any previous connection is valid for resuming the stream. Drops are signaled by gaps in the `id` field: if the received `id` is more than 1 ahead of the last-seen `id`, events were evicted from the buffer. Clients should track the last-seen `id` to detect gaps. @@ -1314,7 +1313,7 @@ paths: description: > Resume after this sequence number. Omit or send 0 to start from the current position. Sequence numbers are process-monotonic, so - a value from a previous telemetry session resumes correctly from that point. + any previous value resumes correctly from that point. responses: "200": description: Live SSE stream of telemetry events. @@ -1448,11 +1447,11 @@ components: BrowserTelemetryConfig: type: object description: > - Telemetry configuration for a browser session. Per-category capture settings. + Telemetry configuration for a browser. Per-category capture settings. Omit a category or set enabled: true to capture it. Set enabled: false to exclude it. Omit the browser key entirely to capture all categories. - Set all four categories to enabled: false to stop telemetry. + Set all four categories to enabled: false to clear the telemetry configuration. properties: browser: $ref: "#/components/schemas/BrowserTelemetryCategoriesConfig" @@ -1489,7 +1488,7 @@ components: additionalProperties: false TelemetryState: type: object - description: Current telemetry session state and configuration. + description: Current telemetry configuration and status. required: - id - status @@ -1499,10 +1498,10 @@ components: properties: id: type: string - description: Unique identifier for the telemetry session. + description: Unique identifier for the current telemetry configuration. status: type: string - description: Current status of the telemetry session. + description: Current telemetry status. enum: - running - stopped @@ -1513,12 +1512,12 @@ components: format: int64 description: >- Process-monotonic sequence number of the last published event. - Does not reset between telemetry sessions. + Does not reset across configuration changes. minimum: 0 created_at: type: string format: date-time - description: When the telemetry session was started. + description: When the current telemetry configuration was applied. additionalProperties: false StartRecordingRequest: type: object From 3459f5923418382c0a1512868747bfb100e48aca Mon Sep 17 00:00:00 2001 From: Archan Datta Date: Fri, 15 May 2026 08:47:25 -0300 Subject: [PATCH 17/28] feat: add typed browser telemetry event schemas to openapi --- server/Makefile | 2 +- server/cmd/api/api/events.go | 8 +- server/lib/oapi/oapi.go | 626 +++++++++++++------------ server/openapi.yaml | 883 ++++++++++++++++++++++++++++++++++- 4 files changed, 1210 insertions(+), 309 deletions(-) diff --git a/server/Makefile b/server/Makefile index b3f7f90e..303ea419 100644 --- a/server/Makefile +++ b/server/Makefile @@ -16,7 +16,7 @@ $(RECORDING_DIR): # 3. go mod tidy to pull deps oapi-generate: pnpm i -g @apiture/openapi-down-convert - openapi-down-convert --input openapi.yaml --output openapi-3.0.yaml + openapi-down-convert --input openapi.yaml --output openapi-3.0.yaml --allOf go tool oapi-codegen -config ./oapi-codegen.yaml ./openapi-3.0.yaml @echo "Fixing oapi-codegen issue https://github.com/oapi-codegen/oapi-codegen/issues/1764..." go run ./scripts/oapi/patch_sse_methods -file ./lib/oapi/oapi.go -expected-replacements 4 diff --git a/server/cmd/api/api/events.go b/server/cmd/api/api/events.go index 50d11722..c215cd96 100644 --- a/server/cmd/api/api/events.go +++ b/server/cmd/api/api/events.go @@ -37,12 +37,10 @@ func (s *ApiService) PublishTelemetryEvent(_ context.Context, req oapi.PublishTe } if body.Source != nil { - if body.Source.Kind != nil { - if *body.Source.Kind == oapi.KernelApi { - return oapi.PublishTelemetryEvent400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "source.kind kernel_api is reserved for server-generated events"}}, nil - } - ev.Source.Kind = events.SourceKind(*body.Source.Kind) + if body.Source.Kind == oapi.KernelApi { + return oapi.PublishTelemetryEvent400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "source.kind kernel_api is reserved for server-generated events"}}, nil } + ev.Source.Kind = events.SourceKind(body.Source.Kind) if body.Source.Event != nil { ev.Source.Event = *body.Source.Event } diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index d673c6ba..f9cd97e4 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -26,6 +26,30 @@ import ( openapi_types "github.com/oapi-codegen/runtime/types" ) +// Defines values for BrowserEventSourceKind. +const ( + Cdp BrowserEventSourceKind = "cdp" + Extension BrowserEventSourceKind = "extension" + KernelApi BrowserEventSourceKind = "kernel_api" + LocalProcess BrowserEventSourceKind = "local_process" +) + +// Valid indicates whether the value is a known member of the BrowserEventSourceKind enum. +func (e BrowserEventSourceKind) Valid() bool { + switch e { + case Cdp: + return true + case Extension: + return true + case KernelApi: + return true + case LocalProcess: + return true + default: + return false + } +} + // Defines values for ClickMouseRequestButton. const ( ClickMouseRequestButtonBack ClickMouseRequestButton = "back" @@ -131,57 +155,6 @@ func (e DragMouseRequestButton) Valid() bool { } } -// Defines values for EventCategory. -const ( - EventCategoryConsole EventCategory = "console" - EventCategoryInteraction EventCategory = "interaction" - EventCategoryNetwork EventCategory = "network" - EventCategoryPage EventCategory = "page" - EventCategorySystem EventCategory = "system" -) - -// Valid indicates whether the value is a known member of the EventCategory enum. -func (e EventCategory) Valid() bool { - switch e { - case EventCategoryConsole: - return true - case EventCategoryInteraction: - return true - case EventCategoryNetwork: - return true - case EventCategoryPage: - return true - case EventCategorySystem: - return true - default: - return false - } -} - -// Defines values for EventSourceKind. -const ( - Cdp EventSourceKind = "cdp" - Extension EventSourceKind = "extension" - KernelApi EventSourceKind = "kernel_api" - LocalProcess EventSourceKind = "local_process" -) - -// Valid indicates whether the value is a known member of the EventSourceKind enum. -func (e EventSourceKind) Valid() bool { - switch e { - case Cdp: - return true - case Extension: - return true - case KernelApi: - return true - case LocalProcess: - return true - default: - return false - } -} - // Defines values for FileSystemEventType. const ( CREATE FileSystemEventType = "CREATE" @@ -332,6 +305,33 @@ func (e PublishEventRequestCategory) Valid() bool { } } +// Defines values for TelemetryEventCategory. +const ( + TelemetryEventCategoryConsole TelemetryEventCategory = "console" + TelemetryEventCategoryInteraction TelemetryEventCategory = "interaction" + TelemetryEventCategoryNetwork TelemetryEventCategory = "network" + TelemetryEventCategoryPage TelemetryEventCategory = "page" + TelemetryEventCategorySystem TelemetryEventCategory = "system" +) + +// Valid indicates whether the value is a known member of the TelemetryEventCategory enum. +func (e TelemetryEventCategory) Valid() bool { + switch e { + case TelemetryEventCategoryConsole: + return true + case TelemetryEventCategoryInteraction: + return true + case TelemetryEventCategoryNetwork: + return true + case TelemetryEventCategoryPage: + return true + case TelemetryEventCategorySystem: + return true + default: + return false + } +} + // Defines values for TelemetryStateStatus. const ( TelemetryStateStatusRunning TelemetryStateStatus = "running" @@ -398,18 +398,60 @@ type BatchComputerActionRequest struct { Actions []ComputerAction `json:"actions"` } +// BrowserCallStack CDP Runtime.StackTrace representing the JavaScript call stack at the time of an event. Fields use CDP naming conventions rather than snake_case to match the Chrome DevTools Protocol wire format. +type BrowserCallStack struct { + // CallFrames Ordered list of call frames, outermost first. + CallFrames []struct { + // ColumnNumber Zero-based column number within the line. + ColumnNumber int `json:"columnNumber"` + + // FunctionName JavaScript function name, or empty string for anonymous functions. + FunctionName string `json:"functionName"` + + // LineNumber Zero-based line number within the script. + LineNumber int `json:"lineNumber"` + + // ScriptId CDP script identifier. + ScriptId string `json:"scriptId"` + + // Url URL or name of the script file. + Url string `json:"url"` + } `json:"callFrames"` + + // Description Optional label for the stack trace (e.g. async cause). + Description *string `json:"description,omitempty"` + + // Parent Parent stack trace for async stacks. + Parent *BrowserCallStack `json:"parent,omitempty"` +} + +// BrowserEventSource Provenance metadata identifying which producer emitted the event. +type BrowserEventSource struct { + // Event Producer-specific event name (e.g. `Runtime.consoleAPICalled` for CDP-sourced console events). + Event *string `json:"event,omitempty"` + + // Kind Event producer. `cdp`: Chrome DevTools Protocol events from the browser. `kernel_api`: Kernel API server (reserved for server-generated events). `extension`: injected Chrome extension. `local_process`: system process running alongside the browser. + Kind BrowserEventSourceKind `json:"kind"` + + // Metadata Producer-specific context (e.g. CDP target/session/frame IDs). + Metadata *map[string]string `json:"metadata,omitempty"` +} + +// BrowserEventSourceKind Event producer. `cdp`: Chrome DevTools Protocol events from the browser. `kernel_api`: Kernel API server (reserved for server-generated events). `extension`: injected Chrome extension. `local_process`: system process running alongside the browser. +type BrowserEventSourceKind string + // BrowserTelemetryCategoriesConfig Per-category telemetry capture settings for browser events. type BrowserTelemetryCategoriesConfig struct { - // Console Configuration for a single telemetry category. + // Console Console output (log, warn, error) and uncaught exceptions. Console *BrowserTelemetryCategoryConfig `json:"console,omitempty"` - // Interaction Configuration for a single telemetry category. + // Interaction User interaction events (clicks, keydowns, scroll). Interaction *BrowserTelemetryCategoryConfig `json:"interaction,omitempty"` - // Network Configuration for a single telemetry category. + // Network HTTP request/response metadata. Network *BrowserTelemetryCategoryConfig `json:"network,omitempty"` - // Page Configuration for a single telemetry category. + // Page Page lifecycle events (navigation, load, layout shifts, LCP). Page *BrowserTelemetryCategoryConfig `json:"page,omitempty"` } @@ -548,42 +590,6 @@ type Error struct { Message string `json:"message"` } -// Event A telemetry event. -type Event struct { - // Category Event category. - Category *EventCategory `json:"category,omitempty"` - - // Data Telemetry event payload. - Data interface{} `json:"data,omitempty"` - - // Source Provenance of the event. - Source *EventSource `json:"source,omitempty"` - - // Truncated Set by the server when the data field was truncated to fit the size limit. - Truncated *bool `json:"truncated,omitempty"` - - // Ts Unix timestamp in microseconds. Defaults to the current time when omitted. - Ts *int64 `json:"ts,omitempty"` - - // Type Event type identifier. - Type string `json:"type"` -} - -// EventCategory Event category. -type EventCategory string - -// EventSource Provenance of the event. -type EventSource struct { - Event *string `json:"event,omitempty"` - - // Kind Source kind. "kernel_api" is reserved for server-generated events. - Kind *EventSourceKind `json:"kind,omitempty"` - Metadata *map[string]string `json:"metadata,omitempty"` -} - -// EventSourceKind Source kind. "kernel_api" is reserved for server-generated events. -type EventSourceKind string - // ExecutePlaywrightRequest Request to execute Playwright code type ExecutePlaywrightRequest struct { // Code TypeScript/JavaScript code to execute. The code has access to 'page', 'context', and 'browser' variables. @@ -901,8 +907,8 @@ type PublishEventRequest struct { // Data Telemetry event payload. Data interface{} `json:"data,omitempty"` - // Source Provenance of the event. - Source *EventSource `json:"source,omitempty"` + // Source Provenance metadata identifying which producer emitted the event. + Source *BrowserEventSource `json:"source,omitempty"` // Type Event type identifier. Type string `json:"type"` @@ -911,15 +917,6 @@ type PublishEventRequest struct { // PublishEventRequestCategory Event category. type PublishEventRequestCategory string -// PublishedEnvelope The envelope assigned to a successfully published event. -type PublishedEnvelope struct { - // Event A telemetry event. - Event Event `json:"event"` - - // Seq Process-monotonic sequence number assigned across the lifetime of the server. Use with Last-Event-ID to resume the SSE stream from this point. - Seq int64 `json:"seq"` -} - // RecorderInfo defines model for RecorderInfo. type RecorderInfo struct { // FinishedAt Timestamp when recording finished @@ -1029,6 +1026,39 @@ type StopRecordingRequest struct { Id *string `json:"id,omitempty"` } +// TelemetryEnvelope The envelope assigned to a successfully published event. +type TelemetryEnvelope struct { + // Event A telemetry event. The wire-level event shape accepted by the publish endpoint and emitted on the SSE stream. Arbitrary `type` strings and `data` payloads are admitted. For browser events emitted by the Kernel image, `data` conforms to the per-type schema documented in the `Browser*Event` / `Browser*EventData` definitions, selected by `type`. + Event TelemetryEvent `json:"event"` + + // Seq Process-monotonic sequence number assigned across the lifetime of the server. Use with Last-Event-ID to resume the SSE stream from this point. + Seq int64 `json:"seq"` +} + +// TelemetryEvent A telemetry event. The wire-level event shape accepted by the publish endpoint and emitted on the SSE stream. Arbitrary `type` strings and `data` payloads are admitted. For browser events emitted by the Kernel image, `data` conforms to the per-type schema documented in the `Browser*Event` / `Browser*EventData` definitions, selected by `type`. +type TelemetryEvent struct { + // Category Event category. + Category *TelemetryEventCategory `json:"category,omitempty"` + + // Data Arbitrary JSON payload. For Kernel-emitted browser events, the payload conforms to the corresponding `Browser*EventData` schema indicated by `type`. + Data interface{} `json:"data,omitempty"` + + // Source Provenance metadata identifying which producer emitted the event. + Source *BrowserEventSource `json:"source,omitempty"` + + // Truncated Set by the server when the data field was truncated to fit the size limit. + Truncated *bool `json:"truncated,omitempty"` + + // Ts Unix timestamp in microseconds. Defaults to the current time when omitted. + Ts *int64 `json:"ts,omitempty"` + + // Type Event type identifier. + Type string `json:"type"` +} + +// TelemetryEventCategory Event category. +type TelemetryEventCategory string + // TelemetryState Current telemetry configuration and status. type TelemetryState struct { // Config Telemetry configuration for a browser. Per-category capture settings. Omit a category or set enabled: true to capture it. Set enabled: false to exclude it. Omit the browser key entirely to capture all categories. Set all four categories to enabled: false to clear the telemetry configuration. @@ -1210,7 +1240,7 @@ type DownloadRecordingParams struct { // StreamTelemetryEventsParams defines parameters for StreamTelemetryEvents. type StreamTelemetryEventsParams struct { - // LastEventID Resume after this sequence number. Omit or send 0 to start from the current position. Sequence numbers are process-monotonic, so any previously seen value resumes correctly from that point. + // LastEventID Resume after this sequence number. Omit or send 0 to start from the current position. Sequence numbers are process-monotonic, so any previous value resumes correctly from that point. LastEventID *string `json:"Last-Event-ID,omitempty"` } @@ -6307,7 +6337,7 @@ func (r PutTelemetryResponse) StatusCode() int { type PublishTelemetryEventResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *PublishedEnvelope + JSON200 *TelemetryEnvelope JSON400 *BadRequestError } @@ -9072,7 +9102,7 @@ func ParsePublishTelemetryEventResponse(rsp *http.Response) (*PublishTelemetryEv switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest PublishedEnvelope + var dest TelemetryEnvelope if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -13114,7 +13144,7 @@ type PublishTelemetryEventResponseObject interface { VisitPublishTelemetryEventResponse(w http.ResponseWriter) error } -type PublishTelemetryEvent200JSONResponse PublishedEnvelope +type PublishTelemetryEvent200JSONResponse TelemetryEnvelope func (response PublishTelemetryEvent200JSONResponse) VisitPublishTelemetryEventResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") @@ -14977,191 +15007,205 @@ func (sh *strictHandler) StreamTelemetryEvents(w http.ResponseWriter, r *http.Re // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "B4Qqco5+t+G5oRVHnebt/x8AAP//3gxADIzlAAA=", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/openapi.yaml b/server/openapi.yaml index 0aaaa9c5..ab8007cd 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -1287,7 +1287,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/PublishedEnvelope" + $ref: "#/components/schemas/TelemetryEnvelope" "400": $ref: "#/components/responses/BadRequestError" /telemetry/stream: @@ -1303,6 +1303,10 @@ paths: Clients should track the last-seen `id` to detect gaps. Fresh connections with no Last-Event-ID start from the current sequence and only see new events. + The wire shape is the generic `TelemetryEvent`. For browser events + emitted by the Kernel image, the event's `data` conforms to the + documented per-type schema (see the `Browser*Event` / `Browser*EventData` + schemas), selected by `type`. operationId: streamTelemetryEvents parameters: - in: header @@ -1326,7 +1330,7 @@ paths: content: text/event-stream: schema: - $ref: "#/components/schemas/PublishedEnvelope" + $ref: "#/components/schemas/TelemetryEnvelope" /scaletozero/disable: post: summary: Idempotently disable scale to zero on this VM. @@ -1353,9 +1357,14 @@ paths: $ref: "#/components/responses/InternalError" components: schemas: - Event: + TelemetryEvent: type: object - description: A telemetry event. + description: > + A telemetry event. The wire-level event shape accepted by the publish + endpoint and emitted on the SSE stream. Arbitrary `type` strings and + `data` payloads are admitted. For browser events emitted by the Kernel + image, `data` conforms to the per-type schema documented in the + `Browser*Event` / `Browser*EventData` definitions, selected by `type`. required: - type properties: @@ -1376,20 +1385,27 @@ components: - interaction - system source: - $ref: "#/components/schemas/EventSource" + $ref: "#/components/schemas/BrowserEventSource" data: - description: Telemetry event payload. + description: > + Arbitrary JSON payload. For Kernel-emitted browser events, the + payload conforms to the corresponding `Browser*EventData` schema + indicated by `type`. truncated: type: boolean description: Set by the server when the data field was truncated to fit the size limit. - EventSource: + BrowserEventSource: type: object - description: Provenance of the event. + description: Provenance metadata identifying which producer emitted the event. + required: [kind] properties: kind: type: string description: > - Source kind. "kernel_api" is reserved for server-generated events. + Event producer. `cdp`: Chrome DevTools Protocol events from the + browser. `kernel_api`: Kernel API server (reserved for + server-generated events). `extension`: injected Chrome extension. + `local_process`: system process running alongside the browser. enum: - cdp - kernel_api @@ -1397,8 +1413,10 @@ components: - local_process event: type: string + description: Producer-specific event name (e.g. `Runtime.consoleAPICalled` for CDP-sourced console events). metadata: type: object + description: Producer-specific context (e.g. CDP target/session/frame IDs). additionalProperties: type: string additionalProperties: false @@ -1422,11 +1440,11 @@ components: - system default: system source: - $ref: "#/components/schemas/EventSource" + $ref: "#/components/schemas/BrowserEventSource" data: description: Telemetry event payload. additionalProperties: false - PublishedEnvelope: + TelemetryEnvelope: type: object description: The envelope assigned to a successfully published event. required: @@ -1442,7 +1460,7 @@ components: the server. Use with Last-Event-ID to resume the SSE stream from this point. event: - $ref: "#/components/schemas/Event" + $ref: "#/components/schemas/TelemetryEvent" additionalProperties: false BrowserTelemetryConfig: type: object @@ -1486,6 +1504,847 @@ components: current state. To enable or disable a category via PATCH, you must send an explicit `true` or `false`. additionalProperties: false + BrowserCallStack: + type: object + description: > + CDP Runtime.StackTrace representing the JavaScript call stack at the + time of an event. Fields use CDP naming conventions rather than + snake_case to match the Chrome DevTools Protocol wire format. + required: [callFrames] + properties: + description: + type: string + description: Optional label for the stack trace (e.g. async cause). + callFrames: + type: array + description: Ordered list of call frames, outermost first. + items: + type: object + required: [functionName, scriptId, url, lineNumber, columnNumber] + properties: + functionName: + type: string + description: JavaScript function name, or empty string for anonymous functions. + scriptId: + type: string + description: CDP script identifier. + url: + type: string + description: URL or name of the script file. + lineNumber: + type: integer + description: Zero-based line number within the script. + columnNumber: + type: integer + description: Zero-based column number within the line. + parent: + $ref: "#/components/schemas/BrowserCallStack" + description: Parent stack trace for async stacks. + BrowserHttpHeaders: + type: object + description: > + HTTP headers map forwarded as-is from CDP without normalization. + Values are typically strings but may be any JSON type. + additionalProperties: true + BrowserEventContext: + type: object + description: > + Browser event context stamped by the browser monitor onto all + CDP-sourced events. Identifies the target, frame, and navigation epoch + in which the event occurred. + properties: + session_id: + type: string + description: CDP session identifier for the target connection. + target_id: + type: string + description: Browser target identifier (stable across navigations within a tab). + target_type: + type: string + description: CDP target type of the page that produced the event. + enum: [page, background_page, service_worker, shared_worker, other] + frame_id: + type: string + description: CDP frame identifier within the target. + loader_id: + type: string + description: CDP document loader identifier, reset on each navigation. + url: + type: string + description: URL relevant to this event; page URL for navigation and page events, request URL for network events. + nav_seq: + type: integer + format: int64 + description: Monotonically increasing navigation sequence number, incremented on each top-level navigation within the target. + BrowserConsoleLogEventData: + allOf: + - $ref: "#/components/schemas/BrowserEventContext" + - type: object + properties: + level: + type: string + description: > + CDP Runtime.consoleAPICalled type, passed through unfiltered + from Chrome. `error` is routed to console_error events instead; + all other CDP console types appear here. See CDP spec for the + full enum. + text: + type: string + description: First console argument coerced to string. + args: + type: array + description: All console arguments coerced to strings. + items: + type: string + stack_trace: + $ref: "#/components/schemas/BrowserCallStack" + BrowserConsoleLogEvent: + type: object + description: A browser console log event (console.log, console.info, console.warn, etc.). + required: [ts, type, source] + properties: + ts: + type: integer + format: int64 + description: Event timestamp in Unix microseconds. + type: + type: string + const: console_log + source: + $ref: "#/components/schemas/BrowserEventSource" + data: + $ref: "#/components/schemas/BrowserConsoleLogEventData" + truncated: + type: boolean + description: True if the data field was truncated due to size limits. + BrowserConsoleErrorEventData: + allOf: + - $ref: "#/components/schemas/BrowserEventContext" + - type: object + properties: + text: + type: string + description: Error message text. Present in both source paths. + stack_trace: + $ref: "#/components/schemas/BrowserCallStack" + level: + type: string + description: CDP console type value, always "error". Present only when sourced from Runtime.consoleAPICalled. + args: + type: array + description: All console arguments coerced to strings. Present only when sourced from Runtime.consoleAPICalled. + items: + type: string + line: + type: integer + description: Line number in the script where the exception was thrown. Present only when sourced from Runtime.exceptionThrown. + column: + type: integer + description: Column number in the script where the exception was thrown. Present only when sourced from Runtime.exceptionThrown. + source_url: + type: string + description: URL of the script file that threw the exception. Present only when sourced from Runtime.exceptionThrown. + BrowserConsoleErrorEvent: + type: object + description: > + A browser console error or uncaught JavaScript exception event. Emitted + from two distinct CDP sources with different data shapes. + Runtime.consoleAPICalled (console.error calls) produces level, text, + args, and stack_trace. Runtime.exceptionThrown (uncaught exceptions) + produces text, line, column, source_url, and stack_trace. Fields not + applicable to the source are absent. + required: [ts, type, source] + properties: + ts: + type: integer + format: int64 + description: Event timestamp in Unix microseconds. + type: + type: string + const: console_error + source: + $ref: "#/components/schemas/BrowserEventSource" + data: + $ref: "#/components/schemas/BrowserConsoleErrorEventData" + truncated: + type: boolean + description: True if the data field was truncated due to size limits. + BrowserNetworkRequestEventData: + allOf: + - $ref: "#/components/schemas/BrowserEventContext" + - type: object + properties: + request_id: + type: string + description: CDP request identifier, unique within the session. + method: + type: string + description: HTTP method as sent on the wire (e.g. GET, POST). + document_url: + type: string + description: URL of the document that initiated the request. + headers: + $ref: "#/components/schemas/BrowserHttpHeaders" + description: Request headers. + resource_type: + type: string + description: CDP Network.ResourceType for the request, passed through as-is from Chrome. Known values include Document, Fetch, XHR, Script, Stylesheet, Image, Media, Font, TextTrack, EventSource, WebSocket, Manifest, Prefetch, Other, and more. + initiator_type: + type: string + description: CDP Initiator.type indicating what caused the request, passed through as-is from Chrome. Known values include script, parser, preload, and other. + post_data: + type: string + description: Request body for POST/PUT requests, if available. + is_redirect: + type: boolean + description: True if this request is the result of a redirect. + redirect_url: + type: string + description: Original URL before the redirect, present when is_redirect is true. + BrowserNetworkRequestEvent: + type: object + description: A browser network request sent event. + required: [ts, type, source] + properties: + ts: + type: integer + format: int64 + description: Event timestamp in Unix microseconds. + type: + type: string + const: network_request + source: + $ref: "#/components/schemas/BrowserEventSource" + data: + $ref: "#/components/schemas/BrowserNetworkRequestEventData" + truncated: + type: boolean + description: True if the data field was truncated due to size limits. + BrowserNetworkResponseEventData: + allOf: + - $ref: "#/components/schemas/BrowserEventContext" + - type: object + properties: + request_id: + type: string + description: CDP request identifier matching the originating network_request event. + method: + type: string + description: HTTP method of the original request. + status: + type: integer + description: HTTP response status code. + status_text: + type: string + description: HTTP response status text (e.g. OK, Not Found). + headers: + $ref: "#/components/schemas/BrowserHttpHeaders" + description: Response headers. + mime_type: + type: string + description: MIME type of the response (e.g. text/html, application/json). + resource_type: + type: string + description: CDP Network.ResourceType for the request, passed through as-is from Chrome. Known values include Document, Fetch, XHR, Script, Stylesheet, Image, Media, Font, TextTrack, EventSource, WebSocket, Manifest, Prefetch, Other, and more. + body: + type: string + description: Truncated response body, present only for text MIME types. + BrowserNetworkResponseEvent: + type: object + description: A browser network response received event. Fired after the response body is fully received, not when headers arrive. + required: [ts, type, source] + properties: + ts: + type: integer + format: int64 + description: Event timestamp in Unix microseconds. + type: + type: string + const: network_response + source: + $ref: "#/components/schemas/BrowserEventSource" + data: + $ref: "#/components/schemas/BrowserNetworkResponseEventData" + truncated: + type: boolean + description: True if the data field was truncated due to size limits. + BrowserNetworkLoadingFailedEventData: + allOf: + - $ref: "#/components/schemas/BrowserEventContext" + - type: object + properties: + request_id: + type: string + description: CDP request identifier matching the originating network_request event. + error_text: + type: string + description: Network error description (e.g. net::ERR_CONNECTION_REFUSED). + canceled: + type: boolean + description: True if the request was canceled by the browser or page script. + resource_type: + type: string + description: CDP Network.ResourceType for the request, passed through as-is from Chrome. Known values include Document, Fetch, XHR, Script, Stylesheet, Image, Media, Font, TextTrack, EventSource, WebSocket, Manifest, Prefetch, Other, and more. + BrowserNetworkLoadingFailedEvent: + type: object + description: A browser network loading failed event. + required: [ts, type, source] + properties: + ts: + type: integer + format: int64 + description: Event timestamp in Unix microseconds. + type: + type: string + const: network_loading_failed + source: + $ref: "#/components/schemas/BrowserEventSource" + data: + $ref: "#/components/schemas/BrowserNetworkLoadingFailedEventData" + truncated: + type: boolean + description: True if the data field was truncated due to size limits. + BrowserNetworkIdleEvent: + type: object + description: A browser network idle event emitted after a 500ms quiet period with no in-flight HTTP requests. + required: [ts, type, source] + properties: + ts: + type: integer + format: int64 + description: Event timestamp in Unix microseconds. + type: + type: string + const: network_idle + source: + $ref: "#/components/schemas/BrowserEventSource" + data: + $ref: "#/components/schemas/BrowserEventContext" + truncated: + type: boolean + description: True if the data field was truncated due to size limits. + BrowserPageNavigationEventData: + type: object + additionalProperties: false + properties: + url: + type: string + description: URL navigated to. + frame_id: + type: string + description: CDP frame identifier of the navigated frame. + parent_frame_id: + type: string + description: Parent frame identifier for subframe navigations; absent for top-level navigations. + loader_id: + type: string + description: New CDP document loader identifier assigned for this navigation. + session_id: + type: string + description: CDP session identifier. + target_id: + type: string + description: Browser target identifier. + target_type: + type: string + description: CDP target type of the navigated frame. + enum: [page, background_page, service_worker, shared_worker, other] + BrowserPageNavigationEvent: + type: object + description: A browser page navigation started event (CDP Page.frameNavigated). Carries nav context fields inline but not nav_seq, as this event resets the navigation epoch. + required: [ts, type, source] + properties: + ts: + type: integer + format: int64 + description: Event timestamp in Unix microseconds. + type: + type: string + const: page_navigation + source: + $ref: "#/components/schemas/BrowserEventSource" + data: + $ref: "#/components/schemas/BrowserPageNavigationEventData" + truncated: + type: boolean + description: True if the data field was truncated due to size limits. + BrowserPageDomContentLoadedEventData: + allOf: + - $ref: "#/components/schemas/BrowserEventContext" + - type: object + properties: + cdp_timestamp: + type: number + description: Chrome monotonic clock value in seconds at which DOMContentLoaded fired, relative to browser process start (not Unix epoch). Use `ts` for wall-clock time. + BrowserPageDomContentLoadedEvent: + type: object + description: A browser DOMContentLoaded event (CDP Page.domContentEventFired). + required: [ts, type, source] + properties: + ts: + type: integer + format: int64 + description: Event timestamp in Unix microseconds. + type: + type: string + const: page_dom_content_loaded + source: + $ref: "#/components/schemas/BrowserEventSource" + data: + $ref: "#/components/schemas/BrowserPageDomContentLoadedEventData" + truncated: + type: boolean + description: True if the data field was truncated due to size limits. + BrowserPageLoadEventData: + allOf: + - $ref: "#/components/schemas/BrowserEventContext" + - type: object + properties: + cdp_timestamp: + type: number + description: Chrome monotonic clock value in seconds at which the load event fired, relative to browser process start (not Unix epoch). Use `ts` for wall-clock time. + BrowserPageLoadEvent: + type: object + description: A browser page load event (CDP Page.loadEventFired). + required: [ts, type, source] + properties: + ts: + type: integer + format: int64 + description: Event timestamp in Unix microseconds. + type: + type: string + const: page_load + source: + $ref: "#/components/schemas/BrowserEventSource" + data: + $ref: "#/components/schemas/BrowserPageLoadEventData" + truncated: + type: boolean + description: True if the data field was truncated due to size limits. + BrowserPageTabOpenedEventData: + type: object + additionalProperties: false + properties: + target_id: + type: string + description: CDP target identifier for the newly opened tab. + target_type: + type: string + description: CDP target type of the new tab. + enum: [page, background_page, service_worker, shared_worker, other] + url: + type: string + description: Initial URL of the new tab. + title: + type: string + description: Initial page title of the new tab. + opener_id: + type: string + description: Target identifier of the tab that opened this one, if any. + BrowserPageTabOpenedEvent: + type: object + description: A new browser tab or target was opened (CDP Target.attachedToTarget for page targets). + required: [ts, type, source] + properties: + ts: + type: integer + format: int64 + description: Event timestamp in Unix microseconds. + type: + type: string + const: page_tab_opened + source: + $ref: "#/components/schemas/BrowserEventSource" + data: + $ref: "#/components/schemas/BrowserPageTabOpenedEventData" + truncated: + type: boolean + description: True if the data field was truncated due to size limits. + BrowserPageLayoutShiftEventData: + allOf: + - $ref: "#/components/schemas/BrowserEventContext" + - type: object + properties: + source_frame_id: + type: string + description: CDP frame identifier of the frame where the layout shift occurred. + time: + type: number + description: Performance Timeline timestamp of the layout shift in milliseconds. + duration: + type: number + description: Duration of the layout shift entry in milliseconds (always 0 for layout shifts per spec). + layout_shift_details: + type: object + additionalProperties: false + description: PerformanceLayoutShift attributes from the Performance Timeline entry. + properties: + value: + type: number + description: Layout shift score for this entry (contribution to CLS). + had_recent_input: + type: boolean + description: True if the layout shift was preceded by user input within 500ms, excluding it from CLS. + BrowserPageLayoutShiftEvent: + type: object + description: A browser cumulative layout shift (CLS) event from the Performance Timeline API. + required: [ts, type, source] + properties: + ts: + type: integer + format: int64 + description: Event timestamp in Unix microseconds. + type: + type: string + const: page_layout_shift + source: + $ref: "#/components/schemas/BrowserEventSource" + data: + $ref: "#/components/schemas/BrowserPageLayoutShiftEventData" + truncated: + type: boolean + description: True if the data field was truncated due to size limits. + BrowserPageLcpEventData: + allOf: + - $ref: "#/components/schemas/BrowserEventContext" + - type: object + properties: + source_frame_id: + type: string + description: CDP frame identifier of the frame where the LCP element was rendered. + time: + type: number + description: Performance Timeline timestamp of the LCP entry in milliseconds. + lcp_details: + type: object + additionalProperties: false + description: LargestContentfulPaint attributes from the Performance Timeline entry. + properties: + render_time: + type: number + description: Render time of the LCP element in milliseconds; 0 for cross-origin images without Timing-Allow-Origin. + load_time: + type: number + description: Load time of the LCP element in milliseconds. + size: + type: number + description: Visible area of the LCP element in pixels squared. + element_id: + type: string + description: id attribute of the LCP element, if present. + url: + type: string + description: URL of the LCP element for image or video elements. + node_id: + type: integer + description: CDP DOM node identifier of the LCP element. + BrowserPageLcpEvent: + type: object + description: A browser Largest Contentful Paint (LCP) event from the Performance Timeline API. + required: [ts, type, source] + properties: + ts: + type: integer + format: int64 + description: Event timestamp in Unix microseconds. + type: + type: string + const: page_lcp + source: + $ref: "#/components/schemas/BrowserEventSource" + data: + $ref: "#/components/schemas/BrowserPageLcpEventData" + truncated: + type: boolean + description: True if the data field was truncated due to size limits. + BrowserPageLayoutSettledEvent: + type: object + description: A browser layout settled event emitted 1 second after page load with no intervening layout shifts, indicating visual stability. Each layout shift resets the 1-second timer. + required: [ts, type, source] + properties: + ts: + type: integer + format: int64 + description: Event timestamp in Unix microseconds. + type: + type: string + const: page_layout_settled + source: + $ref: "#/components/schemas/BrowserEventSource" + data: + $ref: "#/components/schemas/BrowserEventContext" + truncated: + type: boolean + description: True if the data field was truncated due to size limits. + BrowserPageNavigationSettledEvent: + type: object + description: > + Emitted when page_dom_content_loaded and page_layout_settled have both + fired for the same navigation, indicating the page is loaded and + visually stable. Independent of network_idle; a single pending request + does not block it. + required: [ts, type, source] + properties: + ts: + type: integer + format: int64 + description: Event timestamp in Unix microseconds. + type: + type: string + const: page_navigation_settled + source: + $ref: "#/components/schemas/BrowserEventSource" + data: + $ref: "#/components/schemas/BrowserEventContext" + truncated: + type: boolean + description: True if the data field was truncated due to size limits. + BrowserInteractionClickEventData: + allOf: + - $ref: "#/components/schemas/BrowserEventContext" + - type: object + properties: + x: + type: integer + description: Viewport x-coordinate of the click in CSS pixels. + y: + type: integer + description: Viewport y-coordinate of the click in CSS pixels. + selector: + type: string + description: CSS selector path to the clicked element. + tag: + type: string + description: HTML tag name of the clicked element in uppercase (e.g. BUTTON, A, DIV). + text: + type: string + description: Visible text content of the clicked element, trimmed. + BrowserInteractionClickEvent: + type: object + description: A browser user click event captured via injected page script. + required: [ts, type, source] + properties: + ts: + type: integer + format: int64 + description: Event timestamp in Unix microseconds. + type: + type: string + const: interaction_click + source: + $ref: "#/components/schemas/BrowserEventSource" + data: + $ref: "#/components/schemas/BrowserInteractionClickEventData" + truncated: + type: boolean + description: True if the data field was truncated due to size limits. + BrowserInteractionKeyEventData: + allOf: + - $ref: "#/components/schemas/BrowserEventContext" + - type: object + properties: + key: + type: string + description: Key value from the KeyboardEvent (e.g. Enter, Backspace, a). + selector: + type: string + description: CSS selector path to the element that had focus when the key was pressed. + tag: + type: string + description: HTML tag name of the focused element in uppercase (e.g. INPUT, TEXTAREA, DIV). + BrowserInteractionKeyEvent: + type: object + description: A browser keyboard event captured via injected page script. + required: [ts, type, source] + properties: + ts: + type: integer + format: int64 + description: Event timestamp in Unix microseconds. + type: + type: string + const: interaction_key + source: + $ref: "#/components/schemas/BrowserEventSource" + data: + $ref: "#/components/schemas/BrowserInteractionKeyEventData" + truncated: + type: boolean + description: True if the data field was truncated due to size limits. + BrowserInteractionScrollSettledEventData: + allOf: + - $ref: "#/components/schemas/BrowserEventContext" + - type: object + properties: + from_x: + type: integer + description: Scroll x-position at the start of the scroll gesture in CSS pixels. + from_y: + type: integer + description: Scroll y-position at the start of the scroll gesture in CSS pixels. + to_x: + type: integer + description: Final scroll x-position after the gesture settled in CSS pixels. + to_y: + type: integer + description: Final scroll y-position after the gesture settled in CSS pixels. + target_selector: + type: string + description: CSS selector path to the scrolled element. + BrowserInteractionScrollSettledEvent: + type: object + description: A browser scroll settled event emitted after scroll position stops changing, captured via injected page script. + required: [ts, type, source] + properties: + ts: + type: integer + format: int64 + description: Event timestamp in Unix microseconds. + type: + type: string + const: interaction_scroll_settled + source: + $ref: "#/components/schemas/BrowserEventSource" + data: + $ref: "#/components/schemas/BrowserInteractionScrollSettledEventData" + truncated: + type: boolean + description: True if the data field was truncated due to size limits. + BrowserMonitorScreenshotEventData: + type: object + additionalProperties: false + properties: + png: + type: string + contentEncoding: base64 + description: Base64-encoded PNG screenshot of the browser viewport. + BrowserMonitorScreenshotEvent: + type: object + description: A periodic screenshot of the browser viewport. + required: [ts, type, source] + properties: + ts: + type: integer + format: int64 + description: Event timestamp in Unix microseconds. + type: + type: string + const: monitor_screenshot + source: + $ref: "#/components/schemas/BrowserEventSource" + data: + $ref: "#/components/schemas/BrowserMonitorScreenshotEventData" + truncated: + type: boolean + description: True if the data field was truncated due to size limits. + BrowserMonitorDisconnectedEventData: + type: object + additionalProperties: false + properties: + reason: + type: string + description: 'Reason for the disconnection. chrome_restarted: Chrome process restarted.' + enum: [chrome_restarted] + BrowserMonitorDisconnectedEvent: + type: object + description: The CDP connection to Chrome was lost. Telemetry events may be dropped until monitor_reconnected arrives. Treat any in-progress computed state (network_idle, page_layout_settled) as unreliable until then. + required: [ts, type, source] + properties: + ts: + type: integer + format: int64 + description: Event timestamp in Unix microseconds. + type: + type: string + const: monitor_disconnected + source: + $ref: "#/components/schemas/BrowserEventSource" + data: + $ref: "#/components/schemas/BrowserMonitorDisconnectedEventData" + truncated: + type: boolean + description: True if the data field was truncated due to size limits. + BrowserMonitorReconnectedEventData: + type: object + additionalProperties: false + properties: + reconnect_duration_ms: + type: integer + format: int64 + description: Wall-clock time in milliseconds taken to reconnect after the disconnection. + BrowserMonitorReconnectedEvent: + type: object + description: The CDP connection to Chrome was successfully re-established after a disconnection. Events emitted during the gap are lost. Computed state is reset, so navigation and network tracking restart fresh from this point. + required: [ts, type, source] + properties: + ts: + type: integer + format: int64 + description: Event timestamp in Unix microseconds. + type: + type: string + const: monitor_reconnected + source: + $ref: "#/components/schemas/BrowserEventSource" + data: + $ref: "#/components/schemas/BrowserMonitorReconnectedEventData" + truncated: + type: boolean + description: True if the data field was truncated due to size limits. + BrowserMonitorReconnectFailedEventData: + type: object + additionalProperties: false + properties: + reason: + type: string + description: 'Reason for the reconnection failure. reconnect_exhausted: all retry attempts were used up without successfully restoring the CDP connection.' + enum: [reconnect_exhausted] + BrowserMonitorReconnectFailedEvent: + type: object + description: The CDP connection to Chrome could not be re-established after exhausting all reconnection attempts. No further telemetry events will arrive on this session. + required: [ts, type, source] + properties: + ts: + type: integer + format: int64 + description: Event timestamp in Unix microseconds. + type: + type: string + const: monitor_reconnect_failed + source: + $ref: "#/components/schemas/BrowserEventSource" + data: + $ref: "#/components/schemas/BrowserMonitorReconnectFailedEventData" + truncated: + type: boolean + description: True if the data field was truncated due to size limits. + BrowserMonitorInitFailedEventData: + type: object + additionalProperties: false + properties: + step: + type: string + description: The CDP method or initialization step that failed (e.g. Target.setAutoAttach). + BrowserMonitorInitFailedEvent: + type: object + description: The CDP session could not be initialized. + required: [ts, type, source] + properties: + ts: + type: integer + format: int64 + description: Event timestamp in Unix microseconds. + type: + type: string + const: monitor_init_failed + source: + $ref: "#/components/schemas/BrowserEventSource" + data: + $ref: "#/components/schemas/BrowserMonitorInitFailedEventData" + truncated: + type: boolean + description: True if the data field was truncated due to size limits. TelemetryState: type: object description: Current telemetry configuration and status. From 0872666ec3d358cf75479b5022ef015881c14584 Mon Sep 17 00:00:00 2001 From: Archan Datta Date: Fri, 15 May 2026 09:24:18 -0300 Subject: [PATCH 18/28] refactor: use generated BrowserEventSource for internal event envelope source --- server/cmd/api/api/events.go | 10 ++---- server/lib/cdpmonitor/computed.go | 22 +++++-------- server/lib/cdpmonitor/computed_test.go | 10 +++--- server/lib/cdpmonitor/handlers.go | 43 ++++++++++++++------------ server/lib/cdpmonitor/handlers_test.go | 19 ++++++------ server/lib/cdpmonitor/monitor.go | 9 +++--- server/lib/cdpmonitor/monitor_test.go | 6 ++-- server/lib/cdpmonitor/screenshot.go | 3 +- server/lib/events/event.go | 31 +++++-------------- server/lib/events/events_test.go | 30 ++++++++++-------- server/lib/telemetry/telemetry.go | 5 +-- server/lib/telemetry/telemetry_test.go | 22 +++++++------ 12 files changed, 100 insertions(+), 110 deletions(-) diff --git a/server/cmd/api/api/events.go b/server/cmd/api/api/events.go index c215cd96..4ac00be7 100644 --- a/server/cmd/api/api/events.go +++ b/server/cmd/api/api/events.go @@ -40,13 +40,9 @@ func (s *ApiService) PublishTelemetryEvent(_ context.Context, req oapi.PublishTe if body.Source.Kind == oapi.KernelApi { return oapi.PublishTelemetryEvent400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "source.kind kernel_api is reserved for server-generated events"}}, nil } - ev.Source.Kind = events.SourceKind(body.Source.Kind) - if body.Source.Event != nil { - ev.Source.Event = *body.Source.Event - } - if body.Source.Metadata != nil { - ev.Source.Metadata = *body.Source.Metadata - } + ev.Source.Kind = oapi.BrowserEventSourceKind(body.Source.Kind) + ev.Source.Event = body.Source.Event + ev.Source.Metadata = body.Source.Metadata } if body.Data != nil { diff --git a/server/lib/cdpmonitor/computed.go b/server/lib/cdpmonitor/computed.go index fd869d20..f32a0c29 100644 --- a/server/lib/cdpmonitor/computed.go +++ b/server/lib/cdpmonitor/computed.go @@ -7,6 +7,7 @@ import ( "time" "github.com/kernel/kernel-images/server/lib/events" + oapi "github.com/kernel/kernel-images/server/lib/oapi" ) const ( @@ -224,11 +225,8 @@ func (s *computedState) startNetIdleTimer() { Ts: time.Now().UnixMicro(), Type: EventNetworkIdle, Category: events.CategoryNetwork, - Source: events.Source{ - Kind: events.KindCDP, - Metadata: navMeta, - }, - Data: navData, + Source: oapi.BrowserEventSource{Kind: oapi.Cdp, Metadata: &navMeta}, + Data: navData, }) }) } @@ -278,11 +276,8 @@ func (s *computedState) emitLayoutSettled(navSeq int) { Ts: time.Now().UnixMicro(), Type: EventLayoutSettled, Category: events.CategoryPage, - Source: events.Source{ - Kind: events.KindCDP, - Metadata: navMeta, - }, - Data: navData, + Source: oapi.BrowserEventSource{Kind: oapi.Cdp, Metadata: &navMeta}, + Data: navData, }} evs = append(evs, s.pendingNavigationSettled()...) s.mu.Unlock() @@ -314,11 +309,8 @@ func (s *computedState) pendingNavigationSettled() []events.Event { Ts: time.Now().UnixMicro(), Type: EventNavigationSettled, Category: events.CategoryPage, - Source: events.Source{ - Kind: events.KindCDP, - Metadata: s.navMeta, - }, - Data: s.navData, + Source: oapi.BrowserEventSource{Kind: oapi.Cdp, Metadata: &s.navMeta}, + Data: s.navData, }} } return nil diff --git a/server/lib/cdpmonitor/computed_test.go b/server/lib/cdpmonitor/computed_test.go index e2ec519e..e0e35b98 100644 --- a/server/lib/cdpmonitor/computed_test.go +++ b/server/lib/cdpmonitor/computed_test.go @@ -149,9 +149,9 @@ func TestNavDataMetadata(t *testing.T) { ev := ec.waitFor(t, "page_layout_settled", 3*time.Second) assert.Equal(t, events.CategoryPage, ev.Category) - assert.Equal(t, "s1", ev.Source.Metadata[MetadataKeyCDPSessionID]) - assert.Equal(t, "t1", ev.Source.Metadata[MetadataKeyTargetID]) - assert.Equal(t, "page", ev.Source.Metadata[MetadataKeyTargetType]) + assert.Equal(t, "s1", (*ev.Source.Metadata)[MetadataKeyCDPSessionID]) + assert.Equal(t, "t1", (*ev.Source.Metadata)[MetadataKeyTargetID]) + assert.Equal(t, "page", (*ev.Source.Metadata)[MetadataKeyTargetType]) var data map[string]any require.NoError(t, json.Unmarshal(ev.Data, &data)) assert.Equal(t, "s1", data["session_id"]) @@ -168,8 +168,8 @@ func TestNavDataMetadata(t *testing.T) { ev := ec.waitFor(t, "page_navigation_settled", 3*time.Second) assert.Equal(t, events.CategoryPage, ev.Category) - assert.Equal(t, "s1", ev.Source.Metadata[MetadataKeyCDPSessionID]) - assert.Equal(t, "t1", ev.Source.Metadata[MetadataKeyTargetID]) + assert.Equal(t, "s1", (*ev.Source.Metadata)[MetadataKeyCDPSessionID]) + assert.Equal(t, "t1", (*ev.Source.Metadata)[MetadataKeyTargetID]) var data map[string]any require.NoError(t, json.Unmarshal(ev.Data, &data)) assert.Equal(t, "s1", data["session_id"]) diff --git a/server/lib/cdpmonitor/handlers.go b/server/lib/cdpmonitor/handlers.go index c334ddc6..eedb8c0c 100644 --- a/server/lib/cdpmonitor/handlers.go +++ b/server/lib/cdpmonitor/handlers.go @@ -7,6 +7,7 @@ import ( "time" "github.com/kernel/kernel-images/server/lib/events" + oapi "github.com/kernel/kernel-images/server/lib/oapi" ) // logUnmarshalErr logs a Debug message when a handler can't parse CDP params. @@ -17,19 +18,23 @@ func (m *Monitor) logUnmarshalErr(method string, err error) { } // publishEvent stamps common fields and publishes an event. -func (m *Monitor) publishEvent(eventType string, category events.EventCategory, source events.Source, sourceEvent string, data json.RawMessage, sessionID string) { +func (m *Monitor) publishEvent(eventType string, category events.EventCategory, source oapi.BrowserEventSource, sourceEvent string, data json.RawMessage, sessionID string) { src := source - src.Event = sourceEvent + src.Event = &sourceEvent if sessionID != "" { - if src.Metadata == nil { - src.Metadata = make(map[string]string) + meta := make(map[string]string) + if src.Metadata != nil { + for k, v := range *src.Metadata { + meta[k] = v + } } - src.Metadata[MetadataKeyCDPSessionID] = sessionID + meta[MetadataKeyCDPSessionID] = sessionID m.sessionsMu.RLock() info := m.sessions[sessionID] m.sessionsMu.RUnlock() - src.Metadata[MetadataKeyTargetID] = info.targetID - src.Metadata[MetadataKeyTargetType] = info.targetType + meta[MetadataKeyTargetID] = info.targetID + meta[MetadataKeyTargetType] = info.targetType + src.Metadata = &meta } m.publish(events.Event{ Ts: time.Now().UnixMicro(), @@ -145,7 +150,7 @@ func (m *Monitor) handleConsole(p cdpRuntimeConsoleAPICalledParams, sessionID st "args": argValues, "stack_trace": p.StackTrace, }) - m.publishEvent(eventType, events.CategoryConsole, events.Source{Kind: events.KindCDP}, "Runtime.consoleAPICalled", data, sessionID) + m.publishEvent(eventType, events.CategoryConsole, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Runtime.consoleAPICalled", data, sessionID) } func (m *Monitor) handleExceptionThrown(ctx context.Context, p cdpRuntimeExceptionThrownParams, sessionID string) { @@ -158,7 +163,7 @@ func (m *Monitor) handleExceptionThrown(ctx context.Context, p cdpRuntimeExcepti "source_url": p.ExceptionDetails.URL, "stack_trace": p.ExceptionDetails.StackTrace, }) - m.publishEvent(EventConsoleError, events.CategoryConsole, events.Source{Kind: events.KindCDP}, "Runtime.exceptionThrown", data, sessionID) + m.publishEvent(EventConsoleError, events.CategoryConsole, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Runtime.exceptionThrown", data, sessionID) m.tryScreenshot(ctx, "Runtime.exceptionThrown", sessionID) } @@ -205,7 +210,7 @@ func (m *Monitor) handleBindingCalled(p cdpRuntimeBindingCalledParams, sessionID var payloadMap map[string]any _ = json.Unmarshal(payload, &payloadMap) cs := m.computedFor(sessionID) - m.publishEvent(header.Type, events.CategoryInteraction, events.Source{Kind: events.KindCDP}, "Runtime.bindingCalled", cs.navDataWith(payloadMap), sessionID) + m.publishEvent(header.Type, events.CategoryInteraction, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Runtime.bindingCalled", cs.navDataWith(payloadMap), sessionID) } // handleTimelineEvent processes PerformanceTimeline layout-shift and LCP events. @@ -228,7 +233,7 @@ func (m *Monitor) handleTimelineEvent(p cdpPerformanceTimelineEventAddedParams, } cs := m.computedFor(sessionID) data := cs.navDataWith(ev) - m.publishEvent(EventLayoutShift, events.CategoryPage, events.Source{Kind: events.KindCDP}, "PerformanceTimeline.timelineEventAdded", data, sessionID) + m.publishEvent(EventLayoutShift, events.CategoryPage, oapi.BrowserEventSource{Kind: oapi.Cdp}, "PerformanceTimeline.timelineEventAdded", data, sessionID) if cs != nil { cs.onLayoutShift() } @@ -251,7 +256,7 @@ func (m *Monitor) handleTimelineEvent(p cdpPerformanceTimelineEventAddedParams, } cs := m.computedFor(sessionID) data := cs.navDataWith(ev) - m.publishEvent(EventLCP, events.CategoryPage, events.Source{Kind: events.KindCDP}, "PerformanceTimeline.timelineEventAdded", data, sessionID) + m.publishEvent(EventLCP, events.CategoryPage, oapi.BrowserEventSource{Kind: oapi.Cdp}, "PerformanceTimeline.timelineEventAdded", data, sessionID) } } @@ -323,7 +328,7 @@ func (m *Monitor) handleNetworkRequest(p cdpNetworkRequestWillBeSentParams, sess ev["nav_seq"] = cs.currentNavSeq() } data, _ := json.Marshal(ev) - m.publishEvent(EventNetworkRequest, events.CategoryNetwork, events.Source{Kind: events.KindCDP}, "Network.requestWillBeSent", data, sessionID) + m.publishEvent(EventNetworkRequest, events.CategoryNetwork, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Network.requestWillBeSent", data, sessionID) if !isRedirect && cs != nil { cs.onRequest() } @@ -388,7 +393,7 @@ func (m *Monitor) handleLoadingFinished(ctx context.Context, p cdpNetworkLoading ev["body"] = body } data, _ := json.Marshal(ev) - m.publishEvent(EventNetworkResponse, events.CategoryNetwork, events.Source{Kind: events.KindCDP}, "Network.loadingFinished", data, sessionID) + m.publishEvent(EventNetworkResponse, events.CategoryNetwork, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Network.loadingFinished", data, sessionID) }) } @@ -450,7 +455,7 @@ func (m *Monitor) handleLoadingFailed(p cdpNetworkLoadingFailedParams, sessionID ev["nav_seq"] = cs.currentNavSeq() } data, _ := json.Marshal(ev) - m.publishEvent(EventNetworkLoadingFailed, events.CategoryNetwork, events.Source{Kind: events.KindCDP}, "Network.loadingFailed", data, sessionID) + m.publishEvent(EventNetworkLoadingFailed, events.CategoryNetwork, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Network.loadingFailed", data, sessionID) if ok { if cs := m.computedFor(state.sessionID); cs != nil { cs.onLoadingFinished() @@ -475,7 +480,7 @@ func (m *Monitor) handleFrameNavigated(p cdpPageFrameNavigatedParams, sessionID "parent_frame_id": p.Frame.ParentID, "loader_id": p.Frame.LoaderID, }) - m.publishEvent(EventNavigation, events.CategoryPage, events.Source{Kind: events.KindCDP}, "Page.frameNavigated", data, sessionID) + m.publishEvent(EventNavigation, events.CategoryPage, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Page.frameNavigated", data, sessionID) // Only reset state for top-level navigations; subframe (iframe) navigations // should not disrupt main-page tracking. @@ -514,7 +519,7 @@ func (m *Monitor) handleFrameNavigated(p cdpPageFrameNavigatedParams, sessionID func (m *Monitor) handleDOMContentLoaded(p cdpPageDomContentEventFiredParams, sessionID string) { cs := m.computedFor(sessionID) data := cs.navDataWith(map[string]any{"cdp_timestamp": p.Timestamp}) - m.publishEvent(EventDOMContentLoaded, events.CategoryPage, events.Source{Kind: events.KindCDP}, "Page.domContentEventFired", data, sessionID) + m.publishEvent(EventDOMContentLoaded, events.CategoryPage, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Page.domContentEventFired", data, sessionID) if cs != nil { cs.onDOMContentLoaded() } @@ -523,7 +528,7 @@ func (m *Monitor) handleDOMContentLoaded(p cdpPageDomContentEventFiredParams, se func (m *Monitor) handleLoadEventFired(ctx context.Context, p cdpPageLoadEventFiredParams, sessionID string) { cs := m.computedFor(sessionID) data := cs.navDataWith(map[string]any{"cdp_timestamp": p.Timestamp}) - m.publishEvent(EventPageLoad, events.CategoryPage, events.Source{Kind: events.KindCDP}, "Page.loadEventFired", data, sessionID) + m.publishEvent(EventPageLoad, events.CategoryPage, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Page.loadEventFired", data, sessionID) if cs != nil { cs.onPageLoad() } @@ -555,7 +560,7 @@ func (m *Monitor) handleAttachedToTarget(ctx context.Context, p cdpTargetAttache "opener_id": p.TargetInfo.OpenerID, "title": p.TargetInfo.Title, }) - m.publishEvent(EventTabOpened, events.CategoryPage, events.Source{Kind: events.KindCDP}, "Target.attachedToTarget", data, p.SessionID) + m.publishEvent(EventTabOpened, events.CategoryPage, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Target.attachedToTarget", data, p.SessionID) } targetType := p.TargetInfo.Type diff --git a/server/lib/cdpmonitor/handlers_test.go b/server/lib/cdpmonitor/handlers_test.go index cac89b16..aaf7f6d5 100644 --- a/server/lib/cdpmonitor/handlers_test.go +++ b/server/lib/cdpmonitor/handlers_test.go @@ -7,6 +7,7 @@ import ( "time" "github.com/kernel/kernel-images/server/lib/events" + oapi "github.com/kernel/kernel-images/server/lib/oapi" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -28,8 +29,8 @@ func TestConsoleEvents(t *testing.T) { }) ev := ec.waitFor(t, "console_log", 2*time.Second) assert.Equal(t, events.CategoryConsole, ev.Category) - assert.Equal(t, events.KindCDP, ev.Source.Kind) - assert.Equal(t, "Runtime.consoleAPICalled", ev.Source.Event) + assert.Equal(t, oapi.Cdp, ev.Source.Kind) + assert.Equal(t, "Runtime.consoleAPICalled", *ev.Source.Event) var data map[string]any require.NoError(t, json.Unmarshal(ev.Data, &data)) assert.Equal(t, "log", data["level"]) @@ -113,7 +114,7 @@ func TestNetworkEvents(t *testing.T) { }) ev := ec.waitFor(t, "network_request", 2*time.Second) assert.Equal(t, events.CategoryNetwork, ev.Category) - assert.Equal(t, "Network.requestWillBeSent", ev.Source.Event) + assert.Equal(t, "Network.requestWillBeSent", *ev.Source.Event) var data map[string]any require.NoError(t, json.Unmarshal(ev.Data, &data)) @@ -137,7 +138,7 @@ func TestNetworkEvents(t *testing.T) { }) ev2 := ec.waitFor(t, "network_response", 3*time.Second) - assert.Equal(t, "Network.loadingFinished", ev2.Source.Event) + assert.Equal(t, "Network.loadingFinished", *ev2.Source.Event) var data2 map[string]any require.NoError(t, json.Unmarshal(ev2.Data, &data2)) assert.Equal(t, float64(200), data2["status"]) @@ -233,7 +234,7 @@ func TestPageEvents(t *testing.T) { }) ev := ec.waitFor(t, "page_navigation", 2*time.Second) assert.Equal(t, events.CategoryPage, ev.Category) - assert.Equal(t, "Page.frameNavigated", ev.Source.Event) + assert.Equal(t, "Page.frameNavigated", *ev.Source.Event) var data map[string]any require.NoError(t, json.Unmarshal(ev.Data, &data)) assert.Equal(t, "https://example.com/page", data["url"]) @@ -284,7 +285,7 @@ func TestTabOpened(t *testing.T) { }) ev := ec.waitFor(t, "page_tab_opened", 2*time.Second) assert.Equal(t, events.CategoryPage, ev.Category) - assert.Equal(t, "Target.attachedToTarget", ev.Source.Event) + assert.Equal(t, "Target.attachedToTarget", *ev.Source.Event) var data map[string]any require.NoError(t, json.Unmarshal(ev.Data, &data)) assert.Equal(t, "target-tab", data["target_id"]) @@ -326,7 +327,7 @@ func TestBindingAndTimeline(t *testing.T) { }) ev := ec.waitFor(t, "interaction_click", 2*time.Second) assert.Equal(t, events.CategoryInteraction, ev.Category) - assert.Equal(t, "Runtime.bindingCalled", ev.Source.Event) + assert.Equal(t, "Runtime.bindingCalled", *ev.Source.Event) }) t.Run("interaction_scroll_settled", func(t *testing.T) { @@ -361,8 +362,8 @@ func TestBindingAndTimeline(t *testing.T) { }, }) ev := ec.waitFor(t, "page_layout_shift", 2*time.Second) - assert.Equal(t, events.KindCDP, ev.Source.Kind) - assert.Equal(t, "PerformanceTimeline.timelineEventAdded", ev.Source.Event) + assert.Equal(t, oapi.Cdp, ev.Source.Kind) + assert.Equal(t, "PerformanceTimeline.timelineEventAdded", *ev.Source.Event) var data map[string]any require.NoError(t, json.Unmarshal(ev.Data, &data)) assert.Equal(t, "frame-ls", data["source_frame_id"]) diff --git a/server/lib/cdpmonitor/monitor.go b/server/lib/cdpmonitor/monitor.go index c25dfac0..0a038ea6 100644 --- a/server/lib/cdpmonitor/monitor.go +++ b/server/lib/cdpmonitor/monitor.go @@ -11,6 +11,7 @@ import ( "github.com/coder/websocket" "github.com/kernel/kernel-images/server/lib/events" + oapi "github.com/kernel/kernel-images/server/lib/oapi" ) // UpstreamProvider abstracts *devtoolsproxy.UpstreamManager for testability. @@ -394,7 +395,7 @@ func (m *Monitor) initSession(ctx context.Context) { Ts: time.Now().UnixMicro(), Type: EventMonitorInitFailed, Category: events.CategorySystem, - Source: events.Source{Kind: events.KindLocalProcess}, + Source: oapi.BrowserEventSource{Kind: oapi.LocalProcess}, Data: json.RawMessage(`{"step":"Target.setAutoAttach"}`), }) return @@ -492,7 +493,7 @@ func (m *Monitor) handleUpstreamRestart(ctx context.Context, newURL string) { Ts: time.Now().UnixMicro(), Type: EventMonitorDisconnected, Category: events.CategorySystem, - Source: events.Source{Kind: events.KindLocalProcess}, + Source: oapi.BrowserEventSource{Kind: oapi.LocalProcess}, Data: json.RawMessage(`{"reason":"` + ReasonChromeRestarted + `"}`), }) @@ -523,7 +524,7 @@ func (m *Monitor) handleUpstreamRestart(ctx context.Context, newURL string) { Ts: time.Now().UnixMicro(), Type: EventMonitorReconnectFailed, Category: events.CategorySystem, - Source: events.Source{Kind: events.KindLocalProcess}, + Source: oapi.BrowserEventSource{Kind: oapi.LocalProcess}, Data: json.RawMessage(`{"reason":"` + ReasonReconnectExhausted + `"}`), }) } @@ -545,7 +546,7 @@ func (m *Monitor) handleUpstreamRestart(ctx context.Context, newURL string) { Ts: time.Now().UnixMicro(), Type: EventMonitorReconnected, Category: events.CategorySystem, - Source: events.Source{Kind: events.KindLocalProcess}, + Source: oapi.BrowserEventSource{Kind: oapi.LocalProcess}, Data: json.RawMessage(fmt.Sprintf(`{"reconnect_duration_ms":%d}`, reconnectDurationMs)), }) } diff --git a/server/lib/cdpmonitor/monitor_test.go b/server/lib/cdpmonitor/monitor_test.go index 07267078..b198af2a 100644 --- a/server/lib/cdpmonitor/monitor_test.go +++ b/server/lib/cdpmonitor/monitor_test.go @@ -8,6 +8,7 @@ import ( "time" "github.com/kernel/kernel-images/server/lib/events" + oapi "github.com/kernel/kernel-images/server/lib/oapi" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -88,8 +89,9 @@ func TestScreenshot(t *testing.T) { ev := ec.waitFor(t, "monitor_screenshot", 2*time.Second) assert.Equal(t, events.CategorySystem, ev.Category) - assert.Equal(t, events.KindLocalProcess, ev.Source.Kind) - assert.Equal(t, "Page.loadEventFired", ev.Source.Event) + assert.Equal(t, oapi.LocalProcess, ev.Source.Kind) + require.NotNil(t, ev.Source.Event) + assert.Equal(t, "Page.loadEventFired", *ev.Source.Event) var data map[string]any require.NoError(t, json.Unmarshal(ev.Data, &data)) assert.NotEmpty(t, data["png"]) diff --git a/server/lib/cdpmonitor/screenshot.go b/server/lib/cdpmonitor/screenshot.go index b8f17f4a..c71476d9 100644 --- a/server/lib/cdpmonitor/screenshot.go +++ b/server/lib/cdpmonitor/screenshot.go @@ -11,6 +11,7 @@ import ( "time" "github.com/kernel/kernel-images/server/lib/events" + oapi "github.com/kernel/kernel-images/server/lib/oapi" ) // tryScreenshot fires a screenshot if the 2s rate-limit window has elapsed. @@ -90,7 +91,7 @@ func (m *Monitor) captureScreenshot(parentCtx context.Context, sourceEvent strin Ts: time.Now().UnixMicro(), Type: EventScreenshot, Category: events.CategorySystem, - Source: events.Source{Kind: events.KindLocalProcess, Event: sourceEvent, Metadata: navMeta}, + Source: oapi.BrowserEventSource{Kind: oapi.LocalProcess, Event: &sourceEvent, Metadata: &navMeta}, Data: data, }) } diff --git a/server/lib/events/event.go b/server/lib/events/event.go index 1ef8fc04..189e32e9 100644 --- a/server/lib/events/event.go +++ b/server/lib/events/event.go @@ -3,6 +3,8 @@ package events import ( "encoding/json" "log/slog" + + oapi "github.com/kernel/kernel-images/server/lib/oapi" ) // maxS2RecordBytes is the maximum record size for the S2 event pipeline (1 MB). @@ -40,32 +42,15 @@ func ValidCategory(c EventCategory) bool { return ok } -type SourceKind string - -const ( - KindCDP SourceKind = "cdp" - KindKernelAPI SourceKind = "kernel_api" - KindExtension SourceKind = "extension" - KindLocalProcess SourceKind = "local_process" -) - -// Source captures provenance: which producer emitted the event and any -// producer-specific context (e.g. CDP target/session/frame IDs). -type Source struct { - Kind SourceKind `json:"kind"` - Event string `json:"event,omitempty"` - Metadata map[string]string `json:"metadata,omitempty"` -} - // Event is the portable event schema. It contains only producer-emitted content; // pipeline metadata (seq) lives on the Envelope. type Event struct { - Ts int64 `json:"ts"` // Unix microseconds (µs since epoch) - Type string `json:"type"` - Category EventCategory `json:"category"` - Source Source `json:"source"` - Data json.RawMessage `json:"data,omitempty"` - Truncated bool `json:"truncated,omitempty"` + Ts int64 `json:"ts"` // Unix microseconds (µs since epoch) + Type string `json:"type"` + Category EventCategory `json:"category"` + Source oapi.BrowserEventSource `json:"source"` + Data json.RawMessage `json:"data,omitempty"` + Truncated bool `json:"truncated,omitempty"` } // Envelope wraps an Event with pipeline-assigned metadata. diff --git a/server/lib/events/events_test.go b/server/lib/events/events_test.go index 3280e7da..09f33630 100644 --- a/server/lib/events/events_test.go +++ b/server/lib/events/events_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + oapi "github.com/kernel/kernel-images/server/lib/oapi" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -25,15 +26,18 @@ func TestEventSerialization(t *testing.T) { Ts: 1234567890000, Type: "console.log", Category: CategoryConsole, - Source: Source{ - Kind: KindCDP, - Event: "Runtime.consoleAPICalled", - Metadata: map[string]string{ - "target_id": "target-1", - "cdp_session_id": "cdp-session-1", - "frame_id": "frame-1", - "parent_frame_id": "parent-frame-1", - }, + Source: oapi.BrowserEventSource{ + Kind: oapi.Cdp, + Event: func() *string { s := "Runtime.consoleAPICalled"; return &s }(), + Metadata: func() *map[string]string { + m := map[string]string{ + "target_id": "target-1", + "cdp_session_id": "cdp-session-1", + "frame_id": "frame-1", + "parent_frame_id": "parent-frame-1", + } + return &m + }(), }, Data: json.RawMessage(`{"message":"hello"}`), } @@ -64,7 +68,7 @@ func TestEnvelopeSerialization(t *testing.T) { Ts: 1000, Type: "console.log", Category: CategoryConsole, - Source: Source{Kind: KindCDP}, + Source: oapi.BrowserEventSource{Kind: oapi.Cdp}, }, } @@ -87,7 +91,7 @@ func TestEventData(t *testing.T) { Ts: 1000, Type: "page.navigation", Category: CategoryPage, - Source: Source{Kind: KindCDP}, + Source: oapi.BrowserEventSource{Kind: oapi.Cdp}, Data: rawData, } @@ -104,7 +108,7 @@ func TestEventOmitEmpty(t *testing.T) { Ts: 1000, Type: "console.log", Category: CategoryConsole, - Source: Source{Kind: KindCDP}, + Source: oapi.BrowserEventSource{Kind: oapi.Cdp}, } b, err := json.Marshal(ev) @@ -119,7 +123,7 @@ func mkEnv(seq uint64, ev Event) Envelope { } func cdpEvent(typ string, cat EventCategory) Event { - return Event{Type: typ, Category: cat, Source: Source{Kind: KindCDP}} + return Event{Type: typ, Category: cat, Source: oapi.BrowserEventSource{Kind: oapi.Cdp}} } func newTestRingBuffer(t *testing.T, capacity int) *ringBuffer { diff --git a/server/lib/telemetry/telemetry.go b/server/lib/telemetry/telemetry.go index 2920ee97..9d0e432f 100644 --- a/server/lib/telemetry/telemetry.go +++ b/server/lib/telemetry/telemetry.go @@ -67,9 +67,10 @@ func (s *TelemetrySession) publishLocked(ev events.Event) events.Envelope { ev.Ts = time.Now().UnixMicro() } if ev.Source.Metadata == nil { - ev.Source.Metadata = make(map[string]string) + m := make(map[string]string) + ev.Source.Metadata = &m } - ev.Source.Metadata["telemetry_session_id"] = s.id + (*ev.Source.Metadata)["telemetry_session_id"] = s.id return s.es.Publish(events.Envelope{Event: ev}) } diff --git a/server/lib/telemetry/telemetry_test.go b/server/lib/telemetry/telemetry_test.go index 29010282..3acaa18c 100644 --- a/server/lib/telemetry/telemetry_test.go +++ b/server/lib/telemetry/telemetry_test.go @@ -9,6 +9,7 @@ import ( "time" "github.com/kernel/kernel-images/server/lib/events" + oapi "github.com/kernel/kernel-images/server/lib/oapi" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -36,12 +37,13 @@ func readEnvelope(t *testing.T, r *events.Reader, ctx context.Context) events.En } func cdpEvent(typ string, cat events.EventCategory) events.Event { - return events.Event{Type: typ, Category: cat, Source: events.Source{Kind: events.KindCDP}} + return events.Event{Type: typ, Category: cat, Source: oapi.BrowserEventSource{Kind: oapi.Cdp}} } -func telemetrySessionIDFromMetadata(t *testing.T, src events.Source) string { +func telemetrySessionIDFromMetadata(t *testing.T, src oapi.BrowserEventSource) string { t.Helper() - id, ok := src.Metadata["telemetry_session_id"] + require.NotNil(t, src.Metadata, "source.metadata is nil") + id, ok := (*src.Metadata)["telemetry_session_id"] require.True(t, ok, "telemetry_session_id not found in source.metadata") return id } @@ -103,7 +105,7 @@ func TestTelemetrySession(t *testing.T) { reader := ts.NewReader(0) for i := 0; i < 3; i++ { - ts.Publish(events.Event{Type: "page.navigation", Category: events.CategoryPage, Source: events.Source{Kind: events.KindCDP}, Ts: 1}) + ts.Publish(events.Event{Type: "page.navigation", Category: events.CategoryPage, Source: oapi.BrowserEventSource{Kind: oapi.Cdp}, Ts: 1}) } ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) @@ -120,7 +122,7 @@ func TestTelemetrySession(t *testing.T) { reader := ts.NewReader(0) before := time.Now().UnixMicro() - ts.Publish(events.Event{Type: "page.navigation", Category: events.CategoryPage, Source: events.Source{Kind: events.KindCDP}}) + ts.Publish(events.Event{Type: "page.navigation", Category: events.CategoryPage, Source: oapi.BrowserEventSource{Kind: oapi.Cdp}}) after := time.Now().UnixMicro() ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) @@ -135,7 +137,7 @@ func TestTelemetrySession(t *testing.T) { ts := newTestTelemetrySession(t) reader := ts.NewReader(0) - ts.Publish(events.Event{Type: "page.navigation", Category: events.CategoryPage, Source: events.Source{Kind: events.KindCDP}, Ts: 1}) + ts.Publish(events.Event{Type: "page.navigation", Category: events.CategoryPage, Source: oapi.BrowserEventSource{Kind: oapi.Cdp}, Ts: 1}) ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() @@ -150,7 +152,7 @@ func TestTelemetrySession(t *testing.T) { ts.Start("test-uuid", TelemetryConfig{}) reader := ts.NewReader(0) - ts.Publish(events.Event{Type: "page.navigation", Category: events.CategoryPage, Source: events.Source{Kind: events.KindCDP}, Ts: 1}) + ts.Publish(events.Event{Type: "page.navigation", Category: events.CategoryPage, Source: oapi.BrowserEventSource{Kind: oapi.Cdp}, Ts: 1}) ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() @@ -167,7 +169,7 @@ func TestTelemetrySession(t *testing.T) { ts.Publish(events.Event{ Type: "page.navigation", Category: events.CategoryPage, - Source: events.Source{Kind: events.KindCDP}, + Source: oapi.BrowserEventSource{Kind: oapi.Cdp}, Ts: 1, Data: json.RawMessage(`{"url":"https://example.com"}`), }) @@ -186,7 +188,7 @@ func TestTelemetrySession(t *testing.T) { ts.Start("sys-test", TelemetryConfig{Categories: []events.EventCategory{events.CategoryConsole}}) reader := ts.NewReader(0) - ts.Publish(events.Event{Type: "monitor.disconnected", Category: events.CategorySystem, Source: events.Source{Kind: events.KindKernelAPI}, Ts: 1}) + ts.Publish(events.Event{Type: "monitor.disconnected", Category: events.CategorySystem, Source: oapi.BrowserEventSource{Kind: oapi.KernelApi}, Ts: 1}) ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() @@ -206,7 +208,7 @@ func TestTelemetrySession(t *testing.T) { ts.Publish(events.Event{ Type: "page.navigation", Category: events.CategoryPage, - Source: events.Source{Kind: events.KindCDP}, + Source: oapi.BrowserEventSource{Kind: oapi.Cdp}, Ts: 1, Data: json.RawMessage(rawData), }) From f36fa7618612fe3e031b024e1928ec8159ae50b1 Mon Sep 17 00:00:00 2001 From: Archan Datta Date: Fri, 15 May 2026 09:29:58 -0300 Subject: [PATCH 19/28] refactor: use generated TelemetryEventCategory for internal event envelope category --- server/cmd/api/api/events.go | 6 +-- server/cmd/api/api/telemetry.go | 49 +++++++++---------- server/cmd/api/api/telemetry_test.go | 3 +- server/lib/cdpmonitor/computed.go | 6 +-- server/lib/cdpmonitor/computed_test.go | 12 ++--- server/lib/cdpmonitor/handlers.go | 26 +++++----- server/lib/cdpmonitor/handlers_test.go | 21 ++++---- server/lib/cdpmonitor/monitor.go | 8 +-- server/lib/cdpmonitor/monitor_test.go | 3 +- server/lib/cdpmonitor/screenshot.go | 2 +- server/lib/events/event.go | 46 +++++------------ server/lib/events/events_test.go | 40 +++++++-------- .../lib/events/eventsstorage_writer_test.go | 3 +- server/lib/telemetry/telemetry.go | 17 ++++--- server/lib/telemetry/telemetry_test.go | 30 ++++++------ 15 files changed, 124 insertions(+), 148 deletions(-) diff --git a/server/cmd/api/api/events.go b/server/cmd/api/api/events.go index 4ac00be7..7099b9b3 100644 --- a/server/cmd/api/api/events.go +++ b/server/cmd/api/api/events.go @@ -27,13 +27,13 @@ func (s *ApiService) PublishTelemetryEvent(_ context.Context, req oapi.PublishTe ev.Ts = time.Now().UnixMicro() if body.Category != nil { - cat := events.EventCategory(*body.Category) - if !events.ValidCategory(cat) { + cat := oapi.TelemetryEventCategory(*body.Category) + if !cat.Valid() { return oapi.PublishTelemetryEvent400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "invalid category"}}, nil } ev.Category = cat } else { - ev.Category = events.CategorySystem + ev.Category = oapi.TelemetryEventCategorySystem } if body.Source != nil { diff --git a/server/cmd/api/api/telemetry.go b/server/cmd/api/api/telemetry.go index 12c3d7a7..20edcf64 100644 --- a/server/cmd/api/api/telemetry.go +++ b/server/cmd/api/api/telemetry.go @@ -6,7 +6,6 @@ import ( "github.com/nrednav/cuid2" oapi "github.com/kernel/kernel-images/server/lib/oapi" - "github.com/kernel/kernel-images/server/lib/events" "github.com/kernel/kernel-images/server/lib/logger" "github.com/kernel/kernel-images/server/lib/telemetry" ) @@ -139,18 +138,18 @@ func telemetryConfigFromOAPI(cfg *oapi.BrowserTelemetryConfig) (telemetry.Teleme return telemetry.TelemetryConfig{}, true, nil } - cats := make([]events.EventCategory, 0, 5) + cats := make([]oapi.TelemetryEventCategory, 0, 5) if consoleOn { - cats = append(cats, events.CategoryConsole) + cats = append(cats, oapi.TelemetryEventCategoryConsole) } if networkOn { - cats = append(cats, events.CategoryNetwork) + cats = append(cats, oapi.TelemetryEventCategoryNetwork) } if pageOn { - cats = append(cats, events.CategoryPage) + cats = append(cats, oapi.TelemetryEventCategoryPage) } if interactionOn { - cats = append(cats, events.CategoryInteraction) + cats = append(cats, oapi.TelemetryEventCategoryInteraction) } // CategorySystem is always appended by TelemetrySession.Start/UpdateConfig; // no need to include it here. @@ -161,14 +160,14 @@ func telemetryConfigFromOAPI(cfg *oapi.BrowserTelemetryConfig) (telemetry.Teleme // whether all user-facing categories ended up disabled (stop signal). Only categories with an // explicit Enabled field in patch are changed; omitted categories keep their current state. func mergeTelemetryConfig(current telemetry.TelemetryConfig, patch *oapi.BrowserTelemetryCategoriesConfig) (telemetry.TelemetryConfig, bool) { - active := make(map[events.EventCategory]struct{}, len(current.Categories)) + active := make(map[oapi.TelemetryEventCategory]struct{}, len(current.Categories)) for _, c := range current.Categories { - if c != events.CategorySystem { // system is managed internally by TelemetrySession + if c != oapi.TelemetryEventCategorySystem { // system is managed internally by TelemetrySession active[c] = struct{}{} } } - override := func(cat events.EventCategory, field *oapi.BrowserTelemetryCategoryConfig) { + override := func(cat oapi.TelemetryEventCategory, field *oapi.BrowserTelemetryCategoryConfig) { if field == nil || field.Enabled == nil { return // not mentioned in patch — keep current state } @@ -179,18 +178,18 @@ func mergeTelemetryConfig(current telemetry.TelemetryConfig, patch *oapi.Browser } } - override(events.CategoryConsole, patch.Console) - override(events.CategoryNetwork, patch.Network) - override(events.CategoryPage, patch.Page) - override(events.CategoryInteraction, patch.Interaction) + override(oapi.TelemetryEventCategoryConsole, patch.Console) + override(oapi.TelemetryEventCategoryNetwork, patch.Network) + override(oapi.TelemetryEventCategoryPage, patch.Page) + override(oapi.TelemetryEventCategoryInteraction, patch.Interaction) // CategorySystem is managed internally by TelemetrySession; exclude from the // user-facing allDisabled check. - userCats := []events.EventCategory{ - events.CategoryConsole, - events.CategoryNetwork, - events.CategoryPage, - events.CategoryInteraction, + userCats := []oapi.TelemetryEventCategory{ + oapi.TelemetryEventCategoryConsole, + oapi.TelemetryEventCategoryNetwork, + oapi.TelemetryEventCategoryPage, + oapi.TelemetryEventCategoryInteraction, } allDisabled := true for _, c := range userCats { @@ -203,7 +202,7 @@ func mergeTelemetryConfig(current telemetry.TelemetryConfig, patch *oapi.Browser return telemetry.TelemetryConfig{}, true } - cats := make([]events.EventCategory, 0, len(active)) + cats := make([]oapi.TelemetryEventCategory, 0, len(active)) for c := range active { cats = append(cats, c) } @@ -228,22 +227,22 @@ func disabledConfig() oapi.BrowserTelemetryConfig { // suitable for API responses. func telemetryConfigToOAPI(cfg telemetry.TelemetryConfig) oapi.BrowserTelemetryConfig { // Build a set of active categories for O(1) lookup. - active := make(map[events.EventCategory]struct{}, len(cfg.Categories)) + active := make(map[oapi.TelemetryEventCategory]struct{}, len(cfg.Categories)) for _, c := range cfg.Categories { active[c] = struct{}{} } - enabled := func(cat events.EventCategory) *oapi.BrowserTelemetryCategoryConfig { + enabled := func(cat oapi.TelemetryEventCategory) *oapi.BrowserTelemetryCategoryConfig { _, on := active[cat] return &oapi.BrowserTelemetryCategoryConfig{Enabled: &on} } return oapi.BrowserTelemetryConfig{ Browser: &oapi.BrowserTelemetryCategoriesConfig{ - Console: enabled(events.CategoryConsole), - Network: enabled(events.CategoryNetwork), - Page: enabled(events.CategoryPage), - Interaction: enabled(events.CategoryInteraction), + Console: enabled(oapi.TelemetryEventCategoryConsole), + Network: enabled(oapi.TelemetryEventCategoryNetwork), + Page: enabled(oapi.TelemetryEventCategoryPage), + Interaction: enabled(oapi.TelemetryEventCategoryInteraction), }, } } diff --git a/server/cmd/api/api/telemetry_test.go b/server/cmd/api/api/telemetry_test.go index 87a6c0fc..4eab068c 100644 --- a/server/cmd/api/api/telemetry_test.go +++ b/server/cmd/api/api/telemetry_test.go @@ -4,7 +4,6 @@ import ( "context" "testing" - "github.com/kernel/kernel-images/server/lib/events" oapi "github.com/kernel/kernel-images/server/lib/oapi" "github.com/kernel/kernel-images/server/lib/recorder" "github.com/kernel/kernel-images/server/lib/scaletozero" @@ -35,7 +34,7 @@ func TestTelemetryConfigFromOAPI(t *testing.T) { }) require.NoError(t, err) assert.False(t, allDisabled) - assert.Contains(t, cfg.Categories, events.CategoryConsole) + assert.Contains(t, cfg.Categories, oapi.TelemetryEventCategoryConsole) }) t.Run("all false returns allDisabled=true", func(t *testing.T) { diff --git a/server/lib/cdpmonitor/computed.go b/server/lib/cdpmonitor/computed.go index f32a0c29..646ff621 100644 --- a/server/lib/cdpmonitor/computed.go +++ b/server/lib/cdpmonitor/computed.go @@ -224,7 +224,7 @@ func (s *computedState) startNetIdleTimer() { s.publish(events.Event{ Ts: time.Now().UnixMicro(), Type: EventNetworkIdle, - Category: events.CategoryNetwork, + Category: oapi.TelemetryEventCategoryNetwork, Source: oapi.BrowserEventSource{Kind: oapi.Cdp, Metadata: &navMeta}, Data: navData, }) @@ -275,7 +275,7 @@ func (s *computedState) emitLayoutSettled(navSeq int) { evs := []events.Event{{ Ts: time.Now().UnixMicro(), Type: EventLayoutSettled, - Category: events.CategoryPage, + Category: oapi.TelemetryEventCategoryPage, Source: oapi.BrowserEventSource{Kind: oapi.Cdp, Metadata: &navMeta}, Data: navData, }} @@ -308,7 +308,7 @@ func (s *computedState) pendingNavigationSettled() []events.Event { return []events.Event{{ Ts: time.Now().UnixMicro(), Type: EventNavigationSettled, - Category: events.CategoryPage, + Category: oapi.TelemetryEventCategoryPage, Source: oapi.BrowserEventSource{Kind: oapi.Cdp, Metadata: &s.navMeta}, Data: s.navData, }} diff --git a/server/lib/cdpmonitor/computed_test.go b/server/lib/cdpmonitor/computed_test.go index e0e35b98..15bc8b01 100644 --- a/server/lib/cdpmonitor/computed_test.go +++ b/server/lib/cdpmonitor/computed_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/kernel/kernel-images/server/lib/events" + oapi "github.com/kernel/kernel-images/server/lib/oapi" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -34,7 +34,7 @@ func TestNetworkIdle(t *testing.T) { ev := ec.waitFor(t, "network_idle", 2*time.Second) assert.GreaterOrEqual(t, time.Since(t0).Milliseconds(), int64(400), "fired too early") - assert.Equal(t, events.CategoryNetwork, ev.Category) + assert.Equal(t, oapi.TelemetryEventCategoryNetwork, ev.Category) }) t.Run("timer_reset_on_new_request", func(t *testing.T) { @@ -64,7 +64,7 @@ func TestLayoutSettled(t *testing.T) { ev := ec.waitFor(t, "page_layout_settled", 3*time.Second) assert.GreaterOrEqual(t, time.Since(t0).Milliseconds(), int64(900), "fired too early") - assert.Equal(t, events.CategoryPage, ev.Category) + assert.Equal(t, oapi.TelemetryEventCategoryPage, ev.Category) }) t.Run("layout_shift_before_page_load_ignored", func(t *testing.T) { @@ -104,7 +104,7 @@ func TestNavigationSettled(t *testing.T) { cs.onPageLoad() ev := ec.waitFor(t, "page_navigation_settled", 3*time.Second) - assert.Equal(t, events.CategoryPage, ev.Category) + assert.Equal(t, oapi.TelemetryEventCategoryPage, ev.Category) }) t.Run("not_blocked_by_pending_network_request", func(t *testing.T) { @@ -148,7 +148,7 @@ func TestNavDataMetadata(t *testing.T) { cs.onPageLoad() ev := ec.waitFor(t, "page_layout_settled", 3*time.Second) - assert.Equal(t, events.CategoryPage, ev.Category) + assert.Equal(t, oapi.TelemetryEventCategoryPage, ev.Category) assert.Equal(t, "s1", (*ev.Source.Metadata)[MetadataKeyCDPSessionID]) assert.Equal(t, "t1", (*ev.Source.Metadata)[MetadataKeyTargetID]) assert.Equal(t, "page", (*ev.Source.Metadata)[MetadataKeyTargetType]) @@ -167,7 +167,7 @@ func TestNavDataMetadata(t *testing.T) { cs.onPageLoad() ev := ec.waitFor(t, "page_navigation_settled", 3*time.Second) - assert.Equal(t, events.CategoryPage, ev.Category) + assert.Equal(t, oapi.TelemetryEventCategoryPage, ev.Category) assert.Equal(t, "s1", (*ev.Source.Metadata)[MetadataKeyCDPSessionID]) assert.Equal(t, "t1", (*ev.Source.Metadata)[MetadataKeyTargetID]) var data map[string]any diff --git a/server/lib/cdpmonitor/handlers.go b/server/lib/cdpmonitor/handlers.go index eedb8c0c..52f19c31 100644 --- a/server/lib/cdpmonitor/handlers.go +++ b/server/lib/cdpmonitor/handlers.go @@ -18,7 +18,7 @@ func (m *Monitor) logUnmarshalErr(method string, err error) { } // publishEvent stamps common fields and publishes an event. -func (m *Monitor) publishEvent(eventType string, category events.EventCategory, source oapi.BrowserEventSource, sourceEvent string, data json.RawMessage, sessionID string) { +func (m *Monitor) publishEvent(eventType string, category oapi.TelemetryEventCategory, source oapi.BrowserEventSource, sourceEvent string, data json.RawMessage, sessionID string) { src := source src.Event = &sourceEvent if sessionID != "" { @@ -150,7 +150,7 @@ func (m *Monitor) handleConsole(p cdpRuntimeConsoleAPICalledParams, sessionID st "args": argValues, "stack_trace": p.StackTrace, }) - m.publishEvent(eventType, events.CategoryConsole, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Runtime.consoleAPICalled", data, sessionID) + m.publishEvent(eventType, oapi.TelemetryEventCategoryConsole, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Runtime.consoleAPICalled", data, sessionID) } func (m *Monitor) handleExceptionThrown(ctx context.Context, p cdpRuntimeExceptionThrownParams, sessionID string) { @@ -163,7 +163,7 @@ func (m *Monitor) handleExceptionThrown(ctx context.Context, p cdpRuntimeExcepti "source_url": p.ExceptionDetails.URL, "stack_trace": p.ExceptionDetails.StackTrace, }) - m.publishEvent(EventConsoleError, events.CategoryConsole, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Runtime.exceptionThrown", data, sessionID) + m.publishEvent(EventConsoleError, oapi.TelemetryEventCategoryConsole, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Runtime.exceptionThrown", data, sessionID) m.tryScreenshot(ctx, "Runtime.exceptionThrown", sessionID) } @@ -210,7 +210,7 @@ func (m *Monitor) handleBindingCalled(p cdpRuntimeBindingCalledParams, sessionID var payloadMap map[string]any _ = json.Unmarshal(payload, &payloadMap) cs := m.computedFor(sessionID) - m.publishEvent(header.Type, events.CategoryInteraction, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Runtime.bindingCalled", cs.navDataWith(payloadMap), sessionID) + m.publishEvent(header.Type, oapi.TelemetryEventCategoryInteraction, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Runtime.bindingCalled", cs.navDataWith(payloadMap), sessionID) } // handleTimelineEvent processes PerformanceTimeline layout-shift and LCP events. @@ -233,7 +233,7 @@ func (m *Monitor) handleTimelineEvent(p cdpPerformanceTimelineEventAddedParams, } cs := m.computedFor(sessionID) data := cs.navDataWith(ev) - m.publishEvent(EventLayoutShift, events.CategoryPage, oapi.BrowserEventSource{Kind: oapi.Cdp}, "PerformanceTimeline.timelineEventAdded", data, sessionID) + m.publishEvent(EventLayoutShift, oapi.TelemetryEventCategoryPage, oapi.BrowserEventSource{Kind: oapi.Cdp}, "PerformanceTimeline.timelineEventAdded", data, sessionID) if cs != nil { cs.onLayoutShift() } @@ -256,7 +256,7 @@ func (m *Monitor) handleTimelineEvent(p cdpPerformanceTimelineEventAddedParams, } cs := m.computedFor(sessionID) data := cs.navDataWith(ev) - m.publishEvent(EventLCP, events.CategoryPage, oapi.BrowserEventSource{Kind: oapi.Cdp}, "PerformanceTimeline.timelineEventAdded", data, sessionID) + m.publishEvent(EventLCP, oapi.TelemetryEventCategoryPage, oapi.BrowserEventSource{Kind: oapi.Cdp}, "PerformanceTimeline.timelineEventAdded", data, sessionID) } } @@ -328,7 +328,7 @@ func (m *Monitor) handleNetworkRequest(p cdpNetworkRequestWillBeSentParams, sess ev["nav_seq"] = cs.currentNavSeq() } data, _ := json.Marshal(ev) - m.publishEvent(EventNetworkRequest, events.CategoryNetwork, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Network.requestWillBeSent", data, sessionID) + m.publishEvent(EventNetworkRequest, oapi.TelemetryEventCategoryNetwork, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Network.requestWillBeSent", data, sessionID) if !isRedirect && cs != nil { cs.onRequest() } @@ -393,7 +393,7 @@ func (m *Monitor) handleLoadingFinished(ctx context.Context, p cdpNetworkLoading ev["body"] = body } data, _ := json.Marshal(ev) - m.publishEvent(EventNetworkResponse, events.CategoryNetwork, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Network.loadingFinished", data, sessionID) + m.publishEvent(EventNetworkResponse, oapi.TelemetryEventCategoryNetwork, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Network.loadingFinished", data, sessionID) }) } @@ -455,7 +455,7 @@ func (m *Monitor) handleLoadingFailed(p cdpNetworkLoadingFailedParams, sessionID ev["nav_seq"] = cs.currentNavSeq() } data, _ := json.Marshal(ev) - m.publishEvent(EventNetworkLoadingFailed, events.CategoryNetwork, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Network.loadingFailed", data, sessionID) + m.publishEvent(EventNetworkLoadingFailed, oapi.TelemetryEventCategoryNetwork, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Network.loadingFailed", data, sessionID) if ok { if cs := m.computedFor(state.sessionID); cs != nil { cs.onLoadingFinished() @@ -480,7 +480,7 @@ func (m *Monitor) handleFrameNavigated(p cdpPageFrameNavigatedParams, sessionID "parent_frame_id": p.Frame.ParentID, "loader_id": p.Frame.LoaderID, }) - m.publishEvent(EventNavigation, events.CategoryPage, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Page.frameNavigated", data, sessionID) + m.publishEvent(EventNavigation, oapi.TelemetryEventCategoryPage, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Page.frameNavigated", data, sessionID) // Only reset state for top-level navigations; subframe (iframe) navigations // should not disrupt main-page tracking. @@ -519,7 +519,7 @@ func (m *Monitor) handleFrameNavigated(p cdpPageFrameNavigatedParams, sessionID func (m *Monitor) handleDOMContentLoaded(p cdpPageDomContentEventFiredParams, sessionID string) { cs := m.computedFor(sessionID) data := cs.navDataWith(map[string]any{"cdp_timestamp": p.Timestamp}) - m.publishEvent(EventDOMContentLoaded, events.CategoryPage, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Page.domContentEventFired", data, sessionID) + m.publishEvent(EventDOMContentLoaded, oapi.TelemetryEventCategoryPage, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Page.domContentEventFired", data, sessionID) if cs != nil { cs.onDOMContentLoaded() } @@ -528,7 +528,7 @@ func (m *Monitor) handleDOMContentLoaded(p cdpPageDomContentEventFiredParams, se func (m *Monitor) handleLoadEventFired(ctx context.Context, p cdpPageLoadEventFiredParams, sessionID string) { cs := m.computedFor(sessionID) data := cs.navDataWith(map[string]any{"cdp_timestamp": p.Timestamp}) - m.publishEvent(EventPageLoad, events.CategoryPage, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Page.loadEventFired", data, sessionID) + m.publishEvent(EventPageLoad, oapi.TelemetryEventCategoryPage, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Page.loadEventFired", data, sessionID) if cs != nil { cs.onPageLoad() } @@ -560,7 +560,7 @@ func (m *Monitor) handleAttachedToTarget(ctx context.Context, p cdpTargetAttache "opener_id": p.TargetInfo.OpenerID, "title": p.TargetInfo.Title, }) - m.publishEvent(EventTabOpened, events.CategoryPage, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Target.attachedToTarget", data, p.SessionID) + m.publishEvent(EventTabOpened, oapi.TelemetryEventCategoryPage, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Target.attachedToTarget", data, p.SessionID) } targetType := p.TargetInfo.Type diff --git a/server/lib/cdpmonitor/handlers_test.go b/server/lib/cdpmonitor/handlers_test.go index aaf7f6d5..636cad04 100644 --- a/server/lib/cdpmonitor/handlers_test.go +++ b/server/lib/cdpmonitor/handlers_test.go @@ -6,7 +6,6 @@ import ( "testing" "time" - "github.com/kernel/kernel-images/server/lib/events" oapi "github.com/kernel/kernel-images/server/lib/oapi" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -28,7 +27,7 @@ func TestConsoleEvents(t *testing.T) { }, }) ev := ec.waitFor(t, "console_log", 2*time.Second) - assert.Equal(t, events.CategoryConsole, ev.Category) + assert.Equal(t, oapi.TelemetryEventCategoryConsole, ev.Category) assert.Equal(t, oapi.Cdp, ev.Source.Kind) assert.Equal(t, "Runtime.consoleAPICalled", *ev.Source.Event) var data map[string]any @@ -51,7 +50,7 @@ func TestConsoleEvents(t *testing.T) { }, }) ev := ec.waitFor(t, "console_error", 2*time.Second) - assert.Equal(t, events.CategoryConsole, ev.Category) + assert.Equal(t, oapi.TelemetryEventCategoryConsole, ev.Category) var data map[string]any require.NoError(t, json.Unmarshal(ev.Data, &data)) assert.Equal(t, "Uncaught TypeError", data["text"]) @@ -113,7 +112,7 @@ func TestNetworkEvents(t *testing.T) { }, }) ev := ec.waitFor(t, "network_request", 2*time.Second) - assert.Equal(t, events.CategoryNetwork, ev.Category) + assert.Equal(t, oapi.TelemetryEventCategoryNetwork, ev.Category) assert.Equal(t, "Network.requestWillBeSent", *ev.Source.Event) var data map[string]any @@ -164,7 +163,7 @@ func TestNetworkEvents(t *testing.T) { }, }) ev := ec.waitFor(t, "network_loading_failed", 2*time.Second) - assert.Equal(t, events.CategoryNetwork, ev.Category) + assert.Equal(t, oapi.TelemetryEventCategoryNetwork, ev.Category) var data map[string]any require.NoError(t, json.Unmarshal(ev.Data, &data)) assert.Equal(t, "net::ERR_CONNECTION_REFUSED", data["error_text"]) @@ -233,7 +232,7 @@ func TestPageEvents(t *testing.T) { }, }) ev := ec.waitFor(t, "page_navigation", 2*time.Second) - assert.Equal(t, events.CategoryPage, ev.Category) + assert.Equal(t, oapi.TelemetryEventCategoryPage, ev.Category) assert.Equal(t, "Page.frameNavigated", *ev.Source.Event) var data map[string]any require.NoError(t, json.Unmarshal(ev.Data, &data)) @@ -244,7 +243,7 @@ func TestPageEvents(t *testing.T) { "params": map[string]any{"timestamp": 1000.0}, }) ev2 := ec.waitFor(t, "page_dom_content_loaded", 2*time.Second) - assert.Equal(t, events.CategoryPage, ev2.Category) + assert.Equal(t, oapi.TelemetryEventCategoryPage, ev2.Category) var data2 map[string]any require.NoError(t, json.Unmarshal(ev2.Data, &data2)) assert.Equal(t, float64(1000.0), data2["cdp_timestamp"]) @@ -256,7 +255,7 @@ func TestPageEvents(t *testing.T) { "params": map[string]any{"timestamp": 1001.0}, }) ev3 := ec.waitFor(t, "page_load", 2*time.Second) - assert.Equal(t, events.CategoryPage, ev3.Category) + assert.Equal(t, oapi.TelemetryEventCategoryPage, ev3.Category) var data3 map[string]any require.NoError(t, json.Unmarshal(ev3.Data, &data3)) assert.Equal(t, float64(1001.0), data3["cdp_timestamp"]) @@ -284,7 +283,7 @@ func TestTabOpened(t *testing.T) { }, }) ev := ec.waitFor(t, "page_tab_opened", 2*time.Second) - assert.Equal(t, events.CategoryPage, ev.Category) + assert.Equal(t, oapi.TelemetryEventCategoryPage, ev.Category) assert.Equal(t, "Target.attachedToTarget", *ev.Source.Event) var data map[string]any require.NoError(t, json.Unmarshal(ev.Data, &data)) @@ -326,7 +325,7 @@ func TestBindingAndTimeline(t *testing.T) { }, }) ev := ec.waitFor(t, "interaction_click", 2*time.Second) - assert.Equal(t, events.CategoryInteraction, ev.Category) + assert.Equal(t, oapi.TelemetryEventCategoryInteraction, ev.Category) assert.Equal(t, "Runtime.bindingCalled", *ev.Source.Event) }) @@ -339,7 +338,7 @@ func TestBindingAndTimeline(t *testing.T) { }, }) ev := ec.waitFor(t, "interaction_scroll_settled", 2*time.Second) - assert.Equal(t, events.CategoryInteraction, ev.Category) + assert.Equal(t, oapi.TelemetryEventCategoryInteraction, ev.Category) var data map[string]any require.NoError(t, json.Unmarshal(ev.Data, &data)) assert.Equal(t, float64(500), data["to_y"]) diff --git a/server/lib/cdpmonitor/monitor.go b/server/lib/cdpmonitor/monitor.go index 0a038ea6..f3b9807f 100644 --- a/server/lib/cdpmonitor/monitor.go +++ b/server/lib/cdpmonitor/monitor.go @@ -394,7 +394,7 @@ func (m *Monitor) initSession(ctx context.Context) { m.publish(events.Event{ Ts: time.Now().UnixMicro(), Type: EventMonitorInitFailed, - Category: events.CategorySystem, + Category: oapi.TelemetryEventCategorySystem, Source: oapi.BrowserEventSource{Kind: oapi.LocalProcess}, Data: json.RawMessage(`{"step":"Target.setAutoAttach"}`), }) @@ -492,7 +492,7 @@ func (m *Monitor) handleUpstreamRestart(ctx context.Context, newURL string) { m.publish(events.Event{ Ts: time.Now().UnixMicro(), Type: EventMonitorDisconnected, - Category: events.CategorySystem, + Category: oapi.TelemetryEventCategorySystem, Source: oapi.BrowserEventSource{Kind: oapi.LocalProcess}, Data: json.RawMessage(`{"reason":"` + ReasonChromeRestarted + `"}`), }) @@ -523,7 +523,7 @@ func (m *Monitor) handleUpstreamRestart(ctx context.Context, newURL string) { m.publish(events.Event{ Ts: time.Now().UnixMicro(), Type: EventMonitorReconnectFailed, - Category: events.CategorySystem, + Category: oapi.TelemetryEventCategorySystem, Source: oapi.BrowserEventSource{Kind: oapi.LocalProcess}, Data: json.RawMessage(`{"reason":"` + ReasonReconnectExhausted + `"}`), }) @@ -545,7 +545,7 @@ func (m *Monitor) handleUpstreamRestart(ctx context.Context, newURL string) { m.publish(events.Event{ Ts: time.Now().UnixMicro(), Type: EventMonitorReconnected, - Category: events.CategorySystem, + Category: oapi.TelemetryEventCategorySystem, Source: oapi.BrowserEventSource{Kind: oapi.LocalProcess}, Data: json.RawMessage(fmt.Sprintf(`{"reconnect_duration_ms":%d}`, reconnectDurationMs)), }) diff --git a/server/lib/cdpmonitor/monitor_test.go b/server/lib/cdpmonitor/monitor_test.go index b198af2a..09bd4b48 100644 --- a/server/lib/cdpmonitor/monitor_test.go +++ b/server/lib/cdpmonitor/monitor_test.go @@ -7,7 +7,6 @@ import ( "testing" "time" - "github.com/kernel/kernel-images/server/lib/events" oapi "github.com/kernel/kernel-images/server/lib/oapi" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -88,7 +87,7 @@ func TestScreenshot(t *testing.T) { require.Eventually(t, func() bool { return captureCount.Load() == 1 }, 2*time.Second, 20*time.Millisecond) ev := ec.waitFor(t, "monitor_screenshot", 2*time.Second) - assert.Equal(t, events.CategorySystem, ev.Category) + assert.Equal(t, oapi.TelemetryEventCategorySystem, ev.Category) assert.Equal(t, oapi.LocalProcess, ev.Source.Kind) require.NotNil(t, ev.Source.Event) assert.Equal(t, "Page.loadEventFired", *ev.Source.Event) diff --git a/server/lib/cdpmonitor/screenshot.go b/server/lib/cdpmonitor/screenshot.go index c71476d9..9d6cfae7 100644 --- a/server/lib/cdpmonitor/screenshot.go +++ b/server/lib/cdpmonitor/screenshot.go @@ -90,7 +90,7 @@ func (m *Monitor) captureScreenshot(parentCtx context.Context, sourceEvent strin m.publish(events.Event{ Ts: time.Now().UnixMicro(), Type: EventScreenshot, - Category: events.CategorySystem, + Category: oapi.TelemetryEventCategorySystem, Source: oapi.BrowserEventSource{Kind: oapi.LocalProcess, Event: &sourceEvent, Metadata: &navMeta}, Data: data, }) diff --git a/server/lib/events/event.go b/server/lib/events/event.go index 189e32e9..20100ad3 100644 --- a/server/lib/events/event.go +++ b/server/lib/events/event.go @@ -10,47 +10,25 @@ import ( // maxS2RecordBytes is the maximum record size for the S2 event pipeline (1 MB). const maxS2RecordBytes = 1_000_000 -// EventCategory determines type of logging -type EventCategory string - -const ( - CategoryConsole EventCategory = "console" - CategoryNetwork EventCategory = "network" - CategoryPage EventCategory = "page" - CategoryInteraction EventCategory = "interaction" - CategorySystem EventCategory = "system" -) - // AllCategories is the canonical list of all configurable event categories. // CategorySystem events are always captured regardless of telemetry config. -var AllCategories = []EventCategory{ - CategoryConsole, CategoryNetwork, CategoryPage, CategoryInteraction, - CategorySystem, -} - -var validCategories = func() map[EventCategory]struct{} { - m := make(map[EventCategory]struct{}, len(AllCategories)) - for _, c := range AllCategories { - m[c] = struct{}{} - } - return m -}() - -// ValidCategory reports whether c is a known EventCategory. -func ValidCategory(c EventCategory) bool { - _, ok := validCategories[c] - return ok +var AllCategories = []oapi.TelemetryEventCategory{ + oapi.TelemetryEventCategoryConsole, + oapi.TelemetryEventCategoryNetwork, + oapi.TelemetryEventCategoryPage, + oapi.TelemetryEventCategoryInteraction, + oapi.TelemetryEventCategorySystem, } // Event is the portable event schema. It contains only producer-emitted content; // pipeline metadata (seq) lives on the Envelope. type Event struct { - Ts int64 `json:"ts"` // Unix microseconds (µs since epoch) - Type string `json:"type"` - Category EventCategory `json:"category"` - Source oapi.BrowserEventSource `json:"source"` - Data json.RawMessage `json:"data,omitempty"` - Truncated bool `json:"truncated,omitempty"` + Ts int64 `json:"ts"` // Unix microseconds (µs since epoch) + Type string `json:"type"` + Category oapi.TelemetryEventCategory `json:"category"` + Source oapi.BrowserEventSource `json:"source"` + Data json.RawMessage `json:"data,omitempty"` + Truncated bool `json:"truncated,omitempty"` } // Envelope wraps an Event with pipeline-assigned metadata. diff --git a/server/lib/events/events_test.go b/server/lib/events/events_test.go index 09f33630..a21a706f 100644 --- a/server/lib/events/events_test.go +++ b/server/lib/events/events_test.go @@ -25,7 +25,7 @@ func TestEventSerialization(t *testing.T) { ev := Event{ Ts: 1234567890000, Type: "console.log", - Category: CategoryConsole, + Category: oapi.TelemetryEventCategoryConsole, Source: oapi.BrowserEventSource{ Kind: oapi.Cdp, Event: func() *string { s := "Runtime.consoleAPICalled"; return &s }(), @@ -67,7 +67,7 @@ func TestEnvelopeSerialization(t *testing.T) { Event: Event{ Ts: 1000, Type: "console.log", - Category: CategoryConsole, + Category: oapi.TelemetryEventCategoryConsole, Source: oapi.BrowserEventSource{Kind: oapi.Cdp}, }, } @@ -90,7 +90,7 @@ func TestEventData(t *testing.T) { ev := Event{ Ts: 1000, Type: "page.navigation", - Category: CategoryPage, + Category: oapi.TelemetryEventCategoryPage, Source: oapi.BrowserEventSource{Kind: oapi.Cdp}, Data: rawData, } @@ -107,7 +107,7 @@ func TestEventOmitEmpty(t *testing.T) { ev := Event{ Ts: 1000, Type: "console.log", - Category: CategoryConsole, + Category: oapi.TelemetryEventCategoryConsole, Source: oapi.BrowserEventSource{Kind: oapi.Cdp}, } @@ -122,7 +122,7 @@ func mkEnv(seq uint64, ev Event) Envelope { return Envelope{Seq: seq, Event: ev} } -func cdpEvent(typ string, cat EventCategory) Event { +func cdpEvent(typ string, cat oapi.TelemetryEventCategory) Event { return Event{Type: typ, Category: cat, Source: oapi.BrowserEventSource{Kind: oapi.Cdp}} } @@ -139,9 +139,9 @@ func TestRingBuffer(t *testing.T) { reader := rb.newReader(0) envelopes := []Envelope{ - mkEnv(1, cdpEvent("console.log", CategoryConsole)), - mkEnv(2, cdpEvent("network.request", CategoryNetwork)), - mkEnv(3, cdpEvent("page.navigation", CategoryPage)), + mkEnv(1, cdpEvent("console.log", oapi.TelemetryEventCategoryConsole)), + mkEnv(2, cdpEvent("network.request", oapi.TelemetryEventCategoryNetwork)), + mkEnv(3, cdpEvent("page.navigation", oapi.TelemetryEventCategoryPage)), } for _, env := range envelopes { @@ -164,9 +164,9 @@ func TestRingBufferOverflowNoBlock(t *testing.T) { done := make(chan struct{}) go func() { - rb.publish(mkEnv(1, cdpEvent("console.log", CategoryConsole))) - rb.publish(mkEnv(2, cdpEvent("console.log", CategoryConsole))) - rb.publish(mkEnv(3, cdpEvent("console.log", CategoryConsole))) + rb.publish(mkEnv(1, cdpEvent("console.log", oapi.TelemetryEventCategoryConsole))) + rb.publish(mkEnv(2, cdpEvent("console.log", oapi.TelemetryEventCategoryConsole))) + rb.publish(mkEnv(3, cdpEvent("console.log", oapi.TelemetryEventCategoryConsole))) close(done) }() @@ -190,9 +190,9 @@ func TestRingBufferOverflowExistingReader(t *testing.T) { rb := newTestRingBuffer(t,2) reader := rb.newReader(0) - rb.publish(mkEnv(1, cdpEvent("console.log", CategoryConsole))) - rb.publish(mkEnv(2, cdpEvent("console.log", CategoryConsole))) - rb.publish(mkEnv(3, cdpEvent("console.log", CategoryConsole))) + rb.publish(mkEnv(1, cdpEvent("console.log", oapi.TelemetryEventCategoryConsole))) + rb.publish(mkEnv(2, cdpEvent("console.log", oapi.TelemetryEventCategoryConsole))) + rb.publish(mkEnv(3, cdpEvent("console.log", oapi.TelemetryEventCategoryConsole))) ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() @@ -214,7 +214,7 @@ func TestRingBufferOverflowExistingReader(t *testing.T) { func TestNewReaderResume(t *testing.T) { rb := newTestRingBuffer(t,10) for i := uint64(1); i <= 5; i++ { - rb.publish(mkEnv(i, cdpEvent("console.log", CategoryConsole))) + rb.publish(mkEnv(i, cdpEvent("console.log", oapi.TelemetryEventCategoryConsole))) } ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) @@ -238,7 +238,7 @@ func TestNewReaderResume(t *testing.T) { t.Run("resume_before_oldest_triggers_drop", func(t *testing.T) { small := newTestRingBuffer(t, 3) for i := uint64(1); i <= 5; i++ { - small.publish(mkEnv(i, cdpEvent("console.log", CategoryConsole))) + small.publish(mkEnv(i, cdpEvent("console.log", oapi.TelemetryEventCategoryConsole))) } // oldest in ring is seq 3, requesting resume after seq 1 reader := small.newReader(1) @@ -278,7 +278,7 @@ func TestConcurrentPublishRead(t *testing.T) { go func() { defer wg.Done() for i := 1; i <= numEvents; i++ { - rb.publish(mkEnv(uint64(i), cdpEvent("console.log", CategoryConsole))) + rb.publish(mkEnv(uint64(i), cdpEvent("console.log", oapi.TelemetryEventCategoryConsole))) } }() @@ -297,7 +297,7 @@ func TestConcurrentReaders(t *testing.T) { } for i := 0; i < numEvents; i++ { - rb.publish(mkEnv(uint64(i+1), cdpEvent("console.log", CategoryConsole))) + rb.publish(mkEnv(uint64(i+1), cdpEvent("console.log", oapi.TelemetryEventCategoryConsole))) } var wg sync.WaitGroup @@ -336,7 +336,7 @@ func TestRingBufferResetWithActiveReader(t *testing.T) { // Publish some events so the reader advances. for i := uint64(1); i <= 5; i++ { - rb.publish(mkEnv(i, cdpEvent("console.log", CategoryConsole))) + rb.publish(mkEnv(i, cdpEvent("console.log", oapi.TelemetryEventCategoryConsole))) } ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() @@ -354,7 +354,7 @@ func TestRingBufferResetWithActiveReader(t *testing.T) { assert.ErrorIs(t, err, context.DeadlineExceeded, "reader should block after reset") // Publish new events; reader should resume from seq 1. - rb.publish(mkEnv(1, cdpEvent("page.navigation", CategoryPage))) + rb.publish(mkEnv(1, cdpEvent("page.navigation", oapi.TelemetryEventCategoryPage))) env := readEnvelope(t, reader, ctx) assert.Equal(t, uint64(1), env.Seq) assert.Equal(t, "page.navigation", env.Event.Type) diff --git a/server/lib/events/eventsstorage_writer_test.go b/server/lib/events/eventsstorage_writer_test.go index ec9631ad..5bdc31cc 100644 --- a/server/lib/events/eventsstorage_writer_test.go +++ b/server/lib/events/eventsstorage_writer_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + oapi "github.com/kernel/kernel-images/server/lib/oapi" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -60,7 +61,7 @@ func newTestStream(t *testing.T, capacity int) *EventStream { } func makeEvent(typ string) Event { - return Event{Type: typ, Category: CategorySystem} + return Event{Type: typ, Category: oapi.TelemetryEventCategorySystem} } func TestStorageWriter_NormalAppend(t *testing.T) { diff --git a/server/lib/telemetry/telemetry.go b/server/lib/telemetry/telemetry.go index 9d0e432f..0839289e 100644 --- a/server/lib/telemetry/telemetry.go +++ b/server/lib/telemetry/telemetry.go @@ -5,6 +5,7 @@ import ( "time" "github.com/kernel/kernel-images/server/lib/events" + oapi "github.com/kernel/kernel-images/server/lib/oapi" ) // TelemetryConfig holds caller-supplied telemetry preferences. All fields are @@ -13,7 +14,7 @@ import ( type TelemetryConfig struct { // Categories limits which event categories are captured. // nil or empty captures all user-facing categories plus system events. - Categories []events.EventCategory + Categories []oapi.TelemetryEventCategory } // TelemetrySession manages a telemetry session against a shared EventStream. @@ -25,12 +26,12 @@ type TelemetrySession struct { mu sync.Mutex id string sessionStartSeq uint64 - categories map[events.EventCategory]struct{} + categories map[oapi.TelemetryEventCategory]struct{} createdAt time.Time } func NewTelemetrySession(es *events.EventStream) *TelemetrySession { - cats := make(map[events.EventCategory]struct{}, len(events.AllCategories)) + cats := make(map[oapi.TelemetryEventCategory]struct{}, len(events.AllCategories)) for _, c := range events.AllCategories { cats[c] = struct{}{} } @@ -53,11 +54,11 @@ func (s *TelemetrySession) Start(telemetrySessionID string, cfg TelemetryConfig) if len(cats) == 0 { cats = events.AllCategories } - s.categories = make(map[events.EventCategory]struct{}, len(cats)+1) + s.categories = make(map[oapi.TelemetryEventCategory]struct{}, len(cats)+1) for _, c := range cats { s.categories[c] = struct{}{} } - s.categories[events.CategorySystem] = struct{}{} + s.categories[oapi.TelemetryEventCategorySystem] = struct{}{} } // publishLocked stamps telemetry_session_id into ev.Source.Metadata and forwards to the bus. @@ -116,7 +117,7 @@ func (s *TelemetrySession) SessionStartSeq() uint64 { func (s *TelemetrySession) Config() TelemetryConfig { s.mu.Lock() defer s.mu.Unlock() - cats := make([]events.EventCategory, 0, len(s.categories)) + cats := make([]oapi.TelemetryEventCategory, 0, len(s.categories)) for c := range s.categories { cats = append(cats, c) } @@ -139,11 +140,11 @@ func (s *TelemetrySession) UpdateConfig(cfg TelemetryConfig) { if len(cats) == 0 { cats = events.AllCategories } - s.categories = make(map[events.EventCategory]struct{}, len(cats)+1) + s.categories = make(map[oapi.TelemetryEventCategory]struct{}, len(cats)+1) for _, c := range cats { s.categories[c] = struct{}{} } - s.categories[events.CategorySystem] = struct{}{} + s.categories[oapi.TelemetryEventCategorySystem] = struct{}{} } // Active reports whether a telemetry session is currently running. diff --git a/server/lib/telemetry/telemetry_test.go b/server/lib/telemetry/telemetry_test.go index 3acaa18c..78d6686e 100644 --- a/server/lib/telemetry/telemetry_test.go +++ b/server/lib/telemetry/telemetry_test.go @@ -36,7 +36,7 @@ func readEnvelope(t *testing.T, r *events.Reader, ctx context.Context) events.En return *res.Envelope } -func cdpEvent(typ string, cat events.EventCategory) events.Event { +func cdpEvent(typ string, cat oapi.TelemetryEventCategory) events.Event { return events.Event{Type: typ, Category: cat, Source: oapi.BrowserEventSource{Kind: oapi.Cdp}} } @@ -64,7 +64,7 @@ func TestTelemetrySession(t *testing.T) { go func() { defer wg.Done() for j := 0; j < eventsEach; j++ { - ts.Publish(cdpEvent("console.log", events.CategoryConsole)) + ts.Publish(cdpEvent("console.log", oapi.TelemetryEventCategoryConsole)) } }() } @@ -82,11 +82,11 @@ func TestTelemetrySession(t *testing.T) { t.Run("seq_continues_across_sessions", func(t *testing.T) { ts := NewTelemetrySession(newTestEventStream(t, 100)) ts.Start("session-1", TelemetryConfig{}) - ts.Publish(cdpEvent("ev.one", events.CategorySystem)) - ts.Publish(cdpEvent("ev.two", events.CategorySystem)) + ts.Publish(cdpEvent("ev.one", oapi.TelemetryEventCategorySystem)) + ts.Publish(cdpEvent("ev.two", oapi.TelemetryEventCategorySystem)) ts.Start("session-2", TelemetryConfig{}) - ts.Publish(cdpEvent("ev.three", events.CategorySystem)) + ts.Publish(cdpEvent("ev.three", oapi.TelemetryEventCategorySystem)) assert.Equal(t, uint64(2), ts.SessionStartSeq(), "session-2 starts after seq 2") @@ -105,7 +105,7 @@ func TestTelemetrySession(t *testing.T) { reader := ts.NewReader(0) for i := 0; i < 3; i++ { - ts.Publish(events.Event{Type: "page.navigation", Category: events.CategoryPage, Source: oapi.BrowserEventSource{Kind: oapi.Cdp}, Ts: 1}) + ts.Publish(events.Event{Type: "page.navigation", Category: oapi.TelemetryEventCategoryPage, Source: oapi.BrowserEventSource{Kind: oapi.Cdp}, Ts: 1}) } ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) @@ -122,7 +122,7 @@ func TestTelemetrySession(t *testing.T) { reader := ts.NewReader(0) before := time.Now().UnixMicro() - ts.Publish(events.Event{Type: "page.navigation", Category: events.CategoryPage, Source: oapi.BrowserEventSource{Kind: oapi.Cdp}}) + ts.Publish(events.Event{Type: "page.navigation", Category: oapi.TelemetryEventCategoryPage, Source: oapi.BrowserEventSource{Kind: oapi.Cdp}}) after := time.Now().UnixMicro() ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) @@ -137,14 +137,14 @@ func TestTelemetrySession(t *testing.T) { ts := newTestTelemetrySession(t) reader := ts.NewReader(0) - ts.Publish(events.Event{Type: "page.navigation", Category: events.CategoryPage, Source: oapi.BrowserEventSource{Kind: oapi.Cdp}, Ts: 1}) + ts.Publish(events.Event{Type: "page.navigation", Category: oapi.TelemetryEventCategoryPage, Source: oapi.BrowserEventSource{Kind: oapi.Cdp}, Ts: 1}) ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() env := readEnvelope(t, reader, ctx) assert.Equal(t, "page.navigation", env.Event.Type) - assert.Equal(t, events.CategoryPage, env.Event.Category) + assert.Equal(t, oapi.TelemetryEventCategoryPage, env.Event.Category) }) t.Run("start_sets_telemetry_session_id_in_source_metadata", func(t *testing.T) { @@ -152,7 +152,7 @@ func TestTelemetrySession(t *testing.T) { ts.Start("test-uuid", TelemetryConfig{}) reader := ts.NewReader(0) - ts.Publish(events.Event{Type: "page.navigation", Category: events.CategoryPage, Source: oapi.BrowserEventSource{Kind: oapi.Cdp}, Ts: 1}) + ts.Publish(events.Event{Type: "page.navigation", Category: oapi.TelemetryEventCategoryPage, Source: oapi.BrowserEventSource{Kind: oapi.Cdp}, Ts: 1}) ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() @@ -168,7 +168,7 @@ func TestTelemetrySession(t *testing.T) { reader := ts.NewReader(0) ts.Publish(events.Event{ Type: "page.navigation", - Category: events.CategoryPage, + Category: oapi.TelemetryEventCategoryPage, Source: oapi.BrowserEventSource{Kind: oapi.Cdp}, Ts: 1, Data: json.RawMessage(`{"url":"https://example.com"}`), @@ -185,16 +185,16 @@ func TestTelemetrySession(t *testing.T) { t.Run("system_events_always_captured_regardless_of_config", func(t *testing.T) { ts := NewTelemetrySession(newTestEventStream(t, 10)) // Start with only console category — system should still pass through. - ts.Start("sys-test", TelemetryConfig{Categories: []events.EventCategory{events.CategoryConsole}}) + ts.Start("sys-test", TelemetryConfig{Categories: []oapi.TelemetryEventCategory{oapi.TelemetryEventCategoryConsole}}) reader := ts.NewReader(0) - ts.Publish(events.Event{Type: "monitor.disconnected", Category: events.CategorySystem, Source: oapi.BrowserEventSource{Kind: oapi.KernelApi}, Ts: 1}) + ts.Publish(events.Event{Type: "monitor.disconnected", Category: oapi.TelemetryEventCategorySystem, Source: oapi.BrowserEventSource{Kind: oapi.KernelApi}, Ts: 1}) ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() env := readEnvelope(t, reader, ctx) - assert.Equal(t, events.CategorySystem, env.Event.Category) + assert.Equal(t, oapi.TelemetryEventCategorySystem, env.Event.Category) }) t.Run("truncation_applied", func(t *testing.T) { @@ -207,7 +207,7 @@ func TestTelemetrySession(t *testing.T) { ts.Publish(events.Event{ Type: "page.navigation", - Category: events.CategoryPage, + Category: oapi.TelemetryEventCategoryPage, Source: oapi.BrowserEventSource{Kind: oapi.Cdp}, Ts: 1, Data: json.RawMessage(rawData), From bd86bf7546dcd58c0d3146896829aa019ce8ea59 Mon Sep 17 00:00:00 2001 From: Archan Datta Date: Fri, 15 May 2026 10:49:04 -0300 Subject: [PATCH 20/28] feat: add KnownBrowserTelemetryEvent union and construct payloads via typed oapi structs --- server/lib/cdpmonitor/computed.go | 37 +- server/lib/cdpmonitor/computed_test.go | 12 +- server/lib/cdpmonitor/handlers.go | 308 +- server/lib/cdpmonitor/handlers_test.go | 21 +- server/lib/cdpmonitor/monitor.go | 30 +- server/lib/cdpmonitor/monitor_test.go | 3 +- server/lib/cdpmonitor/screenshot.go | 16 +- server/lib/cdpmonitor/util.go | 20 + server/lib/oapi/oapi.go | 4661 ++++++++++++++++++------ server/oapi-codegen.yaml | 5 +- server/openapi.yaml | 89 +- 11 files changed, 3983 insertions(+), 1219 deletions(-) diff --git a/server/lib/cdpmonitor/computed.go b/server/lib/cdpmonitor/computed.go index 646ff621..3921089b 100644 --- a/server/lib/cdpmonitor/computed.go +++ b/server/lib/cdpmonitor/computed.go @@ -100,6 +100,20 @@ func (s *computedState) navDataWith(extra map[string]any) json.RawMessage { return out } + +// currentNavCtxFields returns the current nav context fields for constructing typed event payloads. +// Returns zero values if s is nil (before first navigation). +func (s *computedState) currentNavCtxFields() (sessionID, targetID, targetType, frameID, loaderID, url string, navSeq int64) { + if s == nil { + return + } + s.mu.Lock() + ctx := s.navCtx + seq := int64(s.navSeq) + s.mu.Unlock() + return ctx.sessionID, ctx.targetID, ctx.targetType, ctx.frameID, ctx.loaderID, ctx.url, seq +} + func stopTimer(t *time.Timer) { if t == nil { return @@ -211,7 +225,6 @@ func (s *computedState) startNetIdleTimer() { } stopTimer(s.netTimer) navSeq := s.navSeq - navData := s.navData navMeta := s.navMeta s.netTimer = time.AfterFunc(networkIdleDebounce, func() { s.mu.Lock() @@ -220,13 +233,15 @@ func (s *computedState) startNetIdleTimer() { return } s.netFired = true + ctx := s.navCtx + seq := s.navSeq s.mu.Unlock() s.publish(events.Event{ Ts: time.Now().UnixMicro(), Type: EventNetworkIdle, - Category: oapi.TelemetryEventCategoryNetwork, + Category: events.Network, Source: oapi.BrowserEventSource{Kind: oapi.Cdp, Metadata: &navMeta}, - Data: navData, + Data: marshalNavEventContext(ctx, seq), }) }) } @@ -270,14 +285,15 @@ func (s *computedState) emitLayoutSettled(navSeq int) { } s.layoutFired = true s.navLayoutSettled = true - navData := s.navData + ctx := s.navCtx + seq := s.navSeq navMeta := s.navMeta evs := []events.Event{{ Ts: time.Now().UnixMicro(), Type: EventLayoutSettled, - Category: oapi.TelemetryEventCategoryPage, + Category: events.Page, Source: oapi.BrowserEventSource{Kind: oapi.Cdp, Metadata: &navMeta}, - Data: navData, + Data: marshalNavEventContext(ctx, seq), }} evs = append(evs, s.pendingNavigationSettled()...) s.mu.Unlock() @@ -305,12 +321,15 @@ func (s *computedState) pendingNavigationSettled() []events.Event { } if s.navDOMLoaded && s.navLayoutSettled && !s.navFired { s.navFired = true + ctx := s.navCtx + seq := s.navSeq + navMeta := s.navMeta return []events.Event{{ Ts: time.Now().UnixMicro(), Type: EventNavigationSettled, - Category: oapi.TelemetryEventCategoryPage, - Source: oapi.BrowserEventSource{Kind: oapi.Cdp, Metadata: &s.navMeta}, - Data: s.navData, + Category: events.Page, + Source: oapi.BrowserEventSource{Kind: oapi.Cdp, Metadata: &navMeta}, + Data: marshalNavEventContext(ctx, seq), }} } return nil diff --git a/server/lib/cdpmonitor/computed_test.go b/server/lib/cdpmonitor/computed_test.go index 15bc8b01..c67ac06f 100644 --- a/server/lib/cdpmonitor/computed_test.go +++ b/server/lib/cdpmonitor/computed_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - oapi "github.com/kernel/kernel-images/server/lib/oapi" + "github.com/kernel/kernel-images/server/lib/events" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -34,7 +34,7 @@ func TestNetworkIdle(t *testing.T) { ev := ec.waitFor(t, "network_idle", 2*time.Second) assert.GreaterOrEqual(t, time.Since(t0).Milliseconds(), int64(400), "fired too early") - assert.Equal(t, oapi.TelemetryEventCategoryNetwork, ev.Category) + assert.Equal(t, events.Network, ev.Category) }) t.Run("timer_reset_on_new_request", func(t *testing.T) { @@ -64,7 +64,7 @@ func TestLayoutSettled(t *testing.T) { ev := ec.waitFor(t, "page_layout_settled", 3*time.Second) assert.GreaterOrEqual(t, time.Since(t0).Milliseconds(), int64(900), "fired too early") - assert.Equal(t, oapi.TelemetryEventCategoryPage, ev.Category) + assert.Equal(t, events.Page, ev.Category) }) t.Run("layout_shift_before_page_load_ignored", func(t *testing.T) { @@ -104,7 +104,7 @@ func TestNavigationSettled(t *testing.T) { cs.onPageLoad() ev := ec.waitFor(t, "page_navigation_settled", 3*time.Second) - assert.Equal(t, oapi.TelemetryEventCategoryPage, ev.Category) + assert.Equal(t, events.Page, ev.Category) }) t.Run("not_blocked_by_pending_network_request", func(t *testing.T) { @@ -148,7 +148,7 @@ func TestNavDataMetadata(t *testing.T) { cs.onPageLoad() ev := ec.waitFor(t, "page_layout_settled", 3*time.Second) - assert.Equal(t, oapi.TelemetryEventCategoryPage, ev.Category) + assert.Equal(t, events.Page, ev.Category) assert.Equal(t, "s1", (*ev.Source.Metadata)[MetadataKeyCDPSessionID]) assert.Equal(t, "t1", (*ev.Source.Metadata)[MetadataKeyTargetID]) assert.Equal(t, "page", (*ev.Source.Metadata)[MetadataKeyTargetType]) @@ -167,7 +167,7 @@ func TestNavDataMetadata(t *testing.T) { cs.onPageLoad() ev := ec.waitFor(t, "page_navigation_settled", 3*time.Second) - assert.Equal(t, oapi.TelemetryEventCategoryPage, ev.Category) + assert.Equal(t, events.Page, ev.Category) assert.Equal(t, "s1", (*ev.Source.Metadata)[MetadataKeyCDPSessionID]) assert.Equal(t, "t1", (*ev.Source.Metadata)[MetadataKeyTargetID]) var data map[string]any diff --git a/server/lib/cdpmonitor/handlers.go b/server/lib/cdpmonitor/handlers.go index 52f19c31..86a517da 100644 --- a/server/lib/cdpmonitor/handlers.go +++ b/server/lib/cdpmonitor/handlers.go @@ -144,26 +144,52 @@ func (m *Monitor) handleConsole(p cdpRuntimeConsoleAPICalledParams, sessionID st eventType = EventConsoleError } cs := m.computedFor(sessionID) - data := cs.navDataWith(map[string]any{ - "level": p.Type, - "text": text, - "args": argValues, - "stack_trace": p.StackTrace, + sid, tid, ttype, fid, lid, url, nseq := cs.currentNavCtxFields() + var stackTrace *oapi.BrowserCallStack + if len(p.StackTrace) > 0 { + stackTrace = &oapi.BrowserCallStack{} + _ = json.Unmarshal(p.StackTrace, stackTrace) + } + data, _ := json.Marshal(oapi.BrowserConsoleLogEventData{ + SessionId: sid, + TargetId: tid, + TargetType: oapi.BrowserConsoleLogEventDataTargetType(ttype), + FrameId: ptrOf(fid), + LoaderId: ptrOf(lid), + Url: ptrOf(url), + NavSeq: nseq, + Level: p.Type, + Text: text, + Args: ptrOf(argValues), + StackTrace: stackTrace, }) - m.publishEvent(eventType, oapi.TelemetryEventCategoryConsole, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Runtime.consoleAPICalled", data, sessionID) + m.publishEvent(eventType, events.Console, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Runtime.consoleAPICalled", data, sessionID) } func (m *Monitor) handleExceptionThrown(ctx context.Context, p cdpRuntimeExceptionThrownParams, sessionID string) { cs := m.computedFor(sessionID) + sid, tid, ttype, fid, lid, url, nseq := cs.currentNavCtxFields() + var stackTrace *oapi.BrowserCallStack + if len(p.ExceptionDetails.StackTrace) > 0 { + stackTrace = &oapi.BrowserCallStack{} + _ = json.Unmarshal(p.ExceptionDetails.StackTrace, stackTrace) + } // source_url is the script file URL; distinct from nav context's url (the page URL). - data := cs.navDataWith(map[string]any{ - "text": p.ExceptionDetails.Text, - "line": p.ExceptionDetails.LineNumber, - "column": p.ExceptionDetails.ColumnNumber, - "source_url": p.ExceptionDetails.URL, - "stack_trace": p.ExceptionDetails.StackTrace, + data, _ := json.Marshal(oapi.BrowserConsoleErrorEventData{ + SessionId: sid, + TargetId: tid, + TargetType: oapi.BrowserConsoleErrorEventDataTargetType(ttype), + FrameId: ptrOf(fid), + LoaderId: ptrOf(lid), + Url: ptrOf(url), + NavSeq: nseq, + Text: p.ExceptionDetails.Text, + Line: ptrOf(p.ExceptionDetails.LineNumber), + Column: ptrOf(p.ExceptionDetails.ColumnNumber), + SourceUrl: ptrOf(p.ExceptionDetails.URL), + StackTrace: stackTrace, }) - m.publishEvent(EventConsoleError, oapi.TelemetryEventCategoryConsole, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Runtime.exceptionThrown", data, sessionID) + m.publishEvent(EventConsoleError, events.Console, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Runtime.exceptionThrown", data, sessionID) m.tryScreenshot(ctx, "Runtime.exceptionThrown", sessionID) } @@ -210,7 +236,7 @@ func (m *Monitor) handleBindingCalled(p cdpRuntimeBindingCalledParams, sessionID var payloadMap map[string]any _ = json.Unmarshal(payload, &payloadMap) cs := m.computedFor(sessionID) - m.publishEvent(header.Type, oapi.TelemetryEventCategoryInteraction, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Runtime.bindingCalled", cs.navDataWith(payloadMap), sessionID) + m.publishEvent(header.Type, events.Interaction, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Runtime.bindingCalled", cs.navDataWith(payloadMap), sessionID) } // handleTimelineEvent processes PerformanceTimeline layout-shift and LCP events. @@ -219,44 +245,70 @@ func (m *Monitor) handleTimelineEvent(p cdpPerformanceTimelineEventAddedParams, case timelineEventLayoutShift: // source_frame_id is the frame where the shift occurred; distinct from nav // context's frame_id (the top-level navigated frame). - ev := map[string]any{ - "source_frame_id": p.Event.FrameID, - "time": p.Event.Time, - "duration": p.Event.Duration, + cs := m.computedFor(sessionID) + sid, tid, ttype, fid, lid, url, nseq := cs.currentNavCtxFields() + payload := oapi.BrowserPageLayoutShiftEventData{ + SessionId: sid, + TargetId: tid, + TargetType: oapi.BrowserPageLayoutShiftEventDataTargetType(ttype), + FrameId: ptrOf(fid), + LoaderId: ptrOf(lid), + Url: ptrOf(url), + NavSeq: nseq, + SourceFrameId: p.Event.FrameID, + Time: float32(p.Event.Time), + Duration: float32(p.Event.Duration), } var shift cdpLayoutShiftDetails if p.Event.LayoutShiftDetails != nil && json.Unmarshal(p.Event.LayoutShiftDetails, &shift) == nil { - ev["layout_shift_details"] = map[string]any{ - "value": shift.Value, - "had_recent_input": shift.HadRecentInput, + payload.LayoutShiftDetails = &struct { + HadRecentInput *bool `json:"had_recent_input,omitempty"` + Value *float32 `json:"value,omitempty"` + }{ + Value: ptrOf(float32(shift.Value)), + HadRecentInput: ptrOf(shift.HadRecentInput), } } - cs := m.computedFor(sessionID) - data := cs.navDataWith(ev) - m.publishEvent(EventLayoutShift, oapi.TelemetryEventCategoryPage, oapi.BrowserEventSource{Kind: oapi.Cdp}, "PerformanceTimeline.timelineEventAdded", data, sessionID) + data, _ := json.Marshal(payload) + m.publishEvent(EventLayoutShift, events.Page, oapi.BrowserEventSource{Kind: oapi.Cdp}, "PerformanceTimeline.timelineEventAdded", data, sessionID) if cs != nil { cs.onLayoutShift() } case timelineEventLCP: - ev := map[string]any{ - "source_frame_id": p.Event.FrameID, - "time": p.Event.Time, + cs := m.computedFor(sessionID) + sid, tid, ttype, fid, lid, url, nseq := cs.currentNavCtxFields() + lcpPayload := oapi.BrowserPageLcpEventData{ + SessionId: sid, + TargetId: tid, + TargetType: oapi.BrowserPageLcpEventDataTargetType(ttype), + FrameId: ptrOf(fid), + LoaderId: ptrOf(lid), + Url: ptrOf(url), + NavSeq: nseq, + SourceFrameId: p.Event.FrameID, + Time: float32(p.Event.Time), } var lcp cdpLcpDetails if p.Event.LcpDetails != nil && json.Unmarshal(p.Event.LcpDetails, &lcp) == nil { - ev["lcp_details"] = map[string]any{ - "render_time": lcp.RenderTime, - "load_time": lcp.LoadTime, - "size": lcp.Size, - "element_id": lcp.ElementID, - "url": lcp.URL, - "node_id": lcp.NodeID, + lcpPayload.LcpDetails = &struct { + ElementId *string `json:"element_id,omitempty"` + LoadTime *float32 `json:"load_time,omitempty"` + NodeId *int `json:"node_id,omitempty"` + RenderTime *float32 `json:"render_time,omitempty"` + Size *float32 `json:"size,omitempty"` + Url *string `json:"url,omitempty"` + }{ + RenderTime: ptrOf(float32(lcp.RenderTime)), + LoadTime: ptrOf(float32(lcp.LoadTime)), + Size: ptrOf(float32(lcp.Size)), + ElementId: ptrOf(lcp.ElementID), + Url: ptrOf(lcp.URL), + NodeId: ptrOf(lcp.NodeID), } } - cs := m.computedFor(sessionID) - data := cs.navDataWith(ev) - m.publishEvent(EventLCP, oapi.TelemetryEventCategoryPage, oapi.BrowserEventSource{Kind: oapi.Cdp}, "PerformanceTimeline.timelineEventAdded", data, sessionID) + data, _ := json.Marshal(lcpPayload) + m.publishEvent(EventLCP, events.Page, oapi.BrowserEventSource{Kind: oapi.Cdp}, "PerformanceTimeline.timelineEventAdded", data, sessionID) } } @@ -300,35 +352,39 @@ func (m *Monitor) handleNetworkRequest(p cdpNetworkRequestWillBeSentParams, sess m.sessionsMu.RLock() info := m.sessions[sessionID] m.sessionsMu.RUnlock() - ev := map[string]any{ - "session_id": sessionID, - "target_id": info.targetID, - "target_type": info.targetType, - "request_id": p.RequestID, - "loader_id": p.LoaderID, - "frame_id": p.FrameID, - "document_url": p.DocumentURL, - "method": p.Request.Method, - "url": p.Request.URL, - "headers": p.Request.Headers, - "initiator_type": initiatorType, + cs := m.computedFor(sessionID) + var navSeq int64 + if cs != nil { + navSeq = int64(cs.currentNavSeq()) + } + var hdrs oapi.BrowserHttpHeaders + _ = json.Unmarshal(p.Request.Headers, &hdrs) + payload := oapi.BrowserNetworkRequestEventData{ + SessionId: sessionID, + TargetId: info.targetID, + TargetType: oapi.BrowserNetworkRequestEventDataTargetType(info.targetType), + FrameId: ptrOf(p.FrameID), + LoaderId: ptrOf(p.LoaderID), + Url: ptrOf(p.Request.URL), + NavSeq: navSeq, + RequestId: p.RequestID, + Method: p.Request.Method, + DocumentUrl: p.DocumentURL, + Headers: hdrs, + InitiatorType: initiatorType, } if p.Request.PostData != "" { - ev["post_data"] = p.Request.PostData + payload.PostData = ptrOf(p.Request.PostData) } if p.Type != "" { - ev["resource_type"] = p.Type + payload.ResourceType = ptrOf(p.Type) } if isRedirect { - ev["is_redirect"] = true - ev["redirect_url"] = existing.url - } - cs := m.computedFor(sessionID) - if cs != nil { - ev["nav_seq"] = cs.currentNavSeq() + payload.IsRedirect = ptrOf(true) + payload.RedirectUrl = ptrOf(existing.url) } - data, _ := json.Marshal(ev) - m.publishEvent(EventNetworkRequest, oapi.TelemetryEventCategoryNetwork, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Network.requestWillBeSent", data, sessionID) + data, _ := json.Marshal(payload) + m.publishEvent(EventNetworkRequest, events.Network, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Network.requestWillBeSent", data, sessionID) if !isRedirect && cs != nil { cs.onRequest() } @@ -367,33 +423,35 @@ func (m *Monitor) handleLoadingFinished(ctx context.Context, p cdpNetworkLoading // Fetch response body async to avoid blocking readLoop; binary types are skipped. m.asyncWg.Go(func() { body := m.fetchResponseBody(ctx, p.RequestID, sessionID, state) - ev := map[string]any{ - "session_id": sessionID, - "target_id": info.targetID, - "target_type": info.targetType, - "request_id": p.RequestID, - "loader_id": state.loaderID, - "frame_id": state.frameID, - "method": state.method, - "url": state.url, - "status": state.status, - "headers": state.resHeaders, - "nav_seq": navSeq, + var hdrs oapi.BrowserHttpHeaders + _ = json.Unmarshal(state.resHeaders, &hdrs) + resPayload := oapi.BrowserNetworkResponseEventData{ + SessionId: sessionID, + TargetId: info.targetID, + TargetType: oapi.BrowserNetworkResponseEventDataTargetType(info.targetType), + FrameId: ptrOf(state.frameID), + LoaderId: ptrOf(state.loaderID), + Url: ptrOf(state.url), + NavSeq: int64(navSeq), + RequestId: p.RequestID, + Method: state.method, + Status: state.status, + Headers: hdrs, } if state.statusText != "" { - ev["status_text"] = state.statusText + resPayload.StatusText = ptrOf(state.statusText) } if state.mimeType != "" { - ev["mime_type"] = state.mimeType + resPayload.MimeType = ptrOf(state.mimeType) } if state.resourceType != "" { - ev["resource_type"] = state.resourceType + resPayload.ResourceType = ptrOf(state.resourceType) } if body != "" { - ev["body"] = body + resPayload.Body = ptrOf(body) } - data, _ := json.Marshal(ev) - m.publishEvent(EventNetworkResponse, oapi.TelemetryEventCategoryNetwork, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Network.loadingFinished", data, sessionID) + data, _ := json.Marshal(resPayload) + m.publishEvent(EventNetworkResponse, events.Network, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Network.loadingFinished", data, sessionID) }) } @@ -437,25 +495,29 @@ func (m *Monitor) handleLoadingFailed(p cdpNetworkLoadingFailedParams, sessionID m.sessionsMu.RLock() info := m.sessions[sessionID] m.sessionsMu.RUnlock() - ev := map[string]any{ - "session_id": sessionID, - "target_id": info.targetID, - "target_type": info.targetType, - "request_id": p.RequestID, - "error_text": p.ErrorText, - "canceled": p.Canceled, + var nseq int64 + if cs := m.computedFor(sessionID); cs != nil { + nseq = int64(cs.currentNavSeq()) } - if ok { - ev["url"] = state.url - ev["loader_id"] = state.loaderID - ev["frame_id"] = state.frameID - ev["resource_type"] = state.resourceType + failPayload := oapi.BrowserNetworkLoadingFailedEventData{ + SessionId: sessionID, + TargetId: info.targetID, + TargetType: oapi.BrowserNetworkLoadingFailedEventDataTargetType(info.targetType), + NavSeq: nseq, + RequestId: p.RequestID, + ErrorText: p.ErrorText, + Canceled: p.Canceled, } - if cs := m.computedFor(sessionID); cs != nil { - ev["nav_seq"] = cs.currentNavSeq() + if ok { + failPayload.Url = ptrOf(state.url) + failPayload.LoaderId = ptrOf(state.loaderID) + failPayload.FrameId = ptrOf(state.frameID) + if state.resourceType != "" { + failPayload.ResourceType = ptrOf(state.resourceType) + } } - data, _ := json.Marshal(ev) - m.publishEvent(EventNetworkLoadingFailed, oapi.TelemetryEventCategoryNetwork, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Network.loadingFailed", data, sessionID) + data, _ := json.Marshal(failPayload) + m.publishEvent(EventNetworkLoadingFailed, events.Network, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Network.loadingFailed", data, sessionID) if ok { if cs := m.computedFor(state.sessionID); cs != nil { cs.onLoadingFinished() @@ -471,16 +533,16 @@ func (m *Monitor) handleFrameNavigated(p cdpPageFrameNavigatedParams, sessionID cs := m.computedStates[sessionID] m.sessionsMu.RUnlock() - data, _ := json.Marshal(map[string]any{ - "session_id": sessionID, - "target_id": info.targetID, - "target_type": info.targetType, - "url": p.Frame.URL, - "frame_id": p.Frame.ID, - "parent_frame_id": p.Frame.ParentID, - "loader_id": p.Frame.LoaderID, + data, _ := json.Marshal(oapi.BrowserPageNavigationEventData{ + SessionId: sessionID, + TargetId: info.targetID, + TargetType: oapi.BrowserPageNavigationEventDataTargetType(info.targetType), + Url: p.Frame.URL, + FrameId: p.Frame.ID, + ParentFrameId: ptrOf(p.Frame.ParentID), + LoaderId: p.Frame.LoaderID, }) - m.publishEvent(EventNavigation, oapi.TelemetryEventCategoryPage, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Page.frameNavigated", data, sessionID) + m.publishEvent(EventNavigation, events.Page, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Page.frameNavigated", data, sessionID) // Only reset state for top-level navigations; subframe (iframe) navigations // should not disrupt main-page tracking. @@ -518,8 +580,18 @@ func (m *Monitor) handleFrameNavigated(p cdpPageFrameNavigatedParams, sessionID func (m *Monitor) handleDOMContentLoaded(p cdpPageDomContentEventFiredParams, sessionID string) { cs := m.computedFor(sessionID) - data := cs.navDataWith(map[string]any{"cdp_timestamp": p.Timestamp}) - m.publishEvent(EventDOMContentLoaded, oapi.TelemetryEventCategoryPage, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Page.domContentEventFired", data, sessionID) + sid, tid, ttype, fid, lid, url, nseq := cs.currentNavCtxFields() + data, _ := json.Marshal(oapi.BrowserPageDomContentLoadedEventData{ + SessionId: sid, + TargetId: tid, + TargetType: oapi.BrowserPageDomContentLoadedEventDataTargetType(ttype), + FrameId: ptrOf(fid), + LoaderId: ptrOf(lid), + Url: ptrOf(url), + NavSeq: nseq, + CdpTimestamp: float32(p.Timestamp), + }) + m.publishEvent(EventDOMContentLoaded, events.Page, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Page.domContentEventFired", data, sessionID) if cs != nil { cs.onDOMContentLoaded() } @@ -527,8 +599,18 @@ func (m *Monitor) handleDOMContentLoaded(p cdpPageDomContentEventFiredParams, se func (m *Monitor) handleLoadEventFired(ctx context.Context, p cdpPageLoadEventFiredParams, sessionID string) { cs := m.computedFor(sessionID) - data := cs.navDataWith(map[string]any{"cdp_timestamp": p.Timestamp}) - m.publishEvent(EventPageLoad, oapi.TelemetryEventCategoryPage, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Page.loadEventFired", data, sessionID) + sid, tid, ttype, fid, lid, url, nseq := cs.currentNavCtxFields() + data, _ := json.Marshal(oapi.BrowserPageLoadEventData{ + SessionId: sid, + TargetId: tid, + TargetType: oapi.BrowserPageLoadEventDataTargetType(ttype), + FrameId: ptrOf(fid), + LoaderId: ptrOf(lid), + Url: ptrOf(url), + NavSeq: nseq, + CdpTimestamp: float32(p.Timestamp), + }) + m.publishEvent(EventPageLoad, events.Page, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Page.loadEventFired", data, sessionID) if cs != nil { cs.onPageLoad() } @@ -553,14 +635,14 @@ func (m *Monitor) handleAttachedToTarget(ctx context.Context, p cdpTargetAttache m.sessionsMu.Unlock() if p.TargetInfo.Type == targetTypePage { - data, _ := json.Marshal(map[string]any{ - "target_id": p.TargetInfo.TargetID, - "target_type": p.TargetInfo.Type, - "url": p.TargetInfo.URL, - "opener_id": p.TargetInfo.OpenerID, - "title": p.TargetInfo.Title, + data, _ := json.Marshal(oapi.BrowserPageTabOpenedEventData{ + TargetId: p.TargetInfo.TargetID, + TargetType: oapi.BrowserPageTabOpenedEventDataTargetType(p.TargetInfo.Type), + Url: p.TargetInfo.URL, + OpenerId: ptrOf(p.TargetInfo.OpenerID), + Title: ptrOf(p.TargetInfo.Title), }) - m.publishEvent(EventTabOpened, oapi.TelemetryEventCategoryPage, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Target.attachedToTarget", data, p.SessionID) + m.publishEvent(EventTabOpened, events.Page, oapi.BrowserEventSource{Kind: oapi.Cdp}, "Target.attachedToTarget", data, p.SessionID) } targetType := p.TargetInfo.Type diff --git a/server/lib/cdpmonitor/handlers_test.go b/server/lib/cdpmonitor/handlers_test.go index 636cad04..cb7fe6e4 100644 --- a/server/lib/cdpmonitor/handlers_test.go +++ b/server/lib/cdpmonitor/handlers_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "github.com/kernel/kernel-images/server/lib/events" oapi "github.com/kernel/kernel-images/server/lib/oapi" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -27,7 +28,7 @@ func TestConsoleEvents(t *testing.T) { }, }) ev := ec.waitFor(t, "console_log", 2*time.Second) - assert.Equal(t, oapi.TelemetryEventCategoryConsole, ev.Category) + assert.Equal(t, events.Console, ev.Category) assert.Equal(t, oapi.Cdp, ev.Source.Kind) assert.Equal(t, "Runtime.consoleAPICalled", *ev.Source.Event) var data map[string]any @@ -50,7 +51,7 @@ func TestConsoleEvents(t *testing.T) { }, }) ev := ec.waitFor(t, "console_error", 2*time.Second) - assert.Equal(t, oapi.TelemetryEventCategoryConsole, ev.Category) + assert.Equal(t, events.Console, ev.Category) var data map[string]any require.NoError(t, json.Unmarshal(ev.Data, &data)) assert.Equal(t, "Uncaught TypeError", data["text"]) @@ -112,7 +113,7 @@ func TestNetworkEvents(t *testing.T) { }, }) ev := ec.waitFor(t, "network_request", 2*time.Second) - assert.Equal(t, oapi.TelemetryEventCategoryNetwork, ev.Category) + assert.Equal(t, events.Network, ev.Category) assert.Equal(t, "Network.requestWillBeSent", *ev.Source.Event) var data map[string]any @@ -163,7 +164,7 @@ func TestNetworkEvents(t *testing.T) { }, }) ev := ec.waitFor(t, "network_loading_failed", 2*time.Second) - assert.Equal(t, oapi.TelemetryEventCategoryNetwork, ev.Category) + assert.Equal(t, events.Network, ev.Category) var data map[string]any require.NoError(t, json.Unmarshal(ev.Data, &data)) assert.Equal(t, "net::ERR_CONNECTION_REFUSED", data["error_text"]) @@ -232,7 +233,7 @@ func TestPageEvents(t *testing.T) { }, }) ev := ec.waitFor(t, "page_navigation", 2*time.Second) - assert.Equal(t, oapi.TelemetryEventCategoryPage, ev.Category) + assert.Equal(t, events.Page, ev.Category) assert.Equal(t, "Page.frameNavigated", *ev.Source.Event) var data map[string]any require.NoError(t, json.Unmarshal(ev.Data, &data)) @@ -243,7 +244,7 @@ func TestPageEvents(t *testing.T) { "params": map[string]any{"timestamp": 1000.0}, }) ev2 := ec.waitFor(t, "page_dom_content_loaded", 2*time.Second) - assert.Equal(t, oapi.TelemetryEventCategoryPage, ev2.Category) + assert.Equal(t, events.Page, ev2.Category) var data2 map[string]any require.NoError(t, json.Unmarshal(ev2.Data, &data2)) assert.Equal(t, float64(1000.0), data2["cdp_timestamp"]) @@ -255,7 +256,7 @@ func TestPageEvents(t *testing.T) { "params": map[string]any{"timestamp": 1001.0}, }) ev3 := ec.waitFor(t, "page_load", 2*time.Second) - assert.Equal(t, oapi.TelemetryEventCategoryPage, ev3.Category) + assert.Equal(t, events.Page, ev3.Category) var data3 map[string]any require.NoError(t, json.Unmarshal(ev3.Data, &data3)) assert.Equal(t, float64(1001.0), data3["cdp_timestamp"]) @@ -283,7 +284,7 @@ func TestTabOpened(t *testing.T) { }, }) ev := ec.waitFor(t, "page_tab_opened", 2*time.Second) - assert.Equal(t, oapi.TelemetryEventCategoryPage, ev.Category) + assert.Equal(t, events.Page, ev.Category) assert.Equal(t, "Target.attachedToTarget", *ev.Source.Event) var data map[string]any require.NoError(t, json.Unmarshal(ev.Data, &data)) @@ -325,7 +326,7 @@ func TestBindingAndTimeline(t *testing.T) { }, }) ev := ec.waitFor(t, "interaction_click", 2*time.Second) - assert.Equal(t, oapi.TelemetryEventCategoryInteraction, ev.Category) + assert.Equal(t, events.Interaction, ev.Category) assert.Equal(t, "Runtime.bindingCalled", *ev.Source.Event) }) @@ -338,7 +339,7 @@ func TestBindingAndTimeline(t *testing.T) { }, }) ev := ec.waitFor(t, "interaction_scroll_settled", 2*time.Second) - assert.Equal(t, oapi.TelemetryEventCategoryInteraction, ev.Category) + assert.Equal(t, events.Interaction, ev.Category) var data map[string]any require.NoError(t, json.Unmarshal(ev.Data, &data)) assert.Equal(t, float64(500), data["to_y"]) diff --git a/server/lib/cdpmonitor/monitor.go b/server/lib/cdpmonitor/monitor.go index f3b9807f..c34be25c 100644 --- a/server/lib/cdpmonitor/monitor.go +++ b/server/lib/cdpmonitor/monitor.go @@ -384,19 +384,22 @@ func (m *Monitor) initSession(ctx context.Context) { return } - if _, err := m.send(ctx, "Target.setAutoAttach", map[string]any{ + if _, err := m.send(ctx, cdpMethodSetAutoAttach, map[string]any{ "autoAttach": true, "waitForDebuggerOnStart": false, "flatten": true, }, ""); err != nil && ctx.Err() == nil { // Without auto-attach the monitor will never see new targets: treat as fatal. m.log.Error("cdpmonitor: Target.setAutoAttach failed — monitor will not observe new targets", "err", err) + initFailedData, _ := json.Marshal(oapi.BrowserMonitorInitFailedEventData{ + Step: cdpMethodSetAutoAttach, + }) m.publish(events.Event{ Ts: time.Now().UnixMicro(), Type: EventMonitorInitFailed, - Category: oapi.TelemetryEventCategorySystem, + Category: events.System, Source: oapi.BrowserEventSource{Kind: oapi.LocalProcess}, - Data: json.RawMessage(`{"step":"Target.setAutoAttach"}`), + Data: initFailedData, }) return } @@ -489,12 +492,15 @@ func (m *Monitor) handleUpstreamRestart(ctx context.Context, newURL string) { if ctx.Err() != nil { return } + disconnectedData, _ := json.Marshal(oapi.BrowserMonitorDisconnectedEventData{ + Reason: oapi.ChromeRestarted, + }) m.publish(events.Event{ Ts: time.Now().UnixMicro(), Type: EventMonitorDisconnected, - Category: oapi.TelemetryEventCategorySystem, + Category: events.System, Source: oapi.BrowserEventSource{Kind: oapi.LocalProcess}, - Data: json.RawMessage(`{"reason":"` + ReasonChromeRestarted + `"}`), + Data: disconnectedData, }) startReconnect := time.Now() @@ -520,12 +526,15 @@ func (m *Monitor) handleUpstreamRestart(ctx context.Context, newURL string) { m.lifeMu.Unlock() m.clearState() m.running.Store(false) + reconnectFailedData, _ := json.Marshal(oapi.BrowserMonitorReconnectFailedEventData{ + Reason: oapi.ReconnectExhausted, + }) m.publish(events.Event{ Ts: time.Now().UnixMicro(), Type: EventMonitorReconnectFailed, - Category: oapi.TelemetryEventCategorySystem, + Category: events.System, Source: oapi.BrowserEventSource{Kind: oapi.LocalProcess}, - Data: json.RawMessage(`{"reason":"` + ReasonReconnectExhausted + `"}`), + Data: reconnectFailedData, }) } return @@ -542,12 +551,15 @@ func (m *Monitor) handleUpstreamRestart(ctx context.Context, newURL string) { reconnectDurationMs := time.Since(startReconnect).Milliseconds() m.log.Info("cdpmonitor: reconnected", "url", newURL, "duration_ms", reconnectDurationMs) + reconnectedData, _ := json.Marshal(oapi.BrowserMonitorReconnectedEventData{ + ReconnectDurationMs: reconnectDurationMs, + }) m.publish(events.Event{ Ts: time.Now().UnixMicro(), Type: EventMonitorReconnected, - Category: oapi.TelemetryEventCategorySystem, + Category: events.System, Source: oapi.BrowserEventSource{Kind: oapi.LocalProcess}, - Data: json.RawMessage(fmt.Sprintf(`{"reconnect_duration_ms":%d}`, reconnectDurationMs)), + Data: reconnectedData, }) } diff --git a/server/lib/cdpmonitor/monitor_test.go b/server/lib/cdpmonitor/monitor_test.go index 09bd4b48..33c277fa 100644 --- a/server/lib/cdpmonitor/monitor_test.go +++ b/server/lib/cdpmonitor/monitor_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/kernel/kernel-images/server/lib/events" oapi "github.com/kernel/kernel-images/server/lib/oapi" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -87,7 +88,7 @@ func TestScreenshot(t *testing.T) { require.Eventually(t, func() bool { return captureCount.Load() == 1 }, 2*time.Second, 20*time.Millisecond) ev := ec.waitFor(t, "monitor_screenshot", 2*time.Second) - assert.Equal(t, oapi.TelemetryEventCategorySystem, ev.Category) + assert.Equal(t, events.System, ev.Category) assert.Equal(t, oapi.LocalProcess, ev.Source.Kind) require.NotNil(t, ev.Source.Event) assert.Equal(t, "Page.loadEventFired", *ev.Source.Event) diff --git a/server/lib/cdpmonitor/screenshot.go b/server/lib/cdpmonitor/screenshot.go index 9d6cfae7..2fff5698 100644 --- a/server/lib/cdpmonitor/screenshot.go +++ b/server/lib/cdpmonitor/screenshot.go @@ -3,10 +3,8 @@ package cdpmonitor import ( "bytes" "context" - "encoding/base64" "encoding/json" "fmt" - "maps" "os/exec" "time" @@ -77,20 +75,14 @@ func (m *Monitor) captureScreenshot(parentCtx context.Context, sourceEvent strin } } - encoded := base64.StdEncoding.EncodeToString(pngBytes) - payload := map[string]any{screenshotDataKey: encoded} - if navData != nil { - var nav map[string]any - if json.Unmarshal(navData, &nav) == nil { - maps.Copy(payload, nav) - } - } - data, _ := json.Marshal(payload) + data, _ := json.Marshal(oapi.BrowserMonitorScreenshotEventData{ + Png: pngBytes, + }) m.publish(events.Event{ Ts: time.Now().UnixMicro(), Type: EventScreenshot, - Category: oapi.TelemetryEventCategorySystem, + Category: events.System, Source: oapi.BrowserEventSource{Kind: oapi.LocalProcess, Event: &sourceEvent, Metadata: &navMeta}, Data: data, }) diff --git a/server/lib/cdpmonitor/util.go b/server/lib/cdpmonitor/util.go index 2c5b6d34..d862d9c4 100644 --- a/server/lib/cdpmonitor/util.go +++ b/server/lib/cdpmonitor/util.go @@ -4,8 +4,28 @@ import ( "encoding/json" "strings" "unicode/utf8" + + oapi "github.com/kernel/kernel-images/server/lib/oapi" ) +func ptrOf[T any](v T) *T { return &v } + +// marshalNavEventContext marshals a navContext and sequence number into the +// BrowserEventContext JSON payload used by network_idle, page_layout_settled, +// and page_navigation_settled events. +func marshalNavEventContext(ctx navContext, seq int) json.RawMessage { + data, _ := json.Marshal(oapi.BrowserEventContext{ + SessionId: ctx.sessionID, + TargetId: ctx.targetID, + TargetType: oapi.BrowserEventContextTargetType(ctx.targetType), + FrameId: ptrOf(ctx.frameID), + LoaderId: ptrOf(ctx.loaderID), + Url: ptrOf(ctx.url), + NavSeq: int64(seq), + }) + return data +} + // consoleArgString extracts a display string from a CDP console argument. // For strings it unquotes the JSON value; for other types it returns the raw JSON. func consoleArgString(a cdpRuntimeRemoteObject) string { diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index f9cd97e4..809ede10 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -26,1328 +26,3810 @@ import ( openapi_types "github.com/oapi-codegen/runtime/types" ) -// Defines values for BrowserEventSourceKind. +// Defines values for BrowserConsoleErrorEventType. const ( - Cdp BrowserEventSourceKind = "cdp" - Extension BrowserEventSourceKind = "extension" - KernelApi BrowserEventSourceKind = "kernel_api" - LocalProcess BrowserEventSourceKind = "local_process" + ConsoleError BrowserConsoleErrorEventType = "console_error" ) -// Valid indicates whether the value is a known member of the BrowserEventSourceKind enum. -func (e BrowserEventSourceKind) Valid() bool { +// Valid indicates whether the value is a known member of the BrowserConsoleErrorEventType enum. +func (e BrowserConsoleErrorEventType) Valid() bool { switch e { - case Cdp: - return true - case Extension: - return true - case KernelApi: - return true - case LocalProcess: + case ConsoleError: return true default: return false } } -// Defines values for ClickMouseRequestButton. +// Defines values for BrowserConsoleErrorEventDataTargetType. const ( - ClickMouseRequestButtonBack ClickMouseRequestButton = "back" - ClickMouseRequestButtonForward ClickMouseRequestButton = "forward" - ClickMouseRequestButtonLeft ClickMouseRequestButton = "left" - ClickMouseRequestButtonMiddle ClickMouseRequestButton = "middle" - ClickMouseRequestButtonRight ClickMouseRequestButton = "right" + BrowserConsoleErrorEventDataTargetTypeBackgroundPage BrowserConsoleErrorEventDataTargetType = "background_page" + BrowserConsoleErrorEventDataTargetTypeOther BrowserConsoleErrorEventDataTargetType = "other" + BrowserConsoleErrorEventDataTargetTypePage BrowserConsoleErrorEventDataTargetType = "page" + BrowserConsoleErrorEventDataTargetTypeServiceWorker BrowserConsoleErrorEventDataTargetType = "service_worker" + BrowserConsoleErrorEventDataTargetTypeSharedWorker BrowserConsoleErrorEventDataTargetType = "shared_worker" ) -// Valid indicates whether the value is a known member of the ClickMouseRequestButton enum. -func (e ClickMouseRequestButton) Valid() bool { +// Valid indicates whether the value is a known member of the BrowserConsoleErrorEventDataTargetType enum. +func (e BrowserConsoleErrorEventDataTargetType) Valid() bool { switch e { - case ClickMouseRequestButtonBack: + case BrowserConsoleErrorEventDataTargetTypeBackgroundPage: return true - case ClickMouseRequestButtonForward: + case BrowserConsoleErrorEventDataTargetTypeOther: return true - case ClickMouseRequestButtonLeft: + case BrowserConsoleErrorEventDataTargetTypePage: return true - case ClickMouseRequestButtonMiddle: + case BrowserConsoleErrorEventDataTargetTypeServiceWorker: return true - case ClickMouseRequestButtonRight: + case BrowserConsoleErrorEventDataTargetTypeSharedWorker: return true default: return false } } -// Defines values for ClickMouseRequestClickType. +// Defines values for BrowserConsoleLogEventType. const ( - Click ClickMouseRequestClickType = "click" - Down ClickMouseRequestClickType = "down" - Up ClickMouseRequestClickType = "up" + ConsoleLog BrowserConsoleLogEventType = "console_log" ) -// Valid indicates whether the value is a known member of the ClickMouseRequestClickType enum. -func (e ClickMouseRequestClickType) Valid() bool { +// Valid indicates whether the value is a known member of the BrowserConsoleLogEventType enum. +func (e BrowserConsoleLogEventType) Valid() bool { switch e { - case Click: - return true - case Down: - return true - case Up: + case ConsoleLog: return true default: return false } } -// Defines values for ComputerActionType. +// Defines values for BrowserConsoleLogEventDataTargetType. const ( - ClickMouse ComputerActionType = "click_mouse" - DragMouse ComputerActionType = "drag_mouse" - MoveMouse ComputerActionType = "move_mouse" - PressKey ComputerActionType = "press_key" - Scroll ComputerActionType = "scroll" - SetCursor ComputerActionType = "set_cursor" - Sleep ComputerActionType = "sleep" - TypeText ComputerActionType = "type_text" + BrowserConsoleLogEventDataTargetTypeBackgroundPage BrowserConsoleLogEventDataTargetType = "background_page" + BrowserConsoleLogEventDataTargetTypeOther BrowserConsoleLogEventDataTargetType = "other" + BrowserConsoleLogEventDataTargetTypePage BrowserConsoleLogEventDataTargetType = "page" + BrowserConsoleLogEventDataTargetTypeServiceWorker BrowserConsoleLogEventDataTargetType = "service_worker" + BrowserConsoleLogEventDataTargetTypeSharedWorker BrowserConsoleLogEventDataTargetType = "shared_worker" ) -// Valid indicates whether the value is a known member of the ComputerActionType enum. -func (e ComputerActionType) Valid() bool { +// Valid indicates whether the value is a known member of the BrowserConsoleLogEventDataTargetType enum. +func (e BrowserConsoleLogEventDataTargetType) Valid() bool { switch e { - case ClickMouse: - return true - case DragMouse: - return true - case MoveMouse: - return true - case PressKey: + case BrowserConsoleLogEventDataTargetTypeBackgroundPage: return true - case Scroll: + case BrowserConsoleLogEventDataTargetTypeOther: return true - case SetCursor: + case BrowserConsoleLogEventDataTargetTypePage: return true - case Sleep: + case BrowserConsoleLogEventDataTargetTypeServiceWorker: return true - case TypeText: + case BrowserConsoleLogEventDataTargetTypeSharedWorker: return true default: return false } } -// Defines values for DragMouseRequestButton. +// Defines values for BrowserEventContextTargetType. const ( - DragMouseRequestButtonLeft DragMouseRequestButton = "left" - DragMouseRequestButtonMiddle DragMouseRequestButton = "middle" - DragMouseRequestButtonRight DragMouseRequestButton = "right" + BrowserEventContextTargetTypeBackgroundPage BrowserEventContextTargetType = "background_page" + BrowserEventContextTargetTypeOther BrowserEventContextTargetType = "other" + BrowserEventContextTargetTypePage BrowserEventContextTargetType = "page" + BrowserEventContextTargetTypeServiceWorker BrowserEventContextTargetType = "service_worker" + BrowserEventContextTargetTypeSharedWorker BrowserEventContextTargetType = "shared_worker" ) -// Valid indicates whether the value is a known member of the DragMouseRequestButton enum. -func (e DragMouseRequestButton) Valid() bool { +// Valid indicates whether the value is a known member of the BrowserEventContextTargetType enum. +func (e BrowserEventContextTargetType) Valid() bool { switch e { - case DragMouseRequestButtonLeft: + case BrowserEventContextTargetTypeBackgroundPage: return true - case DragMouseRequestButtonMiddle: + case BrowserEventContextTargetTypeOther: return true - case DragMouseRequestButtonRight: + case BrowserEventContextTargetTypePage: + return true + case BrowserEventContextTargetTypeServiceWorker: + return true + case BrowserEventContextTargetTypeSharedWorker: return true default: return false } } -// Defines values for FileSystemEventType. +// Defines values for BrowserEventSourceKind. const ( - CREATE FileSystemEventType = "CREATE" - DELETE FileSystemEventType = "DELETE" - RENAME FileSystemEventType = "RENAME" - WRITE FileSystemEventType = "WRITE" + Cdp BrowserEventSourceKind = "cdp" + Extension BrowserEventSourceKind = "extension" + KernelApi BrowserEventSourceKind = "kernel_api" + LocalProcess BrowserEventSourceKind = "local_process" ) -// Valid indicates whether the value is a known member of the FileSystemEventType enum. -func (e FileSystemEventType) Valid() bool { +// Valid indicates whether the value is a known member of the BrowserEventSourceKind enum. +func (e BrowserEventSourceKind) Valid() bool { switch e { - case CREATE: + case Cdp: return true - case DELETE: + case Extension: return true - case RENAME: + case KernelApi: return true - case WRITE: + case LocalProcess: return true default: return false } } -// Defines values for PatchDisplayRequestRefreshRate. +// Defines values for BrowserInteractionClickEventType. const ( - N10 PatchDisplayRequestRefreshRate = 10 - N25 PatchDisplayRequestRefreshRate = 25 - N30 PatchDisplayRequestRefreshRate = 30 - N60 PatchDisplayRequestRefreshRate = 60 + InteractionClick BrowserInteractionClickEventType = "interaction_click" ) -// Valid indicates whether the value is a known member of the PatchDisplayRequestRefreshRate enum. -func (e PatchDisplayRequestRefreshRate) Valid() bool { +// Valid indicates whether the value is a known member of the BrowserInteractionClickEventType enum. +func (e BrowserInteractionClickEventType) Valid() bool { switch e { - case N10: - return true - case N25: - return true - case N30: - return true - case N60: + case InteractionClick: return true default: return false } } -// Defines values for ProcessKillRequestSignal. +// Defines values for BrowserInteractionClickEventDataTargetType. const ( - HUP ProcessKillRequestSignal = "HUP" - INT ProcessKillRequestSignal = "INT" - KILL ProcessKillRequestSignal = "KILL" - TERM ProcessKillRequestSignal = "TERM" + BrowserInteractionClickEventDataTargetTypeBackgroundPage BrowserInteractionClickEventDataTargetType = "background_page" + BrowserInteractionClickEventDataTargetTypeOther BrowserInteractionClickEventDataTargetType = "other" + BrowserInteractionClickEventDataTargetTypePage BrowserInteractionClickEventDataTargetType = "page" + BrowserInteractionClickEventDataTargetTypeServiceWorker BrowserInteractionClickEventDataTargetType = "service_worker" + BrowserInteractionClickEventDataTargetTypeSharedWorker BrowserInteractionClickEventDataTargetType = "shared_worker" ) -// Valid indicates whether the value is a known member of the ProcessKillRequestSignal enum. -func (e ProcessKillRequestSignal) Valid() bool { +// Valid indicates whether the value is a known member of the BrowserInteractionClickEventDataTargetType enum. +func (e BrowserInteractionClickEventDataTargetType) Valid() bool { switch e { - case HUP: + case BrowserInteractionClickEventDataTargetTypeBackgroundPage: return true - case INT: + case BrowserInteractionClickEventDataTargetTypeOther: return true - case KILL: + case BrowserInteractionClickEventDataTargetTypePage: return true - case TERM: + case BrowserInteractionClickEventDataTargetTypeServiceWorker: + return true + case BrowserInteractionClickEventDataTargetTypeSharedWorker: return true default: return false } } -// Defines values for ProcessStatusState. +// Defines values for BrowserInteractionKeyEventType. const ( - ProcessStatusStateExited ProcessStatusState = "exited" - ProcessStatusStateRunning ProcessStatusState = "running" + InteractionKey BrowserInteractionKeyEventType = "interaction_key" ) -// Valid indicates whether the value is a known member of the ProcessStatusState enum. -func (e ProcessStatusState) Valid() bool { +// Valid indicates whether the value is a known member of the BrowserInteractionKeyEventType enum. +func (e BrowserInteractionKeyEventType) Valid() bool { switch e { - case ProcessStatusStateExited: - return true - case ProcessStatusStateRunning: + case InteractionKey: return true default: return false } } -// Defines values for ProcessStreamEventEvent. +// Defines values for BrowserInteractionKeyEventDataTargetType. const ( - Exit ProcessStreamEventEvent = "exit" + BrowserInteractionKeyEventDataTargetTypeBackgroundPage BrowserInteractionKeyEventDataTargetType = "background_page" + BrowserInteractionKeyEventDataTargetTypeOther BrowserInteractionKeyEventDataTargetType = "other" + BrowserInteractionKeyEventDataTargetTypePage BrowserInteractionKeyEventDataTargetType = "page" + BrowserInteractionKeyEventDataTargetTypeServiceWorker BrowserInteractionKeyEventDataTargetType = "service_worker" + BrowserInteractionKeyEventDataTargetTypeSharedWorker BrowserInteractionKeyEventDataTargetType = "shared_worker" ) -// Valid indicates whether the value is a known member of the ProcessStreamEventEvent enum. -func (e ProcessStreamEventEvent) Valid() bool { +// Valid indicates whether the value is a known member of the BrowserInteractionKeyEventDataTargetType enum. +func (e BrowserInteractionKeyEventDataTargetType) Valid() bool { switch e { - case Exit: + case BrowserInteractionKeyEventDataTargetTypeBackgroundPage: + return true + case BrowserInteractionKeyEventDataTargetTypeOther: + return true + case BrowserInteractionKeyEventDataTargetTypePage: + return true + case BrowserInteractionKeyEventDataTargetTypeServiceWorker: + return true + case BrowserInteractionKeyEventDataTargetTypeSharedWorker: return true default: return false } } -// Defines values for ProcessStreamEventStream. +// Defines values for BrowserInteractionScrollSettledEventType. const ( - Stderr ProcessStreamEventStream = "stderr" - Stdout ProcessStreamEventStream = "stdout" + InteractionScrollSettled BrowserInteractionScrollSettledEventType = "interaction_scroll_settled" ) -// Valid indicates whether the value is a known member of the ProcessStreamEventStream enum. -func (e ProcessStreamEventStream) Valid() bool { +// Valid indicates whether the value is a known member of the BrowserInteractionScrollSettledEventType enum. +func (e BrowserInteractionScrollSettledEventType) Valid() bool { switch e { - case Stderr: - return true - case Stdout: + case InteractionScrollSettled: return true default: return false } } -// Defines values for PublishEventRequestCategory. +// Defines values for BrowserInteractionScrollSettledEventDataTargetType. const ( - PublishEventRequestCategoryConsole PublishEventRequestCategory = "console" - PublishEventRequestCategoryInteraction PublishEventRequestCategory = "interaction" - PublishEventRequestCategoryNetwork PublishEventRequestCategory = "network" - PublishEventRequestCategoryPage PublishEventRequestCategory = "page" - PublishEventRequestCategorySystem PublishEventRequestCategory = "system" + BrowserInteractionScrollSettledEventDataTargetTypeBackgroundPage BrowserInteractionScrollSettledEventDataTargetType = "background_page" + BrowserInteractionScrollSettledEventDataTargetTypeOther BrowserInteractionScrollSettledEventDataTargetType = "other" + BrowserInteractionScrollSettledEventDataTargetTypePage BrowserInteractionScrollSettledEventDataTargetType = "page" + BrowserInteractionScrollSettledEventDataTargetTypeServiceWorker BrowserInteractionScrollSettledEventDataTargetType = "service_worker" + BrowserInteractionScrollSettledEventDataTargetTypeSharedWorker BrowserInteractionScrollSettledEventDataTargetType = "shared_worker" ) -// Valid indicates whether the value is a known member of the PublishEventRequestCategory enum. -func (e PublishEventRequestCategory) Valid() bool { +// Valid indicates whether the value is a known member of the BrowserInteractionScrollSettledEventDataTargetType enum. +func (e BrowserInteractionScrollSettledEventDataTargetType) Valid() bool { switch e { - case PublishEventRequestCategoryConsole: + case BrowserInteractionScrollSettledEventDataTargetTypeBackgroundPage: return true - case PublishEventRequestCategoryInteraction: + case BrowserInteractionScrollSettledEventDataTargetTypeOther: return true - case PublishEventRequestCategoryNetwork: + case BrowserInteractionScrollSettledEventDataTargetTypePage: return true - case PublishEventRequestCategoryPage: + case BrowserInteractionScrollSettledEventDataTargetTypeServiceWorker: return true - case PublishEventRequestCategorySystem: + case BrowserInteractionScrollSettledEventDataTargetTypeSharedWorker: return true default: return false } } -// Defines values for TelemetryEventCategory. +// Defines values for BrowserMonitorDisconnectedEventType. const ( - TelemetryEventCategoryConsole TelemetryEventCategory = "console" - TelemetryEventCategoryInteraction TelemetryEventCategory = "interaction" - TelemetryEventCategoryNetwork TelemetryEventCategory = "network" - TelemetryEventCategoryPage TelemetryEventCategory = "page" - TelemetryEventCategorySystem TelemetryEventCategory = "system" + MonitorDisconnected BrowserMonitorDisconnectedEventType = "monitor_disconnected" ) -// Valid indicates whether the value is a known member of the TelemetryEventCategory enum. -func (e TelemetryEventCategory) Valid() bool { +// Valid indicates whether the value is a known member of the BrowserMonitorDisconnectedEventType enum. +func (e BrowserMonitorDisconnectedEventType) Valid() bool { switch e { - case TelemetryEventCategoryConsole: - return true - case TelemetryEventCategoryInteraction: - return true - case TelemetryEventCategoryNetwork: - return true - case TelemetryEventCategoryPage: - return true - case TelemetryEventCategorySystem: + case MonitorDisconnected: return true default: return false } } -// Defines values for TelemetryStateStatus. +// Defines values for BrowserMonitorDisconnectedEventDataReason. const ( - TelemetryStateStatusRunning TelemetryStateStatus = "running" - TelemetryStateStatusStopped TelemetryStateStatus = "stopped" + ChromeRestarted BrowserMonitorDisconnectedEventDataReason = "chrome_restarted" ) -// Valid indicates whether the value is a known member of the TelemetryStateStatus enum. -func (e TelemetryStateStatus) Valid() bool { +// Valid indicates whether the value is a known member of the BrowserMonitorDisconnectedEventDataReason enum. +func (e BrowserMonitorDisconnectedEventDataReason) Valid() bool { switch e { - case TelemetryStateStatusRunning: - return true - case TelemetryStateStatusStopped: + case ChromeRestarted: return true default: return false } } -// Defines values for DownloadDirZstdParamsCompressionLevel. +// Defines values for BrowserMonitorInitFailedEventType. const ( - Best DownloadDirZstdParamsCompressionLevel = "best" - Better DownloadDirZstdParamsCompressionLevel = "better" - Default DownloadDirZstdParamsCompressionLevel = "default" - Fastest DownloadDirZstdParamsCompressionLevel = "fastest" + MonitorInitFailed BrowserMonitorInitFailedEventType = "monitor_init_failed" ) -// Valid indicates whether the value is a known member of the DownloadDirZstdParamsCompressionLevel enum. -func (e DownloadDirZstdParamsCompressionLevel) Valid() bool { +// Valid indicates whether the value is a known member of the BrowserMonitorInitFailedEventType enum. +func (e BrowserMonitorInitFailedEventType) Valid() bool { switch e { - case Best: - return true - case Better: - return true - case Default: - return true - case Fastest: + case MonitorInitFailed: return true default: return false } } -// Defines values for LogsStreamParamsSource. +// Defines values for BrowserMonitorReconnectFailedEventType. const ( - Path LogsStreamParamsSource = "path" - Supervisor LogsStreamParamsSource = "supervisor" + MonitorReconnectFailed BrowserMonitorReconnectFailedEventType = "monitor_reconnect_failed" ) -// Valid indicates whether the value is a known member of the LogsStreamParamsSource enum. -func (e LogsStreamParamsSource) Valid() bool { +// Valid indicates whether the value is a known member of the BrowserMonitorReconnectFailedEventType enum. +func (e BrowserMonitorReconnectFailedEventType) Valid() bool { switch e { - case Path: - return true - case Supervisor: + case MonitorReconnectFailed: return true default: return false } } -// BatchComputerActionRequest A batch of computer actions to execute sequentially. -type BatchComputerActionRequest struct { - // Actions Ordered list of actions to execute. Execution stops on the first error. - Actions []ComputerAction `json:"actions"` -} +// Defines values for BrowserMonitorReconnectFailedEventDataReason. +const ( + ReconnectExhausted BrowserMonitorReconnectFailedEventDataReason = "reconnect_exhausted" +) -// BrowserCallStack CDP Runtime.StackTrace representing the JavaScript call stack at the time of an event. Fields use CDP naming conventions rather than snake_case to match the Chrome DevTools Protocol wire format. -type BrowserCallStack struct { - // CallFrames Ordered list of call frames, outermost first. - CallFrames []struct { - // ColumnNumber Zero-based column number within the line. - ColumnNumber int `json:"columnNumber"` +// Valid indicates whether the value is a known member of the BrowserMonitorReconnectFailedEventDataReason enum. +func (e BrowserMonitorReconnectFailedEventDataReason) Valid() bool { + switch e { + case ReconnectExhausted: + return true + default: + return false + } +} - // FunctionName JavaScript function name, or empty string for anonymous functions. - FunctionName string `json:"functionName"` +// Defines values for BrowserMonitorReconnectedEventType. +const ( + MonitorReconnected BrowserMonitorReconnectedEventType = "monitor_reconnected" +) - // LineNumber Zero-based line number within the script. - LineNumber int `json:"lineNumber"` +// Valid indicates whether the value is a known member of the BrowserMonitorReconnectedEventType enum. +func (e BrowserMonitorReconnectedEventType) Valid() bool { + switch e { + case MonitorReconnected: + return true + default: + return false + } +} - // ScriptId CDP script identifier. - ScriptId string `json:"scriptId"` +// Defines values for BrowserMonitorScreenshotEventType. +const ( + MonitorScreenshot BrowserMonitorScreenshotEventType = "monitor_screenshot" +) - // Url URL or name of the script file. - Url string `json:"url"` - } `json:"callFrames"` +// Valid indicates whether the value is a known member of the BrowserMonitorScreenshotEventType enum. +func (e BrowserMonitorScreenshotEventType) Valid() bool { + switch e { + case MonitorScreenshot: + return true + default: + return false + } +} - // Description Optional label for the stack trace (e.g. async cause). - Description *string `json:"description,omitempty"` +// Defines values for BrowserNetworkIdleEventType. +const ( + NetworkIdle BrowserNetworkIdleEventType = "network_idle" +) - // Parent Parent stack trace for async stacks. - Parent *BrowserCallStack `json:"parent,omitempty"` +// Valid indicates whether the value is a known member of the BrowserNetworkIdleEventType enum. +func (e BrowserNetworkIdleEventType) Valid() bool { + switch e { + case NetworkIdle: + return true + default: + return false + } } -// BrowserEventSource Provenance metadata identifying which producer emitted the event. -type BrowserEventSource struct { - // Event Producer-specific event name (e.g. `Runtime.consoleAPICalled` for CDP-sourced console events). - Event *string `json:"event,omitempty"` +// Defines values for BrowserNetworkLoadingFailedEventType. +const ( + NetworkLoadingFailed BrowserNetworkLoadingFailedEventType = "network_loading_failed" +) - // Kind Event producer. `cdp`: Chrome DevTools Protocol events from the browser. `kernel_api`: Kernel API server (reserved for server-generated events). `extension`: injected Chrome extension. `local_process`: system process running alongside the browser. +// Valid indicates whether the value is a known member of the BrowserNetworkLoadingFailedEventType enum. +func (e BrowserNetworkLoadingFailedEventType) Valid() bool { + switch e { + case NetworkLoadingFailed: + return true + default: + return false + } +} + +// Defines values for BrowserNetworkLoadingFailedEventDataTargetType. +const ( + BrowserNetworkLoadingFailedEventDataTargetTypeBackgroundPage BrowserNetworkLoadingFailedEventDataTargetType = "background_page" + BrowserNetworkLoadingFailedEventDataTargetTypeOther BrowserNetworkLoadingFailedEventDataTargetType = "other" + BrowserNetworkLoadingFailedEventDataTargetTypePage BrowserNetworkLoadingFailedEventDataTargetType = "page" + BrowserNetworkLoadingFailedEventDataTargetTypeServiceWorker BrowserNetworkLoadingFailedEventDataTargetType = "service_worker" + BrowserNetworkLoadingFailedEventDataTargetTypeSharedWorker BrowserNetworkLoadingFailedEventDataTargetType = "shared_worker" +) + +// Valid indicates whether the value is a known member of the BrowserNetworkLoadingFailedEventDataTargetType enum. +func (e BrowserNetworkLoadingFailedEventDataTargetType) Valid() bool { + switch e { + case BrowserNetworkLoadingFailedEventDataTargetTypeBackgroundPage: + return true + case BrowserNetworkLoadingFailedEventDataTargetTypeOther: + return true + case BrowserNetworkLoadingFailedEventDataTargetTypePage: + return true + case BrowserNetworkLoadingFailedEventDataTargetTypeServiceWorker: + return true + case BrowserNetworkLoadingFailedEventDataTargetTypeSharedWorker: + return true + default: + return false + } +} + +// Defines values for BrowserNetworkRequestEventType. +const ( + NetworkRequest BrowserNetworkRequestEventType = "network_request" +) + +// Valid indicates whether the value is a known member of the BrowserNetworkRequestEventType enum. +func (e BrowserNetworkRequestEventType) Valid() bool { + switch e { + case NetworkRequest: + return true + default: + return false + } +} + +// Defines values for BrowserNetworkRequestEventDataTargetType. +const ( + BrowserNetworkRequestEventDataTargetTypeBackgroundPage BrowserNetworkRequestEventDataTargetType = "background_page" + BrowserNetworkRequestEventDataTargetTypeOther BrowserNetworkRequestEventDataTargetType = "other" + BrowserNetworkRequestEventDataTargetTypePage BrowserNetworkRequestEventDataTargetType = "page" + BrowserNetworkRequestEventDataTargetTypeServiceWorker BrowserNetworkRequestEventDataTargetType = "service_worker" + BrowserNetworkRequestEventDataTargetTypeSharedWorker BrowserNetworkRequestEventDataTargetType = "shared_worker" +) + +// Valid indicates whether the value is a known member of the BrowserNetworkRequestEventDataTargetType enum. +func (e BrowserNetworkRequestEventDataTargetType) Valid() bool { + switch e { + case BrowserNetworkRequestEventDataTargetTypeBackgroundPage: + return true + case BrowserNetworkRequestEventDataTargetTypeOther: + return true + case BrowserNetworkRequestEventDataTargetTypePage: + return true + case BrowserNetworkRequestEventDataTargetTypeServiceWorker: + return true + case BrowserNetworkRequestEventDataTargetTypeSharedWorker: + return true + default: + return false + } +} + +// Defines values for BrowserNetworkResponseEventType. +const ( + NetworkResponse BrowserNetworkResponseEventType = "network_response" +) + +// Valid indicates whether the value is a known member of the BrowserNetworkResponseEventType enum. +func (e BrowserNetworkResponseEventType) Valid() bool { + switch e { + case NetworkResponse: + return true + default: + return false + } +} + +// Defines values for BrowserNetworkResponseEventDataTargetType. +const ( + BrowserNetworkResponseEventDataTargetTypeBackgroundPage BrowserNetworkResponseEventDataTargetType = "background_page" + BrowserNetworkResponseEventDataTargetTypeOther BrowserNetworkResponseEventDataTargetType = "other" + BrowserNetworkResponseEventDataTargetTypePage BrowserNetworkResponseEventDataTargetType = "page" + BrowserNetworkResponseEventDataTargetTypeServiceWorker BrowserNetworkResponseEventDataTargetType = "service_worker" + BrowserNetworkResponseEventDataTargetTypeSharedWorker BrowserNetworkResponseEventDataTargetType = "shared_worker" +) + +// Valid indicates whether the value is a known member of the BrowserNetworkResponseEventDataTargetType enum. +func (e BrowserNetworkResponseEventDataTargetType) Valid() bool { + switch e { + case BrowserNetworkResponseEventDataTargetTypeBackgroundPage: + return true + case BrowserNetworkResponseEventDataTargetTypeOther: + return true + case BrowserNetworkResponseEventDataTargetTypePage: + return true + case BrowserNetworkResponseEventDataTargetTypeServiceWorker: + return true + case BrowserNetworkResponseEventDataTargetTypeSharedWorker: + return true + default: + return false + } +} + +// Defines values for BrowserPageDomContentLoadedEventType. +const ( + PageDomContentLoaded BrowserPageDomContentLoadedEventType = "page_dom_content_loaded" +) + +// Valid indicates whether the value is a known member of the BrowserPageDomContentLoadedEventType enum. +func (e BrowserPageDomContentLoadedEventType) Valid() bool { + switch e { + case PageDomContentLoaded: + return true + default: + return false + } +} + +// Defines values for BrowserPageDomContentLoadedEventDataTargetType. +const ( + BrowserPageDomContentLoadedEventDataTargetTypeBackgroundPage BrowserPageDomContentLoadedEventDataTargetType = "background_page" + BrowserPageDomContentLoadedEventDataTargetTypeOther BrowserPageDomContentLoadedEventDataTargetType = "other" + BrowserPageDomContentLoadedEventDataTargetTypePage BrowserPageDomContentLoadedEventDataTargetType = "page" + BrowserPageDomContentLoadedEventDataTargetTypeServiceWorker BrowserPageDomContentLoadedEventDataTargetType = "service_worker" + BrowserPageDomContentLoadedEventDataTargetTypeSharedWorker BrowserPageDomContentLoadedEventDataTargetType = "shared_worker" +) + +// Valid indicates whether the value is a known member of the BrowserPageDomContentLoadedEventDataTargetType enum. +func (e BrowserPageDomContentLoadedEventDataTargetType) Valid() bool { + switch e { + case BrowserPageDomContentLoadedEventDataTargetTypeBackgroundPage: + return true + case BrowserPageDomContentLoadedEventDataTargetTypeOther: + return true + case BrowserPageDomContentLoadedEventDataTargetTypePage: + return true + case BrowserPageDomContentLoadedEventDataTargetTypeServiceWorker: + return true + case BrowserPageDomContentLoadedEventDataTargetTypeSharedWorker: + return true + default: + return false + } +} + +// Defines values for BrowserPageLayoutSettledEventType. +const ( + PageLayoutSettled BrowserPageLayoutSettledEventType = "page_layout_settled" +) + +// Valid indicates whether the value is a known member of the BrowserPageLayoutSettledEventType enum. +func (e BrowserPageLayoutSettledEventType) Valid() bool { + switch e { + case PageLayoutSettled: + return true + default: + return false + } +} + +// Defines values for BrowserPageLayoutShiftEventType. +const ( + PageLayoutShift BrowserPageLayoutShiftEventType = "page_layout_shift" +) + +// Valid indicates whether the value is a known member of the BrowserPageLayoutShiftEventType enum. +func (e BrowserPageLayoutShiftEventType) Valid() bool { + switch e { + case PageLayoutShift: + return true + default: + return false + } +} + +// Defines values for BrowserPageLayoutShiftEventDataTargetType. +const ( + BrowserPageLayoutShiftEventDataTargetTypeBackgroundPage BrowserPageLayoutShiftEventDataTargetType = "background_page" + BrowserPageLayoutShiftEventDataTargetTypeOther BrowserPageLayoutShiftEventDataTargetType = "other" + BrowserPageLayoutShiftEventDataTargetTypePage BrowserPageLayoutShiftEventDataTargetType = "page" + BrowserPageLayoutShiftEventDataTargetTypeServiceWorker BrowserPageLayoutShiftEventDataTargetType = "service_worker" + BrowserPageLayoutShiftEventDataTargetTypeSharedWorker BrowserPageLayoutShiftEventDataTargetType = "shared_worker" +) + +// Valid indicates whether the value is a known member of the BrowserPageLayoutShiftEventDataTargetType enum. +func (e BrowserPageLayoutShiftEventDataTargetType) Valid() bool { + switch e { + case BrowserPageLayoutShiftEventDataTargetTypeBackgroundPage: + return true + case BrowserPageLayoutShiftEventDataTargetTypeOther: + return true + case BrowserPageLayoutShiftEventDataTargetTypePage: + return true + case BrowserPageLayoutShiftEventDataTargetTypeServiceWorker: + return true + case BrowserPageLayoutShiftEventDataTargetTypeSharedWorker: + return true + default: + return false + } +} + +// Defines values for BrowserPageLcpEventType. +const ( + PageLcp BrowserPageLcpEventType = "page_lcp" +) + +// Valid indicates whether the value is a known member of the BrowserPageLcpEventType enum. +func (e BrowserPageLcpEventType) Valid() bool { + switch e { + case PageLcp: + return true + default: + return false + } +} + +// Defines values for BrowserPageLcpEventDataTargetType. +const ( + BrowserPageLcpEventDataTargetTypeBackgroundPage BrowserPageLcpEventDataTargetType = "background_page" + BrowserPageLcpEventDataTargetTypeOther BrowserPageLcpEventDataTargetType = "other" + BrowserPageLcpEventDataTargetTypePage BrowserPageLcpEventDataTargetType = "page" + BrowserPageLcpEventDataTargetTypeServiceWorker BrowserPageLcpEventDataTargetType = "service_worker" + BrowserPageLcpEventDataTargetTypeSharedWorker BrowserPageLcpEventDataTargetType = "shared_worker" +) + +// Valid indicates whether the value is a known member of the BrowserPageLcpEventDataTargetType enum. +func (e BrowserPageLcpEventDataTargetType) Valid() bool { + switch e { + case BrowserPageLcpEventDataTargetTypeBackgroundPage: + return true + case BrowserPageLcpEventDataTargetTypeOther: + return true + case BrowserPageLcpEventDataTargetTypePage: + return true + case BrowserPageLcpEventDataTargetTypeServiceWorker: + return true + case BrowserPageLcpEventDataTargetTypeSharedWorker: + return true + default: + return false + } +} + +// Defines values for BrowserPageLoadEventType. +const ( + PageLoad BrowserPageLoadEventType = "page_load" +) + +// Valid indicates whether the value is a known member of the BrowserPageLoadEventType enum. +func (e BrowserPageLoadEventType) Valid() bool { + switch e { + case PageLoad: + return true + default: + return false + } +} + +// Defines values for BrowserPageLoadEventDataTargetType. +const ( + BrowserPageLoadEventDataTargetTypeBackgroundPage BrowserPageLoadEventDataTargetType = "background_page" + BrowserPageLoadEventDataTargetTypeOther BrowserPageLoadEventDataTargetType = "other" + BrowserPageLoadEventDataTargetTypePage BrowserPageLoadEventDataTargetType = "page" + BrowserPageLoadEventDataTargetTypeServiceWorker BrowserPageLoadEventDataTargetType = "service_worker" + BrowserPageLoadEventDataTargetTypeSharedWorker BrowserPageLoadEventDataTargetType = "shared_worker" +) + +// Valid indicates whether the value is a known member of the BrowserPageLoadEventDataTargetType enum. +func (e BrowserPageLoadEventDataTargetType) Valid() bool { + switch e { + case BrowserPageLoadEventDataTargetTypeBackgroundPage: + return true + case BrowserPageLoadEventDataTargetTypeOther: + return true + case BrowserPageLoadEventDataTargetTypePage: + return true + case BrowserPageLoadEventDataTargetTypeServiceWorker: + return true + case BrowserPageLoadEventDataTargetTypeSharedWorker: + return true + default: + return false + } +} + +// Defines values for BrowserPageNavigationEventType. +const ( + PageNavigation BrowserPageNavigationEventType = "page_navigation" +) + +// Valid indicates whether the value is a known member of the BrowserPageNavigationEventType enum. +func (e BrowserPageNavigationEventType) Valid() bool { + switch e { + case PageNavigation: + return true + default: + return false + } +} + +// Defines values for BrowserPageNavigationEventDataTargetType. +const ( + BrowserPageNavigationEventDataTargetTypeBackgroundPage BrowserPageNavigationEventDataTargetType = "background_page" + BrowserPageNavigationEventDataTargetTypeOther BrowserPageNavigationEventDataTargetType = "other" + BrowserPageNavigationEventDataTargetTypePage BrowserPageNavigationEventDataTargetType = "page" + BrowserPageNavigationEventDataTargetTypeServiceWorker BrowserPageNavigationEventDataTargetType = "service_worker" + BrowserPageNavigationEventDataTargetTypeSharedWorker BrowserPageNavigationEventDataTargetType = "shared_worker" +) + +// Valid indicates whether the value is a known member of the BrowserPageNavigationEventDataTargetType enum. +func (e BrowserPageNavigationEventDataTargetType) Valid() bool { + switch e { + case BrowserPageNavigationEventDataTargetTypeBackgroundPage: + return true + case BrowserPageNavigationEventDataTargetTypeOther: + return true + case BrowserPageNavigationEventDataTargetTypePage: + return true + case BrowserPageNavigationEventDataTargetTypeServiceWorker: + return true + case BrowserPageNavigationEventDataTargetTypeSharedWorker: + return true + default: + return false + } +} + +// Defines values for BrowserPageNavigationSettledEventType. +const ( + PageNavigationSettled BrowserPageNavigationSettledEventType = "page_navigation_settled" +) + +// Valid indicates whether the value is a known member of the BrowserPageNavigationSettledEventType enum. +func (e BrowserPageNavigationSettledEventType) Valid() bool { + switch e { + case PageNavigationSettled: + return true + default: + return false + } +} + +// Defines values for BrowserPageTabOpenedEventType. +const ( + PageTabOpened BrowserPageTabOpenedEventType = "page_tab_opened" +) + +// Valid indicates whether the value is a known member of the BrowserPageTabOpenedEventType enum. +func (e BrowserPageTabOpenedEventType) Valid() bool { + switch e { + case PageTabOpened: + return true + default: + return false + } +} + +// Defines values for BrowserPageTabOpenedEventDataTargetType. +const ( + BrowserPageTabOpenedEventDataTargetTypeBackgroundPage BrowserPageTabOpenedEventDataTargetType = "background_page" + BrowserPageTabOpenedEventDataTargetTypeOther BrowserPageTabOpenedEventDataTargetType = "other" + BrowserPageTabOpenedEventDataTargetTypePage BrowserPageTabOpenedEventDataTargetType = "page" + BrowserPageTabOpenedEventDataTargetTypeServiceWorker BrowserPageTabOpenedEventDataTargetType = "service_worker" + BrowserPageTabOpenedEventDataTargetTypeSharedWorker BrowserPageTabOpenedEventDataTargetType = "shared_worker" +) + +// Valid indicates whether the value is a known member of the BrowserPageTabOpenedEventDataTargetType enum. +func (e BrowserPageTabOpenedEventDataTargetType) Valid() bool { + switch e { + case BrowserPageTabOpenedEventDataTargetTypeBackgroundPage: + return true + case BrowserPageTabOpenedEventDataTargetTypeOther: + return true + case BrowserPageTabOpenedEventDataTargetTypePage: + return true + case BrowserPageTabOpenedEventDataTargetTypeServiceWorker: + return true + case BrowserPageTabOpenedEventDataTargetTypeSharedWorker: + return true + default: + return false + } +} + +// Defines values for ClickMouseRequestButton. +const ( + ClickMouseRequestButtonBack ClickMouseRequestButton = "back" + ClickMouseRequestButtonForward ClickMouseRequestButton = "forward" + ClickMouseRequestButtonLeft ClickMouseRequestButton = "left" + ClickMouseRequestButtonMiddle ClickMouseRequestButton = "middle" + ClickMouseRequestButtonRight ClickMouseRequestButton = "right" +) + +// Valid indicates whether the value is a known member of the ClickMouseRequestButton enum. +func (e ClickMouseRequestButton) Valid() bool { + switch e { + case ClickMouseRequestButtonBack: + return true + case ClickMouseRequestButtonForward: + return true + case ClickMouseRequestButtonLeft: + return true + case ClickMouseRequestButtonMiddle: + return true + case ClickMouseRequestButtonRight: + return true + default: + return false + } +} + +// Defines values for ClickMouseRequestClickType. +const ( + Click ClickMouseRequestClickType = "click" + Down ClickMouseRequestClickType = "down" + Up ClickMouseRequestClickType = "up" +) + +// Valid indicates whether the value is a known member of the ClickMouseRequestClickType enum. +func (e ClickMouseRequestClickType) Valid() bool { + switch e { + case Click: + return true + case Down: + return true + case Up: + return true + default: + return false + } +} + +// Defines values for ComputerActionType. +const ( + ClickMouse ComputerActionType = "click_mouse" + DragMouse ComputerActionType = "drag_mouse" + MoveMouse ComputerActionType = "move_mouse" + PressKey ComputerActionType = "press_key" + Scroll ComputerActionType = "scroll" + SetCursor ComputerActionType = "set_cursor" + Sleep ComputerActionType = "sleep" + TypeText ComputerActionType = "type_text" +) + +// Valid indicates whether the value is a known member of the ComputerActionType enum. +func (e ComputerActionType) Valid() bool { + switch e { + case ClickMouse: + return true + case DragMouse: + return true + case MoveMouse: + return true + case PressKey: + return true + case Scroll: + return true + case SetCursor: + return true + case Sleep: + return true + case TypeText: + return true + default: + return false + } +} + +// Defines values for DragMouseRequestButton. +const ( + DragMouseRequestButtonLeft DragMouseRequestButton = "left" + DragMouseRequestButtonMiddle DragMouseRequestButton = "middle" + DragMouseRequestButtonRight DragMouseRequestButton = "right" +) + +// Valid indicates whether the value is a known member of the DragMouseRequestButton enum. +func (e DragMouseRequestButton) Valid() bool { + switch e { + case DragMouseRequestButtonLeft: + return true + case DragMouseRequestButtonMiddle: + return true + case DragMouseRequestButtonRight: + return true + default: + return false + } +} + +// Defines values for FileSystemEventType. +const ( + CREATE FileSystemEventType = "CREATE" + DELETE FileSystemEventType = "DELETE" + RENAME FileSystemEventType = "RENAME" + WRITE FileSystemEventType = "WRITE" +) + +// Valid indicates whether the value is a known member of the FileSystemEventType enum. +func (e FileSystemEventType) Valid() bool { + switch e { + case CREATE: + return true + case DELETE: + return true + case RENAME: + return true + case WRITE: + return true + default: + return false + } +} + +// Defines values for PatchDisplayRequestRefreshRate. +const ( + N10 PatchDisplayRequestRefreshRate = 10 + N25 PatchDisplayRequestRefreshRate = 25 + N30 PatchDisplayRequestRefreshRate = 30 + N60 PatchDisplayRequestRefreshRate = 60 +) + +// Valid indicates whether the value is a known member of the PatchDisplayRequestRefreshRate enum. +func (e PatchDisplayRequestRefreshRate) Valid() bool { + switch e { + case N10: + return true + case N25: + return true + case N30: + return true + case N60: + return true + default: + return false + } +} + +// Defines values for ProcessKillRequestSignal. +const ( + HUP ProcessKillRequestSignal = "HUP" + INT ProcessKillRequestSignal = "INT" + KILL ProcessKillRequestSignal = "KILL" + TERM ProcessKillRequestSignal = "TERM" +) + +// Valid indicates whether the value is a known member of the ProcessKillRequestSignal enum. +func (e ProcessKillRequestSignal) Valid() bool { + switch e { + case HUP: + return true + case INT: + return true + case KILL: + return true + case TERM: + return true + default: + return false + } +} + +// Defines values for ProcessStatusState. +const ( + ProcessStatusStateExited ProcessStatusState = "exited" + ProcessStatusStateRunning ProcessStatusState = "running" +) + +// Valid indicates whether the value is a known member of the ProcessStatusState enum. +func (e ProcessStatusState) Valid() bool { + switch e { + case ProcessStatusStateExited: + return true + case ProcessStatusStateRunning: + return true + default: + return false + } +} + +// Defines values for ProcessStreamEventEvent. +const ( + Exit ProcessStreamEventEvent = "exit" +) + +// Valid indicates whether the value is a known member of the ProcessStreamEventEvent enum. +func (e ProcessStreamEventEvent) Valid() bool { + switch e { + case Exit: + return true + default: + return false + } +} + +// Defines values for ProcessStreamEventStream. +const ( + Stderr ProcessStreamEventStream = "stderr" + Stdout ProcessStreamEventStream = "stdout" +) + +// Valid indicates whether the value is a known member of the ProcessStreamEventStream enum. +func (e ProcessStreamEventStream) Valid() bool { + switch e { + case Stderr: + return true + case Stdout: + return true + default: + return false + } +} + +// Defines values for PublishEventRequestCategory. +const ( + PublishEventRequestCategoryConsole PublishEventRequestCategory = "console" + PublishEventRequestCategoryInteraction PublishEventRequestCategory = "interaction" + PublishEventRequestCategoryNetwork PublishEventRequestCategory = "network" + PublishEventRequestCategoryPage PublishEventRequestCategory = "page" + PublishEventRequestCategorySystem PublishEventRequestCategory = "system" +) + +// Valid indicates whether the value is a known member of the PublishEventRequestCategory enum. +func (e PublishEventRequestCategory) Valid() bool { + switch e { + case PublishEventRequestCategoryConsole: + return true + case PublishEventRequestCategoryInteraction: + return true + case PublishEventRequestCategoryNetwork: + return true + case PublishEventRequestCategoryPage: + return true + case PublishEventRequestCategorySystem: + return true + default: + return false + } +} + +// Defines values for TelemetryEventCategory. +const ( + Console TelemetryEventCategory = "console" + Interaction TelemetryEventCategory = "interaction" + Network TelemetryEventCategory = "network" + Page TelemetryEventCategory = "page" + System TelemetryEventCategory = "system" +) + +// Valid indicates whether the value is a known member of the TelemetryEventCategory enum. +func (e TelemetryEventCategory) Valid() bool { + switch e { + case Console: + return true + case Interaction: + return true + case Network: + return true + case Page: + return true + case System: + return true + default: + return false + } +} + +// Defines values for TelemetryStateStatus. +const ( + TelemetryStateStatusRunning TelemetryStateStatus = "running" + TelemetryStateStatusStopped TelemetryStateStatus = "stopped" +) + +// Valid indicates whether the value is a known member of the TelemetryStateStatus enum. +func (e TelemetryStateStatus) Valid() bool { + switch e { + case TelemetryStateStatusRunning: + return true + case TelemetryStateStatusStopped: + return true + default: + return false + } +} + +// Defines values for DownloadDirZstdParamsCompressionLevel. +const ( + Best DownloadDirZstdParamsCompressionLevel = "best" + Better DownloadDirZstdParamsCompressionLevel = "better" + Default DownloadDirZstdParamsCompressionLevel = "default" + Fastest DownloadDirZstdParamsCompressionLevel = "fastest" +) + +// Valid indicates whether the value is a known member of the DownloadDirZstdParamsCompressionLevel enum. +func (e DownloadDirZstdParamsCompressionLevel) Valid() bool { + switch e { + case Best: + return true + case Better: + return true + case Default: + return true + case Fastest: + return true + default: + return false + } +} + +// Defines values for LogsStreamParamsSource. +const ( + Path LogsStreamParamsSource = "path" + Supervisor LogsStreamParamsSource = "supervisor" +) + +// Valid indicates whether the value is a known member of the LogsStreamParamsSource enum. +func (e LogsStreamParamsSource) Valid() bool { + switch e { + case Path: + return true + case Supervisor: + return true + default: + return false + } +} + +// BatchComputerActionRequest A batch of computer actions to execute sequentially. +type BatchComputerActionRequest struct { + // Actions Ordered list of actions to execute. Execution stops on the first error. + Actions []ComputerAction `json:"actions"` +} + +// BrowserCallStack CDP Runtime.StackTrace representing the JavaScript call stack at the time of an event. Fields use CDP naming conventions rather than snake_case to match the Chrome DevTools Protocol wire format. +type BrowserCallStack struct { + // CallFrames Ordered list of call frames, outermost first. + CallFrames []struct { + // ColumnNumber Zero-based column number within the line. + ColumnNumber int `json:"columnNumber"` + + // FunctionName JavaScript function name, or empty string for anonymous functions. + FunctionName string `json:"functionName"` + + // LineNumber Zero-based line number within the script. + LineNumber int `json:"lineNumber"` + + // ScriptId CDP script identifier. + ScriptId string `json:"scriptId"` + + // Url URL or name of the script file. + Url string `json:"url"` + } `json:"callFrames"` + + // Description Optional label for the stack trace (e.g. async cause). + Description *string `json:"description,omitempty"` + + // Parent Parent stack trace for async stacks. + Parent *BrowserCallStack `json:"parent,omitempty"` +} + +// BrowserConsoleErrorEvent A browser console error or uncaught JavaScript exception event. Emitted from two distinct CDP sources with different data shapes. Runtime.consoleAPICalled (console.error calls) produces level, text, args, and stack_trace. Runtime.exceptionThrown (uncaught exceptions) produces text, line, column, source_url, and stack_trace. Fields not applicable to the source are absent. +type BrowserConsoleErrorEvent struct { + Data *BrowserConsoleErrorEventData `json:"data,omitempty"` + + // Source Provenance metadata identifying which producer emitted the event. + Source BrowserEventSource `json:"source"` + + // Truncated True if the data field was truncated due to size limits. + Truncated *bool `json:"truncated,omitempty"` + + // Ts Event timestamp in Unix microseconds. + Ts int64 `json:"ts"` + Type BrowserConsoleErrorEventType `json:"type"` +} + +// BrowserConsoleErrorEventType defines model for BrowserConsoleErrorEvent.Type. +type BrowserConsoleErrorEventType string + +// BrowserConsoleErrorEventData defines model for BrowserConsoleErrorEventData. +type BrowserConsoleErrorEventData struct { + // Args All console arguments coerced to strings. Present only when sourced from Runtime.consoleAPICalled. + Args *[]string `json:"args,omitempty"` + + // Column Column number in the script where the exception was thrown. Present only when sourced from Runtime.exceptionThrown. + Column *int `json:"column,omitempty"` + + // FrameId CDP frame identifier within the target. + FrameId *string `json:"frame_id,omitempty"` + + // Level CDP console type value, always "error". Present only when sourced from Runtime.consoleAPICalled. + Level *string `json:"level,omitempty"` + + // Line Line number in the script where the exception was thrown. Present only when sourced from Runtime.exceptionThrown. + Line *int `json:"line,omitempty"` + + // LoaderId CDP document loader identifier, reset on each navigation. + LoaderId *string `json:"loader_id,omitempty"` + + // NavSeq Monotonically increasing navigation sequence number, incremented on each top-level navigation within the target. + NavSeq int64 `json:"nav_seq"` + + // SessionId CDP session identifier for the target connection. + SessionId string `json:"session_id"` + + // SourceUrl URL of the script file that threw the exception. Present only when sourced from Runtime.exceptionThrown. + SourceUrl *string `json:"source_url,omitempty"` + + // StackTrace CDP Runtime.StackTrace representing the JavaScript call stack at the time of an event. Fields use CDP naming conventions rather than snake_case to match the Chrome DevTools Protocol wire format. + StackTrace *BrowserCallStack `json:"stack_trace,omitempty"` + + // TargetId Browser target identifier (stable across navigations within a tab). + TargetId string `json:"target_id"` + + // TargetType CDP target type of the page that produced the event. + TargetType BrowserConsoleErrorEventDataTargetType `json:"target_type"` + + // Text Error message text. Present in both source paths. + Text string `json:"text"` + + // Url URL relevant to this event; page URL for navigation and page events, request URL for network events. + Url *string `json:"url,omitempty"` +} + +// BrowserConsoleErrorEventDataTargetType CDP target type of the page that produced the event. +type BrowserConsoleErrorEventDataTargetType string + +// BrowserConsoleLogEvent A browser console log event (console.log, console.info, console.warn, etc.). +type BrowserConsoleLogEvent struct { + Data *BrowserConsoleLogEventData `json:"data,omitempty"` + + // Source Provenance metadata identifying which producer emitted the event. + Source BrowserEventSource `json:"source"` + + // Truncated True if the data field was truncated due to size limits. + Truncated *bool `json:"truncated,omitempty"` + + // Ts Event timestamp in Unix microseconds. + Ts int64 `json:"ts"` + Type BrowserConsoleLogEventType `json:"type"` +} + +// BrowserConsoleLogEventType defines model for BrowserConsoleLogEvent.Type. +type BrowserConsoleLogEventType string + +// BrowserConsoleLogEventData defines model for BrowserConsoleLogEventData. +type BrowserConsoleLogEventData struct { + // Args All console arguments coerced to strings. + Args *[]string `json:"args,omitempty"` + + // FrameId CDP frame identifier within the target. + FrameId *string `json:"frame_id,omitempty"` + + // Level CDP Runtime.consoleAPICalled type, passed through unfiltered from Chrome. `error` is routed to console_error events instead; all other CDP console types appear here. See CDP spec for the full enum. + Level string `json:"level"` + + // LoaderId CDP document loader identifier, reset on each navigation. + LoaderId *string `json:"loader_id,omitempty"` + + // NavSeq Monotonically increasing navigation sequence number, incremented on each top-level navigation within the target. + NavSeq int64 `json:"nav_seq"` + + // SessionId CDP session identifier for the target connection. + SessionId string `json:"session_id"` + + // StackTrace CDP Runtime.StackTrace representing the JavaScript call stack at the time of an event. Fields use CDP naming conventions rather than snake_case to match the Chrome DevTools Protocol wire format. + StackTrace *BrowserCallStack `json:"stack_trace,omitempty"` + + // TargetId Browser target identifier (stable across navigations within a tab). + TargetId string `json:"target_id"` + + // TargetType CDP target type of the page that produced the event. + TargetType BrowserConsoleLogEventDataTargetType `json:"target_type"` + + // Text First console argument coerced to string. + Text string `json:"text"` + + // Url URL relevant to this event; page URL for navigation and page events, request URL for network events. + Url *string `json:"url,omitempty"` +} + +// BrowserConsoleLogEventDataTargetType CDP target type of the page that produced the event. +type BrowserConsoleLogEventDataTargetType string + +// BrowserEventContext Browser event context stamped by the browser monitor onto all CDP-sourced events. Identifies the target, frame, and navigation epoch in which the event occurred. +type BrowserEventContext struct { + // FrameId CDP frame identifier within the target. + FrameId *string `json:"frame_id,omitempty"` + + // LoaderId CDP document loader identifier, reset on each navigation. + LoaderId *string `json:"loader_id,omitempty"` + + // NavSeq Monotonically increasing navigation sequence number, incremented on each top-level navigation within the target. + NavSeq int64 `json:"nav_seq"` + + // SessionId CDP session identifier for the target connection. + SessionId string `json:"session_id"` + + // TargetId Browser target identifier (stable across navigations within a tab). + TargetId string `json:"target_id"` + + // TargetType CDP target type of the page that produced the event. + TargetType BrowserEventContextTargetType `json:"target_type"` + + // Url URL relevant to this event; page URL for navigation and page events, request URL for network events. + Url *string `json:"url,omitempty"` +} + +// BrowserEventContextTargetType CDP target type of the page that produced the event. +type BrowserEventContextTargetType string + +// BrowserEventSource Provenance metadata identifying which producer emitted the event. +type BrowserEventSource struct { + // Event Producer-specific event name (e.g. `Runtime.consoleAPICalled` for CDP-sourced console events). + Event *string `json:"event,omitempty"` + + // Kind Event producer. `cdp`: Chrome DevTools Protocol events from the browser. `kernel_api`: Kernel API server (reserved for server-generated events). `extension`: injected Chrome extension. `local_process`: system process running alongside the browser. Kind BrowserEventSourceKind `json:"kind"` - // Metadata Producer-specific context (e.g. CDP target/session/frame IDs). - Metadata *map[string]string `json:"metadata,omitempty"` + // Metadata Producer-specific context (e.g. CDP target/session/frame IDs). + Metadata *map[string]string `json:"metadata,omitempty"` +} + +// BrowserEventSourceKind Event producer. `cdp`: Chrome DevTools Protocol events from the browser. `kernel_api`: Kernel API server (reserved for server-generated events). `extension`: injected Chrome extension. `local_process`: system process running alongside the browser. +type BrowserEventSourceKind string + +// BrowserHttpHeaders HTTP headers map forwarded as-is from CDP without normalization. Values are typically strings but may be any JSON type. +type BrowserHttpHeaders map[string]interface{} + +// BrowserInteractionClickEvent A browser user click event captured via injected page script. +type BrowserInteractionClickEvent struct { + Data *BrowserInteractionClickEventData `json:"data,omitempty"` + + // Source Provenance metadata identifying which producer emitted the event. + Source BrowserEventSource `json:"source"` + + // Truncated True if the data field was truncated due to size limits. + Truncated *bool `json:"truncated,omitempty"` + + // Ts Event timestamp in Unix microseconds. + Ts int64 `json:"ts"` + Type BrowserInteractionClickEventType `json:"type"` +} + +// BrowserInteractionClickEventType defines model for BrowserInteractionClickEvent.Type. +type BrowserInteractionClickEventType string + +// BrowserInteractionClickEventData defines model for BrowserInteractionClickEventData. +type BrowserInteractionClickEventData struct { + // FrameId CDP frame identifier within the target. + FrameId *string `json:"frame_id,omitempty"` + + // LoaderId CDP document loader identifier, reset on each navigation. + LoaderId *string `json:"loader_id,omitempty"` + + // NavSeq Monotonically increasing navigation sequence number, incremented on each top-level navigation within the target. + NavSeq int64 `json:"nav_seq"` + + // Selector CSS selector path to the clicked element. + Selector string `json:"selector"` + + // SessionId CDP session identifier for the target connection. + SessionId string `json:"session_id"` + + // Tag HTML tag name of the clicked element in uppercase (e.g. BUTTON, A, DIV). + Tag string `json:"tag"` + + // TargetId Browser target identifier (stable across navigations within a tab). + TargetId string `json:"target_id"` + + // TargetType CDP target type of the page that produced the event. + TargetType BrowserInteractionClickEventDataTargetType `json:"target_type"` + + // Text Visible text content of the clicked element, trimmed. + Text *string `json:"text,omitempty"` + + // Url URL relevant to this event; page URL for navigation and page events, request URL for network events. + Url *string `json:"url,omitempty"` + + // X Viewport x-coordinate of the click in CSS pixels. + X int `json:"x"` + + // Y Viewport y-coordinate of the click in CSS pixels. + Y int `json:"y"` +} + +// BrowserInteractionClickEventDataTargetType CDP target type of the page that produced the event. +type BrowserInteractionClickEventDataTargetType string + +// BrowserInteractionKeyEvent A browser keyboard event captured via injected page script. +type BrowserInteractionKeyEvent struct { + Data *BrowserInteractionKeyEventData `json:"data,omitempty"` + + // Source Provenance metadata identifying which producer emitted the event. + Source BrowserEventSource `json:"source"` + + // Truncated True if the data field was truncated due to size limits. + Truncated *bool `json:"truncated,omitempty"` + + // Ts Event timestamp in Unix microseconds. + Ts int64 `json:"ts"` + Type BrowserInteractionKeyEventType `json:"type"` +} + +// BrowserInteractionKeyEventType defines model for BrowserInteractionKeyEvent.Type. +type BrowserInteractionKeyEventType string + +// BrowserInteractionKeyEventData defines model for BrowserInteractionKeyEventData. +type BrowserInteractionKeyEventData struct { + // FrameId CDP frame identifier within the target. + FrameId *string `json:"frame_id,omitempty"` + + // Key Key value from the KeyboardEvent (e.g. Enter, Backspace, a). + Key string `json:"key"` + + // LoaderId CDP document loader identifier, reset on each navigation. + LoaderId *string `json:"loader_id,omitempty"` + + // NavSeq Monotonically increasing navigation sequence number, incremented on each top-level navigation within the target. + NavSeq int64 `json:"nav_seq"` + + // Selector CSS selector path to the element that had focus when the key was pressed. + Selector string `json:"selector"` + + // SessionId CDP session identifier for the target connection. + SessionId string `json:"session_id"` + + // Tag HTML tag name of the focused element in uppercase (e.g. INPUT, TEXTAREA, DIV). + Tag string `json:"tag"` + + // TargetId Browser target identifier (stable across navigations within a tab). + TargetId string `json:"target_id"` + + // TargetType CDP target type of the page that produced the event. + TargetType BrowserInteractionKeyEventDataTargetType `json:"target_type"` + + // Url URL relevant to this event; page URL for navigation and page events, request URL for network events. + Url *string `json:"url,omitempty"` +} + +// BrowserInteractionKeyEventDataTargetType CDP target type of the page that produced the event. +type BrowserInteractionKeyEventDataTargetType string + +// BrowserInteractionScrollSettledEvent A browser scroll settled event emitted after scroll position stops changing, captured via injected page script. +type BrowserInteractionScrollSettledEvent struct { + Data *BrowserInteractionScrollSettledEventData `json:"data,omitempty"` + + // Source Provenance metadata identifying which producer emitted the event. + Source BrowserEventSource `json:"source"` + + // Truncated True if the data field was truncated due to size limits. + Truncated *bool `json:"truncated,omitempty"` + + // Ts Event timestamp in Unix microseconds. + Ts int64 `json:"ts"` + Type BrowserInteractionScrollSettledEventType `json:"type"` +} + +// BrowserInteractionScrollSettledEventType defines model for BrowserInteractionScrollSettledEvent.Type. +type BrowserInteractionScrollSettledEventType string + +// BrowserInteractionScrollSettledEventData defines model for BrowserInteractionScrollSettledEventData. +type BrowserInteractionScrollSettledEventData struct { + // FrameId CDP frame identifier within the target. + FrameId *string `json:"frame_id,omitempty"` + + // FromX Scroll x-position at the start of the scroll gesture in CSS pixels. + FromX int `json:"from_x"` + + // FromY Scroll y-position at the start of the scroll gesture in CSS pixels. + FromY int `json:"from_y"` + + // LoaderId CDP document loader identifier, reset on each navigation. + LoaderId *string `json:"loader_id,omitempty"` + + // NavSeq Monotonically increasing navigation sequence number, incremented on each top-level navigation within the target. + NavSeq int64 `json:"nav_seq"` + + // SessionId CDP session identifier for the target connection. + SessionId string `json:"session_id"` + + // TargetId Browser target identifier (stable across navigations within a tab). + TargetId string `json:"target_id"` + + // TargetSelector CSS selector path to the scrolled element. + TargetSelector string `json:"target_selector"` + + // TargetType CDP target type of the page that produced the event. + TargetType BrowserInteractionScrollSettledEventDataTargetType `json:"target_type"` + + // ToX Final scroll x-position after the gesture settled in CSS pixels. + ToX int `json:"to_x"` + + // ToY Final scroll y-position after the gesture settled in CSS pixels. + ToY int `json:"to_y"` + + // Url URL relevant to this event; page URL for navigation and page events, request URL for network events. + Url *string `json:"url,omitempty"` +} + +// BrowserInteractionScrollSettledEventDataTargetType CDP target type of the page that produced the event. +type BrowserInteractionScrollSettledEventDataTargetType string + +// BrowserMonitorDisconnectedEvent The CDP connection to Chrome was lost. Telemetry events may be dropped until monitor_reconnected arrives. Treat any in-progress computed state (network_idle, page_layout_settled) as unreliable until then. +type BrowserMonitorDisconnectedEvent struct { + Data *BrowserMonitorDisconnectedEventData `json:"data,omitempty"` + + // Source Provenance metadata identifying which producer emitted the event. + Source BrowserEventSource `json:"source"` + + // Truncated True if the data field was truncated due to size limits. + Truncated *bool `json:"truncated,omitempty"` + + // Ts Event timestamp in Unix microseconds. + Ts int64 `json:"ts"` + Type BrowserMonitorDisconnectedEventType `json:"type"` +} + +// BrowserMonitorDisconnectedEventType defines model for BrowserMonitorDisconnectedEvent.Type. +type BrowserMonitorDisconnectedEventType string + +// BrowserMonitorDisconnectedEventData defines model for BrowserMonitorDisconnectedEventData. +type BrowserMonitorDisconnectedEventData struct { + // Reason Reason for the disconnection. chrome_restarted: Chrome process restarted. + Reason BrowserMonitorDisconnectedEventDataReason `json:"reason"` +} + +// BrowserMonitorDisconnectedEventDataReason Reason for the disconnection. chrome_restarted: Chrome process restarted. +type BrowserMonitorDisconnectedEventDataReason string + +// BrowserMonitorInitFailedEvent The CDP session could not be initialized. +type BrowserMonitorInitFailedEvent struct { + Data *BrowserMonitorInitFailedEventData `json:"data,omitempty"` + + // Source Provenance metadata identifying which producer emitted the event. + Source BrowserEventSource `json:"source"` + + // Truncated True if the data field was truncated due to size limits. + Truncated *bool `json:"truncated,omitempty"` + + // Ts Event timestamp in Unix microseconds. + Ts int64 `json:"ts"` + Type BrowserMonitorInitFailedEventType `json:"type"` +} + +// BrowserMonitorInitFailedEventType defines model for BrowserMonitorInitFailedEvent.Type. +type BrowserMonitorInitFailedEventType string + +// BrowserMonitorInitFailedEventData defines model for BrowserMonitorInitFailedEventData. +type BrowserMonitorInitFailedEventData struct { + // Step The CDP method or initialization step that failed (e.g. Target.setAutoAttach). + Step string `json:"step"` +} + +// BrowserMonitorReconnectFailedEvent The CDP connection to Chrome could not be re-established after exhausting all reconnection attempts. No further telemetry events will arrive on this session. +type BrowserMonitorReconnectFailedEvent struct { + Data *BrowserMonitorReconnectFailedEventData `json:"data,omitempty"` + + // Source Provenance metadata identifying which producer emitted the event. + Source BrowserEventSource `json:"source"` + + // Truncated True if the data field was truncated due to size limits. + Truncated *bool `json:"truncated,omitempty"` + + // Ts Event timestamp in Unix microseconds. + Ts int64 `json:"ts"` + Type BrowserMonitorReconnectFailedEventType `json:"type"` +} + +// BrowserMonitorReconnectFailedEventType defines model for BrowserMonitorReconnectFailedEvent.Type. +type BrowserMonitorReconnectFailedEventType string + +// BrowserMonitorReconnectFailedEventData defines model for BrowserMonitorReconnectFailedEventData. +type BrowserMonitorReconnectFailedEventData struct { + // Reason Reason for the reconnection failure. reconnect_exhausted: all retry attempts were used up without successfully restoring the CDP connection. + Reason BrowserMonitorReconnectFailedEventDataReason `json:"reason"` +} + +// BrowserMonitorReconnectFailedEventDataReason Reason for the reconnection failure. reconnect_exhausted: all retry attempts were used up without successfully restoring the CDP connection. +type BrowserMonitorReconnectFailedEventDataReason string + +// BrowserMonitorReconnectedEvent The CDP connection to Chrome was successfully re-established after a disconnection. Events emitted during the gap are lost. Computed state is reset, so navigation and network tracking restart fresh from this point. +type BrowserMonitorReconnectedEvent struct { + Data *BrowserMonitorReconnectedEventData `json:"data,omitempty"` + + // Source Provenance metadata identifying which producer emitted the event. + Source BrowserEventSource `json:"source"` + + // Truncated True if the data field was truncated due to size limits. + Truncated *bool `json:"truncated,omitempty"` + + // Ts Event timestamp in Unix microseconds. + Ts int64 `json:"ts"` + Type BrowserMonitorReconnectedEventType `json:"type"` +} + +// BrowserMonitorReconnectedEventType defines model for BrowserMonitorReconnectedEvent.Type. +type BrowserMonitorReconnectedEventType string + +// BrowserMonitorReconnectedEventData defines model for BrowserMonitorReconnectedEventData. +type BrowserMonitorReconnectedEventData struct { + // ReconnectDurationMs Wall-clock time in milliseconds taken to reconnect after the disconnection. + ReconnectDurationMs int64 `json:"reconnect_duration_ms"` +} + +// BrowserMonitorScreenshotEvent A periodic screenshot of the browser viewport. +type BrowserMonitorScreenshotEvent struct { + Data *BrowserMonitorScreenshotEventData `json:"data,omitempty"` + + // Source Provenance metadata identifying which producer emitted the event. + Source BrowserEventSource `json:"source"` + + // Truncated True if the data field was truncated due to size limits. + Truncated *bool `json:"truncated,omitempty"` + + // Ts Event timestamp in Unix microseconds. + Ts int64 `json:"ts"` + Type BrowserMonitorScreenshotEventType `json:"type"` +} + +// BrowserMonitorScreenshotEventType defines model for BrowserMonitorScreenshotEvent.Type. +type BrowserMonitorScreenshotEventType string + +// BrowserMonitorScreenshotEventData defines model for BrowserMonitorScreenshotEventData. +type BrowserMonitorScreenshotEventData struct { + // Png Base64-encoded PNG screenshot of the browser viewport. + Png []byte `json:"png"` +} + +// BrowserNetworkIdleEvent A browser network idle event emitted after a 500ms quiet period with no in-flight HTTP requests. +type BrowserNetworkIdleEvent struct { + // Data Browser event context stamped by the browser monitor onto all CDP-sourced events. Identifies the target, frame, and navigation epoch in which the event occurred. + Data *BrowserEventContext `json:"data,omitempty"` + + // Source Provenance metadata identifying which producer emitted the event. + Source BrowserEventSource `json:"source"` + + // Truncated True if the data field was truncated due to size limits. + Truncated *bool `json:"truncated,omitempty"` + + // Ts Event timestamp in Unix microseconds. + Ts int64 `json:"ts"` + Type BrowserNetworkIdleEventType `json:"type"` +} + +// BrowserNetworkIdleEventType defines model for BrowserNetworkIdleEvent.Type. +type BrowserNetworkIdleEventType string + +// BrowserNetworkLoadingFailedEvent A browser network loading failed event. +type BrowserNetworkLoadingFailedEvent struct { + Data *BrowserNetworkLoadingFailedEventData `json:"data,omitempty"` + + // Source Provenance metadata identifying which producer emitted the event. + Source BrowserEventSource `json:"source"` + + // Truncated True if the data field was truncated due to size limits. + Truncated *bool `json:"truncated,omitempty"` + + // Ts Event timestamp in Unix microseconds. + Ts int64 `json:"ts"` + Type BrowserNetworkLoadingFailedEventType `json:"type"` +} + +// BrowserNetworkLoadingFailedEventType defines model for BrowserNetworkLoadingFailedEvent.Type. +type BrowserNetworkLoadingFailedEventType string + +// BrowserNetworkLoadingFailedEventData defines model for BrowserNetworkLoadingFailedEventData. +type BrowserNetworkLoadingFailedEventData struct { + // Canceled True if the request was canceled by the browser or page script. + Canceled bool `json:"canceled"` + + // ErrorText Network error description (e.g. net::ERR_CONNECTION_REFUSED). + ErrorText string `json:"error_text"` + + // FrameId CDP frame identifier within the target. + FrameId *string `json:"frame_id,omitempty"` + + // LoaderId CDP document loader identifier, reset on each navigation. + LoaderId *string `json:"loader_id,omitempty"` + + // NavSeq Monotonically increasing navigation sequence number, incremented on each top-level navigation within the target. + NavSeq int64 `json:"nav_seq"` + + // RequestId CDP request identifier matching the originating network_request event. + RequestId string `json:"request_id"` + + // ResourceType CDP Network.ResourceType for the request, passed through as-is from Chrome. Known values include Document, Fetch, XHR, Script, Stylesheet, Image, Media, Font, TextTrack, EventSource, WebSocket, Manifest, Prefetch, Other, and more. + ResourceType *string `json:"resource_type,omitempty"` + + // SessionId CDP session identifier for the target connection. + SessionId string `json:"session_id"` + + // TargetId Browser target identifier (stable across navigations within a tab). + TargetId string `json:"target_id"` + + // TargetType CDP target type of the page that produced the event. + TargetType BrowserNetworkLoadingFailedEventDataTargetType `json:"target_type"` + + // Url URL relevant to this event; page URL for navigation and page events, request URL for network events. + Url *string `json:"url,omitempty"` +} + +// BrowserNetworkLoadingFailedEventDataTargetType CDP target type of the page that produced the event. +type BrowserNetworkLoadingFailedEventDataTargetType string + +// BrowserNetworkRequestEvent A browser network request sent event. +type BrowserNetworkRequestEvent struct { + Data *BrowserNetworkRequestEventData `json:"data,omitempty"` + + // Source Provenance metadata identifying which producer emitted the event. + Source BrowserEventSource `json:"source"` + + // Truncated True if the data field was truncated due to size limits. + Truncated *bool `json:"truncated,omitempty"` + + // Ts Event timestamp in Unix microseconds. + Ts int64 `json:"ts"` + Type BrowserNetworkRequestEventType `json:"type"` +} + +// BrowserNetworkRequestEventType defines model for BrowserNetworkRequestEvent.Type. +type BrowserNetworkRequestEventType string + +// BrowserNetworkRequestEventData defines model for BrowserNetworkRequestEventData. +type BrowserNetworkRequestEventData struct { + // DocumentUrl URL of the document that initiated the request. + DocumentUrl string `json:"document_url"` + + // FrameId CDP frame identifier within the target. + FrameId *string `json:"frame_id,omitempty"` + + // Headers Request headers. + Headers BrowserHttpHeaders `json:"headers"` + + // InitiatorType CDP Initiator.type indicating what caused the request, passed through as-is from Chrome. Known values include script, parser, preload, and other. + InitiatorType string `json:"initiator_type"` + + // IsRedirect True if this request is the result of a redirect. + IsRedirect *bool `json:"is_redirect,omitempty"` + + // LoaderId CDP document loader identifier, reset on each navigation. + LoaderId *string `json:"loader_id,omitempty"` + + // Method HTTP method as sent on the wire (e.g. GET, POST). + Method string `json:"method"` + + // NavSeq Monotonically increasing navigation sequence number, incremented on each top-level navigation within the target. + NavSeq int64 `json:"nav_seq"` + + // PostData Request body for POST/PUT requests, if available. + PostData *string `json:"post_data,omitempty"` + + // RedirectUrl Original URL before the redirect, present when is_redirect is true. + RedirectUrl *string `json:"redirect_url,omitempty"` + + // RequestId CDP request identifier, unique within the session. + RequestId string `json:"request_id"` + + // ResourceType CDP Network.ResourceType for the request, passed through as-is from Chrome. Known values include Document, Fetch, XHR, Script, Stylesheet, Image, Media, Font, TextTrack, EventSource, WebSocket, Manifest, Prefetch, Other, and more. + ResourceType *string `json:"resource_type,omitempty"` + + // SessionId CDP session identifier for the target connection. + SessionId string `json:"session_id"` + + // TargetId Browser target identifier (stable across navigations within a tab). + TargetId string `json:"target_id"` + + // TargetType CDP target type of the page that produced the event. + TargetType BrowserNetworkRequestEventDataTargetType `json:"target_type"` + + // Url URL relevant to this event; page URL for navigation and page events, request URL for network events. + Url *string `json:"url,omitempty"` +} + +// BrowserNetworkRequestEventDataTargetType CDP target type of the page that produced the event. +type BrowserNetworkRequestEventDataTargetType string + +// BrowserNetworkResponseEvent A browser network response received event. Fired after the response body is fully received, not when headers arrive. +type BrowserNetworkResponseEvent struct { + Data *BrowserNetworkResponseEventData `json:"data,omitempty"` + + // Source Provenance metadata identifying which producer emitted the event. + Source BrowserEventSource `json:"source"` + + // Truncated True if the data field was truncated due to size limits. + Truncated *bool `json:"truncated,omitempty"` + + // Ts Event timestamp in Unix microseconds. + Ts int64 `json:"ts"` + Type BrowserNetworkResponseEventType `json:"type"` +} + +// BrowserNetworkResponseEventType defines model for BrowserNetworkResponseEvent.Type. +type BrowserNetworkResponseEventType string + +// BrowserNetworkResponseEventData defines model for BrowserNetworkResponseEventData. +type BrowserNetworkResponseEventData struct { + // Body Truncated response body, present only for text MIME types. + Body *string `json:"body,omitempty"` + + // FrameId CDP frame identifier within the target. + FrameId *string `json:"frame_id,omitempty"` + + // Headers Response headers. + Headers BrowserHttpHeaders `json:"headers"` + + // LoaderId CDP document loader identifier, reset on each navigation. + LoaderId *string `json:"loader_id,omitempty"` + + // Method HTTP method of the original request. + Method string `json:"method"` + + // MimeType MIME type of the response (e.g. text/html, application/json). + MimeType *string `json:"mime_type,omitempty"` + + // NavSeq Monotonically increasing navigation sequence number, incremented on each top-level navigation within the target. + NavSeq int64 `json:"nav_seq"` + + // RequestId CDP request identifier matching the originating network_request event. + RequestId string `json:"request_id"` + + // ResourceType CDP Network.ResourceType for the request, passed through as-is from Chrome. Known values include Document, Fetch, XHR, Script, Stylesheet, Image, Media, Font, TextTrack, EventSource, WebSocket, Manifest, Prefetch, Other, and more. + ResourceType *string `json:"resource_type,omitempty"` + + // SessionId CDP session identifier for the target connection. + SessionId string `json:"session_id"` + + // Status HTTP response status code. + Status int `json:"status"` + + // StatusText HTTP response status text (e.g. OK, Not Found). + StatusText *string `json:"status_text,omitempty"` + + // TargetId Browser target identifier (stable across navigations within a tab). + TargetId string `json:"target_id"` + + // TargetType CDP target type of the page that produced the event. + TargetType BrowserNetworkResponseEventDataTargetType `json:"target_type"` + + // Url URL relevant to this event; page URL for navigation and page events, request URL for network events. + Url *string `json:"url,omitempty"` +} + +// BrowserNetworkResponseEventDataTargetType CDP target type of the page that produced the event. +type BrowserNetworkResponseEventDataTargetType string + +// BrowserPageDomContentLoadedEvent A browser DOMContentLoaded event (CDP Page.domContentEventFired). +type BrowserPageDomContentLoadedEvent struct { + Data *BrowserPageDomContentLoadedEventData `json:"data,omitempty"` + + // Source Provenance metadata identifying which producer emitted the event. + Source BrowserEventSource `json:"source"` + + // Truncated True if the data field was truncated due to size limits. + Truncated *bool `json:"truncated,omitempty"` + + // Ts Event timestamp in Unix microseconds. + Ts int64 `json:"ts"` + Type BrowserPageDomContentLoadedEventType `json:"type"` +} + +// BrowserPageDomContentLoadedEventType defines model for BrowserPageDomContentLoadedEvent.Type. +type BrowserPageDomContentLoadedEventType string + +// BrowserPageDomContentLoadedEventData defines model for BrowserPageDomContentLoadedEventData. +type BrowserPageDomContentLoadedEventData struct { + // CdpTimestamp Chrome monotonic clock value in seconds at which DOMContentLoaded fired, relative to browser process start (not Unix epoch). Use `ts` for wall-clock time. + CdpTimestamp float32 `json:"cdp_timestamp"` + + // FrameId CDP frame identifier within the target. + FrameId *string `json:"frame_id,omitempty"` + + // LoaderId CDP document loader identifier, reset on each navigation. + LoaderId *string `json:"loader_id,omitempty"` + + // NavSeq Monotonically increasing navigation sequence number, incremented on each top-level navigation within the target. + NavSeq int64 `json:"nav_seq"` + + // SessionId CDP session identifier for the target connection. + SessionId string `json:"session_id"` + + // TargetId Browser target identifier (stable across navigations within a tab). + TargetId string `json:"target_id"` + + // TargetType CDP target type of the page that produced the event. + TargetType BrowserPageDomContentLoadedEventDataTargetType `json:"target_type"` + + // Url URL relevant to this event; page URL for navigation and page events, request URL for network events. + Url *string `json:"url,omitempty"` +} + +// BrowserPageDomContentLoadedEventDataTargetType CDP target type of the page that produced the event. +type BrowserPageDomContentLoadedEventDataTargetType string + +// BrowserPageLayoutSettledEvent A browser layout settled event emitted 1 second after page load with no intervening layout shifts, indicating visual stability. Each layout shift resets the 1-second timer. +type BrowserPageLayoutSettledEvent struct { + // Data Browser event context stamped by the browser monitor onto all CDP-sourced events. Identifies the target, frame, and navigation epoch in which the event occurred. + Data *BrowserEventContext `json:"data,omitempty"` + + // Source Provenance metadata identifying which producer emitted the event. + Source BrowserEventSource `json:"source"` + + // Truncated True if the data field was truncated due to size limits. + Truncated *bool `json:"truncated,omitempty"` + + // Ts Event timestamp in Unix microseconds. + Ts int64 `json:"ts"` + Type BrowserPageLayoutSettledEventType `json:"type"` +} + +// BrowserPageLayoutSettledEventType defines model for BrowserPageLayoutSettledEvent.Type. +type BrowserPageLayoutSettledEventType string + +// BrowserPageLayoutShiftEvent A browser cumulative layout shift (CLS) event from the Performance Timeline API. +type BrowserPageLayoutShiftEvent struct { + Data *BrowserPageLayoutShiftEventData `json:"data,omitempty"` + + // Source Provenance metadata identifying which producer emitted the event. + Source BrowserEventSource `json:"source"` + + // Truncated True if the data field was truncated due to size limits. + Truncated *bool `json:"truncated,omitempty"` + + // Ts Event timestamp in Unix microseconds. + Ts int64 `json:"ts"` + Type BrowserPageLayoutShiftEventType `json:"type"` +} + +// BrowserPageLayoutShiftEventType defines model for BrowserPageLayoutShiftEvent.Type. +type BrowserPageLayoutShiftEventType string + +// BrowserPageLayoutShiftEventData defines model for BrowserPageLayoutShiftEventData. +type BrowserPageLayoutShiftEventData struct { + // Duration Duration of the layout shift entry in milliseconds (always 0 for layout shifts per spec). + Duration float32 `json:"duration"` + + // FrameId CDP frame identifier within the target. + FrameId *string `json:"frame_id,omitempty"` + + // LayoutShiftDetails PerformanceLayoutShift attributes from the Performance Timeline entry. + LayoutShiftDetails *struct { + // HadRecentInput True if the layout shift was preceded by user input within 500ms, excluding it from CLS. + HadRecentInput *bool `json:"had_recent_input,omitempty"` + + // Value Layout shift score for this entry (contribution to CLS). + Value *float32 `json:"value,omitempty"` + } `json:"layout_shift_details,omitempty"` + + // LoaderId CDP document loader identifier, reset on each navigation. + LoaderId *string `json:"loader_id,omitempty"` + + // NavSeq Monotonically increasing navigation sequence number, incremented on each top-level navigation within the target. + NavSeq int64 `json:"nav_seq"` + + // SessionId CDP session identifier for the target connection. + SessionId string `json:"session_id"` + + // SourceFrameId CDP frame identifier of the frame where the layout shift occurred. + SourceFrameId string `json:"source_frame_id"` + + // TargetId Browser target identifier (stable across navigations within a tab). + TargetId string `json:"target_id"` + + // TargetType CDP target type of the page that produced the event. + TargetType BrowserPageLayoutShiftEventDataTargetType `json:"target_type"` + + // Time Performance Timeline timestamp of the layout shift in milliseconds. + Time float32 `json:"time"` + + // Url URL relevant to this event; page URL for navigation and page events, request URL for network events. + Url *string `json:"url,omitempty"` +} + +// BrowserPageLayoutShiftEventDataTargetType CDP target type of the page that produced the event. +type BrowserPageLayoutShiftEventDataTargetType string + +// BrowserPageLcpEvent A browser Largest Contentful Paint (LCP) event from the Performance Timeline API. +type BrowserPageLcpEvent struct { + Data *BrowserPageLcpEventData `json:"data,omitempty"` + + // Source Provenance metadata identifying which producer emitted the event. + Source BrowserEventSource `json:"source"` + + // Truncated True if the data field was truncated due to size limits. + Truncated *bool `json:"truncated,omitempty"` + + // Ts Event timestamp in Unix microseconds. + Ts int64 `json:"ts"` + Type BrowserPageLcpEventType `json:"type"` +} + +// BrowserPageLcpEventType defines model for BrowserPageLcpEvent.Type. +type BrowserPageLcpEventType string + +// BrowserPageLcpEventData defines model for BrowserPageLcpEventData. +type BrowserPageLcpEventData struct { + // FrameId CDP frame identifier within the target. + FrameId *string `json:"frame_id,omitempty"` + + // LcpDetails LargestContentfulPaint attributes from the Performance Timeline entry. + LcpDetails *struct { + // ElementId id attribute of the LCP element, if present. + ElementId *string `json:"element_id,omitempty"` + + // LoadTime Load time of the LCP element in milliseconds. + LoadTime *float32 `json:"load_time,omitempty"` + + // NodeId CDP DOM node identifier of the LCP element. + NodeId *int `json:"node_id,omitempty"` + + // RenderTime Render time of the LCP element in milliseconds; 0 for cross-origin images without Timing-Allow-Origin. + RenderTime *float32 `json:"render_time,omitempty"` + + // Size Visible area of the LCP element in pixels squared. + Size *float32 `json:"size,omitempty"` + + // Url URL of the LCP element for image or video elements. + Url *string `json:"url,omitempty"` + } `json:"lcp_details,omitempty"` + + // LoaderId CDP document loader identifier, reset on each navigation. + LoaderId *string `json:"loader_id,omitempty"` + + // NavSeq Monotonically increasing navigation sequence number, incremented on each top-level navigation within the target. + NavSeq int64 `json:"nav_seq"` + + // SessionId CDP session identifier for the target connection. + SessionId string `json:"session_id"` + + // SourceFrameId CDP frame identifier of the frame where the LCP element was rendered. + SourceFrameId string `json:"source_frame_id"` + + // TargetId Browser target identifier (stable across navigations within a tab). + TargetId string `json:"target_id"` + + // TargetType CDP target type of the page that produced the event. + TargetType BrowserPageLcpEventDataTargetType `json:"target_type"` + + // Time Performance Timeline timestamp of the LCP entry in milliseconds. + Time float32 `json:"time"` + + // Url URL relevant to this event; page URL for navigation and page events, request URL for network events. + Url *string `json:"url,omitempty"` +} + +// BrowserPageLcpEventDataTargetType CDP target type of the page that produced the event. +type BrowserPageLcpEventDataTargetType string + +// BrowserPageLoadEvent A browser page load event (CDP Page.loadEventFired). +type BrowserPageLoadEvent struct { + Data *BrowserPageLoadEventData `json:"data,omitempty"` + + // Source Provenance metadata identifying which producer emitted the event. + Source BrowserEventSource `json:"source"` + + // Truncated True if the data field was truncated due to size limits. + Truncated *bool `json:"truncated,omitempty"` + + // Ts Event timestamp in Unix microseconds. + Ts int64 `json:"ts"` + Type BrowserPageLoadEventType `json:"type"` +} + +// BrowserPageLoadEventType defines model for BrowserPageLoadEvent.Type. +type BrowserPageLoadEventType string + +// BrowserPageLoadEventData defines model for BrowserPageLoadEventData. +type BrowserPageLoadEventData struct { + // CdpTimestamp Chrome monotonic clock value in seconds at which the load event fired, relative to browser process start (not Unix epoch). Use `ts` for wall-clock time. + CdpTimestamp float32 `json:"cdp_timestamp"` + + // FrameId CDP frame identifier within the target. + FrameId *string `json:"frame_id,omitempty"` + + // LoaderId CDP document loader identifier, reset on each navigation. + LoaderId *string `json:"loader_id,omitempty"` + + // NavSeq Monotonically increasing navigation sequence number, incremented on each top-level navigation within the target. + NavSeq int64 `json:"nav_seq"` + + // SessionId CDP session identifier for the target connection. + SessionId string `json:"session_id"` + + // TargetId Browser target identifier (stable across navigations within a tab). + TargetId string `json:"target_id"` + + // TargetType CDP target type of the page that produced the event. + TargetType BrowserPageLoadEventDataTargetType `json:"target_type"` + + // Url URL relevant to this event; page URL for navigation and page events, request URL for network events. + Url *string `json:"url,omitempty"` +} + +// BrowserPageLoadEventDataTargetType CDP target type of the page that produced the event. +type BrowserPageLoadEventDataTargetType string + +// BrowserPageNavigationEvent A browser page navigation started event (CDP Page.frameNavigated). Carries nav context fields inline but not nav_seq, as this event resets the navigation epoch. +type BrowserPageNavigationEvent struct { + Data *BrowserPageNavigationEventData `json:"data,omitempty"` + + // Source Provenance metadata identifying which producer emitted the event. + Source BrowserEventSource `json:"source"` + + // Truncated True if the data field was truncated due to size limits. + Truncated *bool `json:"truncated,omitempty"` + + // Ts Event timestamp in Unix microseconds. + Ts int64 `json:"ts"` + Type BrowserPageNavigationEventType `json:"type"` +} + +// BrowserPageNavigationEventType defines model for BrowserPageNavigationEvent.Type. +type BrowserPageNavigationEventType string + +// BrowserPageNavigationEventData defines model for BrowserPageNavigationEventData. +type BrowserPageNavigationEventData struct { + // FrameId CDP frame identifier of the navigated frame. + FrameId string `json:"frame_id"` + + // LoaderId New CDP document loader identifier assigned for this navigation. + LoaderId string `json:"loader_id"` + + // ParentFrameId Parent frame identifier for subframe navigations; absent for top-level navigations. + ParentFrameId *string `json:"parent_frame_id,omitempty"` + + // SessionId CDP session identifier. + SessionId string `json:"session_id"` + + // TargetId Browser target identifier. + TargetId string `json:"target_id"` + + // TargetType CDP target type of the navigated frame. + TargetType BrowserPageNavigationEventDataTargetType `json:"target_type"` + + // Url URL navigated to. + Url string `json:"url"` +} + +// BrowserPageNavigationEventDataTargetType CDP target type of the navigated frame. +type BrowserPageNavigationEventDataTargetType string + +// BrowserPageNavigationSettledEvent Emitted when page_dom_content_loaded and page_layout_settled have both fired for the same navigation, indicating the page is loaded and visually stable. Independent of network_idle; a single pending request does not block it. +type BrowserPageNavigationSettledEvent struct { + // Data Browser event context stamped by the browser monitor onto all CDP-sourced events. Identifies the target, frame, and navigation epoch in which the event occurred. + Data *BrowserEventContext `json:"data,omitempty"` + + // Source Provenance metadata identifying which producer emitted the event. + Source BrowserEventSource `json:"source"` + + // Truncated True if the data field was truncated due to size limits. + Truncated *bool `json:"truncated,omitempty"` + + // Ts Event timestamp in Unix microseconds. + Ts int64 `json:"ts"` + Type BrowserPageNavigationSettledEventType `json:"type"` +} + +// BrowserPageNavigationSettledEventType defines model for BrowserPageNavigationSettledEvent.Type. +type BrowserPageNavigationSettledEventType string + +// BrowserPageTabOpenedEvent A new browser tab or target was opened (CDP Target.attachedToTarget for page targets). +type BrowserPageTabOpenedEvent struct { + Data *BrowserPageTabOpenedEventData `json:"data,omitempty"` + + // Source Provenance metadata identifying which producer emitted the event. + Source BrowserEventSource `json:"source"` + + // Truncated True if the data field was truncated due to size limits. + Truncated *bool `json:"truncated,omitempty"` + + // Ts Event timestamp in Unix microseconds. + Ts int64 `json:"ts"` + Type BrowserPageTabOpenedEventType `json:"type"` +} + +// BrowserPageTabOpenedEventType defines model for BrowserPageTabOpenedEvent.Type. +type BrowserPageTabOpenedEventType string + +// BrowserPageTabOpenedEventData defines model for BrowserPageTabOpenedEventData. +type BrowserPageTabOpenedEventData struct { + // OpenerId Target identifier of the tab that opened this one, if any. + OpenerId *string `json:"opener_id,omitempty"` + + // TargetId CDP target identifier for the newly opened tab. + TargetId string `json:"target_id"` + + // TargetType CDP target type of the new tab. + TargetType BrowserPageTabOpenedEventDataTargetType `json:"target_type"` + + // Title Initial page title of the new tab. + Title *string `json:"title,omitempty"` + + // Url Initial URL of the new tab. + Url string `json:"url"` +} + +// BrowserPageTabOpenedEventDataTargetType CDP target type of the new tab. +type BrowserPageTabOpenedEventDataTargetType string + +// BrowserTelemetryCategoriesConfig Per-category telemetry capture settings for browser events. +type BrowserTelemetryCategoriesConfig struct { + // Console Console output (log, warn, error) and uncaught exceptions. + Console *BrowserTelemetryCategoryConfig `json:"console,omitempty"` + + // Interaction User interaction events (clicks, keydowns, scroll). + Interaction *BrowserTelemetryCategoryConfig `json:"interaction,omitempty"` + + // Network HTTP request/response metadata. + Network *BrowserTelemetryCategoryConfig `json:"network,omitempty"` + + // Page Page lifecycle events (navigation, load, layout shifts, LCP). + Page *BrowserTelemetryCategoryConfig `json:"page,omitempty"` +} + +// BrowserTelemetryCategoryConfig Configuration for a single telemetry category. +type BrowserTelemetryCategoryConfig struct { + // Enabled Whether this category is captured. In PUT requests, omitting this field defaults to true (category enabled). In PATCH requests, omitting this field (or sending an empty object `{}`) is a no-op; the category retains its current state. To enable or disable a category via PATCH, you must send an explicit `true` or `false`. + Enabled *bool `json:"enabled,omitempty"` +} + +// BrowserTelemetryConfig Telemetry configuration for a browser. Per-category capture settings. Omit a category or set enabled: true to capture it. Set enabled: false to exclude it. Omit the browser key entirely to capture all categories. Set all four categories to enabled: false to clear the telemetry configuration. +type BrowserTelemetryConfig struct { + // Browser Per-category telemetry capture settings for browser events. + Browser *BrowserTelemetryCategoriesConfig `json:"browser,omitempty"` +} + +// ClickMouseRequest defines model for ClickMouseRequest. +type ClickMouseRequest struct { + // Button Mouse button to interact with + Button *ClickMouseRequestButton `json:"button,omitempty"` + + // ClickType Type of click action + ClickType *ClickMouseRequestClickType `json:"click_type,omitempty"` + + // HoldKeys Modifier keys to hold during the click + HoldKeys *[]string `json:"hold_keys,omitempty"` + + // NumClicks Number of times to repeat the click + NumClicks *int `json:"num_clicks,omitempty"` + + // X X coordinate of the click position + X int `json:"x"` + + // Y Y coordinate of the click position + Y int `json:"y"` +} + +// ClickMouseRequestButton Mouse button to interact with +type ClickMouseRequestButton string + +// ClickMouseRequestClickType Type of click action +type ClickMouseRequestClickType string + +// ClipboardContent defines model for ClipboardContent. +type ClipboardContent struct { + // Text Current clipboard text content + Text string `json:"text"` +} + +// ComputerAction A single computer action to execute as part of a batch. The `type` field selects which +// action to perform, and the corresponding field contains the action parameters. +// Exactly one action field matching the type must be provided. +type ComputerAction struct { + ClickMouse *ClickMouseRequest `json:"click_mouse,omitempty"` + DragMouse *DragMouseRequest `json:"drag_mouse,omitempty"` + MoveMouse *MoveMouseRequest `json:"move_mouse,omitempty"` + PressKey *PressKeyRequest `json:"press_key,omitempty"` + Scroll *ScrollRequest `json:"scroll,omitempty"` + SetCursor *SetCursorRequest `json:"set_cursor,omitempty"` + + // Sleep Pause execution for a specified duration. + Sleep *SleepAction `json:"sleep,omitempty"` + + // Type The type of action to perform. + Type ComputerActionType `json:"type"` + TypeText *TypeTextRequest `json:"type_text,omitempty"` +} + +// ComputerActionType The type of action to perform. +type ComputerActionType string + +// CreateDirectoryRequest defines model for CreateDirectoryRequest. +type CreateDirectoryRequest struct { + // Mode Optional directory mode (octal string, e.g. 755). Defaults to 755. + Mode *string `json:"mode,omitempty"` + + // Path Absolute directory path to create. + Path string `json:"path"` +} + +// DeletePathRequest defines model for DeletePathRequest. +type DeletePathRequest struct { + // Path Absolute path to delete. + Path string `json:"path"` +} + +// DeleteRecordingRequest defines model for DeleteRecordingRequest. +type DeleteRecordingRequest struct { + // Id Identifier of the recording to delete. Alphanumeric or hyphen. + Id *string `json:"id,omitempty"` +} + +// DisplayConfig defines model for DisplayConfig. +type DisplayConfig struct { + // Height Current display height in pixels + Height *int `json:"height,omitempty"` + + // RefreshRate Current display refresh rate in Hz (may be null if not detectable) + RefreshRate *int `json:"refresh_rate,omitempty"` + + // Width Current display width in pixels + Width *int `json:"width,omitempty"` +} + +// DragMouseRequest defines model for DragMouseRequest. +type DragMouseRequest struct { + // Button Mouse button to drag with + Button *DragMouseRequestButton `json:"button,omitempty"` + + // Delay Delay in milliseconds between button down and starting to move along the path. + Delay *int `json:"delay,omitempty"` + + // DurationMs Target total duration in milliseconds for the entire drag movement when smooth=true. Omit for automatic timing based on total path length. + DurationMs *int `json:"duration_ms,omitempty"` + + // HoldKeys Modifier keys to hold during the drag + HoldKeys *[]string `json:"hold_keys,omitempty"` + + // Path Ordered list of [x, y] coordinate pairs to move through while dragging. Must contain at least 2 points. + Path [][]int `json:"path"` + + // Smooth Use human-like Bezier curves between path waypoints instead of linear interpolation. When true, steps_per_segment and step_delay_ms are ignored. + Smooth *bool `json:"smooth,omitempty"` + + // StepDelayMs Delay in milliseconds between relative steps while dragging. Ignored when smooth=true. + StepDelayMs *int `json:"step_delay_ms,omitempty"` + + // StepsPerSegment Number of relative move steps per segment in the path. Ignored when smooth=true. Minimum 1. + StepsPerSegment *int `json:"steps_per_segment,omitempty"` +} + +// DragMouseRequestButton Mouse button to drag with +type DragMouseRequestButton string + +// Error defines model for Error. +type Error struct { + Message string `json:"message"` +} + +// ExecutePlaywrightRequest Request to execute Playwright code +type ExecutePlaywrightRequest struct { + // Code TypeScript/JavaScript code to execute. The code has access to 'page', 'context', and 'browser' variables. + // Example: "await page.goto('https://example.com'); return await page.title();" + Code string `json:"code"` + + // TimeoutSec Maximum execution time in seconds. Default is 60. + TimeoutSec *int `json:"timeout_sec,omitempty"` +} + +// ExecutePlaywrightResult Result of Playwright code execution +type ExecutePlaywrightResult struct { + // Error Error message if execution failed + Error *string `json:"error,omitempty"` + + // Result The value returned by the code (if any) + Result interface{} `json:"result,omitempty"` + + // Stderr Standard error from the execution + Stderr *string `json:"stderr,omitempty"` + + // Stdout Standard output from the execution + Stdout *string `json:"stdout,omitempty"` + + // Success Whether the code executed successfully + Success bool `json:"success"` +} + +// FileInfo defines model for FileInfo. +type FileInfo struct { + // IsDir Whether the path is a directory. + IsDir bool `json:"is_dir"` + + // ModTime Last modification time. + ModTime time.Time `json:"mod_time"` + + // Mode File mode bits (e.g., "drwxr-xr-x" or "-rw-r--r--"). + Mode string `json:"mode"` + + // Name Base name of the file or directory. + Name string `json:"name"` + + // Path Absolute path. + Path string `json:"path"` + + // SizeBytes Size in bytes. 0 for directories. + SizeBytes int `json:"size_bytes"` +} + +// FileSystemEvent Filesystem change event. +type FileSystemEvent struct { + // IsDir Whether the affected path is a directory. + IsDir *bool `json:"is_dir,omitempty"` + + // Name Base name of the file or directory affected. + Name *string `json:"name,omitempty"` + + // Path Absolute path of the file or directory. + Path string `json:"path"` + + // Type Event type. + Type FileSystemEventType `json:"type"` +} + +// FileSystemEventType Event type. +type FileSystemEventType string + +// KnownBrowserTelemetryEvent Discriminated union of browser telemetry events emitted by the Kernel image. This is a structural taxonomy: any event on the telemetry stream whose `data` conforms to one of the variants below (selected by `type`) is a `KnownBrowserTelemetryEvent`, regardless of who published it. Caller-published events via POST /telemetry/events are not constrained to this union; see `TelemetryEvent` for the wire shape. Validation of caller payloads against this taxonomy is the consumer's responsibility. +type KnownBrowserTelemetryEvent struct { + union json.RawMessage +} + +// ListFiles Array of file or directory information entries. +type ListFiles = []FileInfo + +// LogEvent A log entry from the application. +type LogEvent struct { + // Message Log message text. + Message string `json:"message"` + + // Timestamp Time the log entry was produced. + Timestamp time.Time `json:"timestamp"` +} + +// MousePositionResponse defines model for MousePositionResponse. +type MousePositionResponse struct { + // X X coordinate of the cursor + X int `json:"x"` + + // Y Y coordinate of the cursor + Y int `json:"y"` +} + +// MoveMouseRequest defines model for MoveMouseRequest. +type MoveMouseRequest struct { + // DurationMs Target total duration in milliseconds for the mouse movement when smooth=true. Omit for automatic timing based on distance. + DurationMs *int `json:"duration_ms,omitempty"` + + // HoldKeys Modifier keys to hold during the move + HoldKeys *[]string `json:"hold_keys,omitempty"` + + // Smooth Use human-like Bezier curve path instead of instant mouse movement. + Smooth *bool `json:"smooth,omitempty"` + + // X X coordinate to move the cursor to + X int `json:"x"` + + // Y Y coordinate to move the cursor to + Y int `json:"y"` +} + +// MovePathRequest defines model for MovePathRequest. +type MovePathRequest struct { + // DestPath Absolute destination path. + DestPath string `json:"dest_path"` + + // SrcPath Absolute source path. + SrcPath string `json:"src_path"` } -// BrowserEventSourceKind Event producer. `cdp`: Chrome DevTools Protocol events from the browser. `kernel_api`: Kernel API server (reserved for server-generated events). `extension`: injected Chrome extension. `local_process`: system process running alongside the browser. -type BrowserEventSourceKind string +// OkResponse Generic OK response. +type OkResponse struct { + // Ok Indicates success. + Ok bool `json:"ok"` +} -// BrowserTelemetryCategoriesConfig Per-category telemetry capture settings for browser events. -type BrowserTelemetryCategoriesConfig struct { - // Console Console output (log, warn, error) and uncaught exceptions. - Console *BrowserTelemetryCategoryConfig `json:"console,omitempty"` +// PatchDisplayRequest defines model for PatchDisplayRequest. +type PatchDisplayRequest struct { + // Height Display height in pixels + Height *int `json:"height,omitempty"` - // Interaction User interaction events (clicks, keydowns, scroll). - Interaction *BrowserTelemetryCategoryConfig `json:"interaction,omitempty"` + // RefreshRate Display refresh rate in Hz. If omitted, uses the highest available rate for the resolution. + RefreshRate *PatchDisplayRequestRefreshRate `json:"refresh_rate,omitempty"` - // Network HTTP request/response metadata. - Network *BrowserTelemetryCategoryConfig `json:"network,omitempty"` + // RequireIdle If true, refuse to resize when live view or recording/replay is active. + RequireIdle *bool `json:"require_idle,omitempty"` - // Page Page lifecycle events (navigation, load, layout shifts, LCP). - Page *BrowserTelemetryCategoryConfig `json:"page,omitempty"` + // RestartChromium If true, restart Chromium after resolution change to ensure it adapts to new size. Default is false for headful, true for headless. + RestartChromium *bool `json:"restart_chromium,omitempty"` + + // Width Display width in pixels + Width *int `json:"width,omitempty"` } -// BrowserTelemetryCategoryConfig Configuration for a single telemetry category. -type BrowserTelemetryCategoryConfig struct { - // Enabled Whether this category is captured. In PUT requests, omitting this field defaults to true (category enabled). In PATCH requests, omitting this field (or sending an empty object `{}`) is a no-op; the category retains its current state. To enable or disable a category via PATCH, you must send an explicit `true` or `false`. - Enabled *bool `json:"enabled,omitempty"` +// PatchDisplayRequestRefreshRate Display refresh rate in Hz. If omitted, uses the highest available rate for the resolution. +type PatchDisplayRequestRefreshRate int + +// PressKeyRequest defines model for PressKeyRequest. +type PressKeyRequest struct { + // Duration Duration to hold the keys down in milliseconds. If omitted or 0, keys are tapped. + Duration *int `json:"duration,omitempty"` + + // HoldKeys Optional modifier keys to hold during the key press sequence. + HoldKeys *[]string `json:"hold_keys,omitempty"` + + // Keys List of key symbols to press. Each item should be a key symbol supported by xdotool + // (see X11 keysym definitions). Examples include "Return", "Shift", "Ctrl", "Alt", "F5". + // Items in this list could also be combinations, e.g. "Ctrl+t" or "Ctrl+Shift+Tab". + Keys []string `json:"keys"` } -// BrowserTelemetryConfig Telemetry configuration for a browser. Per-category capture settings. Omit a category or set enabled: true to capture it. Set enabled: false to exclude it. Omit the browser key entirely to capture all categories. Set all four categories to enabled: false to clear the telemetry configuration. -type BrowserTelemetryConfig struct { - // Browser Per-category telemetry capture settings for browser events. - Browser *BrowserTelemetryCategoriesConfig `json:"browser,omitempty"` +// ProcessExecRequest Request to execute a command synchronously. +type ProcessExecRequest struct { + // Args Command arguments. + Args *[]string `json:"args,omitempty"` + + // AsRoot Run the process with root privileges. + AsRoot *bool `json:"as_root,omitempty"` + + // AsUser Run the process as this user. + AsUser *string `json:"as_user,omitempty"` + + // Command Executable or shell command to run. + Command string `json:"command"` + + // Cwd Working directory (absolute path) to run the command in. + Cwd *string `json:"cwd,omitempty"` + + // Env Environment variables to set for the process. + Env *map[string]string `json:"env,omitempty"` + + // TimeoutSec Maximum execution time in seconds. + TimeoutSec *int `json:"timeout_sec,omitempty"` } -// ClickMouseRequest defines model for ClickMouseRequest. -type ClickMouseRequest struct { - // Button Mouse button to interact with - Button *ClickMouseRequestButton `json:"button,omitempty"` +// ProcessExecResult Result of a synchronous command execution. +type ProcessExecResult struct { + // DurationMs Execution duration in milliseconds. + DurationMs *int `json:"duration_ms,omitempty"` - // ClickType Type of click action - ClickType *ClickMouseRequestClickType `json:"click_type,omitempty"` + // ExitCode Process exit code. + ExitCode *int `json:"exit_code,omitempty"` - // HoldKeys Modifier keys to hold during the click - HoldKeys *[]string `json:"hold_keys,omitempty"` + // StderrB64 Base64-encoded stderr buffer. + StderrB64 *string `json:"stderr_b64,omitempty"` - // NumClicks Number of times to repeat the click - NumClicks *int `json:"num_clicks,omitempty"` + // StdoutB64 Base64-encoded stdout buffer. + StdoutB64 *string `json:"stdout_b64,omitempty"` +} - // X X coordinate of the click position - X int `json:"x"` +// ProcessKillRequest Signal to send to the process. +type ProcessKillRequest struct { + // Signal Signal to send. + Signal ProcessKillRequestSignal `json:"signal"` +} - // Y Y coordinate of the click position - Y int `json:"y"` +// ProcessKillRequestSignal Signal to send. +type ProcessKillRequestSignal string + +// ProcessResizeRequest Resize a PTY-backed process. +type ProcessResizeRequest struct { + // Cols New terminal columns. + Cols int `json:"cols"` + + // Rows New terminal rows. + Rows int `json:"rows"` } -// ClickMouseRequestButton Mouse button to interact with -type ClickMouseRequestButton string +// ProcessSpawnRequest defines model for ProcessSpawnRequest. +type ProcessSpawnRequest struct { + // AllocateTty Allocate a pseudo-terminal (PTY) for the process to enable interactive shells. + AllocateTty *bool `json:"allocate_tty,omitempty"` -// ClickMouseRequestClickType Type of click action -type ClickMouseRequestClickType string + // Args Command arguments. + Args *[]string `json:"args,omitempty"` -// ClipboardContent defines model for ClipboardContent. -type ClipboardContent struct { - // Text Current clipboard text content - Text string `json:"text"` + // AsRoot Run the process with root privileges. + AsRoot *bool `json:"as_root,omitempty"` + + // AsUser Run the process as this user. + AsUser *string `json:"as_user,omitempty"` + + // Cols Initial terminal columns when allocate_tty is true. + Cols *int `json:"cols,omitempty"` + + // Command Executable or shell command to run. + Command string `json:"command"` + + // Cwd Working directory (absolute path) to run the command in. + Cwd *string `json:"cwd,omitempty"` + + // Env Environment variables to set for the process. + Env *map[string]string `json:"env,omitempty"` + + // Rows Initial terminal rows when allocate_tty is true. + Rows *int `json:"rows,omitempty"` + + // TimeoutSec Maximum execution time in seconds. + TimeoutSec *int `json:"timeout_sec,omitempty"` } -// ComputerAction A single computer action to execute as part of a batch. The `type` field selects which -// action to perform, and the corresponding field contains the action parameters. -// Exactly one action field matching the type must be provided. -type ComputerAction struct { - ClickMouse *ClickMouseRequest `json:"click_mouse,omitempty"` - DragMouse *DragMouseRequest `json:"drag_mouse,omitempty"` - MoveMouse *MoveMouseRequest `json:"move_mouse,omitempty"` - PressKey *PressKeyRequest `json:"press_key,omitempty"` - Scroll *ScrollRequest `json:"scroll,omitempty"` - SetCursor *SetCursorRequest `json:"set_cursor,omitempty"` +// ProcessSpawnResult Information about a spawned process. +type ProcessSpawnResult struct { + // Pid OS process ID. + Pid *int `json:"pid,omitempty"` - // Sleep Pause execution for a specified duration. - Sleep *SleepAction `json:"sleep,omitempty"` + // ProcessId Server-assigned identifier for the process. + ProcessId *openapi_types.UUID `json:"process_id,omitempty"` - // Type The type of action to perform. - Type ComputerActionType `json:"type"` - TypeText *TypeTextRequest `json:"type_text,omitempty"` + // StartedAt Timestamp when the process started. + StartedAt *time.Time `json:"started_at,omitempty"` } -// ComputerActionType The type of action to perform. -type ComputerActionType string +// ProcessStatus Current status of a process. +type ProcessStatus struct { + // CpuPct Estimated CPU usage percentage. + CpuPct *float32 `json:"cpu_pct,omitempty"` -// CreateDirectoryRequest defines model for CreateDirectoryRequest. -type CreateDirectoryRequest struct { - // Mode Optional directory mode (octal string, e.g. 755). Defaults to 755. - Mode *string `json:"mode,omitempty"` + // ExitCode Exit code if the process has exited. + ExitCode *int `json:"exit_code,omitempty"` - // Path Absolute directory path to create. - Path string `json:"path"` + // MemBytes Estimated resident memory usage in bytes. + MemBytes *int `json:"mem_bytes,omitempty"` + + // State Process state. + State *ProcessStatusState `json:"state,omitempty"` } -// DeletePathRequest defines model for DeletePathRequest. -type DeletePathRequest struct { - // Path Absolute path to delete. - Path string `json:"path"` +// ProcessStatusState Process state. +type ProcessStatusState string + +// ProcessStdinRequest Data to write to the process standard input. +type ProcessStdinRequest struct { + // DataB64 Base64-encoded data to write. + DataB64 string `json:"data_b64"` } -// DeleteRecordingRequest defines model for DeleteRecordingRequest. -type DeleteRecordingRequest struct { - // Id Identifier of the recording to delete. Alphanumeric or hyphen. - Id *string `json:"id,omitempty"` +// ProcessStdinResult Result of writing to stdin. +type ProcessStdinResult struct { + // WrittenBytes Number of bytes written. + WrittenBytes *int `json:"written_bytes,omitempty"` } -// DisplayConfig defines model for DisplayConfig. -type DisplayConfig struct { - // Height Current display height in pixels - Height *int `json:"height,omitempty"` +// ProcessStreamEvent SSE payload representing process output or lifecycle events. +type ProcessStreamEvent struct { + // DataB64 Base64-encoded data from the process stream. + DataB64 *string `json:"data_b64,omitempty"` - // RefreshRate Current display refresh rate in Hz (may be null if not detectable) - RefreshRate *int `json:"refresh_rate,omitempty"` + // Event Lifecycle event type. + Event *ProcessStreamEventEvent `json:"event,omitempty"` - // Width Current display width in pixels - Width *int `json:"width,omitempty"` + // ExitCode Exit code when the event is "exit". + ExitCode *int `json:"exit_code,omitempty"` + + // Stream Source stream of the data chunk. + Stream *ProcessStreamEventStream `json:"stream,omitempty"` } -// DragMouseRequest defines model for DragMouseRequest. -type DragMouseRequest struct { - // Button Mouse button to drag with - Button *DragMouseRequestButton `json:"button,omitempty"` +// ProcessStreamEventEvent Lifecycle event type. +type ProcessStreamEventEvent string - // Delay Delay in milliseconds between button down and starting to move along the path. - Delay *int `json:"delay,omitempty"` +// ProcessStreamEventStream Source stream of the data chunk. +type ProcessStreamEventStream string - // DurationMs Target total duration in milliseconds for the entire drag movement when smooth=true. Omit for automatic timing based on total path length. - DurationMs *int `json:"duration_ms,omitempty"` +// PublishEventRequest Request body for publishing an event into the telemetry stream. +type PublishEventRequest struct { + // Category Event category. + Category *PublishEventRequestCategory `json:"category,omitempty"` + + // Data Telemetry event payload. + Data interface{} `json:"data,omitempty"` - // HoldKeys Modifier keys to hold during the drag - HoldKeys *[]string `json:"hold_keys,omitempty"` + // Source Provenance metadata identifying which producer emitted the event. + Source *BrowserEventSource `json:"source,omitempty"` - // Path Ordered list of [x, y] coordinate pairs to move through while dragging. Must contain at least 2 points. - Path [][]int `json:"path"` + // Type Event type identifier. + Type string `json:"type"` +} - // Smooth Use human-like Bezier curves between path waypoints instead of linear interpolation. When true, steps_per_segment and step_delay_ms are ignored. - Smooth *bool `json:"smooth,omitempty"` +// PublishEventRequestCategory Event category. +type PublishEventRequestCategory string - // StepDelayMs Delay in milliseconds between relative steps while dragging. Ignored when smooth=true. - StepDelayMs *int `json:"step_delay_ms,omitempty"` +// RecorderInfo defines model for RecorderInfo. +type RecorderInfo struct { + // FinishedAt Timestamp when recording finished + FinishedAt *time.Time `json:"finished_at,omitempty"` + Id string `json:"id"` + IsRecording bool `json:"isRecording"` - // StepsPerSegment Number of relative move steps per segment in the path. Ignored when smooth=true. Minimum 1. - StepsPerSegment *int `json:"steps_per_segment,omitempty"` + // StartedAt Timestamp when recording started + StartedAt *time.Time `json:"started_at,omitempty"` } -// DragMouseRequestButton Mouse button to drag with -type DragMouseRequestButton string +// ScreenshotRegion defines model for ScreenshotRegion. +type ScreenshotRegion struct { + // Height Height of the region in pixels + Height int `json:"height"` -// Error defines model for Error. -type Error struct { - Message string `json:"message"` -} + // Width Width of the region in pixels + Width int `json:"width"` -// ExecutePlaywrightRequest Request to execute Playwright code -type ExecutePlaywrightRequest struct { - // Code TypeScript/JavaScript code to execute. The code has access to 'page', 'context', and 'browser' variables. - // Example: "await page.goto('https://example.com'); return await page.title();" - Code string `json:"code"` + // X X coordinate of the region's top-left corner + X int `json:"x"` - // TimeoutSec Maximum execution time in seconds. Default is 60. - TimeoutSec *int `json:"timeout_sec,omitempty"` + // Y Y coordinate of the region's top-left corner + Y int `json:"y"` } -// ExecutePlaywrightResult Result of Playwright code execution -type ExecutePlaywrightResult struct { - // Error Error message if execution failed - Error *string `json:"error,omitempty"` +// ScreenshotRequest defines model for ScreenshotRequest. +type ScreenshotRequest struct { + Region *ScreenshotRegion `json:"region,omitempty"` +} - // Result The value returned by the code (if any) - Result interface{} `json:"result,omitempty"` +// ScrollRequest defines model for ScrollRequest. +type ScrollRequest struct { + // DeltaX Horizontal scroll amount. Positive scrolls right, negative scrolls left. + DeltaX *int `json:"delta_x,omitempty"` - // Stderr Standard error from the execution - Stderr *string `json:"stderr,omitempty"` + // DeltaY Vertical scroll amount. Positive scrolls down, negative scrolls up. + DeltaY *int `json:"delta_y,omitempty"` - // Stdout Standard output from the execution - Stdout *string `json:"stdout,omitempty"` + // HoldKeys Modifier keys to hold during the scroll + HoldKeys *[]string `json:"hold_keys,omitempty"` - // Success Whether the code executed successfully - Success bool `json:"success"` + // X X coordinate at which to perform the scroll + X int `json:"x"` + + // Y Y coordinate at which to perform the scroll + Y int `json:"y"` } -// FileInfo defines model for FileInfo. -type FileInfo struct { - // IsDir Whether the path is a directory. - IsDir bool `json:"is_dir"` +// SetCursorRequest defines model for SetCursorRequest. +type SetCursorRequest struct { + // Hidden Whether the cursor should be hidden + Hidden bool `json:"hidden"` +} - // ModTime Last modification time. - ModTime time.Time `json:"mod_time"` +// SetFilePermissionsRequest defines model for SetFilePermissionsRequest. +type SetFilePermissionsRequest struct { + // Group New group name or GID. + Group *string `json:"group,omitempty"` - // Mode File mode bits (e.g., "drwxr-xr-x" or "-rw-r--r--"). + // Mode File mode bits (octal string, e.g. 644). Mode string `json:"mode"` - // Name Base name of the file or directory. - Name string `json:"name"` + // Owner New owner username or UID. + Owner *string `json:"owner,omitempty"` - // Path Absolute path. + // Path Absolute path whose permissions are to be changed. Path string `json:"path"` - - // SizeBytes Size in bytes. 0 for directories. - SizeBytes int `json:"size_bytes"` } -// FileSystemEvent Filesystem change event. -type FileSystemEvent struct { - // IsDir Whether the affected path is a directory. - IsDir *bool `json:"is_dir,omitempty"` - - // Name Base name of the file or directory affected. - Name *string `json:"name,omitempty"` +// SleepAction Pause execution for a specified duration. +type SleepAction struct { + // DurationMs Duration to sleep in milliseconds. + DurationMs int `json:"duration_ms"` +} - // Path Absolute path of the file or directory. +// StartFsWatchRequest defines model for StartFsWatchRequest. +type StartFsWatchRequest struct { + // Path Directory to watch. Path string `json:"path"` - // Type Event type. - Type FileSystemEventType `json:"type"` + // Recursive Whether to watch recursively. + Recursive *bool `json:"recursive,omitempty"` } -// FileSystemEventType Event type. -type FileSystemEventType string +// StartRecordingRequest defines model for StartRecordingRequest. +type StartRecordingRequest struct { + // Framerate Recording framerate in fps (overrides server default) + Framerate *int `json:"framerate,omitempty"` -// ListFiles Array of file or directory information entries. -type ListFiles = []FileInfo + // Id Optional identifier for the recording session. Alphanumeric or hyphen. + Id *string `json:"id,omitempty"` -// LogEvent A log entry from the application. -type LogEvent struct { - // Message Log message text. - Message string `json:"message"` + // MaxDurationInSeconds Maximum recording duration in seconds (overrides server default) + MaxDurationInSeconds *int `json:"maxDurationInSeconds,omitempty"` - // Timestamp Time the log entry was produced. - Timestamp time.Time `json:"timestamp"` + // MaxFileSizeInMB Maximum file size in MB (overrides server default) + MaxFileSizeInMB *int `json:"maxFileSizeInMB,omitempty"` } -// MousePositionResponse defines model for MousePositionResponse. -type MousePositionResponse struct { - // X X coordinate of the cursor - X int `json:"x"` +// StopRecordingRequest defines model for StopRecordingRequest. +type StopRecordingRequest struct { + // ForceStop Immediately stop without graceful shutdown. This may result in a corrupted video file. + ForceStop *bool `json:"forceStop,omitempty"` - // Y Y coordinate of the cursor - Y int `json:"y"` + // Id Identifier of the recorder to stop. Alphanumeric or hyphen. + Id *string `json:"id,omitempty"` } -// MoveMouseRequest defines model for MoveMouseRequest. -type MoveMouseRequest struct { - // DurationMs Target total duration in milliseconds for the mouse movement when smooth=true. Omit for automatic timing based on distance. - DurationMs *int `json:"duration_ms,omitempty"` +// TelemetryEnvelope The envelope assigned to a successfully published event. +type TelemetryEnvelope struct { + // Event A telemetry event. The wire-level event shape accepted by the publish endpoint and emitted on the SSE stream. Arbitrary `type` strings and `data` payloads are admitted. For browser events emitted by the Kernel image, `data` conforms to the per-type schema documented in the `Browser*Event` / `Browser*EventData` definitions, selected by `type`. + Event TelemetryEvent `json:"event"` - // HoldKeys Modifier keys to hold during the move - HoldKeys *[]string `json:"hold_keys,omitempty"` + // Seq Process-monotonic sequence number assigned across the lifetime of the server. Use with Last-Event-ID to resume the SSE stream from this point. + Seq int64 `json:"seq"` +} - // Smooth Use human-like Bezier curve path instead of instant mouse movement. - Smooth *bool `json:"smooth,omitempty"` +// TelemetryEvent A telemetry event. The wire-level event shape accepted by the publish endpoint and emitted on the SSE stream. Arbitrary `type` strings and `data` payloads are admitted. For browser events emitted by the Kernel image, `data` conforms to the per-type schema documented in the `Browser*Event` / `Browser*EventData` definitions, selected by `type`. +type TelemetryEvent struct { + // Category Event category. + Category *TelemetryEventCategory `json:"category,omitempty"` - // X X coordinate to move the cursor to - X int `json:"x"` + // Data Arbitrary JSON payload. For browser events listed in `KnownBrowserTelemetryEvent`, the payload conforms to the corresponding `Browser*EventData` schema. + Data interface{} `json:"data,omitempty"` - // Y Y coordinate to move the cursor to - Y int `json:"y"` -} + // Source Provenance metadata identifying which producer emitted the event. + Source *BrowserEventSource `json:"source,omitempty"` -// MovePathRequest defines model for MovePathRequest. -type MovePathRequest struct { - // DestPath Absolute destination path. - DestPath string `json:"dest_path"` + // Truncated Set by the server when the data field was truncated to fit the size limit. + Truncated *bool `json:"truncated,omitempty"` - // SrcPath Absolute source path. - SrcPath string `json:"src_path"` -} + // Ts Unix timestamp in microseconds. Defaults to the current time when omitted. + Ts *int64 `json:"ts,omitempty"` -// OkResponse Generic OK response. -type OkResponse struct { - // Ok Indicates success. - Ok bool `json:"ok"` + // Type Event type identifier. + Type string `json:"type"` } -// PatchDisplayRequest defines model for PatchDisplayRequest. -type PatchDisplayRequest struct { - // Height Display height in pixels - Height *int `json:"height,omitempty"` +// TelemetryEventCategory Event category. +type TelemetryEventCategory string - // RefreshRate Display refresh rate in Hz. If omitted, uses the highest available rate for the resolution. - RefreshRate *PatchDisplayRequestRefreshRate `json:"refresh_rate,omitempty"` +// TelemetryState Current telemetry configuration and status. +type TelemetryState struct { + // Config Telemetry configuration for a browser. Per-category capture settings. Omit a category or set enabled: true to capture it. Set enabled: false to exclude it. Omit the browser key entirely to capture all categories. Set all four categories to enabled: false to clear the telemetry configuration. + Config BrowserTelemetryConfig `json:"config"` - // RequireIdle If true, refuse to resize when live view or recording/replay is active. - RequireIdle *bool `json:"require_idle,omitempty"` + // CreatedAt When the current telemetry configuration was applied. + CreatedAt time.Time `json:"created_at"` - // RestartChromium If true, restart Chromium after resolution change to ensure it adapts to new size. Default is false for headful, true for headless. - RestartChromium *bool `json:"restart_chromium,omitempty"` + // Id Unique identifier for the current telemetry configuration. + Id string `json:"id"` - // Width Display width in pixels - Width *int `json:"width,omitempty"` + // Seq Process-monotonic sequence number of the last published event. Does not reset across configuration changes. + Seq int64 `json:"seq"` + + // Status Current telemetry status. + Status TelemetryStateStatus `json:"status"` } -// PatchDisplayRequestRefreshRate Display refresh rate in Hz. If omitted, uses the highest available rate for the resolution. -type PatchDisplayRequestRefreshRate int +// TelemetryStateStatus Current telemetry status. +type TelemetryStateStatus string -// PressKeyRequest defines model for PressKeyRequest. -type PressKeyRequest struct { - // Duration Duration to hold the keys down in milliseconds. If omitted or 0, keys are tapped. - Duration *int `json:"duration,omitempty"` +// TypeTextRequest defines model for TypeTextRequest. +type TypeTextRequest struct { + // Delay Delay in milliseconds between keystrokes. Ignored when smooth is true. + Delay *int `json:"delay,omitempty"` - // HoldKeys Optional modifier keys to hold during the key press sequence. - HoldKeys *[]string `json:"hold_keys,omitempty"` + // Smooth Use human-like variable keystroke timing instead of a fixed delay. + // Defaults to true (same as moveMouse/dragMouse). Set to false for + // xdotool typing with an optional fixed delay between keys (delay=0 is instant). + // When true, text is typed in word-sized chunks with variable intra-word delays + // and natural inter-word pauses. The delay field is ignored when smooth is true. + Smooth *bool `json:"smooth,omitempty"` - // Keys List of key symbols to press. Each item should be a key symbol supported by xdotool - // (see X11 keysym definitions). Examples include "Return", "Shift", "Ctrl", "Alt", "F5". - // Items in this list could also be combinations, e.g. "Ctrl+t" or "Ctrl+Shift+Tab". - Keys []string `json:"keys"` + // Text Text to type on the host computer + Text string `json:"text"` + + // TypoChance Per-character typo injection rate; mistakes are corrected with backspace. + // Default 0. Only applies when smooth is true (silently ignored when + // smooth is false). + TypoChance *float32 `json:"typo_chance,omitempty"` } -// ProcessExecRequest Request to execute a command synchronously. -type ProcessExecRequest struct { - // Args Command arguments. - Args *[]string `json:"args,omitempty"` - - // AsRoot Run the process with root privileges. - AsRoot *bool `json:"as_root,omitempty"` +// WriteClipboardRequest defines model for WriteClipboardRequest. +type WriteClipboardRequest struct { + // Text Text to write to the system clipboard + Text string `json:"text"` +} - // AsUser Run the process as this user. - AsUser *string `json:"as_user,omitempty"` +// BadRequestError defines model for BadRequestError. +type BadRequestError = Error - // Command Executable or shell command to run. - Command string `json:"command"` +// ConflictError defines model for ConflictError. +type ConflictError = Error - // Cwd Working directory (absolute path) to run the command in. - Cwd *string `json:"cwd,omitempty"` +// InternalError defines model for InternalError. +type InternalError = Error - // Env Environment variables to set for the process. - Env *map[string]string `json:"env,omitempty"` +// NotFoundError defines model for NotFoundError. +type NotFoundError = Error - // TimeoutSec Maximum execution time in seconds. - TimeoutSec *int `json:"timeout_sec,omitempty"` +// PatchChromiumFlagsJSONBody defines parameters for PatchChromiumFlags. +type PatchChromiumFlagsJSONBody struct { + // Flags Chromium flags to merge (e.g., ["--kiosk", "--disable-gpu"]) + Flags []string `json:"flags"` } -// ProcessExecResult Result of a synchronous command execution. -type ProcessExecResult struct { - // DurationMs Execution duration in milliseconds. - DurationMs *int `json:"duration_ms,omitempty"` +// PatchChromiumPoliciesJSONBody defines parameters for PatchChromiumPolicies. +type PatchChromiumPoliciesJSONBody map[string]interface{} - // ExitCode Process exit code. - ExitCode *int `json:"exit_code,omitempty"` +// UploadExtensionsAndRestartMultipartBody defines parameters for UploadExtensionsAndRestart. +type UploadExtensionsAndRestartMultipartBody struct { + // Extensions List of extensions to upload and activate + Extensions []struct { + // Name Folder name to place the extension under /home/kernel/extensions/ + Name string `json:"name"` - // StderrB64 Base64-encoded stderr buffer. - StderrB64 *string `json:"stderr_b64,omitempty"` + // ZipFile Zip archive containing an unpacked Chromium extension (must include manifest.json) + ZipFile openapi_types.File `json:"zip_file"` + } `json:"extensions"` +} - // StdoutB64 Base64-encoded stdout buffer. - StdoutB64 *string `json:"stdout_b64,omitempty"` +// DownloadDirZipParams defines parameters for DownloadDirZip. +type DownloadDirZipParams struct { + // Path Absolute directory path to archive and download. + Path string `form:"path" json:"path"` } -// ProcessKillRequest Signal to send to the process. -type ProcessKillRequest struct { - // Signal Signal to send. - Signal ProcessKillRequestSignal `json:"signal"` +// DownloadDirZstdParams defines parameters for DownloadDirZstd. +type DownloadDirZstdParams struct { + // Path Absolute directory path to archive and download. + Path string `form:"path" json:"path"` + + // CompressionLevel Compression level. Higher levels produce smaller archives but take longer. + // - fastest: ~zstd level 1, maximum speed (~300-500 MB/s) + // - default: ~zstd level 3, balanced speed/ratio (~150 MB/s) + // - better: ~zstd level 7, better ratio (~50-80 MB/s) + // - best: ~zstd level 11, best ratio (~20-40 MB/s) + CompressionLevel *DownloadDirZstdParamsCompressionLevel `form:"compression_level,omitempty" json:"compression_level,omitempty"` } -// ProcessKillRequestSignal Signal to send. -type ProcessKillRequestSignal string +// DownloadDirZstdParamsCompressionLevel defines parameters for DownloadDirZstd. +type DownloadDirZstdParamsCompressionLevel string -// ProcessResizeRequest Resize a PTY-backed process. -type ProcessResizeRequest struct { - // Cols New terminal columns. - Cols int `json:"cols"` +// FileInfoParams defines parameters for FileInfo. +type FileInfoParams struct { + // Path Absolute path of the file or directory. + Path string `form:"path" json:"path"` +} - // Rows New terminal rows. - Rows int `json:"rows"` +// ListFilesParams defines parameters for ListFiles. +type ListFilesParams struct { + // Path Absolute directory path. + Path string `form:"path" json:"path"` } -// ProcessSpawnRequest defines model for ProcessSpawnRequest. -type ProcessSpawnRequest struct { - // AllocateTty Allocate a pseudo-terminal (PTY) for the process to enable interactive shells. - AllocateTty *bool `json:"allocate_tty,omitempty"` +// ReadFileParams defines parameters for ReadFile. +type ReadFileParams struct { + // Path Absolute file path to read. + Path string `form:"path" json:"path"` +} - // Args Command arguments. - Args *[]string `json:"args,omitempty"` +// UploadFilesMultipartBody defines parameters for UploadFiles. +type UploadFilesMultipartBody struct { + Files []struct { + // DestPath Absolute destination path to write the file. + DestPath string `json:"dest_path"` + File openapi_types.File `json:"file"` + } `json:"files"` +} - // AsRoot Run the process with root privileges. - AsRoot *bool `json:"as_root,omitempty"` +// UploadZipMultipartBody defines parameters for UploadZip. +type UploadZipMultipartBody struct { + // DestPath Absolute destination directory to extract the archive to. + DestPath string `json:"dest_path"` + ZipFile openapi_types.File `json:"zip_file"` +} - // AsUser Run the process as this user. - AsUser *string `json:"as_user,omitempty"` +// UploadZstdMultipartBody defines parameters for UploadZstd. +type UploadZstdMultipartBody struct { + // Archive The tar.zst archive file. + Archive openapi_types.File `json:"archive"` - // Cols Initial terminal columns when allocate_tty is true. - Cols *int `json:"cols,omitempty"` + // DestPath Absolute destination directory to extract the archive to. + DestPath string `json:"dest_path"` - // Command Executable or shell command to run. - Command string `json:"command"` + // StripComponents Number of leading path components to strip during extraction (like tar --strip-components). + StripComponents *int `json:"strip_components,omitempty"` +} - // Cwd Working directory (absolute path) to run the command in. - Cwd *string `json:"cwd,omitempty"` +// WriteFileParams defines parameters for WriteFile. +type WriteFileParams struct { + // Path Destination absolute file path. + Path string `form:"path" json:"path"` - // Env Environment variables to set for the process. - Env *map[string]string `json:"env,omitempty"` + // Mode Optional file mode (octal string, e.g. 644). Defaults to 644. + Mode *string `form:"mode,omitempty" json:"mode,omitempty"` +} - // Rows Initial terminal rows when allocate_tty is true. - Rows *int `json:"rows,omitempty"` +// LogsStreamParams defines parameters for LogsStream. +type LogsStreamParams struct { + Source LogsStreamParamsSource `form:"source" json:"source"` + Follow *bool `form:"follow,omitempty" json:"follow,omitempty"` - // TimeoutSec Maximum execution time in seconds. - TimeoutSec *int `json:"timeout_sec,omitempty"` + // Path only required if source is path + Path *string `form:"path,omitempty" json:"path,omitempty"` + + // SupervisorProcess only required if source is supervisor + SupervisorProcess *string `form:"supervisor_process,omitempty" json:"supervisor_process,omitempty"` } -// ProcessSpawnResult Information about a spawned process. -type ProcessSpawnResult struct { - // Pid OS process ID. - Pid *int `json:"pid,omitempty"` +// LogsStreamParamsSource defines parameters for LogsStream. +type LogsStreamParamsSource string - // ProcessId Server-assigned identifier for the process. - ProcessId *openapi_types.UUID `json:"process_id,omitempty"` +// DownloadRecordingParams defines parameters for DownloadRecording. +type DownloadRecordingParams struct { + // Id Optional recorder identifier. When omitted, the server uses the default recorder. + Id *string `form:"id,omitempty" json:"id,omitempty"` +} - // StartedAt Timestamp when the process started. - StartedAt *time.Time `json:"started_at,omitempty"` +// StreamTelemetryEventsParams defines parameters for StreamTelemetryEvents. +type StreamTelemetryEventsParams struct { + // LastEventID Resume after this sequence number. Omit or send 0 to start from the current position. Sequence numbers are process-monotonic, so any previous value resumes correctly from that point. + LastEventID *string `json:"Last-Event-ID,omitempty"` } -// ProcessStatus Current status of a process. -type ProcessStatus struct { - // CpuPct Estimated CPU usage percentage. - CpuPct *float32 `json:"cpu_pct,omitempty"` +// PatchChromiumFlagsJSONRequestBody defines body for PatchChromiumFlags for application/json ContentType. +type PatchChromiumFlagsJSONRequestBody PatchChromiumFlagsJSONBody - // ExitCode Exit code if the process has exited. - ExitCode *int `json:"exit_code,omitempty"` +// PatchChromiumPoliciesJSONRequestBody defines body for PatchChromiumPolicies for application/json ContentType. +type PatchChromiumPoliciesJSONRequestBody PatchChromiumPoliciesJSONBody - // MemBytes Estimated resident memory usage in bytes. - MemBytes *int `json:"mem_bytes,omitempty"` +// UploadExtensionsAndRestartMultipartRequestBody defines body for UploadExtensionsAndRestart for multipart/form-data ContentType. +type UploadExtensionsAndRestartMultipartRequestBody UploadExtensionsAndRestartMultipartBody - // State Process state. - State *ProcessStatusState `json:"state,omitempty"` -} +// BatchComputerActionJSONRequestBody defines body for BatchComputerAction for application/json ContentType. +type BatchComputerActionJSONRequestBody = BatchComputerActionRequest -// ProcessStatusState Process state. -type ProcessStatusState string +// ClickMouseJSONRequestBody defines body for ClickMouse for application/json ContentType. +type ClickMouseJSONRequestBody = ClickMouseRequest -// ProcessStdinRequest Data to write to the process standard input. -type ProcessStdinRequest struct { - // DataB64 Base64-encoded data to write. - DataB64 string `json:"data_b64"` -} +// WriteClipboardJSONRequestBody defines body for WriteClipboard for application/json ContentType. +type WriteClipboardJSONRequestBody = WriteClipboardRequest -// ProcessStdinResult Result of writing to stdin. -type ProcessStdinResult struct { - // WrittenBytes Number of bytes written. - WrittenBytes *int `json:"written_bytes,omitempty"` -} +// SetCursorJSONRequestBody defines body for SetCursor for application/json ContentType. +type SetCursorJSONRequestBody = SetCursorRequest -// ProcessStreamEvent SSE payload representing process output or lifecycle events. -type ProcessStreamEvent struct { - // DataB64 Base64-encoded data from the process stream. - DataB64 *string `json:"data_b64,omitempty"` +// DragMouseJSONRequestBody defines body for DragMouse for application/json ContentType. +type DragMouseJSONRequestBody = DragMouseRequest - // Event Lifecycle event type. - Event *ProcessStreamEventEvent `json:"event,omitempty"` +// MoveMouseJSONRequestBody defines body for MoveMouse for application/json ContentType. +type MoveMouseJSONRequestBody = MoveMouseRequest - // ExitCode Exit code when the event is "exit". - ExitCode *int `json:"exit_code,omitempty"` +// PressKeyJSONRequestBody defines body for PressKey for application/json ContentType. +type PressKeyJSONRequestBody = PressKeyRequest - // Stream Source stream of the data chunk. - Stream *ProcessStreamEventStream `json:"stream,omitempty"` -} +// TakeScreenshotJSONRequestBody defines body for TakeScreenshot for application/json ContentType. +type TakeScreenshotJSONRequestBody = ScreenshotRequest -// ProcessStreamEventEvent Lifecycle event type. -type ProcessStreamEventEvent string +// ScrollJSONRequestBody defines body for Scroll for application/json ContentType. +type ScrollJSONRequestBody = ScrollRequest -// ProcessStreamEventStream Source stream of the data chunk. -type ProcessStreamEventStream string +// TypeTextJSONRequestBody defines body for TypeText for application/json ContentType. +type TypeTextJSONRequestBody = TypeTextRequest -// PublishEventRequest Request body for publishing an event into the telemetry stream. -type PublishEventRequest struct { - // Category Event category. - Category *PublishEventRequestCategory `json:"category,omitempty"` +// PatchDisplayJSONRequestBody defines body for PatchDisplay for application/json ContentType. +type PatchDisplayJSONRequestBody = PatchDisplayRequest - // Data Telemetry event payload. - Data interface{} `json:"data,omitempty"` +// CreateDirectoryJSONRequestBody defines body for CreateDirectory for application/json ContentType. +type CreateDirectoryJSONRequestBody = CreateDirectoryRequest + +// DeleteDirectoryJSONRequestBody defines body for DeleteDirectory for application/json ContentType. +type DeleteDirectoryJSONRequestBody = DeletePathRequest - // Source Provenance metadata identifying which producer emitted the event. - Source *BrowserEventSource `json:"source,omitempty"` +// DeleteFileJSONRequestBody defines body for DeleteFile for application/json ContentType. +type DeleteFileJSONRequestBody = DeletePathRequest - // Type Event type identifier. - Type string `json:"type"` -} +// MovePathJSONRequestBody defines body for MovePath for application/json ContentType. +type MovePathJSONRequestBody = MovePathRequest -// PublishEventRequestCategory Event category. -type PublishEventRequestCategory string +// SetFilePermissionsJSONRequestBody defines body for SetFilePermissions for application/json ContentType. +type SetFilePermissionsJSONRequestBody = SetFilePermissionsRequest -// RecorderInfo defines model for RecorderInfo. -type RecorderInfo struct { - // FinishedAt Timestamp when recording finished - FinishedAt *time.Time `json:"finished_at,omitempty"` - Id string `json:"id"` - IsRecording bool `json:"isRecording"` +// UploadFilesMultipartRequestBody defines body for UploadFiles for multipart/form-data ContentType. +type UploadFilesMultipartRequestBody UploadFilesMultipartBody - // StartedAt Timestamp when recording started - StartedAt *time.Time `json:"started_at,omitempty"` -} +// UploadZipMultipartRequestBody defines body for UploadZip for multipart/form-data ContentType. +type UploadZipMultipartRequestBody UploadZipMultipartBody -// ScreenshotRegion defines model for ScreenshotRegion. -type ScreenshotRegion struct { - // Height Height of the region in pixels - Height int `json:"height"` +// UploadZstdMultipartRequestBody defines body for UploadZstd for multipart/form-data ContentType. +type UploadZstdMultipartRequestBody UploadZstdMultipartBody - // Width Width of the region in pixels - Width int `json:"width"` +// StartFsWatchJSONRequestBody defines body for StartFsWatch for application/json ContentType. +type StartFsWatchJSONRequestBody = StartFsWatchRequest - // X X coordinate of the region's top-left corner - X int `json:"x"` +// ExecutePlaywrightCodeJSONRequestBody defines body for ExecutePlaywrightCode for application/json ContentType. +type ExecutePlaywrightCodeJSONRequestBody = ExecutePlaywrightRequest - // Y Y coordinate of the region's top-left corner - Y int `json:"y"` -} +// ProcessExecJSONRequestBody defines body for ProcessExec for application/json ContentType. +type ProcessExecJSONRequestBody = ProcessExecRequest -// ScreenshotRequest defines model for ScreenshotRequest. -type ScreenshotRequest struct { - Region *ScreenshotRegion `json:"region,omitempty"` -} +// ProcessSpawnJSONRequestBody defines body for ProcessSpawn for application/json ContentType. +type ProcessSpawnJSONRequestBody = ProcessSpawnRequest -// ScrollRequest defines model for ScrollRequest. -type ScrollRequest struct { - // DeltaX Horizontal scroll amount. Positive scrolls right, negative scrolls left. - DeltaX *int `json:"delta_x,omitempty"` +// ProcessKillJSONRequestBody defines body for ProcessKill for application/json ContentType. +type ProcessKillJSONRequestBody = ProcessKillRequest - // DeltaY Vertical scroll amount. Positive scrolls down, negative scrolls up. - DeltaY *int `json:"delta_y,omitempty"` +// ProcessResizeJSONRequestBody defines body for ProcessResize for application/json ContentType. +type ProcessResizeJSONRequestBody = ProcessResizeRequest - // HoldKeys Modifier keys to hold during the scroll - HoldKeys *[]string `json:"hold_keys,omitempty"` +// ProcessStdinJSONRequestBody defines body for ProcessStdin for application/json ContentType. +type ProcessStdinJSONRequestBody = ProcessStdinRequest - // X X coordinate at which to perform the scroll - X int `json:"x"` +// DeleteRecordingJSONRequestBody defines body for DeleteRecording for application/json ContentType. +type DeleteRecordingJSONRequestBody = DeleteRecordingRequest - // Y Y coordinate at which to perform the scroll - Y int `json:"y"` -} +// StartRecordingJSONRequestBody defines body for StartRecording for application/json ContentType. +type StartRecordingJSONRequestBody = StartRecordingRequest -// SetCursorRequest defines model for SetCursorRequest. -type SetCursorRequest struct { - // Hidden Whether the cursor should be hidden - Hidden bool `json:"hidden"` -} +// StopRecordingJSONRequestBody defines body for StopRecording for application/json ContentType. +type StopRecordingJSONRequestBody = StopRecordingRequest -// SetFilePermissionsRequest defines model for SetFilePermissionsRequest. -type SetFilePermissionsRequest struct { - // Group New group name or GID. - Group *string `json:"group,omitempty"` +// PatchTelemetryJSONRequestBody defines body for PatchTelemetry for application/json ContentType. +type PatchTelemetryJSONRequestBody = BrowserTelemetryConfig - // Mode File mode bits (octal string, e.g. 644). - Mode string `json:"mode"` +// PutTelemetryJSONRequestBody defines body for PutTelemetry for application/json ContentType. +type PutTelemetryJSONRequestBody = BrowserTelemetryConfig - // Owner New owner username or UID. - Owner *string `json:"owner,omitempty"` +// PublishTelemetryEventJSONRequestBody defines body for PublishTelemetryEvent for application/json ContentType. +type PublishTelemetryEventJSONRequestBody = PublishEventRequest - // Path Absolute path whose permissions are to be changed. - Path string `json:"path"` +// AsBrowserConsoleLogEvent returns the union data inside the KnownBrowserTelemetryEvent as a BrowserConsoleLogEvent +func (t KnownBrowserTelemetryEvent) AsBrowserConsoleLogEvent() (BrowserConsoleLogEvent, error) { + var body BrowserConsoleLogEvent + err := json.Unmarshal(t.union, &body) + return body, err } -// SleepAction Pause execution for a specified duration. -type SleepAction struct { - // DurationMs Duration to sleep in milliseconds. - DurationMs int `json:"duration_ms"` +// FromBrowserConsoleLogEvent overwrites any union data inside the KnownBrowserTelemetryEvent as the provided BrowserConsoleLogEvent +func (t *KnownBrowserTelemetryEvent) FromBrowserConsoleLogEvent(v BrowserConsoleLogEvent) error { + v.Type = "console_log" + b, err := json.Marshal(v) + t.union = b + return err } -// StartFsWatchRequest defines model for StartFsWatchRequest. -type StartFsWatchRequest struct { - // Path Directory to watch. - Path string `json:"path"` +// MergeBrowserConsoleLogEvent performs a merge with any union data inside the KnownBrowserTelemetryEvent, using the provided BrowserConsoleLogEvent +func (t *KnownBrowserTelemetryEvent) MergeBrowserConsoleLogEvent(v BrowserConsoleLogEvent) error { + v.Type = "console_log" + b, err := json.Marshal(v) + if err != nil { + return err + } - // Recursive Whether to watch recursively. - Recursive *bool `json:"recursive,omitempty"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// StartRecordingRequest defines model for StartRecordingRequest. -type StartRecordingRequest struct { - // Framerate Recording framerate in fps (overrides server default) - Framerate *int `json:"framerate,omitempty"` - - // Id Optional identifier for the recording session. Alphanumeric or hyphen. - Id *string `json:"id,omitempty"` - - // MaxDurationInSeconds Maximum recording duration in seconds (overrides server default) - MaxDurationInSeconds *int `json:"maxDurationInSeconds,omitempty"` - - // MaxFileSizeInMB Maximum file size in MB (overrides server default) - MaxFileSizeInMB *int `json:"maxFileSizeInMB,omitempty"` +// AsBrowserConsoleErrorEvent returns the union data inside the KnownBrowserTelemetryEvent as a BrowserConsoleErrorEvent +func (t KnownBrowserTelemetryEvent) AsBrowserConsoleErrorEvent() (BrowserConsoleErrorEvent, error) { + var body BrowserConsoleErrorEvent + err := json.Unmarshal(t.union, &body) + return body, err } -// StopRecordingRequest defines model for StopRecordingRequest. -type StopRecordingRequest struct { - // ForceStop Immediately stop without graceful shutdown. This may result in a corrupted video file. - ForceStop *bool `json:"forceStop,omitempty"` - - // Id Identifier of the recorder to stop. Alphanumeric or hyphen. - Id *string `json:"id,omitempty"` +// FromBrowserConsoleErrorEvent overwrites any union data inside the KnownBrowserTelemetryEvent as the provided BrowserConsoleErrorEvent +func (t *KnownBrowserTelemetryEvent) FromBrowserConsoleErrorEvent(v BrowserConsoleErrorEvent) error { + v.Type = "console_error" + b, err := json.Marshal(v) + t.union = b + return err } -// TelemetryEnvelope The envelope assigned to a successfully published event. -type TelemetryEnvelope struct { - // Event A telemetry event. The wire-level event shape accepted by the publish endpoint and emitted on the SSE stream. Arbitrary `type` strings and `data` payloads are admitted. For browser events emitted by the Kernel image, `data` conforms to the per-type schema documented in the `Browser*Event` / `Browser*EventData` definitions, selected by `type`. - Event TelemetryEvent `json:"event"` +// MergeBrowserConsoleErrorEvent performs a merge with any union data inside the KnownBrowserTelemetryEvent, using the provided BrowserConsoleErrorEvent +func (t *KnownBrowserTelemetryEvent) MergeBrowserConsoleErrorEvent(v BrowserConsoleErrorEvent) error { + v.Type = "console_error" + b, err := json.Marshal(v) + if err != nil { + return err + } - // Seq Process-monotonic sequence number assigned across the lifetime of the server. Use with Last-Event-ID to resume the SSE stream from this point. - Seq int64 `json:"seq"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// TelemetryEvent A telemetry event. The wire-level event shape accepted by the publish endpoint and emitted on the SSE stream. Arbitrary `type` strings and `data` payloads are admitted. For browser events emitted by the Kernel image, `data` conforms to the per-type schema documented in the `Browser*Event` / `Browser*EventData` definitions, selected by `type`. -type TelemetryEvent struct { - // Category Event category. - Category *TelemetryEventCategory `json:"category,omitempty"` +// AsBrowserNetworkRequestEvent returns the union data inside the KnownBrowserTelemetryEvent as a BrowserNetworkRequestEvent +func (t KnownBrowserTelemetryEvent) AsBrowserNetworkRequestEvent() (BrowserNetworkRequestEvent, error) { + var body BrowserNetworkRequestEvent + err := json.Unmarshal(t.union, &body) + return body, err +} - // Data Arbitrary JSON payload. For Kernel-emitted browser events, the payload conforms to the corresponding `Browser*EventData` schema indicated by `type`. - Data interface{} `json:"data,omitempty"` +// FromBrowserNetworkRequestEvent overwrites any union data inside the KnownBrowserTelemetryEvent as the provided BrowserNetworkRequestEvent +func (t *KnownBrowserTelemetryEvent) FromBrowserNetworkRequestEvent(v BrowserNetworkRequestEvent) error { + v.Type = "network_request" + b, err := json.Marshal(v) + t.union = b + return err +} - // Source Provenance metadata identifying which producer emitted the event. - Source *BrowserEventSource `json:"source,omitempty"` +// MergeBrowserNetworkRequestEvent performs a merge with any union data inside the KnownBrowserTelemetryEvent, using the provided BrowserNetworkRequestEvent +func (t *KnownBrowserTelemetryEvent) MergeBrowserNetworkRequestEvent(v BrowserNetworkRequestEvent) error { + v.Type = "network_request" + b, err := json.Marshal(v) + if err != nil { + return err + } - // Truncated Set by the server when the data field was truncated to fit the size limit. - Truncated *bool `json:"truncated,omitempty"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} - // Ts Unix timestamp in microseconds. Defaults to the current time when omitted. - Ts *int64 `json:"ts,omitempty"` +// AsBrowserNetworkResponseEvent returns the union data inside the KnownBrowserTelemetryEvent as a BrowserNetworkResponseEvent +func (t KnownBrowserTelemetryEvent) AsBrowserNetworkResponseEvent() (BrowserNetworkResponseEvent, error) { + var body BrowserNetworkResponseEvent + err := json.Unmarshal(t.union, &body) + return body, err +} - // Type Event type identifier. - Type string `json:"type"` +// FromBrowserNetworkResponseEvent overwrites any union data inside the KnownBrowserTelemetryEvent as the provided BrowserNetworkResponseEvent +func (t *KnownBrowserTelemetryEvent) FromBrowserNetworkResponseEvent(v BrowserNetworkResponseEvent) error { + v.Type = "network_response" + b, err := json.Marshal(v) + t.union = b + return err } -// TelemetryEventCategory Event category. -type TelemetryEventCategory string +// MergeBrowserNetworkResponseEvent performs a merge with any union data inside the KnownBrowserTelemetryEvent, using the provided BrowserNetworkResponseEvent +func (t *KnownBrowserTelemetryEvent) MergeBrowserNetworkResponseEvent(v BrowserNetworkResponseEvent) error { + v.Type = "network_response" + b, err := json.Marshal(v) + if err != nil { + return err + } -// TelemetryState Current telemetry configuration and status. -type TelemetryState struct { - // Config Telemetry configuration for a browser. Per-category capture settings. Omit a category or set enabled: true to capture it. Set enabled: false to exclude it. Omit the browser key entirely to capture all categories. Set all four categories to enabled: false to clear the telemetry configuration. - Config BrowserTelemetryConfig `json:"config"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} - // CreatedAt When the current telemetry configuration was applied. - CreatedAt time.Time `json:"created_at"` +// AsBrowserNetworkLoadingFailedEvent returns the union data inside the KnownBrowserTelemetryEvent as a BrowserNetworkLoadingFailedEvent +func (t KnownBrowserTelemetryEvent) AsBrowserNetworkLoadingFailedEvent() (BrowserNetworkLoadingFailedEvent, error) { + var body BrowserNetworkLoadingFailedEvent + err := json.Unmarshal(t.union, &body) + return body, err +} - // Id Unique identifier for the current telemetry configuration. - Id string `json:"id"` +// FromBrowserNetworkLoadingFailedEvent overwrites any union data inside the KnownBrowserTelemetryEvent as the provided BrowserNetworkLoadingFailedEvent +func (t *KnownBrowserTelemetryEvent) FromBrowserNetworkLoadingFailedEvent(v BrowserNetworkLoadingFailedEvent) error { + v.Type = "network_loading_failed" + b, err := json.Marshal(v) + t.union = b + return err +} - // Seq Process-monotonic sequence number of the last published event. Does not reset across configuration changes. - Seq int64 `json:"seq"` +// MergeBrowserNetworkLoadingFailedEvent performs a merge with any union data inside the KnownBrowserTelemetryEvent, using the provided BrowserNetworkLoadingFailedEvent +func (t *KnownBrowserTelemetryEvent) MergeBrowserNetworkLoadingFailedEvent(v BrowserNetworkLoadingFailedEvent) error { + v.Type = "network_loading_failed" + b, err := json.Marshal(v) + if err != nil { + return err + } - // Status Current telemetry status. - Status TelemetryStateStatus `json:"status"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// TelemetryStateStatus Current telemetry status. -type TelemetryStateStatus string - -// TypeTextRequest defines model for TypeTextRequest. -type TypeTextRequest struct { - // Delay Delay in milliseconds between keystrokes. Ignored when smooth is true. - Delay *int `json:"delay,omitempty"` +// AsBrowserNetworkIdleEvent returns the union data inside the KnownBrowserTelemetryEvent as a BrowserNetworkIdleEvent +func (t KnownBrowserTelemetryEvent) AsBrowserNetworkIdleEvent() (BrowserNetworkIdleEvent, error) { + var body BrowserNetworkIdleEvent + err := json.Unmarshal(t.union, &body) + return body, err +} - // Smooth Use human-like variable keystroke timing instead of a fixed delay. - // Defaults to true (same as moveMouse/dragMouse). Set to false for - // xdotool typing with an optional fixed delay between keys (delay=0 is instant). - // When true, text is typed in word-sized chunks with variable intra-word delays - // and natural inter-word pauses. The delay field is ignored when smooth is true. - Smooth *bool `json:"smooth,omitempty"` +// FromBrowserNetworkIdleEvent overwrites any union data inside the KnownBrowserTelemetryEvent as the provided BrowserNetworkIdleEvent +func (t *KnownBrowserTelemetryEvent) FromBrowserNetworkIdleEvent(v BrowserNetworkIdleEvent) error { + v.Type = "network_idle" + b, err := json.Marshal(v) + t.union = b + return err +} - // Text Text to type on the host computer - Text string `json:"text"` +// MergeBrowserNetworkIdleEvent performs a merge with any union data inside the KnownBrowserTelemetryEvent, using the provided BrowserNetworkIdleEvent +func (t *KnownBrowserTelemetryEvent) MergeBrowserNetworkIdleEvent(v BrowserNetworkIdleEvent) error { + v.Type = "network_idle" + b, err := json.Marshal(v) + if err != nil { + return err + } - // TypoChance Per-character typo injection rate; mistakes are corrected with backspace. - // Default 0. Only applies when smooth is true (silently ignored when - // smooth is false). - TypoChance *float32 `json:"typo_chance,omitempty"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// WriteClipboardRequest defines model for WriteClipboardRequest. -type WriteClipboardRequest struct { - // Text Text to write to the system clipboard - Text string `json:"text"` +// AsBrowserPageNavigationEvent returns the union data inside the KnownBrowserTelemetryEvent as a BrowserPageNavigationEvent +func (t KnownBrowserTelemetryEvent) AsBrowserPageNavigationEvent() (BrowserPageNavigationEvent, error) { + var body BrowserPageNavigationEvent + err := json.Unmarshal(t.union, &body) + return body, err } -// BadRequestError defines model for BadRequestError. -type BadRequestError = Error +// FromBrowserPageNavigationEvent overwrites any union data inside the KnownBrowserTelemetryEvent as the provided BrowserPageNavigationEvent +func (t *KnownBrowserTelemetryEvent) FromBrowserPageNavigationEvent(v BrowserPageNavigationEvent) error { + v.Type = "page_navigation" + b, err := json.Marshal(v) + t.union = b + return err +} -// ConflictError defines model for ConflictError. -type ConflictError = Error +// MergeBrowserPageNavigationEvent performs a merge with any union data inside the KnownBrowserTelemetryEvent, using the provided BrowserPageNavigationEvent +func (t *KnownBrowserTelemetryEvent) MergeBrowserPageNavigationEvent(v BrowserPageNavigationEvent) error { + v.Type = "page_navigation" + b, err := json.Marshal(v) + if err != nil { + return err + } -// InternalError defines model for InternalError. -type InternalError = Error + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} -// NotFoundError defines model for NotFoundError. -type NotFoundError = Error +// AsBrowserPageDomContentLoadedEvent returns the union data inside the KnownBrowserTelemetryEvent as a BrowserPageDomContentLoadedEvent +func (t KnownBrowserTelemetryEvent) AsBrowserPageDomContentLoadedEvent() (BrowserPageDomContentLoadedEvent, error) { + var body BrowserPageDomContentLoadedEvent + err := json.Unmarshal(t.union, &body) + return body, err +} -// PatchChromiumFlagsJSONBody defines parameters for PatchChromiumFlags. -type PatchChromiumFlagsJSONBody struct { - // Flags Chromium flags to merge (e.g., ["--kiosk", "--disable-gpu"]) - Flags []string `json:"flags"` +// FromBrowserPageDomContentLoadedEvent overwrites any union data inside the KnownBrowserTelemetryEvent as the provided BrowserPageDomContentLoadedEvent +func (t *KnownBrowserTelemetryEvent) FromBrowserPageDomContentLoadedEvent(v BrowserPageDomContentLoadedEvent) error { + v.Type = "page_dom_content_loaded" + b, err := json.Marshal(v) + t.union = b + return err } -// PatchChromiumPoliciesJSONBody defines parameters for PatchChromiumPolicies. -type PatchChromiumPoliciesJSONBody map[string]interface{} +// MergeBrowserPageDomContentLoadedEvent performs a merge with any union data inside the KnownBrowserTelemetryEvent, using the provided BrowserPageDomContentLoadedEvent +func (t *KnownBrowserTelemetryEvent) MergeBrowserPageDomContentLoadedEvent(v BrowserPageDomContentLoadedEvent) error { + v.Type = "page_dom_content_loaded" + b, err := json.Marshal(v) + if err != nil { + return err + } -// UploadExtensionsAndRestartMultipartBody defines parameters for UploadExtensionsAndRestart. -type UploadExtensionsAndRestartMultipartBody struct { - // Extensions List of extensions to upload and activate - Extensions []struct { - // Name Folder name to place the extension under /home/kernel/extensions/ - Name string `json:"name"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} - // ZipFile Zip archive containing an unpacked Chromium extension (must include manifest.json) - ZipFile openapi_types.File `json:"zip_file"` - } `json:"extensions"` +// AsBrowserPageLoadEvent returns the union data inside the KnownBrowserTelemetryEvent as a BrowserPageLoadEvent +func (t KnownBrowserTelemetryEvent) AsBrowserPageLoadEvent() (BrowserPageLoadEvent, error) { + var body BrowserPageLoadEvent + err := json.Unmarshal(t.union, &body) + return body, err } -// DownloadDirZipParams defines parameters for DownloadDirZip. -type DownloadDirZipParams struct { - // Path Absolute directory path to archive and download. - Path string `form:"path" json:"path"` +// FromBrowserPageLoadEvent overwrites any union data inside the KnownBrowserTelemetryEvent as the provided BrowserPageLoadEvent +func (t *KnownBrowserTelemetryEvent) FromBrowserPageLoadEvent(v BrowserPageLoadEvent) error { + v.Type = "page_load" + b, err := json.Marshal(v) + t.union = b + return err } -// DownloadDirZstdParams defines parameters for DownloadDirZstd. -type DownloadDirZstdParams struct { - // Path Absolute directory path to archive and download. - Path string `form:"path" json:"path"` +// MergeBrowserPageLoadEvent performs a merge with any union data inside the KnownBrowserTelemetryEvent, using the provided BrowserPageLoadEvent +func (t *KnownBrowserTelemetryEvent) MergeBrowserPageLoadEvent(v BrowserPageLoadEvent) error { + v.Type = "page_load" + b, err := json.Marshal(v) + if err != nil { + return err + } - // CompressionLevel Compression level. Higher levels produce smaller archives but take longer. - // - fastest: ~zstd level 1, maximum speed (~300-500 MB/s) - // - default: ~zstd level 3, balanced speed/ratio (~150 MB/s) - // - better: ~zstd level 7, better ratio (~50-80 MB/s) - // - best: ~zstd level 11, best ratio (~20-40 MB/s) - CompressionLevel *DownloadDirZstdParamsCompressionLevel `form:"compression_level,omitempty" json:"compression_level,omitempty"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// DownloadDirZstdParamsCompressionLevel defines parameters for DownloadDirZstd. -type DownloadDirZstdParamsCompressionLevel string - -// FileInfoParams defines parameters for FileInfo. -type FileInfoParams struct { - // Path Absolute path of the file or directory. - Path string `form:"path" json:"path"` +// AsBrowserPageTabOpenedEvent returns the union data inside the KnownBrowserTelemetryEvent as a BrowserPageTabOpenedEvent +func (t KnownBrowserTelemetryEvent) AsBrowserPageTabOpenedEvent() (BrowserPageTabOpenedEvent, error) { + var body BrowserPageTabOpenedEvent + err := json.Unmarshal(t.union, &body) + return body, err } -// ListFilesParams defines parameters for ListFiles. -type ListFilesParams struct { - // Path Absolute directory path. - Path string `form:"path" json:"path"` +// FromBrowserPageTabOpenedEvent overwrites any union data inside the KnownBrowserTelemetryEvent as the provided BrowserPageTabOpenedEvent +func (t *KnownBrowserTelemetryEvent) FromBrowserPageTabOpenedEvent(v BrowserPageTabOpenedEvent) error { + v.Type = "page_tab_opened" + b, err := json.Marshal(v) + t.union = b + return err } -// ReadFileParams defines parameters for ReadFile. -type ReadFileParams struct { - // Path Absolute file path to read. - Path string `form:"path" json:"path"` +// MergeBrowserPageTabOpenedEvent performs a merge with any union data inside the KnownBrowserTelemetryEvent, using the provided BrowserPageTabOpenedEvent +func (t *KnownBrowserTelemetryEvent) MergeBrowserPageTabOpenedEvent(v BrowserPageTabOpenedEvent) error { + v.Type = "page_tab_opened" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// UploadFilesMultipartBody defines parameters for UploadFiles. -type UploadFilesMultipartBody struct { - Files []struct { - // DestPath Absolute destination path to write the file. - DestPath string `json:"dest_path"` - File openapi_types.File `json:"file"` - } `json:"files"` +// AsBrowserPageLayoutShiftEvent returns the union data inside the KnownBrowserTelemetryEvent as a BrowserPageLayoutShiftEvent +func (t KnownBrowserTelemetryEvent) AsBrowserPageLayoutShiftEvent() (BrowserPageLayoutShiftEvent, error) { + var body BrowserPageLayoutShiftEvent + err := json.Unmarshal(t.union, &body) + return body, err } -// UploadZipMultipartBody defines parameters for UploadZip. -type UploadZipMultipartBody struct { - // DestPath Absolute destination directory to extract the archive to. - DestPath string `json:"dest_path"` - ZipFile openapi_types.File `json:"zip_file"` +// FromBrowserPageLayoutShiftEvent overwrites any union data inside the KnownBrowserTelemetryEvent as the provided BrowserPageLayoutShiftEvent +func (t *KnownBrowserTelemetryEvent) FromBrowserPageLayoutShiftEvent(v BrowserPageLayoutShiftEvent) error { + v.Type = "page_layout_shift" + b, err := json.Marshal(v) + t.union = b + return err } -// UploadZstdMultipartBody defines parameters for UploadZstd. -type UploadZstdMultipartBody struct { - // Archive The tar.zst archive file. - Archive openapi_types.File `json:"archive"` +// MergeBrowserPageLayoutShiftEvent performs a merge with any union data inside the KnownBrowserTelemetryEvent, using the provided BrowserPageLayoutShiftEvent +func (t *KnownBrowserTelemetryEvent) MergeBrowserPageLayoutShiftEvent(v BrowserPageLayoutShiftEvent) error { + v.Type = "page_layout_shift" + b, err := json.Marshal(v) + if err != nil { + return err + } - // DestPath Absolute destination directory to extract the archive to. - DestPath string `json:"dest_path"` + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} - // StripComponents Number of leading path components to strip during extraction (like tar --strip-components). - StripComponents *int `json:"strip_components,omitempty"` +// AsBrowserPageLcpEvent returns the union data inside the KnownBrowserTelemetryEvent as a BrowserPageLcpEvent +func (t KnownBrowserTelemetryEvent) AsBrowserPageLcpEvent() (BrowserPageLcpEvent, error) { + var body BrowserPageLcpEvent + err := json.Unmarshal(t.union, &body) + return body, err } -// WriteFileParams defines parameters for WriteFile. -type WriteFileParams struct { - // Path Destination absolute file path. - Path string `form:"path" json:"path"` +// FromBrowserPageLcpEvent overwrites any union data inside the KnownBrowserTelemetryEvent as the provided BrowserPageLcpEvent +func (t *KnownBrowserTelemetryEvent) FromBrowserPageLcpEvent(v BrowserPageLcpEvent) error { + v.Type = "page_lcp" + b, err := json.Marshal(v) + t.union = b + return err +} - // Mode Optional file mode (octal string, e.g. 644). Defaults to 644. - Mode *string `form:"mode,omitempty" json:"mode,omitempty"` +// MergeBrowserPageLcpEvent performs a merge with any union data inside the KnownBrowserTelemetryEvent, using the provided BrowserPageLcpEvent +func (t *KnownBrowserTelemetryEvent) MergeBrowserPageLcpEvent(v BrowserPageLcpEvent) error { + v.Type = "page_lcp" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// LogsStreamParams defines parameters for LogsStream. -type LogsStreamParams struct { - Source LogsStreamParamsSource `form:"source" json:"source"` - Follow *bool `form:"follow,omitempty" json:"follow,omitempty"` +// AsBrowserPageLayoutSettledEvent returns the union data inside the KnownBrowserTelemetryEvent as a BrowserPageLayoutSettledEvent +func (t KnownBrowserTelemetryEvent) AsBrowserPageLayoutSettledEvent() (BrowserPageLayoutSettledEvent, error) { + var body BrowserPageLayoutSettledEvent + err := json.Unmarshal(t.union, &body) + return body, err +} - // Path only required if source is path - Path *string `form:"path,omitempty" json:"path,omitempty"` +// FromBrowserPageLayoutSettledEvent overwrites any union data inside the KnownBrowserTelemetryEvent as the provided BrowserPageLayoutSettledEvent +func (t *KnownBrowserTelemetryEvent) FromBrowserPageLayoutSettledEvent(v BrowserPageLayoutSettledEvent) error { + v.Type = "page_layout_settled" + b, err := json.Marshal(v) + t.union = b + return err +} - // SupervisorProcess only required if source is supervisor - SupervisorProcess *string `form:"supervisor_process,omitempty" json:"supervisor_process,omitempty"` +// MergeBrowserPageLayoutSettledEvent performs a merge with any union data inside the KnownBrowserTelemetryEvent, using the provided BrowserPageLayoutSettledEvent +func (t *KnownBrowserTelemetryEvent) MergeBrowserPageLayoutSettledEvent(v BrowserPageLayoutSettledEvent) error { + v.Type = "page_layout_settled" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// LogsStreamParamsSource defines parameters for LogsStream. -type LogsStreamParamsSource string +// AsBrowserPageNavigationSettledEvent returns the union data inside the KnownBrowserTelemetryEvent as a BrowserPageNavigationSettledEvent +func (t KnownBrowserTelemetryEvent) AsBrowserPageNavigationSettledEvent() (BrowserPageNavigationSettledEvent, error) { + var body BrowserPageNavigationSettledEvent + err := json.Unmarshal(t.union, &body) + return body, err +} -// DownloadRecordingParams defines parameters for DownloadRecording. -type DownloadRecordingParams struct { - // Id Optional recorder identifier. When omitted, the server uses the default recorder. - Id *string `form:"id,omitempty" json:"id,omitempty"` +// FromBrowserPageNavigationSettledEvent overwrites any union data inside the KnownBrowserTelemetryEvent as the provided BrowserPageNavigationSettledEvent +func (t *KnownBrowserTelemetryEvent) FromBrowserPageNavigationSettledEvent(v BrowserPageNavigationSettledEvent) error { + v.Type = "page_navigation_settled" + b, err := json.Marshal(v) + t.union = b + return err } -// StreamTelemetryEventsParams defines parameters for StreamTelemetryEvents. -type StreamTelemetryEventsParams struct { - // LastEventID Resume after this sequence number. Omit or send 0 to start from the current position. Sequence numbers are process-monotonic, so any previous value resumes correctly from that point. - LastEventID *string `json:"Last-Event-ID,omitempty"` +// MergeBrowserPageNavigationSettledEvent performs a merge with any union data inside the KnownBrowserTelemetryEvent, using the provided BrowserPageNavigationSettledEvent +func (t *KnownBrowserTelemetryEvent) MergeBrowserPageNavigationSettledEvent(v BrowserPageNavigationSettledEvent) error { + v.Type = "page_navigation_settled" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err } -// PatchChromiumFlagsJSONRequestBody defines body for PatchChromiumFlags for application/json ContentType. -type PatchChromiumFlagsJSONRequestBody PatchChromiumFlagsJSONBody +// AsBrowserInteractionClickEvent returns the union data inside the KnownBrowserTelemetryEvent as a BrowserInteractionClickEvent +func (t KnownBrowserTelemetryEvent) AsBrowserInteractionClickEvent() (BrowserInteractionClickEvent, error) { + var body BrowserInteractionClickEvent + err := json.Unmarshal(t.union, &body) + return body, err +} -// PatchChromiumPoliciesJSONRequestBody defines body for PatchChromiumPolicies for application/json ContentType. -type PatchChromiumPoliciesJSONRequestBody PatchChromiumPoliciesJSONBody +// FromBrowserInteractionClickEvent overwrites any union data inside the KnownBrowserTelemetryEvent as the provided BrowserInteractionClickEvent +func (t *KnownBrowserTelemetryEvent) FromBrowserInteractionClickEvent(v BrowserInteractionClickEvent) error { + v.Type = "interaction_click" + b, err := json.Marshal(v) + t.union = b + return err +} -// UploadExtensionsAndRestartMultipartRequestBody defines body for UploadExtensionsAndRestart for multipart/form-data ContentType. -type UploadExtensionsAndRestartMultipartRequestBody UploadExtensionsAndRestartMultipartBody +// MergeBrowserInteractionClickEvent performs a merge with any union data inside the KnownBrowserTelemetryEvent, using the provided BrowserInteractionClickEvent +func (t *KnownBrowserTelemetryEvent) MergeBrowserInteractionClickEvent(v BrowserInteractionClickEvent) error { + v.Type = "interaction_click" + b, err := json.Marshal(v) + if err != nil { + return err + } -// BatchComputerActionJSONRequestBody defines body for BatchComputerAction for application/json ContentType. -type BatchComputerActionJSONRequestBody = BatchComputerActionRequest + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} -// ClickMouseJSONRequestBody defines body for ClickMouse for application/json ContentType. -type ClickMouseJSONRequestBody = ClickMouseRequest +// AsBrowserInteractionKeyEvent returns the union data inside the KnownBrowserTelemetryEvent as a BrowserInteractionKeyEvent +func (t KnownBrowserTelemetryEvent) AsBrowserInteractionKeyEvent() (BrowserInteractionKeyEvent, error) { + var body BrowserInteractionKeyEvent + err := json.Unmarshal(t.union, &body) + return body, err +} -// WriteClipboardJSONRequestBody defines body for WriteClipboard for application/json ContentType. -type WriteClipboardJSONRequestBody = WriteClipboardRequest +// FromBrowserInteractionKeyEvent overwrites any union data inside the KnownBrowserTelemetryEvent as the provided BrowserInteractionKeyEvent +func (t *KnownBrowserTelemetryEvent) FromBrowserInteractionKeyEvent(v BrowserInteractionKeyEvent) error { + v.Type = "interaction_key" + b, err := json.Marshal(v) + t.union = b + return err +} -// SetCursorJSONRequestBody defines body for SetCursor for application/json ContentType. -type SetCursorJSONRequestBody = SetCursorRequest +// MergeBrowserInteractionKeyEvent performs a merge with any union data inside the KnownBrowserTelemetryEvent, using the provided BrowserInteractionKeyEvent +func (t *KnownBrowserTelemetryEvent) MergeBrowserInteractionKeyEvent(v BrowserInteractionKeyEvent) error { + v.Type = "interaction_key" + b, err := json.Marshal(v) + if err != nil { + return err + } -// DragMouseJSONRequestBody defines body for DragMouse for application/json ContentType. -type DragMouseJSONRequestBody = DragMouseRequest + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} -// MoveMouseJSONRequestBody defines body for MoveMouse for application/json ContentType. -type MoveMouseJSONRequestBody = MoveMouseRequest +// AsBrowserInteractionScrollSettledEvent returns the union data inside the KnownBrowserTelemetryEvent as a BrowserInteractionScrollSettledEvent +func (t KnownBrowserTelemetryEvent) AsBrowserInteractionScrollSettledEvent() (BrowserInteractionScrollSettledEvent, error) { + var body BrowserInteractionScrollSettledEvent + err := json.Unmarshal(t.union, &body) + return body, err +} -// PressKeyJSONRequestBody defines body for PressKey for application/json ContentType. -type PressKeyJSONRequestBody = PressKeyRequest +// FromBrowserInteractionScrollSettledEvent overwrites any union data inside the KnownBrowserTelemetryEvent as the provided BrowserInteractionScrollSettledEvent +func (t *KnownBrowserTelemetryEvent) FromBrowserInteractionScrollSettledEvent(v BrowserInteractionScrollSettledEvent) error { + v.Type = "interaction_scroll_settled" + b, err := json.Marshal(v) + t.union = b + return err +} -// TakeScreenshotJSONRequestBody defines body for TakeScreenshot for application/json ContentType. -type TakeScreenshotJSONRequestBody = ScreenshotRequest +// MergeBrowserInteractionScrollSettledEvent performs a merge with any union data inside the KnownBrowserTelemetryEvent, using the provided BrowserInteractionScrollSettledEvent +func (t *KnownBrowserTelemetryEvent) MergeBrowserInteractionScrollSettledEvent(v BrowserInteractionScrollSettledEvent) error { + v.Type = "interaction_scroll_settled" + b, err := json.Marshal(v) + if err != nil { + return err + } -// ScrollJSONRequestBody defines body for Scroll for application/json ContentType. -type ScrollJSONRequestBody = ScrollRequest + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} -// TypeTextJSONRequestBody defines body for TypeText for application/json ContentType. -type TypeTextJSONRequestBody = TypeTextRequest +// AsBrowserMonitorScreenshotEvent returns the union data inside the KnownBrowserTelemetryEvent as a BrowserMonitorScreenshotEvent +func (t KnownBrowserTelemetryEvent) AsBrowserMonitorScreenshotEvent() (BrowserMonitorScreenshotEvent, error) { + var body BrowserMonitorScreenshotEvent + err := json.Unmarshal(t.union, &body) + return body, err +} -// PatchDisplayJSONRequestBody defines body for PatchDisplay for application/json ContentType. -type PatchDisplayJSONRequestBody = PatchDisplayRequest +// FromBrowserMonitorScreenshotEvent overwrites any union data inside the KnownBrowserTelemetryEvent as the provided BrowserMonitorScreenshotEvent +func (t *KnownBrowserTelemetryEvent) FromBrowserMonitorScreenshotEvent(v BrowserMonitorScreenshotEvent) error { + v.Type = "monitor_screenshot" + b, err := json.Marshal(v) + t.union = b + return err +} -// CreateDirectoryJSONRequestBody defines body for CreateDirectory for application/json ContentType. -type CreateDirectoryJSONRequestBody = CreateDirectoryRequest +// MergeBrowserMonitorScreenshotEvent performs a merge with any union data inside the KnownBrowserTelemetryEvent, using the provided BrowserMonitorScreenshotEvent +func (t *KnownBrowserTelemetryEvent) MergeBrowserMonitorScreenshotEvent(v BrowserMonitorScreenshotEvent) error { + v.Type = "monitor_screenshot" + b, err := json.Marshal(v) + if err != nil { + return err + } -// DeleteDirectoryJSONRequestBody defines body for DeleteDirectory for application/json ContentType. -type DeleteDirectoryJSONRequestBody = DeletePathRequest + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} -// DeleteFileJSONRequestBody defines body for DeleteFile for application/json ContentType. -type DeleteFileJSONRequestBody = DeletePathRequest +// AsBrowserMonitorDisconnectedEvent returns the union data inside the KnownBrowserTelemetryEvent as a BrowserMonitorDisconnectedEvent +func (t KnownBrowserTelemetryEvent) AsBrowserMonitorDisconnectedEvent() (BrowserMonitorDisconnectedEvent, error) { + var body BrowserMonitorDisconnectedEvent + err := json.Unmarshal(t.union, &body) + return body, err +} -// MovePathJSONRequestBody defines body for MovePath for application/json ContentType. -type MovePathJSONRequestBody = MovePathRequest +// FromBrowserMonitorDisconnectedEvent overwrites any union data inside the KnownBrowserTelemetryEvent as the provided BrowserMonitorDisconnectedEvent +func (t *KnownBrowserTelemetryEvent) FromBrowserMonitorDisconnectedEvent(v BrowserMonitorDisconnectedEvent) error { + v.Type = "monitor_disconnected" + b, err := json.Marshal(v) + t.union = b + return err +} -// SetFilePermissionsJSONRequestBody defines body for SetFilePermissions for application/json ContentType. -type SetFilePermissionsJSONRequestBody = SetFilePermissionsRequest +// MergeBrowserMonitorDisconnectedEvent performs a merge with any union data inside the KnownBrowserTelemetryEvent, using the provided BrowserMonitorDisconnectedEvent +func (t *KnownBrowserTelemetryEvent) MergeBrowserMonitorDisconnectedEvent(v BrowserMonitorDisconnectedEvent) error { + v.Type = "monitor_disconnected" + b, err := json.Marshal(v) + if err != nil { + return err + } -// UploadFilesMultipartRequestBody defines body for UploadFiles for multipart/form-data ContentType. -type UploadFilesMultipartRequestBody UploadFilesMultipartBody + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} -// UploadZipMultipartRequestBody defines body for UploadZip for multipart/form-data ContentType. -type UploadZipMultipartRequestBody UploadZipMultipartBody +// AsBrowserMonitorReconnectedEvent returns the union data inside the KnownBrowserTelemetryEvent as a BrowserMonitorReconnectedEvent +func (t KnownBrowserTelemetryEvent) AsBrowserMonitorReconnectedEvent() (BrowserMonitorReconnectedEvent, error) { + var body BrowserMonitorReconnectedEvent + err := json.Unmarshal(t.union, &body) + return body, err +} -// UploadZstdMultipartRequestBody defines body for UploadZstd for multipart/form-data ContentType. -type UploadZstdMultipartRequestBody UploadZstdMultipartBody +// FromBrowserMonitorReconnectedEvent overwrites any union data inside the KnownBrowserTelemetryEvent as the provided BrowserMonitorReconnectedEvent +func (t *KnownBrowserTelemetryEvent) FromBrowserMonitorReconnectedEvent(v BrowserMonitorReconnectedEvent) error { + v.Type = "monitor_reconnected" + b, err := json.Marshal(v) + t.union = b + return err +} -// StartFsWatchJSONRequestBody defines body for StartFsWatch for application/json ContentType. -type StartFsWatchJSONRequestBody = StartFsWatchRequest +// MergeBrowserMonitorReconnectedEvent performs a merge with any union data inside the KnownBrowserTelemetryEvent, using the provided BrowserMonitorReconnectedEvent +func (t *KnownBrowserTelemetryEvent) MergeBrowserMonitorReconnectedEvent(v BrowserMonitorReconnectedEvent) error { + v.Type = "monitor_reconnected" + b, err := json.Marshal(v) + if err != nil { + return err + } -// ExecutePlaywrightCodeJSONRequestBody defines body for ExecutePlaywrightCode for application/json ContentType. -type ExecutePlaywrightCodeJSONRequestBody = ExecutePlaywrightRequest + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} -// ProcessExecJSONRequestBody defines body for ProcessExec for application/json ContentType. -type ProcessExecJSONRequestBody = ProcessExecRequest +// AsBrowserMonitorReconnectFailedEvent returns the union data inside the KnownBrowserTelemetryEvent as a BrowserMonitorReconnectFailedEvent +func (t KnownBrowserTelemetryEvent) AsBrowserMonitorReconnectFailedEvent() (BrowserMonitorReconnectFailedEvent, error) { + var body BrowserMonitorReconnectFailedEvent + err := json.Unmarshal(t.union, &body) + return body, err +} -// ProcessSpawnJSONRequestBody defines body for ProcessSpawn for application/json ContentType. -type ProcessSpawnJSONRequestBody = ProcessSpawnRequest +// FromBrowserMonitorReconnectFailedEvent overwrites any union data inside the KnownBrowserTelemetryEvent as the provided BrowserMonitorReconnectFailedEvent +func (t *KnownBrowserTelemetryEvent) FromBrowserMonitorReconnectFailedEvent(v BrowserMonitorReconnectFailedEvent) error { + v.Type = "monitor_reconnect_failed" + b, err := json.Marshal(v) + t.union = b + return err +} -// ProcessKillJSONRequestBody defines body for ProcessKill for application/json ContentType. -type ProcessKillJSONRequestBody = ProcessKillRequest +// MergeBrowserMonitorReconnectFailedEvent performs a merge with any union data inside the KnownBrowserTelemetryEvent, using the provided BrowserMonitorReconnectFailedEvent +func (t *KnownBrowserTelemetryEvent) MergeBrowserMonitorReconnectFailedEvent(v BrowserMonitorReconnectFailedEvent) error { + v.Type = "monitor_reconnect_failed" + b, err := json.Marshal(v) + if err != nil { + return err + } -// ProcessResizeJSONRequestBody defines body for ProcessResize for application/json ContentType. -type ProcessResizeJSONRequestBody = ProcessResizeRequest + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} -// ProcessStdinJSONRequestBody defines body for ProcessStdin for application/json ContentType. -type ProcessStdinJSONRequestBody = ProcessStdinRequest +// AsBrowserMonitorInitFailedEvent returns the union data inside the KnownBrowserTelemetryEvent as a BrowserMonitorInitFailedEvent +func (t KnownBrowserTelemetryEvent) AsBrowserMonitorInitFailedEvent() (BrowserMonitorInitFailedEvent, error) { + var body BrowserMonitorInitFailedEvent + err := json.Unmarshal(t.union, &body) + return body, err +} -// DeleteRecordingJSONRequestBody defines body for DeleteRecording for application/json ContentType. -type DeleteRecordingJSONRequestBody = DeleteRecordingRequest +// FromBrowserMonitorInitFailedEvent overwrites any union data inside the KnownBrowserTelemetryEvent as the provided BrowserMonitorInitFailedEvent +func (t *KnownBrowserTelemetryEvent) FromBrowserMonitorInitFailedEvent(v BrowserMonitorInitFailedEvent) error { + v.Type = "monitor_init_failed" + b, err := json.Marshal(v) + t.union = b + return err +} -// StartRecordingJSONRequestBody defines body for StartRecording for application/json ContentType. -type StartRecordingJSONRequestBody = StartRecordingRequest +// MergeBrowserMonitorInitFailedEvent performs a merge with any union data inside the KnownBrowserTelemetryEvent, using the provided BrowserMonitorInitFailedEvent +func (t *KnownBrowserTelemetryEvent) MergeBrowserMonitorInitFailedEvent(v BrowserMonitorInitFailedEvent) error { + v.Type = "monitor_init_failed" + b, err := json.Marshal(v) + if err != nil { + return err + } -// StopRecordingJSONRequestBody defines body for StopRecording for application/json ContentType. -type StopRecordingJSONRequestBody = StopRecordingRequest + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} -// PatchTelemetryJSONRequestBody defines body for PatchTelemetry for application/json ContentType. -type PatchTelemetryJSONRequestBody = BrowserTelemetryConfig +func (t KnownBrowserTelemetryEvent) Discriminator() (string, error) { + var discriminator struct { + Discriminator string `json:"type"` + } + err := json.Unmarshal(t.union, &discriminator) + return discriminator.Discriminator, err +} + +func (t KnownBrowserTelemetryEvent) ValueByDiscriminator() (interface{}, error) { + discriminator, err := t.Discriminator() + if err != nil { + return nil, err + } + switch discriminator { + case "console_error": + return t.AsBrowserConsoleErrorEvent() + case "console_log": + return t.AsBrowserConsoleLogEvent() + case "interaction_click": + return t.AsBrowserInteractionClickEvent() + case "interaction_key": + return t.AsBrowserInteractionKeyEvent() + case "interaction_scroll_settled": + return t.AsBrowserInteractionScrollSettledEvent() + case "monitor_disconnected": + return t.AsBrowserMonitorDisconnectedEvent() + case "monitor_init_failed": + return t.AsBrowserMonitorInitFailedEvent() + case "monitor_reconnect_failed": + return t.AsBrowserMonitorReconnectFailedEvent() + case "monitor_reconnected": + return t.AsBrowserMonitorReconnectedEvent() + case "monitor_screenshot": + return t.AsBrowserMonitorScreenshotEvent() + case "network_idle": + return t.AsBrowserNetworkIdleEvent() + case "network_loading_failed": + return t.AsBrowserNetworkLoadingFailedEvent() + case "network_request": + return t.AsBrowserNetworkRequestEvent() + case "network_response": + return t.AsBrowserNetworkResponseEvent() + case "page_dom_content_loaded": + return t.AsBrowserPageDomContentLoadedEvent() + case "page_layout_settled": + return t.AsBrowserPageLayoutSettledEvent() + case "page_layout_shift": + return t.AsBrowserPageLayoutShiftEvent() + case "page_lcp": + return t.AsBrowserPageLcpEvent() + case "page_load": + return t.AsBrowserPageLoadEvent() + case "page_navigation": + return t.AsBrowserPageNavigationEvent() + case "page_navigation_settled": + return t.AsBrowserPageNavigationSettledEvent() + case "page_tab_opened": + return t.AsBrowserPageTabOpenedEvent() + default: + return nil, errors.New("unknown discriminator value: " + discriminator) + } +} -// PutTelemetryJSONRequestBody defines body for PutTelemetry for application/json ContentType. -type PutTelemetryJSONRequestBody = BrowserTelemetryConfig +func (t KnownBrowserTelemetryEvent) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} -// PublishTelemetryEventJSONRequestBody defines body for PublishTelemetryEvent for application/json ContentType. -type PublishTelemetryEventJSONRequestBody = PublishEventRequest +func (t *KnownBrowserTelemetryEvent) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} // RequestEditorFn is the function signature for the RequestEditor callback function type RequestEditorFn func(ctx context.Context, req *http.Request) error @@ -15007,205 +17489,280 @@ func (sh *strictHandler) StreamTelemetryEvents(w http.ResponseWriter, r *http.Re // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/oapi-codegen.yaml b/server/oapi-codegen.yaml index ae65d3a3..d814eac1 100644 --- a/server/oapi-codegen.yaml +++ b/server/oapi-codegen.yaml @@ -6,4 +6,7 @@ generate: models: true embedded-spec: true chi-server: true -output: lib/oapi/oapi.go +output: lib/oapi/oapi.go +output-options: + # Emit schemas not reachable from any operation (e.g. documented event taxonomy). + skip-prune: true diff --git a/server/openapi.yaml b/server/openapi.yaml index ab8007cd..6b51ceb2 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -1304,9 +1304,9 @@ paths: Fresh connections with no Last-Event-ID start from the current sequence and only see new events. The wire shape is the generic `TelemetryEvent`. For browser events - emitted by the Kernel image, the event's `data` conforms to the - documented per-type schema (see the `Browser*Event` / `Browser*EventData` - schemas), selected by `type`. + emitted by the Kernel image (or by callers using a documented `type`), + the event's `data` conforms to one of the variants of + `KnownBrowserTelemetryEvent`, selected by `type`. operationId: streamTelemetryEvents parameters: - in: header @@ -1388,9 +1388,9 @@ components: $ref: "#/components/schemas/BrowserEventSource" data: description: > - Arbitrary JSON payload. For Kernel-emitted browser events, the - payload conforms to the corresponding `Browser*EventData` schema - indicated by `type`. + Arbitrary JSON payload. For browser events listed in + `KnownBrowserTelemetryEvent`, the payload conforms to the + corresponding `Browser*EventData` schema. truncated: type: boolean description: Set by the server when the data field was truncated to fit the size limit. @@ -1552,6 +1552,7 @@ components: Browser event context stamped by the browser monitor onto all CDP-sourced events. Identifies the target, frame, and navigation epoch in which the event occurred. + required: [session_id, target_id, target_type, nav_seq] properties: session_id: type: string @@ -1580,6 +1581,7 @@ components: allOf: - $ref: "#/components/schemas/BrowserEventContext" - type: object + required: [level, text] properties: level: type: string @@ -1621,6 +1623,7 @@ components: allOf: - $ref: "#/components/schemas/BrowserEventContext" - type: object + required: [text] properties: text: type: string @@ -1673,6 +1676,7 @@ components: allOf: - $ref: "#/components/schemas/BrowserEventContext" - type: object + required: [request_id, method, document_url, headers, initiator_type] properties: request_id: type: string @@ -1724,6 +1728,7 @@ components: allOf: - $ref: "#/components/schemas/BrowserEventContext" - type: object + required: [request_id, method, status, headers] properties: request_id: type: string @@ -1772,6 +1777,7 @@ components: allOf: - $ref: "#/components/schemas/BrowserEventContext" - type: object + required: [request_id, error_text, canceled] properties: request_id: type: string @@ -1826,6 +1832,7 @@ components: BrowserPageNavigationEventData: type: object additionalProperties: false + required: [session_id, target_id, target_type, url, frame_id, loader_id] properties: url: type: string @@ -1872,6 +1879,7 @@ components: allOf: - $ref: "#/components/schemas/BrowserEventContext" - type: object + required: [cdp_timestamp] properties: cdp_timestamp: type: number @@ -1899,6 +1907,7 @@ components: allOf: - $ref: "#/components/schemas/BrowserEventContext" - type: object + required: [cdp_timestamp] properties: cdp_timestamp: type: number @@ -1925,6 +1934,7 @@ components: BrowserPageTabOpenedEventData: type: object additionalProperties: false + required: [target_id, target_type, url] properties: target_id: type: string @@ -1965,6 +1975,7 @@ components: allOf: - $ref: "#/components/schemas/BrowserEventContext" - type: object + required: [source_frame_id, time, duration] properties: source_frame_id: type: string @@ -2009,6 +2020,7 @@ components: allOf: - $ref: "#/components/schemas/BrowserEventContext" - type: object + required: [source_frame_id, time] properties: source_frame_id: type: string @@ -2104,6 +2116,7 @@ components: allOf: - $ref: "#/components/schemas/BrowserEventContext" - type: object + required: [x, y, selector, tag] properties: x: type: integer @@ -2143,6 +2156,7 @@ components: allOf: - $ref: "#/components/schemas/BrowserEventContext" - type: object + required: [key, selector, tag] properties: key: type: string @@ -2176,6 +2190,7 @@ components: allOf: - $ref: "#/components/schemas/BrowserEventContext" - type: object + required: [from_x, from_y, to_x, to_y, target_selector] properties: from_x: type: integer @@ -2214,6 +2229,7 @@ components: BrowserMonitorScreenshotEventData: type: object additionalProperties: false + required: [png] properties: png: type: string @@ -2241,6 +2257,7 @@ components: BrowserMonitorDisconnectedEventData: type: object additionalProperties: false + required: [reason] properties: reason: type: string @@ -2268,6 +2285,7 @@ components: BrowserMonitorReconnectedEventData: type: object additionalProperties: false + required: [reconnect_duration_ms] properties: reconnect_duration_ms: type: integer @@ -2295,6 +2313,7 @@ components: BrowserMonitorReconnectFailedEventData: type: object additionalProperties: false + required: [reason] properties: reason: type: string @@ -2322,6 +2341,7 @@ components: BrowserMonitorInitFailedEventData: type: object additionalProperties: false + required: [step] properties: step: type: string @@ -2345,6 +2365,63 @@ components: truncated: type: boolean description: True if the data field was truncated due to size limits. + KnownBrowserTelemetryEvent: + description: > + Discriminated union of browser telemetry events emitted by the Kernel + image. This is a structural taxonomy: any event on the telemetry stream + whose `data` conforms to one of the variants below (selected by `type`) + is a `KnownBrowserTelemetryEvent`, regardless of who published it. + Caller-published events via POST /telemetry/events are not constrained + to this union; see `TelemetryEvent` for the wire shape. Validation of + caller payloads against this taxonomy is the consumer's responsibility. + oneOf: + - $ref: "#/components/schemas/BrowserConsoleLogEvent" + - $ref: "#/components/schemas/BrowserConsoleErrorEvent" + - $ref: "#/components/schemas/BrowserNetworkRequestEvent" + - $ref: "#/components/schemas/BrowserNetworkResponseEvent" + - $ref: "#/components/schemas/BrowserNetworkLoadingFailedEvent" + - $ref: "#/components/schemas/BrowserNetworkIdleEvent" + - $ref: "#/components/schemas/BrowserPageNavigationEvent" + - $ref: "#/components/schemas/BrowserPageDomContentLoadedEvent" + - $ref: "#/components/schemas/BrowserPageLoadEvent" + - $ref: "#/components/schemas/BrowserPageTabOpenedEvent" + - $ref: "#/components/schemas/BrowserPageLayoutShiftEvent" + - $ref: "#/components/schemas/BrowserPageLcpEvent" + - $ref: "#/components/schemas/BrowserPageLayoutSettledEvent" + - $ref: "#/components/schemas/BrowserPageNavigationSettledEvent" + - $ref: "#/components/schemas/BrowserInteractionClickEvent" + - $ref: "#/components/schemas/BrowserInteractionKeyEvent" + - $ref: "#/components/schemas/BrowserInteractionScrollSettledEvent" + - $ref: "#/components/schemas/BrowserMonitorScreenshotEvent" + - $ref: "#/components/schemas/BrowserMonitorDisconnectedEvent" + - $ref: "#/components/schemas/BrowserMonitorReconnectedEvent" + - $ref: "#/components/schemas/BrowserMonitorReconnectFailedEvent" + - $ref: "#/components/schemas/BrowserMonitorInitFailedEvent" + discriminator: + propertyName: type + mapping: + console_log: "#/components/schemas/BrowserConsoleLogEvent" + console_error: "#/components/schemas/BrowserConsoleErrorEvent" + network_request: "#/components/schemas/BrowserNetworkRequestEvent" + network_response: "#/components/schemas/BrowserNetworkResponseEvent" + network_loading_failed: "#/components/schemas/BrowserNetworkLoadingFailedEvent" + network_idle: "#/components/schemas/BrowserNetworkIdleEvent" + page_navigation: "#/components/schemas/BrowserPageNavigationEvent" + page_dom_content_loaded: "#/components/schemas/BrowserPageDomContentLoadedEvent" + page_load: "#/components/schemas/BrowserPageLoadEvent" + page_tab_opened: "#/components/schemas/BrowserPageTabOpenedEvent" + page_layout_shift: "#/components/schemas/BrowserPageLayoutShiftEvent" + page_lcp: "#/components/schemas/BrowserPageLcpEvent" + page_layout_settled: "#/components/schemas/BrowserPageLayoutSettledEvent" + page_navigation_settled: "#/components/schemas/BrowserPageNavigationSettledEvent" + interaction_click: "#/components/schemas/BrowserInteractionClickEvent" + interaction_key: "#/components/schemas/BrowserInteractionKeyEvent" + interaction_scroll_settled: "#/components/schemas/BrowserInteractionScrollSettledEvent" + monitor_screenshot: "#/components/schemas/BrowserMonitorScreenshotEvent" + monitor_disconnected: "#/components/schemas/BrowserMonitorDisconnectedEvent" + monitor_reconnected: "#/components/schemas/BrowserMonitorReconnectedEvent" + monitor_reconnect_failed: "#/components/schemas/BrowserMonitorReconnectFailedEvent" + monitor_init_failed: "#/components/schemas/BrowserMonitorInitFailedEvent" TelemetryState: type: object description: Current telemetry configuration and status. From 11738c8628c9af38cb62a8f22b6b00a7d4ef1849 Mon Sep 17 00:00:00 2001 From: Archan Datta Date: Fri, 15 May 2026 10:49:09 -0300 Subject: [PATCH 21/28] refactor: stable events.Console/Network/Page/Interaction/System category constants --- server/cmd/api/api/events.go | 2 +- server/cmd/api/api/telemetry.go | 35 ++++++++--------- server/cmd/api/api/telemetry_test.go | 3 +- server/lib/cdpmonitor/types.go | 7 +--- server/lib/events/event.go | 20 +++++++--- server/lib/events/events_test.go | 38 +++++++++---------- .../lib/events/eventsstorage_writer_test.go | 3 +- server/lib/telemetry/telemetry.go | 4 +- server/lib/telemetry/telemetry_test.go | 28 +++++++------- 9 files changed, 73 insertions(+), 67 deletions(-) diff --git a/server/cmd/api/api/events.go b/server/cmd/api/api/events.go index 7099b9b3..28dd0355 100644 --- a/server/cmd/api/api/events.go +++ b/server/cmd/api/api/events.go @@ -33,7 +33,7 @@ func (s *ApiService) PublishTelemetryEvent(_ context.Context, req oapi.PublishTe } ev.Category = cat } else { - ev.Category = oapi.TelemetryEventCategorySystem + ev.Category = events.System } if body.Source != nil { diff --git a/server/cmd/api/api/telemetry.go b/server/cmd/api/api/telemetry.go index 20edcf64..b3698769 100644 --- a/server/cmd/api/api/telemetry.go +++ b/server/cmd/api/api/telemetry.go @@ -6,6 +6,7 @@ import ( "github.com/nrednav/cuid2" oapi "github.com/kernel/kernel-images/server/lib/oapi" + "github.com/kernel/kernel-images/server/lib/events" "github.com/kernel/kernel-images/server/lib/logger" "github.com/kernel/kernel-images/server/lib/telemetry" ) @@ -140,16 +141,16 @@ func telemetryConfigFromOAPI(cfg *oapi.BrowserTelemetryConfig) (telemetry.Teleme cats := make([]oapi.TelemetryEventCategory, 0, 5) if consoleOn { - cats = append(cats, oapi.TelemetryEventCategoryConsole) + cats = append(cats, events.Console) } if networkOn { - cats = append(cats, oapi.TelemetryEventCategoryNetwork) + cats = append(cats, events.Network) } if pageOn { - cats = append(cats, oapi.TelemetryEventCategoryPage) + cats = append(cats, events.Page) } if interactionOn { - cats = append(cats, oapi.TelemetryEventCategoryInteraction) + cats = append(cats, events.Interaction) } // CategorySystem is always appended by TelemetrySession.Start/UpdateConfig; // no need to include it here. @@ -162,7 +163,7 @@ func telemetryConfigFromOAPI(cfg *oapi.BrowserTelemetryConfig) (telemetry.Teleme func mergeTelemetryConfig(current telemetry.TelemetryConfig, patch *oapi.BrowserTelemetryCategoriesConfig) (telemetry.TelemetryConfig, bool) { active := make(map[oapi.TelemetryEventCategory]struct{}, len(current.Categories)) for _, c := range current.Categories { - if c != oapi.TelemetryEventCategorySystem { // system is managed internally by TelemetrySession + if c != events.System { // system is managed internally by TelemetrySession active[c] = struct{}{} } } @@ -178,18 +179,18 @@ func mergeTelemetryConfig(current telemetry.TelemetryConfig, patch *oapi.Browser } } - override(oapi.TelemetryEventCategoryConsole, patch.Console) - override(oapi.TelemetryEventCategoryNetwork, patch.Network) - override(oapi.TelemetryEventCategoryPage, patch.Page) - override(oapi.TelemetryEventCategoryInteraction, patch.Interaction) + override(events.Console, patch.Console) + override(events.Network, patch.Network) + override(events.Page, patch.Page) + override(events.Interaction, patch.Interaction) // CategorySystem is managed internally by TelemetrySession; exclude from the // user-facing allDisabled check. userCats := []oapi.TelemetryEventCategory{ - oapi.TelemetryEventCategoryConsole, - oapi.TelemetryEventCategoryNetwork, - oapi.TelemetryEventCategoryPage, - oapi.TelemetryEventCategoryInteraction, + events.Console, + events.Network, + events.Page, + events.Interaction, } allDisabled := true for _, c := range userCats { @@ -239,10 +240,10 @@ func telemetryConfigToOAPI(cfg telemetry.TelemetryConfig) oapi.BrowserTelemetryC return oapi.BrowserTelemetryConfig{ Browser: &oapi.BrowserTelemetryCategoriesConfig{ - Console: enabled(oapi.TelemetryEventCategoryConsole), - Network: enabled(oapi.TelemetryEventCategoryNetwork), - Page: enabled(oapi.TelemetryEventCategoryPage), - Interaction: enabled(oapi.TelemetryEventCategoryInteraction), + Console: enabled(events.Console), + Network: enabled(events.Network), + Page: enabled(events.Page), + Interaction: enabled(events.Interaction), }, } } diff --git a/server/cmd/api/api/telemetry_test.go b/server/cmd/api/api/telemetry_test.go index 4eab068c..8a64f408 100644 --- a/server/cmd/api/api/telemetry_test.go +++ b/server/cmd/api/api/telemetry_test.go @@ -4,6 +4,7 @@ import ( "context" "testing" + "github.com/kernel/kernel-images/server/lib/events" oapi "github.com/kernel/kernel-images/server/lib/oapi" "github.com/kernel/kernel-images/server/lib/recorder" "github.com/kernel/kernel-images/server/lib/scaletozero" @@ -34,7 +35,7 @@ func TestTelemetryConfigFromOAPI(t *testing.T) { }) require.NoError(t, err) assert.False(t, allDisabled) - assert.Contains(t, cfg.Categories, oapi.TelemetryEventCategoryConsole) + assert.Contains(t, cfg.Categories, events.Console) }) t.Run("all false returns allDisabled=true", func(t *testing.T) { diff --git a/server/lib/cdpmonitor/types.go b/server/lib/cdpmonitor/types.go index 76769180..c804cb79 100644 --- a/server/lib/cdpmonitor/types.go +++ b/server/lib/cdpmonitor/types.go @@ -68,17 +68,14 @@ const ( timelineEventLCP = "largest-contentful-paint" ) +const cdpMethodSetAutoAttach = "Target.setAutoAttach" + // CDP target type for browser pages (as opposed to workers, iframes, etc.). const targetTypePage = "page" // screenshot event payload key for the base64-encoded PNG data. const screenshotDataKey = "png" -// Reason values carried in monitor lifecycle event payloads. -const ( - ReasonChromeRestarted = "chrome_restarted" - ReasonReconnectExhausted = "reconnect_exhausted" -) // targetInfo holds metadata about an attached CDP target/session. type targetInfo struct { diff --git a/server/lib/events/event.go b/server/lib/events/event.go index 20100ad3..892c7718 100644 --- a/server/lib/events/event.go +++ b/server/lib/events/event.go @@ -10,14 +10,22 @@ import ( // maxS2RecordBytes is the maximum record size for the S2 event pipeline (1 MB). const maxS2RecordBytes = 1_000_000 +const ( + Console = oapi.TelemetryEventCategory("console") + Network = oapi.TelemetryEventCategory("network") + Page = oapi.TelemetryEventCategory("page") + Interaction = oapi.TelemetryEventCategory("interaction") + System = oapi.TelemetryEventCategory("system") +) + // AllCategories is the canonical list of all configurable event categories. -// CategorySystem events are always captured regardless of telemetry config. +// System events are always captured regardless of telemetry config. var AllCategories = []oapi.TelemetryEventCategory{ - oapi.TelemetryEventCategoryConsole, - oapi.TelemetryEventCategoryNetwork, - oapi.TelemetryEventCategoryPage, - oapi.TelemetryEventCategoryInteraction, - oapi.TelemetryEventCategorySystem, + Console, + Network, + Page, + Interaction, + System, } // Event is the portable event schema. It contains only producer-emitted content; diff --git a/server/lib/events/events_test.go b/server/lib/events/events_test.go index a21a706f..24a5ad5c 100644 --- a/server/lib/events/events_test.go +++ b/server/lib/events/events_test.go @@ -25,7 +25,7 @@ func TestEventSerialization(t *testing.T) { ev := Event{ Ts: 1234567890000, Type: "console.log", - Category: oapi.TelemetryEventCategoryConsole, + Category: Console, Source: oapi.BrowserEventSource{ Kind: oapi.Cdp, Event: func() *string { s := "Runtime.consoleAPICalled"; return &s }(), @@ -67,7 +67,7 @@ func TestEnvelopeSerialization(t *testing.T) { Event: Event{ Ts: 1000, Type: "console.log", - Category: oapi.TelemetryEventCategoryConsole, + Category: Console, Source: oapi.BrowserEventSource{Kind: oapi.Cdp}, }, } @@ -90,7 +90,7 @@ func TestEventData(t *testing.T) { ev := Event{ Ts: 1000, Type: "page.navigation", - Category: oapi.TelemetryEventCategoryPage, + Category: Page, Source: oapi.BrowserEventSource{Kind: oapi.Cdp}, Data: rawData, } @@ -107,7 +107,7 @@ func TestEventOmitEmpty(t *testing.T) { ev := Event{ Ts: 1000, Type: "console.log", - Category: oapi.TelemetryEventCategoryConsole, + Category: Console, Source: oapi.BrowserEventSource{Kind: oapi.Cdp}, } @@ -139,9 +139,9 @@ func TestRingBuffer(t *testing.T) { reader := rb.newReader(0) envelopes := []Envelope{ - mkEnv(1, cdpEvent("console.log", oapi.TelemetryEventCategoryConsole)), - mkEnv(2, cdpEvent("network.request", oapi.TelemetryEventCategoryNetwork)), - mkEnv(3, cdpEvent("page.navigation", oapi.TelemetryEventCategoryPage)), + mkEnv(1, cdpEvent("console.log", Console)), + mkEnv(2, cdpEvent("network.request", Network)), + mkEnv(3, cdpEvent("page.navigation", Page)), } for _, env := range envelopes { @@ -164,9 +164,9 @@ func TestRingBufferOverflowNoBlock(t *testing.T) { done := make(chan struct{}) go func() { - rb.publish(mkEnv(1, cdpEvent("console.log", oapi.TelemetryEventCategoryConsole))) - rb.publish(mkEnv(2, cdpEvent("console.log", oapi.TelemetryEventCategoryConsole))) - rb.publish(mkEnv(3, cdpEvent("console.log", oapi.TelemetryEventCategoryConsole))) + rb.publish(mkEnv(1, cdpEvent("console.log", Console))) + rb.publish(mkEnv(2, cdpEvent("console.log", Console))) + rb.publish(mkEnv(3, cdpEvent("console.log", Console))) close(done) }() @@ -190,9 +190,9 @@ func TestRingBufferOverflowExistingReader(t *testing.T) { rb := newTestRingBuffer(t,2) reader := rb.newReader(0) - rb.publish(mkEnv(1, cdpEvent("console.log", oapi.TelemetryEventCategoryConsole))) - rb.publish(mkEnv(2, cdpEvent("console.log", oapi.TelemetryEventCategoryConsole))) - rb.publish(mkEnv(3, cdpEvent("console.log", oapi.TelemetryEventCategoryConsole))) + rb.publish(mkEnv(1, cdpEvent("console.log", Console))) + rb.publish(mkEnv(2, cdpEvent("console.log", Console))) + rb.publish(mkEnv(3, cdpEvent("console.log", Console))) ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() @@ -214,7 +214,7 @@ func TestRingBufferOverflowExistingReader(t *testing.T) { func TestNewReaderResume(t *testing.T) { rb := newTestRingBuffer(t,10) for i := uint64(1); i <= 5; i++ { - rb.publish(mkEnv(i, cdpEvent("console.log", oapi.TelemetryEventCategoryConsole))) + rb.publish(mkEnv(i, cdpEvent("console.log", Console))) } ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) @@ -238,7 +238,7 @@ func TestNewReaderResume(t *testing.T) { t.Run("resume_before_oldest_triggers_drop", func(t *testing.T) { small := newTestRingBuffer(t, 3) for i := uint64(1); i <= 5; i++ { - small.publish(mkEnv(i, cdpEvent("console.log", oapi.TelemetryEventCategoryConsole))) + small.publish(mkEnv(i, cdpEvent("console.log", Console))) } // oldest in ring is seq 3, requesting resume after seq 1 reader := small.newReader(1) @@ -278,7 +278,7 @@ func TestConcurrentPublishRead(t *testing.T) { go func() { defer wg.Done() for i := 1; i <= numEvents; i++ { - rb.publish(mkEnv(uint64(i), cdpEvent("console.log", oapi.TelemetryEventCategoryConsole))) + rb.publish(mkEnv(uint64(i), cdpEvent("console.log", Console))) } }() @@ -297,7 +297,7 @@ func TestConcurrentReaders(t *testing.T) { } for i := 0; i < numEvents; i++ { - rb.publish(mkEnv(uint64(i+1), cdpEvent("console.log", oapi.TelemetryEventCategoryConsole))) + rb.publish(mkEnv(uint64(i+1), cdpEvent("console.log", Console))) } var wg sync.WaitGroup @@ -336,7 +336,7 @@ func TestRingBufferResetWithActiveReader(t *testing.T) { // Publish some events so the reader advances. for i := uint64(1); i <= 5; i++ { - rb.publish(mkEnv(i, cdpEvent("console.log", oapi.TelemetryEventCategoryConsole))) + rb.publish(mkEnv(i, cdpEvent("console.log", Console))) } ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() @@ -354,7 +354,7 @@ func TestRingBufferResetWithActiveReader(t *testing.T) { assert.ErrorIs(t, err, context.DeadlineExceeded, "reader should block after reset") // Publish new events; reader should resume from seq 1. - rb.publish(mkEnv(1, cdpEvent("page.navigation", oapi.TelemetryEventCategoryPage))) + rb.publish(mkEnv(1, cdpEvent("page.navigation", Page))) env := readEnvelope(t, reader, ctx) assert.Equal(t, uint64(1), env.Seq) assert.Equal(t, "page.navigation", env.Event.Type) diff --git a/server/lib/events/eventsstorage_writer_test.go b/server/lib/events/eventsstorage_writer_test.go index 5bdc31cc..2513cb12 100644 --- a/server/lib/events/eventsstorage_writer_test.go +++ b/server/lib/events/eventsstorage_writer_test.go @@ -8,7 +8,6 @@ import ( "testing" "time" - oapi "github.com/kernel/kernel-images/server/lib/oapi" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -61,7 +60,7 @@ func newTestStream(t *testing.T, capacity int) *EventStream { } func makeEvent(typ string) Event { - return Event{Type: typ, Category: oapi.TelemetryEventCategorySystem} + return Event{Type: typ, Category: System} } func TestStorageWriter_NormalAppend(t *testing.T) { diff --git a/server/lib/telemetry/telemetry.go b/server/lib/telemetry/telemetry.go index 0839289e..6da0dc82 100644 --- a/server/lib/telemetry/telemetry.go +++ b/server/lib/telemetry/telemetry.go @@ -58,7 +58,7 @@ func (s *TelemetrySession) Start(telemetrySessionID string, cfg TelemetryConfig) for _, c := range cats { s.categories[c] = struct{}{} } - s.categories[oapi.TelemetryEventCategorySystem] = struct{}{} + s.categories[events.System] = struct{}{} } // publishLocked stamps telemetry_session_id into ev.Source.Metadata and forwards to the bus. @@ -144,7 +144,7 @@ func (s *TelemetrySession) UpdateConfig(cfg TelemetryConfig) { for _, c := range cats { s.categories[c] = struct{}{} } - s.categories[oapi.TelemetryEventCategorySystem] = struct{}{} + s.categories[events.System] = struct{}{} } // Active reports whether a telemetry session is currently running. diff --git a/server/lib/telemetry/telemetry_test.go b/server/lib/telemetry/telemetry_test.go index 78d6686e..126d8a82 100644 --- a/server/lib/telemetry/telemetry_test.go +++ b/server/lib/telemetry/telemetry_test.go @@ -64,7 +64,7 @@ func TestTelemetrySession(t *testing.T) { go func() { defer wg.Done() for j := 0; j < eventsEach; j++ { - ts.Publish(cdpEvent("console.log", oapi.TelemetryEventCategoryConsole)) + ts.Publish(cdpEvent("console.log", events.Console)) } }() } @@ -82,11 +82,11 @@ func TestTelemetrySession(t *testing.T) { t.Run("seq_continues_across_sessions", func(t *testing.T) { ts := NewTelemetrySession(newTestEventStream(t, 100)) ts.Start("session-1", TelemetryConfig{}) - ts.Publish(cdpEvent("ev.one", oapi.TelemetryEventCategorySystem)) - ts.Publish(cdpEvent("ev.two", oapi.TelemetryEventCategorySystem)) + ts.Publish(cdpEvent("ev.one", events.System)) + ts.Publish(cdpEvent("ev.two", events.System)) ts.Start("session-2", TelemetryConfig{}) - ts.Publish(cdpEvent("ev.three", oapi.TelemetryEventCategorySystem)) + ts.Publish(cdpEvent("ev.three", events.System)) assert.Equal(t, uint64(2), ts.SessionStartSeq(), "session-2 starts after seq 2") @@ -105,7 +105,7 @@ func TestTelemetrySession(t *testing.T) { reader := ts.NewReader(0) for i := 0; i < 3; i++ { - ts.Publish(events.Event{Type: "page.navigation", Category: oapi.TelemetryEventCategoryPage, Source: oapi.BrowserEventSource{Kind: oapi.Cdp}, Ts: 1}) + ts.Publish(events.Event{Type: "page.navigation", Category: events.Page, Source: oapi.BrowserEventSource{Kind: oapi.Cdp}, Ts: 1}) } ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) @@ -122,7 +122,7 @@ func TestTelemetrySession(t *testing.T) { reader := ts.NewReader(0) before := time.Now().UnixMicro() - ts.Publish(events.Event{Type: "page.navigation", Category: oapi.TelemetryEventCategoryPage, Source: oapi.BrowserEventSource{Kind: oapi.Cdp}}) + ts.Publish(events.Event{Type: "page.navigation", Category: events.Page, Source: oapi.BrowserEventSource{Kind: oapi.Cdp}}) after := time.Now().UnixMicro() ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) @@ -137,14 +137,14 @@ func TestTelemetrySession(t *testing.T) { ts := newTestTelemetrySession(t) reader := ts.NewReader(0) - ts.Publish(events.Event{Type: "page.navigation", Category: oapi.TelemetryEventCategoryPage, Source: oapi.BrowserEventSource{Kind: oapi.Cdp}, Ts: 1}) + ts.Publish(events.Event{Type: "page.navigation", Category: events.Page, Source: oapi.BrowserEventSource{Kind: oapi.Cdp}, Ts: 1}) ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() env := readEnvelope(t, reader, ctx) assert.Equal(t, "page.navigation", env.Event.Type) - assert.Equal(t, oapi.TelemetryEventCategoryPage, env.Event.Category) + assert.Equal(t, events.Page, env.Event.Category) }) t.Run("start_sets_telemetry_session_id_in_source_metadata", func(t *testing.T) { @@ -152,7 +152,7 @@ func TestTelemetrySession(t *testing.T) { ts.Start("test-uuid", TelemetryConfig{}) reader := ts.NewReader(0) - ts.Publish(events.Event{Type: "page.navigation", Category: oapi.TelemetryEventCategoryPage, Source: oapi.BrowserEventSource{Kind: oapi.Cdp}, Ts: 1}) + ts.Publish(events.Event{Type: "page.navigation", Category: events.Page, Source: oapi.BrowserEventSource{Kind: oapi.Cdp}, Ts: 1}) ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() @@ -168,7 +168,7 @@ func TestTelemetrySession(t *testing.T) { reader := ts.NewReader(0) ts.Publish(events.Event{ Type: "page.navigation", - Category: oapi.TelemetryEventCategoryPage, + Category: events.Page, Source: oapi.BrowserEventSource{Kind: oapi.Cdp}, Ts: 1, Data: json.RawMessage(`{"url":"https://example.com"}`), @@ -185,16 +185,16 @@ func TestTelemetrySession(t *testing.T) { t.Run("system_events_always_captured_regardless_of_config", func(t *testing.T) { ts := NewTelemetrySession(newTestEventStream(t, 10)) // Start with only console category — system should still pass through. - ts.Start("sys-test", TelemetryConfig{Categories: []oapi.TelemetryEventCategory{oapi.TelemetryEventCategoryConsole}}) + ts.Start("sys-test", TelemetryConfig{Categories: []oapi.TelemetryEventCategory{events.Console}}) reader := ts.NewReader(0) - ts.Publish(events.Event{Type: "monitor.disconnected", Category: oapi.TelemetryEventCategorySystem, Source: oapi.BrowserEventSource{Kind: oapi.KernelApi}, Ts: 1}) + ts.Publish(events.Event{Type: "monitor.disconnected", Category: events.System, Source: oapi.BrowserEventSource{Kind: oapi.KernelApi}, Ts: 1}) ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() env := readEnvelope(t, reader, ctx) - assert.Equal(t, oapi.TelemetryEventCategorySystem, env.Event.Category) + assert.Equal(t, events.System, env.Event.Category) }) t.Run("truncation_applied", func(t *testing.T) { @@ -207,7 +207,7 @@ func TestTelemetrySession(t *testing.T) { ts.Publish(events.Event{ Type: "page.navigation", - Category: oapi.TelemetryEventCategoryPage, + Category: events.Page, Source: oapi.BrowserEventSource{Kind: oapi.Cdp}, Ts: 1, Data: json.RawMessage(rawData), From 72927596c71cbb5ca17bd71c1f31d0fa21bc4a9c Mon Sep 17 00:00:00 2001 From: Archan Datta Date: Fri, 15 May 2026 11:15:22 -0300 Subject: [PATCH 22/28] docs: update cdpmonitor README for browser telemetry event taxonomy changes --- server/lib/cdpmonitor/README.md | 35 ++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/server/lib/cdpmonitor/README.md b/server/lib/cdpmonitor/README.md index c62bc3c5..e3e37800 100644 --- a/server/lib/cdpmonitor/README.md +++ b/server/lib/cdpmonitor/README.md @@ -10,7 +10,7 @@ Chrome can restart independently of the monitor. When that happens, `UpstreamPro ## Event taxonomy -**CDP-derived** (1-to-1 with a CDP notification): `console_log`, `console_error`, `network_request`, `network_response`, `network_loading_failed`, `page_tab_opened`, `page_navigation`, `page_dom_content_loaded`, `page_load`, `page_layout_shift` +**CDP-derived** (1-to-1 with a CDP notification): `console_log`, `console_error`, `network_request`, `network_response`, `network_loading_failed`, `page_tab_opened`, `page_navigation`, `page_dom_content_loaded`, `page_load`, `page_layout_shift`, `page_lcp` **Computed** (inferred from sequences of CDP events): `network_idle` (fires when in-flight requests drop to zero), `page_layout_settled` (1 s after `page_load` with no intervening layout shifts), `page_navigation_settled` (fires once `page_dom_content_loaded` and `page_layout_settled` have both fired for the same navigation; intentionally independent of `network_idle` so that a single hung request cannot stall the event). @@ -29,7 +29,7 @@ Chrome can restart independently of the monitor. When that happens, `UpstreamPro | Screenshot capture via ffmpeg | `screenshot.go` | | CDP protocol types | `cdp_proto.go`, `types.go` | | Interaction tracking injected into the page | `interaction.js` | -| Body/MIME capture sizing and text truncation helpers | `util.go` | +| Body/MIME capture sizing, text truncation, and typed payload helpers | `util.go` | ## Internals @@ -68,7 +68,7 @@ restartMu -> lifeMu -> sessionsMu | Lock | Protects | | --- | --- | | `restartMu` | Serializes `handleUpstreamRestart` to prevent overlapping reconnects from rapid Chrome restarts | -| `lifeMu` | `conn`, `lifecycleCtx`, `cancel`, `done`, `readReady` -- all fields that change during Start / Stop / reconnect | +| `lifeMu` | `conn`, `lifecycleCtx`, `cancel`, `done`, `readReady`: all fields that change during Start / Stop / reconnect | | `pendReqMu` | `pendingRequests` (requestId -> `networkReqState`): in-flight network requests accumulating request/response metadata until `loadingFinished` | | `computed.mu` | All `computedState` fields: counters and timers for the `network_idle`, `page_layout_settled`, and `page_navigation_settled` state machines | | `pendMu` | `pending` (id -> reply channel): in-flight CDP commands waiting for a response from Chrome | @@ -79,7 +79,7 @@ Fields that need no mutex use `sync/atomic`: `nextID`, `mainSessionID`, `running ### WebSocket concurrency -`coder/websocket` guarantees one concurrent `Read` and one concurrent `Write` are safe on the same connection. `readLoop` is the sole reader. All writes go through `send`, which calls `conn.Write` directly -- `conn.Write` is internally serialized by the library, so no external write mutex is needed. +`coder/websocket` guarantees one concurrent `Read` and one concurrent `Write` are safe on the same connection. `readLoop` is the sole reader. All writes go through `send`, which calls `conn.Write` directly; `conn.Write` is internally serialized by the library, so no external write mutex is needed. ## Event data model @@ -89,13 +89,21 @@ Every event arrives as an `Envelope`: ```json { - "telemetry_session_id": "cs_abc123", "seq": 42, "event": { "ts": 1746123456789000, "type": "network_request", "category": "network", - "source": { ... }, + "source": { + "kind": "cdp", + "event": "Network.requestWillBeSent", + "metadata": { + "telemetry_session_id": "cs_abc123", + "cdp_session_id": "...", + "target_id": "...", + "target_type": "page" + } + }, "data": { ... }, "truncated": false } @@ -104,12 +112,12 @@ Every event arrives as an `Envelope`: | Field | Type | Description | | --- | --- | --- | -| `telemetry_session_id` | string | Pipeline-assigned ID for the telemetry session (not a CDP concept). | | `seq` | uint64 | Monotonically increasing per-telemetry-session sequence number. | | `event.ts` | int64 | Wall-clock time the monitor emitted the event, as **Unix microseconds** (µs since epoch). | | `event.type` | string | See [Event taxonomy](#event-taxonomy). | | `event.category` | string | One of: `console`, `network`, `page`, `interaction`, `system`. | | `event.truncated` | bool | `true` if `data` was nulled to fit the 1 MB pipeline limit. | +| `event.source.metadata.telemetry_session_id` | string | Pipeline-assigned ID for the telemetry session, stamped by the telemetry layer. | ### Source object @@ -149,7 +157,7 @@ target_id <- one per tab/window; stable across navigations | `target_id` | `source.metadata`, most `data` objects | The browser tab. Use this to group all events from one tab session. | | `cdp_session_id` | `source.metadata` | The WebSocket sub-channel. Not stable across reconnects. | | `frame_id` | `page_navigation`, `network_request`, `network_response`, `network_loading_failed` | The frame the request or navigation belongs to. Top-level frame has no `parent_frame_id`. | -| `source_frame_id` | `page_layout_shift` | The frame where the layout shift occurred. Distinct from the nav context `frame_id`, which is always the top-level navigated frame. | +| `source_frame_id` | `page_layout_shift`, `page_lcp` | The frame where the layout shift or LCP element occurred. Distinct from the nav context `frame_id`, which is always the top-level navigated frame. | | `loader_id` | `page_navigation`, `network_request`, `network_response` | The document load that owns a request. Join `network_request.loader_id` to `page_navigation.loader_id` to correlate requests with the navigation that triggered them. | | `request_id` | `network_request`, `network_response`, `network_loading_failed` | A single request chain (including redirects). Links request to its eventual response or failure. | @@ -167,7 +175,9 @@ Most event `data` objects include a nav context block stamped at the last `page_ ### Per-event data fields -Fields below are the unique additions per event type. Unless otherwise noted, events also include the nav context fields described above. Network events are the exception: they carry their own `loader_id` and `frame_id` directly and do not include nav context. +The canonical schema for each event's `data` payload is defined in `openapi.yaml` under the corresponding `Browser*EventData` schema (e.g. `BrowserNetworkRequestEventData`). All 22 event shapes are collected into the `KnownBrowserTelemetryEvent` discriminated union, which maps each `type` string to its concrete schema; use that as the entry point when looking up a specific event's fields. The table below summarises the key fields; refer to the schema for the authoritative field list and types. + +Unless otherwise noted, events also include the nav context fields described above. Network events are the exception: they carry their own `loader_id` and `frame_id` directly and do not include nav context. #### Console events @@ -190,9 +200,10 @@ Fields below are the unique additions per event type. Unless otherwise noted, ev | --- | --- | | `page_tab_opened` | `target_id`, `target_type`, `url`, `opener_id`, `title`. Emitted before the first navigation; no nav context. | | `page_navigation` | `session_id`, `target_id`, `target_type`, `url`, `frame_id`, `parent_frame_id` (absent for top-level frames), `loader_id`. This event establishes the nav context stamped on all subsequent events for the session. | -| `page_dom_content_loaded` | Nav context + `cdp_timestamp` (CDP monotonic seconds; not a wall-clock timestamp -- use `event.ts` for ordering). | +| `page_dom_content_loaded` | Nav context + `cdp_timestamp` (CDP monotonic seconds; not a wall-clock timestamp, use `event.ts` for ordering). | | `page_load` | Nav context + `cdp_timestamp` (CDP monotonic seconds). | -| `page_layout_shift` | Nav context + `source_frame_id`, `time`, `duration`. Optional `layout_shift_details` object: `value`, `had_recent_input`. Optional `lcp_details` object: `render_time`, `load_time`, `size`, `element_id`, `url`, `node_id`. Chrome multiplexes LCP candidate data through the same `PerformanceTimeline.timelineEventAdded` notification, so both may appear on a single event. | +| `page_layout_shift` | Nav context + `source_frame_id`, `time`, `duration`. Optional `layout_shift_details`: `value`, `had_recent_input`. | +| `page_lcp` | Nav context + `source_frame_id`, `time`. Optional `lcp_details`: `render_time`, `load_time`, `size`, `element_id`, `url`, `node_id`. | #### Computed events @@ -218,4 +229,4 @@ Lifecycle events use `source.kind = "local_process"` and carry no nav context, e | `monitor_disconnected` | `reason: "chrome_restarted"`. | | `monitor_reconnected` | `reconnect_duration_ms`. | | `monitor_reconnect_failed` | `reason: "reconnect_exhausted"`. | -| `monitor_init_failed` | `step` (name of the init step that failed, e.g. `"Target.setAutoAttach"`). | \ No newline at end of file +| `monitor_init_failed` | `step` (name of the init step that failed, e.g. `"Target.setAutoAttach"`). | From 5967aa513cd78573d9961e2b2bd0f67ca13e57b4 Mon Sep 17 00:00:00 2001 From: Archan Datta Date: Fri, 15 May 2026 11:19:41 -0300 Subject: [PATCH 23/28] fix: avoid nil-map panic in screenshot publish and drop unused navData --- server/lib/cdpmonitor/screenshot.go | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/server/lib/cdpmonitor/screenshot.go b/server/lib/cdpmonitor/screenshot.go index 2fff5698..72b7b007 100644 --- a/server/lib/cdpmonitor/screenshot.go +++ b/server/lib/cdpmonitor/screenshot.go @@ -32,14 +32,13 @@ func (m *Monitor) tryScreenshot(ctx context.Context, sourceEvent, sessionID stri m.screenshotInFlight.Store(false) return } - var navData json.RawMessage var navMeta map[string]string if cs := m.computedFor(sessionID); cs != nil { - navData, navMeta = cs.navSnapshot() + _, navMeta = cs.navSnapshot() } m.asyncWg.Go(func() { defer m.screenshotInFlight.Store(false) - m.captureScreenshot(ctx, sourceEvent, navData, navMeta) + m.captureScreenshot(ctx, sourceEvent, navMeta) }) } @@ -47,9 +46,9 @@ const screenshotTimeout = 10 * time.Second // captureScreenshot takes a screenshot via ffmpeg x11grab (or the screenshotFn // seam in tests), optionally downscales it, and publishes a screenshot event. -// navData and navMeta are pre-snapped from the owning session's computedState; -// they may be nil if no state machine exists for the session. -func (m *Monitor) captureScreenshot(parentCtx context.Context, sourceEvent string, navData json.RawMessage, navMeta map[string]string) { +// navMeta is pre-snapped from the owning session's computedState; it may be nil +// if no state machine exists for the session. +func (m *Monitor) captureScreenshot(parentCtx context.Context, sourceEvent string, navMeta map[string]string) { ctx, cancel := context.WithTimeout(parentCtx, screenshotTimeout) defer cancel() var pngBytes []byte @@ -79,11 +78,15 @@ func (m *Monitor) captureScreenshot(parentCtx context.Context, sourceEvent strin Png: pngBytes, }) + src := oapi.BrowserEventSource{Kind: oapi.LocalProcess, Event: &sourceEvent} + if navMeta != nil { + src.Metadata = &navMeta + } m.publish(events.Event{ Ts: time.Now().UnixMicro(), Type: EventScreenshot, Category: events.System, - Source: oapi.BrowserEventSource{Kind: oapi.LocalProcess, Event: &sourceEvent, Metadata: &navMeta}, + Source: src, Data: data, }) } From c1475eae4fb322ffbd65eeae06ca2067e3c5a21d Mon Sep 17 00:00:00 2001 From: Archan Datta Date: Fri, 15 May 2026 11:29:56 -0300 Subject: [PATCH 24/28] review: scrub lifecycle fields from TelemetryState schema Drop id, status, created_at from TelemetryState so the /telemetry API reads as declarative config setting/unsetting rather than a start/stop session API. Also clean up remaining 'not active' / 'status: stopped' phrasing in path descriptions and handler docs. --- server/cmd/api/api/telemetry.go | 42 +- server/cmd/api/api/telemetry_test.go | 37 +- server/e2e/e2e_s2_storage_test.go | 3 +- server/lib/oapi/oapi.go | 587 +++++++++++++-------------- server/openapi.yaml | 25 +- 5 files changed, 320 insertions(+), 374 deletions(-) diff --git a/server/cmd/api/api/telemetry.go b/server/cmd/api/api/telemetry.go index b3698769..3599c3d3 100644 --- a/server/cmd/api/api/telemetry.go +++ b/server/cmd/api/api/telemetry.go @@ -12,20 +12,20 @@ import ( ) // GetTelemetry handles GET /telemetry. -// Returns the current telemetry state. Returns 404 if telemetry is not active. +// Returns the current telemetry configuration. Returns 404 if telemetry is not configured. func (s *ApiService) GetTelemetry(_ context.Context, _ oapi.GetTelemetryRequestObject) (oapi.GetTelemetryResponseObject, error) { s.monitorMu.Lock() defer s.monitorMu.Unlock() if !s.telemetrySession.Active() { - return oapi.GetTelemetry404JSONResponse{NotFoundErrorJSONResponse: oapi.NotFoundErrorJSONResponse{Message: "telemetry is not active"}}, nil + return oapi.GetTelemetry404JSONResponse{NotFoundErrorJSONResponse: oapi.NotFoundErrorJSONResponse{Message: "telemetry is not configured"}}, nil } return oapi.GetTelemetry200JSONResponse(s.buildTelemetryResponse()), nil } // PutTelemetry handles PUT /telemetry. -// Creates (201) or replaces (200) the telemetry session with the given config. -// Setting all four categories to enabled:false stops an active session (200, status stopped). +// Sets the telemetry configuration. Returns 201 if not previously configured, 200 if it was. +// Setting all four categories to enabled:false clears the configuration (200). func (s *ApiService) PutTelemetry(ctx context.Context, req oapi.PutTelemetryRequestObject) (oapi.PutTelemetryResponseObject, error) { s.monitorMu.Lock() defer s.monitorMu.Unlock() @@ -39,13 +39,13 @@ func (s *ApiService) PutTelemetry(ctx context.Context, req oapi.PutTelemetryRequ if allDisabled { if !wasActive { - // Already stopped; all-disabled is idempotent. - return oapi.PutTelemetry200JSONResponse(oapi.TelemetryState{Status: oapi.TelemetryStateStatusStopped, Config: disabledConfig()}), nil + // Already cleared; all-disabled is idempotent. + return oapi.PutTelemetry200JSONResponse(oapi.TelemetryState{Config: disabledConfig(), Seq: int64(s.telemetrySession.Seq())}), nil } - // All categories disabled: stop the running session. + // All categories disabled: clear the configuration. s.cdpMonitor.Stop() s.telemetrySession.Stop() - return oapi.PutTelemetry200JSONResponse(oapi.TelemetryState{Status: oapi.TelemetryStateStatusStopped, Config: disabledConfig()}), nil + return oapi.PutTelemetry200JSONResponse(oapi.TelemetryState{Config: disabledConfig(), Seq: int64(s.telemetrySession.Seq())}), nil } if wasActive { @@ -69,14 +69,14 @@ func (s *ApiService) PutTelemetry(ctx context.Context, req oapi.PutTelemetryRequ } // PatchTelemetry handles PATCH /telemetry. -// Updates the configuration of the active telemetry session. Returns 404 if not active. -// Setting all four categories to enabled:false stops the session (200, status stopped). +// Partially updates the telemetry configuration. Returns 404 if not configured. +// Setting all four categories to enabled:false clears the configuration (200). func (s *ApiService) PatchTelemetry(_ context.Context, req oapi.PatchTelemetryRequestObject) (oapi.PatchTelemetryResponseObject, error) { s.monitorMu.Lock() defer s.monitorMu.Unlock() if !s.telemetrySession.Active() { - return oapi.PatchTelemetry404JSONResponse{NotFoundErrorJSONResponse: oapi.NotFoundErrorJSONResponse{Message: "telemetry is not active"}}, nil + return oapi.PatchTelemetry404JSONResponse{NotFoundErrorJSONResponse: oapi.NotFoundErrorJSONResponse{Message: "telemetry is not configured"}}, nil } if req.Body != nil && req.Body.Browser != nil { @@ -85,10 +85,10 @@ func (s *ApiService) PatchTelemetry(_ context.Context, req oapi.PatchTelemetryRe current := s.telemetrySession.Config() cfg, allDisabled := mergeTelemetryConfig(current, req.Body.Browser) if allDisabled { - // All categories disabled: stop the session. + // All categories disabled: clear the configuration. s.cdpMonitor.Stop() s.telemetrySession.Stop() - return oapi.PatchTelemetry200JSONResponse(oapi.TelemetryState{Status: oapi.TelemetryStateStatusStopped, Config: disabledConfig()}), nil + return oapi.PatchTelemetry200JSONResponse(oapi.TelemetryState{Config: disabledConfig(), Seq: int64(s.telemetrySession.Seq())}), nil } s.telemetrySession.UpdateConfig(cfg) } @@ -96,21 +96,11 @@ func (s *ApiService) PatchTelemetry(_ context.Context, req oapi.PatchTelemetryRe return oapi.PatchTelemetry200JSONResponse(s.buildTelemetryResponse()), nil } -// buildTelemetryResponse constructs a TelemetryState response from the current session state. +// buildTelemetryResponse constructs a TelemetryState response from the current configuration. func (s *ApiService) buildTelemetryResponse() oapi.TelemetryState { - cfg := s.telemetrySession.Config() - - status := oapi.TelemetryStateStatusStopped - if s.cdpMonitor.IsRunning() { - status = oapi.TelemetryStateStatusRunning - } - return oapi.TelemetryState{ - Id: s.telemetrySession.ID(), - Status: status, - Config: telemetryConfigToOAPI(cfg), - Seq: int64(s.telemetrySession.Seq()), - CreatedAt: s.telemetrySession.CreatedAt(), + Config: telemetryConfigToOAPI(s.telemetrySession.Config()), + Seq: int64(s.telemetrySession.Seq()), } } diff --git a/server/cmd/api/api/telemetry_test.go b/server/cmd/api/api/telemetry_test.go index 8a64f408..b954fa64 100644 --- a/server/cmd/api/api/telemetry_test.go +++ b/server/cmd/api/api/telemetry_test.go @@ -75,9 +75,7 @@ func TestPutTelemetry(t *testing.T) { require.NoError(t, err) r201, ok := resp.(oapi.PutTelemetry201JSONResponse) require.True(t, ok, "expected 201, got %T", resp) - assert.NotEmpty(t, r201.Id) - assert.NotZero(t, r201.CreatedAt) - assert.NotEmpty(t, r201.Status) + require.NotNil(t, r201.Config.Browser) }) t.Run("creates session with config (201)", func(t *testing.T) { @@ -113,7 +111,7 @@ func TestPutTelemetry(t *testing.T) { assert.True(t, ok, "expected 200 on replace, got %T", resp) }) - t.Run("all-false stops active session (200, status stopped)", func(t *testing.T) { + t.Run("all-false clears active configuration (200, all-disabled config)", func(t *testing.T) { svc := newTestService(t, newMockRecordManager()) _, err := svc.PutTelemetry(ctx, oapi.PutTelemetryRequestObject{}) require.NoError(t, err) @@ -132,7 +130,12 @@ func TestPutTelemetry(t *testing.T) { require.NoError(t, err) r200, ok := resp.(oapi.PutTelemetry200JSONResponse) require.True(t, ok, "expected 200, got %T", resp) - assert.Equal(t, oapi.TelemetryStateStatusStopped, r200.Status) + require.NotNil(t, r200.Config.Browser) + require.NotNil(t, r200.Config.Browser.Console) + assert.False(t, *r200.Config.Browser.Console.Enabled) + assert.False(t, *r200.Config.Browser.Network.Enabled) + assert.False(t, *r200.Config.Browser.Page.Enabled) + assert.False(t, *r200.Config.Browser.Interaction.Enabled) }) } @@ -156,8 +159,7 @@ func TestGetTelemetry(t *testing.T) { require.NoError(t, err) r200, ok := resp.(oapi.GetTelemetry200JSONResponse) require.True(t, ok) - assert.Equal(t, started.Id, r200.Id) - assert.Equal(t, started.CreatedAt, r200.CreatedAt) + assert.Equal(t, started.Config, r200.Config) }) } @@ -207,10 +209,10 @@ func TestPatchTelemetry(t *testing.T) { require.NoError(t, err) r200, ok := resp.(oapi.PatchTelemetry200JSONResponse) require.True(t, ok) - assert.Equal(t, started.Id, r200.Id) + assert.Equal(t, started.Config, r200.Config) }) - t.Run("all-false stops session", func(t *testing.T) { + t.Run("all-false clears configuration", func(t *testing.T) { svc := newTestService(t, newMockRecordManager()) _, err := svc.PutTelemetry(ctx, oapi.PutTelemetryRequestObject{}) require.NoError(t, err) @@ -229,14 +231,18 @@ func TestPatchTelemetry(t *testing.T) { require.NoError(t, err) r200, ok := resp.(oapi.PatchTelemetry200JSONResponse) require.True(t, ok, "expected 200, got %T", resp) - assert.Equal(t, oapi.TelemetryStateStatusStopped, r200.Status) + require.NotNil(t, r200.Config.Browser) + require.NotNil(t, r200.Config.Browser.Console) + assert.False(t, *r200.Config.Browser.Console.Enabled) + assert.False(t, *r200.Config.Browser.Network.Enabled) + assert.False(t, *r200.Config.Browser.Page.Enabled) + assert.False(t, *r200.Config.Browser.Interaction.Enabled) }) - t.Run("put succeeds after patch-stop", func(t *testing.T) { + t.Run("put returns 201 after patch clears configuration", func(t *testing.T) { svc := newTestService(t, newMockRecordManager()) - startResp, err := svc.PutTelemetry(ctx, oapi.PutTelemetryRequestObject{}) + _, err := svc.PutTelemetry(ctx, oapi.PutTelemetryRequestObject{}) require.NoError(t, err) - firstID := startResp.(oapi.PutTelemetry201JSONResponse).Id f := false _, err = svc.PatchTelemetry(ctx, oapi.PatchTelemetryRequestObject{ @@ -253,9 +259,8 @@ func TestPatchTelemetry(t *testing.T) { resp, err := svc.PutTelemetry(ctx, oapi.PutTelemetryRequestObject{}) require.NoError(t, err) - r201, ok := resp.(oapi.PutTelemetry201JSONResponse) - require.True(t, ok) - assert.NotEqual(t, firstID, r201.Id) + _, ok := resp.(oapi.PutTelemetry201JSONResponse) + assert.True(t, ok, "expected 201 after clear, got %T", resp) }) } diff --git a/server/e2e/e2e_s2_storage_test.go b/server/e2e/e2e_s2_storage_test.go index 2ccf6e40..9bf9e839 100644 --- a/server/e2e/e2e_s2_storage_test.go +++ b/server/e2e/e2e_s2_storage_test.go @@ -63,8 +63,7 @@ func TestS2StorageWriter(t *testing.T) { require.NoError(t, err) require.Equal(t, http.StatusCreated, startResp.StatusCode(), "put telemetry: %s", string(startResp.Body)) require.NotNil(t, startResp.JSON201) - sessionID := startResp.JSON201.Id - t.Logf("telemetry session started: %s", sessionID) + t.Log("telemetry configured") // Let the session run briefly so at least one event is published. time.Sleep(500 * time.Millisecond) diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index 809ede10..d1dd6eb7 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -994,16 +994,16 @@ func (e ProcessKillRequestSignal) Valid() bool { // Defines values for ProcessStatusState. const ( - ProcessStatusStateExited ProcessStatusState = "exited" - ProcessStatusStateRunning ProcessStatusState = "running" + Exited ProcessStatusState = "exited" + Running ProcessStatusState = "running" ) // Valid indicates whether the value is a known member of the ProcessStatusState enum. func (e ProcessStatusState) Valid() bool { switch e { - case ProcessStatusStateExited: + case Exited: return true - case ProcessStatusStateRunning: + case Running: return true default: return false @@ -1097,24 +1097,6 @@ func (e TelemetryEventCategory) Valid() bool { } } -// Defines values for TelemetryStateStatus. -const ( - TelemetryStateStatusRunning TelemetryStateStatus = "running" - TelemetryStateStatusStopped TelemetryStateStatus = "stopped" -) - -// Valid indicates whether the value is a known member of the TelemetryStateStatus enum. -func (e TelemetryStateStatus) Valid() bool { - switch e { - case TelemetryStateStatusRunning: - return true - case TelemetryStateStatusStopped: - return true - default: - return false - } -} - // Defines values for DownloadDirZstdParamsCompressionLevel. const ( Best DownloadDirZstdParamsCompressionLevel = "best" @@ -2852,27 +2834,15 @@ type TelemetryEvent struct { // TelemetryEventCategory Event category. type TelemetryEventCategory string -// TelemetryState Current telemetry configuration and status. +// TelemetryState Current telemetry configuration. type TelemetryState struct { // Config Telemetry configuration for a browser. Per-category capture settings. Omit a category or set enabled: true to capture it. Set enabled: false to exclude it. Omit the browser key entirely to capture all categories. Set all four categories to enabled: false to clear the telemetry configuration. Config BrowserTelemetryConfig `json:"config"` - // CreatedAt When the current telemetry configuration was applied. - CreatedAt time.Time `json:"created_at"` - - // Id Unique identifier for the current telemetry configuration. - Id string `json:"id"` - // Seq Process-monotonic sequence number of the last published event. Does not reset across configuration changes. Seq int64 `json:"seq"` - - // Status Current telemetry status. - Status TelemetryStateStatus `json:"status"` } -// TelemetryStateStatus Current telemetry status. -type TelemetryStateStatus string - // TypeTextRequest defines model for TypeTextRequest. type TypeTextRequest struct { // Delay Delay in milliseconds between keystrokes. Ignored when smooth is true. @@ -17489,280 +17459,279 @@ func (sh *strictHandler) StreamTelemetryEvents(w http.ResponseWriter, r *http.Re // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/openapi.yaml b/server/openapi.yaml index 6b51ceb2..d2685e2d 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -1215,8 +1215,8 @@ paths: description: > Sets the telemetry configuration. Returns 201 if telemetry was not previously configured; returns 200 if it was. Setting all four categories - to enabled: false clears the configuration (returns 200 with - status: stopped); this is idempotent when telemetry is not configured. + to enabled: false clears the configuration; this is idempotent when + telemetry is not configured. operationId: putTelemetry requestBody: required: false @@ -1247,8 +1247,7 @@ paths: Partially updates the telemetry configuration. Only categories explicitly set in the request body are changed; omitted categories retain their current settings. Returns 404 if telemetry is not configured. Setting - all four categories to enabled: false clears the configuration (returns - 200 with status: stopped). + all four categories to enabled: false clears the configuration. operationId: patchTelemetry requestBody: required: true @@ -2424,23 +2423,11 @@ components: monitor_init_failed: "#/components/schemas/BrowserMonitorInitFailedEvent" TelemetryState: type: object - description: Current telemetry configuration and status. + description: Current telemetry configuration. required: - - id - - status - config - seq - - created_at properties: - id: - type: string - description: Unique identifier for the current telemetry configuration. - status: - type: string - description: Current telemetry status. - enum: - - running - - stopped config: $ref: "#/components/schemas/BrowserTelemetryConfig" seq: @@ -2450,10 +2437,6 @@ components: Process-monotonic sequence number of the last published event. Does not reset across configuration changes. minimum: 0 - created_at: - type: string - format: date-time - description: When the current telemetry configuration was applied. additionalProperties: false StartRecordingRequest: type: object From 05d6f59ceacb27f17510ec75b462d353212cae1f Mon Sep 17 00:00:00 2001 From: Archan Datta Date: Fri, 15 May 2026 12:20:51 -0300 Subject: [PATCH 25/28] review: add optional applied_at to TelemetryState for observability --- server/cmd/api/api/telemetry.go | 6 +- server/cmd/api/api/telemetry_test.go | 3 + server/e2e/e2e_s2_storage_test.go | 2 +- server/lib/oapi/oapi.go | 518 ++++++++++++++------------- server/lib/telemetry/telemetry.go | 12 +- server/openapi.yaml | 6 + 6 files changed, 283 insertions(+), 264 deletions(-) diff --git a/server/cmd/api/api/telemetry.go b/server/cmd/api/api/telemetry.go index 3599c3d3..a4b4cfe4 100644 --- a/server/cmd/api/api/telemetry.go +++ b/server/cmd/api/api/telemetry.go @@ -98,10 +98,14 @@ func (s *ApiService) PatchTelemetry(_ context.Context, req oapi.PatchTelemetryRe // buildTelemetryResponse constructs a TelemetryState response from the current configuration. func (s *ApiService) buildTelemetryResponse() oapi.TelemetryState { - return oapi.TelemetryState{ + resp := oapi.TelemetryState{ Config: telemetryConfigToOAPI(s.telemetrySession.Config()), Seq: int64(s.telemetrySession.Seq()), } + if appliedAt := s.telemetrySession.AppliedAt(); !appliedAt.IsZero() { + resp.AppliedAt = &appliedAt + } + return resp } // telemetryConfigFromOAPI converts an *oapi.BrowserTelemetryConfig to a telemetry.TelemetryConfig. diff --git a/server/cmd/api/api/telemetry_test.go b/server/cmd/api/api/telemetry_test.go index b954fa64..91df62e9 100644 --- a/server/cmd/api/api/telemetry_test.go +++ b/server/cmd/api/api/telemetry_test.go @@ -76,6 +76,8 @@ func TestPutTelemetry(t *testing.T) { r201, ok := resp.(oapi.PutTelemetry201JSONResponse) require.True(t, ok, "expected 201, got %T", resp) require.NotNil(t, r201.Config.Browser) + require.NotNil(t, r201.AppliedAt) + assert.False(t, r201.AppliedAt.IsZero()) }) t.Run("creates session with config (201)", func(t *testing.T) { @@ -136,6 +138,7 @@ func TestPutTelemetry(t *testing.T) { assert.False(t, *r200.Config.Browser.Network.Enabled) assert.False(t, *r200.Config.Browser.Page.Enabled) assert.False(t, *r200.Config.Browser.Interaction.Enabled) + assert.Nil(t, r200.AppliedAt, "applied_at must be omitted when telemetry is unconfigured") }) } diff --git a/server/e2e/e2e_s2_storage_test.go b/server/e2e/e2e_s2_storage_test.go index 9bf9e839..d51bdcf9 100644 --- a/server/e2e/e2e_s2_storage_test.go +++ b/server/e2e/e2e_s2_storage_test.go @@ -80,7 +80,7 @@ func TestS2StorageWriter(t *testing.T) { }) require.NoError(t, err) require.Equal(t, http.StatusOK, stopResp.StatusCode(), "patch telemetry: %s", string(stopResp.Body)) - t.Log("telemetry session stopped") + t.Log("telemetry configuration cleared") // Give the storage writer time to flush to S2 (batcher linger + network). time.Sleep(2 * time.Second) diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index d1dd6eb7..ea62a3d4 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -2836,6 +2836,9 @@ type TelemetryEventCategory string // TelemetryState Current telemetry configuration. type TelemetryState struct { + // AppliedAt Wall-clock time at which the current configuration was applied. Omitted when telemetry is not configured. + AppliedAt *time.Time `json:"applied_at,omitempty"` + // Config Telemetry configuration for a browser. Per-category capture settings. Omit a category or set enabled: true to capture it. Set enabled: false to exclude it. Omit the browser key entirely to capture all categories. Set all four categories to enabled: false to clear the telemetry configuration. Config BrowserTelemetryConfig `json:"config"` @@ -17459,7 +17462,7 @@ func (sh *strictHandler) StreamTelemetryEvents(w http.ResponseWriter, r *http.Re // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+y9+3Mbt5I/+q+geLfK0ndJSk7i7D1O7Q+OJCfa+KGS5JOzOcqVwJkmidUQmAAYSbTL", + "H4sIAAAAAAAC/+y9+3Mbt5I/+q+geLfK0ndJSk6c7D1O7Q+OJCfa+KGS5JOzOcqVwJkmidUQmAAYSbTL", "+7ffQjcwDw6GL0l+nK+rTu064uDZje5Go/vTH3qJmuVKgrSm9/xDT4PJlTSA//EzT0/hrwKMPdJaafen", "REkL0rp/8jzPRMKtUHLvf4yS7m8mmcKMu3/9m4Zx73nv/9mr+t+jX80e9fbx48d+LwWTaJG7TnrP3YDM", "j9j72O8dKDnORPKpRg/DuaGPpQUtefaJhg7DsTPQN6CZ/7Dfe6PsS1XI9BPN442yDMfrud/858QKNpke", @@ -17476,262 +17479,263 @@ var swaggerSpec = []string{ "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", + "1v9/AAAA//8LTo0nS2gBAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/lib/telemetry/telemetry.go b/server/lib/telemetry/telemetry.go index 6da0dc82..538a2f63 100644 --- a/server/lib/telemetry/telemetry.go +++ b/server/lib/telemetry/telemetry.go @@ -27,7 +27,7 @@ type TelemetrySession struct { id string sessionStartSeq uint64 categories map[oapi.TelemetryEventCategory]struct{} - createdAt time.Time + appliedAt time.Time } func NewTelemetrySession(es *events.EventStream) *TelemetrySession { @@ -46,7 +46,7 @@ func (s *TelemetrySession) Start(telemetrySessionID string, cfg TelemetryConfig) defer s.mu.Unlock() s.id = telemetrySessionID s.sessionStartSeq = s.es.Seq() - s.createdAt = time.Now() + s.appliedAt = time.Now() // Build the category filter. CategorySystem is always included so // kernel_api events (e.g. monitor_disconnected) are never dropped. @@ -124,11 +124,12 @@ func (s *TelemetrySession) Config() TelemetryConfig { return TelemetryConfig{Categories: cats} } -// CreatedAt returns when the current session was started. -func (s *TelemetrySession) CreatedAt() time.Time { +// AppliedAt returns when the current configuration was applied, or the zero +// time if telemetry is not configured. +func (s *TelemetrySession) AppliedAt() time.Time { s.mu.Lock() defer s.mu.Unlock() - return s.createdAt + return s.appliedAt } // UpdateConfig applies a new TelemetryConfig to the running session. @@ -160,4 +161,5 @@ func (s *TelemetrySession) Stop() { s.mu.Lock() defer s.mu.Unlock() s.id = "" + s.appliedAt = time.Time{} } diff --git a/server/openapi.yaml b/server/openapi.yaml index d2685e2d..8b9827cb 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -2437,6 +2437,12 @@ components: Process-monotonic sequence number of the last published event. Does not reset across configuration changes. minimum: 0 + applied_at: + type: string + format: date-time + description: >- + Wall-clock time at which the current configuration was applied. + Omitted when telemetry is not configured. additionalProperties: false StartRecordingRequest: type: object From d6d80bbfe1eef9a55ea4b0ec60bf770d773f88e3 Mon Sep 17 00:00:00 2001 From: Archan Datta Date: Fri, 15 May 2026 12:51:53 -0300 Subject: [PATCH 26/28] review: snapshot network nav_seq, hoist BrowserTargetType, document partial-context events --- server/lib/cdpmonitor/README.md | 11 +- server/lib/cdpmonitor/handlers.go | 53 +- server/lib/cdpmonitor/types.go | 1 + server/lib/cdpmonitor/util.go | 2 +- server/lib/oapi/oapi.go | 1037 +++++++++-------------------- server/openapi.yaml | 29 +- 6 files changed, 370 insertions(+), 763 deletions(-) diff --git a/server/lib/cdpmonitor/README.md b/server/lib/cdpmonitor/README.md index e3e37800..f306b7a0 100644 --- a/server/lib/cdpmonitor/README.md +++ b/server/lib/cdpmonitor/README.md @@ -171,7 +171,16 @@ Most event `data` objects include a nav context block stamped at the last `page_ | `frame_id` | Frame ID of the navigated top-level frame. | | `loader_id` | Loader ID of the current document. | | `url` | URL of the current page at the time of the last navigation. | -| `nav_seq` | Monotonically increasing counter, incremented on each `page_navigation`. Use it to detect that the page has navigated between two events in the same session. | +| `nav_seq` | Monotonically increasing counter, incremented on each `page_navigation`. Use it to detect that the page has navigated between two events in the same session. For `network_request`/`network_response`/`network_loading_failed`, the `nav_seq` is captured at request-send time and carried forward to the response so a request/response pair always shares an epoch. | + +### Events that do not compose `BrowserEventContext` + +Of the 22 event types, two intentionally omit the standard nav context block: + +- `page_tab_opened`: fires before a CDP session is attached to the new target, so `session_id`, `frame_id`, `loader_id`, and `nav_seq` are absent. Only `target_id`, `target_type`, and the tab's initial `url`/`title`/`opener_id` are populated. +- `page_navigation`: resets the navigation epoch, so it carries the new context fields inline but omits `nav_seq` (the value reported by subsequent events for this epoch is `nav_seq + 1`). + +Consumers that destructure `BrowserEventContext` generically should treat these two events as special cases. ### Per-event data fields diff --git a/server/lib/cdpmonitor/handlers.go b/server/lib/cdpmonitor/handlers.go index 86a517da..ddf19b2c 100644 --- a/server/lib/cdpmonitor/handlers.go +++ b/server/lib/cdpmonitor/handlers.go @@ -153,7 +153,7 @@ func (m *Monitor) handleConsole(p cdpRuntimeConsoleAPICalledParams, sessionID st data, _ := json.Marshal(oapi.BrowserConsoleLogEventData{ SessionId: sid, TargetId: tid, - TargetType: oapi.BrowserConsoleLogEventDataTargetType(ttype), + TargetType: oapi.BrowserTargetType(ttype), FrameId: ptrOf(fid), LoaderId: ptrOf(lid), Url: ptrOf(url), @@ -178,7 +178,7 @@ func (m *Monitor) handleExceptionThrown(ctx context.Context, p cdpRuntimeExcepti data, _ := json.Marshal(oapi.BrowserConsoleErrorEventData{ SessionId: sid, TargetId: tid, - TargetType: oapi.BrowserConsoleErrorEventDataTargetType(ttype), + TargetType: oapi.BrowserTargetType(ttype), FrameId: ptrOf(fid), LoaderId: ptrOf(lid), Url: ptrOf(url), @@ -250,7 +250,7 @@ func (m *Monitor) handleTimelineEvent(p cdpPerformanceTimelineEventAddedParams, payload := oapi.BrowserPageLayoutShiftEventData{ SessionId: sid, TargetId: tid, - TargetType: oapi.BrowserPageLayoutShiftEventDataTargetType(ttype), + TargetType: oapi.BrowserTargetType(ttype), FrameId: ptrOf(fid), LoaderId: ptrOf(lid), Url: ptrOf(url), @@ -281,7 +281,7 @@ func (m *Monitor) handleTimelineEvent(p cdpPerformanceTimelineEventAddedParams, lcpPayload := oapi.BrowserPageLcpEventData{ SessionId: sid, TargetId: tid, - TargetType: oapi.BrowserPageLcpEventDataTargetType(ttype), + TargetType: oapi.BrowserTargetType(ttype), FrameId: ptrOf(fid), LoaderId: ptrOf(lid), Url: ptrOf(url), @@ -327,6 +327,15 @@ func (m *Monitor) handleNetworkRequest(p cdpNetworkRequestWillBeSentParams, sess initiatorType = raw.Type } + m.sessionsMu.RLock() + info := m.sessions[sessionID] + m.sessionsMu.RUnlock() + cs := m.computedFor(sessionID) + var navSeq int64 + if cs != nil { + navSeq = int64(cs.currentNavSeq()) + } + // Redirects reuse the same requestId and fire additional requestWillBeSent // events, but only a single loadingFinished fires per chain. Only increment // netPending for genuinely new requests to avoid permanently inflating the @@ -336,6 +345,8 @@ func (m *Monitor) handleNetworkRequest(p cdpNetworkRequestWillBeSentParams, sess addedAt := existing.addedAt if !isRedirect { addedAt = time.Now() + } else { + navSeq = existing.navSeq } m.pendingRequests[p.RequestID] = networkReqState{ sessionID: sessionID, @@ -346,23 +357,16 @@ func (m *Monitor) handleNetworkRequest(p cdpNetworkRequestWillBeSentParams, sess resourceType: p.Type, loaderID: p.LoaderID, frameID: p.FrameID, + navSeq: navSeq, addedAt: addedAt, } m.pendReqMu.Unlock() - m.sessionsMu.RLock() - info := m.sessions[sessionID] - m.sessionsMu.RUnlock() - cs := m.computedFor(sessionID) - var navSeq int64 - if cs != nil { - navSeq = int64(cs.currentNavSeq()) - } var hdrs oapi.BrowserHttpHeaders _ = json.Unmarshal(p.Request.Headers, &hdrs) payload := oapi.BrowserNetworkRequestEventData{ SessionId: sessionID, TargetId: info.targetID, - TargetType: oapi.BrowserNetworkRequestEventDataTargetType(info.targetType), + TargetType: oapi.BrowserTargetType(info.targetType), FrameId: ptrOf(p.FrameID), LoaderId: ptrOf(p.LoaderID), Url: ptrOf(p.Request.URL), @@ -415,10 +419,8 @@ func (m *Monitor) handleLoadingFinished(ctx context.Context, p cdpNetworkLoading m.sessionsMu.RLock() info := m.sessions[sessionID] m.sessionsMu.RUnlock() - var navSeq int if cs := m.computedFor(state.sessionID); cs != nil { cs.onLoadingFinished() - navSeq = cs.currentNavSeq() } // Fetch response body async to avoid blocking readLoop; binary types are skipped. m.asyncWg.Go(func() { @@ -428,11 +430,11 @@ func (m *Monitor) handleLoadingFinished(ctx context.Context, p cdpNetworkLoading resPayload := oapi.BrowserNetworkResponseEventData{ SessionId: sessionID, TargetId: info.targetID, - TargetType: oapi.BrowserNetworkResponseEventDataTargetType(info.targetType), + TargetType: oapi.BrowserTargetType(info.targetType), FrameId: ptrOf(state.frameID), LoaderId: ptrOf(state.loaderID), Url: ptrOf(state.url), - NavSeq: int64(navSeq), + NavSeq: state.navSeq, RequestId: p.RequestID, Method: state.method, Status: state.status, @@ -495,14 +497,19 @@ func (m *Monitor) handleLoadingFailed(p cdpNetworkLoadingFailedParams, sessionID m.sessionsMu.RLock() info := m.sessions[sessionID] m.sessionsMu.RUnlock() + // Prefer the navSeq captured at requestWillBeSent time so request/failure + // pairs share an epoch. For untracked requests (in flight at CDP attach), + // fall back to the current navSeq. var nseq int64 - if cs := m.computedFor(sessionID); cs != nil { + if ok { + nseq = state.navSeq + } else if cs := m.computedFor(sessionID); cs != nil { nseq = int64(cs.currentNavSeq()) } failPayload := oapi.BrowserNetworkLoadingFailedEventData{ SessionId: sessionID, TargetId: info.targetID, - TargetType: oapi.BrowserNetworkLoadingFailedEventDataTargetType(info.targetType), + TargetType: oapi.BrowserTargetType(info.targetType), NavSeq: nseq, RequestId: p.RequestID, ErrorText: p.ErrorText, @@ -536,7 +543,7 @@ func (m *Monitor) handleFrameNavigated(p cdpPageFrameNavigatedParams, sessionID data, _ := json.Marshal(oapi.BrowserPageNavigationEventData{ SessionId: sessionID, TargetId: info.targetID, - TargetType: oapi.BrowserPageNavigationEventDataTargetType(info.targetType), + TargetType: oapi.BrowserTargetType(info.targetType), Url: p.Frame.URL, FrameId: p.Frame.ID, ParentFrameId: ptrOf(p.Frame.ParentID), @@ -584,7 +591,7 @@ func (m *Monitor) handleDOMContentLoaded(p cdpPageDomContentEventFiredParams, se data, _ := json.Marshal(oapi.BrowserPageDomContentLoadedEventData{ SessionId: sid, TargetId: tid, - TargetType: oapi.BrowserPageDomContentLoadedEventDataTargetType(ttype), + TargetType: oapi.BrowserTargetType(ttype), FrameId: ptrOf(fid), LoaderId: ptrOf(lid), Url: ptrOf(url), @@ -603,7 +610,7 @@ func (m *Monitor) handleLoadEventFired(ctx context.Context, p cdpPageLoadEventFi data, _ := json.Marshal(oapi.BrowserPageLoadEventData{ SessionId: sid, TargetId: tid, - TargetType: oapi.BrowserPageLoadEventDataTargetType(ttype), + TargetType: oapi.BrowserTargetType(ttype), FrameId: ptrOf(fid), LoaderId: ptrOf(lid), Url: ptrOf(url), @@ -637,7 +644,7 @@ func (m *Monitor) handleAttachedToTarget(ctx context.Context, p cdpTargetAttache if p.TargetInfo.Type == targetTypePage { data, _ := json.Marshal(oapi.BrowserPageTabOpenedEventData{ TargetId: p.TargetInfo.TargetID, - TargetType: oapi.BrowserPageTabOpenedEventDataTargetType(p.TargetInfo.Type), + TargetType: oapi.BrowserTargetType(p.TargetInfo.Type), Url: p.TargetInfo.URL, OpenerId: ptrOf(p.TargetInfo.OpenerID), Title: ptrOf(p.TargetInfo.Title), diff --git a/server/lib/cdpmonitor/types.go b/server/lib/cdpmonitor/types.go index c804cb79..dc4da3f9 100644 --- a/server/lib/cdpmonitor/types.go +++ b/server/lib/cdpmonitor/types.go @@ -116,6 +116,7 @@ type networkReqState struct { resourceType string loaderID string frameID string + navSeq int64 status int statusText string resHeaders json.RawMessage diff --git a/server/lib/cdpmonitor/util.go b/server/lib/cdpmonitor/util.go index d862d9c4..df03e44a 100644 --- a/server/lib/cdpmonitor/util.go +++ b/server/lib/cdpmonitor/util.go @@ -17,7 +17,7 @@ func marshalNavEventContext(ctx navContext, seq int) json.RawMessage { data, _ := json.Marshal(oapi.BrowserEventContext{ SessionId: ctx.sessionID, TargetId: ctx.targetID, - TargetType: oapi.BrowserEventContextTargetType(ctx.targetType), + TargetType: oapi.BrowserTargetType(ctx.targetType), FrameId: ptrOf(ctx.frameID), LoaderId: ptrOf(ctx.loaderID), Url: ptrOf(ctx.url), diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index ea62a3d4..5b04809a 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -41,33 +41,6 @@ func (e BrowserConsoleErrorEventType) Valid() bool { } } -// Defines values for BrowserConsoleErrorEventDataTargetType. -const ( - BrowserConsoleErrorEventDataTargetTypeBackgroundPage BrowserConsoleErrorEventDataTargetType = "background_page" - BrowserConsoleErrorEventDataTargetTypeOther BrowserConsoleErrorEventDataTargetType = "other" - BrowserConsoleErrorEventDataTargetTypePage BrowserConsoleErrorEventDataTargetType = "page" - BrowserConsoleErrorEventDataTargetTypeServiceWorker BrowserConsoleErrorEventDataTargetType = "service_worker" - BrowserConsoleErrorEventDataTargetTypeSharedWorker BrowserConsoleErrorEventDataTargetType = "shared_worker" -) - -// Valid indicates whether the value is a known member of the BrowserConsoleErrorEventDataTargetType enum. -func (e BrowserConsoleErrorEventDataTargetType) Valid() bool { - switch e { - case BrowserConsoleErrorEventDataTargetTypeBackgroundPage: - return true - case BrowserConsoleErrorEventDataTargetTypeOther: - return true - case BrowserConsoleErrorEventDataTargetTypePage: - return true - case BrowserConsoleErrorEventDataTargetTypeServiceWorker: - return true - case BrowserConsoleErrorEventDataTargetTypeSharedWorker: - return true - default: - return false - } -} - // Defines values for BrowserConsoleLogEventType. const ( ConsoleLog BrowserConsoleLogEventType = "console_log" @@ -83,60 +56,6 @@ func (e BrowserConsoleLogEventType) Valid() bool { } } -// Defines values for BrowserConsoleLogEventDataTargetType. -const ( - BrowserConsoleLogEventDataTargetTypeBackgroundPage BrowserConsoleLogEventDataTargetType = "background_page" - BrowserConsoleLogEventDataTargetTypeOther BrowserConsoleLogEventDataTargetType = "other" - BrowserConsoleLogEventDataTargetTypePage BrowserConsoleLogEventDataTargetType = "page" - BrowserConsoleLogEventDataTargetTypeServiceWorker BrowserConsoleLogEventDataTargetType = "service_worker" - BrowserConsoleLogEventDataTargetTypeSharedWorker BrowserConsoleLogEventDataTargetType = "shared_worker" -) - -// Valid indicates whether the value is a known member of the BrowserConsoleLogEventDataTargetType enum. -func (e BrowserConsoleLogEventDataTargetType) Valid() bool { - switch e { - case BrowserConsoleLogEventDataTargetTypeBackgroundPage: - return true - case BrowserConsoleLogEventDataTargetTypeOther: - return true - case BrowserConsoleLogEventDataTargetTypePage: - return true - case BrowserConsoleLogEventDataTargetTypeServiceWorker: - return true - case BrowserConsoleLogEventDataTargetTypeSharedWorker: - return true - default: - return false - } -} - -// Defines values for BrowserEventContextTargetType. -const ( - BrowserEventContextTargetTypeBackgroundPage BrowserEventContextTargetType = "background_page" - BrowserEventContextTargetTypeOther BrowserEventContextTargetType = "other" - BrowserEventContextTargetTypePage BrowserEventContextTargetType = "page" - BrowserEventContextTargetTypeServiceWorker BrowserEventContextTargetType = "service_worker" - BrowserEventContextTargetTypeSharedWorker BrowserEventContextTargetType = "shared_worker" -) - -// Valid indicates whether the value is a known member of the BrowserEventContextTargetType enum. -func (e BrowserEventContextTargetType) Valid() bool { - switch e { - case BrowserEventContextTargetTypeBackgroundPage: - return true - case BrowserEventContextTargetTypeOther: - return true - case BrowserEventContextTargetTypePage: - return true - case BrowserEventContextTargetTypeServiceWorker: - return true - case BrowserEventContextTargetTypeSharedWorker: - return true - default: - return false - } -} - // Defines values for BrowserEventSourceKind. const ( Cdp BrowserEventSourceKind = "cdp" @@ -176,33 +95,6 @@ func (e BrowserInteractionClickEventType) Valid() bool { } } -// Defines values for BrowserInteractionClickEventDataTargetType. -const ( - BrowserInteractionClickEventDataTargetTypeBackgroundPage BrowserInteractionClickEventDataTargetType = "background_page" - BrowserInteractionClickEventDataTargetTypeOther BrowserInteractionClickEventDataTargetType = "other" - BrowserInteractionClickEventDataTargetTypePage BrowserInteractionClickEventDataTargetType = "page" - BrowserInteractionClickEventDataTargetTypeServiceWorker BrowserInteractionClickEventDataTargetType = "service_worker" - BrowserInteractionClickEventDataTargetTypeSharedWorker BrowserInteractionClickEventDataTargetType = "shared_worker" -) - -// Valid indicates whether the value is a known member of the BrowserInteractionClickEventDataTargetType enum. -func (e BrowserInteractionClickEventDataTargetType) Valid() bool { - switch e { - case BrowserInteractionClickEventDataTargetTypeBackgroundPage: - return true - case BrowserInteractionClickEventDataTargetTypeOther: - return true - case BrowserInteractionClickEventDataTargetTypePage: - return true - case BrowserInteractionClickEventDataTargetTypeServiceWorker: - return true - case BrowserInteractionClickEventDataTargetTypeSharedWorker: - return true - default: - return false - } -} - // Defines values for BrowserInteractionKeyEventType. const ( InteractionKey BrowserInteractionKeyEventType = "interaction_key" @@ -218,33 +110,6 @@ func (e BrowserInteractionKeyEventType) Valid() bool { } } -// Defines values for BrowserInteractionKeyEventDataTargetType. -const ( - BrowserInteractionKeyEventDataTargetTypeBackgroundPage BrowserInteractionKeyEventDataTargetType = "background_page" - BrowserInteractionKeyEventDataTargetTypeOther BrowserInteractionKeyEventDataTargetType = "other" - BrowserInteractionKeyEventDataTargetTypePage BrowserInteractionKeyEventDataTargetType = "page" - BrowserInteractionKeyEventDataTargetTypeServiceWorker BrowserInteractionKeyEventDataTargetType = "service_worker" - BrowserInteractionKeyEventDataTargetTypeSharedWorker BrowserInteractionKeyEventDataTargetType = "shared_worker" -) - -// Valid indicates whether the value is a known member of the BrowserInteractionKeyEventDataTargetType enum. -func (e BrowserInteractionKeyEventDataTargetType) Valid() bool { - switch e { - case BrowserInteractionKeyEventDataTargetTypeBackgroundPage: - return true - case BrowserInteractionKeyEventDataTargetTypeOther: - return true - case BrowserInteractionKeyEventDataTargetTypePage: - return true - case BrowserInteractionKeyEventDataTargetTypeServiceWorker: - return true - case BrowserInteractionKeyEventDataTargetTypeSharedWorker: - return true - default: - return false - } -} - // Defines values for BrowserInteractionScrollSettledEventType. const ( InteractionScrollSettled BrowserInteractionScrollSettledEventType = "interaction_scroll_settled" @@ -260,33 +125,6 @@ func (e BrowserInteractionScrollSettledEventType) Valid() bool { } } -// Defines values for BrowserInteractionScrollSettledEventDataTargetType. -const ( - BrowserInteractionScrollSettledEventDataTargetTypeBackgroundPage BrowserInteractionScrollSettledEventDataTargetType = "background_page" - BrowserInteractionScrollSettledEventDataTargetTypeOther BrowserInteractionScrollSettledEventDataTargetType = "other" - BrowserInteractionScrollSettledEventDataTargetTypePage BrowserInteractionScrollSettledEventDataTargetType = "page" - BrowserInteractionScrollSettledEventDataTargetTypeServiceWorker BrowserInteractionScrollSettledEventDataTargetType = "service_worker" - BrowserInteractionScrollSettledEventDataTargetTypeSharedWorker BrowserInteractionScrollSettledEventDataTargetType = "shared_worker" -) - -// Valid indicates whether the value is a known member of the BrowserInteractionScrollSettledEventDataTargetType enum. -func (e BrowserInteractionScrollSettledEventDataTargetType) Valid() bool { - switch e { - case BrowserInteractionScrollSettledEventDataTargetTypeBackgroundPage: - return true - case BrowserInteractionScrollSettledEventDataTargetTypeOther: - return true - case BrowserInteractionScrollSettledEventDataTargetTypePage: - return true - case BrowserInteractionScrollSettledEventDataTargetTypeServiceWorker: - return true - case BrowserInteractionScrollSettledEventDataTargetTypeSharedWorker: - return true - default: - return false - } -} - // Defines values for BrowserMonitorDisconnectedEventType. const ( MonitorDisconnected BrowserMonitorDisconnectedEventType = "monitor_disconnected" @@ -422,33 +260,6 @@ func (e BrowserNetworkLoadingFailedEventType) Valid() bool { } } -// Defines values for BrowserNetworkLoadingFailedEventDataTargetType. -const ( - BrowserNetworkLoadingFailedEventDataTargetTypeBackgroundPage BrowserNetworkLoadingFailedEventDataTargetType = "background_page" - BrowserNetworkLoadingFailedEventDataTargetTypeOther BrowserNetworkLoadingFailedEventDataTargetType = "other" - BrowserNetworkLoadingFailedEventDataTargetTypePage BrowserNetworkLoadingFailedEventDataTargetType = "page" - BrowserNetworkLoadingFailedEventDataTargetTypeServiceWorker BrowserNetworkLoadingFailedEventDataTargetType = "service_worker" - BrowserNetworkLoadingFailedEventDataTargetTypeSharedWorker BrowserNetworkLoadingFailedEventDataTargetType = "shared_worker" -) - -// Valid indicates whether the value is a known member of the BrowserNetworkLoadingFailedEventDataTargetType enum. -func (e BrowserNetworkLoadingFailedEventDataTargetType) Valid() bool { - switch e { - case BrowserNetworkLoadingFailedEventDataTargetTypeBackgroundPage: - return true - case BrowserNetworkLoadingFailedEventDataTargetTypeOther: - return true - case BrowserNetworkLoadingFailedEventDataTargetTypePage: - return true - case BrowserNetworkLoadingFailedEventDataTargetTypeServiceWorker: - return true - case BrowserNetworkLoadingFailedEventDataTargetTypeSharedWorker: - return true - default: - return false - } -} - // Defines values for BrowserNetworkRequestEventType. const ( NetworkRequest BrowserNetworkRequestEventType = "network_request" @@ -464,33 +275,6 @@ func (e BrowserNetworkRequestEventType) Valid() bool { } } -// Defines values for BrowserNetworkRequestEventDataTargetType. -const ( - BrowserNetworkRequestEventDataTargetTypeBackgroundPage BrowserNetworkRequestEventDataTargetType = "background_page" - BrowserNetworkRequestEventDataTargetTypeOther BrowserNetworkRequestEventDataTargetType = "other" - BrowserNetworkRequestEventDataTargetTypePage BrowserNetworkRequestEventDataTargetType = "page" - BrowserNetworkRequestEventDataTargetTypeServiceWorker BrowserNetworkRequestEventDataTargetType = "service_worker" - BrowserNetworkRequestEventDataTargetTypeSharedWorker BrowserNetworkRequestEventDataTargetType = "shared_worker" -) - -// Valid indicates whether the value is a known member of the BrowserNetworkRequestEventDataTargetType enum. -func (e BrowserNetworkRequestEventDataTargetType) Valid() bool { - switch e { - case BrowserNetworkRequestEventDataTargetTypeBackgroundPage: - return true - case BrowserNetworkRequestEventDataTargetTypeOther: - return true - case BrowserNetworkRequestEventDataTargetTypePage: - return true - case BrowserNetworkRequestEventDataTargetTypeServiceWorker: - return true - case BrowserNetworkRequestEventDataTargetTypeSharedWorker: - return true - default: - return false - } -} - // Defines values for BrowserNetworkResponseEventType. const ( NetworkResponse BrowserNetworkResponseEventType = "network_response" @@ -506,33 +290,6 @@ func (e BrowserNetworkResponseEventType) Valid() bool { } } -// Defines values for BrowserNetworkResponseEventDataTargetType. -const ( - BrowserNetworkResponseEventDataTargetTypeBackgroundPage BrowserNetworkResponseEventDataTargetType = "background_page" - BrowserNetworkResponseEventDataTargetTypeOther BrowserNetworkResponseEventDataTargetType = "other" - BrowserNetworkResponseEventDataTargetTypePage BrowserNetworkResponseEventDataTargetType = "page" - BrowserNetworkResponseEventDataTargetTypeServiceWorker BrowserNetworkResponseEventDataTargetType = "service_worker" - BrowserNetworkResponseEventDataTargetTypeSharedWorker BrowserNetworkResponseEventDataTargetType = "shared_worker" -) - -// Valid indicates whether the value is a known member of the BrowserNetworkResponseEventDataTargetType enum. -func (e BrowserNetworkResponseEventDataTargetType) Valid() bool { - switch e { - case BrowserNetworkResponseEventDataTargetTypeBackgroundPage: - return true - case BrowserNetworkResponseEventDataTargetTypeOther: - return true - case BrowserNetworkResponseEventDataTargetTypePage: - return true - case BrowserNetworkResponseEventDataTargetTypeServiceWorker: - return true - case BrowserNetworkResponseEventDataTargetTypeSharedWorker: - return true - default: - return false - } -} - // Defines values for BrowserPageDomContentLoadedEventType. const ( PageDomContentLoaded BrowserPageDomContentLoadedEventType = "page_dom_content_loaded" @@ -548,33 +305,6 @@ func (e BrowserPageDomContentLoadedEventType) Valid() bool { } } -// Defines values for BrowserPageDomContentLoadedEventDataTargetType. -const ( - BrowserPageDomContentLoadedEventDataTargetTypeBackgroundPage BrowserPageDomContentLoadedEventDataTargetType = "background_page" - BrowserPageDomContentLoadedEventDataTargetTypeOther BrowserPageDomContentLoadedEventDataTargetType = "other" - BrowserPageDomContentLoadedEventDataTargetTypePage BrowserPageDomContentLoadedEventDataTargetType = "page" - BrowserPageDomContentLoadedEventDataTargetTypeServiceWorker BrowserPageDomContentLoadedEventDataTargetType = "service_worker" - BrowserPageDomContentLoadedEventDataTargetTypeSharedWorker BrowserPageDomContentLoadedEventDataTargetType = "shared_worker" -) - -// Valid indicates whether the value is a known member of the BrowserPageDomContentLoadedEventDataTargetType enum. -func (e BrowserPageDomContentLoadedEventDataTargetType) Valid() bool { - switch e { - case BrowserPageDomContentLoadedEventDataTargetTypeBackgroundPage: - return true - case BrowserPageDomContentLoadedEventDataTargetTypeOther: - return true - case BrowserPageDomContentLoadedEventDataTargetTypePage: - return true - case BrowserPageDomContentLoadedEventDataTargetTypeServiceWorker: - return true - case BrowserPageDomContentLoadedEventDataTargetTypeSharedWorker: - return true - default: - return false - } -} - // Defines values for BrowserPageLayoutSettledEventType. const ( PageLayoutSettled BrowserPageLayoutSettledEventType = "page_layout_settled" @@ -605,33 +335,6 @@ func (e BrowserPageLayoutShiftEventType) Valid() bool { } } -// Defines values for BrowserPageLayoutShiftEventDataTargetType. -const ( - BrowserPageLayoutShiftEventDataTargetTypeBackgroundPage BrowserPageLayoutShiftEventDataTargetType = "background_page" - BrowserPageLayoutShiftEventDataTargetTypeOther BrowserPageLayoutShiftEventDataTargetType = "other" - BrowserPageLayoutShiftEventDataTargetTypePage BrowserPageLayoutShiftEventDataTargetType = "page" - BrowserPageLayoutShiftEventDataTargetTypeServiceWorker BrowserPageLayoutShiftEventDataTargetType = "service_worker" - BrowserPageLayoutShiftEventDataTargetTypeSharedWorker BrowserPageLayoutShiftEventDataTargetType = "shared_worker" -) - -// Valid indicates whether the value is a known member of the BrowserPageLayoutShiftEventDataTargetType enum. -func (e BrowserPageLayoutShiftEventDataTargetType) Valid() bool { - switch e { - case BrowserPageLayoutShiftEventDataTargetTypeBackgroundPage: - return true - case BrowserPageLayoutShiftEventDataTargetTypeOther: - return true - case BrowserPageLayoutShiftEventDataTargetTypePage: - return true - case BrowserPageLayoutShiftEventDataTargetTypeServiceWorker: - return true - case BrowserPageLayoutShiftEventDataTargetTypeSharedWorker: - return true - default: - return false - } -} - // Defines values for BrowserPageLcpEventType. const ( PageLcp BrowserPageLcpEventType = "page_lcp" @@ -647,33 +350,6 @@ func (e BrowserPageLcpEventType) Valid() bool { } } -// Defines values for BrowserPageLcpEventDataTargetType. -const ( - BrowserPageLcpEventDataTargetTypeBackgroundPage BrowserPageLcpEventDataTargetType = "background_page" - BrowserPageLcpEventDataTargetTypeOther BrowserPageLcpEventDataTargetType = "other" - BrowserPageLcpEventDataTargetTypePage BrowserPageLcpEventDataTargetType = "page" - BrowserPageLcpEventDataTargetTypeServiceWorker BrowserPageLcpEventDataTargetType = "service_worker" - BrowserPageLcpEventDataTargetTypeSharedWorker BrowserPageLcpEventDataTargetType = "shared_worker" -) - -// Valid indicates whether the value is a known member of the BrowserPageLcpEventDataTargetType enum. -func (e BrowserPageLcpEventDataTargetType) Valid() bool { - switch e { - case BrowserPageLcpEventDataTargetTypeBackgroundPage: - return true - case BrowserPageLcpEventDataTargetTypeOther: - return true - case BrowserPageLcpEventDataTargetTypePage: - return true - case BrowserPageLcpEventDataTargetTypeServiceWorker: - return true - case BrowserPageLcpEventDataTargetTypeSharedWorker: - return true - default: - return false - } -} - // Defines values for BrowserPageLoadEventType. const ( PageLoad BrowserPageLoadEventType = "page_load" @@ -689,33 +365,6 @@ func (e BrowserPageLoadEventType) Valid() bool { } } -// Defines values for BrowserPageLoadEventDataTargetType. -const ( - BrowserPageLoadEventDataTargetTypeBackgroundPage BrowserPageLoadEventDataTargetType = "background_page" - BrowserPageLoadEventDataTargetTypeOther BrowserPageLoadEventDataTargetType = "other" - BrowserPageLoadEventDataTargetTypePage BrowserPageLoadEventDataTargetType = "page" - BrowserPageLoadEventDataTargetTypeServiceWorker BrowserPageLoadEventDataTargetType = "service_worker" - BrowserPageLoadEventDataTargetTypeSharedWorker BrowserPageLoadEventDataTargetType = "shared_worker" -) - -// Valid indicates whether the value is a known member of the BrowserPageLoadEventDataTargetType enum. -func (e BrowserPageLoadEventDataTargetType) Valid() bool { - switch e { - case BrowserPageLoadEventDataTargetTypeBackgroundPage: - return true - case BrowserPageLoadEventDataTargetTypeOther: - return true - case BrowserPageLoadEventDataTargetTypePage: - return true - case BrowserPageLoadEventDataTargetTypeServiceWorker: - return true - case BrowserPageLoadEventDataTargetTypeSharedWorker: - return true - default: - return false - } -} - // Defines values for BrowserPageNavigationEventType. const ( PageNavigation BrowserPageNavigationEventType = "page_navigation" @@ -731,33 +380,6 @@ func (e BrowserPageNavigationEventType) Valid() bool { } } -// Defines values for BrowserPageNavigationEventDataTargetType. -const ( - BrowserPageNavigationEventDataTargetTypeBackgroundPage BrowserPageNavigationEventDataTargetType = "background_page" - BrowserPageNavigationEventDataTargetTypeOther BrowserPageNavigationEventDataTargetType = "other" - BrowserPageNavigationEventDataTargetTypePage BrowserPageNavigationEventDataTargetType = "page" - BrowserPageNavigationEventDataTargetTypeServiceWorker BrowserPageNavigationEventDataTargetType = "service_worker" - BrowserPageNavigationEventDataTargetTypeSharedWorker BrowserPageNavigationEventDataTargetType = "shared_worker" -) - -// Valid indicates whether the value is a known member of the BrowserPageNavigationEventDataTargetType enum. -func (e BrowserPageNavigationEventDataTargetType) Valid() bool { - switch e { - case BrowserPageNavigationEventDataTargetTypeBackgroundPage: - return true - case BrowserPageNavigationEventDataTargetTypeOther: - return true - case BrowserPageNavigationEventDataTargetTypePage: - return true - case BrowserPageNavigationEventDataTargetTypeServiceWorker: - return true - case BrowserPageNavigationEventDataTargetTypeSharedWorker: - return true - default: - return false - } -} - // Defines values for BrowserPageNavigationSettledEventType. const ( PageNavigationSettled BrowserPageNavigationSettledEventType = "page_navigation_settled" @@ -788,27 +410,27 @@ func (e BrowserPageTabOpenedEventType) Valid() bool { } } -// Defines values for BrowserPageTabOpenedEventDataTargetType. +// Defines values for BrowserTargetType. const ( - BrowserPageTabOpenedEventDataTargetTypeBackgroundPage BrowserPageTabOpenedEventDataTargetType = "background_page" - BrowserPageTabOpenedEventDataTargetTypeOther BrowserPageTabOpenedEventDataTargetType = "other" - BrowserPageTabOpenedEventDataTargetTypePage BrowserPageTabOpenedEventDataTargetType = "page" - BrowserPageTabOpenedEventDataTargetTypeServiceWorker BrowserPageTabOpenedEventDataTargetType = "service_worker" - BrowserPageTabOpenedEventDataTargetTypeSharedWorker BrowserPageTabOpenedEventDataTargetType = "shared_worker" + BrowserTargetTypeBackgroundPage BrowserTargetType = "background_page" + BrowserTargetTypeOther BrowserTargetType = "other" + BrowserTargetTypePage BrowserTargetType = "page" + BrowserTargetTypeServiceWorker BrowserTargetType = "service_worker" + BrowserTargetTypeSharedWorker BrowserTargetType = "shared_worker" ) -// Valid indicates whether the value is a known member of the BrowserPageTabOpenedEventDataTargetType enum. -func (e BrowserPageTabOpenedEventDataTargetType) Valid() bool { +// Valid indicates whether the value is a known member of the BrowserTargetType enum. +func (e BrowserTargetType) Valid() bool { switch e { - case BrowserPageTabOpenedEventDataTargetTypeBackgroundPage: + case BrowserTargetTypeBackgroundPage: return true - case BrowserPageTabOpenedEventDataTargetTypeOther: + case BrowserTargetTypeOther: return true - case BrowserPageTabOpenedEventDataTargetTypePage: + case BrowserTargetTypePage: return true - case BrowserPageTabOpenedEventDataTargetTypeServiceWorker: + case BrowserTargetTypeServiceWorker: return true - case BrowserPageTabOpenedEventDataTargetTypeSharedWorker: + case BrowserTargetTypeSharedWorker: return true default: return false @@ -1226,7 +848,7 @@ type BrowserConsoleErrorEventData struct { TargetId string `json:"target_id"` // TargetType CDP target type of the page that produced the event. - TargetType BrowserConsoleErrorEventDataTargetType `json:"target_type"` + TargetType BrowserTargetType `json:"target_type"` // Text Error message text. Present in both source paths. Text string `json:"text"` @@ -1235,9 +857,6 @@ type BrowserConsoleErrorEventData struct { Url *string `json:"url,omitempty"` } -// BrowserConsoleErrorEventDataTargetType CDP target type of the page that produced the event. -type BrowserConsoleErrorEventDataTargetType string - // BrowserConsoleLogEvent A browser console log event (console.log, console.info, console.warn, etc.). type BrowserConsoleLogEvent struct { Data *BrowserConsoleLogEventData `json:"data,omitempty"` @@ -1283,7 +902,7 @@ type BrowserConsoleLogEventData struct { TargetId string `json:"target_id"` // TargetType CDP target type of the page that produced the event. - TargetType BrowserConsoleLogEventDataTargetType `json:"target_type"` + TargetType BrowserTargetType `json:"target_type"` // Text First console argument coerced to string. Text string `json:"text"` @@ -1292,9 +911,6 @@ type BrowserConsoleLogEventData struct { Url *string `json:"url,omitempty"` } -// BrowserConsoleLogEventDataTargetType CDP target type of the page that produced the event. -type BrowserConsoleLogEventDataTargetType string - // BrowserEventContext Browser event context stamped by the browser monitor onto all CDP-sourced events. Identifies the target, frame, and navigation epoch in which the event occurred. type BrowserEventContext struct { // FrameId CDP frame identifier within the target. @@ -1313,15 +929,12 @@ type BrowserEventContext struct { TargetId string `json:"target_id"` // TargetType CDP target type of the page that produced the event. - TargetType BrowserEventContextTargetType `json:"target_type"` + TargetType BrowserTargetType `json:"target_type"` // Url URL relevant to this event; page URL for navigation and page events, request URL for network events. Url *string `json:"url,omitempty"` } -// BrowserEventContextTargetType CDP target type of the page that produced the event. -type BrowserEventContextTargetType string - // BrowserEventSource Provenance metadata identifying which producer emitted the event. type BrowserEventSource struct { // Event Producer-specific event name (e.g. `Runtime.consoleAPICalled` for CDP-sourced console events). @@ -1382,7 +995,7 @@ type BrowserInteractionClickEventData struct { TargetId string `json:"target_id"` // TargetType CDP target type of the page that produced the event. - TargetType BrowserInteractionClickEventDataTargetType `json:"target_type"` + TargetType BrowserTargetType `json:"target_type"` // Text Visible text content of the clicked element, trimmed. Text *string `json:"text,omitempty"` @@ -1397,9 +1010,6 @@ type BrowserInteractionClickEventData struct { Y int `json:"y"` } -// BrowserInteractionClickEventDataTargetType CDP target type of the page that produced the event. -type BrowserInteractionClickEventDataTargetType string - // BrowserInteractionKeyEvent A browser keyboard event captured via injected page script. type BrowserInteractionKeyEvent struct { Data *BrowserInteractionKeyEventData `json:"data,omitempty"` @@ -1445,15 +1055,12 @@ type BrowserInteractionKeyEventData struct { TargetId string `json:"target_id"` // TargetType CDP target type of the page that produced the event. - TargetType BrowserInteractionKeyEventDataTargetType `json:"target_type"` + TargetType BrowserTargetType `json:"target_type"` // Url URL relevant to this event; page URL for navigation and page events, request URL for network events. Url *string `json:"url,omitempty"` } -// BrowserInteractionKeyEventDataTargetType CDP target type of the page that produced the event. -type BrowserInteractionKeyEventDataTargetType string - // BrowserInteractionScrollSettledEvent A browser scroll settled event emitted after scroll position stops changing, captured via injected page script. type BrowserInteractionScrollSettledEvent struct { Data *BrowserInteractionScrollSettledEventData `json:"data,omitempty"` @@ -1499,7 +1106,7 @@ type BrowserInteractionScrollSettledEventData struct { TargetSelector string `json:"target_selector"` // TargetType CDP target type of the page that produced the event. - TargetType BrowserInteractionScrollSettledEventDataTargetType `json:"target_type"` + TargetType BrowserTargetType `json:"target_type"` // ToX Final scroll x-position after the gesture settled in CSS pixels. ToX int `json:"to_x"` @@ -1511,9 +1118,6 @@ type BrowserInteractionScrollSettledEventData struct { Url *string `json:"url,omitempty"` } -// BrowserInteractionScrollSettledEventDataTargetType CDP target type of the page that produced the event. -type BrowserInteractionScrollSettledEventDataTargetType string - // BrowserMonitorDisconnectedEvent The CDP connection to Chrome was lost. Telemetry events may be dropped until monitor_reconnected arrives. Treat any in-progress computed state (network_idle, page_layout_settled) as unreliable until then. type BrowserMonitorDisconnectedEvent struct { Data *BrowserMonitorDisconnectedEventData `json:"data,omitempty"` @@ -1659,7 +1263,7 @@ type BrowserNetworkIdleEvent struct { // BrowserNetworkIdleEventType defines model for BrowserNetworkIdleEvent.Type. type BrowserNetworkIdleEventType string -// BrowserNetworkLoadingFailedEvent A browser network loading failed event. +// BrowserNetworkLoadingFailedEvent A browser network loading failed event. If the request was already in flight when CDP attached (no prior `network_request` was emitted for it), `url`, `frame_id`, `loader_id`, and `resource_type` are absent; `BrowserEventContext` is partially populated in that case. type BrowserNetworkLoadingFailedEvent struct { Data *BrowserNetworkLoadingFailedEventData `json:"data,omitempty"` @@ -1707,15 +1311,12 @@ type BrowserNetworkLoadingFailedEventData struct { TargetId string `json:"target_id"` // TargetType CDP target type of the page that produced the event. - TargetType BrowserNetworkLoadingFailedEventDataTargetType `json:"target_type"` + TargetType BrowserTargetType `json:"target_type"` // Url URL relevant to this event; page URL for navigation and page events, request URL for network events. Url *string `json:"url,omitempty"` } -// BrowserNetworkLoadingFailedEventDataTargetType CDP target type of the page that produced the event. -type BrowserNetworkLoadingFailedEventDataTargetType string - // BrowserNetworkRequestEvent A browser network request sent event. type BrowserNetworkRequestEvent struct { Data *BrowserNetworkRequestEventData `json:"data,omitempty"` @@ -1779,15 +1380,12 @@ type BrowserNetworkRequestEventData struct { TargetId string `json:"target_id"` // TargetType CDP target type of the page that produced the event. - TargetType BrowserNetworkRequestEventDataTargetType `json:"target_type"` + TargetType BrowserTargetType `json:"target_type"` // Url URL relevant to this event; page URL for navigation and page events, request URL for network events. Url *string `json:"url,omitempty"` } -// BrowserNetworkRequestEventDataTargetType CDP target type of the page that produced the event. -type BrowserNetworkRequestEventDataTargetType string - // BrowserNetworkResponseEvent A browser network response received event. Fired after the response body is fully received, not when headers arrive. type BrowserNetworkResponseEvent struct { Data *BrowserNetworkResponseEventData `json:"data,omitempty"` @@ -1848,15 +1446,12 @@ type BrowserNetworkResponseEventData struct { TargetId string `json:"target_id"` // TargetType CDP target type of the page that produced the event. - TargetType BrowserNetworkResponseEventDataTargetType `json:"target_type"` + TargetType BrowserTargetType `json:"target_type"` // Url URL relevant to this event; page URL for navigation and page events, request URL for network events. Url *string `json:"url,omitempty"` } -// BrowserNetworkResponseEventDataTargetType CDP target type of the page that produced the event. -type BrowserNetworkResponseEventDataTargetType string - // BrowserPageDomContentLoadedEvent A browser DOMContentLoaded event (CDP Page.domContentEventFired). type BrowserPageDomContentLoadedEvent struct { Data *BrowserPageDomContentLoadedEventData `json:"data,omitempty"` @@ -1896,15 +1491,12 @@ type BrowserPageDomContentLoadedEventData struct { TargetId string `json:"target_id"` // TargetType CDP target type of the page that produced the event. - TargetType BrowserPageDomContentLoadedEventDataTargetType `json:"target_type"` + TargetType BrowserTargetType `json:"target_type"` // Url URL relevant to this event; page URL for navigation and page events, request URL for network events. Url *string `json:"url,omitempty"` } -// BrowserPageDomContentLoadedEventDataTargetType CDP target type of the page that produced the event. -type BrowserPageDomContentLoadedEventDataTargetType string - // BrowserPageLayoutSettledEvent A browser layout settled event emitted 1 second after page load with no intervening layout shifts, indicating visual stability. Each layout shift resets the 1-second timer. type BrowserPageLayoutSettledEvent struct { // Data Browser event context stamped by the browser monitor onto all CDP-sourced events. Identifies the target, frame, and navigation epoch in which the event occurred. @@ -1975,7 +1567,7 @@ type BrowserPageLayoutShiftEventData struct { TargetId string `json:"target_id"` // TargetType CDP target type of the page that produced the event. - TargetType BrowserPageLayoutShiftEventDataTargetType `json:"target_type"` + TargetType BrowserTargetType `json:"target_type"` // Time Performance Timeline timestamp of the layout shift in milliseconds. Time float32 `json:"time"` @@ -1984,9 +1576,6 @@ type BrowserPageLayoutShiftEventData struct { Url *string `json:"url,omitempty"` } -// BrowserPageLayoutShiftEventDataTargetType CDP target type of the page that produced the event. -type BrowserPageLayoutShiftEventDataTargetType string - // BrowserPageLcpEvent A browser Largest Contentful Paint (LCP) event from the Performance Timeline API. type BrowserPageLcpEvent struct { Data *BrowserPageLcpEventData `json:"data,omitempty"` @@ -2047,7 +1636,7 @@ type BrowserPageLcpEventData struct { TargetId string `json:"target_id"` // TargetType CDP target type of the page that produced the event. - TargetType BrowserPageLcpEventDataTargetType `json:"target_type"` + TargetType BrowserTargetType `json:"target_type"` // Time Performance Timeline timestamp of the LCP entry in milliseconds. Time float32 `json:"time"` @@ -2056,9 +1645,6 @@ type BrowserPageLcpEventData struct { Url *string `json:"url,omitempty"` } -// BrowserPageLcpEventDataTargetType CDP target type of the page that produced the event. -type BrowserPageLcpEventDataTargetType string - // BrowserPageLoadEvent A browser page load event (CDP Page.loadEventFired). type BrowserPageLoadEvent struct { Data *BrowserPageLoadEventData `json:"data,omitempty"` @@ -2098,15 +1684,12 @@ type BrowserPageLoadEventData struct { TargetId string `json:"target_id"` // TargetType CDP target type of the page that produced the event. - TargetType BrowserPageLoadEventDataTargetType `json:"target_type"` + TargetType BrowserTargetType `json:"target_type"` // Url URL relevant to this event; page URL for navigation and page events, request URL for network events. Url *string `json:"url,omitempty"` } -// BrowserPageLoadEventDataTargetType CDP target type of the page that produced the event. -type BrowserPageLoadEventDataTargetType string - // BrowserPageNavigationEvent A browser page navigation started event (CDP Page.frameNavigated). Carries nav context fields inline but not nav_seq, as this event resets the navigation epoch. type BrowserPageNavigationEvent struct { Data *BrowserPageNavigationEventData `json:"data,omitempty"` @@ -2142,16 +1725,13 @@ type BrowserPageNavigationEventData struct { // TargetId Browser target identifier. TargetId string `json:"target_id"` - // TargetType CDP target type of the navigated frame. - TargetType BrowserPageNavigationEventDataTargetType `json:"target_type"` + // TargetType CDP target type of the page that produced the event. + TargetType BrowserTargetType `json:"target_type"` // Url URL navigated to. Url string `json:"url"` } -// BrowserPageNavigationEventDataTargetType CDP target type of the navigated frame. -type BrowserPageNavigationEventDataTargetType string - // BrowserPageNavigationSettledEvent Emitted when page_dom_content_loaded and page_layout_settled have both fired for the same navigation, indicating the page is loaded and visually stable. Independent of network_idle; a single pending request does not block it. type BrowserPageNavigationSettledEvent struct { // Data Browser event context stamped by the browser monitor onto all CDP-sourced events. Identifies the target, frame, and navigation epoch in which the event occurred. @@ -2171,7 +1751,7 @@ type BrowserPageNavigationSettledEvent struct { // BrowserPageNavigationSettledEventType defines model for BrowserPageNavigationSettledEvent.Type. type BrowserPageNavigationSettledEventType string -// BrowserPageTabOpenedEvent A new browser tab or target was opened (CDP Target.attachedToTarget for page targets). +// BrowserPageTabOpenedEvent A new browser tab or target was opened (CDP Target.attachedToTarget for page targets). Fires before a CDP session is attached to the new target, so `session_id`, `frame_id`, `loader_id`, and `nav_seq` are absent; this event does not compose `BrowserEventContext`. Consumers reading context fields generically should treat it as a special case. type BrowserPageTabOpenedEvent struct { Data *BrowserPageTabOpenedEventData `json:"data,omitempty"` @@ -2197,8 +1777,8 @@ type BrowserPageTabOpenedEventData struct { // TargetId CDP target identifier for the newly opened tab. TargetId string `json:"target_id"` - // TargetType CDP target type of the new tab. - TargetType BrowserPageTabOpenedEventDataTargetType `json:"target_type"` + // TargetType CDP target type of the page that produced the event. + TargetType BrowserTargetType `json:"target_type"` // Title Initial page title of the new tab. Title *string `json:"title,omitempty"` @@ -2207,8 +1787,8 @@ type BrowserPageTabOpenedEventData struct { Url string `json:"url"` } -// BrowserPageTabOpenedEventDataTargetType CDP target type of the new tab. -type BrowserPageTabOpenedEventDataTargetType string +// BrowserTargetType CDP target type of the page that produced the event. +type BrowserTargetType string // BrowserTelemetryCategoriesConfig Per-category telemetry capture settings for browser events. type BrowserTelemetryCategoriesConfig struct { @@ -17463,279 +17043,282 @@ func (sh *strictHandler) StreamTelemetryEvents(w http.ResponseWriter, r *http.Re var swaggerSpec = []string{ "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", - "1v9/AAAA//8LTo0nS2gBAA==", + "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", + "lq71/x8AAP///qlzWbtpAQA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/openapi.yaml b/server/openapi.yaml index 8b9827cb..212b3bb7 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -1545,6 +1545,10 @@ components: HTTP headers map forwarded as-is from CDP without normalization. Values are typically strings but may be any JSON type. additionalProperties: true + BrowserTargetType: + type: string + description: CDP target type of the page that produced the event. + enum: [page, background_page, service_worker, shared_worker, other] BrowserEventContext: type: object description: > @@ -1560,9 +1564,7 @@ components: type: string description: Browser target identifier (stable across navigations within a tab). target_type: - type: string - description: CDP target type of the page that produced the event. - enum: [page, background_page, service_worker, shared_worker, other] + $ref: "#/components/schemas/BrowserTargetType" frame_id: type: string description: CDP frame identifier within the target. @@ -1792,7 +1794,11 @@ components: description: CDP Network.ResourceType for the request, passed through as-is from Chrome. Known values include Document, Fetch, XHR, Script, Stylesheet, Image, Media, Font, TextTrack, EventSource, WebSocket, Manifest, Prefetch, Other, and more. BrowserNetworkLoadingFailedEvent: type: object - description: A browser network loading failed event. + description: > + A browser network loading failed event. If the request was already in + flight when CDP attached (no prior `network_request` was emitted for + it), `url`, `frame_id`, `loader_id`, and `resource_type` are absent; + `BrowserEventContext` is partially populated in that case. required: [ts, type, source] properties: ts: @@ -1852,9 +1858,7 @@ components: type: string description: Browser target identifier. target_type: - type: string - description: CDP target type of the navigated frame. - enum: [page, background_page, service_worker, shared_worker, other] + $ref: "#/components/schemas/BrowserTargetType" BrowserPageNavigationEvent: type: object description: A browser page navigation started event (CDP Page.frameNavigated). Carries nav context fields inline but not nav_seq, as this event resets the navigation epoch. @@ -1939,9 +1943,7 @@ components: type: string description: CDP target identifier for the newly opened tab. target_type: - type: string - description: CDP target type of the new tab. - enum: [page, background_page, service_worker, shared_worker, other] + $ref: "#/components/schemas/BrowserTargetType" url: type: string description: Initial URL of the new tab. @@ -1953,7 +1955,12 @@ components: description: Target identifier of the tab that opened this one, if any. BrowserPageTabOpenedEvent: type: object - description: A new browser tab or target was opened (CDP Target.attachedToTarget for page targets). + description: > + A new browser tab or target was opened (CDP Target.attachedToTarget + for page targets). Fires before a CDP session is attached to the new + target, so `session_id`, `frame_id`, `loader_id`, and `nav_seq` are + absent; this event does not compose `BrowserEventContext`. Consumers + reading context fields generically should treat it as a special case. required: [ts, type, source] properties: ts: From 29277f1883b1ecc8112cca72f1e8812e82550891 Mon Sep 17 00:00:00 2001 From: Archan Datta Date: Fri, 15 May 2026 13:18:25 -0300 Subject: [PATCH 27/28] review: fix test --- server/lib/cdpmonitor/types.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/server/lib/cdpmonitor/types.go b/server/lib/cdpmonitor/types.go index dc4da3f9..2d5b0422 100644 --- a/server/lib/cdpmonitor/types.go +++ b/server/lib/cdpmonitor/types.go @@ -73,10 +73,6 @@ const cdpMethodSetAutoAttach = "Target.setAutoAttach" // CDP target type for browser pages (as opposed to workers, iframes, etc.). const targetTypePage = "page" -// screenshot event payload key for the base64-encoded PNG data. -const screenshotDataKey = "png" - - // targetInfo holds metadata about an attached CDP target/session. type targetInfo struct { targetID string From 381ffa4e634b9c934d80c17baf460594947f8c85 Mon Sep 17 00:00:00 2001 From: Archan Datta Date: Fri, 15 May 2026 14:17:49 -0300 Subject: [PATCH 28/28] review: clarify seq is process-monotonic and stream data references Browser*EventData --- server/lib/cdpmonitor/README.md | 2 +- server/lib/oapi/oapi.go | 38 ++++++++++++++++----------------- server/openapi.yaml | 4 ++-- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/server/lib/cdpmonitor/README.md b/server/lib/cdpmonitor/README.md index f306b7a0..4c972e1d 100644 --- a/server/lib/cdpmonitor/README.md +++ b/server/lib/cdpmonitor/README.md @@ -112,7 +112,7 @@ Every event arrives as an `Envelope`: | Field | Type | Description | | --- | --- | --- | -| `seq` | uint64 | Monotonically increasing per-telemetry-session sequence number. | +| `seq` | uint64 | Process-monotonic sequence number; does not reset across telemetry config changes. | | `event.ts` | int64 | Wall-clock time the monitor emitted the event, as **Unix microseconds** (µs since epoch). | | `event.type` | string | See [Event taxonomy](#event-taxonomy). | | `event.category` | string | One of: `console`, `network`, `page`, `interaction`, `system`. | diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index 5b04809a..2269d704 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -17042,7 +17042,7 @@ func (sh *strictHandler) StreamTelemetryEvents(w http.ResponseWriter, r *http.Re // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+y9+3Mbt5I/+q+geLfK0ndJSk6c7D1O7Q+OJCfa+KGS5JOzOcqVwJkmidUQmAAYSbTL", + "H4sIAAAAAAAC/+y9+3Mbt5I/+q+geLfK0i5JyYmTvcep7w+OJCfa+KGS5JOzOcqVwJkmidUQmAAYSbTL", "+7ffQjcwDw6GL0l+nK+rTu064uDZDzQa3Z/+0EvULFcSpDW95x96GkyupAH8j595egp/FWDskdZKuz8l", "SlqQ1v2T53kmEm6Fknv/Y5R0fzPJFGbc/evfNIx7z3v/z17V/x79avaot48fP/Z7KZhEi9x10nvuBmR+", "xN7Hfu9AyXEmkk81ehjODX0sLWjJs080dBiOnYG+Ac38h/3eG2VfqkKmn2geb5RlOF7P/eY/J1awyfRA", @@ -17148,12 +17148,12 @@ var swaggerSpec = []string{ "nkUjRGW9EWm8UB2J8szpjFVqrq2wPvZ7qeaT9Zofaj5ZbD1TN7Be69fqBhZbI/z/pS9isKzxifvwN5jX", "2pKhuqohIYPXm4G9TApt1MpD4QzsAX5Yb50BgYgubeg+8ixccy+0cfzC/a3FYQ3Q3Rp9G/tNPQf8g2or", "y61p0Lax8rCQmOauOl2xTHdOnMOdLbdnUcrjuaD93oEGbuEQ04GVnm93eM5UCktK+aehd+Y+ZDsqsRhL", - "r7FQAqYH/ccPP+wO2WHNfv2PH35AC5pbC9p19//9c3/wH39++L7/7OO/xR947DTiAR0ZlTltU00iQMMn", - "uPSFQfaG/2c1ipYbKbaZh5CBhRNup9vt44olhImnOMzDT/wUEjz7JtvNPuaWOm45vnQYpLYS9iLLp1wW", - "M9AicYbwdJ4HtPUa/fng/YvBH/uDvw3+/Pd/Wy9W6FCYPOPrmvkLgcKAxlzngZtS34y+q0KlOqLCEGzz", - "UnMLq7v0XzON0J6S/fqe7Xg4fFlkGRNj9HqnYCHB56Hd6KC3Io0x1OJo+NnS+Ue3dvEEehyD26nNDmO7", - "NLLJ6o4p0BQyPm/YofuLpsqh+6QV+T4Cewsgw0ScoY2WBgZpeO51+p+KJXqvn8XwiZmQYuYmuh+jyVLg", - "TO8utsopyPBla27Bq0vXOtohN5dZifFgZkrZ6X8itgNdCfFuWlg141YkzuJ2axhxQ2VkaUDULxnIiV8H", + "r7FQAqYH/ecPP+wO2WHNfv3PH35AC5pbC9p19//9c3/wn39++L7/7OO/xR947DTiAR0ZlTltU00iQMMn", + "uPSFQfaG/74aRcuNFNvMQ8jAwgm30+32ccUSwsRTHObhJ34KCZ59k+1mH3NLHbccXzoMUlsJe5HlUy6L", + "GWiROEN4Os8D2nqN/nzw/sXgj/3B3wZ//se/rRcrdChMnvF1zfyFQGFAY67zwE2pb0bfVaFSHVFhCLZ5", + "qbmF1V36r5lGaE/Jfn3PdjwcviyyjIkxer1TsJDg89BudNBbkcYYanE0/Gzp/KNbu3gCPY7B7dRmh7Fd", + "GtlkdccUaAoZnzfs0P1FU+XQfdKKfB+BvQWQYSLO0EZLA4M0PPc6/U/FEr3Xz2L4xExIMXMT3Y/RZClw", + "pncXW+UUZPiyNbfg1aVrHe2Qm8usxHgwM6Xs9P8gtgNdCfFuWlg141YkzuJ2axhxQ2VkaUDULxnIiV8H", "v6N1PN3f39+vreuH6MLuc8twS9jokhHXlG81xu6xTBg0K/9512fzP+smfc6FNiXtQobv7VRkNImJkJMh", "e11Q0WZnOzJuWQbcWPYd4eM2C2kvTrm2ITN+d0y/foebV/3H4mqW/ki0bPBwrKrkOwNsWsy4HGTiGtjP", "8F5gHpK+gYqbkcK3fE4LCZW13VZlQrorPV5vc5X5SpO/Y40oNxpCr5vLHPSlgQlyGokD5JcoZJczqkgp", @@ -17187,7 +17187,7 @@ var swaggerSpec = []string{ "K9nm++/21wsKW4wN3u7kXRGwFU7dcGw5nsJzDKO0FuG1aizqyL3fp2+5BmZ5npN9tTwmZMlBWga5zlad", "qNcwR7A7w4zbHH+ir3/Axsd/5UOdXO9mPhupDAfHgTxItRsiJLWPgPHat8wUea60f324S5VVKruQOwaA", "/ePpU1zLfMZSGGNZGSXN7pD5wIeq8MFF7xSfwy96fXbRw/sr/fPA6oz+9SLzf3r5w0VveEHhThQRIwzF", - "ayU4QZ4Z5WaZqNnIH1nGxwhTf/9uw0sq/heO9u/nfITdbrChC9oadzeqrwld6ugOkgeLbeFueTOMn5pL", + "ayU4QZ4Z5WaZqNnIH1nGxwhTf/9hw0sq/heO9h/nfITdbrChC9oadzeqrwld6ugOkgeLbeFueTOMn5pL", "p0ekKkwWyRjketIMk/pnJOWVeuJ6UpQoeutzFTeXWqlmkFN8GYUPX/JoW4if7pqyXIsbkcEEOtQON5eF", "z/ta3mUAqXJfu65kkeHpEXR8O3OK1h55ucSNDnmGZgpZVm65OwuKOEZQchtLzlQai+ZXl9UdXn9p3fU9", "+rcrGoRAGBcXsNrmAnnTzV4fYvGtnmYfPi4S7EjeCK0kXjzKuCWEePCgHrWtr+1Gxfmt2KPNwo26Cdgd", @@ -17217,12 +17217,12 @@ var swaggerSpec = []string{ "AqgcXEZLofGaGcQIO/hXp9d1UFUoC2E6jNzN1Uw51mOkiG0xhnoxR+JrqjmGERKvuLEDHHlwfOjj0Aof", "7n12dhQ8Rt5RJgxhDFEoS6tcwgYPa26Nwaf251IadoXHL6ROE2jKrdDgCx2RUwXTfRFCJa+lVXvKMZAp", "rgdhVELqtU+erlY/ZC/0SFjNdciA9naWoSoglE5dJQ9rYDylzobsZQt4flmOdz+WnI0zBj1A5w2xTVl6", - "CtKA2xNKi/wfn/W8t/CXQ+y3FirVZ+3U7ihoaMOR9rm9ZhUp/uvs7ZvSaRbb50wYvz/LU9UJuYMc0Iv7", - "3kRtje0oEcRt3ONVSDkDG7jFn0ylY7izYIp1OpvAiquiKevXTMECKY2SKY1qKQ0sTH8F06HKCs3OBzVu", - "WFjlcb2WJe3PwtvWFq+IXZji7fC4PM9Eh1vx92ahx2ZhybCZTfx2R1/fJWVmlHXAqhmJUG+IGq7/5Irg", - "Ah6EciP4cw96vvWxVdZPN7Z1orLDUEAJS0KGk625LXRfjBfw2eCu5JdP64jyzgKG7cYOtPshPV7D3Fit", - "rsFE0dmi8Q5xBLmtMmFCiF41j5AJVMuIcZrozl2H3UqGF/KwVfABK81xgykqmAO1lwaczl0C+Xd6K4SQ", - "X0gf8+tUgBsLbRYumQoXnNp4jZ1iO/i3/9x3++ITdXaHF7KGGIgw5G7X5jmdErdKpwOnK1N6FfNBpOXK", + "CtKA2xNKi/y7z3reW/jLIfZbC5Xqs3ZqdxQ0tOFI+9xes4oU/3X29k3pNIvtcyaM35/lqeqE3EEO6MV9", + "b6K2xnaUCOI27vEqpJyBDdziT6bSMdxZMMU6nU1gxVXRlPVrpmCBlEbJlEa1lAYWpr+C6VBlhWbngxo3", + "LKzyuF7LkvZn4W1ri1fELkzxdnhcnmeiw634e7PQY7OwZNjMJn67o6/vkjIzyjpg1YxEqDdEDdd/ckVw", + "AQ9CuRH8uQc93/rYKuunG9s6UdlhKKCEJSHDydbcFrovxgv4bHBX8sundUR5ZwHDdmMH2v2QHq9hbqxW", + "12Ci6GzReIc4gtxWmTAhRK+aR8gEqmXEOE10567DbiXDC3nYKviAlea4wRQVzIHaSwNO5y6B/Du9FULI", + "L6SP+XUqwI2FNguXTIULTm28xk6xHfzb/9l3++ITdXaHF7KGGIgw5G7X5jmdErdKpwOnK1N6FfNBpOXK", "hbSaD9xXNKC5kO78l5yAWPBgo59zXhhHJ2eS0NxIQ7u5LCFdtExEvwNX3bEi7isCQ9NhMFXGlpDmHUA6", "6tIJTALLeRGLQ0y5O6idzT7PFRPSSYKTOHeN/YnNhLH8GsjgwXMSbQncsxFPrk3OE6iYgO0P2VuZzb0K", "M7EdYDtGZCBtNm/s04WsPkPe2KWtKu9k+8OnUa7vqJnbiSn/uxYWShT87QR9ObUaIQoB+CkMuC0Y/kcs", @@ -17268,7 +17268,7 @@ var swaggerSpec = []string{ "BccLxu67vnaX8vWFXMLY7A9jU6bGY9CGGTGRWMwc0zrG3FjQ5YAIjyrTC5lC/U/u31xTEuN7kfsLMU+m", "Am6w+BDYxV5QjOKvHjWpcnv0tYhV/0MbSr9cLnoHh+xXMZmCpv8qK3IxM6N6xiHUko0Kyyy/BpYpOQE9", "vJADooSxz9n/OmpTF+xpn/mkGkdYSNnO/36/vz/4YX+fvf55z+y6hj5pqNnw+z4b8YzLxJlSruUeUoDt", - "/O/TH2ptiXDNpv/RD/QMTX7YH/y/jUataT7t41/LFt/tD56VLTooUuOWS+ymVydHBREY/lVlM/ut6vVr", + "/O/TH2ptiXDNpv/ZD/QMTX7YH/y/jUataT7t41/LFt/tD56VLTooUuOWS+ymVydHBREY/lVlM/ut6vVr", "v9GU8R8mBvC4qVb00nsvtXjuZfv/MtVom8su1aPTX5chL8qrxaZqKEvzrasTVpau/xJO2M1swqo8YZuh", "0Mqr1T78CtnmF7CN6o0BjLtFvZJtMmEs2ummk2+qIpLbHSZfJ6dUq46wSnV9yyjv7yvkFYyER8pTkG6b", "N7DsYNf1LRTKe8Rn54e4uuEzb+Xu+ArphCvA0miYW7BMmDXwtLx0R2X5FHjqr9zriTIOFkxC1/+XIs0q", @@ -17315,10 +17315,10 @@ var swaggerSpec = []string{ "Z4IqwmswxSxcO4OEHWqV06QoqIeqwUx4boJBdyXSKzYWkKXP8fCjOzSIG0jpN2EIRcFOuWRPGZ8CT0PI", "cebmagAkftoPb223oJ1gC8ybLWsAjorxGPSQHWQCv/J1a6zmyXWkNyfNKVhILM53yF5i9HVNoCkZXaqF", "LaMatuWwld3pSeWIgWH9BgABpgM/OHV0K9xeTXmOIf5YpgIkaJGwq6aSuKJaOiHc268cvBE8mmPb37Cc", - "MxX8YDvu8zmWunWcQgUcOEtVUsxAulZXdp7D1S49hmCPTwy7chx4hfyi9AxNCAS3IApgSLjH2L36Tapb", - "uXgY02z7zEAGiZ8bDRStAoGM02y8EuHt1LEeMD62WIVHmEVFPWRvZ8JiwTmQKdunfPEomULphHVlCwv+", - "NgQES/2TOIATF60hQUwBGoq7MYS0wwokkx4GqvekBj99vpyNtbT1qzU03VeXzrG4AsYNO8PHwcGZYxLP", - "lq71/x8AAP///qlzWbtpAQA=", + "MxX8YDvu8zmWunWcQgUcOEtVUsxAulZXdp7D1S49hmCPTwy7chx4hfyi9KwEnJiFpL0rf/r+O07rED8m", + "ee8zAxkkfj7UebTyAzJLc3krUd1OHbsB42OLlXeEWVTOQ/Z2JiwWmQOZsn3KEY+SJpRLWFeesMhvQyiw", + "vD+JADgR0RoSxBGgobgbQ0g7rIAx6TGgekNq8NDny9NYS0O/WkO7fXUpHIsrYNywM3wQHJw5JvFs6Vr/", + "/wEAAP//c4aJWK9pAQA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/openapi.yaml b/server/openapi.yaml index 212b3bb7..1c913ed1 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -1304,8 +1304,8 @@ paths: and only see new events. The wire shape is the generic `TelemetryEvent`. For browser events emitted by the Kernel image (or by callers using a documented `type`), - the event's `data` conforms to one of the variants of - `KnownBrowserTelemetryEvent`, selected by `type`. + the event's `data` conforms to the matching `Browser*EventData` + schema, selected by `type`. operationId: streamTelemetryEvents parameters: - in: header