package insights import ( "time" json "github.com/goccy/go-json" "github.com/google/uuid" ) // Values implementing this interface are used by insights clients to notify // the application when a message send succeeded or failed. // // Callback methods are called by a client's internal goroutines. There are no // guarantees about which goroutine triggers the callbacks, whether calls are // made sequentially or in parallel, or whether callback order matches enqueue order. // // Callback methods must return quickly and avoid long blocking operations so // they do not interfere with the client's internal workflow. type Callback interface { // Success is called for every message that was successfully sent to the API. Success(APIMessage) // Failure is called for every message that failed to be sent to the API and // will be discarded by the client. The error describes the send or serialization failure. Failure(APIMessage, error) } // This interface is used to represent insights objects that can be sent via // a client. // // Types like insights.Capture, insights.Alias, etc... implement this interface // and therefore can be passed to the insights.Client.Enqueue method. type Message interface { // Validate checks the internal structure of the message. It returns nil when // the message is valid or an error describing the invalid field. Validate() error // APIfy converts the message into its PostHog batch API representation. APIfy() APIMessage // internal prevents external packages from satisfying Message. Calling it panics. internal() } // Returns the time value passed as first argument, unless it's the zero-value, // in that case the default value passed as second argument is returned. func makeTimestamp(t time.Time, def time.Time) time.Time { if t == (time.Time{}) { return def } return t } // makeUUID returns the UUID passed as first argument if non-empty, // otherwise generates and returns a new random UUID (v4). func makeUUID(u string) string { if u != "" { return u } return uuid.New().String() } // batch represents objects sent to the /batch/ endpoint with pre-serialized messages. // Messages are pre-serialized as json.RawMessage for efficient batch building - // json.Marshal embeds them directly without re-encoding. type batch struct { ApiKey string `json:"api_key"` HistoricalMigration bool `json:"historical_migration,omitempty"` Messages []json.RawMessage `json:"batch"` } // APIMessage is a wire-format message produced by Message.APIfy and passed to callbacks. type APIMessage any // prepareForSend creates the API message and serializes it to JSON. // Returns pre-serialized JSON for efficient batch building, the original // APIMessage for callbacks, and any serialization error. // Size is derived from len(json.RawMessage) when needed - O(1) operation. func prepareForSend(msg Message) (json.RawMessage, APIMessage, error) { apiMsg := msg.APIfy() data, err := json.Marshal(apiMsg) if err != nil { return nil, apiMsg, err } return json.RawMessage(data), apiMsg, nil } const ( maxBatchBytes = 500000 maxMessageBytes = 500000 )