Skip to content

Commit

Permalink
Allow sending analytics to custom server
Browse files Browse the repository at this point in the history
  • Loading branch information
tulir committed Sep 29, 2023
1 parent e1edb64 commit d2110f6
Show file tree
Hide file tree
Showing 8 changed files with 54 additions and 41 deletions.
25 changes: 12 additions & 13 deletions segment.go → analytics.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,35 +26,34 @@ import (
"maunium.net/go/mautrix/id"
)

const SegmentURL = "https://api.segment.io/v1/track"

type SegmentClient struct {
type AnalyticsClient struct {
url string
key string
userID string
log log.Logger
client http.Client
}

var Segment SegmentClient
var Analytics AnalyticsClient

func (sc *SegmentClient) trackSync(userID id.UserID, event string, properties map[string]interface{}) error {
func (sc *AnalyticsClient) trackSync(userID id.UserID, event string, properties map[string]interface{}) error {
var buf bytes.Buffer
var segmentUserID string
if Segment.userID != "" {
segmentUserID = Segment.userID
var analyticsUserID string
if Analytics.userID != "" {
analyticsUserID = Analytics.userID
} else {
segmentUserID = userID.String()
analyticsUserID = userID.String()
}
err := json.NewEncoder(&buf).Encode(map[string]interface{}{
"userId": segmentUserID,
"userId": analyticsUserID,
"event": event,
"properties": properties,
})
if err != nil {
return err
}

req, err := http.NewRequest("POST", SegmentURL, &buf)
req, err := http.NewRequest(http.MethodPost, sc.url, &buf)
if err != nil {
return err
}
Expand All @@ -70,11 +69,11 @@ func (sc *SegmentClient) trackSync(userID id.UserID, event string, properties ma
return nil
}

func (sc *SegmentClient) IsEnabled() bool {
func (sc *AnalyticsClient) IsEnabled() bool {
return len(sc.key) > 0
}

func (sc *SegmentClient) Track(userID id.UserID, event string, properties ...map[string]interface{}) {
func (sc *AnalyticsClient) Track(userID id.UserID, event string, properties ...map[string]interface{}) {
if !sc.IsEnabled() {
return
} else if len(properties) > 1 {
Expand Down
7 changes: 5 additions & 2 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@ import (
type Config struct {
*bridgeconfig.BaseConfig `yaml:",inline"`

SegmentKey string `yaml:"segment_key"`
SegmentUserID string `yaml:"segment_user_id"`
Analytics struct {
Host string `yaml:"host"`
Token string `yaml:"token"`
UserID string `yaml:"user_id"`
}

Metrics struct {
Enabled bool `yaml:"enabled"`
Expand Down
7 changes: 4 additions & 3 deletions config/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ import (
func DoUpgrade(helper *up.Helper) {
bridgeconfig.Upgrader.DoUpgrade(helper)

helper.Copy(up.Str|up.Null, "segment_key")
helper.Copy(up.Str|up.Null, "segment_user_id")
helper.Copy(up.Str|up.Null, "analytics", "host")
helper.Copy(up.Str|up.Null, "analytics", "token")
helper.Copy(up.Str|up.Null, "analytics", "user_id")

helper.Copy(up.Bool, "metrics", "enabled")
helper.Copy(up.Str, "metrics", "listen")
Expand Down Expand Up @@ -181,7 +182,7 @@ var SpacedBlocks = [][]string{
{"appservice", "database"},
{"appservice", "id"},
{"appservice", "as_token"},
{"segment_key"},
{"analytics"},
{"metrics"},
{"whatsapp"},
{"bridge"},
Expand Down
12 changes: 8 additions & 4 deletions example-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,14 @@ appservice:
as_token: "This value is generated when generating the registration"
hs_token: "This value is generated when generating the registration"

# Segment API key to track some events, like provisioning API login and encryption errors.
segment_key: null
# Optional user_id to use when sending Segment events. If null, defaults to using mxID.
segment_user_id: null
# Segment-compatible analytics endpoint for tracking some events, like provisioning API login and encryption errors.
analytics:
# Hostname of the tracking server. The path is hardcoded to /v1/track
host: api.segment.io
# API key to send with tracking requests. Tracking is disabled if this is null.
token: null
# Optional user ID for tracking events. If null, defaults to using Matrix user ID.
user_id: null

# Prometheus config.
metrics:
Expand Down
20 changes: 13 additions & 7 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package main
import (
_ "embed"
"net/http"
"net/url"
"os"
"strconv"
"strings"
Expand Down Expand Up @@ -90,13 +91,18 @@ func (br *WABridge) Init() {
br.EventProcessor.On(TypeMSC3381PollResponse, br.MatrixHandler.HandleMessage)
br.EventProcessor.On(TypeMSC3381V2PollResponse, br.MatrixHandler.HandleMessage)

Segment.log = br.Log.Sub("Segment")
Segment.key = br.Config.SegmentKey
Segment.userID = br.Config.SegmentUserID
if Segment.IsEnabled() {
Segment.log.Infoln("Segment metrics are enabled")
if Segment.userID != "" {
Segment.log.Infoln("Overriding Segment user_id with %v", Segment.userID)
Analytics.log = br.Log.Sub("Analytics")
Analytics.url = (&url.URL{
Scheme: "https",
Host: br.Config.Analytics.Host,
Path: "/v1/track",
}).String()
Analytics.key = br.Config.Analytics.Token
Analytics.userID = br.Config.Analytics.UserID
if Analytics.IsEnabled() {
Analytics.log.Infoln("Analytics metrics are enabled")
if Analytics.userID != "" {
Analytics.log.Infoln("Overriding analytics user_id with %v", Analytics.userID)
}
}

Expand Down
4 changes: 2 additions & 2 deletions portal.go
Original file line number Diff line number Diff line change
Expand Up @@ -773,7 +773,7 @@ func (portal *Portal) handleUndecryptableMessage(source *User, evt *events.Undec
if evt.IsUnavailable {
metricType = "unavailable"
}
Segment.Track(source.MXID, "WhatsApp undecryptable message", map[string]interface{}{
Analytics.Track(source.MXID, "WhatsApp undecryptable message", map[string]interface{}{
"messageID": evt.Info.ID,
"undecryptableType": metricType,
})
Expand Down Expand Up @@ -849,7 +849,7 @@ func (portal *Portal) handleMessage(source *User, evt *events.Message, historica
if evt.UnavailableRequestID != "" {
resolveType = "phone"
}
Segment.Track(source.MXID, "WhatsApp undecryptable message resolved", map[string]interface{}{
Analytics.Track(source.MXID, "WhatsApp undecryptable message resolved", map[string]interface{}{
"messageID": evt.Info.ID,
"resolveType": resolveType,
})
Expand Down
16 changes: 8 additions & 8 deletions provisioning.go
Original file line number Diff line number Diff line change
Expand Up @@ -692,7 +692,7 @@ func (prov *ProvisioningAPI) Login(w http.ResponseWriter, r *http.Request) {
}
}
user.log.Debugln("Started login via provisioning API")
Segment.Track(user.MXID, "$login_start")
Analytics.Track(user.MXID, "$login_start")

for {
select {
Expand All @@ -701,7 +701,7 @@ func (prov *ProvisioningAPI) Login(w http.ResponseWriter, r *http.Request) {
case whatsmeow.QRChannelSuccess.Event:
jid := user.Client.Store.ID
user.log.Debugln("Successful login as", jid, "via provisioning API")
Segment.Track(user.MXID, "$login_success")
Analytics.Track(user.MXID, "$login_success")
_ = c.WriteJSON(map[string]interface{}{
"success": true,
"jid": jid,
Expand All @@ -711,44 +711,44 @@ func (prov *ProvisioningAPI) Login(w http.ResponseWriter, r *http.Request) {
case whatsmeow.QRChannelTimeout.Event:
user.log.Debugln("Login via provisioning API timed out")
errCode := "login timed out"
Segment.Track(user.MXID, "$login_failure", map[string]interface{}{"error": errCode})
Analytics.Track(user.MXID, "$login_failure", map[string]interface{}{"error": errCode})
_ = c.WriteJSON(Error{
Error: "QR code scan timed out. Please try again.",
ErrCode: errCode,
})
case whatsmeow.QRChannelErrUnexpectedEvent.Event:
user.log.Debugln("Login via provisioning API failed due to unexpected event")
errCode := "unexpected event"
Segment.Track(user.MXID, "$login_failure", map[string]interface{}{"error": errCode})
Analytics.Track(user.MXID, "$login_failure", map[string]interface{}{"error": errCode})
_ = c.WriteJSON(Error{
Error: "Got unexpected event while waiting for QRs, perhaps you're already logged in?",
ErrCode: errCode,
})
case whatsmeow.QRChannelClientOutdated.Event:
user.log.Debugln("Login via provisioning API failed due to outdated client")
errCode := "bridge outdated"
Segment.Track(user.MXID, "$login_failure", map[string]interface{}{"error": errCode})
Analytics.Track(user.MXID, "$login_failure", map[string]interface{}{"error": errCode})
_ = c.WriteJSON(Error{
Error: "Got client outdated error while waiting for QRs. The bridge must be updated to continue.",
ErrCode: errCode,
})
case whatsmeow.QRChannelScannedWithoutMultidevice.Event:
errCode := "multidevice not enabled"
Segment.Track(user.MXID, "$login_failure", map[string]interface{}{"error": errCode})
Analytics.Track(user.MXID, "$login_failure", map[string]interface{}{"error": errCode})
_ = c.WriteJSON(Error{
Error: "Please enable the WhatsApp multidevice beta and scan the QR code again.",
ErrCode: errCode,
})
continue
case "error":
errCode := "fatal error"
Segment.Track(user.MXID, "$login_failure", map[string]interface{}{"error": errCode})
Analytics.Track(user.MXID, "$login_failure", map[string]interface{}{"error": errCode})
_ = c.WriteJSON(Error{
Error: "Fatal error while logging in",
ErrCode: errCode,
})
case "code":
Segment.Track(user.MXID, "$qrcode_retrieved")
Analytics.Track(user.MXID, "$qrcode_retrieved")
_ = c.WriteJSON(map[string]interface{}{
"code": evt.Code,
"timeout": int(evt.Timeout.Seconds()),
Expand Down
4 changes: 2 additions & 2 deletions user.go
Original file line number Diff line number Diff line change
Expand Up @@ -498,15 +498,15 @@ func (user *User) createClient(sess *store.Device) {
user.Client.SetForceActiveDeliveryReceipts(user.bridge.Config.Bridge.ForceActiveDeliveryReceipts)
user.Client.AutomaticMessageRerequestFromPhone = true
user.Client.GetMessageForRetry = func(requester, to types.JID, id types.MessageID) *waProto.Message {
Segment.Track(user.MXID, "WhatsApp incoming retry (message not found)", map[string]interface{}{
Analytics.Track(user.MXID, "WhatsApp incoming retry (message not found)", map[string]interface{}{
"requester": user.obfuscateJID(requester),
"messageID": id,
})
user.bridge.Metrics.TrackRetryReceipt(0, false)
return nil
}
user.Client.PreRetryCallback = func(receipt *events.Receipt, messageID types.MessageID, retryCount int, msg *waProto.Message) bool {
Segment.Track(user.MXID, "WhatsApp incoming retry (accepted)", map[string]interface{}{
Analytics.Track(user.MXID, "WhatsApp incoming retry (accepted)", map[string]interface{}{
"requester": user.obfuscateJID(receipt.Sender),
"messageID": messageID,
"retryCount": retryCount,
Expand Down

0 comments on commit d2110f6

Please sign in to comment.