Files
commerce/thirdparty/woopra/client.go
zeekay 1943f1c093 Hanzo Commerce
Multi-tenant commerce and billing: orders, subscriptions, invoices, plans,
usage metering and a credit ledger. One Go binary. MIT OR Apache-2.0.

Claude-Session: https://claude.ai/code/session_01SAtaDFm8VDiWrhYHNNtxMw
2026-09-23 21:52:16 -07:00

147 lines
3.4 KiB
Go

package woopra
import (
"context"
"errors"
"net/http"
"net/url"
"strconv"
"time"
"github.com/hanzoai/commerce/log"
)
const (
// To track events in Woopra, send Http GET requests to:
TRACK_API_ENDPOINT = "https://www.woopra.com/track/ce"
// Identify requests can be used to ID a visitor and/or add properties to that visitor. The endpoint is:
IDENTIFY_API_ENDPOINT = "https://www.woopra.com/track/identify"
)
// Tracker used for storing sharable settings
type Tracker struct {
Host string
Timeout int
}
// Person used for identifying person (user) for upcoming tracking
type Person struct {
Id string
Name string
Email string
Values map[string]string
}
// Context is an intermediate representation of concatenated track-request specific data
type Context struct {
Tracker
Person
Event string
VisitorProperties map[string]string
UserAgent string
httpClient *http.Client
}
// NewTracker creates new instance of sharable Tracker struct
func NewTracker(config map[string]string) (*Tracker, error) {
if len(config["host"]) == 0 {
return &Tracker{}, errors.New(
"Host should be specified. This is domain as registered in Woopra, " +
"it identifies which project environment to receive the tracking request",
)
}
return &Tracker{Host: config["host"]}, nil
}
// Identify is used to create reusable person-specific association.
// Last (optional) argument is used to proxy UserAgent data to Woopra
func (t Tracker) Identify(ctx context.Context, person Person, args ...string) *Context {
var userAgent string
if len(args) > 0 {
userAgent = args[0]
}
httpClient := &http.Client{Timeout: 55 * time.Second}
return &Context{
Tracker: t,
Person: person,
UserAgent: userAgent,
httpClient: httpClient,
}
}
// Push identify a user without any tracking event
func (ctx *Context) Push() *Context {
ctx.performRequest(IDENTIFY_API_ENDPOINT)
return ctx
}
// Track is tracking a custom event for identified person
func (ctx *Context) Track(event string, properties map[string]string) *Context {
ctx.Event = event
ctx.VisitorProperties = properties
ctx.performRequest(TRACK_API_ENDPOINT)
return ctx
}
// performRequest is a low-level abstraction, used for sending requests to the Woopra API
func (ctx *Context) performRequest(endpoint string) {
req, _ := http.NewRequest("POST", endpoint, nil)
// proxying User-Agent header from client to Woopra
if len(ctx.UserAgent) > 0 {
header := make(http.Header)
header.Add("User-Agent", ctx.UserAgent)
req.Header = header
}
req.URL.RawQuery = ctx.prepareQuery()
go func() {
resp, err := ctx.httpClient.Do(req)
if err != nil {
log.Panic(err)
}
defer resp.Body.Close()
}()
}
// prepareQuery concatenates all needed request parameters
func (ctx *Context) prepareQuery() string {
values := make(url.Values)
values.Add("host", ctx.Tracker.Host)
for k, v := range ctx.Person.Values {
values.Add("cv_"+k, v)
}
if ctx.Tracker.Timeout > 0 {
values.Add("timeout", strconv.Itoa(ctx.Tracker.Timeout))
}
if len(ctx.Person.Id) > 0 {
values.Add("cv_id", ctx.Person.Id)
}
if len(ctx.Person.Email) > 0 {
values.Add("cv_email", ctx.Person.Email)
}
if len(ctx.Person.Name) > 0 {
values.Add("cv_name", ctx.Person.Name)
}
if len(ctx.Event) > 0 {
values.Add("event", ctx.Event)
}
for k, v := range ctx.VisitorProperties {
values.Add("ce_"+k, v)
}
return values.Encode()
}