184 lines
4.1 KiB
Go
184 lines
4.1 KiB
Go
package mixpanel
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
ErrTrackFailed = errors.New("Mixpanel did not return 1 when tracking")
|
|
|
|
IgnoreTime *time.Time = &time.Time{}
|
|
)
|
|
|
|
// The Mixapanel struct store the mixpanel endpoint and the project token
|
|
type Mixpanel interface {
|
|
// Create a mixpanel event
|
|
Track(distinctId, eventName string, e *Event) error
|
|
|
|
// Set properties for a mixpanel user.
|
|
Update(distinctId string, u *Update) error
|
|
|
|
Alias(distinctId, newId string) error
|
|
}
|
|
|
|
// The Mixapanel struct store the mixpanel endpoint and the project token
|
|
type mixpanel struct {
|
|
Client *http.Client
|
|
Token string
|
|
ApiURL string
|
|
}
|
|
|
|
// A mixpanel event
|
|
type Event struct {
|
|
// IP-address of the user. Leave empty to use autodetect, or set to "0" to
|
|
// not specify an ip-address.
|
|
IP string
|
|
|
|
// Timestamp. Set to nil to use the current time.
|
|
Timestamp *time.Time
|
|
|
|
// Custom properties. At least one must be specified.
|
|
Properties map[string]interface{}
|
|
}
|
|
|
|
// An update of a user in mixpanel
|
|
type Update struct {
|
|
// IP-address of the user. Leave empty to use autodetect, or set to "0" to
|
|
// not specify an ip-address at all.
|
|
IP string
|
|
|
|
// Timestamp. Set to nil to use the current time, or IgnoreTime to not use a
|
|
// timestamp.
|
|
Timestamp *time.Time
|
|
|
|
// Update operation such as "$set", "$update" etc.
|
|
Operation string
|
|
|
|
// Custom properties. At least one must be specified.
|
|
Properties map[string]interface{}
|
|
}
|
|
|
|
// Track create a events to current distinct id
|
|
func (m *mixpanel) Alias(distinctId, newId string) error {
|
|
props := map[string]interface{}{
|
|
"token": m.Token,
|
|
"distinct_id": distinctId,
|
|
"alias": newId,
|
|
}
|
|
|
|
params := map[string]interface{}{
|
|
"event": "$create_alias",
|
|
"properties": props,
|
|
}
|
|
|
|
return m.send("track", params, false)
|
|
}
|
|
|
|
// Track create a events to current distinct id
|
|
func (m *mixpanel) Track(distinctId, eventName string, e *Event) error {
|
|
props := map[string]interface{}{
|
|
"token": m.Token,
|
|
"distinct_id": distinctId,
|
|
}
|
|
if e.IP != "" {
|
|
props["ip"] = e.IP
|
|
}
|
|
if e.Timestamp != nil {
|
|
props["time"] = e.Timestamp.Unix()
|
|
}
|
|
|
|
for key, value := range e.Properties {
|
|
props[key] = value
|
|
}
|
|
|
|
params := map[string]interface{}{
|
|
"event": eventName,
|
|
"properties": props,
|
|
}
|
|
|
|
autoGeolocate := e.IP == ""
|
|
|
|
return m.send("track", params, autoGeolocate)
|
|
}
|
|
|
|
// Updates a user in mixpanel. See
|
|
// https://mixpanel.com/help/reference/http#people-analytics-updates
|
|
func (m *mixpanel) Update(distinctId string, u *Update) error {
|
|
params := map[string]interface{}{
|
|
"$token": m.Token,
|
|
"$distinct_id": distinctId,
|
|
}
|
|
|
|
if u.IP != "" {
|
|
params["$ip"] = u.IP
|
|
}
|
|
if u.Timestamp == IgnoreTime {
|
|
params["$ignore_time"] = true
|
|
} else if u.Timestamp != nil {
|
|
params["$time"] = u.Timestamp.Unix()
|
|
}
|
|
|
|
params[u.Operation] = u.Properties
|
|
|
|
autoGeolocate := u.IP == ""
|
|
|
|
return m.send("engage", params, autoGeolocate)
|
|
}
|
|
|
|
func (m *mixpanel) to64(data string) string {
|
|
bytes := []byte(data)
|
|
return base64.StdEncoding.EncodeToString(bytes)
|
|
}
|
|
|
|
func (m *mixpanel) send(eventType string, params interface{}, autoGeolocate bool) error {
|
|
dataJSON, _ := json.Marshal(params)
|
|
data := string(dataJSON)
|
|
|
|
url := m.ApiURL + "/" + eventType + "?data=" + m.to64(data)
|
|
|
|
if autoGeolocate {
|
|
url += "&ip=1"
|
|
}
|
|
|
|
if resp, err := m.Client.Get(url); err != nil {
|
|
return fmt.Errorf("mixpanel: %s", err.Error())
|
|
} else {
|
|
defer resp.Body.Close()
|
|
body, bodyErr := ioutil.ReadAll(resp.Body)
|
|
if bodyErr != nil {
|
|
return fmt.Errorf("mixpanel: %s", bodyErr.Error())
|
|
}
|
|
if string(body) != "1" && string(body) != "1\n" {
|
|
return ErrTrackFailed
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// New returns the client instance. If apiURL is blank, the default will be used
|
|
// ("https://api.mixpanel.com").
|
|
func New(token, apiURL string) Mixpanel {
|
|
return NewFromClient(http.DefaultClient, token, apiURL)
|
|
}
|
|
|
|
// Creates a client instance using the specified client instance. This is useful
|
|
// when using a proxy.
|
|
func NewFromClient(c *http.Client, token, apiURL string) Mixpanel {
|
|
if apiURL == "" {
|
|
apiURL = "https://api.mixpanel.com"
|
|
}
|
|
|
|
return &mixpanel{
|
|
Client: c,
|
|
Token: token,
|
|
ApiURL: apiURL,
|
|
}
|
|
}
|