2018-05-05 17:00:56 +03:00

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,
}
}