diff --git a/segment.go b/analytics.go similarity index 76% rename from segment.go rename to analytics.go index 16b6e53..60ebd0a 100644 --- a/segment.go +++ b/analytics.go @@ -26,27 +26,26 @@ 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, }) @@ -54,7 +53,7 @@ func (sc *SegmentClient) trackSync(userID id.UserID, event string, properties ma return err } - req, err := http.NewRequest("POST", SegmentURL, &buf) + req, err := http.NewRequest(http.MethodPost, sc.url, &buf) if err != nil { return err } @@ -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 { diff --git a/config/config.go b/config/config.go index 2aaab9a..69dbd2c 100644 --- a/config/config.go +++ b/config/config.go @@ -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"` diff --git a/config/upgrade.go b/config/upgrade.go index f307651..13d7f79 100644 --- a/config/upgrade.go +++ b/config/upgrade.go @@ -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") @@ -181,7 +182,7 @@ var SpacedBlocks = [][]string{ {"appservice", "database"}, {"appservice", "id"}, {"appservice", "as_token"}, - {"segment_key"}, + {"analytics"}, {"metrics"}, {"whatsapp"}, {"bridge"}, diff --git a/example-config.yaml b/example-config.yaml index 33e5a79..95ed83d 100644 --- a/example-config.yaml +++ b/example-config.yaml @@ -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: diff --git a/main.go b/main.go index cc86364..0c7d7e4 100644 --- a/main.go +++ b/main.go @@ -19,6 +19,7 @@ package main import ( _ "embed" "net/http" + "net/url" "os" "strconv" "strings" @@ -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) } } diff --git a/portal.go b/portal.go index ce91a77..cf1ad94 100644 --- a/portal.go +++ b/portal.go @@ -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, }) @@ -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, }) diff --git a/provisioning.go b/provisioning.go index c9060a8..3f6bfc4 100644 --- a/provisioning.go +++ b/provisioning.go @@ -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 { @@ -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, @@ -711,7 +711,7 @@ 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, @@ -719,7 +719,7 @@ func (prov *ProvisioningAPI) Login(w http.ResponseWriter, r *http.Request) { 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, @@ -727,14 +727,14 @@ func (prov *ProvisioningAPI) Login(w http.ResponseWriter, r *http.Request) { 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, @@ -742,13 +742,13 @@ func (prov *ProvisioningAPI) Login(w http.ResponseWriter, r *http.Request) { 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()), diff --git a/user.go b/user.go index 9adfde9..794d100 100644 --- a/user.go +++ b/user.go @@ -498,7 +498,7 @@ 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, }) @@ -506,7 +506,7 @@ func (user *User) createClient(sess *store.Device) { 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,