MM-23574: Remove Segment. (#14712)
* MM-23574: Eliminate Segment. * Update dependencies. * Fix review suggestions.
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
6158e91308
Коммит
e5addef19b
@@ -8,16 +8,13 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
rudder "github.com/rudderlabs/analytics-go"
|
||||
"github.com/segmentio/analytics-go"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/mlog"
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
"github.com/mattermost/mattermost-server/v5/store"
|
||||
rudder "github.com/rudderlabs/analytics-go"
|
||||
)
|
||||
|
||||
const (
|
||||
SEGMENT_KEY = "placeholder_segment_key"
|
||||
RUDDER_KEY = "placeholder_rudder_key"
|
||||
RUDDER_DATAPLANE_URL = "placeholder_rudder_dataplane_url"
|
||||
|
||||
@@ -75,21 +72,8 @@ func (s *Server) SendDailyDiagnostics() {
|
||||
}
|
||||
|
||||
func (s *Server) sendDailyDiagnostics(override bool) {
|
||||
if *s.Config().LogSettings.EnableDiagnostics && s.IsLeader() && (!strings.Contains(SEGMENT_KEY, "placeholder") || override) {
|
||||
s.initDiagnostics("")
|
||||
s.trackActivity()
|
||||
s.trackConfig()
|
||||
s.trackLicense()
|
||||
s.trackPlugins()
|
||||
s.trackServer()
|
||||
s.trackPermissions()
|
||||
s.trackElasticsearch()
|
||||
s.trackGroups()
|
||||
s.trackChannelModeration()
|
||||
}
|
||||
|
||||
if *s.Config().LogSettings.EnableDiagnostics && s.IsLeader() && ((!strings.Contains(RUDDER_KEY, "placeholder") && !strings.Contains(RUDDER_DATAPLANE_URL, "placeholder")) || override) {
|
||||
s.initRudder(RUDDER_DATAPLANE_URL)
|
||||
s.initDiagnostics(RUDDER_DATAPLANE_URL)
|
||||
s.trackActivity()
|
||||
s.trackConfig()
|
||||
s.trackLicense()
|
||||
@@ -103,14 +87,6 @@ func (s *Server) sendDailyDiagnostics(override bool) {
|
||||
}
|
||||
|
||||
func (s *Server) SendDiagnostic(event string, properties map[string]interface{}) {
|
||||
if s.diagnosticClient != nil {
|
||||
s.diagnosticClient.Enqueue(analytics.Track{
|
||||
Event: event,
|
||||
UserId: s.diagnosticId,
|
||||
Properties: properties,
|
||||
})
|
||||
}
|
||||
|
||||
if s.rudderClient != nil {
|
||||
s.rudderClient.Enqueue(rudder.Track{
|
||||
Event: event,
|
||||
|
||||
@@ -63,219 +63,6 @@ func TestPluginVersion(t *testing.T) {
|
||||
assert.Empty(t, pluginVersion(plugins, "unknown.plugin"))
|
||||
}
|
||||
|
||||
func TestSegmentDiagnostics(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
th := SetupWithCustomConfig(t, func(config *model.Config) {
|
||||
*config.PluginSettings.Enable = false
|
||||
})
|
||||
defer th.TearDown()
|
||||
|
||||
type payload struct {
|
||||
MessageId string
|
||||
SentAt time.Time
|
||||
Batch []struct {
|
||||
MessageId string
|
||||
UserId string
|
||||
Event string
|
||||
Timestamp time.Time
|
||||
Properties map[string]interface{}
|
||||
}
|
||||
Context struct {
|
||||
Library struct {
|
||||
Name string
|
||||
Version string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data := make(chan payload, 100)
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
var p payload
|
||||
err = json.Unmarshal(body, &p)
|
||||
require.NoError(t, err)
|
||||
|
||||
data <- p
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
diagnosticID := "test-diagnostic-id-12345"
|
||||
th.App.SetDiagnosticId(diagnosticID)
|
||||
th.Server.initDiagnostics(server.URL)
|
||||
|
||||
assertPayload := func(t *testing.T, actual payload, event string, properties map[string]interface{}) {
|
||||
assert.NotEmpty(t, actual.MessageId)
|
||||
assert.False(t, actual.SentAt.IsZero())
|
||||
if assert.Len(t, actual.Batch, 1) {
|
||||
assert.NotEmpty(t, actual.Batch[0].MessageId, "message id should not be empty")
|
||||
assert.Equal(t, diagnosticID, actual.Batch[0].UserId)
|
||||
if event != "" {
|
||||
assert.Equal(t, event, actual.Batch[0].Event)
|
||||
}
|
||||
assert.False(t, actual.Batch[0].Timestamp.IsZero(), "batch timestamp should not be the zero value")
|
||||
if properties != nil {
|
||||
assert.Equal(t, properties, actual.Batch[0].Properties)
|
||||
}
|
||||
}
|
||||
assert.Equal(t, "analytics-go", actual.Context.Library.Name)
|
||||
assert.Equal(t, "3.0.0", actual.Context.Library.Version)
|
||||
}
|
||||
|
||||
// Should send a client identify message
|
||||
select {
|
||||
case identifyMessage := <-data:
|
||||
assertPayload(t, identifyMessage, "", nil)
|
||||
case <-time.After(time.Second * 1):
|
||||
require.Fail(t, "Did not receive ID message")
|
||||
}
|
||||
|
||||
t.Run("Send", func(t *testing.T) {
|
||||
testValue := "test-send-value-6789"
|
||||
th.App.Srv().SendDiagnostic("Testing Diagnostic", map[string]interface{}{
|
||||
"hey": testValue,
|
||||
})
|
||||
select {
|
||||
case result := <-data:
|
||||
assertPayload(t, result, "Testing Diagnostic", map[string]interface{}{
|
||||
"hey": testValue,
|
||||
})
|
||||
case <-time.After(time.Second * 1):
|
||||
require.Fail(t, "Did not receive diagnostic")
|
||||
}
|
||||
})
|
||||
|
||||
// Plugins remain disabled at this point
|
||||
t.Run("SendDailyDiagnosticsPluginsDisabled", func(t *testing.T) {
|
||||
th.App.Srv().sendDailyDiagnostics(true)
|
||||
|
||||
var info []string
|
||||
// Collect the info sent.
|
||||
Loop:
|
||||
for {
|
||||
select {
|
||||
case result := <-data:
|
||||
assertPayload(t, result, "", nil)
|
||||
info = append(info, result.Batch[0].Event)
|
||||
case <-time.After(time.Second * 1):
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
|
||||
for _, item := range []string{
|
||||
TRACK_CONFIG_SERVICE,
|
||||
TRACK_CONFIG_TEAM,
|
||||
TRACK_CONFIG_SQL,
|
||||
TRACK_CONFIG_LOG,
|
||||
TRACK_CONFIG_AUDIT,
|
||||
TRACK_CONFIG_NOTIFICATION_LOG,
|
||||
TRACK_CONFIG_FILE,
|
||||
TRACK_CONFIG_RATE,
|
||||
TRACK_CONFIG_EMAIL,
|
||||
TRACK_CONFIG_PRIVACY,
|
||||
TRACK_CONFIG_OAUTH,
|
||||
TRACK_CONFIG_LDAP,
|
||||
TRACK_CONFIG_COMPLIANCE,
|
||||
TRACK_CONFIG_LOCALIZATION,
|
||||
TRACK_CONFIG_SAML,
|
||||
TRACK_CONFIG_PASSWORD,
|
||||
TRACK_CONFIG_CLUSTER,
|
||||
TRACK_CONFIG_METRICS,
|
||||
TRACK_CONFIG_SUPPORT,
|
||||
TRACK_CONFIG_NATIVEAPP,
|
||||
TRACK_CONFIG_EXPERIMENTAL,
|
||||
TRACK_CONFIG_ANALYTICS,
|
||||
TRACK_CONFIG_PLUGIN,
|
||||
TRACK_ACTIVITY,
|
||||
TRACK_SERVER,
|
||||
TRACK_CONFIG_MESSAGE_EXPORT,
|
||||
// TRACK_PLUGINS,
|
||||
} {
|
||||
require.Contains(t, info, item)
|
||||
}
|
||||
})
|
||||
|
||||
// Enable plugins for the remainder of the tests.
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = true })
|
||||
|
||||
t.Run("SendDailyDiagnostics", func(t *testing.T) {
|
||||
th.App.Srv().sendDailyDiagnostics(true)
|
||||
|
||||
var info []string
|
||||
// Collect the info sent.
|
||||
Loop:
|
||||
for {
|
||||
select {
|
||||
case result := <-data:
|
||||
assertPayload(t, result, "", nil)
|
||||
info = append(info, result.Batch[0].Event)
|
||||
case <-time.After(time.Second * 1):
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
|
||||
for _, item := range []string{
|
||||
TRACK_CONFIG_SERVICE,
|
||||
TRACK_CONFIG_TEAM,
|
||||
TRACK_CONFIG_SQL,
|
||||
TRACK_CONFIG_LOG,
|
||||
TRACK_CONFIG_AUDIT,
|
||||
TRACK_CONFIG_NOTIFICATION_LOG,
|
||||
TRACK_CONFIG_FILE,
|
||||
TRACK_CONFIG_RATE,
|
||||
TRACK_CONFIG_EMAIL,
|
||||
TRACK_CONFIG_PRIVACY,
|
||||
TRACK_CONFIG_OAUTH,
|
||||
TRACK_CONFIG_LDAP,
|
||||
TRACK_CONFIG_COMPLIANCE,
|
||||
TRACK_CONFIG_LOCALIZATION,
|
||||
TRACK_CONFIG_SAML,
|
||||
TRACK_CONFIG_PASSWORD,
|
||||
TRACK_CONFIG_CLUSTER,
|
||||
TRACK_CONFIG_METRICS,
|
||||
TRACK_CONFIG_SUPPORT,
|
||||
TRACK_CONFIG_NATIVEAPP,
|
||||
TRACK_CONFIG_EXPERIMENTAL,
|
||||
TRACK_CONFIG_ANALYTICS,
|
||||
TRACK_CONFIG_PLUGIN,
|
||||
TRACK_ACTIVITY,
|
||||
TRACK_SERVER,
|
||||
TRACK_CONFIG_MESSAGE_EXPORT,
|
||||
TRACK_PLUGINS,
|
||||
} {
|
||||
require.Contains(t, info, item)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("SendDailyDiagnosticsNoSegmentKey", func(t *testing.T) {
|
||||
th.App.Srv().SendDailyDiagnostics()
|
||||
|
||||
select {
|
||||
case <-data:
|
||||
require.Fail(t, "Should not send diagnostics when the segment key is not set")
|
||||
case <-time.After(time.Second * 1):
|
||||
// Did not receive diagnostics
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("SendDailyDiagnosticsDisabled", func(t *testing.T) {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.LogSettings.EnableDiagnostics = false })
|
||||
|
||||
th.App.Srv().sendDailyDiagnostics(true)
|
||||
|
||||
select {
|
||||
case <-data:
|
||||
require.Fail(t, "Should not send diagnostics when they are disabled")
|
||||
case <-time.After(time.Second * 1):
|
||||
// Did not receive diagnostics
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestRudderDiagnostics(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.SkipNow()
|
||||
@@ -319,7 +106,7 @@ func TestRudderDiagnostics(t *testing.T) {
|
||||
|
||||
diagnosticID := "test-diagnostic-id-12345"
|
||||
th.App.SetDiagnosticId(diagnosticID)
|
||||
th.Server.initRudder(server.URL)
|
||||
th.Server.initDiagnostics(server.URL)
|
||||
|
||||
assertPayload := func(t *testing.T, actual payload, event string, properties map[string]interface{}) {
|
||||
t.Helper()
|
||||
|
||||
@@ -24,7 +24,6 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/cors"
|
||||
rudder "github.com/rudderlabs/analytics-go"
|
||||
analytics "github.com/segmentio/analytics-go"
|
||||
|
||||
"github.com/throttled/throttled"
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
@@ -132,9 +131,8 @@ type Server struct {
|
||||
clientConfigHash atomic.Value
|
||||
limitedClientConfig atomic.Value
|
||||
|
||||
diagnosticId string
|
||||
diagnosticClient analytics.Client
|
||||
rudderClient rudder.Client
|
||||
diagnosticId string
|
||||
rudderClient rudder.Client
|
||||
|
||||
phase2PermissionsMigrationComplete bool
|
||||
|
||||
@@ -1158,26 +1156,8 @@ func (s *Server) stopSearchEngine() {
|
||||
}
|
||||
}
|
||||
|
||||
// initDiagnostics initialises the Rudder client for the diagnostics system.
|
||||
func (s *Server) initDiagnostics(endpoint string) {
|
||||
if s.diagnosticClient == nil {
|
||||
config := analytics.Config{}
|
||||
config.Logger = analytics.StdLogger(s.Log.StdLog(mlog.String("source", "segment")))
|
||||
// For testing
|
||||
if endpoint != "" {
|
||||
config.Endpoint = endpoint
|
||||
config.Verbose = true
|
||||
config.BatchSize = 1
|
||||
}
|
||||
client, _ := analytics.NewWithConfig(SEGMENT_KEY, config)
|
||||
client.Enqueue(analytics.Identify{
|
||||
UserId: s.diagnosticId,
|
||||
})
|
||||
|
||||
s.diagnosticClient = client
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) initRudder(endpoint string) {
|
||||
if s.rudderClient == nil {
|
||||
config := rudder.Config{}
|
||||
config.Logger = rudder.StdLogger(s.Log.StdLog(mlog.String("source", "rudder")))
|
||||
@@ -1200,24 +1180,13 @@ func (s *Server) initRudder(endpoint string) {
|
||||
}
|
||||
}
|
||||
|
||||
// shutdownDiagnostics closes the diagnostic client.
|
||||
// shutdownDiagnostics closes the diagnostics system Rudder client.
|
||||
func (s *Server) shutdownDiagnostics() error {
|
||||
var segmentErr, rudderErr error
|
||||
if s.diagnosticClient != nil {
|
||||
segmentErr = s.diagnosticClient.Close()
|
||||
}
|
||||
|
||||
if s.rudderClient != nil {
|
||||
rudderErr = s.rudderClient.Close()
|
||||
return s.rudderClient.Close()
|
||||
}
|
||||
|
||||
if segmentErr != nil && rudderErr != nil {
|
||||
return errors.New(fmt.Sprintf("%s, %s", segmentErr.Error(), rudderErr.Error()))
|
||||
} else if segmentErr != nil {
|
||||
return segmentErr
|
||||
}
|
||||
|
||||
return rudderErr
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetHubs returns the list of hubs. This method is safe
|
||||
|
||||
1
go.mod
1
go.mod
@@ -92,7 +92,6 @@ require (
|
||||
github.com/rudderlabs/analytics-go v3.2.1+incompatible
|
||||
github.com/russellhaering/goxmldsig v0.0.0-20180430223755-7acd5e4a6ef7
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd
|
||||
github.com/segmentio/analytics-go v3.1.0+incompatible
|
||||
github.com/segmentio/backo-go v0.0.0-20200129164019-23eae7c10bd3 // indirect
|
||||
github.com/sirupsen/logrus v1.5.0
|
||||
github.com/smartystreets/assertions v1.0.0 // indirect
|
||||
|
||||
2
go.sum
2
go.sum
@@ -604,8 +604,6 @@ github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFo
|
||||
github.com/satori/go.uuid v0.0.0-20180103174451-36e9d2ebbde5/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/segmentio/analytics-go v3.1.0+incompatible h1:IyiOfUgQFVHvsykKKbdI7ZsH374uv3/DfZUo9+G0Z80=
|
||||
github.com/segmentio/analytics-go v3.1.0+incompatible/go.mod h1:C7CYBtQWk4vRk2RyLu0qOcbHJ18E3F1HV2C/8JvKN48=
|
||||
github.com/segmentio/backo-go v0.0.0-20200129164019-23eae7c10bd3 h1:ZuhckGJ10ulaKkdvJtiAqsLTiPrLaXSdnVgXJKJkTxE=
|
||||
github.com/segmentio/backo-go v0.0.0-20200129164019-23eae7c10bd3/go.mod h1:9/Rh6yILuLysoQnZ2oNooD2g7aBnvM7r/fNVxRNWfBc=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
|
||||
@@ -85,13 +85,13 @@ func UpdateAssetsSubpath(subpath string) error {
|
||||
|
||||
newRootHtml := string(oldRootHtml)
|
||||
|
||||
reCSP := regexp.MustCompile(`<meta http-equiv="Content-Security-Policy" content="script-src 'self' cdn.segment.com/analytics.js/ cdn.rudderlabs.com/([^"]*)">`)
|
||||
reCSP := regexp.MustCompile(`<meta http-equiv="Content-Security-Policy" content="script-src 'self' cdn.rudderlabs.com/([^"]*)">`)
|
||||
if results := reCSP.FindAllString(newRootHtml, -1); len(results) == 0 {
|
||||
return fmt.Errorf("failed to find 'Content-Security-Policy' meta tag to rewrite")
|
||||
}
|
||||
|
||||
newRootHtml = reCSP.ReplaceAllLiteralString(newRootHtml, fmt.Sprintf(
|
||||
`<meta http-equiv="Content-Security-Policy" content="script-src 'self' cdn.segment.com/analytics.js/ cdn.rudderlabs.com/%s">`,
|
||||
`<meta http-equiv="Content-Security-Policy" content="script-src 'self' cdn.rudderlabs.com/%s">`,
|
||||
GetSubpathScriptHash(subpath),
|
||||
))
|
||||
|
||||
|
||||
@@ -268,19 +268,19 @@ func sToP(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
||||
const contentSecurityPolicyNotFoundHtml = `<!DOCTYPE html> <html lang=en> <head> <meta charset=utf-8> <meta http-equiv=Content-Security-Policy content="script-src 'self' cdn.segment.com/analytics.js/ cdn.rudderlabs.com/"> <meta http-equiv=X-UA-Compatible content="IE=edge"> <meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"> <meta name=robots content="noindex, nofollow"> <meta name=referrer content=no-referrer> <title>Mattermost</title> <meta name=apple-mobile-web-app-capable content=yes> <meta name=apple-mobile-web-app-status-bar-style content=default> <meta name=mobile-web-app-capable content=yes> <meta name=apple-mobile-web-app-title content=Mattermost> <meta name=application-name content=Mattermost> <meta name=format-detection content="telephone=no"> <link rel=apple-touch-icon sizes=57x57 href=/static/files/78b7e73b41b8731ce2c41c870ecc8886.png> <link rel=apple-touch-icon sizes=60x60 href=/static/files/51d00ffd13afb6d74fd8f6dfdeef768a.png> <link rel=apple-touch-icon sizes=72x72 href=/static/files/23645596f8f78f017bd4d457abb855c4.png> <link rel=apple-touch-icon sizes=76x76 href=/static/files/26e9d72f472663a00b4b206149459fab.png> <link rel=apple-touch-icon sizes=144x144 href=/static/files/7bd91659bf3fc8c68fcd45fc1db9c630.png> <link rel=apple-touch-icon sizes=120x120 href=/static/files/fa69ffe11eb334aaef5aece8d848ca62.png> <link rel=apple-touch-icon sizes=152x152 href=/static/files/f046777feb6ab12fc43b8f9908b1db35.png> <link rel=icon type=image/png sizes=16x16 href=/static/files/02b96247d275680adaaabf01c71c571d.png> <link rel=icon type=image/png sizes=32x32 href=/static/files/1d9020f201a6762421cab8d30624fdd8.png> <link rel=icon type=image/png sizes=96x96 href=/static/files/fe23af39ae98d77dc26ae8586565970f.png> <link rel=icon type=image/png sizes=192x192 href=/static/files/d7ff68a7675f84337cc154c3d4abe713.png> <link rel=manifest href=/static/files/a985ad72552ad069537d6eea81e719c7.json> <link rel=stylesheet class=code_theme> <style>.error-screen{font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding-top:50px;max-width:750px;font-size:14px;color:#333;margin:auto;display:none;line-height:1.5}.error-screen h2{font-size:30px;font-weight:400;line-height:1.2}.error-screen ul{padding-left:15px;line-height:1.7;margin-top:0;margin-bottom:10px}.error-screen hr{color:#ddd;margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.error-screen-visible{display:block}</style> <link href="/static/main.364fd054d7a6d741efc6.css" rel="stylesheet"><script type="text/javascript" src="/static/main.e49599ac425584ffead5.js"></script></head> <body class=font--open_sans> <div id=root> <div class=error-screen> <h2>Cannot connect to Mattermost</h2> <hr/> <p>We're having trouble connecting to Mattermost. If refreshing this page (Ctrl+R or Command+R) does not work, please verify that your computer is connected to the internet.</p> <br/> </div> <div class=loading-screen style=position:relative> <div class=loading__content> <div class="round round-1"></div> <div class="round round-2"></div> <div class="round round-3"></div> </div> </div> </div> <noscript> To use Mattermost, please enable JavaScript. </noscript> </body> </html>`
|
||||
const contentSecurityPolicyNotFoundHtml = `<!DOCTYPE html> <html lang=en> <head> <meta charset=utf-8> <meta http-equiv=Content-Security-Policy content="script-src 'self' cdn.rudderlabs.com/"> <meta http-equiv=X-UA-Compatible content="IE=edge"> <meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"> <meta name=robots content="noindex, nofollow"> <meta name=referrer content=no-referrer> <title>Mattermost</title> <meta name=apple-mobile-web-app-capable content=yes> <meta name=apple-mobile-web-app-status-bar-style content=default> <meta name=mobile-web-app-capable content=yes> <meta name=apple-mobile-web-app-title content=Mattermost> <meta name=application-name content=Mattermost> <meta name=format-detection content="telephone=no"> <link rel=apple-touch-icon sizes=57x57 href=/static/files/78b7e73b41b8731ce2c41c870ecc8886.png> <link rel=apple-touch-icon sizes=60x60 href=/static/files/51d00ffd13afb6d74fd8f6dfdeef768a.png> <link rel=apple-touch-icon sizes=72x72 href=/static/files/23645596f8f78f017bd4d457abb855c4.png> <link rel=apple-touch-icon sizes=76x76 href=/static/files/26e9d72f472663a00b4b206149459fab.png> <link rel=apple-touch-icon sizes=144x144 href=/static/files/7bd91659bf3fc8c68fcd45fc1db9c630.png> <link rel=apple-touch-icon sizes=120x120 href=/static/files/fa69ffe11eb334aaef5aece8d848ca62.png> <link rel=apple-touch-icon sizes=152x152 href=/static/files/f046777feb6ab12fc43b8f9908b1db35.png> <link rel=icon type=image/png sizes=16x16 href=/static/files/02b96247d275680adaaabf01c71c571d.png> <link rel=icon type=image/png sizes=32x32 href=/static/files/1d9020f201a6762421cab8d30624fdd8.png> <link rel=icon type=image/png sizes=96x96 href=/static/files/fe23af39ae98d77dc26ae8586565970f.png> <link rel=icon type=image/png sizes=192x192 href=/static/files/d7ff68a7675f84337cc154c3d4abe713.png> <link rel=manifest href=/static/files/a985ad72552ad069537d6eea81e719c7.json> <link rel=stylesheet class=code_theme> <style>.error-screen{font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding-top:50px;max-width:750px;font-size:14px;color:#333;margin:auto;display:none;line-height:1.5}.error-screen h2{font-size:30px;font-weight:400;line-height:1.2}.error-screen ul{padding-left:15px;line-height:1.7;margin-top:0;margin-bottom:10px}.error-screen hr{color:#ddd;margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.error-screen-visible{display:block}</style> <link href="/static/main.364fd054d7a6d741efc6.css" rel="stylesheet"><script type="text/javascript" src="/static/main.e49599ac425584ffead5.js"></script></head> <body class=font--open_sans> <div id=root> <div class=error-screen> <h2>Cannot connect to Mattermost</h2> <hr/> <p>We're having trouble connecting to Mattermost. If refreshing this page (Ctrl+R or Command+R) does not work, please verify that your computer is connected to the internet.</p> <br/> </div> <div class=loading-screen style=position:relative> <div class=loading__content> <div class="round round-1"></div> <div class="round round-2"></div> <div class="round round-3"></div> </div> </div> </div> <noscript> To use Mattermost, please enable JavaScript. </noscript> </body> </html>`
|
||||
|
||||
const contentSecurityPolicyNotFound2Html = `<!DOCTYPE html> <html lang=en> <head> <meta charset=utf-8> <meta http-equiv=Content-Security-Policy content="script-src 'self' cdn.segment.com/analytics.js/ cdn.rudderlabs.com/ 'unsafe-eval'"> <meta http-equiv=X-UA-Compatible content="IE=edge"> <meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"> <meta name=robots content="noindex, nofollow"> <meta name=referrer content=no-referrer> <title>Mattermost</title> <meta name=apple-mobile-web-app-capable content=yes> <meta name=apple-mobile-web-app-status-bar-style content=default> <meta name=mobile-web-app-capable content=yes> <meta name=apple-mobile-web-app-title content=Mattermost> <meta name=application-name content=Mattermost> <meta name=format-detection content="telephone=no"> <link rel=apple-touch-icon sizes=57x57 href=/static/files/78b7e73b41b8731ce2c41c870ecc8886.png> <link rel=apple-touch-icon sizes=60x60 href=/static/files/51d00ffd13afb6d74fd8f6dfdeef768a.png> <link rel=apple-touch-icon sizes=72x72 href=/static/files/23645596f8f78f017bd4d457abb855c4.png> <link rel=apple-touch-icon sizes=76x76 href=/static/files/26e9d72f472663a00b4b206149459fab.png> <link rel=apple-touch-icon sizes=144x144 href=/static/files/7bd91659bf3fc8c68fcd45fc1db9c630.png> <link rel=apple-touch-icon sizes=120x120 href=/static/files/fa69ffe11eb334aaef5aece8d848ca62.png> <link rel=apple-touch-icon sizes=152x152 href=/static/files/f046777feb6ab12fc43b8f9908b1db35.png> <link rel=icon type=image/png sizes=16x16 href=/static/files/02b96247d275680adaaabf01c71c571d.png> <link rel=icon type=image/png sizes=32x32 href=/static/files/1d9020f201a6762421cab8d30624fdd8.png> <link rel=icon type=image/png sizes=96x96 href=/static/files/fe23af39ae98d77dc26ae8586565970f.png> <link rel=icon type=image/png sizes=192x192 href=/static/files/d7ff68a7675f84337cc154c3d4abe713.png> <link rel=manifest href=/static/files/a985ad72552ad069537d6eea81e719c7.json> <link rel=stylesheet class=code_theme> <style>.error-screen{font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding-top:50px;max-width:750px;font-size:14px;color:#333;margin:auto;display:none;line-height:1.5}.error-screen h2{font-size:30px;font-weight:400;line-height:1.2}.error-screen ul{padding-left:15px;line-height:1.7;margin-top:0;margin-bottom:10px}.error-screen hr{color:#ddd;margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.error-screen-visible{display:block}</style> <link href="/static/main.364fd054d7a6d741efc6.css" rel="stylesheet"><script type="text/javascript" src="/static/main.e49599ac425584ffead5.js"></script></head> <body class=font--open_sans> <div id=root> <div class=error-screen> <h2>Cannot connect to Mattermost</h2> <hr/> <p>We're having trouble connecting to Mattermost. If refreshing this page (Ctrl+R or Command+R) does not work, please verify that your computer is connected to the internet.</p> <br/> </div> <div class=loading-screen style=position:relative> <div class=loading__content> <div class="round round-1"></div> <div class="round round-2"></div> <div class="round round-3"></div> </div> </div> </div> <noscript> To use Mattermost, please enable JavaScript. </noscript> </body> </html>`
|
||||
const contentSecurityPolicyNotFound2Html = `<!DOCTYPE html> <html lang=en> <head> <meta charset=utf-8> <meta http-equiv=Content-Security-Policy content="script-src 'self' cdn.rudderlabs.com/ 'unsafe-eval'"> <meta http-equiv=X-UA-Compatible content="IE=edge"> <meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"> <meta name=robots content="noindex, nofollow"> <meta name=referrer content=no-referrer> <title>Mattermost</title> <meta name=apple-mobile-web-app-capable content=yes> <meta name=apple-mobile-web-app-status-bar-style content=default> <meta name=mobile-web-app-capable content=yes> <meta name=apple-mobile-web-app-title content=Mattermost> <meta name=application-name content=Mattermost> <meta name=format-detection content="telephone=no"> <link rel=apple-touch-icon sizes=57x57 href=/static/files/78b7e73b41b8731ce2c41c870ecc8886.png> <link rel=apple-touch-icon sizes=60x60 href=/static/files/51d00ffd13afb6d74fd8f6dfdeef768a.png> <link rel=apple-touch-icon sizes=72x72 href=/static/files/23645596f8f78f017bd4d457abb855c4.png> <link rel=apple-touch-icon sizes=76x76 href=/static/files/26e9d72f472663a00b4b206149459fab.png> <link rel=apple-touch-icon sizes=144x144 href=/static/files/7bd91659bf3fc8c68fcd45fc1db9c630.png> <link rel=apple-touch-icon sizes=120x120 href=/static/files/fa69ffe11eb334aaef5aece8d848ca62.png> <link rel=apple-touch-icon sizes=152x152 href=/static/files/f046777feb6ab12fc43b8f9908b1db35.png> <link rel=icon type=image/png sizes=16x16 href=/static/files/02b96247d275680adaaabf01c71c571d.png> <link rel=icon type=image/png sizes=32x32 href=/static/files/1d9020f201a6762421cab8d30624fdd8.png> <link rel=icon type=image/png sizes=96x96 href=/static/files/fe23af39ae98d77dc26ae8586565970f.png> <link rel=icon type=image/png sizes=192x192 href=/static/files/d7ff68a7675f84337cc154c3d4abe713.png> <link rel=manifest href=/static/files/a985ad72552ad069537d6eea81e719c7.json> <link rel=stylesheet class=code_theme> <style>.error-screen{font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding-top:50px;max-width:750px;font-size:14px;color:#333;margin:auto;display:none;line-height:1.5}.error-screen h2{font-size:30px;font-weight:400;line-height:1.2}.error-screen ul{padding-left:15px;line-height:1.7;margin-top:0;margin-bottom:10px}.error-screen hr{color:#ddd;margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.error-screen-visible{display:block}</style> <link href="/static/main.364fd054d7a6d741efc6.css" rel="stylesheet"><script type="text/javascript" src="/static/main.e49599ac425584ffead5.js"></script></head> <body class=font--open_sans> <div id=root> <div class=error-screen> <h2>Cannot connect to Mattermost</h2> <hr/> <p>We're having trouble connecting to Mattermost. If refreshing this page (Ctrl+R or Command+R) does not work, please verify that your computer is connected to the internet.</p> <br/> </div> <div class=loading-screen style=position:relative> <div class=loading__content> <div class="round round-1"></div> <div class="round round-2"></div> <div class="round round-3"></div> </div> </div> </div> <noscript> To use Mattermost, please enable JavaScript. </noscript> </body> </html>`
|
||||
|
||||
const baseRootHtml = `<!DOCTYPE html> <html lang=en> <head> <meta charset=utf-8> <meta http-equiv="Content-Security-Policy" content="script-src 'self' cdn.segment.com/analytics.js/ cdn.rudderlabs.com/"> <meta http-equiv=X-UA-Compatible content="IE=edge"> <meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"> <meta name=robots content="noindex, nofollow"> <meta name=referrer content=no-referrer> <title>Mattermost</title> <meta name=apple-mobile-web-app-capable content=yes> <meta name=apple-mobile-web-app-status-bar-style content=default> <meta name=mobile-web-app-capable content=yes> <meta name=apple-mobile-web-app-title content=Mattermost> <meta name=application-name content=Mattermost> <meta name=format-detection content="telephone=no"> <link rel=apple-touch-icon sizes=57x57 href=/static/files/78b7e73b41b8731ce2c41c870ecc8886.png> <link rel=apple-touch-icon sizes=60x60 href=/static/files/51d00ffd13afb6d74fd8f6dfdeef768a.png> <link rel=apple-touch-icon sizes=72x72 href=/static/files/23645596f8f78f017bd4d457abb855c4.png> <link rel=apple-touch-icon sizes=76x76 href=/static/files/26e9d72f472663a00b4b206149459fab.png> <link rel=apple-touch-icon sizes=144x144 href=/static/files/7bd91659bf3fc8c68fcd45fc1db9c630.png> <link rel=apple-touch-icon sizes=120x120 href=/static/files/fa69ffe11eb334aaef5aece8d848ca62.png> <link rel=apple-touch-icon sizes=152x152 href=/static/files/f046777feb6ab12fc43b8f9908b1db35.png> <link rel=icon type=image/png sizes=16x16 href=/static/files/02b96247d275680adaaabf01c71c571d.png> <link rel=icon type=image/png sizes=32x32 href=/static/files/1d9020f201a6762421cab8d30624fdd8.png> <link rel=icon type=image/png sizes=96x96 href=/static/files/fe23af39ae98d77dc26ae8586565970f.png> <link rel=icon type=image/png sizes=192x192 href=/static/files/d7ff68a7675f84337cc154c3d4abe713.png> <link rel=manifest href=/static/files/a985ad72552ad069537d6eea81e719c7.json> <link rel=stylesheet class=code_theme> <style>.error-screen{font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding-top:50px;max-width:750px;font-size:14px;color:#333;margin:auto;display:none;line-height:1.5}.error-screen h2{font-size:30px;font-weight:400;line-height:1.2}.error-screen ul{padding-left:15px;line-height:1.7;margin-top:0;margin-bottom:10px}.error-screen hr{color:#ddd;margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.error-screen-visible{display:block}</style> <link href="/static/main.364fd054d7a6d741efc6.css" rel="stylesheet"><script type="text/javascript" src="/static/main.e49599ac425584ffead5.js"></script></head> <body class=font--open_sans> <div id=root> <div class=error-screen> <h2>Cannot connect to Mattermost</h2> <hr/> <p>We're having trouble connecting to Mattermost. If refreshing this page (Ctrl+R or Command+R) does not work, please verify that your computer is connected to the internet.</p> <br/> </div> <div class=loading-screen style=position:relative> <div class=loading__content> <div class="round round-1"></div> <div class="round round-2"></div> <div class="round round-3"></div> </div> </div> </div> <noscript> To use Mattermost, please enable JavaScript. </noscript> </body> </html>`
|
||||
const baseRootHtml = `<!DOCTYPE html> <html lang=en> <head> <meta charset=utf-8> <meta http-equiv="Content-Security-Policy" content="script-src 'self' cdn.rudderlabs.com/"> <meta http-equiv=X-UA-Compatible content="IE=edge"> <meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"> <meta name=robots content="noindex, nofollow"> <meta name=referrer content=no-referrer> <title>Mattermost</title> <meta name=apple-mobile-web-app-capable content=yes> <meta name=apple-mobile-web-app-status-bar-style content=default> <meta name=mobile-web-app-capable content=yes> <meta name=apple-mobile-web-app-title content=Mattermost> <meta name=application-name content=Mattermost> <meta name=format-detection content="telephone=no"> <link rel=apple-touch-icon sizes=57x57 href=/static/files/78b7e73b41b8731ce2c41c870ecc8886.png> <link rel=apple-touch-icon sizes=60x60 href=/static/files/51d00ffd13afb6d74fd8f6dfdeef768a.png> <link rel=apple-touch-icon sizes=72x72 href=/static/files/23645596f8f78f017bd4d457abb855c4.png> <link rel=apple-touch-icon sizes=76x76 href=/static/files/26e9d72f472663a00b4b206149459fab.png> <link rel=apple-touch-icon sizes=144x144 href=/static/files/7bd91659bf3fc8c68fcd45fc1db9c630.png> <link rel=apple-touch-icon sizes=120x120 href=/static/files/fa69ffe11eb334aaef5aece8d848ca62.png> <link rel=apple-touch-icon sizes=152x152 href=/static/files/f046777feb6ab12fc43b8f9908b1db35.png> <link rel=icon type=image/png sizes=16x16 href=/static/files/02b96247d275680adaaabf01c71c571d.png> <link rel=icon type=image/png sizes=32x32 href=/static/files/1d9020f201a6762421cab8d30624fdd8.png> <link rel=icon type=image/png sizes=96x96 href=/static/files/fe23af39ae98d77dc26ae8586565970f.png> <link rel=icon type=image/png sizes=192x192 href=/static/files/d7ff68a7675f84337cc154c3d4abe713.png> <link rel=manifest href=/static/files/a985ad72552ad069537d6eea81e719c7.json> <link rel=stylesheet class=code_theme> <style>.error-screen{font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding-top:50px;max-width:750px;font-size:14px;color:#333;margin:auto;display:none;line-height:1.5}.error-screen h2{font-size:30px;font-weight:400;line-height:1.2}.error-screen ul{padding-left:15px;line-height:1.7;margin-top:0;margin-bottom:10px}.error-screen hr{color:#ddd;margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.error-screen-visible{display:block}</style> <link href="/static/main.364fd054d7a6d741efc6.css" rel="stylesheet"><script type="text/javascript" src="/static/main.e49599ac425584ffead5.js"></script></head> <body class=font--open_sans> <div id=root> <div class=error-screen> <h2>Cannot connect to Mattermost</h2> <hr/> <p>We're having trouble connecting to Mattermost. If refreshing this page (Ctrl+R or Command+R) does not work, please verify that your computer is connected to the internet.</p> <br/> </div> <div class=loading-screen style=position:relative> <div class=loading__content> <div class="round round-1"></div> <div class="round round-2"></div> <div class="round round-3"></div> </div> </div> </div> <noscript> To use Mattermost, please enable JavaScript. </noscript> </body> </html>`
|
||||
|
||||
const baseCss = `@font-face{font-family:FontAwesome;src:url(/static/files/674f50d287a8c48dc19ba404d20fe713.eot);src:url(/static/files/674f50d287a8c48dc19ba404d20fe713.eot?#iefix&v=4.7.0) format("embedded-opentype"),url(/static/files/af7ae505a9eed503f8b8e6982036873e.woff2) format("woff2"),url(/static/files/fee66e712a8a08eef5805a46892932ad.woff) format("woff"),url(/static/files/b06871f281fee6b241d60582ae9369b9.ttf) format("truetype"),url(/static/files/677433a0892aaed7b7d2628c313c9775.svg#fontawesomeregular) format("svg");font-weight:400;font-style:normal}`
|
||||
|
||||
const subpathRootHtml = `<!DOCTYPE html> <html lang=en> <head> <meta charset=utf-8> <meta http-equiv="Content-Security-Policy" content="script-src 'self' cdn.segment.com/analytics.js/ cdn.rudderlabs.com/ 'sha256-tPOjw+tkVs9axL78ZwGtYl975dtyPHB6LYKAO2R3gR4='"> <meta http-equiv=X-UA-Compatible content="IE=edge"> <meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"> <meta name=robots content="noindex, nofollow"> <meta name=referrer content=no-referrer> <title>Mattermost</title> <meta name=apple-mobile-web-app-capable content=yes> <meta name=apple-mobile-web-app-status-bar-style content=default> <meta name=mobile-web-app-capable content=yes> <meta name=apple-mobile-web-app-title content=Mattermost> <meta name=application-name content=Mattermost> <meta name=format-detection content="telephone=no"> <link rel=apple-touch-icon sizes=57x57 href=/subpath/static/files/78b7e73b41b8731ce2c41c870ecc8886.png> <link rel=apple-touch-icon sizes=60x60 href=/subpath/static/files/51d00ffd13afb6d74fd8f6dfdeef768a.png> <link rel=apple-touch-icon sizes=72x72 href=/subpath/static/files/23645596f8f78f017bd4d457abb855c4.png> <link rel=apple-touch-icon sizes=76x76 href=/subpath/static/files/26e9d72f472663a00b4b206149459fab.png> <link rel=apple-touch-icon sizes=144x144 href=/subpath/static/files/7bd91659bf3fc8c68fcd45fc1db9c630.png> <link rel=apple-touch-icon sizes=120x120 href=/subpath/static/files/fa69ffe11eb334aaef5aece8d848ca62.png> <link rel=apple-touch-icon sizes=152x152 href=/subpath/static/files/f046777feb6ab12fc43b8f9908b1db35.png> <link rel=icon type=image/png sizes=16x16 href=/subpath/static/files/02b96247d275680adaaabf01c71c571d.png> <link rel=icon type=image/png sizes=32x32 href=/subpath/static/files/1d9020f201a6762421cab8d30624fdd8.png> <link rel=icon type=image/png sizes=96x96 href=/subpath/static/files/fe23af39ae98d77dc26ae8586565970f.png> <link rel=icon type=image/png sizes=192x192 href=/subpath/static/files/d7ff68a7675f84337cc154c3d4abe713.png> <link rel=manifest href=/subpath/static/files/a985ad72552ad069537d6eea81e719c7.json> <link rel=stylesheet class=code_theme> <style>.error-screen{font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding-top:50px;max-width:750px;font-size:14px;color:#333;margin:auto;display:none;line-height:1.5}.error-screen h2{font-size:30px;font-weight:400;line-height:1.2}.error-screen ul{padding-left:15px;line-height:1.7;margin-top:0;margin-bottom:10px}.error-screen hr{color:#ddd;margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.error-screen-visible{display:block}</style><script>window.publicPath='/subpath/static/'</script> <link href="/subpath/static/main.364fd054d7a6d741efc6.css" rel="stylesheet"><script type="text/javascript" src="/subpath/static/main.e49599ac425584ffead5.js"></script></head> <body class=font--open_sans> <div id=root> <div class=error-screen> <h2>Cannot connect to Mattermost</h2> <hr/> <p>We're having trouble connecting to Mattermost. If refreshing this page (Ctrl+R or Command+R) does not work, please verify that your computer is connected to the internet.</p> <br/> </div> <div class=loading-screen style=position:relative> <div class=loading__content> <div class="round round-1"></div> <div class="round round-2"></div> <div class="round round-3"></div> </div> </div> </div> <noscript> To use Mattermost, please enable JavaScript. </noscript> </body> </html>`
|
||||
const subpathRootHtml = `<!DOCTYPE html> <html lang=en> <head> <meta charset=utf-8> <meta http-equiv="Content-Security-Policy" content="script-src 'self' cdn.rudderlabs.com/ 'sha256-tPOjw+tkVs9axL78ZwGtYl975dtyPHB6LYKAO2R3gR4='"> <meta http-equiv=X-UA-Compatible content="IE=edge"> <meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"> <meta name=robots content="noindex, nofollow"> <meta name=referrer content=no-referrer> <title>Mattermost</title> <meta name=apple-mobile-web-app-capable content=yes> <meta name=apple-mobile-web-app-status-bar-style content=default> <meta name=mobile-web-app-capable content=yes> <meta name=apple-mobile-web-app-title content=Mattermost> <meta name=application-name content=Mattermost> <meta name=format-detection content="telephone=no"> <link rel=apple-touch-icon sizes=57x57 href=/subpath/static/files/78b7e73b41b8731ce2c41c870ecc8886.png> <link rel=apple-touch-icon sizes=60x60 href=/subpath/static/files/51d00ffd13afb6d74fd8f6dfdeef768a.png> <link rel=apple-touch-icon sizes=72x72 href=/subpath/static/files/23645596f8f78f017bd4d457abb855c4.png> <link rel=apple-touch-icon sizes=76x76 href=/subpath/static/files/26e9d72f472663a00b4b206149459fab.png> <link rel=apple-touch-icon sizes=144x144 href=/subpath/static/files/7bd91659bf3fc8c68fcd45fc1db9c630.png> <link rel=apple-touch-icon sizes=120x120 href=/subpath/static/files/fa69ffe11eb334aaef5aece8d848ca62.png> <link rel=apple-touch-icon sizes=152x152 href=/subpath/static/files/f046777feb6ab12fc43b8f9908b1db35.png> <link rel=icon type=image/png sizes=16x16 href=/subpath/static/files/02b96247d275680adaaabf01c71c571d.png> <link rel=icon type=image/png sizes=32x32 href=/subpath/static/files/1d9020f201a6762421cab8d30624fdd8.png> <link rel=icon type=image/png sizes=96x96 href=/subpath/static/files/fe23af39ae98d77dc26ae8586565970f.png> <link rel=icon type=image/png sizes=192x192 href=/subpath/static/files/d7ff68a7675f84337cc154c3d4abe713.png> <link rel=manifest href=/subpath/static/files/a985ad72552ad069537d6eea81e719c7.json> <link rel=stylesheet class=code_theme> <style>.error-screen{font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding-top:50px;max-width:750px;font-size:14px;color:#333;margin:auto;display:none;line-height:1.5}.error-screen h2{font-size:30px;font-weight:400;line-height:1.2}.error-screen ul{padding-left:15px;line-height:1.7;margin-top:0;margin-bottom:10px}.error-screen hr{color:#ddd;margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.error-screen-visible{display:block}</style><script>window.publicPath='/subpath/static/'</script> <link href="/subpath/static/main.364fd054d7a6d741efc6.css" rel="stylesheet"><script type="text/javascript" src="/subpath/static/main.e49599ac425584ffead5.js"></script></head> <body class=font--open_sans> <div id=root> <div class=error-screen> <h2>Cannot connect to Mattermost</h2> <hr/> <p>We're having trouble connecting to Mattermost. If refreshing this page (Ctrl+R or Command+R) does not work, please verify that your computer is connected to the internet.</p> <br/> </div> <div class=loading-screen style=position:relative> <div class=loading__content> <div class="round round-1"></div> <div class="round round-2"></div> <div class="round round-3"></div> </div> </div> </div> <noscript> To use Mattermost, please enable JavaScript. </noscript> </body> </html>`
|
||||
|
||||
const subpathCss = `@font-face{font-family:FontAwesome;src:url(/subpath/static/files/674f50d287a8c48dc19ba404d20fe713.eot);src:url(/subpath/static/files/674f50d287a8c48dc19ba404d20fe713.eot?#iefix&v=4.7.0) format("embedded-opentype"),url(/subpath/static/files/af7ae505a9eed503f8b8e6982036873e.woff2) format("woff2"),url(/subpath/static/files/fee66e712a8a08eef5805a46892932ad.woff) format("woff"),url(/subpath/static/files/b06871f281fee6b241d60582ae9369b9.ttf) format("truetype"),url(/subpath/static/files/677433a0892aaed7b7d2628c313c9775.svg#fontawesomeregular) format("svg");font-weight:400;font-style:normal}`
|
||||
|
||||
const newSubpathRootHtml = `<!DOCTYPE html> <html lang=en> <head> <meta charset=utf-8> <meta http-equiv="Content-Security-Policy" content="script-src 'self' cdn.segment.com/analytics.js/ cdn.rudderlabs.com/ 'sha256-mbRaPRRpWz6MNkX9SyXWMJ8XnWV4w/DoqK2M0ryUAvc='"> <meta http-equiv=X-UA-Compatible content="IE=edge"> <meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"> <meta name=robots content="noindex, nofollow"> <meta name=referrer content=no-referrer> <title>Mattermost</title> <meta name=apple-mobile-web-app-capable content=yes> <meta name=apple-mobile-web-app-status-bar-style content=default> <meta name=mobile-web-app-capable content=yes> <meta name=apple-mobile-web-app-title content=Mattermost> <meta name=application-name content=Mattermost> <meta name=format-detection content="telephone=no"> <link rel=apple-touch-icon sizes=57x57 href=/nested/subpath/static/files/78b7e73b41b8731ce2c41c870ecc8886.png> <link rel=apple-touch-icon sizes=60x60 href=/nested/subpath/static/files/51d00ffd13afb6d74fd8f6dfdeef768a.png> <link rel=apple-touch-icon sizes=72x72 href=/nested/subpath/static/files/23645596f8f78f017bd4d457abb855c4.png> <link rel=apple-touch-icon sizes=76x76 href=/nested/subpath/static/files/26e9d72f472663a00b4b206149459fab.png> <link rel=apple-touch-icon sizes=144x144 href=/nested/subpath/static/files/7bd91659bf3fc8c68fcd45fc1db9c630.png> <link rel=apple-touch-icon sizes=120x120 href=/nested/subpath/static/files/fa69ffe11eb334aaef5aece8d848ca62.png> <link rel=apple-touch-icon sizes=152x152 href=/nested/subpath/static/files/f046777feb6ab12fc43b8f9908b1db35.png> <link rel=icon type=image/png sizes=16x16 href=/nested/subpath/static/files/02b96247d275680adaaabf01c71c571d.png> <link rel=icon type=image/png sizes=32x32 href=/nested/subpath/static/files/1d9020f201a6762421cab8d30624fdd8.png> <link rel=icon type=image/png sizes=96x96 href=/nested/subpath/static/files/fe23af39ae98d77dc26ae8586565970f.png> <link rel=icon type=image/png sizes=192x192 href=/nested/subpath/static/files/d7ff68a7675f84337cc154c3d4abe713.png> <link rel=manifest href=/nested/subpath/static/files/a985ad72552ad069537d6eea81e719c7.json> <link rel=stylesheet class=code_theme> <style>.error-screen{font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding-top:50px;max-width:750px;font-size:14px;color:#333;margin:auto;display:none;line-height:1.5}.error-screen h2{font-size:30px;font-weight:400;line-height:1.2}.error-screen ul{padding-left:15px;line-height:1.7;margin-top:0;margin-bottom:10px}.error-screen hr{color:#ddd;margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.error-screen-visible{display:block}</style><script>window.publicPath='/nested/subpath/static/'</script> <link href="/nested/subpath/static/main.364fd054d7a6d741efc6.css" rel="stylesheet"><script type="text/javascript" src="/nested/subpath/static/main.e49599ac425584ffead5.js"></script></head> <body class=font--open_sans> <div id=root> <div class=error-screen> <h2>Cannot connect to Mattermost</h2> <hr/> <p>We're having trouble connecting to Mattermost. If refreshing this page (Ctrl+R or Command+R) does not work, please verify that your computer is connected to the internet.</p> <br/> </div> <div class=loading-screen style=position:relative> <div class=loading__content> <div class="round round-1"></div> <div class="round round-2"></div> <div class="round round-3"></div> </div> </div> </div> <noscript> To use Mattermost, please enable JavaScript. </noscript> </body> </html>`
|
||||
const newSubpathRootHtml = `<!DOCTYPE html> <html lang=en> <head> <meta charset=utf-8> <meta http-equiv="Content-Security-Policy" content="script-src 'self' cdn.rudderlabs.com/ 'sha256-mbRaPRRpWz6MNkX9SyXWMJ8XnWV4w/DoqK2M0ryUAvc='"> <meta http-equiv=X-UA-Compatible content="IE=edge"> <meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"> <meta name=robots content="noindex, nofollow"> <meta name=referrer content=no-referrer> <title>Mattermost</title> <meta name=apple-mobile-web-app-capable content=yes> <meta name=apple-mobile-web-app-status-bar-style content=default> <meta name=mobile-web-app-capable content=yes> <meta name=apple-mobile-web-app-title content=Mattermost> <meta name=application-name content=Mattermost> <meta name=format-detection content="telephone=no"> <link rel=apple-touch-icon sizes=57x57 href=/nested/subpath/static/files/78b7e73b41b8731ce2c41c870ecc8886.png> <link rel=apple-touch-icon sizes=60x60 href=/nested/subpath/static/files/51d00ffd13afb6d74fd8f6dfdeef768a.png> <link rel=apple-touch-icon sizes=72x72 href=/nested/subpath/static/files/23645596f8f78f017bd4d457abb855c4.png> <link rel=apple-touch-icon sizes=76x76 href=/nested/subpath/static/files/26e9d72f472663a00b4b206149459fab.png> <link rel=apple-touch-icon sizes=144x144 href=/nested/subpath/static/files/7bd91659bf3fc8c68fcd45fc1db9c630.png> <link rel=apple-touch-icon sizes=120x120 href=/nested/subpath/static/files/fa69ffe11eb334aaef5aece8d848ca62.png> <link rel=apple-touch-icon sizes=152x152 href=/nested/subpath/static/files/f046777feb6ab12fc43b8f9908b1db35.png> <link rel=icon type=image/png sizes=16x16 href=/nested/subpath/static/files/02b96247d275680adaaabf01c71c571d.png> <link rel=icon type=image/png sizes=32x32 href=/nested/subpath/static/files/1d9020f201a6762421cab8d30624fdd8.png> <link rel=icon type=image/png sizes=96x96 href=/nested/subpath/static/files/fe23af39ae98d77dc26ae8586565970f.png> <link rel=icon type=image/png sizes=192x192 href=/nested/subpath/static/files/d7ff68a7675f84337cc154c3d4abe713.png> <link rel=manifest href=/nested/subpath/static/files/a985ad72552ad069537d6eea81e719c7.json> <link rel=stylesheet class=code_theme> <style>.error-screen{font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding-top:50px;max-width:750px;font-size:14px;color:#333;margin:auto;display:none;line-height:1.5}.error-screen h2{font-size:30px;font-weight:400;line-height:1.2}.error-screen ul{padding-left:15px;line-height:1.7;margin-top:0;margin-bottom:10px}.error-screen hr{color:#ddd;margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.error-screen-visible{display:block}</style><script>window.publicPath='/nested/subpath/static/'</script> <link href="/nested/subpath/static/main.364fd054d7a6d741efc6.css" rel="stylesheet"><script type="text/javascript" src="/nested/subpath/static/main.e49599ac425584ffead5.js"></script></head> <body class=font--open_sans> <div id=root> <div class=error-screen> <h2>Cannot connect to Mattermost</h2> <hr/> <p>We're having trouble connecting to Mattermost. If refreshing this page (Ctrl+R or Command+R) does not work, please verify that your computer is connected to the internet.</p> <br/> </div> <div class=loading-screen style=position:relative> <div class=loading__content> <div class="round round-1"></div> <div class="round round-2"></div> <div class="round round-3"></div> </div> </div> </div> <noscript> To use Mattermost, please enable JavaScript. </noscript> </body> </html>`
|
||||
|
||||
const newSubpathCss = `@font-face{font-family:FontAwesome;src:url(/nested/subpath/static/files/674f50d287a8c48dc19ba404d20fe713.eot);src:url(/nested/subpath/static/files/674f50d287a8c48dc19ba404d20fe713.eot?#iefix&v=4.7.0) format("embedded-opentype"),url(/nested/subpath/static/files/af7ae505a9eed503f8b8e6982036873e.woff2) format("woff2"),url(/nested/subpath/static/files/fee66e712a8a08eef5805a46892932ad.woff) format("woff"),url(/nested/subpath/static/files/b06871f281fee6b241d60582ae9369b9.ttf) format("truetype"),url(/nested/subpath/static/files/677433a0892aaed7b7d2628c313c9775.svg#fontawesomeregular) format("svg");font-weight:400;font-style:normal}`
|
||||
|
||||
|
||||
32
vendor/github.com/segmentio/analytics-go/.gitignore
сгенерированный
поставляемый
32
vendor/github.com/segmentio/analytics-go/.gitignore
сгенерированный
поставляемый
@@ -1,32 +0,0 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
|
||||
# Emacs
|
||||
*~
|
||||
\#*
|
||||
.\#*
|
||||
|
||||
# Artifacts
|
||||
tmp/*
|
||||
6
vendor/github.com/segmentio/analytics-go/.gitmodules
сгенерированный
поставляемый
6
vendor/github.com/segmentio/analytics-go/.gitmodules
сгенерированный
поставляемый
@@ -1,6 +0,0 @@
|
||||
[submodule "vendor/github.com/segmentio/backo-go"]
|
||||
path = vendor/github.com/segmentio/backo-go
|
||||
url = https://github.com/segmentio/backo-go
|
||||
[submodule "vendor/github.com/xtgo/uuid"]
|
||||
path = vendor/github.com/xtgo/uuid
|
||||
url = https://github.com/xtgo/uuid
|
||||
88
vendor/github.com/segmentio/analytics-go/History.md
сгенерированный
поставляемый
88
vendor/github.com/segmentio/analytics-go/History.md
сгенерированный
поставляемый
@@ -1,88 +0,0 @@
|
||||
|
||||
v3.1.0 / 2019-09-20
|
||||
===================
|
||||
|
||||
* add consistent panic error message
|
||||
* Expose the Message interface Validate method
|
||||
* return error if a custom type is enqueued
|
||||
* Handle pointer types in Enqueue()
|
||||
* message: update maxMessageBytes to 32KB
|
||||
|
||||
v3.0.1 / 2018-10-02
|
||||
===================
|
||||
|
||||
* Migrate from Circle V1 format to Circle V2
|
||||
* Adds CLI for sending segment events
|
||||
* Vendor packages back-go and uuid instead of using gitsubmodules
|
||||
|
||||
|
||||
v3.0.0 / 2016-06-02
|
||||
===================
|
||||
|
||||
* 3.0 is a significant rewrite with multiple breaking changes.
|
||||
* [Quickstart](https://segment.com/docs/sources/server/go/quickstart/).
|
||||
* [Documentation](https://segment.com/docs/sources/server/go/).
|
||||
* [GoDocs](https://godoc.org/gopkg.in/segmentio/analytics-go.v3).
|
||||
* [What's New in v3](https://segment.com/docs/sources/server/go/#what-s-new-in-v3).
|
||||
|
||||
|
||||
v2.1.0 / 2015-12-28
|
||||
===================
|
||||
|
||||
* Add ability to set custom timestamps for messages.
|
||||
* Add ability to set a custom `net/http` client.
|
||||
* Add ability to set a custom logger.
|
||||
* Fix edge case when client would try to upload no messages.
|
||||
* Properly upload in-flight messages when client is asked to shutdown.
|
||||
* Add ability to set `.integrations` field on messages.
|
||||
* Fix resource leak with interval ticker after shutdown.
|
||||
* Add retries and back-off when uploading messages.
|
||||
* Add ability to set custom flush interval.
|
||||
|
||||
v2.0.0 / 2015-02-03
|
||||
===================
|
||||
|
||||
* rewrite with breaking API changes
|
||||
|
||||
v1.2.0 / 2014-09-03
|
||||
==================
|
||||
|
||||
* add public .Flush() method
|
||||
* rename .Stop() to .Close()
|
||||
|
||||
v1.1.0 / 2014-09-02
|
||||
==================
|
||||
|
||||
* add client.Stop() to flash/wait. Closes #7
|
||||
|
||||
v1.0.0 / 2014-08-26
|
||||
==================
|
||||
|
||||
* fix response close
|
||||
* change comments to be more go-like
|
||||
* change uuid libraries
|
||||
|
||||
0.1.2 / 2014-06-11
|
||||
==================
|
||||
|
||||
* add runnable example
|
||||
* fix: close body
|
||||
|
||||
0.1.1 / 2014-05-31
|
||||
==================
|
||||
|
||||
* refactor locking
|
||||
|
||||
0.1.0 / 2014-05-22
|
||||
==================
|
||||
|
||||
* replace Debug option with debug package
|
||||
|
||||
0.0.2 / 2014-05-20
|
||||
==================
|
||||
|
||||
* add .Start()
|
||||
* add mutexes
|
||||
* rename BufferSize to FlushAt and FlushInterval to FlushAfter
|
||||
* lower FlushInterval to 5 seconds
|
||||
* lower BufferSize to 20 to match other clients
|
||||
21
vendor/github.com/segmentio/analytics-go/License.md
сгенерированный
поставляемый
21
vendor/github.com/segmentio/analytics-go/License.md
сгенерированный
поставляемый
@@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Segment, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
31
vendor/github.com/segmentio/analytics-go/Makefile
сгенерированный
поставляемый
31
vendor/github.com/segmentio/analytics-go/Makefile
сгенерированный
поставляемый
@@ -1,31 +0,0 @@
|
||||
ifndef CIRCLE_ARTIFACTS
|
||||
CIRCLE_ARTIFACTS=tmp
|
||||
endif
|
||||
|
||||
bootstrap:
|
||||
.buildscript/bootstrap.sh
|
||||
|
||||
dependencies:
|
||||
@go get -v -t ./...
|
||||
|
||||
vet:
|
||||
@go vet ./...
|
||||
|
||||
test: vet
|
||||
@mkdir -p ${CIRCLE_ARTIFACTS}
|
||||
@go test -race -coverprofile=${CIRCLE_ARTIFACTS}/cover.out .
|
||||
@go tool cover -func ${CIRCLE_ARTIFACTS}/cover.out -o ${CIRCLE_ARTIFACTS}/cover.txt
|
||||
@go tool cover -html ${CIRCLE_ARTIFACTS}/cover.out -o ${CIRCLE_ARTIFACTS}/cover.html
|
||||
|
||||
build: test
|
||||
@go build ./...
|
||||
|
||||
e2e:
|
||||
@if [ "$(RUN_E2E_TESTS)" != "true" ]; then \
|
||||
echo "Skipping end to end tests."; else \
|
||||
go get github.com/segmentio/library-e2e-tester/cmd/tester; \
|
||||
tester -segment-write-key=$(SEGMENT_WRITE_KEY) -webhook-auth-username=$(WEBHOOK_AUTH_USERNAME) -webhook-bucket=$(WEBHOOK_BUCKET) -path='cli' -concurrency=2 -skip='advance|alias'; fi
|
||||
|
||||
ci: dependencies test e2e
|
||||
|
||||
.PHONY: bootstrap dependencies vet test e2e ci
|
||||
55
vendor/github.com/segmentio/analytics-go/Readme.md
сгенерированный
поставляемый
55
vendor/github.com/segmentio/analytics-go/Readme.md
сгенерированный
поставляемый
@@ -1,55 +0,0 @@
|
||||
# analytics-go [](https://circleci.com/gh/segmentio/analytics-go/tree/master) [](https://godoc.org/github.com/segmentio/analytics-go)
|
||||
|
||||
Segment analytics client for Go.
|
||||
|
||||
## Installation
|
||||
|
||||
The package can be simply installed via go get, we recommend that you use a
|
||||
package version management system like the Go vendor directory or a tool like
|
||||
Godep to avoid issues related to API breaking changes introduced between major
|
||||
versions of the library.
|
||||
|
||||
To install it in the GOPATH:
|
||||
```
|
||||
go get https://github.com/segmentio/analytics-go
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
The links bellow should provide all the documentation needed to make the best
|
||||
use of the library and the Segment API:
|
||||
|
||||
- [Documentation](https://segment.com/docs/libraries/go/)
|
||||
- [godoc](https://godoc.org/gopkg.in/segmentio/analytics-go.v3)
|
||||
- [API](https://segment.com/docs/libraries/http/)
|
||||
- [Specs](https://segment.com/docs/spec/)
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/segmentio/analytics-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Instantiates a client to use send messages to the segment API.
|
||||
client := analytics.New(os.Getenv("SEGMENT_WRITE_KEY"))
|
||||
|
||||
// Enqueues a track event that will be sent asynchronously.
|
||||
client.Enqueue(analytics.Track{
|
||||
UserId: "test-user",
|
||||
Event: "test-snippet",
|
||||
})
|
||||
|
||||
// Flushes any queued messages and closes the client.
|
||||
client.Close()
|
||||
}
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
The library is released under the [MIT license](License.md).
|
||||
44
vendor/github.com/segmentio/analytics-go/alias.go
сгенерированный
поставляемый
44
vendor/github.com/segmentio/analytics-go/alias.go
сгенерированный
поставляемый
@@ -1,44 +0,0 @@
|
||||
package analytics
|
||||
|
||||
import "time"
|
||||
|
||||
var _ Message = (*Alias)(nil)
|
||||
|
||||
// This type represents object sent in a alias call as described in
|
||||
// https://segment.com/docs/libraries/http/#alias
|
||||
type Alias struct {
|
||||
// This field is exported for serialization purposes and shouldn't be set by
|
||||
// the application, its value is always overwritten by the library.
|
||||
Type string `json:"type,omitempty"`
|
||||
|
||||
MessageId string `json:"messageId,omitempty"`
|
||||
PreviousId string `json:"previousId"`
|
||||
UserId string `json:"userId"`
|
||||
Timestamp time.Time `json:"timestamp,omitempty"`
|
||||
Context *Context `json:"context,omitempty"`
|
||||
Integrations Integrations `json:"integrations,omitempty"`
|
||||
}
|
||||
|
||||
func (msg Alias) internal() {
|
||||
panic(unimplementedError)
|
||||
}
|
||||
|
||||
func (msg Alias) Validate() error {
|
||||
if len(msg.UserId) == 0 {
|
||||
return FieldError{
|
||||
Type: "analytics.Alias",
|
||||
Name: "UserId",
|
||||
Value: msg.UserId,
|
||||
}
|
||||
}
|
||||
|
||||
if len(msg.PreviousId) == 0 {
|
||||
return FieldError{
|
||||
Type: "analytics.Alias",
|
||||
Name: "PreviousId",
|
||||
Value: msg.PreviousId,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
431
vendor/github.com/segmentio/analytics-go/analytics.go
сгенерированный
поставляемый
431
vendor/github.com/segmentio/analytics-go/analytics.go
сгенерированный
поставляемый
@@ -1,431 +0,0 @@
|
||||
package analytics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"sync"
|
||||
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Version of the client.
|
||||
const Version = "3.0.0"
|
||||
const unimplementedError = "not implemented"
|
||||
|
||||
// This interface is the main API exposed by the analytics package.
|
||||
// Values that satsify this interface are returned by the client constructors
|
||||
// provided by the package and provide a way to send messages via the HTTP API.
|
||||
type Client interface {
|
||||
io.Closer
|
||||
|
||||
// Queues a message to be sent by the client when the conditions for a batch
|
||||
// upload are met.
|
||||
// This is the main method you'll be using, a typical flow would look like
|
||||
// this:
|
||||
//
|
||||
// client := analytics.New(writeKey)
|
||||
// ...
|
||||
// client.Enqueue(analytics.Track{ ... })
|
||||
// ...
|
||||
// client.Close()
|
||||
//
|
||||
// The method returns an error if the message queue not be queued, which
|
||||
// happens if the client was already closed at the time the method was
|
||||
// called or if the message was malformed.
|
||||
Enqueue(Message) error
|
||||
}
|
||||
|
||||
type client struct {
|
||||
Config
|
||||
key string
|
||||
|
||||
// This channel is where the `Enqueue` method writes messages so they can be
|
||||
// picked up and pushed by the backend goroutine taking care of applying the
|
||||
// batching rules.
|
||||
msgs chan Message
|
||||
|
||||
// These two channels are used to synchronize the client shutting down when
|
||||
// `Close` is called.
|
||||
// The first channel is closed to signal the backend goroutine that it has
|
||||
// to stop, then the second one is closed by the backend goroutine to signal
|
||||
// that it has finished flushing all queued messages.
|
||||
quit chan struct{}
|
||||
shutdown chan struct{}
|
||||
|
||||
// This HTTP client is used to send requests to the backend, it uses the
|
||||
// HTTP transport provided in the configuration.
|
||||
http http.Client
|
||||
}
|
||||
|
||||
// Instantiate a new client that uses the write key passed as first argument to
|
||||
// send messages to the backend.
|
||||
// The client is created with the default configuration.
|
||||
func New(writeKey string) Client {
|
||||
// Here we can ignore the error because the default config is always valid.
|
||||
c, _ := NewWithConfig(writeKey, Config{})
|
||||
return c
|
||||
}
|
||||
|
||||
// Instantiate a new client that uses the write key and configuration passed as
|
||||
// arguments to send messages to the backend.
|
||||
// The function will return an error if the configuration contained impossible
|
||||
// values (like a negative flush interval for example).
|
||||
// When the function returns an error the returned client will always be nil.
|
||||
func NewWithConfig(writeKey string, config Config) (cli Client, err error) {
|
||||
if err = config.validate(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c := &client{
|
||||
Config: makeConfig(config),
|
||||
key: writeKey,
|
||||
msgs: make(chan Message, 100),
|
||||
quit: make(chan struct{}),
|
||||
shutdown: make(chan struct{}),
|
||||
http: makeHttpClient(config.Transport),
|
||||
}
|
||||
|
||||
go c.loop()
|
||||
|
||||
cli = c
|
||||
return
|
||||
}
|
||||
|
||||
func makeHttpClient(transport http.RoundTripper) http.Client {
|
||||
httpClient := http.Client{
|
||||
Transport: transport,
|
||||
}
|
||||
if supportsTimeout(transport) {
|
||||
httpClient.Timeout = 10 * time.Second
|
||||
}
|
||||
return httpClient
|
||||
}
|
||||
|
||||
func dereferenceMessage(msg Message) Message {
|
||||
switch m := msg.(type) {
|
||||
case *Alias:
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
return *m
|
||||
case *Group:
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
return *m
|
||||
case *Identify:
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
return *m
|
||||
case *Page:
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
return *m
|
||||
case *Screen:
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
return *m
|
||||
case *Track:
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
return *m
|
||||
}
|
||||
|
||||
return msg
|
||||
}
|
||||
|
||||
func (c *client) Enqueue(msg Message) (err error) {
|
||||
msg = dereferenceMessage(msg)
|
||||
if err = msg.Validate(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var id = c.uid()
|
||||
var ts = c.now()
|
||||
|
||||
switch m := msg.(type) {
|
||||
case Alias:
|
||||
m.Type = "alias"
|
||||
m.MessageId = makeMessageId(m.MessageId, id)
|
||||
m.Timestamp = makeTimestamp(m.Timestamp, ts)
|
||||
msg = m
|
||||
|
||||
case Group:
|
||||
m.Type = "group"
|
||||
m.MessageId = makeMessageId(m.MessageId, id)
|
||||
m.Timestamp = makeTimestamp(m.Timestamp, ts)
|
||||
msg = m
|
||||
|
||||
case Identify:
|
||||
m.Type = "identify"
|
||||
m.MessageId = makeMessageId(m.MessageId, id)
|
||||
m.Timestamp = makeTimestamp(m.Timestamp, ts)
|
||||
msg = m
|
||||
|
||||
case Page:
|
||||
m.Type = "page"
|
||||
m.MessageId = makeMessageId(m.MessageId, id)
|
||||
m.Timestamp = makeTimestamp(m.Timestamp, ts)
|
||||
msg = m
|
||||
|
||||
case Screen:
|
||||
m.Type = "screen"
|
||||
m.MessageId = makeMessageId(m.MessageId, id)
|
||||
m.Timestamp = makeTimestamp(m.Timestamp, ts)
|
||||
msg = m
|
||||
|
||||
case Track:
|
||||
m.Type = "track"
|
||||
m.MessageId = makeMessageId(m.MessageId, id)
|
||||
m.Timestamp = makeTimestamp(m.Timestamp, ts)
|
||||
msg = m
|
||||
|
||||
default:
|
||||
err = fmt.Errorf("messages with custom types cannot be enqueued: %T", msg)
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
// When the `msgs` channel is closed writing to it will trigger a panic.
|
||||
// To avoid letting the panic propagate to the caller we recover from it
|
||||
// and instead report that the client has been closed and shouldn't be
|
||||
// used anymore.
|
||||
if recover() != nil {
|
||||
err = ErrClosed
|
||||
}
|
||||
}()
|
||||
|
||||
c.msgs <- msg
|
||||
return
|
||||
}
|
||||
|
||||
// Close and flush metrics.
|
||||
func (c *client) Close() (err error) {
|
||||
defer func() {
|
||||
// Always recover, a panic could be raised if `c`.quit was closed which
|
||||
// means the method was called more than once.
|
||||
if recover() != nil {
|
||||
err = ErrClosed
|
||||
}
|
||||
}()
|
||||
close(c.quit)
|
||||
<-c.shutdown
|
||||
return
|
||||
}
|
||||
|
||||
// Asychronously send a batched requests.
|
||||
func (c *client) sendAsync(msgs []message, wg *sync.WaitGroup, ex *executor) {
|
||||
wg.Add(1)
|
||||
|
||||
if !ex.do(func() {
|
||||
defer wg.Done()
|
||||
defer func() {
|
||||
// In case a bug is introduced in the send function that triggers
|
||||
// a panic, we don't want this to ever crash the application so we
|
||||
// catch it here and log it instead.
|
||||
if err := recover(); err != nil {
|
||||
c.errorf("panic - %s", err)
|
||||
}
|
||||
}()
|
||||
c.send(msgs)
|
||||
}) {
|
||||
wg.Done()
|
||||
c.errorf("sending messages failed - %s", ErrTooManyRequests)
|
||||
c.notifyFailure(msgs, ErrTooManyRequests)
|
||||
}
|
||||
}
|
||||
|
||||
// Send batch request.
|
||||
func (c *client) send(msgs []message) {
|
||||
const attempts = 10
|
||||
|
||||
b, err := json.Marshal(batch{
|
||||
MessageId: c.uid(),
|
||||
SentAt: c.now(),
|
||||
Messages: msgs,
|
||||
Context: c.DefaultContext,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
c.errorf("marshalling messages - %s", err)
|
||||
c.notifyFailure(msgs, err)
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i != attempts; i++ {
|
||||
if err = c.upload(b); err == nil {
|
||||
c.notifySuccess(msgs)
|
||||
return
|
||||
}
|
||||
|
||||
// Wait for either a retry timeout or the client to be closed.
|
||||
select {
|
||||
case <-time.After(c.RetryAfter(i)):
|
||||
case <-c.quit:
|
||||
c.errorf("%d messages dropped because they failed to be sent and the client was closed", len(msgs))
|
||||
c.notifyFailure(msgs, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.errorf("%d messages dropped because they failed to be sent after %d attempts", len(msgs), attempts)
|
||||
c.notifyFailure(msgs, err)
|
||||
}
|
||||
|
||||
// Upload serialized batch message.
|
||||
func (c *client) upload(b []byte) error {
|
||||
url := c.Endpoint + "/v1/batch"
|
||||
req, err := http.NewRequest("POST", url, bytes.NewReader(b))
|
||||
if err != nil {
|
||||
c.errorf("creating request - %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Add("User-Agent", "analytics-go (version: "+Version+")")
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
req.Header.Add("Content-Length", string(len(b)))
|
||||
req.SetBasicAuth(c.key, "")
|
||||
|
||||
res, err := c.http.Do(req)
|
||||
|
||||
if err != nil {
|
||||
c.errorf("sending request - %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
return c.report(res)
|
||||
}
|
||||
|
||||
// Report on response body.
|
||||
func (c *client) report(res *http.Response) (err error) {
|
||||
var body []byte
|
||||
|
||||
if res.StatusCode < 300 {
|
||||
c.debugf("response %s", res.Status)
|
||||
return
|
||||
}
|
||||
|
||||
if body, err = ioutil.ReadAll(res.Body); err != nil {
|
||||
c.errorf("response %d %s - %s", res.StatusCode, res.Status, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.logf("response %d %s – %s", res.StatusCode, res.Status, string(body))
|
||||
return fmt.Errorf("%d %s", res.StatusCode, res.Status)
|
||||
}
|
||||
|
||||
// Batch loop.
|
||||
func (c *client) loop() {
|
||||
defer close(c.shutdown)
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
defer wg.Wait()
|
||||
|
||||
tick := time.NewTicker(c.Interval)
|
||||
defer tick.Stop()
|
||||
|
||||
ex := newExecutor(c.maxConcurrentRequests)
|
||||
defer ex.close()
|
||||
|
||||
mq := messageQueue{
|
||||
maxBatchSize: c.BatchSize,
|
||||
maxBatchBytes: c.maxBatchBytes(),
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case msg := <-c.msgs:
|
||||
c.push(&mq, msg, wg, ex)
|
||||
|
||||
case <-tick.C:
|
||||
c.flush(&mq, wg, ex)
|
||||
|
||||
case <-c.quit:
|
||||
c.debugf("exit requested – draining messages")
|
||||
|
||||
// Drain the msg channel, we have to close it first so no more
|
||||
// messages can be pushed and otherwise the loop would never end.
|
||||
close(c.msgs)
|
||||
for msg := range c.msgs {
|
||||
c.push(&mq, msg, wg, ex)
|
||||
}
|
||||
|
||||
c.flush(&mq, wg, ex)
|
||||
c.debugf("exit")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) push(q *messageQueue, m Message, wg *sync.WaitGroup, ex *executor) {
|
||||
var msg message
|
||||
var err error
|
||||
|
||||
if msg, err = makeMessage(m, maxMessageBytes); err != nil {
|
||||
c.errorf("%s - %v", err, m)
|
||||
c.notifyFailure([]message{{m, nil}}, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.debugf("buffer (%d/%d) %v", len(q.pending), c.BatchSize, m)
|
||||
|
||||
if msgs := q.push(msg); msgs != nil {
|
||||
c.debugf("exceeded messages batch limit with batch of %d messages – flushing", len(msgs))
|
||||
c.sendAsync(msgs, wg, ex)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) flush(q *messageQueue, wg *sync.WaitGroup, ex *executor) {
|
||||
if msgs := q.flush(); msgs != nil {
|
||||
c.debugf("flushing %d messages", len(msgs))
|
||||
c.sendAsync(msgs, wg, ex)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) debugf(format string, args ...interface{}) {
|
||||
if c.Verbose {
|
||||
c.logf(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) logf(format string, args ...interface{}) {
|
||||
c.Logger.Logf(format, args...)
|
||||
}
|
||||
|
||||
func (c *client) errorf(format string, args ...interface{}) {
|
||||
c.Logger.Errorf(format, args...)
|
||||
}
|
||||
|
||||
func (c *client) maxBatchBytes() int {
|
||||
b, _ := json.Marshal(batch{
|
||||
MessageId: c.uid(),
|
||||
SentAt: c.now(),
|
||||
Context: c.DefaultContext,
|
||||
})
|
||||
return maxBatchBytes - len(b)
|
||||
}
|
||||
|
||||
func (c *client) notifySuccess(msgs []message) {
|
||||
if c.Callback != nil {
|
||||
for _, m := range msgs {
|
||||
c.Callback.Success(m.msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) notifyFailure(msgs []message, err error) {
|
||||
if c.Callback != nil {
|
||||
for _, m := range msgs {
|
||||
c.Callback.Failure(m.msg, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
173
vendor/github.com/segmentio/analytics-go/config.go
сгенерированный
поставляемый
173
vendor/github.com/segmentio/analytics-go/config.go
сгенерированный
поставляемый
@@ -1,173 +0,0 @@
|
||||
package analytics
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/segmentio/backo-go"
|
||||
"github.com/xtgo/uuid"
|
||||
)
|
||||
|
||||
// Instances of this type carry the different configuration options that may
|
||||
// be set when instantiating a client.
|
||||
//
|
||||
// Each field's zero-value is either meaningful or interpreted as using the
|
||||
// default value defined by the library.
|
||||
type Config struct {
|
||||
|
||||
// The endpoint to which the client connect and send their messages, set to
|
||||
// `DefaultEndpoint` by default.
|
||||
Endpoint string
|
||||
|
||||
// The flushing interval of the client. Messages will be sent when they've
|
||||
// been queued up to the maximum batch size or when the flushing interval
|
||||
// timer triggers.
|
||||
Interval time.Duration
|
||||
|
||||
// The HTTP transport used by the client, this allows an application to
|
||||
// redefine how requests are being sent at the HTTP level (for example,
|
||||
// to change the connection pooling policy).
|
||||
// If none is specified the client uses `http.DefaultTransport`.
|
||||
Transport http.RoundTripper
|
||||
|
||||
// The logger used by the client to output info or error messages when that
|
||||
// are generated by background operations.
|
||||
// If none is specified the client uses a standard logger that outputs to
|
||||
// `os.Stderr`.
|
||||
Logger Logger
|
||||
|
||||
// The callback object that will be used by the client to notify the
|
||||
// application when messages sends to the backend API succeeded or failed.
|
||||
Callback Callback
|
||||
|
||||
// The maximum number of messages that will be sent in one API call.
|
||||
// Messages will be sent when they've been queued up to the maximum batch
|
||||
// size or when the flushing interval timer triggers.
|
||||
// Note that the API will still enforce a 500KB limit on each HTTP request
|
||||
// which is independent from the number of embedded messages.
|
||||
BatchSize int
|
||||
|
||||
// When set to true the client will send more frequent and detailed messages
|
||||
// to its logger.
|
||||
Verbose bool
|
||||
|
||||
// The default context set on each message sent by the client.
|
||||
DefaultContext *Context
|
||||
|
||||
// The retry policy used by the client to resend requests that have failed.
|
||||
// The function is called with how many times the operation has been retried
|
||||
// and is expected to return how long the client should wait before trying
|
||||
// again.
|
||||
// If not set the client will fallback to use a default retry policy.
|
||||
RetryAfter func(int) time.Duration
|
||||
|
||||
// A function called by the client to generate unique message identifiers.
|
||||
// The client uses a UUID generator if none is provided.
|
||||
// This field is not exported and only exposed internally to let unit tests
|
||||
// mock the id generation.
|
||||
uid func() string
|
||||
|
||||
// A function called by the client to get the current time, `time.Now` is
|
||||
// used by default.
|
||||
// This field is not exported and only exposed internally to let unit tests
|
||||
// mock the current time.
|
||||
now func() time.Time
|
||||
|
||||
// The maximum number of goroutines that will be spawned by a client to send
|
||||
// requests to the backend API.
|
||||
// This field is not exported and only exposed internally to let unit tests
|
||||
// mock the current time.
|
||||
maxConcurrentRequests int
|
||||
}
|
||||
|
||||
// This constant sets the default endpoint to which client instances send
|
||||
// messages if none was explictly set.
|
||||
const DefaultEndpoint = "https://api.segment.io"
|
||||
|
||||
// This constant sets the default flush interval used by client instances if
|
||||
// none was explicitly set.
|
||||
const DefaultInterval = 5 * time.Second
|
||||
|
||||
// This constant sets the default batch size used by client instances if none
|
||||
// was explicitly set.
|
||||
const DefaultBatchSize = 250
|
||||
|
||||
// Verifies that fields that don't have zero-values are set to valid values,
|
||||
// returns an error describing the problem if a field was invalid.
|
||||
func (c *Config) validate() error {
|
||||
if c.Interval < 0 {
|
||||
return ConfigError{
|
||||
Reason: "negative time intervals are not supported",
|
||||
Field: "Interval",
|
||||
Value: c.Interval,
|
||||
}
|
||||
}
|
||||
|
||||
if c.BatchSize < 0 {
|
||||
return ConfigError{
|
||||
Reason: "negative batch sizes are not supported",
|
||||
Field: "BatchSize",
|
||||
Value: c.BatchSize,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Given a config object as argument the function will set all zero-values to
|
||||
// their defaults and return the modified object.
|
||||
func makeConfig(c Config) Config {
|
||||
if len(c.Endpoint) == 0 {
|
||||
c.Endpoint = DefaultEndpoint
|
||||
}
|
||||
|
||||
if c.Interval == 0 {
|
||||
c.Interval = DefaultInterval
|
||||
}
|
||||
|
||||
if c.Transport == nil {
|
||||
c.Transport = http.DefaultTransport
|
||||
}
|
||||
|
||||
if c.Logger == nil {
|
||||
c.Logger = newDefaultLogger()
|
||||
}
|
||||
|
||||
if c.BatchSize == 0 {
|
||||
c.BatchSize = DefaultBatchSize
|
||||
}
|
||||
|
||||
if c.DefaultContext == nil {
|
||||
c.DefaultContext = &Context{}
|
||||
}
|
||||
|
||||
if c.RetryAfter == nil {
|
||||
c.RetryAfter = backo.DefaultBacko().Duration
|
||||
}
|
||||
|
||||
if c.uid == nil {
|
||||
c.uid = uid
|
||||
}
|
||||
|
||||
if c.now == nil {
|
||||
c.now = time.Now
|
||||
}
|
||||
|
||||
if c.maxConcurrentRequests == 0 {
|
||||
c.maxConcurrentRequests = 1000
|
||||
}
|
||||
|
||||
// We always overwrite the 'library' field of the default context set on the
|
||||
// client because we want this information to be accurate.
|
||||
c.DefaultContext.Library = LibraryInfo{
|
||||
Name: "analytics-go",
|
||||
Version: Version,
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// This function returns a string representation of a UUID, it's the default
|
||||
// function used for generating unique IDs.
|
||||
func uid() string {
|
||||
return uuid.NewRandom().String()
|
||||
}
|
||||
148
vendor/github.com/segmentio/analytics-go/context.go
сгенерированный
поставляемый
148
vendor/github.com/segmentio/analytics-go/context.go
сгенерированный
поставляемый
@@ -1,148 +0,0 @@
|
||||
package analytics
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// This type provides the representation of the `context` object as defined in
|
||||
// https://segment.com/docs/spec/common/#context
|
||||
type Context struct {
|
||||
App AppInfo `json:"app,omitempty"`
|
||||
Campaign CampaignInfo `json:"campaign,omitempty"`
|
||||
Device DeviceInfo `json:"device,omitempty"`
|
||||
Library LibraryInfo `json:"library,omitempty"`
|
||||
Location LocationInfo `json:"location,omitempty"`
|
||||
Network NetworkInfo `json:"network,omitempty"`
|
||||
OS OSInfo `json:"os,omitempty"`
|
||||
Page PageInfo `json:"page,omitempty"`
|
||||
Referrer ReferrerInfo `json:"referrer,omitempty"`
|
||||
Screen ScreenInfo `json:"screen,omitempty"`
|
||||
IP net.IP `json:"ip,omitempty"`
|
||||
Locale string `json:"locale,omitempty"`
|
||||
Timezone string `json:"timezone,omitempty"`
|
||||
UserAgent string `json:"userAgent,omitempty"`
|
||||
Traits Traits `json:"traits,omitempty"`
|
||||
|
||||
// This map is used to allow extensions to the context specifications that
|
||||
// may not be documented or could be introduced in the future.
|
||||
// The fields of this map are inlined in the serialized context object,
|
||||
// there is no actual "extra" field in the JSON representation.
|
||||
Extra map[string]interface{} `json:"-"`
|
||||
}
|
||||
|
||||
// This type provides the representation of the `context.app` object as defined
|
||||
// in https://segment.com/docs/spec/common/#context
|
||||
type AppInfo struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Build string `json:"build,omitempty"`
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
}
|
||||
|
||||
// This type provides the representation of the `context.campaign` object as
|
||||
// defined in https://segment.com/docs/spec/common/#context
|
||||
type CampaignInfo struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Source string `json:"source,omitempty"`
|
||||
Medium string `json:"medium,omitempty"`
|
||||
Term string `json:"term,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
}
|
||||
|
||||
// This type provides the representation of the `context.device` object as
|
||||
// defined in https://segment.com/docs/spec/common/#context
|
||||
type DeviceInfo struct {
|
||||
Id string `json:"id,omitempty"`
|
||||
Manufacturer string `json:"manufacturer,omitempty"`
|
||||
Model string `json:"model,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
AdvertisingID string `json:"advertisingId,omitempty"`
|
||||
}
|
||||
|
||||
// This type provides the representation of the `context.library` object as
|
||||
// defined in https://segment.com/docs/spec/common/#context
|
||||
type LibraryInfo struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
// This type provides the representation of the `context.location` object as
|
||||
// defined in https://segment.com/docs/spec/common/#context
|
||||
type LocationInfo struct {
|
||||
City string `json:"city,omitempty"`
|
||||
Country string `json:"country,omitempty"`
|
||||
Region string `json:"region,omitempty"`
|
||||
Latitude float64 `json:"latitude,omitempty"`
|
||||
Longitude float64 `json:"longitude,omitempty"`
|
||||
Speed float64 `json:"speed,omitempty"`
|
||||
}
|
||||
|
||||
// This type provides the representation of the `context.network` object as
|
||||
// defined in https://segment.com/docs/spec/common/#context
|
||||
type NetworkInfo struct {
|
||||
Bluetooth bool `json:"bluetooth,omitempty"`
|
||||
Cellular bool `json:"cellular,omitempty"`
|
||||
WIFI bool `json:"wifi,omitempty"`
|
||||
Carrier string `json:"carrier,omitempty"`
|
||||
}
|
||||
|
||||
// This type provides the representation of the `context.os` object as defined
|
||||
// in https://segment.com/docs/spec/common/#context
|
||||
type OSInfo struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
// This type provides the representation of the `context.page` object as
|
||||
// defined in https://segment.com/docs/spec/common/#context
|
||||
type PageInfo struct {
|
||||
Hash string `json:"hash,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Referrer string `json:"referrer,omitempty"`
|
||||
Search string `json:"search,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// This type provides the representation of the `context.referrer` object as
|
||||
// defined in https://segment.com/docs/spec/common/#context
|
||||
type ReferrerInfo struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Link string `json:"link,omitempty"`
|
||||
}
|
||||
|
||||
// This type provides the representation of the `context.screen` object as
|
||||
// defined in https://segment.com/docs/spec/common/#context
|
||||
type ScreenInfo struct {
|
||||
Density int `json:"density,omitempty"`
|
||||
Width int `json:"width,omitempty"`
|
||||
Height int `json:"height,omitempty"`
|
||||
}
|
||||
|
||||
// Satisfy the `json.Marshaler` interface. We have to flatten out the `Extra`
|
||||
// field but the standard json package doesn't support it yet.
|
||||
// Implementing this interface allows us to override the default marshaling of
|
||||
// the context object and to the inlining ourselves.
|
||||
//
|
||||
// Related discussion: https://github.com/golang/go/issues/6213
|
||||
func (ctx Context) MarshalJSON() ([]byte, error) {
|
||||
v := reflect.ValueOf(ctx)
|
||||
n := v.NumField()
|
||||
m := make(map[string]interface{}, n+len(ctx.Extra))
|
||||
|
||||
// Copy the `Extra` map into the map representation of the context, it is
|
||||
// important to do this operation before going through the actual struct
|
||||
// fields so the latter take precendence and override duplicated values
|
||||
// that would be set in the extensions.
|
||||
for name, value := range ctx.Extra {
|
||||
m[name] = value
|
||||
}
|
||||
|
||||
return json.Marshal(structToMap(v, m))
|
||||
}
|
||||
60
vendor/github.com/segmentio/analytics-go/error.go
сгенерированный
поставляемый
60
vendor/github.com/segmentio/analytics-go/error.go
сгенерированный
поставляемый
@@ -1,60 +0,0 @@
|
||||
package analytics
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Returned by the `NewWithConfig` function when the one of the configuration
|
||||
// fields was set to an impossible value (like a negative duration).
|
||||
type ConfigError struct {
|
||||
|
||||
// A human-readable message explaining why the configuration field's value
|
||||
// is invalid.
|
||||
Reason string
|
||||
|
||||
// The name of the configuration field that was carrying an invalid value.
|
||||
Field string
|
||||
|
||||
// The value of the configuration field that caused the error.
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
func (e ConfigError) Error() string {
|
||||
return fmt.Sprintf("analytics.NewWithConfig: %s (analytics.Config.%s: %#v)", e.Reason, e.Field, e.Value)
|
||||
}
|
||||
|
||||
// Instances of this type are used to represent errors returned when a field was
|
||||
// no initialize properly in a structure passed as argument to one of the
|
||||
// functions of this package.
|
||||
type FieldError struct {
|
||||
|
||||
// The human-readable representation of the type of structure that wasn't
|
||||
// initialized properly.
|
||||
Type string
|
||||
|
||||
// The name of the field that wasn't properly initialized.
|
||||
Name string
|
||||
|
||||
// The value of the field that wasn't properly initialized.
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
func (e FieldError) Error() string {
|
||||
return fmt.Sprintf("%s.%s: invalid field value: %#v", e.Type, e.Name, e.Value)
|
||||
}
|
||||
|
||||
var (
|
||||
// This error is returned by methods of the `Client` interface when they are
|
||||
// called after the client was already closed.
|
||||
ErrClosed = errors.New("the client was already closed")
|
||||
|
||||
// This error is used to notify the application that too many requests are
|
||||
// already being sent and no more messages can be accepted.
|
||||
ErrTooManyRequests = errors.New("too many requests are already in-flight")
|
||||
|
||||
// This error is used to notify the client callbacks that a message send
|
||||
// failed because the JSON representation of a message exceeded the upper
|
||||
// limit.
|
||||
ErrMessageTooBig = errors.New("the message exceeds the maximum allowed size")
|
||||
)
|
||||
53
vendor/github.com/segmentio/analytics-go/executor.go
сгенерированный
поставляемый
53
vendor/github.com/segmentio/analytics-go/executor.go
сгенерированный
поставляемый
@@ -1,53 +0,0 @@
|
||||
package analytics
|
||||
|
||||
import "sync"
|
||||
|
||||
type executor struct {
|
||||
queue chan func()
|
||||
mutex sync.Mutex
|
||||
size int
|
||||
cap int
|
||||
}
|
||||
|
||||
func newExecutor(cap int) *executor {
|
||||
e := &executor{
|
||||
queue: make(chan func(), 1),
|
||||
cap: cap,
|
||||
}
|
||||
go e.loop()
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *executor) do(task func()) (ok bool) {
|
||||
e.mutex.Lock()
|
||||
|
||||
if e.size != e.cap {
|
||||
e.queue <- task
|
||||
e.size++
|
||||
ok = true
|
||||
}
|
||||
|
||||
e.mutex.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
func (e *executor) close() {
|
||||
close(e.queue)
|
||||
}
|
||||
|
||||
func (e *executor) loop() {
|
||||
for task := range e.queue {
|
||||
go e.run(task)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *executor) run(task func()) {
|
||||
defer e.done()
|
||||
task()
|
||||
}
|
||||
|
||||
func (e *executor) done() {
|
||||
e.mutex.Lock()
|
||||
e.size--
|
||||
e.mutex.Unlock()
|
||||
}
|
||||
46
vendor/github.com/segmentio/analytics-go/group.go
сгенерированный
поставляемый
46
vendor/github.com/segmentio/analytics-go/group.go
сгенерированный
поставляемый
@@ -1,46 +0,0 @@
|
||||
package analytics
|
||||
|
||||
import "time"
|
||||
|
||||
var _ Message = (*Group)(nil)
|
||||
|
||||
// This type represents object sent in a group call as described in
|
||||
// https://segment.com/docs/libraries/http/#group
|
||||
type Group struct {
|
||||
// This field is exported for serialization purposes and shouldn't be set by
|
||||
// the application, its value is always overwritten by the library.
|
||||
Type string `json:"type,omitempty"`
|
||||
|
||||
MessageId string `json:"messageId,omitempty"`
|
||||
AnonymousId string `json:"anonymousId,omitempty"`
|
||||
UserId string `json:"userId,omitempty"`
|
||||
GroupId string `json:"groupId"`
|
||||
Timestamp time.Time `json:"timestamp,omitempty"`
|
||||
Context *Context `json:"context,omitempty"`
|
||||
Traits Traits `json:"traits,omitempty"`
|
||||
Integrations Integrations `json:"integrations,omitempty"`
|
||||
}
|
||||
|
||||
func (msg Group) internal() {
|
||||
panic(unimplementedError)
|
||||
}
|
||||
|
||||
func (msg Group) Validate() error {
|
||||
if len(msg.GroupId) == 0 {
|
||||
return FieldError{
|
||||
Type: "analytics.Group",
|
||||
Name: "GroupId",
|
||||
Value: msg.GroupId,
|
||||
}
|
||||
}
|
||||
|
||||
if len(msg.UserId) == 0 && len(msg.AnonymousId) == 0 {
|
||||
return FieldError{
|
||||
Type: "analytics.Group",
|
||||
Name: "UserId",
|
||||
Value: msg.UserId,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
37
vendor/github.com/segmentio/analytics-go/identify.go
сгенерированный
поставляемый
37
vendor/github.com/segmentio/analytics-go/identify.go
сгенерированный
поставляемый
@@ -1,37 +0,0 @@
|
||||
package analytics
|
||||
|
||||
import "time"
|
||||
|
||||
var _ Message = (*Identify)(nil)
|
||||
|
||||
// This type represents object sent in an identify call as described in
|
||||
// https://segment.com/docs/libraries/http/#identify
|
||||
type Identify struct {
|
||||
// This field is exported for serialization purposes and shouldn't be set by
|
||||
// the application, its value is always overwritten by the library.
|
||||
Type string `json:"type,omitempty"`
|
||||
|
||||
MessageId string `json:"messageId,omitempty"`
|
||||
AnonymousId string `json:"anonymousId,omitempty"`
|
||||
UserId string `json:"userId,omitempty"`
|
||||
Timestamp time.Time `json:"timestamp,omitempty"`
|
||||
Context *Context `json:"context,omitempty"`
|
||||
Traits Traits `json:"traits,omitempty"`
|
||||
Integrations Integrations `json:"integrations,omitempty"`
|
||||
}
|
||||
|
||||
func (msg Identify) internal() {
|
||||
panic(unimplementedError)
|
||||
}
|
||||
|
||||
func (msg Identify) Validate() error {
|
||||
if len(msg.UserId) == 0 && len(msg.AnonymousId) == 0 {
|
||||
return FieldError{
|
||||
Type: "analytics.Identify",
|
||||
Name: "UserId",
|
||||
Value: msg.UserId,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
44
vendor/github.com/segmentio/analytics-go/integrations.go
сгенерированный
поставляемый
44
vendor/github.com/segmentio/analytics-go/integrations.go
сгенерированный
поставляемый
@@ -1,44 +0,0 @@
|
||||
package analytics
|
||||
|
||||
// This type is used to represent integrations in messages that support it.
|
||||
// It is a free-form where values are most often booleans that enable or
|
||||
// disable integrations.
|
||||
// Here's a quick example of how this type is meant to be used:
|
||||
//
|
||||
// analytics.Track{
|
||||
// UserId: "0123456789",
|
||||
// Integrations: analytics.NewIntegrations()
|
||||
// .EnableAll()
|
||||
// .Disable("Salesforce")
|
||||
// .Disable("Marketo"),
|
||||
// }
|
||||
//
|
||||
// The specifications can be found at https://segment.com/docs/spec/common/#integrations
|
||||
type Integrations map[string]interface{}
|
||||
|
||||
func NewIntegrations() Integrations {
|
||||
return make(Integrations, 10)
|
||||
}
|
||||
|
||||
func (i Integrations) EnableAll() Integrations {
|
||||
return i.Enable("all")
|
||||
}
|
||||
|
||||
func (i Integrations) DisableAll() Integrations {
|
||||
return i.Disable("all")
|
||||
}
|
||||
|
||||
func (i Integrations) Enable(name string) Integrations {
|
||||
return i.Set(name, true)
|
||||
}
|
||||
|
||||
func (i Integrations) Disable(name string) Integrations {
|
||||
return i.Set(name, false)
|
||||
}
|
||||
|
||||
// Sets an integration named by the first argument to the specified value, any
|
||||
// value other than `false` will be interpreted as enabling the integration.
|
||||
func (i Integrations) Set(name string, value interface{}) Integrations {
|
||||
i[name] = value
|
||||
return i
|
||||
}
|
||||
87
vendor/github.com/segmentio/analytics-go/json.go
сгенерированный
поставляемый
87
vendor/github.com/segmentio/analytics-go/json.go
сгенерированный
поставляемый
@@ -1,87 +0,0 @@
|
||||
package analytics
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Imitate what what the JSON package would do when serializing a struct value,
|
||||
// the only difference is we we don't serialize zero-value struct fields as well.
|
||||
// Note that this function doesn't recursively convert structures to maps, only
|
||||
// the value passed as argument is transformed.
|
||||
func structToMap(v reflect.Value, m map[string]interface{}) map[string]interface{} {
|
||||
t := v.Type()
|
||||
n := t.NumField()
|
||||
|
||||
if m == nil {
|
||||
m = make(map[string]interface{}, n)
|
||||
}
|
||||
|
||||
for i := 0; i != n; i++ {
|
||||
field := t.Field(i)
|
||||
value := v.Field(i)
|
||||
name, omitempty := parseJsonTag(field.Tag.Get("json"), field.Name)
|
||||
|
||||
if name != "-" && !(omitempty && isZeroValue(value)) {
|
||||
m[name] = value.Interface()
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// Parses a JSON tag the way the json package would do it, returing the expected
|
||||
// name of the field once serialized and if empty values should be omitted.
|
||||
func parseJsonTag(tag string, defName string) (name string, omitempty bool) {
|
||||
args := strings.Split(tag, ",")
|
||||
|
||||
if len(args) == 0 || len(args[0]) == 0 {
|
||||
name = defName
|
||||
} else {
|
||||
name = args[0]
|
||||
}
|
||||
|
||||
if len(args) > 1 && args[1] == "omitempty" {
|
||||
omitempty = true
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Checks if the value given as argument is a zero-value, it is based on the
|
||||
// isEmptyValue function in https://golang.org/src/encoding/json/encode.go
|
||||
// but also checks struct types recursively.
|
||||
func isZeroValue(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||
return v.Len() == 0
|
||||
|
||||
case reflect.Bool:
|
||||
return !v.Bool()
|
||||
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return v.Int() == 0
|
||||
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return v.Uint() == 0
|
||||
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float() == 0
|
||||
|
||||
case reflect.Interface, reflect.Ptr:
|
||||
return v.IsNil()
|
||||
|
||||
case reflect.Struct:
|
||||
for i, n := 0, v.NumField(); i != n; i++ {
|
||||
if !isZeroValue(v.Field(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
case reflect.Invalid:
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
47
vendor/github.com/segmentio/analytics-go/logger.go
сгенерированный
поставляемый
47
vendor/github.com/segmentio/analytics-go/logger.go
сгенерированный
поставляемый
@@ -1,47 +0,0 @@
|
||||
package analytics
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Instances of types implementing this interface can be used to define where
|
||||
// the analytics client logs are written.
|
||||
type Logger interface {
|
||||
|
||||
// Analytics clients call this method to log regular messages about the
|
||||
// operations they perform.
|
||||
// Messages logged by this method are usually tagged with an `INFO` log
|
||||
// level in common logging libraries.
|
||||
Logf(format string, args ...interface{})
|
||||
|
||||
// Analytics clients call this method to log errors they encounter while
|
||||
// sending events to the backend servers.
|
||||
// Messages logged by this method are usually tagged with an `ERROR` log
|
||||
// level in common logging libraries.
|
||||
Errorf(format string, args ...interface{})
|
||||
}
|
||||
|
||||
// This function instantiate an object that statisfies the analytics.Logger
|
||||
// interface and send logs to standard logger passed as argument.
|
||||
func StdLogger(logger *log.Logger) Logger {
|
||||
return stdLogger{
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
type stdLogger struct {
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
func (l stdLogger) Logf(format string, args ...interface{}) {
|
||||
l.logger.Printf("INFO: "+format, args...)
|
||||
}
|
||||
|
||||
func (l stdLogger) Errorf(format string, args ...interface{}) {
|
||||
l.logger.Printf("ERROR: "+format, args...)
|
||||
}
|
||||
|
||||
func newDefaultLogger() Logger {
|
||||
return StdLogger(log.New(os.Stderr, "segment ", log.LstdFlags))
|
||||
}
|
||||
131
vendor/github.com/segmentio/analytics-go/message.go
сгенерированный
поставляемый
131
vendor/github.com/segmentio/analytics-go/message.go
сгенерированный
поставляемый
@@ -1,131 +0,0 @@
|
||||
package analytics
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Values implementing this interface are used by analytics clients to notify
|
||||
// the application when a message send succeeded or failed.
|
||||
//
|
||||
// Callback methods are called by a client's internal goroutines, there are no
|
||||
// guarantees on which goroutine will trigger the callbacks, the calls can be
|
||||
// made sequentially or in parallel, the order doesn't depend on the order of
|
||||
// messages were queued to the client.
|
||||
//
|
||||
// Callback methods must return quickly and not cause long blocking operations
|
||||
// to avoid interferring with the client's internal work flow.
|
||||
type Callback interface {
|
||||
|
||||
// This method is called for every message that was successfully sent to
|
||||
// the API.
|
||||
Success(Message)
|
||||
|
||||
// This method is called for every message that failed to be sent to the
|
||||
// API and will be discarded by the client.
|
||||
Failure(Message, error)
|
||||
}
|
||||
|
||||
// This interface is used to represent analytics objects that can be sent via
|
||||
// a client.
|
||||
//
|
||||
// Types like analytics.Track, analytics.Page, etc... implement this interface
|
||||
// and therefore can be passed to the analytics.Client.Send method.
|
||||
type Message interface {
|
||||
|
||||
// Validate validates the internal structure of the message, the method must return
|
||||
// nil if the message is valid, or an error describing what went wrong.
|
||||
Validate() error
|
||||
|
||||
// internal is an unexposed interface function to ensure only types defined within this package can satisfy the Message interface. Invoking this method will panic.
|
||||
internal()
|
||||
}
|
||||
|
||||
// Takes a message id as first argument and returns it, unless it's the zero-
|
||||
// value, in that case the default id passed as second argument is returned.
|
||||
func makeMessageId(id string, def string) string {
|
||||
if len(id) == 0 {
|
||||
return def
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// Returns the time value passed as first argument, unless it's the zero-value,
|
||||
// in that case the default value passed as second argument is returned.
|
||||
func makeTimestamp(t time.Time, def time.Time) time.Time {
|
||||
if t == (time.Time{}) {
|
||||
return def
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// This structure represents objects sent to the /v1/batch endpoint. We don't
|
||||
// export this type because it's only meant to be used internally to send groups
|
||||
// of messages in one API call.
|
||||
type batch struct {
|
||||
MessageId string `json:"messageId"`
|
||||
SentAt time.Time `json:"sentAt"`
|
||||
Messages []message `json:"batch"`
|
||||
Context *Context `json:"context"`
|
||||
}
|
||||
|
||||
type message struct {
|
||||
msg Message
|
||||
json []byte
|
||||
}
|
||||
|
||||
func makeMessage(m Message, maxBytes int) (msg message, err error) {
|
||||
if msg.json, err = json.Marshal(m); err == nil {
|
||||
if len(msg.json) > maxBytes {
|
||||
err = ErrMessageTooBig
|
||||
} else {
|
||||
msg.msg = m
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m message) MarshalJSON() ([]byte, error) {
|
||||
return m.json, nil
|
||||
}
|
||||
|
||||
func (m message) size() int {
|
||||
// The `+ 1` is for the comma that sits between each items of a JSON array.
|
||||
return len(m.json) + 1
|
||||
}
|
||||
|
||||
type messageQueue struct {
|
||||
pending []message
|
||||
bytes int
|
||||
maxBatchSize int
|
||||
maxBatchBytes int
|
||||
}
|
||||
|
||||
func (q *messageQueue) push(m message) (b []message) {
|
||||
if (q.bytes + m.size()) > q.maxBatchBytes {
|
||||
b = q.flush()
|
||||
}
|
||||
|
||||
if q.pending == nil {
|
||||
q.pending = make([]message, 0, q.maxBatchSize)
|
||||
}
|
||||
|
||||
q.pending = append(q.pending, m)
|
||||
q.bytes += len(m.json)
|
||||
|
||||
if b == nil && len(q.pending) == q.maxBatchSize {
|
||||
b = q.flush()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (q *messageQueue) flush() (msgs []message) {
|
||||
msgs, q.pending, q.bytes = q.pending, nil, 0
|
||||
return
|
||||
}
|
||||
|
||||
const (
|
||||
maxBatchBytes = 500000
|
||||
maxMessageBytes = 32000
|
||||
)
|
||||
38
vendor/github.com/segmentio/analytics-go/page.go
сгенерированный
поставляемый
38
vendor/github.com/segmentio/analytics-go/page.go
сгенерированный
поставляемый
@@ -1,38 +0,0 @@
|
||||
package analytics
|
||||
|
||||
import "time"
|
||||
|
||||
var _ Message = (*Page)(nil)
|
||||
|
||||
// This type represents object sent in a page call as described in
|
||||
// https://segment.com/docs/libraries/http/#page
|
||||
type Page struct {
|
||||
// This field is exported for serialization purposes and shouldn't be set by
|
||||
// the application, its value is always overwritten by the library.
|
||||
Type string `json:"type,omitempty"`
|
||||
|
||||
MessageId string `json:"messageId,omitempty"`
|
||||
AnonymousId string `json:"anonymousId,omitempty"`
|
||||
UserId string `json:"userId,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Timestamp time.Time `json:"timestamp,omitempty"`
|
||||
Context *Context `json:"context,omitempty"`
|
||||
Properties Properties `json:"properties,omitempty"`
|
||||
Integrations Integrations `json:"integrations,omitempty"`
|
||||
}
|
||||
|
||||
func (msg Page) internal() {
|
||||
panic(unimplementedError)
|
||||
}
|
||||
|
||||
func (msg Page) Validate() error {
|
||||
if len(msg.UserId) == 0 && len(msg.AnonymousId) == 0 {
|
||||
return FieldError{
|
||||
Type: "analytics.Page",
|
||||
Name: "UserId",
|
||||
Value: msg.UserId,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
117
vendor/github.com/segmentio/analytics-go/properties.go
сгенерированный
поставляемый
117
vendor/github.com/segmentio/analytics-go/properties.go
сгенерированный
поставляемый
@@ -1,117 +0,0 @@
|
||||
package analytics
|
||||
|
||||
// This type is used to represent properties in messages that support it.
|
||||
// It is a free-form object so the application can set any value it sees fit but
|
||||
// a few helper method are defined to make it easier to instantiate properties with
|
||||
// common fields.
|
||||
// Here's a quick example of how this type is meant to be used:
|
||||
//
|
||||
// analytics.Page{
|
||||
// UserId: "0123456789",
|
||||
// Properties: analytics.NewProperties()
|
||||
// .SetRevenue(10.0)
|
||||
// .SetCurrency("USD"),
|
||||
// }
|
||||
//
|
||||
type Properties map[string]interface{}
|
||||
|
||||
func NewProperties() Properties {
|
||||
return make(Properties, 10)
|
||||
}
|
||||
|
||||
func (p Properties) SetRevenue(revenue float64) Properties {
|
||||
return p.Set("revenue", revenue)
|
||||
}
|
||||
|
||||
func (p Properties) SetCurrency(currency string) Properties {
|
||||
return p.Set("currency", currency)
|
||||
}
|
||||
|
||||
func (p Properties) SetValue(value float64) Properties {
|
||||
return p.Set("value", value)
|
||||
}
|
||||
|
||||
func (p Properties) SetPath(path string) Properties {
|
||||
return p.Set("path", path)
|
||||
}
|
||||
|
||||
func (p Properties) SetReferrer(referrer string) Properties {
|
||||
return p.Set("referrer", referrer)
|
||||
}
|
||||
|
||||
func (p Properties) SetTitle(title string) Properties {
|
||||
return p.Set("title", title)
|
||||
}
|
||||
|
||||
func (p Properties) SetURL(url string) Properties {
|
||||
return p.Set("url", url)
|
||||
}
|
||||
|
||||
func (p Properties) SetName(name string) Properties {
|
||||
return p.Set("name", name)
|
||||
}
|
||||
|
||||
func (p Properties) SetCategory(category string) Properties {
|
||||
return p.Set("category", category)
|
||||
}
|
||||
|
||||
func (p Properties) SetSKU(sku string) Properties {
|
||||
return p.Set("sku", sku)
|
||||
}
|
||||
|
||||
func (p Properties) SetPrice(price float64) Properties {
|
||||
return p.Set("price", price)
|
||||
}
|
||||
|
||||
func (p Properties) SetProductId(id string) Properties {
|
||||
return p.Set("id", id)
|
||||
}
|
||||
|
||||
func (p Properties) SetOrderId(id string) Properties {
|
||||
return p.Set("orderId", id)
|
||||
}
|
||||
|
||||
func (p Properties) SetTotal(total float64) Properties {
|
||||
return p.Set("total", total)
|
||||
}
|
||||
|
||||
func (p Properties) SetSubtotal(subtotal float64) Properties {
|
||||
return p.Set("subtotal", subtotal)
|
||||
}
|
||||
|
||||
func (p Properties) SetShipping(shipping float64) Properties {
|
||||
return p.Set("shipping", shipping)
|
||||
}
|
||||
|
||||
func (p Properties) SetTax(tax float64) Properties {
|
||||
return p.Set("tax", tax)
|
||||
}
|
||||
|
||||
func (p Properties) SetDiscount(discount float64) Properties {
|
||||
return p.Set("discount", discount)
|
||||
}
|
||||
|
||||
func (p Properties) SetCoupon(coupon string) Properties {
|
||||
return p.Set("coupon", coupon)
|
||||
}
|
||||
|
||||
func (p Properties) SetProducts(products ...Product) Properties {
|
||||
return p.Set("products", products)
|
||||
}
|
||||
|
||||
func (p Properties) SetRepeat(repeat bool) Properties {
|
||||
return p.Set("repeat", repeat)
|
||||
}
|
||||
|
||||
func (p Properties) Set(name string, value interface{}) Properties {
|
||||
p[name] = value
|
||||
return p
|
||||
}
|
||||
|
||||
// This type represents products in the E-commerce API.
|
||||
type Product struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
SKU string `json:"sky,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Price float64 `json:"price"`
|
||||
}
|
||||
38
vendor/github.com/segmentio/analytics-go/screen.go
сгенерированный
поставляемый
38
vendor/github.com/segmentio/analytics-go/screen.go
сгенерированный
поставляемый
@@ -1,38 +0,0 @@
|
||||
package analytics
|
||||
|
||||
import "time"
|
||||
|
||||
var _ Message = (*Screen)(nil)
|
||||
|
||||
// This type represents object sent in a screen call as described in
|
||||
// https://segment.com/docs/libraries/http/#screen
|
||||
type Screen struct {
|
||||
// This field is exported for serialization purposes and shouldn't be set by
|
||||
// the application, its value is always overwritten by the library.
|
||||
Type string `json:"type,omitempty"`
|
||||
|
||||
MessageId string `json:"messageId,omitempty"`
|
||||
AnonymousId string `json:"anonymousId,omitempty"`
|
||||
UserId string `json:"userId,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Timestamp time.Time `json:"timestamp,omitempty"`
|
||||
Context *Context `json:"context,omitempty"`
|
||||
Properties Properties `json:"properties,omitempty"`
|
||||
Integrations Integrations `json:"integrations,omitempty"`
|
||||
}
|
||||
|
||||
func (msg Screen) internal() {
|
||||
panic(unimplementedError)
|
||||
}
|
||||
|
||||
func (msg Screen) Validate() error {
|
||||
if len(msg.UserId) == 0 && len(msg.AnonymousId) == 0 {
|
||||
return FieldError{
|
||||
Type: "analytics.Screen",
|
||||
Name: "UserId",
|
||||
Value: msg.UserId,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
16
vendor/github.com/segmentio/analytics-go/timeout_15.go
сгенерированный
поставляемый
16
vendor/github.com/segmentio/analytics-go/timeout_15.go
сгенерированный
поставляемый
@@ -1,16 +0,0 @@
|
||||
// +build !go1.6
|
||||
|
||||
package analytics
|
||||
|
||||
import "net/http"
|
||||
|
||||
// http clients on versions of go before 1.6 only support timeout if the
|
||||
// transport implements the `CancelRequest` method.
|
||||
func supportsTimeout(transport http.RoundTripper) bool {
|
||||
_, ok := transport.(requestCanceler)
|
||||
return ok
|
||||
}
|
||||
|
||||
type requestCanceler interface {
|
||||
CancelRequest(*http.Request)
|
||||
}
|
||||
10
vendor/github.com/segmentio/analytics-go/timeout_16.go
сгенерированный
поставляемый
10
vendor/github.com/segmentio/analytics-go/timeout_16.go
сгенерированный
поставляемый
@@ -1,10 +0,0 @@
|
||||
// +build go1.6
|
||||
|
||||
package analytics
|
||||
|
||||
import "net/http"
|
||||
|
||||
// http clients on versions of go after 1.6 always support timeout.
|
||||
func supportsTimeout(transport http.RoundTripper) bool {
|
||||
return true
|
||||
}
|
||||
46
vendor/github.com/segmentio/analytics-go/track.go
сгенерированный
поставляемый
46
vendor/github.com/segmentio/analytics-go/track.go
сгенерированный
поставляемый
@@ -1,46 +0,0 @@
|
||||
package analytics
|
||||
|
||||
import "time"
|
||||
|
||||
var _ Message = (*Track)(nil)
|
||||
|
||||
// This type represents object sent in a track call as described in
|
||||
// https://segment.com/docs/libraries/http/#track
|
||||
type Track struct {
|
||||
// This field is exported for serialization purposes and shouldn't be set by
|
||||
// the application, its value is always overwritten by the library.
|
||||
Type string `json:"type,omitempty"`
|
||||
|
||||
MessageId string `json:"messageId,omitempty"`
|
||||
AnonymousId string `json:"anonymousId,omitempty"`
|
||||
UserId string `json:"userId,omitempty"`
|
||||
Event string `json:"event"`
|
||||
Timestamp time.Time `json:"timestamp,omitempty"`
|
||||
Context *Context `json:"context,omitempty"`
|
||||
Properties Properties `json:"properties,omitempty"`
|
||||
Integrations Integrations `json:"integrations,omitempty"`
|
||||
}
|
||||
|
||||
func (msg Track) internal() {
|
||||
panic(unimplementedError)
|
||||
}
|
||||
|
||||
func (msg Track) Validate() error {
|
||||
if len(msg.Event) == 0 {
|
||||
return FieldError{
|
||||
Type: "analytics.Track",
|
||||
Name: "Event",
|
||||
Value: msg.Event,
|
||||
}
|
||||
}
|
||||
|
||||
if len(msg.UserId) == 0 && len(msg.AnonymousId) == 0 {
|
||||
return FieldError{
|
||||
Type: "analytics.Track",
|
||||
Name: "UserId",
|
||||
Value: msg.UserId,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
89
vendor/github.com/segmentio/analytics-go/traits.go
сгенерированный
поставляемый
89
vendor/github.com/segmentio/analytics-go/traits.go
сгенерированный
поставляемый
@@ -1,89 +0,0 @@
|
||||
package analytics
|
||||
|
||||
import "time"
|
||||
|
||||
// This type is used to represent traits in messages that support it.
|
||||
// It is a free-form object so the application can set any value it sees fit but
|
||||
// a few helper method are defined to make it easier to instantiate traits with
|
||||
// common fields.
|
||||
// Here's a quick example of how this type is meant to be used:
|
||||
//
|
||||
// analytics.Identify{
|
||||
// UserId: "0123456789",
|
||||
// Traits: analytics.NewTraits()
|
||||
// .SetFirstName("Luke")
|
||||
// .SetLastName("Skywalker")
|
||||
// .Set("Role", "Jedi"),
|
||||
// }
|
||||
//
|
||||
// The specifications can be found at https://segment.com/docs/spec/identify/#traits
|
||||
type Traits map[string]interface{}
|
||||
|
||||
func NewTraits() Traits {
|
||||
return make(Traits, 10)
|
||||
}
|
||||
|
||||
func (t Traits) SetAddress(address string) Traits {
|
||||
return t.Set("address", address)
|
||||
}
|
||||
|
||||
func (t Traits) SetAge(age int) Traits {
|
||||
return t.Set("age", age)
|
||||
}
|
||||
|
||||
func (t Traits) SetAvatar(url string) Traits {
|
||||
return t.Set("avatar", url)
|
||||
}
|
||||
|
||||
func (t Traits) SetBirthday(date time.Time) Traits {
|
||||
return t.Set("birthday", date)
|
||||
}
|
||||
|
||||
func (t Traits) SetCreatedAt(date time.Time) Traits {
|
||||
return t.Set("createdAt", date)
|
||||
}
|
||||
|
||||
func (t Traits) SetDescription(desc string) Traits {
|
||||
return t.Set("description", desc)
|
||||
}
|
||||
|
||||
func (t Traits) SetEmail(email string) Traits {
|
||||
return t.Set("email", email)
|
||||
}
|
||||
|
||||
func (t Traits) SetFirstName(firstName string) Traits {
|
||||
return t.Set("firstName", firstName)
|
||||
}
|
||||
|
||||
func (t Traits) SetGender(gender string) Traits {
|
||||
return t.Set("gender", gender)
|
||||
}
|
||||
|
||||
func (t Traits) SetLastName(lastName string) Traits {
|
||||
return t.Set("lastName", lastName)
|
||||
}
|
||||
|
||||
func (t Traits) SetName(name string) Traits {
|
||||
return t.Set("name", name)
|
||||
}
|
||||
|
||||
func (t Traits) SetPhone(phone string) Traits {
|
||||
return t.Set("phone", phone)
|
||||
}
|
||||
|
||||
func (t Traits) SetTitle(title string) Traits {
|
||||
return t.Set("title", title)
|
||||
}
|
||||
|
||||
func (t Traits) SetUsername(username string) Traits {
|
||||
return t.Set("username", username)
|
||||
}
|
||||
|
||||
func (t Traits) SetWebsite(url string) Traits {
|
||||
return t.Set("website", url)
|
||||
}
|
||||
|
||||
func (t Traits) Set(field string, value interface{}) Traits {
|
||||
t[field] = value
|
||||
return t
|
||||
}
|
||||
3
vendor/modules.txt
поставляемый
3
vendor/modules.txt
поставляемый
@@ -403,9 +403,6 @@ github.com/rwcarlsen/goexif/exif
|
||||
github.com/rwcarlsen/goexif/tiff
|
||||
# github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529
|
||||
github.com/sean-/seed
|
||||
# github.com/segmentio/analytics-go v3.1.0+incompatible
|
||||
## explicit
|
||||
github.com/segmentio/analytics-go
|
||||
# github.com/segmentio/backo-go v0.0.0-20200129164019-23eae7c10bd3
|
||||
## explicit
|
||||
github.com/segmentio/backo-go
|
||||
|
||||
@@ -167,7 +167,7 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("X-Frame-Options", "SAMEORIGIN")
|
||||
// Set content security policy. This is also specified in the root.html of the webapp in a meta tag.
|
||||
w.Header().Set("Content-Security-Policy", fmt.Sprintf(
|
||||
"frame-ancestors 'self'; script-src 'self' cdn.rudderlabs.com cdn.segment.com/analytics.js/%s",
|
||||
"frame-ancestors 'self'; script-src 'self' cdn.rudderlabs.com%s",
|
||||
h.cspShaDirective,
|
||||
))
|
||||
} else {
|
||||
|
||||
@@ -298,7 +298,7 @@ func TestHandlerServeCSPHeader(t *testing.T) {
|
||||
response := httptest.NewRecorder()
|
||||
handler.ServeHTTP(response, request)
|
||||
assert.Equal(t, 200, response.Code)
|
||||
assert.Equal(t, response.Header()["Content-Security-Policy"], []string{"frame-ancestors 'self'; script-src 'self' cdn.rudderlabs.com cdn.segment.com/analytics.js/"})
|
||||
assert.Equal(t, response.Header()["Content-Security-Policy"], []string{"frame-ancestors 'self'; script-src 'self' cdn.rudderlabs.com"})
|
||||
})
|
||||
|
||||
t.Run("static, with subpath", func(t *testing.T) {
|
||||
@@ -337,12 +337,12 @@ func TestHandlerServeCSPHeader(t *testing.T) {
|
||||
response := httptest.NewRecorder()
|
||||
handler.ServeHTTP(response, request)
|
||||
assert.Equal(t, 200, response.Code)
|
||||
assert.Equal(t, response.Header()["Content-Security-Policy"], []string{"frame-ancestors 'self'; script-src 'self' cdn.rudderlabs.com cdn.segment.com/analytics.js/"})
|
||||
assert.Equal(t, response.Header()["Content-Security-Policy"], []string{"frame-ancestors 'self'; script-src 'self' cdn.rudderlabs.com"})
|
||||
|
||||
// TODO: It's hard to unit test this now that the CSP directive is effectively
|
||||
// decided in Setup(). Circle back to this in master once the memory store is
|
||||
// merged, allowing us to mock the desired initial config to take effect in Setup().
|
||||
// assert.Contains(t, response.Header()["Content-Security-Policy"], "frame-ancestors 'self'; script-src 'self' cdn.segment.com/analytics.js/ 'sha256-tPOjw+tkVs9axL78ZwGtYl975dtyPHB6LYKAO2R3gR4='")
|
||||
// assert.Contains(t, response.Header()["Content-Security-Policy"], "frame-ancestors 'self'; script-src 'self' cdn.rudderlabs.com 'sha256-tPOjw+tkVs9axL78ZwGtYl975dtyPHB6LYKAO2R3gR4='")
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.SiteURL = *cfg.ServiceSettings.SiteURL + "/subpath2"
|
||||
@@ -352,9 +352,9 @@ func TestHandlerServeCSPHeader(t *testing.T) {
|
||||
response = httptest.NewRecorder()
|
||||
handler.ServeHTTP(response, request)
|
||||
assert.Equal(t, 200, response.Code)
|
||||
assert.Equal(t, response.Header()["Content-Security-Policy"], []string{"frame-ancestors 'self'; script-src 'self' cdn.rudderlabs.com cdn.segment.com/analytics.js/"})
|
||||
assert.Equal(t, response.Header()["Content-Security-Policy"], []string{"frame-ancestors 'self'; script-src 'self' cdn.rudderlabs.com"})
|
||||
// TODO: See above.
|
||||
// assert.Contains(t, response.Header()["Content-Security-Policy"], "frame-ancestors 'self'; script-src 'self' cdn.segment.com/analytics.js/ 'sha256-tPOjw+tkVs9axL78ZwGtYl975dtyPHB6LYKAO2R3gR4='", "csp header incorrectly changed after subpath changed")
|
||||
// assert.Contains(t, response.Header()["Content-Security-Policy"], "frame-ancestors 'self'; script-src 'self' cdn.rudderlabs.com 'sha256-tPOjw+tkVs9axL78ZwGtYl975dtyPHB6LYKAO2R3gR4='", "csp header incorrectly changed after subpath changed")
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Ссылка в новой задаче
Block a user