diff --git a/.github/workflows/api-ee.yaml b/.github/workflows/api-ee.yaml index e77a80565..e41779634 100644 --- a/.github/workflows/api-ee.yaml +++ b/.github/workflows/api-ee.yaml @@ -43,7 +43,7 @@ jobs: id: build-image env: DOCKER_REPO: ${{ secrets.EE_REGISTRY_URL }} - IMAGE_TAG: ${{ github.sha }}-ee + IMAGE_TAG: ${{ github.ref_name }}_${{ github.sha }}-ee ENVIRONMENT: staging run: | cd api @@ -91,7 +91,7 @@ jobs: env: DOCKER_REPO: ${{ secrets.EE_REGISTRY_URL }} # We're not passing -ee flag, because helm will add that. - IMAGE_TAG: ${{ github.sha }} + IMAGE_TAG: ${{ github.ref_name }}_${{ github.sha }} ENVIRONMENT: staging # - name: Debug Job diff --git a/.github/workflows/api.yaml b/.github/workflows/api.yaml index 2f11de7e4..ee49ded09 100644 --- a/.github/workflows/api.yaml +++ b/.github/workflows/api.yaml @@ -42,7 +42,7 @@ jobs: id: build-image env: DOCKER_REPO: ${{ secrets.OSS_REGISTRY_URL }} - IMAGE_TAG: ${{ github.sha }} + IMAGE_TAG: ${{ github.ref_name }}_${{ github.sha }} ENVIRONMENT: staging run: | cd api @@ -90,7 +90,7 @@ jobs: helm template openreplay -n app openreplay -f vars.yaml -f /tmp/image_override.yaml --set ingress-nginx.enabled=false --set skipMigration=true --no-hooks | kubectl apply -n app -f - env: DOCKER_REPO: ${{ secrets.OSS_REGISTRY_URL }} - IMAGE_TAG: ${{ github.sha }} + IMAGE_TAG: ${{ github.ref_name }}_${{ github.sha }} ENVIRONMENT: staging # - name: Debug Job diff --git a/.github/workflows/frontend.yaml b/.github/workflows/frontend.yaml index 4b2d9cd93..2f2fd3989 100644 --- a/.github/workflows/frontend.yaml +++ b/.github/workflows/frontend.yaml @@ -41,7 +41,7 @@ jobs: id: build-image env: DOCKER_REPO: ${{ secrets.OSS_REGISTRY_URL }} - IMAGE_TAG: ${{ github.sha }} + IMAGE_TAG: ${{ github.ref_name }}_${{ github.sha }} ENVIRONMENT: staging run: | set -x @@ -84,7 +84,7 @@ jobs: helm template openreplay -n app openreplay -f vars.yaml -f /tmp/image_override.yaml --set ingress-nginx.enabled=false --set skipMigration=true --no-hooks | kubectl apply -n app -f - env: DOCKER_REPO: ${{ secrets.OSS_REGISTRY_URL }} - IMAGE_TAG: ${{ github.sha }} + IMAGE_TAG: ${{ github.ref_name }}_${{ github.sha }} ENVIRONMENT: staging @@ -130,7 +130,7 @@ jobs: env: DOCKER_REPO: ${{ secrets.EE_REGISTRY_URL }} # We're not passing -ee flag, because helm will add that. - IMAGE_TAG: ${{ github.sha }} + IMAGE_TAG: ${{ github.ref_name }}_${{ github.sha }} ENVIRONMENT: staging # - name: Debug Job diff --git a/.github/workflows/utilities.yaml b/.github/workflows/utilities.yaml index 8a1e5b457..92e130c84 100644 --- a/.github/workflows/utilities.yaml +++ b/.github/workflows/utilities.yaml @@ -36,7 +36,7 @@ jobs: id: build-image env: DOCKER_REPO: ${{ secrets.OSS_REGISTRY_URL }} - IMAGE_TAG: ${{ github.sha }} + IMAGE_TAG: ${{ github.ref_name }}_${{ github.sha }} ENVIRONMENT: staging run: | cd utilities @@ -53,7 +53,7 @@ jobs: bash kube-install.sh --app utilities env: DOCKER_REPO: ${{ secrets.OSS_REGISTRY_URL }} - IMAGE_TAG: ${{ github.sha }} + IMAGE_TAG: ${{ github.ref_name }}_${{ github.sha }} ENVIRONMENT: staging # - name: Debug Job diff --git a/.github/workflows/workers-ee.yaml b/.github/workflows/workers-ee.yaml index d1b06a9fb..3035148ec 100644 --- a/.github/workflows/workers-ee.yaml +++ b/.github/workflows/workers-ee.yaml @@ -49,7 +49,7 @@ jobs: id: build-image env: DOCKER_REPO: ${{ secrets.EE_REGISTRY_URL }} - IMAGE_TAG: ${{ github.sha }}-ee + IMAGE_TAG: ${{ github.ref_name }}_${{ github.sha }}-ee ENVIRONMENT: staging run: | # @@ -96,7 +96,7 @@ jobs: - name: Deploying to kuberntes env: # We're not passing -ee flag, because helm will add that. - IMAGE_TAG: ${{ github.sha }} + IMAGE_TAG: ${{ github.ref_name }}_${{ github.sha }} run: | # # Deploying image to environment. diff --git a/.github/workflows/workers.yaml b/.github/workflows/workers.yaml index 2f215470b..155b183ed 100644 --- a/.github/workflows/workers.yaml +++ b/.github/workflows/workers.yaml @@ -49,7 +49,7 @@ jobs: id: build-image env: DOCKER_REPO: ${{ secrets.OSS_REGISTRY_URL }} - IMAGE_TAG: ${{ github.sha }} + IMAGE_TAG: ${{ github.ref_name }}_${{ github.sha }} ENVIRONMENT: staging run: | # @@ -95,7 +95,7 @@ jobs: - name: Deploying to kuberntes env: - IMAGE_TAG: ${{ github.sha }} + IMAGE_TAG: ${{ github.ref_name }}_${{ github.sha }} run: | # # Deploying image to environment. diff --git a/api/build.sh b/api/build.sh index 58689f85d..ccebccea3 100644 --- a/api/build.sh +++ b/api/build.sh @@ -6,6 +6,7 @@ # Default will be OSS build. # Usage: IMAGE_TAG=latest DOCKER_REPO=myDockerHubID bash build.sh +set -e git_sha1=${IMAGE_TAG:-$(git rev-parse HEAD)} envarg="default-foss" @@ -46,4 +47,4 @@ IMAGE_TAG=$IMAGE_TAG PUSH_IMAGE=$PUSH_IMAGE DOCKER_REPO=$DOCKER_REPO bash build_ [[ $1 == "ee" ]] && { cp ../ee/api/build_crons.sh . IMAGE_TAG=$IMAGE_TAG PUSH_IMAGE=$PUSH_IMAGE DOCKER_REPO=$DOCKER_REPO bash build_crons.sh $1 -} \ No newline at end of file +} diff --git a/backend/build.sh b/backend/build.sh index d2a919d9a..1be4fcc85 100755 --- a/backend/build.sh +++ b/backend/build.sh @@ -7,6 +7,7 @@ # Example # Usage: IMAGE_TAG=latest DOCKER_REPO=myDockerHubID bash build.sh +set -e git_sha1=${IMAGE_TAG:-$(git rev-parse HEAD)} ee="false" @@ -25,6 +26,7 @@ function build_service() { [[ $PUSH_IMAGE -eq 1 ]] && { docker push ${DOCKER_REPO:-'local'}/$image:${git_sha1} } + echo "Build completed for $image" return } @@ -38,6 +40,8 @@ function build_api(){ } [[ $2 != "" ]] && { build_service $2 + cd ../backend + rm -rf ../_backend return } for image in $(ls cmd); diff --git a/backend/cmd/assets/file b/backend/cmd/assets/file deleted file mode 100644 index f0018a2e8..000000000 --- a/backend/cmd/assets/file +++ /dev/null @@ -1 +0,0 @@ -GROUP_CACHE=from_file \ No newline at end of file diff --git a/backend/cmd/assets/main.go b/backend/cmd/assets/main.go index b81ff9b5a..220300e74 100644 --- a/backend/cmd/assets/main.go +++ b/backend/cmd/assets/main.go @@ -3,7 +3,6 @@ package main import ( "context" "log" - "openreplay/backend/pkg/queue/types" "os" "os/signal" "syscall" @@ -31,40 +30,28 @@ func main() { log.Printf("can't create assets_total metric: %s", err) } - consumer := queue.NewMessageConsumer( + msgHandler := func(msg messages.Message) { + switch m := msg.(type) { + case *messages.AssetCache: + cacher.CacheURL(m.SessionID(), m.URL) + totalAssets.Add(context.Background(), 1) + // TODO: connect to "raw" topic in order to listen for JSException + case *messages.JSException: + sourceList, err := assets.ExtractJSExceptionSources(&m.Payload) + if err != nil { + log.Printf("Error on source extraction: %v", err) + return + } + for _, source := range sourceList { + cacher.CacheJSFile(source) + } + } + } + + msgConsumer := queue.NewConsumer( cfg.GroupCache, []string{cfg.TopicCache}, - func(sessionID uint64, iter messages.Iterator, meta *types.Meta) { - for iter.Next() { - if iter.Type() == messages.MsgAssetCache { - m := iter.Message().Decode() - if m == nil { - return - } - msg := m.(*messages.AssetCache) - cacher.CacheURL(sessionID, msg.URL) - totalAssets.Add(context.Background(), 1) - } else if iter.Type() == messages.MsgErrorEvent { - m := iter.Message().Decode() - if m == nil { - return - } - msg := m.(*messages.ErrorEvent) - if msg.Source != "js_exception" { - continue - } - sourceList, err := assets.ExtractJSExceptionSources(&msg.Payload) - if err != nil { - log.Printf("Error on source extraction: %v", err) - continue - } - for _, source := range sourceList { - cacher.CacheJSFile(source) - } - } - } - iter.Close() - }, + messages.NewMessageIterator(msgHandler, []int{messages.MsgAssetCache, messages.MsgJSException}, true), true, cfg.MessageSizeLimit, ) @@ -79,15 +66,18 @@ func main() { select { case sig := <-sigchan: log.Printf("Caught signal %v: terminating\n", sig) - consumer.Close() + cacher.Stop() + msgConsumer.Close() os.Exit(0) case err := <-cacher.Errors: log.Printf("Error while caching: %v", err) - // TODO: notify user case <-tick: cacher.UpdateTimeouts() default: - if err := consumer.ConsumeNext(); err != nil { + if !cacher.CanCache() { + continue + } + if err := msgConsumer.ConsumeNext(); err != nil { log.Fatalf("Error on consumption: %v", err) } } diff --git a/backend/cmd/db/main.go b/backend/cmd/db/main.go index a807cc253..2b99883d2 100644 --- a/backend/cmd/db/main.go +++ b/backend/cmd/db/main.go @@ -45,10 +45,6 @@ func main() { // Create handler's aggregator builderMap := sessions.NewBuilderMap(handlersFabric) - keepMessage := func(tp int) bool { - return tp == messages.MsgMetadata || tp == messages.MsgIssueEvent || tp == messages.MsgSessionStart || tp == messages.MsgSessionEnd || tp == messages.MsgUserID || tp == messages.MsgUserAnonymousID || tp == messages.MsgCustomEvent || tp == messages.MsgClickEvent || tp == messages.MsgInputEvent || tp == messages.MsgPageEvent || tp == messages.MsgErrorEvent || tp == messages.MsgFetchEvent || tp == messages.MsgGraphQLEvent || tp == messages.MsgIntegrationEvent || tp == messages.MsgPerformanceTrackAggr || tp == messages.MsgResourceEvent || tp == messages.MsgLongTask || tp == messages.MsgJSException || tp == messages.MsgResourceTiming || tp == messages.MsgRawCustomEvent || tp == messages.MsgCustomIssue || tp == messages.MsgFetch || tp == messages.MsgGraphQL || tp == messages.MsgStateAction || tp == messages.MsgSetInputTarget || tp == messages.MsgSetInputValue || tp == messages.MsgCreateDocument || tp == messages.MsgMouseClick || tp == messages.MsgSetPageLocation || tp == messages.MsgPageLoadTiming || tp == messages.MsgPageRenderTiming - } - var producer types.Producer = nil if cfg.UseQuickwit { producer = queue.NewProducer(cfg.MessageSizeLimit, true) @@ -60,69 +56,66 @@ func main() { saver.InitStats() statsLogger := logger.NewQueueStats(cfg.LoggerTimeout) + msgFilter := []int{messages.MsgMetadata, messages.MsgIssueEvent, messages.MsgSessionStart, messages.MsgSessionEnd, + messages.MsgUserID, messages.MsgUserAnonymousID, messages.MsgClickEvent, + messages.MsgIntegrationEvent, messages.MsgPerformanceTrackAggr, + messages.MsgJSException, messages.MsgResourceTiming, + messages.MsgRawCustomEvent, messages.MsgCustomIssue, messages.MsgFetch, messages.MsgGraphQL, + messages.MsgStateAction, messages.MsgSetInputTarget, messages.MsgSetInputValue, messages.MsgCreateDocument, + messages.MsgMouseClick, messages.MsgSetPageLocation, messages.MsgPageLoadTiming, messages.MsgPageRenderTiming} + // Handler logic - handler := func(sessionID uint64, iter messages.Iterator, meta *types.Meta) { - statsLogger.Collect(sessionID, meta) + msgHandler := func(msg messages.Message) { + statsLogger.Collect(msg) - for iter.Next() { - if !keepMessage(iter.Type()) { - continue + // Just save session data into db without additional checks + if err := saver.InsertMessage(msg); err != nil { + if !postgres.IsPkeyViolation(err) { + log.Printf("Message Insertion Error %v, SessionID: %v, Message: %v", err, msg.SessionID(), msg) } - msg := iter.Message().Decode() - if msg == nil { - return - } - - // Just save session data into db without additional checks - if err := saver.InsertMessage(sessionID, msg); err != nil { - if !postgres.IsPkeyViolation(err) { - log.Printf("Message Insertion Error %v, SessionID: %v, Message: %v", err, sessionID, msg) - } - return - } - - session, err := pg.GetSession(sessionID) - if session == nil { - if err != nil && !errors.Is(err, cache.NilSessionInCacheError) { - log.Printf("Error on session retrieving from cache: %v, SessionID: %v, Message: %v", err, sessionID, msg) - } - return - } - - // Save statistics to db - err = saver.InsertStats(session, msg) - if err != nil { - log.Printf("Stats Insertion Error %v; Session: %v, Message: %v", err, session, msg) - } - - // Handle heuristics and save to temporary queue in memory - builderMap.HandleMessage(sessionID, msg, msg.Meta().Index) - - // Process saved heuristics messages as usual messages above in the code - builderMap.IterateSessionReadyMessages(sessionID, func(msg messages.Message) { - if err := saver.InsertMessage(sessionID, msg); err != nil { - if !postgres.IsPkeyViolation(err) { - log.Printf("Message Insertion Error %v; Session: %v, Message %v", err, session, msg) - } - return - } - - if err := saver.InsertStats(session, msg); err != nil { - log.Printf("Stats Insertion Error %v; Session: %v, Message %v", err, session, msg) - } - }) + return } - iter.Close() + + session, err := pg.GetSession(msg.SessionID()) + if session == nil { + if err != nil && !errors.Is(err, cache.NilSessionInCacheError) { + log.Printf("Error on session retrieving from cache: %v, SessionID: %v, Message: %v", err, msg.SessionID(), msg) + } + return + } + + // Save statistics to db + err = saver.InsertStats(session, msg) + if err != nil { + log.Printf("Stats Insertion Error %v; Session: %v, Message: %v", err, session, msg) + } + + // Handle heuristics and save to temporary queue in memory + builderMap.HandleMessage(msg) + + // Process saved heuristics messages as usual messages above in the code + builderMap.IterateSessionReadyMessages(msg.SessionID(), func(msg messages.Message) { + if err := saver.InsertMessage(msg); err != nil { + if !postgres.IsPkeyViolation(err) { + log.Printf("Message Insertion Error %v; Session: %v, Message %v", err, session, msg) + } + return + } + + if err := saver.InsertStats(session, msg); err != nil { + log.Printf("Stats Insertion Error %v; Session: %v, Message %v", err, session, msg) + } + }) } // Init consumer - consumer := queue.NewMessageConsumer( + consumer := queue.NewConsumer( cfg.GroupDB, []string{ cfg.TopicRawWeb, cfg.TopicAnalytics, }, - handler, + messages.NewMessageIterator(msgHandler, msgFilter, true), false, cfg.MessageSizeLimit, ) @@ -133,33 +126,36 @@ func main() { signal.Notify(sigchan, syscall.SIGINT, syscall.SIGTERM) commitTick := time.Tick(cfg.CommitBatchTimeout) + + // Send collected batches to db + commitDBUpdates := func() { + start := time.Now() + pg.CommitBatches() + pgDur := time.Now().Sub(start).Milliseconds() + + start = time.Now() + if err := saver.CommitStats(); err != nil { + log.Printf("Error on stats commit: %v", err) + } + chDur := time.Now().Sub(start).Milliseconds() + log.Printf("commit duration(ms), pg: %d, ch: %d", pgDur, chDur) + + if err := consumer.Commit(); err != nil { + log.Printf("Error on consumer commit: %v", err) + } + } for { select { case sig := <-sigchan: - log.Printf("Caught signal %v: terminating\n", sig) + log.Printf("Caught signal %s: terminating\n", sig.String()) + commitDBUpdates() consumer.Close() os.Exit(0) case <-commitTick: - // Send collected batches to db - start := time.Now() - pg.CommitBatches() - pgDur := time.Now().Sub(start).Milliseconds() - - start = time.Now() - if err := saver.CommitStats(consumer.HasFirstPartition()); err != nil { - log.Printf("Error on stats commit: %v", err) - } - chDur := time.Now().Sub(start).Milliseconds() - log.Printf("commit duration(ms), pg: %d, ch: %d", pgDur, chDur) - - // TODO: use commit worker to save time each tick - if err := consumer.Commit(); err != nil { - log.Printf("Error on consumer commit: %v", err) - } + commitDBUpdates() default: // Handle new message from queue - err := consumer.ConsumeNext() - if err != nil { + if err := consumer.ConsumeNext(); err != nil { log.Fatalf("Error on consumption: %v", err) } } diff --git a/backend/cmd/ender/main.go b/backend/cmd/ender/main.go index a2dafa689..913629f0e 100644 --- a/backend/cmd/ender/main.go +++ b/backend/cmd/ender/main.go @@ -2,7 +2,7 @@ package main import ( "log" - "openreplay/backend/pkg/queue/types" + "openreplay/backend/internal/storage" "os" "os/signal" "syscall" @@ -20,42 +20,27 @@ import ( ) func main() { - metrics := monitoring.New("ender") - log.SetFlags(log.LstdFlags | log.LUTC | log.Llongfile) - - // Load service configuration + metrics := monitoring.New("ender") cfg := ender.New() pg := cache.NewPGCache(postgres.NewConn(cfg.Postgres, 0, 0, metrics), cfg.ProjectExpirationTimeoutMs) defer pg.Close() - // Init all modules - statsLogger := logger.NewQueueStats(cfg.LoggerTimeout) - sessions, err := sessionender.New(metrics, intervals.EVENTS_SESSION_END_TIMEOUT, cfg.PartitionsNumber) + sessions, err := sessionender.New(metrics, intervals.EVENTS_SESSION_END_TIMEOUT, cfg.PartitionsNumber, logger.NewQueueStats(cfg.LoggerTimeout)) if err != nil { log.Printf("can't init ender service: %s", err) return } + producer := queue.NewProducer(cfg.MessageSizeLimit, true) - consumer := queue.NewMessageConsumer( + consumer := queue.NewConsumer( cfg.GroupEnder, - []string{ - cfg.TopicRawWeb, - }, - func(sessionID uint64, iter messages.Iterator, meta *types.Meta) { - for iter.Next() { - if iter.Type() == messages.MsgSessionStart || iter.Type() == messages.MsgSessionEnd { - continue - } - if iter.Message().Meta().Timestamp == 0 { - log.Printf("ZERO TS, sessID: %d, msgType: %d", sessionID, iter.Type()) - } - statsLogger.Collect(sessionID, meta) - sessions.UpdateSession(sessionID, meta.Timestamp, iter.Message().Meta().Timestamp) - } - iter.Close() - }, + []string{cfg.TopicRawWeb}, + messages.NewMessageIterator( + func(msg messages.Message) { sessions.UpdateSession(msg) }, + []int{messages.MsgTimestamp}, + false), false, cfg.MessageSizeLimit, ) @@ -94,7 +79,16 @@ func main() { currDuration, newDuration) return true } - if err := producer.Produce(cfg.TopicRawWeb, sessionID, messages.Encode(msg)); err != nil { + if cfg.UseEncryption { + if key := storage.GenerateEncryptionKey(); key != nil { + if err := pg.InsertSessionEncryptionKey(sessionID, key); err != nil { + log.Printf("can't save session encryption key: %s, session will not be encrypted", err) + } else { + msg.EncryptionKey = string(key) + } + } + } + if err := producer.Produce(cfg.TopicRawWeb, sessionID, msg.Encode()); err != nil { log.Printf("can't send sessionEnd to topic: %s; sessID: %d", err, sessionID) return false } diff --git a/backend/cmd/heuristics/main.go b/backend/cmd/heuristics/main.go index be27a86bd..9e4804089 100644 --- a/backend/cmd/heuristics/main.go +++ b/backend/cmd/heuristics/main.go @@ -2,7 +2,6 @@ package main import ( "log" - "openreplay/backend/pkg/queue/types" "os" "os/signal" "syscall" @@ -47,25 +46,18 @@ func main() { // Init producer and consumer for data bus producer := queue.NewProducer(cfg.MessageSizeLimit, true) - consumer := queue.NewMessageConsumer( + + msgHandler := func(msg messages.Message) { + statsLogger.Collect(msg) + builderMap.HandleMessage(msg) + } + + consumer := queue.NewConsumer( cfg.GroupHeuristics, []string{ cfg.TopicRawWeb, }, - func(sessionID uint64, iter messages.Iterator, meta *types.Meta) { - var lastMessageID uint64 - for iter.Next() { - statsLogger.Collect(sessionID, meta) - msg := iter.Message().Decode() - if msg == nil { - log.Printf("failed batch, sess: %d, lastIndex: %d", sessionID, lastMessageID) - continue - } - lastMessageID = msg.Meta().Index - builderMap.HandleMessage(sessionID, msg, iter.Message().Meta().Index) - } - iter.Close() - }, + messages.NewMessageIterator(msgHandler, nil, true), false, cfg.MessageSizeLimit, ) @@ -86,7 +78,7 @@ func main() { os.Exit(0) case <-tick: builderMap.IterateReadyMessages(func(sessionID uint64, readyMsg messages.Message) { - producer.Produce(cfg.TopicAnalytics, sessionID, messages.Encode(readyMsg)) + producer.Produce(cfg.TopicAnalytics, sessionID, readyMsg.Encode()) }) producer.Flush(cfg.ProducerTimeout) consumer.Commit() diff --git a/backend/cmd/integrations/main.go b/backend/cmd/integrations/main.go index 86490c6ab..4f5a30dcf 100644 --- a/backend/cmd/integrations/main.go +++ b/backend/cmd/integrations/main.go @@ -13,12 +13,10 @@ import ( "openreplay/backend/pkg/db/postgres" "openreplay/backend/pkg/intervals" - "openreplay/backend/pkg/messages" "openreplay/backend/pkg/queue" "openreplay/backend/pkg/token" ) -// func main() { metrics := monitoring.New("integrations") @@ -84,7 +82,7 @@ func main() { } sessionID = sessData.ID } - producer.Produce(cfg.TopicAnalytics, sessionID, messages.Encode(event.IntegrationEvent)) + producer.Produce(cfg.TopicAnalytics, sessionID, event.IntegrationEvent.Encode()) case err := <-manager.Errors: log.Printf("Integration error: %v\n", err) case i := <-manager.RequestDataUpdates: diff --git a/backend/cmd/sink/main.go b/backend/cmd/sink/main.go index bd7fddf20..1f34760e1 100644 --- a/backend/cmd/sink/main.go +++ b/backend/cmd/sink/main.go @@ -3,7 +3,7 @@ package main import ( "context" "log" - "openreplay/backend/pkg/queue/types" + "openreplay/backend/pkg/pprof" "os" "os/signal" "syscall" @@ -13,13 +13,15 @@ import ( "openreplay/backend/internal/sink/assetscache" "openreplay/backend/internal/sink/oswriter" "openreplay/backend/internal/storage" - . "openreplay/backend/pkg/messages" + "openreplay/backend/pkg/messages" "openreplay/backend/pkg/monitoring" "openreplay/backend/pkg/queue" "openreplay/backend/pkg/url/assets" ) func main() { + pprof.StartProfilingServer() + metrics := monitoring.New("sink") log.SetFlags(log.LstdFlags | log.LUTC | log.Llongfile) @@ -35,9 +37,10 @@ func main() { producer := queue.NewProducer(cfg.MessageSizeLimit, true) defer producer.Close(cfg.ProducerCloseTimeout) rewriter := assets.NewRewriter(cfg.AssetsOrigin) - assetMessageHandler := assetscache.New(cfg, rewriter, producer) + assetMessageHandler := assetscache.New(cfg, rewriter, producer, metrics) counter := storage.NewLogCounter() + // Session message metrics totalMessages, err := metrics.RegisterCounter("messages_total") if err != nil { log.Printf("can't create messages_total metric: %s", err) @@ -51,64 +54,75 @@ func main() { log.Printf("can't create messages_size metric: %s", err) } - consumer := queue.NewMessageConsumer( + msgHandler := func(msg messages.Message) { + // [METRICS] Increase the number of processed messages + totalMessages.Add(context.Background(), 1) + + // Send SessionEnd trigger to storage service + if msg.TypeID() == messages.MsgSessionEnd { + if err := producer.Produce(cfg.TopicTrigger, msg.SessionID(), msg.Encode()); err != nil { + log.Printf("can't send SessionEnd to trigger topic: %s; sessID: %d", err, msg.SessionID()) + } + return + } + + // Process assets + if msg.TypeID() == messages.MsgSetNodeAttributeURLBased || + msg.TypeID() == messages.MsgSetCSSDataURLBased || + msg.TypeID() == messages.MsgCSSInsertRuleURLBased || + msg.TypeID() == messages.MsgAdoptedSSReplaceURLBased || + msg.TypeID() == messages.MsgAdoptedSSInsertRuleURLBased { + m := msg.Decode() + if m == nil { + log.Printf("assets decode err, info: %s", msg.Meta().Batch().Info()) + return + } + msg = assetMessageHandler.ParseAssets(m) + } + + // Filter message + if !messages.IsReplayerType(msg.TypeID()) { + return + } + + // If message timestamp is empty, use at least ts of session start + ts := msg.Meta().Timestamp + if ts == 0 { + log.Printf("zero ts; sessID: %d, msgType: %d", msg.SessionID(), msg.TypeID()) + } else { + // Log ts of last processed message + counter.Update(msg.SessionID(), time.UnixMilli(ts)) + } + + // Write encoded message with index to session file + data := msg.EncodeWithIndex() + if data == nil { + log.Printf("can't encode with index, err: %s", err) + return + } + if messages.IsDOMType(msg.TypeID()) { + if err := writer.WriteDOM(msg.SessionID(), data); err != nil { + log.Printf("DOM Writer error: %s, info: %s", err, msg.Meta().Batch().Info()) + } + } + if !messages.IsDOMType(msg.TypeID()) || msg.TypeID() == messages.MsgTimestamp { + // TODO: write only necessary timestamps + if err := writer.WriteDEV(msg.SessionID(), data); err != nil { + log.Printf("Devtools Writer error: %s, info: %s", err, msg.Meta().Batch().Info()) + } + } + + // [METRICS] Increase the number of written to the files messages and the message size + messageSize.Record(context.Background(), float64(len(data))) + savedMessages.Add(context.Background(), 1) + } + + consumer := queue.NewConsumer( cfg.GroupSink, []string{ cfg.TopicRawWeb, }, - func(sessionID uint64, iter Iterator, meta *types.Meta) { - for iter.Next() { - // [METRICS] Increase the number of processed messages - totalMessages.Add(context.Background(), 1) - - // Send SessionEnd trigger to storage service - if iter.Type() == MsgSessionEnd { - if err := producer.Produce(cfg.TopicTrigger, sessionID, iter.Message().Encode()); err != nil { - log.Printf("can't send SessionEnd to trigger topic: %s; sessID: %d", err, sessionID) - } - continue - } - - msg := iter.Message() - // Process assets - if iter.Type() == MsgSetNodeAttributeURLBased || - iter.Type() == MsgSetCSSDataURLBased || - iter.Type() == MsgCSSInsertRuleURLBased || - iter.Type() == MsgAdoptedSSReplaceURLBased || - iter.Type() == MsgAdoptedSSInsertRuleURLBased { - m := msg.Decode() - if m == nil { - return - } - msg = assetMessageHandler.ParseAssets(sessionID, m) // TODO: filter type only once (use iterator inide or bring ParseAssets out here). - } - - // Filter message - if !IsReplayerType(msg.TypeID()) { - continue - } - - // If message timestamp is empty, use at least ts of session start - ts := msg.Meta().Timestamp - if ts == 0 { - log.Printf("zero ts; sessID: %d, msgType: %d", sessionID, iter.Type()) - } else { - // Log ts of last processed message - counter.Update(sessionID, time.UnixMilli(ts)) - } - - // Write encoded message with index to session file - data := msg.EncodeWithIndex() - if err := writer.Write(sessionID, data); err != nil { - log.Printf("Writer error: %v\n", err) - } - - // [METRICS] Increase the number of written to the files messages and the message size - messageSize.Record(context.Background(), float64(len(data))) - savedMessages.Add(context.Background(), 1) - } - iter.Close() - }, + messages.NewMessageIterator(msgHandler, nil, false), false, cfg.MessageSizeLimit, ) @@ -122,6 +136,9 @@ func main() { select { case sig := <-sigchan: log.Printf("Caught signal %v: terminating\n", sig) + if err := writer.SyncAll(); err != nil { + log.Printf("sync error: %v\n", err) + } if err := consumer.Commit(); err != nil { log.Printf("can't commit messages: %s", err) } @@ -129,7 +146,7 @@ func main() { os.Exit(0) case <-tick: if err := writer.SyncAll(); err != nil { - log.Fatalf("Sync error: %v\n", err) + log.Fatalf("sync error: %v\n", err) } counter.Print() if err := consumer.Commit(); err != nil { @@ -142,5 +159,4 @@ func main() { } } } - } diff --git a/backend/cmd/storage/main.go b/backend/cmd/storage/main.go index b3848c5de..07c6eec91 100644 --- a/backend/cmd/storage/main.go +++ b/backend/cmd/storage/main.go @@ -2,10 +2,8 @@ package main import ( "log" - "openreplay/backend/pkg/queue/types" "os" "os/signal" - "strconv" "syscall" "time" @@ -38,24 +36,24 @@ func main() { log.Fatalf("can't init sessionFinder module: %s", err) } - consumer := queue.NewMessageConsumer( + consumer := queue.NewConsumer( cfg.GroupStorage, []string{ cfg.TopicTrigger, }, - func(sessionID uint64, iter messages.Iterator, meta *types.Meta) { - for iter.Next() { - if iter.Type() == messages.MsgSessionEnd { - msg := iter.Message().Decode().(*messages.SessionEnd) - if err := srv.UploadKey(strconv.FormatUint(sessionID, 10), 5); err != nil { - log.Printf("can't find session: %d", sessionID) - sessionFinder.Find(sessionID, msg.Timestamp) - } - // Log timestamp of last processed session - counter.Update(sessionID, time.UnixMilli(meta.Timestamp)) + messages.NewMessageIterator( + func(msg messages.Message) { + sesEnd := msg.(*messages.SessionEnd) + if err := srv.UploadSessionFiles(sesEnd); err != nil { + log.Printf("can't find session: %d", msg.SessionID()) + sessionFinder.Find(msg.SessionID(), sesEnd.Timestamp) } - } - }, + // Log timestamp of last processed session + counter.Update(msg.SessionID(), time.UnixMilli(msg.Meta().Batch().Timestamp())) + }, + []int{messages.MsgSessionEnd}, + true, + ), true, cfg.MessageSizeLimit, ) diff --git a/backend/go.mod b/backend/go.mod index e38e304a1..0eead389c 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -5,6 +5,7 @@ go 1.18 require ( cloud.google.com/go/logging v1.4.2 github.com/ClickHouse/clickhouse-go/v2 v2.2.0 + github.com/Masterminds/semver v1.5.0 github.com/aws/aws-sdk-go v1.44.98 github.com/btcsuite/btcutil v1.0.2 github.com/elastic/go-elasticsearch/v7 v7.13.1 diff --git a/backend/go.sum b/backend/go.sum index 681b2b9d4..dbaee7216 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -64,6 +64,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/ClickHouse/clickhouse-go v1.5.4/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI= github.com/ClickHouse/clickhouse-go/v2 v2.2.0 h1:dj00TDKY+xwuTJdbpspCSmTLFyWzRJerTHwaBxut1C0= github.com/ClickHouse/clickhouse-go/v2 v2.2.0/go.mod h1:8f2XZUi7XoeU+uPIytSi1cvx8fmJxi7vIgqpvYTF1+o= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= diff --git a/backend/internal/assets/cacher/cacher.go b/backend/internal/assets/cacher/cacher.go index 619c28c7c..b56c97d74 100644 --- a/backend/internal/assets/cacher/cacher.go +++ b/backend/internal/assets/cacher/cacher.go @@ -33,6 +33,11 @@ type cacher struct { sizeLimit int downloadedAssets syncfloat64.Counter requestHeaders map[string]string + workers *WorkerPool +} + +func (c *cacher) CanCache() bool { + return c.workers.CanAddTask() } func NewCacher(cfg *config.Config, metrics *monitoring.Metrics) *cacher { @@ -44,7 +49,7 @@ func NewCacher(cfg *config.Config, metrics *monitoring.Metrics) *cacher { if err != nil { log.Printf("can't create downloaded_assets metric: %s", err) } - return &cacher{ + c := &cacher{ timeoutMap: newTimeoutMap(), s3: storage.NewS3(cfg.AWSRegion, cfg.S3BucketAssets), httpClient: &http.Client{ @@ -60,47 +65,48 @@ func NewCacher(cfg *config.Config, metrics *monitoring.Metrics) *cacher { downloadedAssets: downloadedAssets, requestHeaders: cfg.AssetsRequestHeaders, } + c.workers = NewPool(64, c.CacheFile) + return c } -func (c *cacher) cacheURL(requestURL string, sessionID uint64, depth byte, urlContext string, isJS bool) { - var cachePath string - if isJS { - cachePath = assets.GetCachePathForJS(requestURL) - } else { - cachePath = assets.GetCachePathForAssets(sessionID, requestURL) - } - if c.timeoutMap.contains(cachePath) { - return - } - c.timeoutMap.add(cachePath) - crTime := c.s3.GetCreationTime(cachePath) - if crTime != nil && crTime.After(time.Now().Add(-MAX_STORAGE_TIME)) { // recently uploaded - return - } +func (c *cacher) CacheFile(task *Task) { + c.cacheURL(task) +} - req, _ := http.NewRequest("GET", requestURL, nil) - req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 6.1; rv:31.0) Gecko/20100101 Firefox/31.0") +func (c *cacher) cacheURL(t *Task) { + t.retries-- + req, _ := http.NewRequest("GET", t.requestURL, nil) + if t.retries%2 == 0 { + req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 6.1; rv:31.0) Gecko/20100101 Firefox/31.0") + } for k, v := range c.requestHeaders { req.Header.Set(k, v) } res, err := c.httpClient.Do(req) if err != nil { - c.Errors <- errors.Wrap(err, urlContext) + c.Errors <- errors.Wrap(err, t.urlContext) return } defer res.Body.Close() if res.StatusCode >= 400 { - // TODO: retry - c.Errors <- errors.Wrap(fmt.Errorf("Status code is %v, ", res.StatusCode), urlContext) + printErr := true + // Retry 403 error + if res.StatusCode == 403 && t.retries > 0 { + c.workers.AddTask(t) + printErr = false + } + if printErr { + c.Errors <- errors.Wrap(fmt.Errorf("Status code is %v, ", res.StatusCode), t.urlContext) + } return } data, err := ioutil.ReadAll(io.LimitReader(res.Body, int64(c.sizeLimit+1))) if err != nil { - c.Errors <- errors.Wrap(err, urlContext) + c.Errors <- errors.Wrap(err, t.urlContext) return } if len(data) > c.sizeLimit { - c.Errors <- errors.Wrap(errors.New("Maximum size exceeded"), urlContext) + c.Errors <- errors.Wrap(errors.New("Maximum size exceeded"), t.urlContext) return } @@ -112,44 +118,94 @@ func (c *cacher) cacheURL(requestURL string, sessionID uint64, depth byte, urlCo strData := string(data) if isCSS { - strData = c.rewriter.RewriteCSS(sessionID, requestURL, strData) // TODO: one method for rewrite and return list + strData = c.rewriter.RewriteCSS(t.sessionID, t.requestURL, strData) // TODO: one method for rewrite and return list } // TODO: implement in streams - err = c.s3.Upload(strings.NewReader(strData), cachePath, contentType, false) + err = c.s3.Upload(strings.NewReader(strData), t.cachePath, contentType, false) if err != nil { - c.Errors <- errors.Wrap(err, urlContext) + c.Errors <- errors.Wrap(err, t.urlContext) return } c.downloadedAssets.Add(context.Background(), 1) if isCSS { - if depth > 0 { + if t.depth > 0 { for _, extractedURL := range assets.ExtractURLsFromCSS(string(data)) { - if fullURL, cachable := assets.GetFullCachableURL(requestURL, extractedURL); cachable { - go c.cacheURL(fullURL, sessionID, depth-1, urlContext+"\n -> "+fullURL, false) + if fullURL, cachable := assets.GetFullCachableURL(t.requestURL, extractedURL); cachable { + c.checkTask(&Task{ + requestURL: fullURL, + sessionID: t.sessionID, + depth: t.depth - 1, + urlContext: t.urlContext + "\n -> " + fullURL, + isJS: false, + retries: setRetries(), + }) } } if err != nil { - c.Errors <- errors.Wrap(err, urlContext) + c.Errors <- errors.Wrap(err, t.urlContext) return } } else { - c.Errors <- errors.Wrap(errors.New("Maximum recursion cache depth exceeded"), urlContext) + c.Errors <- errors.Wrap(errors.New("Maximum recursion cache depth exceeded"), t.urlContext) return } } return } +func (c *cacher) checkTask(newTask *Task) { + // check if file was recently uploaded + var cachePath string + if newTask.isJS { + cachePath = assets.GetCachePathForJS(newTask.requestURL) + } else { + cachePath = assets.GetCachePathForAssets(newTask.sessionID, newTask.requestURL) + } + if c.timeoutMap.contains(cachePath) { + return + } + c.timeoutMap.add(cachePath) + crTime := c.s3.GetCreationTime(cachePath) + if crTime != nil && crTime.After(time.Now().Add(-MAX_STORAGE_TIME)) { + return + } + // add new file in queue to download + newTask.cachePath = cachePath + c.workers.AddTask(newTask) +} + func (c *cacher) CacheJSFile(sourceURL string) { - go c.cacheURL(sourceURL, 0, 0, sourceURL, true) + c.checkTask(&Task{ + requestURL: sourceURL, + sessionID: 0, + depth: 0, + urlContext: sourceURL, + isJS: true, + retries: setRetries(), + }) } func (c *cacher) CacheURL(sessionID uint64, fullURL string) { - go c.cacheURL(fullURL, sessionID, MAX_CACHE_DEPTH, fullURL, false) + c.checkTask(&Task{ + requestURL: fullURL, + sessionID: sessionID, + depth: MAX_CACHE_DEPTH, + urlContext: fullURL, + isJS: false, + retries: setRetries(), + }) } func (c *cacher) UpdateTimeouts() { c.timeoutMap.deleteOutdated() } + +func (c *cacher) Stop() { + c.workers.Stop() +} + +func setRetries() int { + return 10 +} diff --git a/backend/internal/assets/cacher/pool.go b/backend/internal/assets/cacher/pool.go new file mode 100644 index 000000000..15ccb74b0 --- /dev/null +++ b/backend/internal/assets/cacher/pool.go @@ -0,0 +1,80 @@ +package cacher + +import ( + "log" + "sync" +) + +type Task struct { + requestURL string + sessionID uint64 + depth byte + urlContext string + isJS bool + cachePath string + retries int +} + +type WorkerPool struct { + tasks chan *Task + wg sync.WaitGroup + done chan struct{} + term sync.Once + size int + job Job +} + +func (p *WorkerPool) CanAddTask() bool { + if len(p.tasks) < cap(p.tasks) { + return true + } + return false +} + +type Job func(task *Task) + +func NewPool(size int, job Job) *WorkerPool { + newPool := &WorkerPool{ + tasks: make(chan *Task, 128), + done: make(chan struct{}), + size: size, + job: job, + } + newPool.init() + return newPool +} + +func (p *WorkerPool) init() { + p.wg.Add(p.size) + for i := 0; i < p.size; i++ { + go p.worker() + } +} + +func (p *WorkerPool) worker() { + for { + select { + case newTask := <-p.tasks: + p.job(newTask) + case <-p.done: + p.wg.Done() + return + } + } +} + +func (p *WorkerPool) AddTask(task *Task) { + if task.retries <= 0 { + return + } + p.tasks <- task +} + +func (p *WorkerPool) Stop() { + log.Printf("stopping workers") + p.term.Do(func() { + close(p.done) + }) + p.wg.Wait() + log.Printf("all workers have been stopped") +} diff --git a/backend/internal/config/configurator/configurator.go b/backend/internal/config/configurator/configurator.go index 3ffa8f7d7..5335d4a91 100644 --- a/backend/internal/config/configurator/configurator.go +++ b/backend/internal/config/configurator/configurator.go @@ -17,9 +17,6 @@ import ( ) func readFile(path string) (map[string]string, error) { - if path == "" { - return nil, fmt.Errorf("file path is empty") - } file, err := os.Open(path) if err != nil { return nil, fmt.Errorf("can't open file: %s", err) @@ -40,6 +37,10 @@ func readFile(path string) (map[string]string, error) { } func parseFile(a interface{}, path string) { + // Skip parsing process without logs if we don't have path to config file + if path == "" { + return + } envs, err := readFile(path) if err != nil { log.Printf("can't parse config file: %s", err) diff --git a/backend/internal/config/ender/config.go b/backend/internal/config/ender/config.go index 64c07eb7c..fb315acbe 100644 --- a/backend/internal/config/ender/config.go +++ b/backend/internal/config/ender/config.go @@ -14,6 +14,7 @@ type Config struct { TopicRawWeb string `env:"TOPIC_RAW_WEB,required"` ProducerTimeout int `env:"PRODUCER_TIMEOUT,default=2000"` PartitionsNumber int `env:"PARTITIONS_NUMBER,required"` + UseEncryption bool `env:"USE_ENCRYPTION,default=false"` } func New() *Config { diff --git a/backend/internal/config/sink/config.go b/backend/internal/config/sink/config.go index 215484082..a7481f93a 100644 --- a/backend/internal/config/sink/config.go +++ b/backend/internal/config/sink/config.go @@ -17,6 +17,8 @@ type Config struct { CacheAssets bool `env:"CACHE_ASSETS,required"` AssetsOrigin string `env:"ASSETS_ORIGIN,required"` ProducerCloseTimeout int `env:"PRODUCER_CLOSE_TIMEOUT,default=15000"` + CacheThreshold int64 `env:"CACHE_THRESHOLD,default=75"` + CacheExpiration int64 `env:"CACHE_EXPIRATION,default=120"` } func New() *Config { diff --git a/backend/internal/db/datasaver/messages.go b/backend/internal/db/datasaver/messages.go index 702c2f210..621659c6d 100644 --- a/backend/internal/db/datasaver/messages.go +++ b/backend/internal/db/datasaver/messages.go @@ -5,7 +5,8 @@ import ( . "openreplay/backend/pkg/messages" ) -func (mi *Saver) InsertMessage(sessionID uint64, msg Message) error { +func (mi *Saver) InsertMessage(msg Message) error { + sessionID := msg.SessionID() switch m := msg.(type) { // Common case *Metadata: @@ -37,23 +38,16 @@ func (mi *Saver) InsertMessage(sessionID uint64, msg Message) error { case *PageEvent: mi.sendToFTS(msg, sessionID) return mi.pg.InsertWebPageEvent(sessionID, m) - case *ErrorEvent: - return mi.pg.InsertWebErrorEvent(sessionID, m) case *FetchEvent: mi.sendToFTS(msg, sessionID) return mi.pg.InsertWebFetchEvent(sessionID, m) case *GraphQLEvent: mi.sendToFTS(msg, sessionID) return mi.pg.InsertWebGraphQLEvent(sessionID, m) + case *JSException: + return mi.pg.InsertWebJSException(m) case *IntegrationEvent: - return mi.pg.InsertWebErrorEvent(sessionID, &ErrorEvent{ - MessageID: m.Meta().Index, - Timestamp: m.Timestamp, - Source: m.Source, - Name: m.Name, - Message: m.Message, - Payload: m.Payload, - }) + return mi.pg.InsertWebIntegrationEvent(m) // IOS case *IOSSessionStart: diff --git a/backend/internal/db/datasaver/stats.go b/backend/internal/db/datasaver/stats.go index 17028ca5c..b523ecdbe 100644 --- a/backend/internal/db/datasaver/stats.go +++ b/backend/internal/db/datasaver/stats.go @@ -16,12 +16,10 @@ func (si *Saver) InsertStats(session *Session, msg Message) error { return si.pg.InsertWebStatsPerformance(session.SessionID, m) case *ResourceEvent: return si.pg.InsertWebStatsResourceEvent(session.SessionID, m) - case *LongTask: - return si.pg.InsertWebStatsLongtask(session.SessionID, m) } return nil } -func (si *Saver) CommitStats(optimize bool) error { +func (si *Saver) CommitStats() error { return nil } diff --git a/backend/internal/http/router/handlers-ios.go b/backend/internal/http/router/handlers-ios.go index 43fd0a7a9..e0fc73b6f 100644 --- a/backend/internal/http/router/handlers-ios.go +++ b/backend/internal/http/router/handlers-ios.go @@ -69,12 +69,12 @@ func (e *Router) startSessionHandlerIOS(w http.ResponseWriter, r *http.Request) } // TODO: if EXPIRED => send message for two sessions association expTime := startTime.Add(time.Duration(p.MaxSessionDuration) * time.Millisecond) - tokenData = &token.TokenData{sessionID, expTime.UnixMilli()} + tokenData = &token.TokenData{sessionID, 0, expTime.UnixMilli()} country := e.services.GeoIP.ExtractISOCodeFromHTTPRequest(r) // The difference with web is mostly here: - e.services.Producer.Produce(e.cfg.TopicRawIOS, tokenData.ID, Encode(&IOSSessionStart{ + sessStart := &IOSSessionStart{ Timestamp: req.Timestamp, ProjectID: uint64(p.ProjectID), TrackerVersion: req.TrackerVersion, @@ -85,7 +85,8 @@ func (e *Router) startSessionHandlerIOS(w http.ResponseWriter, r *http.Request) UserDevice: ios.MapIOSDevice(req.UserDevice), UserDeviceType: ios.GetIOSDeviceType(req.UserDevice), UserCountry: country, - })) + } + e.services.Producer.Produce(e.cfg.TopicRawIOS, tokenData.ID, sessStart.Encode()) } ResponseWithJSON(w, &StartIOSSessionResponse{ diff --git a/backend/internal/http/router/handlers-web.go b/backend/internal/http/router/handlers-web.go index 04728de4e..08c4c75c5 100644 --- a/backend/internal/http/router/handlers-web.go +++ b/backend/internal/http/router/handlers-web.go @@ -3,6 +3,7 @@ package router import ( "encoding/json" "errors" + "github.com/Masterminds/semver" "go.opentelemetry.io/otel/attribute" "io" "log" @@ -37,6 +38,22 @@ func (e *Router) readBody(w http.ResponseWriter, r *http.Request, limit int64) ( return bodyBytes, nil } +func getSessionTimestamp(req *StartSessionRequest, startTimeMili int64) (ts uint64) { + ts = uint64(req.Timestamp) + c, err := semver.NewConstraint(">=4.1.6") + if err != nil { + return + } + v, err := semver.NewVersion(req.TrackerVersion) + if err != nil { + return + } + if c.Check(v) { + return uint64(startTimeMili) + } + return +} + func (e *Router) startSessionHandlerWeb(w http.ResponseWriter, r *http.Request) { startTime := time.Now() @@ -91,17 +108,22 @@ func (e *Router) startSessionHandlerWeb(w http.ResponseWriter, r *http.Request) ResponseWithError(w, http.StatusForbidden, errors.New("browser not recognized")) return } - sessionID, err := e.services.Flaker.Compose(uint64(startTime.UnixMilli())) + startTimeMili := startTime.UnixMilli() + sessionID, err := e.services.Flaker.Compose(uint64(startTimeMili)) if err != nil { ResponseWithError(w, http.StatusInternalServerError, err) return } // TODO: if EXPIRED => send message for two sessions association expTime := startTime.Add(time.Duration(p.MaxSessionDuration) * time.Millisecond) - tokenData = &token.TokenData{ID: sessionID, ExpTime: expTime.UnixMilli()} + tokenData = &token.TokenData{ + ID: sessionID, + Delay: startTimeMili - req.Timestamp, + ExpTime: expTime.UnixMilli(), + } sessionStart := &SessionStart{ - Timestamp: req.Timestamp, + Timestamp: getSessionTimestamp(req, startTimeMili), ProjectID: uint64(p.ProjectID), TrackerVersion: req.TrackerVersion, RevID: req.RevID, @@ -125,7 +147,7 @@ func (e *Router) startSessionHandlerWeb(w http.ResponseWriter, r *http.Request) } // Send sessionStart message to kafka - if err := e.services.Producer.Produce(e.cfg.TopicRawWeb, tokenData.ID, Encode(sessionStart)); err != nil { + if err := e.services.Producer.Produce(e.cfg.TopicRawWeb, tokenData.ID, sessionStart.Encode()); err != nil { log.Printf("can't send session start: %s", err) } } @@ -137,6 +159,7 @@ func (e *Router) startSessionHandlerWeb(w http.ResponseWriter, r *http.Request) ProjectID: strconv.FormatUint(uint64(p.ProjectID), 10), BeaconSizeLimit: e.cfg.BeaconSizeLimit, StartTimestamp: int64(flakeid.ExtractTimestamp(tokenData.ID)), + Delay: tokenData.Delay, }) } diff --git a/backend/internal/http/router/model.go b/backend/internal/http/router/model.go index 42a500c06..b2ddfd9b4 100644 --- a/backend/internal/http/router/model.go +++ b/backend/internal/http/router/model.go @@ -4,7 +4,7 @@ type StartSessionRequest struct { Token string `json:"token"` UserUUID *string `json:"userUUID"` RevID string `json:"revID"` - Timestamp uint64 `json:"timestamp"` + Timestamp int64 `json:"timestamp"` TrackerVersion string `json:"trackerVersion"` IsSnippet bool `json:"isSnippet"` DeviceMemory uint64 `json:"deviceMemory"` diff --git a/backend/internal/sessionender/ender.go b/backend/internal/sessionender/ender.go index 2f8e1e1ba..dbd3eb901 100644 --- a/backend/internal/sessionender/ender.go +++ b/backend/internal/sessionender/ender.go @@ -5,6 +5,8 @@ import ( "fmt" "go.opentelemetry.io/otel/metric/instrument/syncfloat64" "log" + log2 "openreplay/backend/pkg/log" + "openreplay/backend/pkg/messages" "openreplay/backend/pkg/monitoring" "time" ) @@ -27,9 +29,10 @@ type SessionEnder struct { timeCtrl *timeController activeSessions syncfloat64.UpDownCounter totalSessions syncfloat64.Counter + stats log2.QueueStats } -func New(metrics *monitoring.Metrics, timeout int64, parts int) (*SessionEnder, error) { +func New(metrics *monitoring.Metrics, timeout int64, parts int, stats log2.QueueStats) (*SessionEnder, error) { if metrics == nil { return nil, fmt.Errorf("metrics module is empty") } @@ -48,24 +51,31 @@ func New(metrics *monitoring.Metrics, timeout int64, parts int) (*SessionEnder, timeCtrl: NewTimeController(parts), activeSessions: activeSessions, totalSessions: totalSessions, + stats: stats, }, nil } // UpdateSession save timestamp for new sessions and update for existing sessions -func (se *SessionEnder) UpdateSession(sessionID uint64, timestamp, msgTimestamp int64) { - localTS := time.Now().UnixMilli() - currTS := timestamp - if currTS == 0 { +func (se *SessionEnder) UpdateSession(msg messages.Message) { + se.stats.Collect(msg) + var ( + sessionID = msg.Meta().SessionID() + batchTimestamp = msg.Meta().Batch().Timestamp() + msgTimestamp = msg.Meta().Timestamp + localTimestamp = time.Now().UnixMilli() + ) + if batchTimestamp == 0 { log.Printf("got empty timestamp for sessionID: %d", sessionID) return } - se.timeCtrl.UpdateTime(sessionID, currTS) + se.timeCtrl.UpdateTime(sessionID, batchTimestamp) sess, ok := se.sessions[sessionID] if !ok { + // Register new session se.sessions[sessionID] = &session{ - lastTimestamp: currTS, // timestamp from message broker - lastUpdate: localTS, // local timestamp - lastUserTime: msgTimestamp, // last timestamp from user's machine + lastTimestamp: batchTimestamp, // timestamp from message broker + lastUpdate: localTimestamp, // local timestamp + lastUserTime: msgTimestamp, // last timestamp from user's machine isEnded: false, } se.activeSessions.Add(context.Background(), 1) @@ -77,9 +87,9 @@ func (se *SessionEnder) UpdateSession(sessionID uint64, timestamp, msgTimestamp sess.lastUserTime = msgTimestamp } // Keep information about the latest message for generating sessionEnd trigger - if currTS > sess.lastTimestamp { - sess.lastTimestamp = currTS - sess.lastUpdate = localTS + if batchTimestamp > sess.lastTimestamp { + sess.lastTimestamp = batchTimestamp + sess.lastUpdate = localTimestamp sess.isEnded = false } } diff --git a/backend/internal/sink/assetscache/assets.go b/backend/internal/sink/assetscache/assets.go index 8218af4e6..96f225a45 100644 --- a/backend/internal/sink/assetscache/assets.go +++ b/backend/internal/sink/assetscache/assets.go @@ -1,35 +1,80 @@ package assetscache import ( + "context" + "crypto/md5" + "go.opentelemetry.io/otel/metric/instrument/syncfloat64" + "io" "log" + "net/url" "openreplay/backend/internal/config/sink" "openreplay/backend/pkg/messages" + "openreplay/backend/pkg/monitoring" "openreplay/backend/pkg/queue/types" "openreplay/backend/pkg/url/assets" + "time" ) -type AssetsCache struct { - cfg *sink.Config - rewriter *assets.Rewriter - producer types.Producer +type CachedAsset struct { + msg string + ts time.Time } -func New(cfg *sink.Config, rewriter *assets.Rewriter, producer types.Producer) *AssetsCache { +type AssetsCache struct { + cfg *sink.Config + rewriter *assets.Rewriter + producer types.Producer + cache map[string]*CachedAsset + totalAssets syncfloat64.Counter + cachedAssets syncfloat64.Counter + skippedAssets syncfloat64.Counter + assetSize syncfloat64.Histogram + assetDuration syncfloat64.Histogram +} + +func New(cfg *sink.Config, rewriter *assets.Rewriter, producer types.Producer, metrics *monitoring.Metrics) *AssetsCache { + // Assets metrics + totalAssets, err := metrics.RegisterCounter("assets_total") + if err != nil { + log.Printf("can't create assets_total metric: %s", err) + } + cachedAssets, err := metrics.RegisterCounter("assets_cached") + if err != nil { + log.Printf("can't create assets_cached metric: %s", err) + } + skippedAssets, err := metrics.RegisterCounter("assets_skipped") + if err != nil { + log.Printf("can't create assets_skipped metric: %s", err) + } + assetSize, err := metrics.RegisterHistogram("asset_size") + if err != nil { + log.Printf("can't create asset_size metric: %s", err) + } + assetDuration, err := metrics.RegisterHistogram("asset_duration") + if err != nil { + log.Printf("can't create asset_duration metric: %s", err) + } return &AssetsCache{ - cfg: cfg, - rewriter: rewriter, - producer: producer, + cfg: cfg, + rewriter: rewriter, + producer: producer, + cache: make(map[string]*CachedAsset, 64), + totalAssets: totalAssets, + cachedAssets: cachedAssets, + skippedAssets: skippedAssets, + assetSize: assetSize, + assetDuration: assetDuration, } } -func (e *AssetsCache) ParseAssets(sessID uint64, msg messages.Message) messages.Message { +func (e *AssetsCache) ParseAssets(msg messages.Message) messages.Message { switch m := msg.(type) { case *messages.SetNodeAttributeURLBased: if m.Name == "src" || m.Name == "href" { newMsg := &messages.SetNodeAttribute{ ID: m.ID, Name: m.Name, - Value: e.handleURL(sessID, m.BaseURL, m.Value), + Value: e.handleURL(m.SessionID(), m.BaseURL, m.Value), } newMsg.SetMeta(msg.Meta()) return newMsg @@ -37,7 +82,7 @@ func (e *AssetsCache) ParseAssets(sessID uint64, msg messages.Message) messages. newMsg := &messages.SetNodeAttribute{ ID: m.ID, Name: m.Name, - Value: e.handleCSS(sessID, m.BaseURL, m.Value), + Value: e.handleCSS(m.SessionID(), m.BaseURL, m.Value), } newMsg.SetMeta(msg.Meta()) return newMsg @@ -45,7 +90,7 @@ func (e *AssetsCache) ParseAssets(sessID uint64, msg messages.Message) messages. case *messages.SetCSSDataURLBased: newMsg := &messages.SetCSSData{ ID: m.ID, - Data: e.handleCSS(sessID, m.BaseURL, m.Data), + Data: e.handleCSS(m.SessionID(), m.BaseURL, m.Data), } newMsg.SetMeta(msg.Meta()) return newMsg @@ -53,14 +98,14 @@ func (e *AssetsCache) ParseAssets(sessID uint64, msg messages.Message) messages. newMsg := &messages.CSSInsertRule{ ID: m.ID, Index: m.Index, - Rule: e.handleCSS(sessID, m.BaseURL, m.Rule), + Rule: e.handleCSS(m.SessionID(), m.BaseURL, m.Rule), } newMsg.SetMeta(msg.Meta()) return newMsg case *messages.AdoptedSSReplaceURLBased: newMsg := &messages.AdoptedSSReplace{ SheetID: m.SheetID, - Text: e.handleCSS(sessID, m.BaseURL, m.Text), + Text: e.handleCSS(m.SessionID(), m.BaseURL, m.Text), } newMsg.SetMeta(msg.Meta()) return newMsg @@ -68,7 +113,7 @@ func (e *AssetsCache) ParseAssets(sessID uint64, msg messages.Message) messages. newMsg := &messages.AdoptedSSInsertRule{ SheetID: m.SheetID, Index: m.Index, - Rule: e.handleCSS(sessID, m.BaseURL, m.Rule), + Rule: e.handleCSS(m.SessionID(), m.BaseURL, m.Rule), } newMsg.SetMeta(msg.Meta()) return newMsg @@ -78,10 +123,11 @@ func (e *AssetsCache) ParseAssets(sessID uint64, msg messages.Message) messages. func (e *AssetsCache) sendAssetForCache(sessionID uint64, baseURL string, relativeURL string) { if fullURL, cacheable := assets.GetFullCachableURL(baseURL, relativeURL); cacheable { + assetMessage := &messages.AssetCache{URL: fullURL} if err := e.producer.Produce( e.cfg.TopicCache, sessionID, - messages.Encode(&messages.AssetCache{URL: fullURL}), + assetMessage.Encode(), ); err != nil { log.Printf("can't send asset to cache topic, sessID: %d, err: %s", sessionID, err) } @@ -94,18 +140,67 @@ func (e *AssetsCache) sendAssetsForCacheFromCSS(sessionID uint64, baseURL string } } -func (e *AssetsCache) handleURL(sessionID uint64, baseURL string, url string) string { +func (e *AssetsCache) handleURL(sessionID uint64, baseURL string, urlVal string) string { if e.cfg.CacheAssets { - e.sendAssetForCache(sessionID, baseURL, url) - return e.rewriter.RewriteURL(sessionID, baseURL, url) + e.sendAssetForCache(sessionID, baseURL, urlVal) + return e.rewriter.RewriteURL(sessionID, baseURL, urlVal) + } else { + return assets.ResolveURL(baseURL, urlVal) } - return assets.ResolveURL(baseURL, url) } func (e *AssetsCache) handleCSS(sessionID uint64, baseURL string, css string) string { + ctx := context.Background() + e.totalAssets.Add(ctx, 1) + // Try to find asset in cache + h := md5.New() + // Cut first part of url (scheme + host) + u, err := url.Parse(baseURL) + if err != nil { + log.Printf("can't parse url: %s, err: %s", baseURL, err) + if e.cfg.CacheAssets { + e.sendAssetsForCacheFromCSS(sessionID, baseURL, css) + } + return e.getRewrittenCSS(sessionID, baseURL, css) + } + justUrl := u.Scheme + "://" + u.Host + "/" + // Calculate hash sum of url + css + io.WriteString(h, justUrl) + io.WriteString(h, css) + hash := string(h.Sum(nil)) + // Check the resulting hash in cache + if cachedAsset, ok := e.cache[hash]; ok { + if int64(time.Now().Sub(cachedAsset.ts).Minutes()) < e.cfg.CacheExpiration { + e.skippedAssets.Add(ctx, 1) + return cachedAsset.msg + } + } + // Send asset to download in assets service if e.cfg.CacheAssets { e.sendAssetsForCacheFromCSS(sessionID, baseURL, css) - return e.rewriter.RewriteCSS(sessionID, baseURL, css) } - return assets.ResolveCSS(baseURL, css) + // Rewrite asset + start := time.Now() + res := e.getRewrittenCSS(sessionID, baseURL, css) + duration := time.Now().Sub(start).Milliseconds() + e.assetSize.Record(ctx, float64(len(res))) + e.assetDuration.Record(ctx, float64(duration)) + // Save asset to cache if we spent more than threshold + if duration > e.cfg.CacheThreshold { + e.cache[hash] = &CachedAsset{ + msg: res, + ts: time.Now(), + } + e.cachedAssets.Add(ctx, 1) + } + // Return rewritten asset + return res +} + +func (e *AssetsCache) getRewrittenCSS(sessionID uint64, url, css string) string { + if e.cfg.CacheAssets { + return e.rewriter.RewriteCSS(sessionID, url, css) + } else { + return assets.ResolveCSS(url, css) + } } diff --git a/backend/internal/sink/oswriter/oswriter.go b/backend/internal/sink/oswriter/oswriter.go index 4feb3e2aa..0719f6124 100644 --- a/backend/internal/sink/oswriter/oswriter.go +++ b/backend/internal/sink/oswriter/oswriter.go @@ -3,6 +3,7 @@ package oswriter import ( "math" "os" + "path/filepath" "strconv" "time" ) @@ -10,26 +11,26 @@ import ( type Writer struct { ulimit int dir string - files map[uint64]*os.File - atimes map[uint64]int64 + files map[string]*os.File + atimes map[string]int64 } func NewWriter(ulimit uint16, dir string) *Writer { return &Writer{ ulimit: int(ulimit), dir: dir + "/", - files: make(map[uint64]*os.File), - atimes: make(map[uint64]int64), + files: make(map[string]*os.File), + atimes: make(map[string]int64), } } -func (w *Writer) open(key uint64) (*os.File, error) { - file, ok := w.files[key] +func (w *Writer) open(fname string) (*os.File, error) { + file, ok := w.files[fname] if ok { return file, nil } if len(w.atimes) == w.ulimit { - var m_k uint64 + var m_k string var m_t int64 = math.MaxInt64 for k, t := range w.atimes { if t < m_t { @@ -37,21 +38,28 @@ func (w *Writer) open(key uint64) (*os.File, error) { m_t = t } } - if err := w.Close(m_k); err != nil { + if err := w.close(m_k); err != nil { return nil, err } } - file, err := os.OpenFile(w.dir+strconv.FormatUint(key, 10), os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) + + // mkdir if not exist + pathTo := w.dir + filepath.Dir(fname) + if _, err := os.Stat(pathTo); os.IsNotExist(err) { + os.MkdirAll(pathTo, 0644) + } + + file, err := os.OpenFile(w.dir+fname, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) if err != nil { return nil, err } - w.files[key] = file - w.atimes[key] = time.Now().Unix() + w.files[fname] = file + w.atimes[fname] = time.Now().Unix() return file, nil } -func (w *Writer) Close(key uint64) error { - file := w.files[key] +func (w *Writer) close(fname string) error { + file := w.files[fname] if file == nil { return nil } @@ -61,17 +69,24 @@ func (w *Writer) Close(key uint64) error { if err := file.Close(); err != nil { return err } - delete(w.files, key) - delete(w.atimes, key) + delete(w.files, fname) + delete(w.atimes, fname) return nil } -func (w *Writer) Write(key uint64, data []byte) error { - file, err := w.open(key) +func (w *Writer) WriteDOM(sid uint64, data []byte) error { + return w.write(strconv.FormatUint(sid, 10)+"/dom.mob", data) +} + +func (w *Writer) WriteDEV(sid uint64, data []byte) error { + return w.write(strconv.FormatUint(sid, 10)+"/devtools.mob", data) +} + +func (w *Writer) write(fname string, data []byte) error { + file, err := w.open(fname) if err != nil { return err } - // TODO: add check for the number of recorded bytes to file _, err = file.Write(data) return err } diff --git a/backend/internal/storage/encryptor.go b/backend/internal/storage/encryptor.go new file mode 100644 index 000000000..be835116c --- /dev/null +++ b/backend/internal/storage/encryptor.go @@ -0,0 +1,17 @@ +package storage + +import ( + "errors" +) + +func GenerateEncryptionKey() []byte { + return nil +} + +func EncryptData(data, fullKey []byte) ([]byte, error) { + return nil, errors.New("not supported") +} + +func DecryptData(data, fullKey []byte) ([]byte, error) { + return nil, errors.New("not supported") +} diff --git a/backend/internal/storage/storage.go b/backend/internal/storage/storage.go index 8d79468db..a4738c10e 100644 --- a/backend/internal/storage/storage.go +++ b/backend/internal/storage/storage.go @@ -8,6 +8,7 @@ import ( "log" config "openreplay/backend/internal/config/storage" "openreplay/backend/pkg/flakeid" + "openreplay/backend/pkg/messages" "openreplay/backend/pkg/monitoring" "openreplay/backend/pkg/storage" "os" @@ -16,13 +17,16 @@ import ( ) type Storage struct { - cfg *config.Config - s3 *storage.S3 - startBytes []byte - totalSessions syncfloat64.Counter - sessionSize syncfloat64.Histogram - readingTime syncfloat64.Histogram - archivingTime syncfloat64.Histogram + cfg *config.Config + s3 *storage.S3 + startBytes []byte + + totalSessions syncfloat64.Counter + sessionDOMSize syncfloat64.Histogram + sessionDevtoolsSize syncfloat64.Histogram + readingDOMTime syncfloat64.Histogram + readingTime syncfloat64.Histogram + archivingTime syncfloat64.Histogram } func New(cfg *config.Config, s3 *storage.S3, metrics *monitoring.Metrics) (*Storage, error) { @@ -37,10 +41,14 @@ func New(cfg *config.Config, s3 *storage.S3, metrics *monitoring.Metrics) (*Stor if err != nil { log.Printf("can't create sessions_total metric: %s", err) } - sessionSize, err := metrics.RegisterHistogram("sessions_size") + sessionDOMSize, err := metrics.RegisterHistogram("sessions_size") if err != nil { log.Printf("can't create session_size metric: %s", err) } + sessionDevtoolsSize, err := metrics.RegisterHistogram("sessions_dt_size") + if err != nil { + log.Printf("can't create sessions_dt_size metric: %s", err) + } readingTime, err := metrics.RegisterHistogram("reading_duration") if err != nil { log.Printf("can't create reading_duration metric: %s", err) @@ -50,17 +58,30 @@ func New(cfg *config.Config, s3 *storage.S3, metrics *monitoring.Metrics) (*Stor log.Printf("can't create archiving_duration metric: %s", err) } return &Storage{ - cfg: cfg, - s3: s3, - startBytes: make([]byte, cfg.FileSplitSize), - totalSessions: totalSessions, - sessionSize: sessionSize, - readingTime: readingTime, - archivingTime: archivingTime, + cfg: cfg, + s3: s3, + startBytes: make([]byte, cfg.FileSplitSize), + totalSessions: totalSessions, + sessionDOMSize: sessionDOMSize, + sessionDevtoolsSize: sessionDevtoolsSize, + readingTime: readingTime, + archivingTime: archivingTime, }, nil } -func (s *Storage) UploadKey(key string, retryCount int) error { +func (s *Storage) UploadSessionFiles(msg *messages.SessionEnd) error { + sessionDir := strconv.FormatUint(msg.SessionID(), 10) + if err := s.uploadKey(msg.SessionID(), sessionDir+"/dom.mob", true, 5, msg.EncryptionKey); err != nil { + return err + } + if err := s.uploadKey(msg.SessionID(), sessionDir+"/devtools.mob", false, 4, msg.EncryptionKey); err != nil { + return err + } + return nil +} + +// TODO: make a bit cleaner +func (s *Storage) uploadKey(sessID uint64, key string, shouldSplit bool, retryCount int, encryptionKey string) error { if retryCount <= 0 { return nil } @@ -68,7 +89,6 @@ func (s *Storage) UploadKey(key string, retryCount int) error { start := time.Now() file, err := os.Open(s.cfg.FSDir + "/" + key) if err != nil { - sessID, _ := strconv.ParseUint(key, 10, 64) return fmt.Errorf("File open error: %v; sessID: %s, part: %d, sessStart: %s\n", err, key, sessID%16, time.UnixMilli(int64(flakeid.ExtractTimestamp(sessID))), @@ -76,45 +96,123 @@ func (s *Storage) UploadKey(key string, retryCount int) error { } defer file.Close() - nRead, err := file.Read(s.startBytes) - if err != nil { - sessID, _ := strconv.ParseUint(key, 10, 64) - log.Printf("File read error: %s; sessID: %s, part: %d, sessStart: %s", - err, - key, - sessID%16, - time.UnixMilli(int64(flakeid.ExtractTimestamp(sessID))), - ) - time.AfterFunc(s.cfg.RetryTimeout, func() { - s.UploadKey(key, retryCount-1) - }) - return nil - } - s.readingTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds())) - - start = time.Now() - startReader := bytes.NewBuffer(s.startBytes[:nRead]) - if err := s.s3.Upload(s.gzipFile(startReader), key, "application/octet-stream", true); err != nil { - log.Fatalf("Storage: start upload failed. %v\n", err) - } - if nRead == s.cfg.FileSplitSize { - if err := s.s3.Upload(s.gzipFile(file), key+"e", "application/octet-stream", true); err != nil { - log.Fatalf("Storage: end upload failed. %v\n", err) - } - } - s.archivingTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds())) - - // Save metrics - var fileSize float64 = 0 + var fileSize int64 = 0 fileInfo, err := file.Stat() if err != nil { log.Printf("can't get file info: %s", err) } else { - fileSize = float64(fileInfo.Size()) + fileSize = fileInfo.Size() + } + var encryptedData []byte + if shouldSplit { + nRead, err := file.Read(s.startBytes) + if err != nil { + log.Printf("File read error: %s; sessID: %s, part: %d, sessStart: %s", + err, + key, + sessID%16, + time.UnixMilli(int64(flakeid.ExtractTimestamp(sessID))), + ) + time.AfterFunc(s.cfg.RetryTimeout, func() { + s.uploadKey(sessID, key, shouldSplit, retryCount-1, encryptionKey) + }) + return nil + } + s.readingTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds())) + + start = time.Now() + // Encrypt session file if we have encryption key + if encryptionKey != "" { + encryptedData, err = EncryptData(s.startBytes[:nRead], []byte(encryptionKey)) + if err != nil { + log.Printf("can't encrypt data: %s", err) + encryptedData = s.startBytes[:nRead] + } + } else { + encryptedData = s.startBytes[:nRead] + } + // Compress and save to s3 + startReader := bytes.NewBuffer(encryptedData) + if err := s.s3.Upload(s.gzipFile(startReader), key+"s", "application/octet-stream", true); err != nil { + log.Fatalf("Storage: start upload failed. %v\n", err) + } + // TODO: fix possible error (if we read less then FileSplitSize) + if nRead == s.cfg.FileSplitSize { + restPartSize := fileSize - int64(nRead) + fileData := make([]byte, restPartSize) + nRead, err = file.Read(fileData) + if err != nil { + log.Printf("File read error: %s; sessID: %s, part: %d, sessStart: %s", + err, + key, + sessID%16, + time.UnixMilli(int64(flakeid.ExtractTimestamp(sessID))), + ) + return nil + } + if int64(nRead) != restPartSize { + log.Printf("can't read the rest part of file") + } + + // Encrypt session file if we have encryption key + if encryptionKey != "" { + encryptedData, err = EncryptData(fileData, []byte(encryptionKey)) + if err != nil { + log.Printf("can't encrypt data: %s", err) + encryptedData = fileData + } + } else { + encryptedData = fileData + } + // Compress and save to s3 + endReader := bytes.NewBuffer(encryptedData) + if err := s.s3.Upload(s.gzipFile(endReader), key+"e", "application/octet-stream", true); err != nil { + log.Fatalf("Storage: end upload failed. %v\n", err) + } + } + s.archivingTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds())) + } else { + start = time.Now() + fileData := make([]byte, fileSize) + nRead, err := file.Read(fileData) + if err != nil { + log.Printf("File read error: %s; sessID: %s, part: %d, sessStart: %s", + err, + key, + sessID%16, + time.UnixMilli(int64(flakeid.ExtractTimestamp(sessID))), + ) + return nil + } + if int64(nRead) != fileSize { + log.Printf("can't read the rest part of file") + } + + // Encrypt session file if we have encryption key + if encryptionKey != "" { + encryptedData, err = EncryptData(fileData, []byte(encryptionKey)) + if err != nil { + log.Printf("can't encrypt data: %s", err) + encryptedData = fileData + } + } else { + encryptedData = fileData + } + endReader := bytes.NewBuffer(encryptedData) + if err := s.s3.Upload(s.gzipFile(endReader), key+"s", "application/octet-stream", true); err != nil { + log.Fatalf("Storage: end upload failed. %v\n", err) + } + s.archivingTime.Record(context.Background(), float64(time.Now().Sub(start).Milliseconds())) + } + + // Save metrics + ctx, _ := context.WithTimeout(context.Background(), time.Millisecond*200) + if shouldSplit { + s.totalSessions.Add(ctx, 1) + s.sessionDOMSize.Record(ctx, float64(fileSize)) + } else { + s.sessionDevtoolsSize.Record(ctx, float64(fileSize)) } - ctx, _ := context.WithTimeout(context.Background(), time.Millisecond*200) - s.sessionSize.Record(ctx, fileSize) - s.totalSessions.Add(ctx, 1) return nil } diff --git a/backend/pkg/db/cache/messages-common.go b/backend/pkg/db/cache/messages-common.go index 41cdb1895..a4df19cc3 100644 --- a/backend/pkg/db/cache/messages-common.go +++ b/backend/pkg/db/cache/messages-common.go @@ -11,6 +11,10 @@ func (c *PGCache) InsertSessionEnd(sessionID uint64, timestamp uint64) (uint64, return c.Conn.InsertSessionEnd(sessionID, timestamp) } +func (c *PGCache) InsertSessionEncryptionKey(sessionID uint64, key []byte) error { + return c.Conn.InsertSessionEncryptionKey(sessionID, key) +} + func (c *PGCache) HandleSessionEnd(sessionID uint64) error { if err := c.Conn.HandleSessionEnd(sessionID); err != nil { log.Printf("can't handle session end: %s", err) diff --git a/backend/pkg/db/cache/messages-web.go b/backend/pkg/db/cache/messages-web.go index 87098552b..50b531d0a 100644 --- a/backend/pkg/db/cache/messages-web.go +++ b/backend/pkg/db/cache/messages-web.go @@ -71,6 +71,12 @@ func (c *PGCache) HandleWebSessionEnd(sessionID uint64, e *SessionEnd) error { return c.HandleSessionEnd(sessionID) } +func (c *PGCache) InsertWebJSException(e *JSException) error { + return c.InsertWebErrorEvent(e.SessionID(), WrapJSException(e)) +} +func (c *PGCache) InsertWebIntegrationEvent(e *IntegrationEvent) error { + return c.InsertWebErrorEvent(e.SessionID(), WrapIntegrationEvent(e)) +} func (c *PGCache) InsertWebErrorEvent(sessionID uint64, e *ErrorEvent) error { session, err := c.GetSession(sessionID) if err != nil { diff --git a/backend/pkg/db/cache/pg-cache.go b/backend/pkg/db/cache/pg-cache.go index ca31bcd82..bb87f9f4c 100644 --- a/backend/pkg/db/cache/pg-cache.go +++ b/backend/pkg/db/cache/pg-cache.go @@ -13,11 +13,6 @@ type ProjectMeta struct { expirationTime time.Time } -// !TODO: remove old sessions by timeout to avoid memleaks - -/* - * Cache layer around the stateless PG adapter -**/ type PGCache struct { *postgres.Conn sessions map[uint64]*Session @@ -26,7 +21,6 @@ type PGCache struct { projectExpirationTimeout time.Duration } -// TODO: create conn automatically func NewPGCache(pgConn *postgres.Conn, projectExpirationTimeoutMs int64) *PGCache { return &PGCache{ Conn: pgConn, diff --git a/backend/pkg/db/postgres/connector.go b/backend/pkg/db/postgres/connector.go index 63f42e25b..9e269fcc0 100644 --- a/backend/pkg/db/postgres/connector.go +++ b/backend/pkg/db/postgres/connector.go @@ -55,8 +55,7 @@ func NewConn(url string, queueLimit, sizeLimit int, metrics *monitoring.Metrics) } c, err := pgxpool.Connect(context.Background(), url) if err != nil { - log.Println(err) - log.Fatalln("pgxpool.Connect Error") + log.Fatalf("pgxpool.Connect err: %s", err) } conn := &Conn{ batches: make(map[uint64]*pgx.Batch), diff --git a/backend/pkg/db/postgres/messages-common.go b/backend/pkg/db/postgres/messages-common.go index dbdedb02d..e69d9508c 100644 --- a/backend/pkg/db/postgres/messages-common.go +++ b/backend/pkg/db/postgres/messages-common.go @@ -82,6 +82,10 @@ func (conn *Conn) InsertSessionEnd(sessionID uint64, timestamp uint64) (uint64, return dur, nil } +func (conn *Conn) InsertSessionEncryptionKey(sessionID uint64, key []byte) error { + return conn.c.Exec(`UPDATE sessions SET file_key = $2 WHERE session_id = $1`, sessionID, string(key)) +} + func (conn *Conn) HandleSessionEnd(sessionID uint64) error { sqlRequest := ` UPDATE sessions @@ -99,14 +103,14 @@ func (conn *Conn) HandleSessionEnd(sessionID uint64) error { } func (conn *Conn) InsertRequest(sessionID uint64, timestamp uint64, index uint64, url string, duration uint64, success bool) error { - if err := conn.requests.Append(sessionID, timestamp, getSqIdx(index), url, duration, success); err != nil { + if err := conn.requests.Append(sessionID, timestamp, index, url, duration, success); err != nil { return fmt.Errorf("insert request in bulk err: %s", err) } return nil } func (conn *Conn) InsertCustomEvent(sessionID uint64, timestamp uint64, index uint64, name string, payload string) error { - if err := conn.customEvents.Append(sessionID, timestamp, getSqIdx(index), name, payload); err != nil { + if err := conn.customEvents.Append(sessionID, timestamp, index, name, payload); err != nil { return fmt.Errorf("insert custom event in bulk err: %s", err) } return nil @@ -160,20 +164,16 @@ func (conn *Conn) InsertIssueEvent(sessionID uint64, projectID uint32, e *messag if *payload == "" || *payload == "{}" { payload = nil } - context := &e.Context - if *context == "" || *context == "{}" { - context = nil - } if err = tx.exec(` INSERT INTO issues ( - project_id, issue_id, type, context_string, context + project_id, issue_id, type, context_string ) (SELECT - project_id, $2, $3, $4, CAST($5 AS jsonb) + project_id, $2, $3, $4 FROM sessions WHERE session_id = $1 )ON CONFLICT DO NOTHING`, - sessionID, issueID, e.Type, e.ContextString, context, + sessionID, issueID, e.Type, e.ContextString, ); err != nil { return err } @@ -184,7 +184,7 @@ func (conn *Conn) InsertIssueEvent(sessionID uint64, projectID uint32, e *messag $1, $2, $3, $4, CAST($5 AS jsonb) )`, sessionID, issueID, e.Timestamp, - getSqIdx(e.MessageID), + e.MessageID, payload, ); err != nil { return err @@ -204,7 +204,7 @@ func (conn *Conn) InsertIssueEvent(sessionID uint64, projectID uint32, e *messag VALUES ($1, $2, $3, left($4, 2700), $5, 'error') `, - sessionID, getSqIdx(e.MessageID), e.Timestamp, e.ContextString, e.Payload, + sessionID, e.MessageID, e.Timestamp, e.ContextString, e.Payload, ); err != nil { return err } diff --git a/backend/pkg/db/postgres/messages-web-stats.go b/backend/pkg/db/postgres/messages-web-stats.go index 396f2e74d..ba7db4da4 100644 --- a/backend/pkg/db/postgres/messages-web-stats.go +++ b/backend/pkg/db/postgres/messages-web-stats.go @@ -5,11 +5,6 @@ import ( "openreplay/backend/pkg/url" ) -func (conn *Conn) InsertWebStatsLongtask(sessionID uint64, l *LongTask) error { - return nil // Do we even use them? - // conn.exec(``); -} - func (conn *Conn) InsertWebStatsPerformance(sessionID uint64, p *PerformanceTrackAggr) error { timestamp := (p.TimestampEnd + p.TimestampStart) / 2 diff --git a/backend/pkg/db/postgres/messages-web.go b/backend/pkg/db/postgres/messages-web.go index 9758587b8..f57c3a8de 100644 --- a/backend/pkg/db/postgres/messages-web.go +++ b/backend/pkg/db/postgres/messages-web.go @@ -2,18 +2,12 @@ package postgres import ( "log" - "math" - "openreplay/backend/pkg/hashid" + "openreplay/backend/pkg/db/types" . "openreplay/backend/pkg/messages" "openreplay/backend/pkg/url" ) -// TODO: change messages and replace everywhere to e.Index -func getSqIdx(messageID uint64) uint { - return uint(messageID % math.MaxInt32) -} - func (conn *Conn) InsertWebCustomEvent(sessionID uint64, projectID uint32, e *CustomEvent) error { err := conn.InsertCustomEvent(sessionID, e.Timestamp, e.MessageID, @@ -93,7 +87,7 @@ func (conn *Conn) InsertWebInputEvent(sessionID uint64, projectID uint32, e *Inp return nil } -func (conn *Conn) InsertWebErrorEvent(sessionID uint64, projectID uint32, e *ErrorEvent) (err error) { +func (conn *Conn) InsertWebErrorEvent(sessionID uint64, projectID uint32, e *types.ErrorEvent) (err error) { tx, err := conn.c.Begin() if err != nil { return err @@ -105,7 +99,7 @@ func (conn *Conn) InsertWebErrorEvent(sessionID uint64, projectID uint32, e *Err } } }() - errorID := hashid.WebErrorID(projectID, e) + errorID := e.ID(projectID) if err = tx.exec(` INSERT INTO errors @@ -135,6 +129,18 @@ func (conn *Conn) InsertWebErrorEvent(sessionID uint64, projectID uint32, e *Err return err } err = tx.commit() + + // Insert tags + sqlRequest := ` + INSERT INTO public.errors_tags ( + session_id, message_id, error_id, key, value + ) VALUES ( + $1, $2, $3, $4, $5 + ) ON CONFLICT DO NOTHING` + for key, value := range e.Tags { + conn.batchQueue(sessionID, sqlRequest, sessionID, e.MessageID, errorID, key, value) + } + return } @@ -163,7 +169,7 @@ func (conn *Conn) InsertWebFetchEvent(sessionID uint64, projectID uint32, savePa $12, $13 ) ON CONFLICT DO NOTHING` conn.batchQueue(sessionID, sqlRequest, - sessionID, e.Timestamp, getSqIdx(e.MessageID), + sessionID, e.Timestamp, e.MessageID, e.URL, host, path, query, request, response, e.Status, url.EnsureMethod(e.Method), e.Duration, e.Status < 400, diff --git a/backend/pkg/db/types/error-event.go b/backend/pkg/db/types/error-event.go new file mode 100644 index 000000000..fdda13148 --- /dev/null +++ b/backend/pkg/db/types/error-event.go @@ -0,0 +1,89 @@ +package types + +import ( + "encoding/hex" + "encoding/json" + "hash/fnv" + "log" + "strconv" + + . "openreplay/backend/pkg/messages" +) + +type ErrorEvent struct { + MessageID uint64 + Timestamp uint64 + Source string + Name string + Message string + Payload string + Tags map[string]*string +} + +func unquote(s string) string { + if s[0] == '"' { + return s[1 : len(s)-1] + } + return s +} +func parseTags(tagsJSON string) (tags map[string]*string, err error) { + if tagsJSON[0] == '[' { + var tagsArr []json.RawMessage + if err = json.Unmarshal([]byte(tagsJSON), &tagsArr); err != nil { + return + } + + tags = make(map[string]*string) + for _, keyBts := range tagsArr { + tags[unquote(string(keyBts))] = nil + } + } else if tagsJSON[0] == '{' { + var tagsObj map[string]json.RawMessage + if err = json.Unmarshal([]byte(tagsJSON), &tagsObj); err != nil { + return + } + + tags = make(map[string]*string) + for key, valBts := range tagsObj { + val := unquote(string(valBts)) + tags[key] = &val + } + } + return +} + +func WrapJSException(m *JSException) *ErrorEvent { + meta, err := parseTags(m.Metadata) + if err != nil { + log.Printf("Error on parsing Exception metadata: %v", err) + } + return &ErrorEvent{ + MessageID: m.Meta().Index, + Timestamp: uint64(m.Meta().Timestamp), + Source: "js_exception", + Name: m.Name, + Message: m.Message, + Payload: m.Payload, + Tags: meta, + } +} + +func WrapIntegrationEvent(m *IntegrationEvent) *ErrorEvent { + return &ErrorEvent{ + MessageID: m.Meta().Index, // This will be always 0 here since it's coming from backend TODO: find another way to index + Timestamp: m.Timestamp, + Source: m.Source, + Name: m.Name, + Message: m.Message, + Payload: m.Payload, + } +} + +func (e *ErrorEvent) ID(projectID uint32) string { + hash := fnv.New128a() + hash.Write([]byte(e.Source)) + hash.Write([]byte(e.Name)) + hash.Write([]byte(e.Message)) + hash.Write([]byte(e.Payload)) + return strconv.FormatUint(uint64(projectID), 16) + hex.EncodeToString(hash.Sum(nil)) +} diff --git a/backend/pkg/handlers/custom/eventMapper.go b/backend/pkg/handlers/custom/eventMapper.go index f1bd4cafe..3280e7ebc 100644 --- a/backend/pkg/handlers/custom/eventMapper.go +++ b/backend/pkg/handlers/custom/eventMapper.go @@ -56,15 +56,6 @@ func (b *EventMapper) Handle(message Message, messageID uint64, timestamp uint64 Selector: msg.Selector, } } - case *JSException: - return &ErrorEvent{ - MessageID: messageID, - Timestamp: timestamp, - Source: "js_exception", - Name: msg.Name, - Message: msg.Message, - Payload: msg.Payload, - } case *ResourceTiming: return &ResourceEvent{ MessageID: messageID, diff --git a/backend/pkg/hashid/hashid.go b/backend/pkg/hashid/hashid.go index 8620b0e69..25ce11369 100644 --- a/backend/pkg/hashid/hashid.go +++ b/backend/pkg/hashid/hashid.go @@ -23,12 +23,3 @@ func IOSCrashID(projectID uint32, crash *messages.IOSCrash) string { hash.Write([]byte(crash.Stacktrace)) return strconv.FormatUint(uint64(projectID), 16) + hex.EncodeToString(hash.Sum(nil)) } - -func WebErrorID(projectID uint32, errorEvent *messages.ErrorEvent) string { - hash := fnv.New128a() - hash.Write([]byte(errorEvent.Source)) - hash.Write([]byte(errorEvent.Name)) - hash.Write([]byte(errorEvent.Message)) - hash.Write([]byte(errorEvent.Payload)) - return strconv.FormatUint(uint64(projectID), 16) + hex.EncodeToString(hash.Sum(nil)) -} diff --git a/backend/pkg/log/queue.go b/backend/pkg/log/queue.go index ced815bd2..e9ac5dc1f 100644 --- a/backend/pkg/log/queue.go +++ b/backend/pkg/log/queue.go @@ -5,8 +5,7 @@ import ( "log" "time" - "openreplay/backend/pkg/queue/types" - //"openreplay/backend/pkg/env" + "openreplay/backend/pkg/messages" ) type partitionStats struct { @@ -18,15 +17,15 @@ type partitionStats struct { } // Update partition statistic -func (prt *partitionStats) update(m *types.Meta) { - if prt.maxts < m.Timestamp { - prt.maxts = m.Timestamp +func (prt *partitionStats) update(m *messages.BatchInfo) { + if prt.maxts < m.Timestamp() { + prt.maxts = m.Timestamp() } - if prt.mints > m.Timestamp || prt.mints == 0 { - prt.mints = m.Timestamp + if prt.mints > m.Timestamp() || prt.mints == 0 { + prt.mints = m.Timestamp() } - prt.lastts = m.Timestamp - prt.lastID = m.ID + prt.lastts = m.Timestamp() + prt.lastID = m.ID() prt.count += 1 } @@ -35,6 +34,10 @@ type queueStats struct { tick <-chan time.Time } +type QueueStats interface { + Collect(msg messages.Message) +} + func NewQueueStats(sec int) *queueStats { return &queueStats{ prts: make(map[int32]*partitionStats), @@ -43,14 +46,14 @@ func NewQueueStats(sec int) *queueStats { } // Collect writes new data to partition statistic -func (qs *queueStats) Collect(sessionID uint64, m *types.Meta) { - prti := int32(sessionID % 16) // TODO use GetKeyPartition from kafka/key.go +func (qs *queueStats) Collect(msg messages.Message) { + prti := int32(msg.SessionID() % 16) // TODO use GetKeyPartition from kafka/key.go prt, ok := qs.prts[prti] if !ok { qs.prts[prti] = &partitionStats{} prt = qs.prts[prti] } - prt.update(m) + prt.update(msg.Meta().Batch()) select { case <-qs.tick: diff --git a/backend/pkg/messages/batch.go b/backend/pkg/messages/batch.go deleted file mode 100644 index 887e5ddb3..000000000 --- a/backend/pkg/messages/batch.go +++ /dev/null @@ -1,197 +0,0 @@ -package messages - -import ( - "bytes" - "io" - "log" - "strings" -) - -type Iterator interface { - Next() bool // Return true if we have next message - Type() int // Return type of the next message - Message() Message // Return raw or decoded message - Close() -} - -type iteratorImpl struct { - data *bytes.Reader - index uint64 - timestamp int64 - version uint64 - msgType uint64 - msgSize uint64 - canSkip bool - msg Message - url string -} - -func NewIterator(data []byte) Iterator { - return &iteratorImpl{ - data: bytes.NewReader(data), - } -} - -func (i *iteratorImpl) Next() bool { - if i.canSkip { - if _, err := i.data.Seek(int64(i.msgSize), io.SeekCurrent); err != nil { - log.Printf("seek err: %s", err) - return false - } - } - i.canSkip = false - - var err error - i.msgType, err = ReadUint(i.data) - if err != nil { - if err == io.EOF { - return false - } - log.Printf("can't read message type: %s", err) - return false - } - - if i.version > 0 && messageHasSize(i.msgType) { - // Read message size if it is a new protocol version - i.msgSize, err = ReadSize(i.data) - if err != nil { - log.Printf("can't read message size: %s", err) - return false - } - i.msg = &RawMessage{ - tp: i.msgType, - size: i.msgSize, - meta: &message{}, - reader: i.data, - skipped: &i.canSkip, - } - i.canSkip = true - } else { - i.msg, err = ReadMessage(i.msgType, i.data) - if err == io.EOF { - return false - } else if err != nil { - if strings.HasPrefix(err.Error(), "Unknown message code:") { - code := strings.TrimPrefix(err.Error(), "Unknown message code: ") - i.msg, err = DecodeExtraMessage(code, i.data) - if err != nil { - log.Printf("can't decode msg: %s", err) - return false - } - } else { - log.Printf("Batch Message decoding error on message with index %v, err: %s", i.index, err) - return false - } - } - i.msg = transformDeprecated(i.msg) - } - - // Process meta information - isBatchMeta := false - switch i.msgType { - case MsgBatchMetadata: - if i.index != 0 { // Might be several 0-0 BatchMeta in a row without an error though - log.Printf("Batch Metadata found at the end of the batch") - return false - } - msg := i.msg.Decode() - if msg == nil { - return false - } - m := msg.(*BatchMetadata) - i.index = m.PageNo<<32 + m.FirstIndex // 2^32 is the maximum count of messages per page (ha-ha) - i.timestamp = m.Timestamp - i.version = m.Version - i.url = m.Url - isBatchMeta = true - if i.version > 1 { - log.Printf("incorrect batch version, skip current batch") - return false - } - case MsgBatchMeta: // Is not required to be present in batch since IOS doesn't have it (though we might change it) - if i.index != 0 { // Might be several 0-0 BatchMeta in a row without an error though - log.Printf("Batch Meta found at the end of the batch") - return false - } - msg := i.msg.Decode() - if msg == nil { - return false - } - m := msg.(*BatchMeta) - i.index = m.PageNo<<32 + m.FirstIndex // 2^32 is the maximum count of messages per page (ha-ha) - i.timestamp = m.Timestamp - isBatchMeta = true - // continue readLoop - case MsgIOSBatchMeta: - if i.index != 0 { // Might be several 0-0 BatchMeta in a row without an error though - log.Printf("Batch Meta found at the end of the batch") - return false - } - msg := i.msg.Decode() - if msg == nil { - return false - } - m := msg.(*IOSBatchMeta) - i.index = m.FirstIndex - i.timestamp = int64(m.Timestamp) - isBatchMeta = true - // continue readLoop - case MsgTimestamp: - msg := i.msg.Decode() - if msg == nil { - return false - } - m := msg.(*Timestamp) - i.timestamp = int64(m.Timestamp) - // No skipping here for making it easy to encode back the same sequence of message - // continue readLoop - case MsgSessionStart: - msg := i.msg.Decode() - if msg == nil { - return false - } - m := msg.(*SessionStart) - i.timestamp = int64(m.Timestamp) - case MsgSessionEnd: - msg := i.msg.Decode() - if msg == nil { - return false - } - m := msg.(*SessionEnd) - i.timestamp = int64(m.Timestamp) - case MsgSetPageLocation: - msg := i.msg.Decode() - if msg == nil { - return false - } - m := msg.(*SetPageLocation) - i.url = m.URL - } - i.msg.Meta().Index = i.index - i.msg.Meta().Timestamp = i.timestamp - i.msg.Meta().Url = i.url - - if !isBatchMeta { // Without that indexes will be unique anyway, though shifted by 1 because BatchMeta is not counted in tracker - i.index++ - } - return true -} - -func (i *iteratorImpl) Type() int { - return int(i.msgType) -} - -func (i *iteratorImpl) Message() Message { - return i.msg -} - -func (i *iteratorImpl) Close() { - _, err := i.data.Seek(0, io.SeekEnd) - if err != nil { - log.Printf("can't set seek pointer at the end: %s", err) - } -} - -func messageHasSize(msgType uint64) bool { - return !(msgType == 80 || msgType == 81 || msgType == 82) -} diff --git a/backend/pkg/messages/extra.go b/backend/pkg/messages/extra.go deleted file mode 100644 index b2a57e2ad..000000000 --- a/backend/pkg/messages/extra.go +++ /dev/null @@ -1,56 +0,0 @@ -package messages - -import ( - "encoding/binary" - "fmt" - "io" -) - -type SessionSearch struct { - message - Timestamp uint64 - Partition uint64 -} - -func (msg *SessionSearch) Encode() []byte { - buf := make([]byte, 11) - buf[0] = 127 - p := 1 - p = WriteUint(msg.Timestamp, buf, p) - p = WriteUint(msg.Partition, buf, p) - return buf[:p] -} - -func (msg *SessionSearch) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - -func (msg *SessionSearch) Decode() Message { - return msg -} - -func (msg *SessionSearch) TypeID() int { - return 127 -} - -func DecodeExtraMessage(code string, reader io.Reader) (Message, error) { - var err error - if code != "127" { - return nil, fmt.Errorf("unknown message code: %s", code) - } - msg := &SessionSearch{} - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, fmt.Errorf("can't read message timestamp: %s", err) - } - if msg.Partition, err = ReadUint(reader); err != nil { - return nil, fmt.Errorf("can't read last partition: %s", err) - } - return msg, nil -} diff --git a/backend/pkg/messages/facade.go b/backend/pkg/messages/facade.go deleted file mode 100644 index ebc9e7983..000000000 --- a/backend/pkg/messages/facade.go +++ /dev/null @@ -1,5 +0,0 @@ -package messages - -func Encode(msg Message) []byte { - return msg.Encode() -} diff --git a/backend/pkg/messages/filters.go b/backend/pkg/messages/filters.go index e79d1d987..116221b2d 100644 --- a/backend/pkg/messages/filters.go +++ b/backend/pkg/messages/filters.go @@ -2,9 +2,13 @@ package messages func IsReplayerType(id int) bool { - return 0 == id || 4 == id || 5 == id || 6 == id || 7 == id || 8 == id || 9 == id || 10 == id || 11 == id || 12 == id || 13 == id || 14 == id || 15 == id || 16 == id || 18 == id || 19 == id || 20 == id || 22 == id || 37 == id || 38 == id || 39 == id || 40 == id || 41 == id || 44 == id || 45 == id || 46 == id || 47 == id || 48 == id || 49 == id || 54 == id || 55 == id || 59 == id || 60 == id || 61 == id || 67 == id || 69 == id || 70 == id || 71 == id || 72 == id || 73 == id || 74 == id || 75 == id || 76 == id || 77 == id || 79 == id || 90 == id || 93 == id || 96 == id || 100 == id || 102 == id || 103 == id || 105 == id + return 80 != id && 81 != id && 82 != id && 1 != id && 3 != id && 17 != id && 23 != id && 24 != id && 25 != id && 26 != id && 27 != id && 28 != id && 29 != id && 30 != id && 31 != id && 32 != id && 33 != id && 35 != id && 36 != id && 42 != id && 43 != id && 50 != id && 51 != id && 52 != id && 53 != id && 56 != id && 62 != id && 63 != id && 64 != id && 66 != id && 78 != id && 126 != id && 127 != id && 107 != id && 91 != id && 92 != id && 94 != id && 95 != id && 97 != id && 98 != id && 99 != id && 101 != id && 104 != id && 110 != id && 111 != id } func IsIOSType(id int) bool { return 107 == id || 90 == id || 91 == id || 92 == id || 93 == id || 94 == id || 95 == id || 96 == id || 97 == id || 98 == id || 99 == id || 100 == id || 101 == id || 102 == id || 103 == id || 104 == id || 105 == id || 110 == id || 111 == id } + +func IsDOMType(id int) bool { + return 0 == id || 4 == id || 5 == id || 6 == id || 7 == id || 8 == id || 9 == id || 10 == id || 11 == id || 12 == id || 13 == id || 14 == id || 15 == id || 16 == id || 18 == id || 19 == id || 20 == id || 37 == id || 38 == id || 49 == id || 54 == id || 55 == id || 59 == id || 60 == id || 61 == id || 67 == id || 69 == id || 70 == id || 71 == id || 72 == id || 73 == id || 74 == id || 75 == id || 76 == id || 77 == id || 90 == id || 93 == id || 96 == id || 100 == id || 102 == id || 103 == id || 105 == id +} \ No newline at end of file diff --git a/backend/pkg/messages/iterator.go b/backend/pkg/messages/iterator.go new file mode 100644 index 000000000..8b23cb97e --- /dev/null +++ b/backend/pkg/messages/iterator.go @@ -0,0 +1,215 @@ +package messages + +import ( + "bytes" + "fmt" + "io" + "log" +) + +// MessageHandler processes one message using service logic +type MessageHandler func(Message) + +// MessageIterator iterates by all messages in batch +type MessageIterator interface { + Iterate(batchData []byte, batchInfo *BatchInfo) +} + +type messageIteratorImpl struct { + filter map[int]struct{} + preFilter map[int]struct{} + handler MessageHandler + autoDecode bool + version uint64 + size uint64 + canSkip bool + broken bool + messageInfo *message + batchInfo *BatchInfo +} + +func NewMessageIterator(messageHandler MessageHandler, messageFilter []int, autoDecode bool) MessageIterator { + iter := &messageIteratorImpl{handler: messageHandler, autoDecode: autoDecode} + if len(messageFilter) != 0 { + filter := make(map[int]struct{}, len(messageFilter)) + for _, msgType := range messageFilter { + filter[msgType] = struct{}{} + } + iter.filter = filter + } + iter.preFilter = map[int]struct{}{ + MsgBatchMetadata: {}, MsgBatchMeta: {}, MsgTimestamp: {}, + MsgSessionStart: {}, MsgSessionEnd: {}, MsgSetPageLocation: {}, + MsgSessionEndDeprecated: {}} + return iter +} + +func (i *messageIteratorImpl) prepareVars(batchInfo *BatchInfo) { + i.batchInfo = batchInfo + i.messageInfo = &message{batch: batchInfo} + i.version = 0 + i.canSkip = false + i.broken = false + i.size = 0 +} + +func (i *messageIteratorImpl) Iterate(batchData []byte, batchInfo *BatchInfo) { + // Prepare iterator before processing messages in batch + i.prepareVars(batchInfo) + + // Initialize batch reader + reader := bytes.NewReader(batchData) + + // Process until end of batch or parsing error + for { + // Increase message index (can be overwritten by batch info message) + i.messageInfo.Index++ + + if i.broken { + log.Printf("skipping broken batch, info: %s", i.batchInfo.Info()) + return + } + + if i.canSkip { + if _, err := reader.Seek(int64(i.size), io.SeekCurrent); err != nil { + log.Printf("can't skip message: %s, info: %s", err, i.batchInfo.Info()) + return + } + } + i.canSkip = false + + // Read message type + msgType, err := ReadUint(reader) + if err != nil { + if err != io.EOF { + log.Printf("can't read message type: %s, info: %s", err, i.batchInfo.Info()) + } + return + } + + var msg Message + // Read message body (and decode if protocol version less than 1) + if i.version > 0 && messageHasSize(msgType) { + // Read message size if it is a new protocol version + i.size, err = ReadSize(reader) + if err != nil { + log.Printf("can't read message size: %s, info: %s", err, i.batchInfo.Info()) + return + } + msg = &RawMessage{ + tp: msgType, + size: i.size, + reader: reader, + skipped: &i.canSkip, + broken: &i.broken, + meta: i.messageInfo, + } + i.canSkip = true + } else { + msg, err = ReadMessage(msgType, reader) + if err != nil { + if err != io.EOF { + log.Printf("can't read message body: %s, info: %s", err, i.batchInfo.Info()) + } + return + } + msg = transformDeprecated(msg) + } + + // Preprocess "system" messages + if _, ok := i.preFilter[msg.TypeID()]; ok { + msg = msg.Decode() + if msg == nil { + log.Printf("decode error, type: %d, info: %s", msgType, i.batchInfo.Info()) + return + } + if err := i.preprocessing(msg); err != nil { + log.Printf("message preprocessing err: %s", err) + return + } + } + + // Skip messages we don't have in filter + if i.filter != nil { + if _, ok := i.filter[msg.TypeID()]; !ok { + continue + } + } + + if i.autoDecode { + msg = msg.Decode() + if msg == nil { + log.Printf("decode error, type: %d, info: %s", msgType, i.batchInfo.Info()) + return + } + } + + // Set meta information for message + msg.Meta().SetMeta(i.messageInfo) + + // Process message + i.handler(msg) + } +} + +func (i *messageIteratorImpl) zeroTsLog(msgType string) { + log.Printf("zero timestamp in %s, info: %s", msgType, i.batchInfo.Info()) +} + +func (i *messageIteratorImpl) preprocessing(msg Message) error { + switch m := msg.(type) { + case *BatchMetadata: + if i.messageInfo.Index > 1 { // Might be several 0-0 BatchMeta in a row without an error though + return fmt.Errorf("batchMetadata found at the end of the batch, info: %s", i.batchInfo.Info()) + } + if m.Version > 1 { + return fmt.Errorf("incorrect batch version: %d, skip current batch, info: %s", i.version, i.batchInfo.Info()) + } + i.messageInfo.Index = m.PageNo<<32 + m.FirstIndex // 2^32 is the maximum count of messages per page (ha-ha) + i.messageInfo.Timestamp = m.Timestamp + if m.Timestamp == 0 { + i.zeroTsLog("BatchMetadata") + } + i.messageInfo.Url = m.Url + i.version = m.Version + i.batchInfo.version = m.Version + + case *BatchMeta: // Is not required to be present in batch since IOS doesn't have it (though we might change it) + if i.messageInfo.Index > 1 { // Might be several 0-0 BatchMeta in a row without an error though + return fmt.Errorf("batchMeta found at the end of the batch, info: %s", i.batchInfo.Info()) + } + i.messageInfo.Index = m.PageNo<<32 + m.FirstIndex // 2^32 is the maximum count of messages per page (ha-ha) + i.messageInfo.Timestamp = m.Timestamp + if m.Timestamp == 0 { + i.zeroTsLog("BatchMeta") + } + + case *Timestamp: + i.messageInfo.Timestamp = int64(m.Timestamp) + if m.Timestamp == 0 { + i.zeroTsLog("Timestamp") + } + + case *SessionStart: + i.messageInfo.Timestamp = int64(m.Timestamp) + if m.Timestamp == 0 { + i.zeroTsLog("SessionStart") + log.Printf("zero session start, project: %d, UA: %s, tracker: %s, info: %s", + m.ProjectID, m.UserAgent, m.TrackerVersion, i.batchInfo.Info()) + } + + case *SessionEnd: + i.messageInfo.Timestamp = int64(m.Timestamp) + if m.Timestamp == 0 { + i.zeroTsLog("SessionEnd") + } + + case *SetPageLocation: + i.messageInfo.Url = m.URL + } + return nil +} + +func messageHasSize(msgType uint64) bool { + return !(msgType == 80 || msgType == 81 || msgType == 82) +} diff --git a/backend/pkg/messages/legacy-message-transform.go b/backend/pkg/messages/legacy-message-transform.go index 3a42cdab0..5e2bd3ed7 100644 --- a/backend/pkg/messages/legacy-message-transform.go +++ b/backend/pkg/messages/legacy-message-transform.go @@ -2,13 +2,18 @@ package messages func transformDeprecated(msg Message) Message { switch m := msg.(type) { - case *MouseClickDepricated: - return &MouseClick{ - ID: m.ID, - HesitationTime: m.HesitationTime, - Label: m.Label, + case *JSExceptionDeprecated: + return &JSException{ + Name: m.Name, + Message: m.Message, + Payload: m.Payload, + Metadata: "{}", + } + case *SessionEndDeprecated: + return &SessionEnd{ + Timestamp: m.Timestamp, + EncryptionKey: "", } - default: - return msg } + return msg } diff --git a/backend/pkg/messages/message.go b/backend/pkg/messages/message.go index 16ab1920d..7bb2572eb 100644 --- a/backend/pkg/messages/message.go +++ b/backend/pkg/messages/message.go @@ -1,20 +1,6 @@ package messages -type message struct { - Timestamp int64 - Index uint64 - Url string -} - -func (m *message) Meta() *message { - return m -} - -func (m *message) SetMeta(origin *message) { - m.Timestamp = origin.Timestamp - m.Index = origin.Index - m.Url = origin.Url -} +import "fmt" type Message interface { Encode() []byte @@ -22,4 +8,74 @@ type Message interface { Decode() Message TypeID() int Meta() *message + SessionID() uint64 +} + +// BatchInfo represents common information for all messages inside data batch +type BatchInfo struct { + sessionID uint64 + id uint64 + topic string + partition uint64 + timestamp int64 + version uint64 +} + +func NewBatchInfo(sessID uint64, topic string, id, partition uint64, ts int64) *BatchInfo { + return &BatchInfo{ + sessionID: sessID, + id: id, + topic: topic, + partition: partition, + timestamp: ts, + } +} + +func (b *BatchInfo) SessionID() uint64 { + return b.sessionID +} + +func (b *BatchInfo) ID() uint64 { + return b.id +} + +func (b *BatchInfo) Timestamp() int64 { + return b.timestamp +} + +func (b *BatchInfo) Info() string { + return fmt.Sprintf("session: %d, partition: %d, offset: %d, ver: %d", b.sessionID, b.partition, b.id, b.version) +} + +type message struct { + Timestamp int64 + Index uint64 + Url string + batch *BatchInfo +} + +func (m *message) Batch() *BatchInfo { + return m.batch +} + +func (m *message) Meta() *message { + return m +} + +func (m *message) SetMeta(origin *message) { + m.batch = origin.batch + m.Timestamp = origin.Timestamp + m.Index = origin.Index + m.Url = origin.Url +} + +func (m *message) SessionID() uint64 { + return m.batch.sessionID +} + +func (m *message) SetSessionID(sessID uint64) { + if m.batch == nil { + m.batch = &BatchInfo{} + } + m.batch.sessionID = sessID } diff --git a/backend/pkg/messages/messages.go b/backend/pkg/messages/messages.go index 8cdb95722..5d914bc0f 100644 --- a/backend/pkg/messages/messages.go +++ b/backend/pkg/messages/messages.go @@ -4,204 +4,209 @@ package messages import "encoding/binary" const ( - MsgBatchMeta = 80 - MsgBatchMetadata = 81 + MsgBatchMeta = 80 - MsgPartitionedMessage = 82 + MsgBatchMetadata = 81 - MsgTimestamp = 0 + MsgPartitionedMessage = 82 - MsgSessionStart = 1 + MsgTimestamp = 0 - MsgSessionEnd = 3 + MsgSessionStart = 1 - MsgSetPageLocation = 4 + MsgSessionEndDeprecated = 3 - MsgSetViewportSize = 5 + MsgSetPageLocation = 4 - MsgSetViewportScroll = 6 + MsgSetViewportSize = 5 - MsgCreateDocument = 7 + MsgSetViewportScroll = 6 - MsgCreateElementNode = 8 + MsgCreateDocument = 7 - MsgCreateTextNode = 9 + MsgCreateElementNode = 8 - MsgMoveNode = 10 + MsgCreateTextNode = 9 - MsgRemoveNode = 11 + MsgMoveNode = 10 - MsgSetNodeAttribute = 12 + MsgRemoveNode = 11 - MsgRemoveNodeAttribute = 13 + MsgSetNodeAttribute = 12 - MsgSetNodeData = 14 + MsgRemoveNodeAttribute = 13 - MsgSetCSSData = 15 + MsgSetNodeData = 14 - MsgSetNodeScroll = 16 + MsgSetCSSData = 15 - MsgSetInputTarget = 17 + MsgSetNodeScroll = 16 - MsgSetInputValue = 18 + MsgSetInputTarget = 17 - MsgSetInputChecked = 19 + MsgSetInputValue = 18 - MsgMouseMove = 20 + MsgSetInputChecked = 19 - MsgMouseClickDepricated = 21 + MsgMouseMove = 20 - MsgConsoleLog = 22 + MsgConsoleLog = 22 - MsgPageLoadTiming = 23 + MsgPageLoadTiming = 23 - MsgPageRenderTiming = 24 + MsgPageRenderTiming = 24 - MsgJSException = 25 + MsgJSExceptionDeprecated = 25 - MsgIntegrationEvent = 26 + MsgIntegrationEvent = 26 - MsgRawCustomEvent = 27 + MsgRawCustomEvent = 27 - MsgUserID = 28 + MsgUserID = 28 - MsgUserAnonymousID = 29 + MsgUserAnonymousID = 29 - MsgMetadata = 30 + MsgMetadata = 30 - MsgPageEvent = 31 + MsgPageEvent = 31 - MsgInputEvent = 32 + MsgInputEvent = 32 - MsgClickEvent = 33 + MsgClickEvent = 33 - MsgErrorEvent = 34 + MsgResourceEvent = 35 - MsgResourceEvent = 35 + MsgCustomEvent = 36 - MsgCustomEvent = 36 + MsgCSSInsertRule = 37 - MsgCSSInsertRule = 37 + MsgCSSDeleteRule = 38 - MsgCSSDeleteRule = 38 + MsgFetch = 39 - MsgFetch = 39 + MsgProfiler = 40 - MsgProfiler = 40 + MsgOTable = 41 - MsgOTable = 41 + MsgStateAction = 42 - MsgStateAction = 42 + MsgStateActionEvent = 43 - MsgStateActionEvent = 43 + MsgRedux = 44 - MsgRedux = 44 + MsgVuex = 45 - MsgVuex = 45 + MsgMobX = 46 - MsgMobX = 46 + MsgNgRx = 47 - MsgNgRx = 47 + MsgGraphQL = 48 - MsgGraphQL = 48 + MsgPerformanceTrack = 49 - MsgPerformanceTrack = 49 + MsgGraphQLEvent = 50 - MsgGraphQLEvent = 50 + MsgFetchEvent = 51 - MsgFetchEvent = 51 + MsgDOMDrop = 52 - MsgDOMDrop = 52 + MsgResourceTiming = 53 - MsgResourceTiming = 53 + MsgConnectionInformation = 54 - MsgConnectionInformation = 54 + MsgSetPageVisibility = 55 - MsgSetPageVisibility = 55 + MsgPerformanceTrackAggr = 56 - MsgPerformanceTrackAggr = 56 + MsgLongTask = 59 - MsgLongTask = 59 + MsgSetNodeAttributeURLBased = 60 - MsgSetNodeAttributeURLBased = 60 + MsgSetCSSDataURLBased = 61 - MsgSetCSSDataURLBased = 61 + MsgIssueEvent = 62 - MsgIssueEvent = 62 + MsgTechnicalInfo = 63 - MsgTechnicalInfo = 63 + MsgCustomIssue = 64 - MsgCustomIssue = 64 + MsgAssetCache = 66 - MsgAssetCache = 66 + MsgCSSInsertRuleURLBased = 67 - MsgCSSInsertRuleURLBased = 67 + MsgMouseClick = 69 - MsgMouseClick = 69 + MsgCreateIFrameDocument = 70 - MsgCreateIFrameDocument = 70 + MsgAdoptedSSReplaceURLBased = 71 - MsgAdoptedSSReplaceURLBased = 71 + MsgAdoptedSSReplace = 72 - MsgAdoptedSSReplace = 72 + MsgAdoptedSSInsertRuleURLBased = 73 - MsgAdoptedSSInsertRuleURLBased = 73 + MsgAdoptedSSInsertRule = 74 - MsgAdoptedSSInsertRule = 74 + MsgAdoptedSSDeleteRule = 75 - MsgAdoptedSSDeleteRule = 75 + MsgAdoptedSSAddOwner = 76 - MsgAdoptedSSAddOwner = 76 + MsgAdoptedSSRemoveOwner = 77 - MsgAdoptedSSRemoveOwner = 77 + MsgZustand = 79 - MsgZustand = 79 + MsgJSException = 78 - MsgIOSBatchMeta = 107 + MsgSessionEnd = 126 - MsgIOSSessionStart = 90 + MsgSessionSearch = 127 - MsgIOSSessionEnd = 91 + MsgIOSBatchMeta = 107 - MsgIOSMetadata = 92 + MsgIOSSessionStart = 90 - MsgIOSCustomEvent = 93 + MsgIOSSessionEnd = 91 - MsgIOSUserID = 94 + MsgIOSMetadata = 92 - MsgIOSUserAnonymousID = 95 + MsgIOSCustomEvent = 93 - MsgIOSScreenChanges = 96 + MsgIOSUserID = 94 - MsgIOSCrash = 97 + MsgIOSUserAnonymousID = 95 - MsgIOSScreenEnter = 98 + MsgIOSScreenChanges = 96 - MsgIOSScreenLeave = 99 + MsgIOSCrash = 97 - MsgIOSClickEvent = 100 + MsgIOSScreenEnter = 98 - MsgIOSInputEvent = 101 + MsgIOSScreenLeave = 99 - MsgIOSPerformanceEvent = 102 + MsgIOSClickEvent = 100 - MsgIOSLog = 103 + MsgIOSInputEvent = 101 - MsgIOSInternalError = 104 + MsgIOSPerformanceEvent = 102 - MsgIOSNetworkCall = 105 + MsgIOSLog = 103 - MsgIOSPerformanceAggregated = 110 + MsgIOSInternalError = 104 + + MsgIOSNetworkCall = 105 + + MsgIOSPerformanceAggregated = 110 + + MsgIOSIssueEvent = 111 - MsgIOSIssueEvent = 111 ) + type BatchMeta struct { message - PageNo uint64 + PageNo uint64 FirstIndex uint64 - Timestamp int64 + Timestamp int64 } func (msg *BatchMeta) Encode() []byte { @@ -215,14 +220,14 @@ func (msg *BatchMeta) Encode() []byte { } func (msg *BatchMeta) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *BatchMeta) Decode() Message { @@ -235,11 +240,11 @@ func (msg *BatchMeta) TypeID() int { type BatchMetadata struct { message - Version uint64 - PageNo uint64 + Version uint64 + PageNo uint64 FirstIndex uint64 - Timestamp int64 - Location string + Timestamp int64 + Location string } func (msg *BatchMetadata) Encode() []byte { @@ -255,14 +260,14 @@ func (msg *BatchMetadata) Encode() []byte { } func (msg *BatchMetadata) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *BatchMetadata) Decode() Message { @@ -275,7 +280,7 @@ func (msg *BatchMetadata) TypeID() int { type PartitionedMessage struct { message - PartNo uint64 + PartNo uint64 PartTotal uint64 } @@ -289,14 +294,14 @@ func (msg *PartitionedMessage) Encode() []byte { } func (msg *PartitionedMessage) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *PartitionedMessage) Decode() Message { @@ -321,14 +326,14 @@ func (msg *Timestamp) Encode() []byte { } func (msg *Timestamp) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *Timestamp) Decode() Message { @@ -341,22 +346,22 @@ func (msg *Timestamp) TypeID() int { type SessionStart struct { message - Timestamp uint64 - ProjectID uint64 - TrackerVersion string - RevID string - UserUUID string - UserAgent string - UserOS string - UserOSVersion string - UserBrowser string - UserBrowserVersion string - UserDevice string - UserDeviceType string + Timestamp uint64 + ProjectID uint64 + TrackerVersion string + RevID string + UserUUID string + UserAgent string + UserOS string + UserOSVersion string + UserBrowser string + UserBrowserVersion string + UserDevice string + UserDeviceType string UserDeviceMemorySize uint64 - UserDeviceHeapSize uint64 - UserCountry string - UserID string + UserDeviceHeapSize uint64 + UserCountry string + UserID string } func (msg *SessionStart) Encode() []byte { @@ -383,14 +388,14 @@ func (msg *SessionStart) Encode() []byte { } func (msg *SessionStart) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *SessionStart) Decode() Message { @@ -401,12 +406,12 @@ func (msg *SessionStart) TypeID() int { return 1 } -type SessionEnd struct { +type SessionEndDeprecated struct { message Timestamp uint64 } -func (msg *SessionEnd) Encode() []byte { +func (msg *SessionEndDeprecated) Encode() []byte { buf := make([]byte, 11) buf[0] = 3 p := 1 @@ -414,29 +419,29 @@ func (msg *SessionEnd) Encode() []byte { return buf[:p] } -func (msg *SessionEnd) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data +func (msg *SessionEndDeprecated) EncodeWithIndex() []byte { + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } -func (msg *SessionEnd) Decode() Message { +func (msg *SessionEndDeprecated) Decode() Message { return msg } -func (msg *SessionEnd) TypeID() int { +func (msg *SessionEndDeprecated) TypeID() int { return 3 } type SetPageLocation struct { message - URL string - Referrer string + URL string + Referrer string NavigationStart uint64 } @@ -451,14 +456,14 @@ func (msg *SetPageLocation) Encode() []byte { } func (msg *SetPageLocation) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *SetPageLocation) Decode() Message { @@ -471,7 +476,7 @@ func (msg *SetPageLocation) TypeID() int { type SetViewportSize struct { message - Width uint64 + Width uint64 Height uint64 } @@ -485,14 +490,14 @@ func (msg *SetViewportSize) Encode() []byte { } func (msg *SetViewportSize) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *SetViewportSize) Decode() Message { @@ -519,14 +524,14 @@ func (msg *SetViewportScroll) Encode() []byte { } func (msg *SetViewportScroll) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *SetViewportScroll) Decode() Message { @@ -539,6 +544,7 @@ func (msg *SetViewportScroll) TypeID() int { type CreateDocument struct { message + } func (msg *CreateDocument) Encode() []byte { @@ -550,14 +556,14 @@ func (msg *CreateDocument) Encode() []byte { } func (msg *CreateDocument) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *CreateDocument) Decode() Message { @@ -570,11 +576,11 @@ func (msg *CreateDocument) TypeID() int { type CreateElementNode struct { message - ID uint64 + ID uint64 ParentID uint64 - index uint64 - Tag string - SVG bool + index uint64 + Tag string + SVG bool } func (msg *CreateElementNode) Encode() []byte { @@ -590,14 +596,14 @@ func (msg *CreateElementNode) Encode() []byte { } func (msg *CreateElementNode) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *CreateElementNode) Decode() Message { @@ -610,9 +616,9 @@ func (msg *CreateElementNode) TypeID() int { type CreateTextNode struct { message - ID uint64 + ID uint64 ParentID uint64 - Index uint64 + Index uint64 } func (msg *CreateTextNode) Encode() []byte { @@ -626,14 +632,14 @@ func (msg *CreateTextNode) Encode() []byte { } func (msg *CreateTextNode) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *CreateTextNode) Decode() Message { @@ -646,9 +652,9 @@ func (msg *CreateTextNode) TypeID() int { type MoveNode struct { message - ID uint64 + ID uint64 ParentID uint64 - Index uint64 + Index uint64 } func (msg *MoveNode) Encode() []byte { @@ -662,14 +668,14 @@ func (msg *MoveNode) Encode() []byte { } func (msg *MoveNode) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *MoveNode) Decode() Message { @@ -694,14 +700,14 @@ func (msg *RemoveNode) Encode() []byte { } func (msg *RemoveNode) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *RemoveNode) Decode() Message { @@ -714,8 +720,8 @@ func (msg *RemoveNode) TypeID() int { type SetNodeAttribute struct { message - ID uint64 - Name string + ID uint64 + Name string Value string } @@ -730,14 +736,14 @@ func (msg *SetNodeAttribute) Encode() []byte { } func (msg *SetNodeAttribute) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *SetNodeAttribute) Decode() Message { @@ -750,7 +756,7 @@ func (msg *SetNodeAttribute) TypeID() int { type RemoveNodeAttribute struct { message - ID uint64 + ID uint64 Name string } @@ -764,14 +770,14 @@ func (msg *RemoveNodeAttribute) Encode() []byte { } func (msg *RemoveNodeAttribute) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *RemoveNodeAttribute) Decode() Message { @@ -784,7 +790,7 @@ func (msg *RemoveNodeAttribute) TypeID() int { type SetNodeData struct { message - ID uint64 + ID uint64 Data string } @@ -798,14 +804,14 @@ func (msg *SetNodeData) Encode() []byte { } func (msg *SetNodeData) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *SetNodeData) Decode() Message { @@ -818,7 +824,7 @@ func (msg *SetNodeData) TypeID() int { type SetCSSData struct { message - ID uint64 + ID uint64 Data string } @@ -832,14 +838,14 @@ func (msg *SetCSSData) Encode() []byte { } func (msg *SetCSSData) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *SetCSSData) Decode() Message { @@ -853,8 +859,8 @@ func (msg *SetCSSData) TypeID() int { type SetNodeScroll struct { message ID uint64 - X int64 - Y int64 + X int64 + Y int64 } func (msg *SetNodeScroll) Encode() []byte { @@ -868,14 +874,14 @@ func (msg *SetNodeScroll) Encode() []byte { } func (msg *SetNodeScroll) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *SetNodeScroll) Decode() Message { @@ -888,7 +894,7 @@ func (msg *SetNodeScroll) TypeID() int { type SetInputTarget struct { message - ID uint64 + ID uint64 Label string } @@ -902,14 +908,14 @@ func (msg *SetInputTarget) Encode() []byte { } func (msg *SetInputTarget) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *SetInputTarget) Decode() Message { @@ -922,9 +928,9 @@ func (msg *SetInputTarget) TypeID() int { type SetInputValue struct { message - ID uint64 + ID uint64 Value string - Mask int64 + Mask int64 } func (msg *SetInputValue) Encode() []byte { @@ -938,14 +944,14 @@ func (msg *SetInputValue) Encode() []byte { } func (msg *SetInputValue) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *SetInputValue) Decode() Message { @@ -958,7 +964,7 @@ func (msg *SetInputValue) TypeID() int { type SetInputChecked struct { message - ID uint64 + ID uint64 Checked bool } @@ -972,14 +978,14 @@ func (msg *SetInputChecked) Encode() []byte { } func (msg *SetInputChecked) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *SetInputChecked) Decode() Message { @@ -1006,14 +1012,14 @@ func (msg *MouseMove) Encode() []byte { } func (msg *MouseMove) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *MouseMove) Decode() Message { @@ -1024,42 +1030,6 @@ func (msg *MouseMove) TypeID() int { return 20 } -type MouseClickDepricated struct { - message - ID uint64 - HesitationTime uint64 - Label string -} - -func (msg *MouseClickDepricated) Encode() []byte { - buf := make([]byte, 31+len(msg.Label)) - buf[0] = 21 - p := 1 - p = WriteUint(msg.ID, buf, p) - p = WriteUint(msg.HesitationTime, buf, p) - p = WriteString(msg.Label, buf, p) - return buf[:p] -} - -func (msg *MouseClickDepricated) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - -func (msg *MouseClickDepricated) Decode() Message { - return msg -} - -func (msg *MouseClickDepricated) TypeID() int { - return 21 -} - type ConsoleLog struct { message Level string @@ -1076,14 +1046,14 @@ func (msg *ConsoleLog) Encode() []byte { } func (msg *ConsoleLog) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *ConsoleLog) Decode() Message { @@ -1096,15 +1066,15 @@ func (msg *ConsoleLog) TypeID() int { type PageLoadTiming struct { message - RequestStart uint64 - ResponseStart uint64 - ResponseEnd uint64 + RequestStart uint64 + ResponseStart uint64 + ResponseEnd uint64 DomContentLoadedEventStart uint64 - DomContentLoadedEventEnd uint64 - LoadEventStart uint64 - LoadEventEnd uint64 - FirstPaint uint64 - FirstContentfulPaint uint64 + DomContentLoadedEventEnd uint64 + LoadEventStart uint64 + LoadEventEnd uint64 + FirstPaint uint64 + FirstContentfulPaint uint64 } func (msg *PageLoadTiming) Encode() []byte { @@ -1124,14 +1094,14 @@ func (msg *PageLoadTiming) Encode() []byte { } func (msg *PageLoadTiming) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *PageLoadTiming) Decode() Message { @@ -1144,8 +1114,8 @@ func (msg *PageLoadTiming) TypeID() int { type PageRenderTiming struct { message - SpeedIndex uint64 - VisuallyComplete uint64 + SpeedIndex uint64 + VisuallyComplete uint64 TimeToInteractive uint64 } @@ -1160,14 +1130,14 @@ func (msg *PageRenderTiming) Encode() []byte { } func (msg *PageRenderTiming) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *PageRenderTiming) Decode() Message { @@ -1178,14 +1148,14 @@ func (msg *PageRenderTiming) TypeID() int { return 24 } -type JSException struct { +type JSExceptionDeprecated struct { message - Name string + Name string Message string Payload string } -func (msg *JSException) Encode() []byte { +func (msg *JSExceptionDeprecated) Encode() []byte { buf := make([]byte, 31+len(msg.Name)+len(msg.Message)+len(msg.Payload)) buf[0] = 25 p := 1 @@ -1195,32 +1165,32 @@ func (msg *JSException) Encode() []byte { return buf[:p] } -func (msg *JSException) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data +func (msg *JSExceptionDeprecated) EncodeWithIndex() []byte { + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } -func (msg *JSException) Decode() Message { +func (msg *JSExceptionDeprecated) Decode() Message { return msg } -func (msg *JSException) TypeID() int { +func (msg *JSExceptionDeprecated) TypeID() int { return 25 } type IntegrationEvent struct { message Timestamp uint64 - Source string - Name string - Message string - Payload string + Source string + Name string + Message string + Payload string } func (msg *IntegrationEvent) Encode() []byte { @@ -1236,14 +1206,14 @@ func (msg *IntegrationEvent) Encode() []byte { } func (msg *IntegrationEvent) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *IntegrationEvent) Decode() Message { @@ -1256,7 +1226,7 @@ func (msg *IntegrationEvent) TypeID() int { type RawCustomEvent struct { message - Name string + Name string Payload string } @@ -1270,14 +1240,14 @@ func (msg *RawCustomEvent) Encode() []byte { } func (msg *RawCustomEvent) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *RawCustomEvent) Decode() Message { @@ -1302,14 +1272,14 @@ func (msg *UserID) Encode() []byte { } func (msg *UserID) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *UserID) Decode() Message { @@ -1334,14 +1304,14 @@ func (msg *UserAnonymousID) Encode() []byte { } func (msg *UserAnonymousID) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *UserAnonymousID) Decode() Message { @@ -1354,7 +1324,7 @@ func (msg *UserAnonymousID) TypeID() int { type Metadata struct { message - Key string + Key string Value string } @@ -1368,14 +1338,14 @@ func (msg *Metadata) Encode() []byte { } func (msg *Metadata) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *Metadata) Decode() Message { @@ -1388,23 +1358,23 @@ func (msg *Metadata) TypeID() int { type PageEvent struct { message - MessageID uint64 - Timestamp uint64 - URL string - Referrer string - Loaded bool - RequestStart uint64 - ResponseStart uint64 - ResponseEnd uint64 + MessageID uint64 + Timestamp uint64 + URL string + Referrer string + Loaded bool + RequestStart uint64 + ResponseStart uint64 + ResponseEnd uint64 DomContentLoadedEventStart uint64 - DomContentLoadedEventEnd uint64 - LoadEventStart uint64 - LoadEventEnd uint64 - FirstPaint uint64 - FirstContentfulPaint uint64 - SpeedIndex uint64 - VisuallyComplete uint64 - TimeToInteractive uint64 + DomContentLoadedEventEnd uint64 + LoadEventStart uint64 + LoadEventEnd uint64 + FirstPaint uint64 + FirstContentfulPaint uint64 + SpeedIndex uint64 + VisuallyComplete uint64 + TimeToInteractive uint64 } func (msg *PageEvent) Encode() []byte { @@ -1432,14 +1402,14 @@ func (msg *PageEvent) Encode() []byte { } func (msg *PageEvent) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *PageEvent) Decode() Message { @@ -1452,11 +1422,11 @@ func (msg *PageEvent) TypeID() int { type InputEvent struct { message - MessageID uint64 - Timestamp uint64 - Value string + MessageID uint64 + Timestamp uint64 + Value string ValueMasked bool - Label string + Label string } func (msg *InputEvent) Encode() []byte { @@ -1472,14 +1442,14 @@ func (msg *InputEvent) Encode() []byte { } func (msg *InputEvent) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *InputEvent) Decode() Message { @@ -1492,11 +1462,11 @@ func (msg *InputEvent) TypeID() int { type ClickEvent struct { message - MessageID uint64 - Timestamp uint64 + MessageID uint64 + Timestamp uint64 HesitationTime uint64 - Label string - Selector string + Label string + Selector string } func (msg *ClickEvent) Encode() []byte { @@ -1512,14 +1482,14 @@ func (msg *ClickEvent) Encode() []byte { } func (msg *ClickEvent) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *ClickEvent) Decode() Message { @@ -1530,62 +1500,20 @@ func (msg *ClickEvent) TypeID() int { return 33 } -type ErrorEvent struct { +type ResourceEvent struct { message MessageID uint64 Timestamp uint64 - Source string - Name string - Message string - Payload string -} - -func (msg *ErrorEvent) Encode() []byte { - buf := make([]byte, 61+len(msg.Source)+len(msg.Name)+len(msg.Message)+len(msg.Payload)) - buf[0] = 34 - p := 1 - p = WriteUint(msg.MessageID, buf, p) - p = WriteUint(msg.Timestamp, buf, p) - p = WriteString(msg.Source, buf, p) - p = WriteString(msg.Name, buf, p) - p = WriteString(msg.Message, buf, p) - p = WriteString(msg.Payload, buf, p) - return buf[:p] -} - -func (msg *ErrorEvent) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data -} - -func (msg *ErrorEvent) Decode() Message { - return msg -} - -func (msg *ErrorEvent) TypeID() int { - return 34 -} - -type ResourceEvent struct { - message - MessageID uint64 - Timestamp uint64 - Duration uint64 - TTFB uint64 - HeaderSize uint64 + Duration uint64 + TTFB uint64 + HeaderSize uint64 EncodedBodySize uint64 DecodedBodySize uint64 - URL string - Type string - Success bool - Method string - Status uint64 + URL string + Type string + Success bool + Method string + Status uint64 } func (msg *ResourceEvent) Encode() []byte { @@ -1608,14 +1536,14 @@ func (msg *ResourceEvent) Encode() []byte { } func (msg *ResourceEvent) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *ResourceEvent) Decode() Message { @@ -1630,8 +1558,8 @@ type CustomEvent struct { message MessageID uint64 Timestamp uint64 - Name string - Payload string + Name string + Payload string } func (msg *CustomEvent) Encode() []byte { @@ -1646,14 +1574,14 @@ func (msg *CustomEvent) Encode() []byte { } func (msg *CustomEvent) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *CustomEvent) Decode() Message { @@ -1666,8 +1594,8 @@ func (msg *CustomEvent) TypeID() int { type CSSInsertRule struct { message - ID uint64 - Rule string + ID uint64 + Rule string Index uint64 } @@ -1682,14 +1610,14 @@ func (msg *CSSInsertRule) Encode() []byte { } func (msg *CSSInsertRule) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *CSSInsertRule) Decode() Message { @@ -1702,7 +1630,7 @@ func (msg *CSSInsertRule) TypeID() int { type CSSDeleteRule struct { message - ID uint64 + ID uint64 Index uint64 } @@ -1716,14 +1644,14 @@ func (msg *CSSDeleteRule) Encode() []byte { } func (msg *CSSDeleteRule) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *CSSDeleteRule) Decode() Message { @@ -1736,13 +1664,13 @@ func (msg *CSSDeleteRule) TypeID() int { type Fetch struct { message - Method string - URL string - Request string - Response string - Status uint64 + Method string + URL string + Request string + Response string + Status uint64 Timestamp uint64 - Duration uint64 + Duration uint64 } func (msg *Fetch) Encode() []byte { @@ -1760,14 +1688,14 @@ func (msg *Fetch) Encode() []byte { } func (msg *Fetch) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *Fetch) Decode() Message { @@ -1780,10 +1708,10 @@ func (msg *Fetch) TypeID() int { type Profiler struct { message - Name string + Name string Duration uint64 - Args string - Result string + Args string + Result string } func (msg *Profiler) Encode() []byte { @@ -1798,14 +1726,14 @@ func (msg *Profiler) Encode() []byte { } func (msg *Profiler) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *Profiler) Decode() Message { @@ -1818,7 +1746,7 @@ func (msg *Profiler) TypeID() int { type OTable struct { message - Key string + Key string Value string } @@ -1832,14 +1760,14 @@ func (msg *OTable) Encode() []byte { } func (msg *OTable) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *OTable) Decode() Message { @@ -1864,14 +1792,14 @@ func (msg *StateAction) Encode() []byte { } func (msg *StateAction) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *StateAction) Decode() Message { @@ -1886,7 +1814,7 @@ type StateActionEvent struct { message MessageID uint64 Timestamp uint64 - Type string + Type string } func (msg *StateActionEvent) Encode() []byte { @@ -1900,14 +1828,14 @@ func (msg *StateActionEvent) Encode() []byte { } func (msg *StateActionEvent) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *StateActionEvent) Decode() Message { @@ -1920,8 +1848,8 @@ func (msg *StateActionEvent) TypeID() int { type Redux struct { message - Action string - State string + Action string + State string Duration uint64 } @@ -1936,14 +1864,14 @@ func (msg *Redux) Encode() []byte { } func (msg *Redux) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *Redux) Decode() Message { @@ -1957,7 +1885,7 @@ func (msg *Redux) TypeID() int { type Vuex struct { message Mutation string - State string + State string } func (msg *Vuex) Encode() []byte { @@ -1970,14 +1898,14 @@ func (msg *Vuex) Encode() []byte { } func (msg *Vuex) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *Vuex) Decode() Message { @@ -1990,7 +1918,7 @@ func (msg *Vuex) TypeID() int { type MobX struct { message - Type string + Type string Payload string } @@ -2004,14 +1932,14 @@ func (msg *MobX) Encode() []byte { } func (msg *MobX) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *MobX) Decode() Message { @@ -2024,8 +1952,8 @@ func (msg *MobX) TypeID() int { type NgRx struct { message - Action string - State string + Action string + State string Duration uint64 } @@ -2040,14 +1968,14 @@ func (msg *NgRx) Encode() []byte { } func (msg *NgRx) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *NgRx) Decode() Message { @@ -2062,8 +1990,8 @@ type GraphQL struct { message OperationKind string OperationName string - Variables string - Response string + Variables string + Response string } func (msg *GraphQL) Encode() []byte { @@ -2078,14 +2006,14 @@ func (msg *GraphQL) Encode() []byte { } func (msg *GraphQL) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *GraphQL) Decode() Message { @@ -2098,10 +2026,10 @@ func (msg *GraphQL) TypeID() int { type PerformanceTrack struct { message - Frames int64 - Ticks int64 + Frames int64 + Ticks int64 TotalJSHeapSize uint64 - UsedJSHeapSize uint64 + UsedJSHeapSize uint64 } func (msg *PerformanceTrack) Encode() []byte { @@ -2116,14 +2044,14 @@ func (msg *PerformanceTrack) Encode() []byte { } func (msg *PerformanceTrack) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *PerformanceTrack) Decode() Message { @@ -2136,12 +2064,12 @@ func (msg *PerformanceTrack) TypeID() int { type GraphQLEvent struct { message - MessageID uint64 - Timestamp uint64 + MessageID uint64 + Timestamp uint64 OperationKind string OperationName string - Variables string - Response string + Variables string + Response string } func (msg *GraphQLEvent) Encode() []byte { @@ -2158,14 +2086,14 @@ func (msg *GraphQLEvent) Encode() []byte { } func (msg *GraphQLEvent) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *GraphQLEvent) Decode() Message { @@ -2180,12 +2108,12 @@ type FetchEvent struct { message MessageID uint64 Timestamp uint64 - Method string - URL string - Request string - Response string - Status uint64 - Duration uint64 + Method string + URL string + Request string + Response string + Status uint64 + Duration uint64 } func (msg *FetchEvent) Encode() []byte { @@ -2204,14 +2132,14 @@ func (msg *FetchEvent) Encode() []byte { } func (msg *FetchEvent) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *FetchEvent) Decode() Message { @@ -2236,14 +2164,14 @@ func (msg *DOMDrop) Encode() []byte { } func (msg *DOMDrop) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *DOMDrop) Decode() Message { @@ -2256,14 +2184,14 @@ func (msg *DOMDrop) TypeID() int { type ResourceTiming struct { message - Timestamp uint64 - Duration uint64 - TTFB uint64 - HeaderSize uint64 + Timestamp uint64 + Duration uint64 + TTFB uint64 + HeaderSize uint64 EncodedBodySize uint64 DecodedBodySize uint64 - URL string - Initiator string + URL string + Initiator string } func (msg *ResourceTiming) Encode() []byte { @@ -2282,14 +2210,14 @@ func (msg *ResourceTiming) Encode() []byte { } func (msg *ResourceTiming) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *ResourceTiming) Decode() Message { @@ -2303,7 +2231,7 @@ func (msg *ResourceTiming) TypeID() int { type ConnectionInformation struct { message Downlink uint64 - Type string + Type string } func (msg *ConnectionInformation) Encode() []byte { @@ -2316,14 +2244,14 @@ func (msg *ConnectionInformation) Encode() []byte { } func (msg *ConnectionInformation) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *ConnectionInformation) Decode() Message { @@ -2348,14 +2276,14 @@ func (msg *SetPageVisibility) Encode() []byte { } func (msg *SetPageVisibility) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *SetPageVisibility) Decode() Message { @@ -2368,20 +2296,20 @@ func (msg *SetPageVisibility) TypeID() int { type PerformanceTrackAggr struct { message - TimestampStart uint64 - TimestampEnd uint64 - MinFPS uint64 - AvgFPS uint64 - MaxFPS uint64 - MinCPU uint64 - AvgCPU uint64 - MaxCPU uint64 + TimestampStart uint64 + TimestampEnd uint64 + MinFPS uint64 + AvgFPS uint64 + MaxFPS uint64 + MinCPU uint64 + AvgCPU uint64 + MaxCPU uint64 MinTotalJSHeapSize uint64 AvgTotalJSHeapSize uint64 MaxTotalJSHeapSize uint64 - MinUsedJSHeapSize uint64 - AvgUsedJSHeapSize uint64 - MaxUsedJSHeapSize uint64 + MinUsedJSHeapSize uint64 + AvgUsedJSHeapSize uint64 + MaxUsedJSHeapSize uint64 } func (msg *PerformanceTrackAggr) Encode() []byte { @@ -2406,14 +2334,14 @@ func (msg *PerformanceTrackAggr) Encode() []byte { } func (msg *PerformanceTrackAggr) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *PerformanceTrackAggr) Decode() Message { @@ -2426,12 +2354,12 @@ func (msg *PerformanceTrackAggr) TypeID() int { type LongTask struct { message - Timestamp uint64 - Duration uint64 - Context uint64 + Timestamp uint64 + Duration uint64 + Context uint64 ContainerType uint64 - ContainerSrc string - ContainerId string + ContainerSrc string + ContainerId string ContainerName string } @@ -2450,14 +2378,14 @@ func (msg *LongTask) Encode() []byte { } func (msg *LongTask) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *LongTask) Decode() Message { @@ -2470,9 +2398,9 @@ func (msg *LongTask) TypeID() int { type SetNodeAttributeURLBased struct { message - ID uint64 - Name string - Value string + ID uint64 + Name string + Value string BaseURL string } @@ -2488,14 +2416,14 @@ func (msg *SetNodeAttributeURLBased) Encode() []byte { } func (msg *SetNodeAttributeURLBased) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *SetNodeAttributeURLBased) Decode() Message { @@ -2508,8 +2436,8 @@ func (msg *SetNodeAttributeURLBased) TypeID() int { type SetCSSDataURLBased struct { message - ID uint64 - Data string + ID uint64 + Data string BaseURL string } @@ -2524,14 +2452,14 @@ func (msg *SetCSSDataURLBased) Encode() []byte { } func (msg *SetCSSDataURLBased) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *SetCSSDataURLBased) Decode() Message { @@ -2544,12 +2472,12 @@ func (msg *SetCSSDataURLBased) TypeID() int { type IssueEvent struct { message - MessageID uint64 - Timestamp uint64 - Type string + MessageID uint64 + Timestamp uint64 + Type string ContextString string - Context string - Payload string + Context string + Payload string } func (msg *IssueEvent) Encode() []byte { @@ -2566,14 +2494,14 @@ func (msg *IssueEvent) Encode() []byte { } func (msg *IssueEvent) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *IssueEvent) Decode() Message { @@ -2586,7 +2514,7 @@ func (msg *IssueEvent) TypeID() int { type TechnicalInfo struct { message - Type string + Type string Value string } @@ -2600,14 +2528,14 @@ func (msg *TechnicalInfo) Encode() []byte { } func (msg *TechnicalInfo) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *TechnicalInfo) Decode() Message { @@ -2620,7 +2548,7 @@ func (msg *TechnicalInfo) TypeID() int { type CustomIssue struct { message - Name string + Name string Payload string } @@ -2634,14 +2562,14 @@ func (msg *CustomIssue) Encode() []byte { } func (msg *CustomIssue) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *CustomIssue) Decode() Message { @@ -2666,14 +2594,14 @@ func (msg *AssetCache) Encode() []byte { } func (msg *AssetCache) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *AssetCache) Decode() Message { @@ -2686,9 +2614,9 @@ func (msg *AssetCache) TypeID() int { type CSSInsertRuleURLBased struct { message - ID uint64 - Rule string - Index uint64 + ID uint64 + Rule string + Index uint64 BaseURL string } @@ -2704,14 +2632,14 @@ func (msg *CSSInsertRuleURLBased) Encode() []byte { } func (msg *CSSInsertRuleURLBased) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *CSSInsertRuleURLBased) Decode() Message { @@ -2724,10 +2652,10 @@ func (msg *CSSInsertRuleURLBased) TypeID() int { type MouseClick struct { message - ID uint64 + ID uint64 HesitationTime uint64 - Label string - Selector string + Label string + Selector string } func (msg *MouseClick) Encode() []byte { @@ -2742,14 +2670,14 @@ func (msg *MouseClick) Encode() []byte { } func (msg *MouseClick) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *MouseClick) Decode() Message { @@ -2763,7 +2691,7 @@ func (msg *MouseClick) TypeID() int { type CreateIFrameDocument struct { message FrameID uint64 - ID uint64 + ID uint64 } func (msg *CreateIFrameDocument) Encode() []byte { @@ -2776,14 +2704,14 @@ func (msg *CreateIFrameDocument) Encode() []byte { } func (msg *CreateIFrameDocument) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *CreateIFrameDocument) Decode() Message { @@ -2797,7 +2725,7 @@ func (msg *CreateIFrameDocument) TypeID() int { type AdoptedSSReplaceURLBased struct { message SheetID uint64 - Text string + Text string BaseURL string } @@ -2812,14 +2740,14 @@ func (msg *AdoptedSSReplaceURLBased) Encode() []byte { } func (msg *AdoptedSSReplaceURLBased) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *AdoptedSSReplaceURLBased) Decode() Message { @@ -2833,7 +2761,7 @@ func (msg *AdoptedSSReplaceURLBased) TypeID() int { type AdoptedSSReplace struct { message SheetID uint64 - Text string + Text string } func (msg *AdoptedSSReplace) Encode() []byte { @@ -2846,14 +2774,14 @@ func (msg *AdoptedSSReplace) Encode() []byte { } func (msg *AdoptedSSReplace) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *AdoptedSSReplace) Decode() Message { @@ -2867,8 +2795,8 @@ func (msg *AdoptedSSReplace) TypeID() int { type AdoptedSSInsertRuleURLBased struct { message SheetID uint64 - Rule string - Index uint64 + Rule string + Index uint64 BaseURL string } @@ -2884,14 +2812,14 @@ func (msg *AdoptedSSInsertRuleURLBased) Encode() []byte { } func (msg *AdoptedSSInsertRuleURLBased) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *AdoptedSSInsertRuleURLBased) Decode() Message { @@ -2905,8 +2833,8 @@ func (msg *AdoptedSSInsertRuleURLBased) TypeID() int { type AdoptedSSInsertRule struct { message SheetID uint64 - Rule string - Index uint64 + Rule string + Index uint64 } func (msg *AdoptedSSInsertRule) Encode() []byte { @@ -2920,14 +2848,14 @@ func (msg *AdoptedSSInsertRule) Encode() []byte { } func (msg *AdoptedSSInsertRule) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *AdoptedSSInsertRule) Decode() Message { @@ -2941,7 +2869,7 @@ func (msg *AdoptedSSInsertRule) TypeID() int { type AdoptedSSDeleteRule struct { message SheetID uint64 - Index uint64 + Index uint64 } func (msg *AdoptedSSDeleteRule) Encode() []byte { @@ -2954,14 +2882,14 @@ func (msg *AdoptedSSDeleteRule) Encode() []byte { } func (msg *AdoptedSSDeleteRule) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *AdoptedSSDeleteRule) Decode() Message { @@ -2975,7 +2903,7 @@ func (msg *AdoptedSSDeleteRule) TypeID() int { type AdoptedSSAddOwner struct { message SheetID uint64 - ID uint64 + ID uint64 } func (msg *AdoptedSSAddOwner) Encode() []byte { @@ -2988,14 +2916,14 @@ func (msg *AdoptedSSAddOwner) Encode() []byte { } func (msg *AdoptedSSAddOwner) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *AdoptedSSAddOwner) Decode() Message { @@ -3009,7 +2937,7 @@ func (msg *AdoptedSSAddOwner) TypeID() int { type AdoptedSSRemoveOwner struct { message SheetID uint64 - ID uint64 + ID uint64 } func (msg *AdoptedSSRemoveOwner) Encode() []byte { @@ -3022,14 +2950,14 @@ func (msg *AdoptedSSRemoveOwner) Encode() []byte { } func (msg *AdoptedSSRemoveOwner) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *AdoptedSSRemoveOwner) Decode() Message { @@ -3043,7 +2971,7 @@ func (msg *AdoptedSSRemoveOwner) TypeID() int { type Zustand struct { message Mutation string - State string + State string } func (msg *Zustand) Encode() []byte { @@ -3056,14 +2984,14 @@ func (msg *Zustand) Encode() []byte { } func (msg *Zustand) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *Zustand) Decode() Message { @@ -3074,10 +3002,116 @@ func (msg *Zustand) TypeID() int { return 79 } +type JSException struct { + message + Name string + Message string + Payload string + Metadata string +} + +func (msg *JSException) Encode() []byte { + buf := make([]byte, 41+len(msg.Name)+len(msg.Message)+len(msg.Payload)+len(msg.Metadata)) + buf[0] = 78 + p := 1 + p = WriteString(msg.Name, buf, p) + p = WriteString(msg.Message, buf, p) + p = WriteString(msg.Payload, buf, p) + p = WriteString(msg.Metadata, buf, p) + return buf[:p] +} + +func (msg *JSException) EncodeWithIndex() []byte { + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data +} + +func (msg *JSException) Decode() Message { + return msg +} + +func (msg *JSException) TypeID() int { + return 78 +} + +type SessionEnd struct { + message + Timestamp uint64 + EncryptionKey string +} + +func (msg *SessionEnd) Encode() []byte { + buf := make([]byte, 21+len(msg.EncryptionKey)) + buf[0] = 126 + p := 1 + p = WriteUint(msg.Timestamp, buf, p) + p = WriteString(msg.EncryptionKey, buf, p) + return buf[:p] +} + +func (msg *SessionEnd) EncodeWithIndex() []byte { + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data +} + +func (msg *SessionEnd) Decode() Message { + return msg +} + +func (msg *SessionEnd) TypeID() int { + return 126 +} + +type SessionSearch struct { + message + Timestamp uint64 + Partition uint64 +} + +func (msg *SessionSearch) Encode() []byte { + buf := make([]byte, 21) + buf[0] = 127 + p := 1 + p = WriteUint(msg.Timestamp, buf, p) + p = WriteUint(msg.Partition, buf, p) + return buf[:p] +} + +func (msg *SessionSearch) EncodeWithIndex() []byte { + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data +} + +func (msg *SessionSearch) Decode() Message { + return msg +} + +func (msg *SessionSearch) TypeID() int { + return 127 +} + type IOSBatchMeta struct { message - Timestamp uint64 - Length uint64 + Timestamp uint64 + Length uint64 FirstIndex uint64 } @@ -3092,14 +3126,14 @@ func (msg *IOSBatchMeta) Encode() []byte { } func (msg *IOSBatchMeta) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *IOSBatchMeta) Decode() Message { @@ -3112,16 +3146,16 @@ func (msg *IOSBatchMeta) TypeID() int { type IOSSessionStart struct { message - Timestamp uint64 - ProjectID uint64 + Timestamp uint64 + ProjectID uint64 TrackerVersion string - RevID string - UserUUID string - UserOS string - UserOSVersion string - UserDevice string + RevID string + UserUUID string + UserOS string + UserOSVersion string + UserDevice string UserDeviceType string - UserCountry string + UserCountry string } func (msg *IOSSessionStart) Encode() []byte { @@ -3142,14 +3176,14 @@ func (msg *IOSSessionStart) Encode() []byte { } func (msg *IOSSessionStart) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *IOSSessionStart) Decode() Message { @@ -3174,14 +3208,14 @@ func (msg *IOSSessionEnd) Encode() []byte { } func (msg *IOSSessionEnd) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *IOSSessionEnd) Decode() Message { @@ -3195,9 +3229,9 @@ func (msg *IOSSessionEnd) TypeID() int { type IOSMetadata struct { message Timestamp uint64 - Length uint64 - Key string - Value string + Length uint64 + Key string + Value string } func (msg *IOSMetadata) Encode() []byte { @@ -3212,14 +3246,14 @@ func (msg *IOSMetadata) Encode() []byte { } func (msg *IOSMetadata) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *IOSMetadata) Decode() Message { @@ -3233,9 +3267,9 @@ func (msg *IOSMetadata) TypeID() int { type IOSCustomEvent struct { message Timestamp uint64 - Length uint64 - Name string - Payload string + Length uint64 + Name string + Payload string } func (msg *IOSCustomEvent) Encode() []byte { @@ -3250,14 +3284,14 @@ func (msg *IOSCustomEvent) Encode() []byte { } func (msg *IOSCustomEvent) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *IOSCustomEvent) Decode() Message { @@ -3271,8 +3305,8 @@ func (msg *IOSCustomEvent) TypeID() int { type IOSUserID struct { message Timestamp uint64 - Length uint64 - Value string + Length uint64 + Value string } func (msg *IOSUserID) Encode() []byte { @@ -3286,14 +3320,14 @@ func (msg *IOSUserID) Encode() []byte { } func (msg *IOSUserID) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *IOSUserID) Decode() Message { @@ -3307,8 +3341,8 @@ func (msg *IOSUserID) TypeID() int { type IOSUserAnonymousID struct { message Timestamp uint64 - Length uint64 - Value string + Length uint64 + Value string } func (msg *IOSUserAnonymousID) Encode() []byte { @@ -3322,14 +3356,14 @@ func (msg *IOSUserAnonymousID) Encode() []byte { } func (msg *IOSUserAnonymousID) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *IOSUserAnonymousID) Decode() Message { @@ -3343,11 +3377,11 @@ func (msg *IOSUserAnonymousID) TypeID() int { type IOSScreenChanges struct { message Timestamp uint64 - Length uint64 - X uint64 - Y uint64 - Width uint64 - Height uint64 + Length uint64 + X uint64 + Y uint64 + Width uint64 + Height uint64 } func (msg *IOSScreenChanges) Encode() []byte { @@ -3364,14 +3398,14 @@ func (msg *IOSScreenChanges) Encode() []byte { } func (msg *IOSScreenChanges) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *IOSScreenChanges) Decode() Message { @@ -3384,10 +3418,10 @@ func (msg *IOSScreenChanges) TypeID() int { type IOSCrash struct { message - Timestamp uint64 - Length uint64 - Name string - Reason string + Timestamp uint64 + Length uint64 + Name string + Reason string Stacktrace string } @@ -3404,14 +3438,14 @@ func (msg *IOSCrash) Encode() []byte { } func (msg *IOSCrash) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *IOSCrash) Decode() Message { @@ -3425,9 +3459,9 @@ func (msg *IOSCrash) TypeID() int { type IOSScreenEnter struct { message Timestamp uint64 - Length uint64 - Title string - ViewName string + Length uint64 + Title string + ViewName string } func (msg *IOSScreenEnter) Encode() []byte { @@ -3442,14 +3476,14 @@ func (msg *IOSScreenEnter) Encode() []byte { } func (msg *IOSScreenEnter) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *IOSScreenEnter) Decode() Message { @@ -3463,9 +3497,9 @@ func (msg *IOSScreenEnter) TypeID() int { type IOSScreenLeave struct { message Timestamp uint64 - Length uint64 - Title string - ViewName string + Length uint64 + Title string + ViewName string } func (msg *IOSScreenLeave) Encode() []byte { @@ -3480,14 +3514,14 @@ func (msg *IOSScreenLeave) Encode() []byte { } func (msg *IOSScreenLeave) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *IOSScreenLeave) Decode() Message { @@ -3501,10 +3535,10 @@ func (msg *IOSScreenLeave) TypeID() int { type IOSClickEvent struct { message Timestamp uint64 - Length uint64 - Label string - X uint64 - Y uint64 + Length uint64 + Label string + X uint64 + Y uint64 } func (msg *IOSClickEvent) Encode() []byte { @@ -3520,14 +3554,14 @@ func (msg *IOSClickEvent) Encode() []byte { } func (msg *IOSClickEvent) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *IOSClickEvent) Decode() Message { @@ -3540,11 +3574,11 @@ func (msg *IOSClickEvent) TypeID() int { type IOSInputEvent struct { message - Timestamp uint64 - Length uint64 - Value string + Timestamp uint64 + Length uint64 + Value string ValueMasked bool - Label string + Label string } func (msg *IOSInputEvent) Encode() []byte { @@ -3560,14 +3594,14 @@ func (msg *IOSInputEvent) Encode() []byte { } func (msg *IOSInputEvent) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *IOSInputEvent) Decode() Message { @@ -3581,9 +3615,9 @@ func (msg *IOSInputEvent) TypeID() int { type IOSPerformanceEvent struct { message Timestamp uint64 - Length uint64 - Name string - Value uint64 + Length uint64 + Name string + Value uint64 } func (msg *IOSPerformanceEvent) Encode() []byte { @@ -3598,14 +3632,14 @@ func (msg *IOSPerformanceEvent) Encode() []byte { } func (msg *IOSPerformanceEvent) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *IOSPerformanceEvent) Decode() Message { @@ -3619,9 +3653,9 @@ func (msg *IOSPerformanceEvent) TypeID() int { type IOSLog struct { message Timestamp uint64 - Length uint64 - Severity string - Content string + Length uint64 + Severity string + Content string } func (msg *IOSLog) Encode() []byte { @@ -3636,14 +3670,14 @@ func (msg *IOSLog) Encode() []byte { } func (msg *IOSLog) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *IOSLog) Decode() Message { @@ -3657,8 +3691,8 @@ func (msg *IOSLog) TypeID() int { type IOSInternalError struct { message Timestamp uint64 - Length uint64 - Content string + Length uint64 + Content string } func (msg *IOSInternalError) Encode() []byte { @@ -3672,14 +3706,14 @@ func (msg *IOSInternalError) Encode() []byte { } func (msg *IOSInternalError) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *IOSInternalError) Decode() Message { @@ -3693,14 +3727,14 @@ func (msg *IOSInternalError) TypeID() int { type IOSNetworkCall struct { message Timestamp uint64 - Length uint64 - Duration uint64 - Headers string - Body string - URL string - Success bool - Method string - Status uint64 + Length uint64 + Duration uint64 + Headers string + Body string + URL string + Success bool + Method string + Status uint64 } func (msg *IOSNetworkCall) Encode() []byte { @@ -3720,14 +3754,14 @@ func (msg *IOSNetworkCall) Encode() []byte { } func (msg *IOSNetworkCall) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *IOSNetworkCall) Decode() Message { @@ -3741,19 +3775,19 @@ func (msg *IOSNetworkCall) TypeID() int { type IOSPerformanceAggregated struct { message TimestampStart uint64 - TimestampEnd uint64 - MinFPS uint64 - AvgFPS uint64 - MaxFPS uint64 - MinCPU uint64 - AvgCPU uint64 - MaxCPU uint64 - MinMemory uint64 - AvgMemory uint64 - MaxMemory uint64 - MinBattery uint64 - AvgBattery uint64 - MaxBattery uint64 + TimestampEnd uint64 + MinFPS uint64 + AvgFPS uint64 + MaxFPS uint64 + MinCPU uint64 + AvgCPU uint64 + MaxCPU uint64 + MinMemory uint64 + AvgMemory uint64 + MaxMemory uint64 + MinBattery uint64 + AvgBattery uint64 + MaxBattery uint64 } func (msg *IOSPerformanceAggregated) Encode() []byte { @@ -3778,14 +3812,14 @@ func (msg *IOSPerformanceAggregated) Encode() []byte { } func (msg *IOSPerformanceAggregated) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *IOSPerformanceAggregated) Decode() Message { @@ -3798,11 +3832,11 @@ func (msg *IOSPerformanceAggregated) TypeID() int { type IOSIssueEvent struct { message - Timestamp uint64 - Type string + Timestamp uint64 + Type string ContextString string - Context string - Payload string + Context string + Payload string } func (msg *IOSIssueEvent) Encode() []byte { @@ -3818,14 +3852,14 @@ func (msg *IOSIssueEvent) Encode() []byte { } func (msg *IOSIssueEvent) EncodeWithIndex() []byte { - encoded := msg.Encode() - if IsIOSType(msg.TypeID()) { - return encoded - } - data := make([]byte, len(encoded)+8) - copy(data[8:], encoded[:]) - binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) - return data + encoded := msg.Encode() + if IsIOSType(msg.TypeID()) { + return encoded + } + data := make([]byte, len(encoded)+8) + copy(data[8:], encoded[:]) + binary.LittleEndian.PutUint64(data[0:], msg.Meta().Index) + return data } func (msg *IOSIssueEvent) Decode() Message { @@ -3835,3 +3869,4 @@ func (msg *IOSIssueEvent) Decode() Message { func (msg *IOSIssueEvent) TypeID() int { return 111 } + diff --git a/backend/pkg/messages/raw.go b/backend/pkg/messages/raw.go index b9dba5de2..33419d115 100644 --- a/backend/pkg/messages/raw.go +++ b/backend/pkg/messages/raw.go @@ -16,6 +16,7 @@ type RawMessage struct { meta *message encoded bool skipped *bool + broken *bool } func (m *RawMessage) Encode() []byte { @@ -28,7 +29,7 @@ func (m *RawMessage) Encode() []byte { *m.skipped = false _, err := io.ReadFull(m.reader, m.data[1:]) if err != nil { - log.Printf("message encode err: %s", err) + log.Printf("message encode err: %s, type: %d, sess: %d", err, m.tp, m.SessionID()) return nil } return m.data @@ -36,7 +37,10 @@ func (m *RawMessage) Encode() []byte { func (m *RawMessage) EncodeWithIndex() []byte { if !m.encoded { - m.Encode() + if m.Encode() == nil { + *m.broken = true + return nil + } } if IsIOSType(int(m.tp)) { return m.data @@ -49,13 +53,18 @@ func (m *RawMessage) EncodeWithIndex() []byte { func (m *RawMessage) Decode() Message { if !m.encoded { - m.Encode() + if m.Encode() == nil { + *m.broken = true + return nil + } } msg, err := ReadMessage(m.tp, bytes.NewReader(m.data[1:])) if err != nil { log.Printf("decode err: %s", err) + *m.broken = true return nil } + msg = transformDeprecated(msg) msg.Meta().SetMeta(m.meta) return msg } @@ -67,3 +76,10 @@ func (m *RawMessage) TypeID() int { func (m *RawMessage) Meta() *message { return m.meta } + +func (m *RawMessage) SessionID() uint64 { + if m.meta != nil { + return m.meta.SessionID() + } + return 0 +} diff --git a/backend/pkg/messages/read-message.go b/backend/pkg/messages/read-message.go index 1b0f579af..e52fb4d44 100644 --- a/backend/pkg/messages/read-message.go +++ b/backend/pkg/messages/read-message.go @@ -6,1720 +6,1822 @@ import ( "io" ) + func DecodeBatchMeta(reader io.Reader) (Message, error) { - var err error = nil - msg := &BatchMeta{} - if msg.PageNo, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.FirstIndex, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Timestamp, err = ReadInt(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &BatchMeta{} + if msg.PageNo, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.FirstIndex, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Timestamp, err = ReadInt(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeBatchMetadata(reader io.Reader) (Message, error) { - var err error = nil - msg := &BatchMetadata{} - if msg.Version, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.PageNo, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.FirstIndex, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Timestamp, err = ReadInt(reader); err != nil { - return nil, err - } - if msg.Location, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &BatchMetadata{} + if msg.Version, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.PageNo, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.FirstIndex, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Timestamp, err = ReadInt(reader); err != nil { + return nil, err + } + if msg.Location, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodePartitionedMessage(reader io.Reader) (Message, error) { - var err error = nil - msg := &PartitionedMessage{} - if msg.PartNo, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.PartTotal, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &PartitionedMessage{} + if msg.PartNo, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.PartTotal, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeTimestamp(reader io.Reader) (Message, error) { - var err error = nil - msg := &Timestamp{} - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &Timestamp{} + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeSessionStart(reader io.Reader) (Message, error) { - var err error = nil - msg := &SessionStart{} - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.ProjectID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.TrackerVersion, err = ReadString(reader); err != nil { - return nil, err - } - if msg.RevID, err = ReadString(reader); err != nil { - return nil, err - } - if msg.UserUUID, err = ReadString(reader); err != nil { - return nil, err - } - if msg.UserAgent, err = ReadString(reader); err != nil { - return nil, err - } - if msg.UserOS, err = ReadString(reader); err != nil { - return nil, err - } - if msg.UserOSVersion, err = ReadString(reader); err != nil { - return nil, err - } - if msg.UserBrowser, err = ReadString(reader); err != nil { - return nil, err - } - if msg.UserBrowserVersion, err = ReadString(reader); err != nil { - return nil, err - } - if msg.UserDevice, err = ReadString(reader); err != nil { - return nil, err - } - if msg.UserDeviceType, err = ReadString(reader); err != nil { - return nil, err - } - if msg.UserDeviceMemorySize, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.UserDeviceHeapSize, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.UserCountry, err = ReadString(reader); err != nil { - return nil, err - } - if msg.UserID, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &SessionStart{} + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.ProjectID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.TrackerVersion, err = ReadString(reader); err != nil { + return nil, err + } + if msg.RevID, err = ReadString(reader); err != nil { + return nil, err + } + if msg.UserUUID, err = ReadString(reader); err != nil { + return nil, err + } + if msg.UserAgent, err = ReadString(reader); err != nil { + return nil, err + } + if msg.UserOS, err = ReadString(reader); err != nil { + return nil, err + } + if msg.UserOSVersion, err = ReadString(reader); err != nil { + return nil, err + } + if msg.UserBrowser, err = ReadString(reader); err != nil { + return nil, err + } + if msg.UserBrowserVersion, err = ReadString(reader); err != nil { + return nil, err + } + if msg.UserDevice, err = ReadString(reader); err != nil { + return nil, err + } + if msg.UserDeviceType, err = ReadString(reader); err != nil { + return nil, err + } + if msg.UserDeviceMemorySize, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.UserDeviceHeapSize, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.UserCountry, err = ReadString(reader); err != nil { + return nil, err + } + if msg.UserID, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } -func DecodeSessionEnd(reader io.Reader) (Message, error) { - var err error = nil - msg := &SessionEnd{} - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, err + +func DecodeSessionEndDeprecated(reader io.Reader) (Message, error) { + var err error = nil + msg := &SessionEndDeprecated{} + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeSetPageLocation(reader io.Reader) (Message, error) { - var err error = nil - msg := &SetPageLocation{} - if msg.URL, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Referrer, err = ReadString(reader); err != nil { - return nil, err - } - if msg.NavigationStart, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &SetPageLocation{} + if msg.URL, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Referrer, err = ReadString(reader); err != nil { + return nil, err + } + if msg.NavigationStart, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeSetViewportSize(reader io.Reader) (Message, error) { - var err error = nil - msg := &SetViewportSize{} - if msg.Width, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Height, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &SetViewportSize{} + if msg.Width, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Height, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeSetViewportScroll(reader io.Reader) (Message, error) { - var err error = nil - msg := &SetViewportScroll{} - if msg.X, err = ReadInt(reader); err != nil { - return nil, err - } - if msg.Y, err = ReadInt(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &SetViewportScroll{} + if msg.X, err = ReadInt(reader); err != nil { + return nil, err + } + if msg.Y, err = ReadInt(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeCreateDocument(reader io.Reader) (Message, error) { - var err error = nil - msg := &CreateDocument{} - - return msg, err + var err error = nil + msg := &CreateDocument{} + + return msg, err } + func DecodeCreateElementNode(reader io.Reader) (Message, error) { - var err error = nil - msg := &CreateElementNode{} - if msg.ID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.ParentID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.index, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Tag, err = ReadString(reader); err != nil { - return nil, err - } - if msg.SVG, err = ReadBoolean(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &CreateElementNode{} + if msg.ID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.ParentID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.index, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Tag, err = ReadString(reader); err != nil { + return nil, err + } + if msg.SVG, err = ReadBoolean(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeCreateTextNode(reader io.Reader) (Message, error) { - var err error = nil - msg := &CreateTextNode{} - if msg.ID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.ParentID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Index, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &CreateTextNode{} + if msg.ID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.ParentID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Index, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeMoveNode(reader io.Reader) (Message, error) { - var err error = nil - msg := &MoveNode{} - if msg.ID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.ParentID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Index, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &MoveNode{} + if msg.ID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.ParentID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Index, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeRemoveNode(reader io.Reader) (Message, error) { - var err error = nil - msg := &RemoveNode{} - if msg.ID, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &RemoveNode{} + if msg.ID, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeSetNodeAttribute(reader io.Reader) (Message, error) { - var err error = nil - msg := &SetNodeAttribute{} - if msg.ID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Name, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Value, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &SetNodeAttribute{} + if msg.ID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Name, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Value, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeRemoveNodeAttribute(reader io.Reader) (Message, error) { - var err error = nil - msg := &RemoveNodeAttribute{} - if msg.ID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Name, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &RemoveNodeAttribute{} + if msg.ID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Name, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeSetNodeData(reader io.Reader) (Message, error) { - var err error = nil - msg := &SetNodeData{} - if msg.ID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Data, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &SetNodeData{} + if msg.ID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Data, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeSetCSSData(reader io.Reader) (Message, error) { - var err error = nil - msg := &SetCSSData{} - if msg.ID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Data, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &SetCSSData{} + if msg.ID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Data, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeSetNodeScroll(reader io.Reader) (Message, error) { - var err error = nil - msg := &SetNodeScroll{} - if msg.ID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.X, err = ReadInt(reader); err != nil { - return nil, err - } - if msg.Y, err = ReadInt(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &SetNodeScroll{} + if msg.ID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.X, err = ReadInt(reader); err != nil { + return nil, err + } + if msg.Y, err = ReadInt(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeSetInputTarget(reader io.Reader) (Message, error) { - var err error = nil - msg := &SetInputTarget{} - if msg.ID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Label, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &SetInputTarget{} + if msg.ID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Label, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeSetInputValue(reader io.Reader) (Message, error) { - var err error = nil - msg := &SetInputValue{} - if msg.ID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Value, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Mask, err = ReadInt(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &SetInputValue{} + if msg.ID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Value, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Mask, err = ReadInt(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeSetInputChecked(reader io.Reader) (Message, error) { - var err error = nil - msg := &SetInputChecked{} - if msg.ID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Checked, err = ReadBoolean(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &SetInputChecked{} + if msg.ID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Checked, err = ReadBoolean(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeMouseMove(reader io.Reader) (Message, error) { - var err error = nil - msg := &MouseMove{} - if msg.X, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Y, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &MouseMove{} + if msg.X, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Y, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, err } -func DecodeMouseClickDepricated(reader io.Reader) (Message, error) { - var err error = nil - msg := &MouseClickDepricated{} - if msg.ID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.HesitationTime, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Label, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err -} func DecodeConsoleLog(reader io.Reader) (Message, error) { - var err error = nil - msg := &ConsoleLog{} - if msg.Level, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Value, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &ConsoleLog{} + if msg.Level, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Value, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodePageLoadTiming(reader io.Reader) (Message, error) { - var err error = nil - msg := &PageLoadTiming{} - if msg.RequestStart, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.ResponseStart, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.ResponseEnd, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.DomContentLoadedEventStart, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.DomContentLoadedEventEnd, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.LoadEventStart, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.LoadEventEnd, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.FirstPaint, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.FirstContentfulPaint, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &PageLoadTiming{} + if msg.RequestStart, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.ResponseStart, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.ResponseEnd, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.DomContentLoadedEventStart, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.DomContentLoadedEventEnd, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.LoadEventStart, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.LoadEventEnd, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.FirstPaint, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.FirstContentfulPaint, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, err } + func DecodePageRenderTiming(reader io.Reader) (Message, error) { - var err error = nil - msg := &PageRenderTiming{} - if msg.SpeedIndex, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.VisuallyComplete, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.TimeToInteractive, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &PageRenderTiming{} + if msg.SpeedIndex, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.VisuallyComplete, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.TimeToInteractive, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, err } -func DecodeJSException(reader io.Reader) (Message, error) { - var err error = nil - msg := &JSException{} - if msg.Name, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Message, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Payload, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + +func DecodeJSExceptionDeprecated(reader io.Reader) (Message, error) { + var err error = nil + msg := &JSExceptionDeprecated{} + if msg.Name, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Message, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Payload, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeIntegrationEvent(reader io.Reader) (Message, error) { - var err error = nil - msg := &IntegrationEvent{} - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Source, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Name, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Message, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Payload, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &IntegrationEvent{} + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Source, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Name, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Message, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Payload, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeRawCustomEvent(reader io.Reader) (Message, error) { - var err error = nil - msg := &RawCustomEvent{} - if msg.Name, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Payload, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &RawCustomEvent{} + if msg.Name, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Payload, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeUserID(reader io.Reader) (Message, error) { - var err error = nil - msg := &UserID{} - if msg.ID, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &UserID{} + if msg.ID, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeUserAnonymousID(reader io.Reader) (Message, error) { - var err error = nil - msg := &UserAnonymousID{} - if msg.ID, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &UserAnonymousID{} + if msg.ID, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeMetadata(reader io.Reader) (Message, error) { - var err error = nil - msg := &Metadata{} - if msg.Key, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Value, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &Metadata{} + if msg.Key, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Value, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodePageEvent(reader io.Reader) (Message, error) { - var err error = nil - msg := &PageEvent{} - if msg.MessageID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.URL, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Referrer, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Loaded, err = ReadBoolean(reader); err != nil { - return nil, err - } - if msg.RequestStart, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.ResponseStart, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.ResponseEnd, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.DomContentLoadedEventStart, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.DomContentLoadedEventEnd, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.LoadEventStart, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.LoadEventEnd, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.FirstPaint, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.FirstContentfulPaint, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.SpeedIndex, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.VisuallyComplete, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.TimeToInteractive, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &PageEvent{} + if msg.MessageID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.URL, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Referrer, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Loaded, err = ReadBoolean(reader); err != nil { + return nil, err + } + if msg.RequestStart, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.ResponseStart, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.ResponseEnd, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.DomContentLoadedEventStart, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.DomContentLoadedEventEnd, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.LoadEventStart, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.LoadEventEnd, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.FirstPaint, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.FirstContentfulPaint, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.SpeedIndex, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.VisuallyComplete, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.TimeToInteractive, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeInputEvent(reader io.Reader) (Message, error) { - var err error = nil - msg := &InputEvent{} - if msg.MessageID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Value, err = ReadString(reader); err != nil { - return nil, err - } - if msg.ValueMasked, err = ReadBoolean(reader); err != nil { - return nil, err - } - if msg.Label, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &InputEvent{} + if msg.MessageID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Value, err = ReadString(reader); err != nil { + return nil, err + } + if msg.ValueMasked, err = ReadBoolean(reader); err != nil { + return nil, err + } + if msg.Label, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeClickEvent(reader io.Reader) (Message, error) { - var err error = nil - msg := &ClickEvent{} - if msg.MessageID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.HesitationTime, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Label, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Selector, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &ClickEvent{} + if msg.MessageID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.HesitationTime, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Label, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Selector, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } -func DecodeErrorEvent(reader io.Reader) (Message, error) { - var err error = nil - msg := &ErrorEvent{} - if msg.MessageID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Source, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Name, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Message, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Payload, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err -} func DecodeResourceEvent(reader io.Reader) (Message, error) { - var err error = nil - msg := &ResourceEvent{} - if msg.MessageID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Duration, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.TTFB, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.HeaderSize, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.EncodedBodySize, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.DecodedBodySize, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.URL, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Type, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Success, err = ReadBoolean(reader); err != nil { - return nil, err - } - if msg.Method, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Status, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &ResourceEvent{} + if msg.MessageID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Duration, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.TTFB, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.HeaderSize, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.EncodedBodySize, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.DecodedBodySize, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.URL, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Type, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Success, err = ReadBoolean(reader); err != nil { + return nil, err + } + if msg.Method, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Status, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeCustomEvent(reader io.Reader) (Message, error) { - var err error = nil - msg := &CustomEvent{} - if msg.MessageID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Name, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Payload, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &CustomEvent{} + if msg.MessageID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Name, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Payload, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeCSSInsertRule(reader io.Reader) (Message, error) { - var err error = nil - msg := &CSSInsertRule{} - if msg.ID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Rule, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Index, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &CSSInsertRule{} + if msg.ID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Rule, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Index, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeCSSDeleteRule(reader io.Reader) (Message, error) { - var err error = nil - msg := &CSSDeleteRule{} - if msg.ID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Index, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &CSSDeleteRule{} + if msg.ID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Index, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeFetch(reader io.Reader) (Message, error) { - var err error = nil - msg := &Fetch{} - if msg.Method, err = ReadString(reader); err != nil { - return nil, err - } - if msg.URL, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Request, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Response, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Status, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Duration, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &Fetch{} + if msg.Method, err = ReadString(reader); err != nil { + return nil, err + } + if msg.URL, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Request, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Response, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Status, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Duration, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeProfiler(reader io.Reader) (Message, error) { - var err error = nil - msg := &Profiler{} - if msg.Name, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Duration, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Args, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Result, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &Profiler{} + if msg.Name, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Duration, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Args, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Result, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeOTable(reader io.Reader) (Message, error) { - var err error = nil - msg := &OTable{} - if msg.Key, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Value, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &OTable{} + if msg.Key, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Value, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeStateAction(reader io.Reader) (Message, error) { - var err error = nil - msg := &StateAction{} - if msg.Type, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &StateAction{} + if msg.Type, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeStateActionEvent(reader io.Reader) (Message, error) { - var err error = nil - msg := &StateActionEvent{} - if msg.MessageID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Type, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &StateActionEvent{} + if msg.MessageID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Type, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeRedux(reader io.Reader) (Message, error) { - var err error = nil - msg := &Redux{} - if msg.Action, err = ReadString(reader); err != nil { - return nil, err - } - if msg.State, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Duration, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &Redux{} + if msg.Action, err = ReadString(reader); err != nil { + return nil, err + } + if msg.State, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Duration, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeVuex(reader io.Reader) (Message, error) { - var err error = nil - msg := &Vuex{} - if msg.Mutation, err = ReadString(reader); err != nil { - return nil, err - } - if msg.State, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &Vuex{} + if msg.Mutation, err = ReadString(reader); err != nil { + return nil, err + } + if msg.State, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeMobX(reader io.Reader) (Message, error) { - var err error = nil - msg := &MobX{} - if msg.Type, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Payload, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &MobX{} + if msg.Type, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Payload, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeNgRx(reader io.Reader) (Message, error) { - var err error = nil - msg := &NgRx{} - if msg.Action, err = ReadString(reader); err != nil { - return nil, err - } - if msg.State, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Duration, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &NgRx{} + if msg.Action, err = ReadString(reader); err != nil { + return nil, err + } + if msg.State, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Duration, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeGraphQL(reader io.Reader) (Message, error) { - var err error = nil - msg := &GraphQL{} - if msg.OperationKind, err = ReadString(reader); err != nil { - return nil, err - } - if msg.OperationName, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Variables, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Response, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &GraphQL{} + if msg.OperationKind, err = ReadString(reader); err != nil { + return nil, err + } + if msg.OperationName, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Variables, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Response, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodePerformanceTrack(reader io.Reader) (Message, error) { - var err error = nil - msg := &PerformanceTrack{} - if msg.Frames, err = ReadInt(reader); err != nil { - return nil, err - } - if msg.Ticks, err = ReadInt(reader); err != nil { - return nil, err - } - if msg.TotalJSHeapSize, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.UsedJSHeapSize, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &PerformanceTrack{} + if msg.Frames, err = ReadInt(reader); err != nil { + return nil, err + } + if msg.Ticks, err = ReadInt(reader); err != nil { + return nil, err + } + if msg.TotalJSHeapSize, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.UsedJSHeapSize, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeGraphQLEvent(reader io.Reader) (Message, error) { - var err error = nil - msg := &GraphQLEvent{} - if msg.MessageID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.OperationKind, err = ReadString(reader); err != nil { - return nil, err - } - if msg.OperationName, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Variables, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Response, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &GraphQLEvent{} + if msg.MessageID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.OperationKind, err = ReadString(reader); err != nil { + return nil, err + } + if msg.OperationName, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Variables, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Response, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeFetchEvent(reader io.Reader) (Message, error) { - var err error = nil - msg := &FetchEvent{} - if msg.MessageID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Method, err = ReadString(reader); err != nil { - return nil, err - } - if msg.URL, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Request, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Response, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Status, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Duration, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &FetchEvent{} + if msg.MessageID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Method, err = ReadString(reader); err != nil { + return nil, err + } + if msg.URL, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Request, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Response, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Status, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Duration, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeDOMDrop(reader io.Reader) (Message, error) { - var err error = nil - msg := &DOMDrop{} - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &DOMDrop{} + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeResourceTiming(reader io.Reader) (Message, error) { - var err error = nil - msg := &ResourceTiming{} - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Duration, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.TTFB, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.HeaderSize, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.EncodedBodySize, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.DecodedBodySize, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.URL, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Initiator, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &ResourceTiming{} + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Duration, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.TTFB, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.HeaderSize, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.EncodedBodySize, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.DecodedBodySize, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.URL, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Initiator, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeConnectionInformation(reader io.Reader) (Message, error) { - var err error = nil - msg := &ConnectionInformation{} - if msg.Downlink, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Type, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &ConnectionInformation{} + if msg.Downlink, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Type, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeSetPageVisibility(reader io.Reader) (Message, error) { - var err error = nil - msg := &SetPageVisibility{} - if msg.hidden, err = ReadBoolean(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &SetPageVisibility{} + if msg.hidden, err = ReadBoolean(reader); err != nil { + return nil, err + } + return msg, err } + func DecodePerformanceTrackAggr(reader io.Reader) (Message, error) { - var err error = nil - msg := &PerformanceTrackAggr{} - if msg.TimestampStart, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.TimestampEnd, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.MinFPS, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.AvgFPS, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.MaxFPS, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.MinCPU, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.AvgCPU, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.MaxCPU, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.MinTotalJSHeapSize, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.AvgTotalJSHeapSize, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.MaxTotalJSHeapSize, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.MinUsedJSHeapSize, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.AvgUsedJSHeapSize, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.MaxUsedJSHeapSize, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &PerformanceTrackAggr{} + if msg.TimestampStart, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.TimestampEnd, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.MinFPS, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.AvgFPS, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.MaxFPS, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.MinCPU, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.AvgCPU, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.MaxCPU, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.MinTotalJSHeapSize, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.AvgTotalJSHeapSize, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.MaxTotalJSHeapSize, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.MinUsedJSHeapSize, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.AvgUsedJSHeapSize, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.MaxUsedJSHeapSize, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeLongTask(reader io.Reader) (Message, error) { - var err error = nil - msg := &LongTask{} - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Duration, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Context, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.ContainerType, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.ContainerSrc, err = ReadString(reader); err != nil { - return nil, err - } - if msg.ContainerId, err = ReadString(reader); err != nil { - return nil, err - } - if msg.ContainerName, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &LongTask{} + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Duration, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Context, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.ContainerType, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.ContainerSrc, err = ReadString(reader); err != nil { + return nil, err + } + if msg.ContainerId, err = ReadString(reader); err != nil { + return nil, err + } + if msg.ContainerName, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeSetNodeAttributeURLBased(reader io.Reader) (Message, error) { - var err error = nil - msg := &SetNodeAttributeURLBased{} - if msg.ID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Name, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Value, err = ReadString(reader); err != nil { - return nil, err - } - if msg.BaseURL, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &SetNodeAttributeURLBased{} + if msg.ID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Name, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Value, err = ReadString(reader); err != nil { + return nil, err + } + if msg.BaseURL, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeSetCSSDataURLBased(reader io.Reader) (Message, error) { - var err error = nil - msg := &SetCSSDataURLBased{} - if msg.ID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Data, err = ReadString(reader); err != nil { - return nil, err - } - if msg.BaseURL, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &SetCSSDataURLBased{} + if msg.ID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Data, err = ReadString(reader); err != nil { + return nil, err + } + if msg.BaseURL, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeIssueEvent(reader io.Reader) (Message, error) { - var err error = nil - msg := &IssueEvent{} - if msg.MessageID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Type, err = ReadString(reader); err != nil { - return nil, err - } - if msg.ContextString, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Context, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Payload, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &IssueEvent{} + if msg.MessageID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Type, err = ReadString(reader); err != nil { + return nil, err + } + if msg.ContextString, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Context, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Payload, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeTechnicalInfo(reader io.Reader) (Message, error) { - var err error = nil - msg := &TechnicalInfo{} - if msg.Type, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Value, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &TechnicalInfo{} + if msg.Type, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Value, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeCustomIssue(reader io.Reader) (Message, error) { - var err error = nil - msg := &CustomIssue{} - if msg.Name, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Payload, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &CustomIssue{} + if msg.Name, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Payload, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeAssetCache(reader io.Reader) (Message, error) { - var err error = nil - msg := &AssetCache{} - if msg.URL, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &AssetCache{} + if msg.URL, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeCSSInsertRuleURLBased(reader io.Reader) (Message, error) { - var err error = nil - msg := &CSSInsertRuleURLBased{} - if msg.ID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Rule, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Index, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.BaseURL, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &CSSInsertRuleURLBased{} + if msg.ID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Rule, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Index, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.BaseURL, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeMouseClick(reader io.Reader) (Message, error) { - var err error = nil - msg := &MouseClick{} - if msg.ID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.HesitationTime, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Label, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Selector, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &MouseClick{} + if msg.ID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.HesitationTime, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Label, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Selector, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeCreateIFrameDocument(reader io.Reader) (Message, error) { - var err error = nil - msg := &CreateIFrameDocument{} - if msg.FrameID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.ID, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &CreateIFrameDocument{} + if msg.FrameID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.ID, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeAdoptedSSReplaceURLBased(reader io.Reader) (Message, error) { - var err error = nil - msg := &AdoptedSSReplaceURLBased{} - if msg.SheetID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Text, err = ReadString(reader); err != nil { - return nil, err - } - if msg.BaseURL, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &AdoptedSSReplaceURLBased{} + if msg.SheetID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Text, err = ReadString(reader); err != nil { + return nil, err + } + if msg.BaseURL, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeAdoptedSSReplace(reader io.Reader) (Message, error) { - var err error = nil - msg := &AdoptedSSReplace{} - if msg.SheetID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Text, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &AdoptedSSReplace{} + if msg.SheetID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Text, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeAdoptedSSInsertRuleURLBased(reader io.Reader) (Message, error) { - var err error = nil - msg := &AdoptedSSInsertRuleURLBased{} - if msg.SheetID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Rule, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Index, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.BaseURL, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &AdoptedSSInsertRuleURLBased{} + if msg.SheetID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Rule, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Index, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.BaseURL, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeAdoptedSSInsertRule(reader io.Reader) (Message, error) { - var err error = nil - msg := &AdoptedSSInsertRule{} - if msg.SheetID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Rule, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Index, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &AdoptedSSInsertRule{} + if msg.SheetID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Rule, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Index, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeAdoptedSSDeleteRule(reader io.Reader) (Message, error) { - var err error = nil - msg := &AdoptedSSDeleteRule{} - if msg.SheetID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Index, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &AdoptedSSDeleteRule{} + if msg.SheetID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Index, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeAdoptedSSAddOwner(reader io.Reader) (Message, error) { - var err error = nil - msg := &AdoptedSSAddOwner{} - if msg.SheetID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.ID, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &AdoptedSSAddOwner{} + if msg.SheetID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.ID, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeAdoptedSSRemoveOwner(reader io.Reader) (Message, error) { - var err error = nil - msg := &AdoptedSSRemoveOwner{} - if msg.SheetID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.ID, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &AdoptedSSRemoveOwner{} + if msg.SheetID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.ID, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeZustand(reader io.Reader) (Message, error) { - var err error = nil - msg := &Zustand{} - if msg.Mutation, err = ReadString(reader); err != nil { - return nil, err - } - if msg.State, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &Zustand{} + if msg.Mutation, err = ReadString(reader); err != nil { + return nil, err + } + if msg.State, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + +func DecodeJSException(reader io.Reader) (Message, error) { + var err error = nil + msg := &JSException{} + if msg.Name, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Message, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Payload, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Metadata, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err +} + + +func DecodeSessionEnd(reader io.Reader) (Message, error) { + var err error = nil + msg := &SessionEnd{} + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.EncryptionKey, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err +} + + +func DecodeSessionSearch(reader io.Reader) (Message, error) { + var err error = nil + msg := &SessionSearch{} + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Partition, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, err +} + + func DecodeIOSBatchMeta(reader io.Reader) (Message, error) { - var err error = nil - msg := &IOSBatchMeta{} - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Length, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.FirstIndex, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &IOSBatchMeta{} + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Length, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.FirstIndex, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeIOSSessionStart(reader io.Reader) (Message, error) { - var err error = nil - msg := &IOSSessionStart{} - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.ProjectID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.TrackerVersion, err = ReadString(reader); err != nil { - return nil, err - } - if msg.RevID, err = ReadString(reader); err != nil { - return nil, err - } - if msg.UserUUID, err = ReadString(reader); err != nil { - return nil, err - } - if msg.UserOS, err = ReadString(reader); err != nil { - return nil, err - } - if msg.UserOSVersion, err = ReadString(reader); err != nil { - return nil, err - } - if msg.UserDevice, err = ReadString(reader); err != nil { - return nil, err - } - if msg.UserDeviceType, err = ReadString(reader); err != nil { - return nil, err - } - if msg.UserCountry, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &IOSSessionStart{} + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.ProjectID, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.TrackerVersion, err = ReadString(reader); err != nil { + return nil, err + } + if msg.RevID, err = ReadString(reader); err != nil { + return nil, err + } + if msg.UserUUID, err = ReadString(reader); err != nil { + return nil, err + } + if msg.UserOS, err = ReadString(reader); err != nil { + return nil, err + } + if msg.UserOSVersion, err = ReadString(reader); err != nil { + return nil, err + } + if msg.UserDevice, err = ReadString(reader); err != nil { + return nil, err + } + if msg.UserDeviceType, err = ReadString(reader); err != nil { + return nil, err + } + if msg.UserCountry, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeIOSSessionEnd(reader io.Reader) (Message, error) { - var err error = nil - msg := &IOSSessionEnd{} - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &IOSSessionEnd{} + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeIOSMetadata(reader io.Reader) (Message, error) { - var err error = nil - msg := &IOSMetadata{} - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Length, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Key, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Value, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &IOSMetadata{} + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Length, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Key, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Value, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeIOSCustomEvent(reader io.Reader) (Message, error) { - var err error = nil - msg := &IOSCustomEvent{} - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Length, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Name, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Payload, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &IOSCustomEvent{} + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Length, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Name, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Payload, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeIOSUserID(reader io.Reader) (Message, error) { - var err error = nil - msg := &IOSUserID{} - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Length, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Value, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &IOSUserID{} + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Length, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Value, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeIOSUserAnonymousID(reader io.Reader) (Message, error) { - var err error = nil - msg := &IOSUserAnonymousID{} - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Length, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Value, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &IOSUserAnonymousID{} + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Length, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Value, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeIOSScreenChanges(reader io.Reader) (Message, error) { - var err error = nil - msg := &IOSScreenChanges{} - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Length, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.X, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Y, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Width, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Height, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &IOSScreenChanges{} + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Length, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.X, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Y, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Width, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Height, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeIOSCrash(reader io.Reader) (Message, error) { - var err error = nil - msg := &IOSCrash{} - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Length, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Name, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Reason, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Stacktrace, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &IOSCrash{} + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Length, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Name, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Reason, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Stacktrace, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeIOSScreenEnter(reader io.Reader) (Message, error) { - var err error = nil - msg := &IOSScreenEnter{} - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Length, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Title, err = ReadString(reader); err != nil { - return nil, err - } - if msg.ViewName, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &IOSScreenEnter{} + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Length, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Title, err = ReadString(reader); err != nil { + return nil, err + } + if msg.ViewName, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeIOSScreenLeave(reader io.Reader) (Message, error) { - var err error = nil - msg := &IOSScreenLeave{} - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Length, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Title, err = ReadString(reader); err != nil { - return nil, err - } - if msg.ViewName, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &IOSScreenLeave{} + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Length, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Title, err = ReadString(reader); err != nil { + return nil, err + } + if msg.ViewName, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeIOSClickEvent(reader io.Reader) (Message, error) { - var err error = nil - msg := &IOSClickEvent{} - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Length, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Label, err = ReadString(reader); err != nil { - return nil, err - } - if msg.X, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Y, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &IOSClickEvent{} + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Length, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Label, err = ReadString(reader); err != nil { + return nil, err + } + if msg.X, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Y, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeIOSInputEvent(reader io.Reader) (Message, error) { - var err error = nil - msg := &IOSInputEvent{} - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Length, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Value, err = ReadString(reader); err != nil { - return nil, err - } - if msg.ValueMasked, err = ReadBoolean(reader); err != nil { - return nil, err - } - if msg.Label, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &IOSInputEvent{} + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Length, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Value, err = ReadString(reader); err != nil { + return nil, err + } + if msg.ValueMasked, err = ReadBoolean(reader); err != nil { + return nil, err + } + if msg.Label, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeIOSPerformanceEvent(reader io.Reader) (Message, error) { - var err error = nil - msg := &IOSPerformanceEvent{} - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Length, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Name, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Value, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &IOSPerformanceEvent{} + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Length, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Name, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Value, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeIOSLog(reader io.Reader) (Message, error) { - var err error = nil - msg := &IOSLog{} - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Length, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Severity, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Content, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &IOSLog{} + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Length, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Severity, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Content, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeIOSInternalError(reader io.Reader) (Message, error) { - var err error = nil - msg := &IOSInternalError{} - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Length, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Content, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &IOSInternalError{} + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Length, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Content, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeIOSNetworkCall(reader io.Reader) (Message, error) { - var err error = nil - msg := &IOSNetworkCall{} - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Length, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Duration, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Headers, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Body, err = ReadString(reader); err != nil { - return nil, err - } - if msg.URL, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Success, err = ReadBoolean(reader); err != nil { - return nil, err - } - if msg.Method, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Status, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &IOSNetworkCall{} + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Length, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Duration, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Headers, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Body, err = ReadString(reader); err != nil { + return nil, err + } + if msg.URL, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Success, err = ReadBoolean(reader); err != nil { + return nil, err + } + if msg.Method, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Status, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeIOSPerformanceAggregated(reader io.Reader) (Message, error) { - var err error = nil - msg := &IOSPerformanceAggregated{} - if msg.TimestampStart, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.TimestampEnd, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.MinFPS, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.AvgFPS, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.MaxFPS, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.MinCPU, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.AvgCPU, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.MaxCPU, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.MinMemory, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.AvgMemory, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.MaxMemory, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.MinBattery, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.AvgBattery, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.MaxBattery, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &IOSPerformanceAggregated{} + if msg.TimestampStart, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.TimestampEnd, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.MinFPS, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.AvgFPS, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.MaxFPS, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.MinCPU, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.AvgCPU, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.MaxCPU, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.MinMemory, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.AvgMemory, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.MaxMemory, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.MinBattery, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.AvgBattery, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.MaxBattery, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, err } + func DecodeIOSIssueEvent(reader io.Reader) (Message, error) { - var err error = nil - msg := &IOSIssueEvent{} - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Type, err = ReadString(reader); err != nil { - return nil, err - } - if msg.ContextString, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Context, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Payload, err = ReadString(reader); err != nil { - return nil, err - } - return msg, err + var err error = nil + msg := &IOSIssueEvent{} + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + if msg.Type, err = ReadString(reader); err != nil { + return nil, err + } + if msg.ContextString, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Context, err = ReadString(reader); err != nil { + return nil, err + } + if msg.Payload, err = ReadString(reader); err != nil { + return nil, err + } + return msg, err } + + func ReadMessage(t uint64, reader io.Reader) (Message, error) { switch t { @@ -1739,7 +1841,7 @@ func ReadMessage(t uint64, reader io.Reader) (Message, error) { return DecodeSessionStart(reader) case 3: - return DecodeSessionEnd(reader) + return DecodeSessionEndDeprecated(reader) case 4: return DecodeSetPageLocation(reader) @@ -1792,9 +1894,6 @@ func ReadMessage(t uint64, reader io.Reader) (Message, error) { case 20: return DecodeMouseMove(reader) - case 21: - return DecodeMouseClickDepricated(reader) - case 22: return DecodeConsoleLog(reader) @@ -1805,7 +1904,7 @@ func ReadMessage(t uint64, reader io.Reader) (Message, error) { return DecodePageRenderTiming(reader) case 25: - return DecodeJSException(reader) + return DecodeJSExceptionDeprecated(reader) case 26: return DecodeIntegrationEvent(reader) @@ -1831,9 +1930,6 @@ func ReadMessage(t uint64, reader io.Reader) (Message, error) { case 33: return DecodeClickEvent(reader) - case 34: - return DecodeErrorEvent(reader) - case 35: return DecodeResourceEvent(reader) @@ -1954,6 +2050,15 @@ func ReadMessage(t uint64, reader io.Reader) (Message, error) { case 79: return DecodeZustand(reader) + case 78: + return DecodeJSException(reader) + + case 126: + return DecodeSessionEnd(reader) + + case 127: + return DecodeSessionSearch(reader) + case 107: return DecodeIOSBatchMeta(reader) diff --git a/backend/pkg/queue/import.go b/backend/pkg/queue/import.go index d5daa1dd5..978798ce2 100644 --- a/backend/pkg/queue/import.go +++ b/backend/pkg/queue/import.go @@ -1,12 +1,13 @@ package queue import ( + "openreplay/backend/pkg/messages" "openreplay/backend/pkg/queue/types" "openreplay/backend/pkg/redisstream" ) -func NewConsumer(group string, topics []string, handler types.MessageHandler, _ bool, _ int) types.Consumer { - return redisstream.NewConsumer(group, topics, handler) +func NewConsumer(group string, topics []string, iterator messages.MessageIterator, _ bool, _ int) types.Consumer { + return redisstream.NewConsumer(group, topics, iterator) } func NewProducer(_ int, _ bool) types.Producer { diff --git a/backend/pkg/queue/messages.go b/backend/pkg/queue/messages.go deleted file mode 100644 index f52813492..000000000 --- a/backend/pkg/queue/messages.go +++ /dev/null @@ -1,12 +0,0 @@ -package queue - -import ( - "openreplay/backend/pkg/messages" - "openreplay/backend/pkg/queue/types" -) - -func NewMessageConsumer(group string, topics []string, handler types.RawMessageHandler, autoCommit bool, messageSizeLimit int) types.Consumer { - return NewConsumer(group, topics, func(sessionID uint64, value []byte, meta *types.Meta) { - handler(sessionID, messages.NewIterator(value), meta) - }, autoCommit, messageSizeLimit) -} diff --git a/backend/pkg/queue/types/types.go b/backend/pkg/queue/types/types.go index 0f196c608..48408ce10 100644 --- a/backend/pkg/queue/types/types.go +++ b/backend/pkg/queue/types/types.go @@ -1,30 +1,17 @@ package types -import ( - "openreplay/backend/pkg/messages" -) - +// Consumer reads batches of session data from queue (redis or kafka) type Consumer interface { ConsumeNext() error - Commit() error CommitBack(gap int64) error + Commit() error Close() - HasFirstPartition() bool } +// Producer sends batches of session data to queue (redis or kafka) type Producer interface { Produce(topic string, key uint64, value []byte) error ProduceToPartition(topic string, partition, key uint64, value []byte) error - Close(timeout int) Flush(timeout int) + Close(timeout int) } - -type Meta struct { - ID uint64 - Topic string - Timestamp int64 -} - -type MessageHandler func(uint64, []byte, *Meta) -type DecodedMessageHandler func(uint64, messages.Message, *Meta) -type RawMessageHandler func(uint64, messages.Iterator, *Meta) diff --git a/backend/pkg/redisstream/consumer.go b/backend/pkg/redisstream/consumer.go index bae70120d..228b2c7a0 100644 --- a/backend/pkg/redisstream/consumer.go +++ b/backend/pkg/redisstream/consumer.go @@ -3,6 +3,7 @@ package redisstream import ( "log" "net" + "openreplay/backend/pkg/messages" "sort" "strconv" "strings" @@ -10,8 +11,6 @@ import ( _redis "github.com/go-redis/redis" "github.com/pkg/errors" - - "openreplay/backend/pkg/queue/types" ) type idsInfo struct { @@ -21,16 +20,16 @@ type idsInfo struct { type streamPendingIDsMap map[string]*idsInfo type Consumer struct { - redis *_redis.Client - streams []string - group string - messageHandler types.MessageHandler - idsPending streamPendingIDsMap - lastTs int64 - autoCommit bool + redis *_redis.Client + streams []string + group string + messageIterator messages.MessageIterator + idsPending streamPendingIDsMap + lastTs int64 + autoCommit bool } -func NewConsumer(group string, streams []string, messageHandler types.MessageHandler) *Consumer { +func NewConsumer(group string, streams []string, messageIterator messages.MessageIterator) *Consumer { redis := getRedisClient() for _, stream := range streams { err := redis.XGroupCreateMkStream(stream, group, "0").Err() @@ -52,12 +51,12 @@ func NewConsumer(group string, streams []string, messageHandler types.MessageHan } return &Consumer{ - redis: redis, - messageHandler: messageHandler, - streams: streams, - group: group, - autoCommit: true, - idsPending: idsPending, + redis: redis, + messageIterator: messageIterator, + streams: streams, + group: group, + autoCommit: true, + idsPending: idsPending, } } @@ -102,11 +101,8 @@ func (c *Consumer) ConsumeNext() error { if idx > 0x1FFF { return errors.New("Too many messages per ms in redis") } - c.messageHandler(sessionID, []byte(valueString), &types.Meta{ - Topic: r.Stream, - Timestamp: int64(ts), - ID: ts<<13 | (idx & 0x1FFF), // Max: 4096 messages/ms for 69 years - }) + bID := ts<<13 | (idx & 0x1FFF) // Max: 4096 messages/ms for 69 years + c.messageIterator.Iterate([]byte(valueString), messages.NewBatchInfo(sessionID, r.Stream, bID, 0, int64(ts))) if c.autoCommit { if err = c.redis.XAck(r.Stream, c.group, m.ID).Err(); err != nil { return errors.Wrapf(err, "Acknoledgment error for messageID %v", m.ID) @@ -161,7 +157,3 @@ func (c *Consumer) CommitBack(gap int64) error { func (c *Consumer) Close() { // noop } - -func (c *Consumer) HasFirstPartition() bool { - return false -} diff --git a/backend/pkg/sessions/builder.go b/backend/pkg/sessions/builder.go index c9cb0b6dd..d21fd890a 100644 --- a/backend/pkg/sessions/builder.go +++ b/backend/pkg/sessions/builder.go @@ -66,6 +66,7 @@ func (b *builder) handleMessage(message Message, messageID uint64) { b.lastSystemTime = time.Now() for _, p := range b.processors { if rm := p.Handle(message, messageID, b.timestamp); rm != nil { + rm.Meta().SetMeta(message.Meta()) b.readyMsgs = append(b.readyMsgs, rm) } } diff --git a/backend/pkg/sessions/builderMap.go b/backend/pkg/sessions/builderMap.go index f26993c13..bdf8e8686 100644 --- a/backend/pkg/sessions/builderMap.go +++ b/backend/pkg/sessions/builderMap.go @@ -30,7 +30,9 @@ func (m *builderMap) GetBuilder(sessionID uint64) *builder { return b } -func (m *builderMap) HandleMessage(sessionID uint64, msg Message, messageID uint64) { +func (m *builderMap) HandleMessage(msg Message) { + sessionID := msg.SessionID() + messageID := msg.Meta().Index b := m.GetBuilder(sessionID) b.handleMessage(msg, messageID) } @@ -39,6 +41,7 @@ func (m *builderMap) iterateSessionReadyMessages(sessionID uint64, b *builder, i if b.ended || b.lastSystemTime.Add(FORCE_DELETE_TIMEOUT).Before(time.Now()) { for _, p := range b.processors { if rm := p.Build(); rm != nil { + rm.Meta().SetSessionID(sessionID) b.readyMsgs = append(b.readyMsgs, rm) } } diff --git a/backend/pkg/token/tokenizer.go b/backend/pkg/token/tokenizer.go index f61e1f145..dd45907a8 100644 --- a/backend/pkg/token/tokenizer.go +++ b/backend/pkg/token/tokenizer.go @@ -23,6 +23,7 @@ func NewTokenizer(secret string) *Tokenizer { type TokenData struct { ID uint64 + Delay int64 ExpTime int64 } @@ -34,6 +35,7 @@ func (tokenizer *Tokenizer) sign(body string) []byte { func (tokenizer *Tokenizer) Compose(d TokenData) string { body := strconv.FormatUint(d.ID, 36) + + "." + strconv.FormatInt(d.Delay, 36) + "." + strconv.FormatInt(d.ExpTime, 36) sign := base58.Encode(tokenizer.sign(body)) return body + "." + sign @@ -41,12 +43,12 @@ func (tokenizer *Tokenizer) Compose(d TokenData) string { func (tokenizer *Tokenizer) Parse(token string) (*TokenData, error) { data := strings.Split(token, ".") - if len(data) != 3 { + if len(data) != 4 { return nil, errors.New("wrong token format") } if !hmac.Equal( base58.Decode(data[len(data)-1]), - tokenizer.sign(data[0]+"."+data[1]), + tokenizer.sign(strings.Join(data[:len(data)-1], ".")), ) { return nil, errors.New("wrong token sign") } @@ -54,12 +56,16 @@ func (tokenizer *Tokenizer) Parse(token string) (*TokenData, error) { if err != nil { return nil, err } - expTime, err := strconv.ParseInt(data[1], 36, 64) + delay, err := strconv.ParseInt(data[1], 36, 64) + if err != nil { + return nil, err + } + expTime, err := strconv.ParseInt(data[2], 36, 64) if err != nil { return nil, err } if expTime <= time.Now().UnixMilli() { - return &TokenData{id, expTime}, EXPIRED + return &TokenData{id, delay, expTime}, EXPIRED } - return &TokenData{id, expTime}, nil + return &TokenData{id, delay, expTime}, nil } diff --git a/ee/backend/internal/db/datasaver/messages.go b/ee/backend/internal/db/datasaver/messages.go index 3187a0c91..f28bd3b8f 100644 --- a/ee/backend/internal/db/datasaver/messages.go +++ b/ee/backend/internal/db/datasaver/messages.go @@ -3,31 +3,40 @@ package datasaver import ( "fmt" "log" - "openreplay/backend/pkg/messages" + . "openreplay/backend/pkg/messages" ) -func (mi *Saver) InsertMessage(sessionID uint64, msg messages.Message) error { +func (mi *Saver) InsertMessage(msg Message) error { + sessionID := msg.SessionID() switch m := msg.(type) { // Common - case *messages.Metadata: + case *Metadata: if err := mi.pg.InsertMetadata(sessionID, m); err != nil { return fmt.Errorf("insert metadata err: %s", err) } return nil - case *messages.IssueEvent: + case *IssueEvent: + session, err := mi.pg.GetSession(sessionID) + if err != nil { + log.Printf("can't get session info for CH: %s", err) + } else { + if err := mi.ch.InsertIssue(session, m); err != nil { + log.Printf("can't insert issue event into clickhouse: %s", err) + } + } return mi.pg.InsertIssueEvent(sessionID, m) //TODO: message adapter (transformer) (at the level of pkg/message) for types: *IOSMetadata, *IOSIssueEvent and others // Web - case *messages.SessionStart: + case *SessionStart: return mi.pg.HandleWebSessionStart(sessionID, m) - case *messages.SessionEnd: + case *SessionEnd: return mi.pg.HandleWebSessionEnd(sessionID, m) - case *messages.UserID: + case *UserID: return mi.pg.InsertWebUserID(sessionID, m) - case *messages.UserAnonymousID: + case *UserAnonymousID: return mi.pg.InsertWebUserAnonymousID(sessionID, m) - case *messages.CustomEvent: + case *CustomEvent: session, err := mi.pg.GetSession(sessionID) if err != nil { log.Printf("can't get session info for CH: %s", err) @@ -37,17 +46,19 @@ func (mi *Saver) InsertMessage(sessionID uint64, msg messages.Message) error { } } return mi.pg.InsertWebCustomEvent(sessionID, m) - case *messages.ClickEvent: + case *ClickEvent: return mi.pg.InsertWebClickEvent(sessionID, m) - case *messages.InputEvent: + case *InputEvent: return mi.pg.InsertWebInputEvent(sessionID, m) // Unique Web messages - case *messages.PageEvent: + case *PageEvent: return mi.pg.InsertWebPageEvent(sessionID, m) - case *messages.ErrorEvent: - return mi.pg.InsertWebErrorEvent(sessionID, m) - case *messages.FetchEvent: + case *JSException: + return mi.pg.InsertWebJSException(m) + case *IntegrationEvent: + return mi.pg.InsertWebIntegrationEvent(m) + case *FetchEvent: session, err := mi.pg.GetSession(sessionID) if err != nil { log.Printf("can't get session info for CH: %s", err) @@ -62,7 +73,7 @@ func (mi *Saver) InsertMessage(sessionID uint64, msg messages.Message) error { } } return mi.pg.InsertWebFetchEvent(sessionID, m) - case *messages.GraphQLEvent: + case *GraphQLEvent: session, err := mi.pg.GetSession(sessionID) if err != nil { log.Printf("can't get session info for CH: %s", err) @@ -72,39 +83,30 @@ func (mi *Saver) InsertMessage(sessionID uint64, msg messages.Message) error { } } return mi.pg.InsertWebGraphQLEvent(sessionID, m) - case *messages.IntegrationEvent: - return mi.pg.InsertWebErrorEvent(sessionID, &messages.ErrorEvent{ - MessageID: m.Meta().Index, - Timestamp: m.Timestamp, - Source: m.Source, - Name: m.Name, - Message: m.Message, - Payload: m.Payload, - }) - case *messages.SetPageLocation: + case *SetPageLocation: return mi.pg.InsertSessionReferrer(sessionID, m.Referrer) // IOS - case *messages.IOSSessionStart: + case *IOSSessionStart: return mi.pg.InsertIOSSessionStart(sessionID, m) - case *messages.IOSSessionEnd: + case *IOSSessionEnd: return mi.pg.InsertIOSSessionEnd(sessionID, m) - case *messages.IOSUserID: + case *IOSUserID: return mi.pg.InsertIOSUserID(sessionID, m) - case *messages.IOSUserAnonymousID: + case *IOSUserAnonymousID: return mi.pg.InsertIOSUserAnonymousID(sessionID, m) - case *messages.IOSCustomEvent: + case *IOSCustomEvent: return mi.pg.InsertIOSCustomEvent(sessionID, m) - case *messages.IOSClickEvent: + case *IOSClickEvent: return mi.pg.InsertIOSClickEvent(sessionID, m) - case *messages.IOSInputEvent: + case *IOSInputEvent: return mi.pg.InsertIOSInputEvent(sessionID, m) // Unique IOS messages - case *messages.IOSNetworkCall: + case *IOSNetworkCall: return mi.pg.InsertIOSNetworkCall(sessionID, m) - case *messages.IOSScreenEnter: + case *IOSScreenEnter: return mi.pg.InsertIOSScreenEnter(sessionID, m) - case *messages.IOSCrash: + case *IOSCrash: return mi.pg.InsertIOSCrash(sessionID, m) } diff --git a/ee/backend/internal/db/datasaver/stats.go b/ee/backend/internal/db/datasaver/stats.go index e018a2575..c18918c63 100644 --- a/ee/backend/internal/db/datasaver/stats.go +++ b/ee/backend/internal/db/datasaver/stats.go @@ -32,12 +32,14 @@ func (si *Saver) InsertStats(session *types.Session, msg messages.Message) error return si.ch.InsertWebPageEvent(session, m) case *messages.ResourceEvent: return si.ch.InsertWebResourceEvent(session, m) - case *messages.ErrorEvent: - return si.ch.InsertWebErrorEvent(session, m) + case *messages.JSException: + return si.ch.InsertWebErrorEvent(session, types.WrapJSException(m)) + case *messages.IntegrationEvent: + return si.ch.InsertWebErrorEvent(session, types.WrapIntegrationEvent(m)) } return nil } -func (si *Saver) CommitStats(optimize bool) error { +func (si *Saver) CommitStats() error { return si.ch.Commit() } diff --git a/ee/backend/internal/storage/encryptor.go b/ee/backend/internal/storage/encryptor.go new file mode 100644 index 000000000..457f45323 --- /dev/null +++ b/ee/backend/internal/storage/encryptor.go @@ -0,0 +1,65 @@ +package storage + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "errors" + "fmt" + "math/rand" +) + +const letterSet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + +func GenerateEncryptionKey() []byte { + return append(generateRandomBytes(16), generateRandomBytes(16)...) +} + +func generateRandomBytes(size int) []byte { + b := make([]byte, size) + for i := range b { + b[i] = letterSet[rand.Int63()%int64(len(letterSet))] + } + return b +} + +func fillLastBlock(rawText []byte, blockSize int) []byte { + padding := blockSize - len(rawText)%blockSize + padText := bytes.Repeat([]byte{byte(padding)}, padding) + return append(rawText, padText...) +} + +func EncryptData(data, fullKey []byte) ([]byte, error) { + if len(fullKey) != 32 { + return nil, errors.New("wrong format of encryption key") + } + key, iv := fullKey[:16], fullKey[16:] + // Fill the last block of data by zeros + paddedData := fillLastBlock(data, aes.BlockSize) + // Create new AES cipher with CBC encryptor + block, err := aes.NewCipher(key) + if err != nil { + return nil, fmt.Errorf("cbc encryptor failed: %s", err) + } + mode := cipher.NewCBCEncrypter(block, iv) + // Encrypting data + ciphertext := make([]byte, len(paddedData)) + mode.CryptBlocks(ciphertext, paddedData) + // Return encrypted data + return ciphertext, nil +} + +func DecryptData(data, fullKey []byte) ([]byte, error) { + if len(fullKey) != 32 { + return nil, errors.New("wrong format of encryption key") + } + key, iv := fullKey[:16], fullKey[16:] + block, err := aes.NewCipher(key) + if err != nil { + return nil, fmt.Errorf("cbc encryptor failed: %s", err) + } + cbc := cipher.NewCBCDecrypter(block, iv) + res := make([]byte, len(data)) + cbc.CryptBlocks(res, data) + return res, nil +} diff --git a/ee/backend/pkg/db/clickhouse/bulk.go b/ee/backend/pkg/db/clickhouse/bulk.go new file mode 100644 index 000000000..706b66f68 --- /dev/null +++ b/ee/backend/pkg/db/clickhouse/bulk.go @@ -0,0 +1,55 @@ +package clickhouse + +import ( + "context" + "errors" + "fmt" + "log" + + "github.com/ClickHouse/clickhouse-go/v2/lib/driver" +) + +type Bulk interface { + Append(args ...interface{}) error + Send() error +} + +type bulkImpl struct { + conn driver.Conn + query string + values [][]interface{} +} + +func NewBulk(conn driver.Conn, query string) (Bulk, error) { + switch { + case conn == nil: + return nil, errors.New("clickhouse connection is empty") + case query == "": + return nil, errors.New("query is empty") + } + return &bulkImpl{ + conn: conn, + query: query, + values: make([][]interface{}, 0), + }, nil +} + +func (b *bulkImpl) Append(args ...interface{}) error { + b.values = append(b.values, args) + return nil +} + +func (b *bulkImpl) Send() error { + batch, err := b.conn.PrepareBatch(context.Background(), b.query) + if err != nil { + return fmt.Errorf("can't create new batch: %s", err) + } + for _, set := range b.values { + if err := batch.Append(set...); err != nil { + log.Printf("can't append value set to batch, err: %s", err) + log.Printf("failed query: %s", b.query) + } + } + b.values = make([][]interface{}, 0) + return batch.Send() +} diff --git a/ee/backend/pkg/db/clickhouse/connector.go b/ee/backend/pkg/db/clickhouse/connector.go index e15ca13b2..9637c8fe2 100644 --- a/ee/backend/pkg/db/clickhouse/connector.go +++ b/ee/backend/pkg/db/clickhouse/connector.go @@ -1,13 +1,11 @@ package clickhouse import ( - "context" "errors" "fmt" "github.com/ClickHouse/clickhouse-go/v2" "github.com/ClickHouse/clickhouse-go/v2/lib/driver" "log" - "math" "openreplay/backend/pkg/db/types" "openreplay/backend/pkg/hashid" "openreplay/backend/pkg/messages" @@ -18,54 +16,6 @@ import ( "openreplay/backend/pkg/license" ) -type Bulk interface { - Append(args ...interface{}) error - Send() error -} - -type bulkImpl struct { - conn driver.Conn - query string - values [][]interface{} -} - -func NewBulk(conn driver.Conn, query string) (Bulk, error) { - switch { - case conn == nil: - return nil, errors.New("clickhouse connection is empty") - case query == "": - return nil, errors.New("query is empty") - } - return &bulkImpl{ - conn: conn, - query: query, - values: make([][]interface{}, 0), - }, nil -} - -func (b *bulkImpl) Append(args ...interface{}) error { - b.values = append(b.values, args) - return nil -} - -func (b *bulkImpl) Send() error { - batch, err := b.conn.PrepareBatch(context.Background(), b.query) - if err != nil { - return fmt.Errorf("can't create new batch: %s", err) - } - for _, set := range b.values { - if err := batch.Append(set...); err != nil { - log.Printf("can't append value set to batch, err: %s", err) - log.Printf("failed query: %s", b.query) - } - } - b.values = make([][]interface{}, 0) - return batch.Send() -} - -var CONTEXT_MAP = map[uint64]string{0: "unknown", 1: "self", 2: "same-origin-ancestor", 3: "same-origin-descendant", 4: "same-origin", 5: "cross-origin-ancestor", 6: "cross-origin-descendant", 7: "cross-origin-unreachable", 8: "multiple-contexts"} -var CONTAINER_TYPE_MAP = map[uint64]string{0: "window", 1: "iframe", 2: "embed", 3: "object"} - type Connector interface { Prepare() error Commit() error @@ -74,12 +24,13 @@ type Connector interface { InsertWebPageEvent(session *types.Session, msg *messages.PageEvent) error InsertWebClickEvent(session *types.Session, msg *messages.ClickEvent) error InsertWebInputEvent(session *types.Session, msg *messages.InputEvent) error - InsertWebErrorEvent(session *types.Session, msg *messages.ErrorEvent) error + InsertWebErrorEvent(session *types.Session, msg *types.ErrorEvent) error InsertWebPerformanceTrackAggr(session *types.Session, msg *messages.PerformanceTrackAggr) error InsertAutocomplete(session *types.Session, msgType, msgValue string) error InsertRequest(session *types.Session, msg *messages.FetchEvent, savePayload bool) error InsertCustom(session *types.Session, msg *messages.CustomEvent) error InsertGraphQL(session *types.Session, msg *messages.GraphQLEvent) error + InsertIssue(session *types.Session, msg *messages.IssueEvent) error } type connectorImpl struct { @@ -131,11 +82,13 @@ var batches = map[string]string{ "pages": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, url, request_start, response_start, response_end, dom_content_loaded_event_start, dom_content_loaded_event_end, load_event_start, load_event_end, first_paint, first_contentful_paint_time, speed_index, visually_complete, time_to_interactive, event_type) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", "clicks": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, label, hesitation_time, event_type) VALUES (?, ?, ?, ?, ?, ?, ?)", "inputs": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, label, event_type) VALUES (?, ?, ?, ?, ?, ?)", - "errors": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, source, name, message, error_id, event_type) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", + "errors": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, source, name, message, error_id, event_type, error_tags_keys, error_tags_values) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", "performance": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, url, min_fps, avg_fps, max_fps, min_cpu, avg_cpu, max_cpu, min_total_js_heap_size, avg_total_js_heap_size, max_total_js_heap_size, min_used_js_heap_size, avg_used_js_heap_size, max_used_js_heap_size, event_type) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", "requests": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, url, request_body, response_body, status, method, duration, success, event_type) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", "custom": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, name, payload, event_type) VALUES (?, ?, ?, ?, ?, ?, ?)", "graphql": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, name, request_body, response_body, event_type) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + "issuesEvents": "INSERT INTO experimental.events (session_id, project_id, message_id, datetime, issue_id, issue_type, event_type) VALUES (?, ?, ?, ?, ?, ?, ?)", + "issues": "INSERT INTO experimental.issues (project_id, issue_id, type, context_string) VALUES (?, ?, ?, ?)", } func (c *connectorImpl) Prepare() error { @@ -162,6 +115,32 @@ func (c *connectorImpl) checkError(name string, err error) { } } +func (c *connectorImpl) InsertIssue(session *types.Session, msg *messages.IssueEvent) error { + issueID := hashid.IssueID(session.ProjectID, msg) + if err := c.batches["issuesEvents"].Append( + session.SessionID, + uint16(session.ProjectID), + msg.MessageID, + datetime(msg.Timestamp), + issueID, + msg.Type, + "ISSUE", + ); err != nil { + c.checkError("issuesEvents", err) + return fmt.Errorf("can't append to issuesEvents batch: %s", err) + } + if err := c.batches["issues"].Append( + uint16(session.ProjectID), + issueID, + msg.Type, + msg.ContextString, + ); err != nil { + c.checkError("issues", err) + return fmt.Errorf("can't append to issues batch: %s", err) + } + return nil +} + func (c *connectorImpl) InsertWebSession(session *types.Session) error { if session.Duration == nil { return errors.New("trying to insert session with nil duration") @@ -297,7 +276,13 @@ func (c *connectorImpl) InsertWebInputEvent(session *types.Session, msg *message return nil } -func (c *connectorImpl) InsertWebErrorEvent(session *types.Session, msg *messages.ErrorEvent) error { +func (c *connectorImpl) InsertWebErrorEvent(session *types.Session, msg *types.ErrorEvent) error { + keys, values := make([]string, 0, len(msg.Tags)), make([]*string, 0, len(msg.Tags)) + for k, v := range msg.Tags { + keys = append(keys, k) + values = append(values, v) + } + if err := c.batches["errors"].Append( session.SessionID, uint16(session.ProjectID), @@ -306,8 +291,10 @@ func (c *connectorImpl) InsertWebErrorEvent(session *types.Session, msg *message msg.Source, nullableString(msg.Name), msg.Message, - hashid.WebErrorID(session.ProjectID, msg), + msg.ID(session.ProjectID), "ERROR", + keys, + values, ); err != nil { c.checkError("errors", err) return fmt.Errorf("can't append to errors batch: %s", err) @@ -420,40 +407,3 @@ func (c *connectorImpl) InsertGraphQL(session *types.Session, msg *messages.Grap } return nil } - -func nullableUint16(v uint16) *uint16 { - var p *uint16 = nil - if v != 0 { - p = &v - } - return p -} - -func nullableUint32(v uint32) *uint32 { - var p *uint32 = nil - if v != 0 { - p = &v - } - return p -} - -func nullableString(v string) *string { - var p *string = nil - if v != "" { - p = &v - } - return p -} - -func datetime(timestamp uint64) time.Time { - t := time.Unix(int64(timestamp/1e3), 0) - // Temporal solution for not correct timestamps in performance messages - if t.Year() < 2022 || t.Year() > 2025 { - return time.Now() - } - return t -} - -func getSqIdx(messageID uint64) uint { - return uint(messageID % math.MaxInt32) -} diff --git a/ee/backend/pkg/db/clickhouse/insert_type.go b/ee/backend/pkg/db/clickhouse/insert_type.go new file mode 100644 index 000000000..b83898d6f --- /dev/null +++ b/ee/backend/pkg/db/clickhouse/insert_type.go @@ -0,0 +1,38 @@ +package clickhouse + +import ( + "time" +) + +func nullableUint16(v uint16) *uint16 { + var p *uint16 = nil + if v != 0 { + p = &v + } + return p +} + +func nullableUint32(v uint32) *uint32 { + var p *uint32 = nil + if v != 0 { + p = &v + } + return p +} + +func nullableString(v string) *string { + var p *string = nil + if v != "" { + p = &v + } + return p +} + +func datetime(timestamp uint64) time.Time { + t := time.Unix(int64(timestamp/1e3), 0) + // Temporal solution for not correct timestamps in performance messages + if t.Year() < 2022 || t.Year() > 2025 { + return time.Now() + } + return t +} diff --git a/ee/backend/pkg/failover/failover.go b/ee/backend/pkg/failover/failover.go index d69a2c86a..1b9321afc 100644 --- a/ee/backend/pkg/failover/failover.go +++ b/ee/backend/pkg/failover/failover.go @@ -8,7 +8,6 @@ import ( "openreplay/backend/pkg/messages" "openreplay/backend/pkg/queue" "openreplay/backend/pkg/queue/types" - "strconv" ) const numberOfPartitions = 16 @@ -55,19 +54,16 @@ func NewSessionFinder(cfg *config.Config, stg *storage.Storage) (SessionFinder, done: make(chan struct{}, 1), } finder.producer = queue.NewProducer(cfg.MessageSizeLimit, false) - finder.consumer = queue.NewMessageConsumer( + finder.consumer = queue.NewConsumer( cfg.GroupFailover, []string{ cfg.TopicFailover, }, - func(sessionID uint64, iter messages.Iterator, meta *types.Meta) { - for iter.Next() { - if iter.Type() == 127 { - m := iter.Message().Decode().(*messages.SessionSearch) - finder.findSession(sessionID, m.Timestamp, m.Partition) - } - } - }, + messages.NewMessageIterator( + func(msg messages.Message) { + m := msg.(*messages.SessionSearch) + finder.findSession(m.SessionID(), m.Timestamp, m.Partition) + }, []int{messages.MsgSessionSearch}, true), true, cfg.MessageSizeLimit, ) @@ -93,7 +89,9 @@ func (s *sessionFinderImpl) worker() { } func (s *sessionFinderImpl) findSession(sessionID, timestamp, partition uint64) { - err := s.storage.UploadKey(strconv.FormatUint(sessionID, 10), 5) + sessEnd := &messages.SessionEnd{Timestamp: timestamp} + sessEnd.SetSessionID(sessionID) + err := s.storage.UploadSessionFiles(sessEnd) if err == nil { log.Printf("found session: %d in partition: %d, original: %d", sessionID, partition, sessionID%numberOfPartitions) @@ -128,7 +126,7 @@ func (s *sessionFinderImpl) nextPartition(partition uint64) uint64 { // Create sessionSearch message and send it to queue func (s *sessionFinderImpl) sendSearchMessage(sessionID, timestamp, partition uint64) { msg := &messages.SessionSearch{Timestamp: timestamp, Partition: partition} - if err := s.producer.ProduceToPartition(s.topicName, partition, sessionID, messages.Encode(msg)); err != nil { + if err := s.producer.ProduceToPartition(s.topicName, partition, sessionID, msg.Encode()); err != nil { log.Printf("can't send SessionSearch to failover topic: %s; sessID: %d", err, sessionID) } } diff --git a/ee/backend/pkg/kafka/consumer.go b/ee/backend/pkg/kafka/consumer.go index ca37917f1..b951fcd9c 100644 --- a/ee/backend/pkg/kafka/consumer.go +++ b/ee/backend/pkg/kafka/consumer.go @@ -2,6 +2,7 @@ package kafka import ( "log" + "openreplay/backend/pkg/messages" "os" "time" @@ -9,16 +10,15 @@ import ( "gopkg.in/confluentinc/confluent-kafka-go.v1/kafka" "openreplay/backend/pkg/env" - "openreplay/backend/pkg/queue/types" ) type Message = kafka.Message type Consumer struct { - c *kafka.Consumer - messageHandler types.MessageHandler - commitTicker *time.Ticker - pollTimeout uint + c *kafka.Consumer + messageIterator messages.MessageIterator + commitTicker *time.Ticker + pollTimeout uint lastReceivedPrtTs map[int32]int64 } @@ -26,7 +26,7 @@ type Consumer struct { func NewConsumer( group string, topics []string, - messageHandler types.MessageHandler, + messageIterator messages.MessageIterator, autoCommit bool, messageSizeLimit int, ) *Consumer { @@ -70,7 +70,7 @@ func NewConsumer( return &Consumer{ c: c, - messageHandler: messageHandler, + messageIterator: messageIterator, commitTicker: commitTicker, pollTimeout: 200, lastReceivedPrtTs: make(map[int32]int64), @@ -171,11 +171,14 @@ func (consumer *Consumer) ConsumeNext() error { return errors.Wrap(e.TopicPartition.Error, "Consumer Partition Error") } ts := e.Timestamp.UnixMilli() - consumer.messageHandler(decodeKey(e.Key), e.Value, &types.Meta{ - Topic: *(e.TopicPartition.Topic), - ID: uint64(e.TopicPartition.Offset), - Timestamp: ts, - }) + consumer.messageIterator.Iterate( + e.Value, + messages.NewBatchInfo( + decodeKey(e.Key), + *(e.TopicPartition.Topic), + uint64(e.TopicPartition.Offset), + uint64(e.TopicPartition.Partition), + ts)) consumer.lastReceivedPrtTs[e.TopicPartition.Partition] = ts case kafka.Error: if e.Code() == kafka.ErrAllBrokersDown || e.Code() == kafka.ErrMaxPollExceeded { @@ -194,16 +197,3 @@ func (consumer *Consumer) Close() { log.Printf("Kafka consumer close error: %v", err) } } - -func (consumer *Consumer) HasFirstPartition() bool { - assigned, err := consumer.c.Assignment() - if err != nil { - return false - } - for _, p := range assigned { - if p.Partition == 1 { - return true - } - } - return false -} diff --git a/ee/backend/pkg/queue/import.go b/ee/backend/pkg/queue/import.go index a0c6a02f1..2aaf22f94 100644 --- a/ee/backend/pkg/queue/import.go +++ b/ee/backend/pkg/queue/import.go @@ -3,12 +3,13 @@ package queue import ( "openreplay/backend/pkg/kafka" "openreplay/backend/pkg/license" + "openreplay/backend/pkg/messages" "openreplay/backend/pkg/queue/types" ) -func NewConsumer(group string, topics []string, handler types.MessageHandler, autoCommit bool, messageSizeLimit int) types.Consumer { +func NewConsumer(group string, topics []string, iterator messages.MessageIterator, autoCommit bool, messageSizeLimit int) types.Consumer { license.CheckLicense() - return kafka.NewConsumer(group, topics, handler, autoCommit, messageSizeLimit) + return kafka.NewConsumer(group, topics, iterator, autoCommit, messageSizeLimit) } func NewProducer(messageSizeLimit int, useBatch bool) types.Producer { diff --git a/ee/connectors/msgcodec/messages.py b/ee/connectors/msgcodec/messages.py index e1fe393a4..026c12f63 100644 --- a/ee/connectors/msgcodec/messages.py +++ b/ee/connectors/msgcodec/messages.py @@ -63,7 +63,7 @@ class SessionStart(Message): self.user_id = user_id -class SessionEnd(Message): +class SessionEndDeprecated(Message): __id__ = 3 def __init__(self, timestamp): @@ -213,15 +213,6 @@ class MouseMove(Message): self.y = y -class MouseClickDepricated(Message): - __id__ = 21 - - def __init__(self, id, hesitation_time, label): - self.id = id - self.hesitation_time = hesitation_time - self.label = label - - class ConsoleLog(Message): __id__ = 22 @@ -254,7 +245,7 @@ class PageRenderTiming(Message): self.time_to_interactive = time_to_interactive -class JSException(Message): +class JSExceptionDeprecated(Message): __id__ = 25 def __init__(self, name, message, payload): @@ -349,18 +340,6 @@ class ClickEvent(Message): self.selector = selector -class ErrorEvent(Message): - __id__ = 34 - - def __init__(self, message_id, timestamp, source, name, message, payload): - self.message_id = message_id - self.timestamp = timestamp - self.source = source - self.name = name - self.message = message - self.payload = payload - - class ResourceEvent(Message): __id__ = 35 @@ -752,6 +731,32 @@ class Zustand(Message): self.state = state +class JSException(Message): + __id__ = 78 + + def __init__(self, name, message, payload, metadata): + self.name = name + self.message = message + self.payload = payload + self.metadata = metadata + + +class SessionEnd(Message): + __id__ = 126 + + def __init__(self, timestamp, encryption_key): + self.timestamp = timestamp + self.encryption_key = encryption_key + + +class SessionSearch(Message): + __id__ = 127 + + def __init__(self, timestamp, partition): + self.timestamp = timestamp + self.partition = partition + + class IOSBatchMeta(Message): __id__ = 107 diff --git a/ee/connectors/msgcodec/msgcodec.py b/ee/connectors/msgcodec/msgcodec.py index d53c3e75d..eaea7c507 100644 --- a/ee/connectors/msgcodec/msgcodec.py +++ b/ee/connectors/msgcodec/msgcodec.py @@ -124,7 +124,7 @@ class MessageCodec(Codec): ) if message_id == 3: - return SessionEnd( + return SessionEndDeprecated( timestamp=self.read_uint(reader) ) @@ -237,13 +237,6 @@ class MessageCodec(Codec): y=self.read_uint(reader) ) - if message_id == 21: - return MouseClickDepricated( - id=self.read_uint(reader), - hesitation_time=self.read_uint(reader), - label=self.read_string(reader) - ) - if message_id == 22: return ConsoleLog( level=self.read_string(reader), @@ -271,7 +264,7 @@ class MessageCodec(Codec): ) if message_id == 25: - return JSException( + return JSExceptionDeprecated( name=self.read_string(reader), message=self.read_string(reader), payload=self.read_string(reader) @@ -347,16 +340,6 @@ class MessageCodec(Codec): selector=self.read_string(reader) ) - if message_id == 34: - return ErrorEvent( - message_id=self.read_uint(reader), - timestamp=self.read_uint(reader), - source=self.read_string(reader), - name=self.read_string(reader), - message=self.read_string(reader), - payload=self.read_string(reader) - ) - if message_id == 35: return ResourceEvent( message_id=self.read_uint(reader), @@ -668,6 +651,26 @@ class MessageCodec(Codec): state=self.read_string(reader) ) + if message_id == 78: + return JSException( + name=self.read_string(reader), + message=self.read_string(reader), + payload=self.read_string(reader), + metadata=self.read_string(reader) + ) + + if message_id == 126: + return SessionEnd( + timestamp=self.read_uint(reader), + encryption_key=self.read_string(reader) + ) + + if message_id == 127: + return SessionSearch( + timestamp=self.read_uint(reader), + partition=self.read_uint(reader) + ) + if message_id == 107: return IOSBatchMeta( timestamp=self.read_uint(reader), diff --git a/ee/scripts/helm/db/init_dbs/postgresql/1.8.2/1.8.2.sql b/ee/scripts/helm/db/init_dbs/postgresql/1.8.2/1.8.2.sql index b796cc6f3..9742cd426 100644 --- a/ee/scripts/helm/db/init_dbs/postgresql/1.8.2/1.8.2.sql +++ b/ee/scripts/helm/db/init_dbs/postgresql/1.8.2/1.8.2.sql @@ -60,4 +60,6 @@ BEGIN END; $$ LANGUAGE plpgsql; +ALTER TABLE sessions ADD file_key BYTEA NULL; + COMMIT; \ No newline at end of file diff --git a/ee/sourcemap-reader/Readme.md b/ee/sourcemap-reader/Readme.md new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/.env.sample b/frontend/.env.sample index 3c8da9433..3c88729b9 100644 --- a/frontend/.env.sample +++ b/frontend/.env.sample @@ -22,5 +22,5 @@ MINIO_ACCESS_KEY = '' MINIO_SECRET_KEY = '' # APP and TRACKER VERSIONS -VERSION = '1.8.1' -TRACKER_VERSION = '4.1.0' +VERSION = '1.8.2' +TRACKER_VERSION = '4.1.5' diff --git a/frontend/README.md b/frontend/README.md index e2b4a3898..3eb54ede6 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,7 +1,7 @@ # openreplay-ui OpenReplay prototype UI -On new icon addition: +On new icon addition: `yarn gen:icons` ## Documentation @@ -14,3 +14,7 @@ On new icon addition: Labels in comments: TEMP = temporary code TODO = things to implement + +## Contributing notes + +Please use `dev` branch as base and target branch. diff --git a/frontend/app/api_client.js b/frontend/app/api_client.js index 33f7ffe66..0e4699359 100644 --- a/frontend/app/api_client.js +++ b/frontend/app/api_client.js @@ -26,6 +26,7 @@ const siteIdRequiredPaths = [ '/dashboards', '/metrics', '/unprocessed', + '/notes', // '/custom_metrics/sessions', ]; @@ -37,7 +38,7 @@ const noStoringFetchPathStarts = [ // null? export const clean = (obj, forbidenValues = [ undefined, '' ]) => { - const keys = Array.isArray(obj) + const keys = Array.isArray(obj) ? new Array(obj.length).fill().map((_, i) => i) : Object.keys(obj); const retObj = Array.isArray(obj) ? [] : {}; @@ -49,7 +50,7 @@ export const clean = (obj, forbidenValues = [ undefined, '' ]) => { retObj[key] = value; } }); - + return retObj; } @@ -70,7 +71,7 @@ export default class APIClient { this.siteId = siteId; } - fetch(path, params, options = { clean: true }) { + fetch(path, params, options = { clean: true }) { if (params !== undefined) { const cleanedParams = options.clean ? clean(params) : params; this.init.body = JSON.stringify(cleanedParams); diff --git a/frontend/app/api_middleware.js b/frontend/app/api_middleware.js index 783ebe8c3..1846a9dbc 100644 --- a/frontend/app/api_middleware.js +++ b/frontend/app/api_middleware.js @@ -2,27 +2,27 @@ import logger from 'App/logger'; import APIClient from './api_client'; import { UPDATE, DELETE } from './duck/jwt'; -export default store => next => (action) => { +export default (store) => (next) => (action) => { const { types, call, ...rest } = action; if (!call) { return next(action); } - const [ REQUEST, SUCCESS, FAILURE ] = types; + const [REQUEST, SUCCESS, FAILURE] = types; next({ ...rest, type: REQUEST }); const client = new APIClient(); return call(client) - .then(async response => { + .then(async (response) => { if (response.status === 403) { next({ type: DELETE }); } if (!response.ok) { - const text = await response.text() + const text = await response.text(); return Promise.reject(text); } - return response.json() + return response.json(); }) - .then(json => json || {}) // TEMP TODO on server: no empty responces + .then((json) => json || {}) // TEMP TODO on server: no empty responces .then(({ jwt, errors, data }) => { if (errors) { next({ type: FAILURE, errors, data }); @@ -34,14 +34,22 @@ export default store => next => (action) => { } }) .catch((e) => { - logger.error("Error during API request. ", e) - return next({ type: FAILURE, errors: JSON.parse(e).errors || [] }); + logger.error('Error during API request. ', e); + return next({ type: FAILURE, errors: parseError(e) }); }); }; +function parseError(e) { + try { + return JSON.parse(e).errors || []; + } catch { + return e; + } +} + function jwtExpired(token) { try { - const base64Url = token.split('.')[ 1 ]; + const base64Url = token.split('.')[1]; const base64 = base64Url.replace('-', '+').replace('_', '/'); const tokenObj = JSON.parse(window.atob(base64)); return tokenObj.exp * 1000 < Date.now(); // exp in Unix time (sec) diff --git a/frontend/app/components/Alerts/Notifications/Notifications.tsx b/frontend/app/components/Alerts/Notifications/Notifications.tsx index d6327d530..b5421fd29 100644 --- a/frontend/app/components/Alerts/Notifications/Notifications.tsx +++ b/frontend/app/components/Alerts/Notifications/Notifications.tsx @@ -22,24 +22,30 @@ function Notifications(props: Props) { useEffect(() => { const interval = setInterval(() => { - notificationStore.fetchNotificationsCount() + notificationStore.fetchNotificationsCount(); }, AUTOREFRESH_INTERVAL); return () => clearInterval(interval); }, []); return useObserver(() => ( - -
showModal(, { right: true }) }> -
- { count } -
- + +
showModal(, { right: true })} + > +
+ {count}
- + +
+
)); } -export default connect((state: any) => ({ - notifications: state.getIn(['notifications', 'list']), -}), { fetchList, setLastRead, setViewed, clearAll })(Notifications); \ No newline at end of file +export default connect( + (state: any) => ({ + notifications: state.getIn(['notifications', 'list']), + }), + { fetchList, setLastRead, setViewed, clearAll } +)(Notifications); diff --git a/frontend/app/components/Alerts/Notifications/notifications.module.css b/frontend/app/components/Alerts/Notifications/notifications.module.css index 3e4101502..f88767b36 100644 --- a/frontend/app/components/Alerts/Notifications/notifications.module.css +++ b/frontend/app/components/Alerts/Notifications/notifications.module.css @@ -9,16 +9,16 @@ display: flex; align-items: center; padding: 0 15px; - height: 50px; + height: 49px; transition: all 0.3s; &:hover { - background-color: $gray-lightest; + background-color: $active-blue; transition: all 0.2s; } &[data-active=true] { - background-color: $gray-lightest; + background-color: $active-blue; } } diff --git a/frontend/app/components/Assist/ChatControls/ChatControls.tsx b/frontend/app/components/Assist/ChatControls/ChatControls.tsx index 0b85dab09..625cc7f31 100644 --- a/frontend/app/components/Assist/ChatControls/ChatControls.tsx +++ b/frontend/app/components/Assist/ChatControls/ChatControls.tsx @@ -19,11 +19,11 @@ function ChatControls({ stream, endCall, videoEnabled, setVideoEnabled, isPresta if (!stream) { return; } setAudioEnabled(stream.toggleAudio()); } - + const toggleVideo = () => { if (!stream) { return; } stream.toggleVideo() - .then(setVideoEnabled) + .then((v) => setVideoEnabled(v)) } /** muting user if he is auto connected to the call */ diff --git a/frontend/app/components/Assist/ChatWindow/ChatWindow.tsx b/frontend/app/components/Assist/ChatWindow/ChatWindow.tsx index c070c3b50..35225c5d5 100644 --- a/frontend/app/components/Assist/ChatWindow/ChatWindow.tsx +++ b/frontend/app/components/Assist/ChatWindow/ChatWindow.tsx @@ -1,27 +1,35 @@ -import React, { useState, useEffect } from 'react' -import VideoContainer from '../components/VideoContainer' -import cn from 'classnames' -import Counter from 'App/components/shared/SessionItem/Counter' -import stl from './chatWindow.module.css' -import ChatControls from '../ChatControls/ChatControls' +import React, { useState, useEffect } from 'react'; +import VideoContainer from '../components/VideoContainer'; +import cn from 'classnames'; +import Counter from 'App/components/shared/SessionItem/Counter'; +import stl from './chatWindow.module.css'; +import ChatControls from '../ChatControls/ChatControls'; import Draggable from 'react-draggable'; import type { LocalStream } from 'Player/MessageDistributor/managers/LocalStream'; +import { toggleVideoLocalStream } from 'Player' export interface Props { - incomeStream: MediaStream[] | null, - localStream: LocalStream | null, - userId: string, + incomeStream: MediaStream[] | null; + localStream: LocalStream | null; + userId: string; isPrestart?: boolean; - endCall: () => void + endCall: () => void; } function ChatWindow({ userId, incomeStream, localStream, endCall, isPrestart }: Props) { - const [localVideoEnabled, setLocalVideoEnabled] = useState(false) + const [localVideoEnabled, setLocalVideoEnabled] = useState(false); + const [anyRemoteEnabled, setRemoteEnabled] = useState(false); + + const onlyLocalEnabled = localVideoEnabled && !anyRemoteEnabled; + + useEffect(() => { + toggleVideoLocalStream(localVideoEnabled) + }, [localVideoEnabled]) return ( - +
@@ -30,21 +38,39 @@ function ChatWindow({ userId, incomeStream, localStream, endCall, isPrestart }:
{incomeStream && incomeStream.length > 2 ? ' (+ other agents in the call)' : ''}
- +
-
- {incomeStream - ? incomeStream.map(stream => ) : ( +
+ {incomeStream ? ( + incomeStream.map((stream) => ( + + + + )) + ) : (
Error obtaining incoming streams
)} -
- +
+
- +
- ) + ); } -export default ChatWindow +export default ChatWindow; diff --git a/frontend/app/components/Assist/components/VideoContainer/VideoContainer.tsx b/frontend/app/components/Assist/components/VideoContainer/VideoContainer.tsx index bff02fb0c..97b8c9f9e 100644 --- a/frontend/app/components/Assist/components/VideoContainer/VideoContainer.tsx +++ b/frontend/app/components/Assist/components/VideoContainer/VideoContainer.tsx @@ -1,12 +1,13 @@ -import React, { useEffect, useRef } from 'react' +import React, { useEffect, useRef } from 'react'; interface Props { - stream: MediaStream | null - muted?: boolean, - height?: number + stream: MediaStream | null; + muted?: boolean; + height?: number | string; + setRemoteEnabled?: (isEnabled: boolean) => void; } -function VideoContainer({ stream, muted = false, height = 280 }: Props) { +function VideoContainer({ stream, muted = false, height = 280, setRemoteEnabled }: Props) { const ref = useRef(null); const [isEnabled, setEnabled] = React.useState(false); @@ -14,24 +15,43 @@ function VideoContainer({ stream, muted = false, height = 280 }: Props) { if (ref.current) { ref.current.srcObject = stream; } - }, [ ref.current, stream, stream.getVideoTracks()[0]?.getSettings().width ]) + }, [ref.current, stream, stream.getVideoTracks()[0]?.getSettings().width]); useEffect(() => { - if (!stream) { return } + if (!stream) { + return; + } const iid = setInterval(() => { - const settings = stream.getVideoTracks()[0]?.getSettings() - const isDummyVideoTrack = settings ? (settings.width === 2 || settings.frameRate === 0 || !settings.frameRate && !settings.width) : true - const shouldBeEnabled = !isDummyVideoTrack - isEnabled !== shouldBeEnabled ? setEnabled(shouldBeEnabled) : null; - }, 500) - return () => clearInterval(iid) - }, [ stream, isEnabled ]) + const track = stream.getVideoTracks()[0] + const settings = track?.getSettings(); + const isDummyVideoTrack = settings + ? settings.width === 2 || + settings.frameRate === 0 || + (!settings.frameRate && !settings.width) + : true; + const shouldBeEnabled = track.enabled && !isDummyVideoTrack; + + if (isEnabled !== shouldBeEnabled) { + setEnabled(shouldBeEnabled); + setRemoteEnabled?.(shouldBeEnabled); + } + }, 500); + return () => clearInterval(iid); + }, [stream, isEnabled]); return ( -
-
diff --git a/frontend/app/components/Client/Integrations/ZustandDoc/ZustandDoc.js b/frontend/app/components/Client/Integrations/ZustandDoc/ZustandDoc.js index 35ff5ecdf..5944e3836 100644 --- a/frontend/app/components/Client/Integrations/ZustandDoc/ZustandDoc.js +++ b/frontend/app/components/Client/Integrations/ZustandDoc/ZustandDoc.js @@ -81,7 +81,7 @@ const useBearStore = create( } /> - +
); diff --git a/frontend/app/components/Dashboard/components/Alerts/NewAlert.tsx b/frontend/app/components/Dashboard/components/Alerts/NewAlert.tsx index c44d1c31b..aee10e143 100644 --- a/frontend/app/components/Dashboard/components/Alerts/NewAlert.tsx +++ b/frontend/app/components/Dashboard/components/Alerts/NewAlert.tsx @@ -146,7 +146,7 @@ const NewAlert = (props: IProps) => { // @ts-ignore .toJS(); - + const writeQueryOption = ( e: React.ChangeEvent, @@ -196,7 +196,7 @@ const NewAlert = (props: IProps) => { > - +
{ title="Notify Through" description="You'll be noticed in app notifications. Additionally opt in to receive alerts on:" content={ - {
-
- +
diff --git a/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx b/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx index bdb9d6b7a..7bb716733 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx @@ -9,7 +9,7 @@ import DashboardListItem from './DashboardListItem'; function DashboardList() { const { dashboardStore } = useStore(); const [shownDashboards, setDashboards] = React.useState([]); - const dashboards = dashboardStore.dashboards; + const dashboards = dashboardStore.sortedDashboards; const dashboardsSearch = dashboardStore.dashboardsSearch; React.useEffect(() => { diff --git a/frontend/app/components/Dashboard/components/DashboardRouter/DashboardRouter.tsx b/frontend/app/components/Dashboard/components/DashboardRouter/DashboardRouter.tsx index a7c2d62fd..419ac64b2 100644 --- a/frontend/app/components/Dashboard/components/DashboardRouter/DashboardRouter.tsx +++ b/frontend/app/components/Dashboard/components/DashboardRouter/DashboardRouter.tsx @@ -74,11 +74,13 @@ function DashboardRouter(props: Props) { - - + + {/* @ts-ignore */} + - + + {/* @ts-ignore */} diff --git a/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx b/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx index b7748d1a7..42129f699 100644 --- a/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx +++ b/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx @@ -23,7 +23,7 @@ function DashboardSideMenu(props: Props) { return (
- + {/* */}
redirect(withSiteId(dashboard(), siteId))} />
-
+
redirect(withSiteId(metrics(), siteId))} />
-
+
{ - if (!queryParams.has('modal')) return; - queryParams.delete('modal'); - props.history.replace({ - search: queryParams.toString(), - }); - }; - const pushQuery = () => { - if (!queryParams.has('modal')) props.history.push('?modal=addMetric'); - }; + const trimQuery = () => { + if (!queryParams.has('modal')) return; + queryParams.delete('modal'); + props.history.replace({ + search: queryParams.toString(), + }); + }; + const pushQuery = () => { + if (!queryParams.has('modal')) props.history.push('?modal=addMetric'); + }; - useEffect(() => { - if (queryParams.has('modal')) { - onAddWidgets(); - trimQuery(); - } - }, []); + useEffect(() => { + if (queryParams.has('modal')) { + onAddWidgets(); + trimQuery(); + } + }, []); - useEffect(() => { - const isExists = dashboardStore.getDashboardById(dashboardId); - if (!isExists) { - props.history.push(withSiteId(`/dashboard`, siteId)); - } - }, [dashboardId]); + useEffect(() => { + const isExists = dashboardStore.getDashboardById(dashboardId); + if (!isExists) { + props.history.push(withSiteId(`/dashboard`, siteId)); + } + }, [dashboardId]); - useEffect(() => { - if (!dashboard || !dashboard.dashboardId) return; - dashboardStore.fetch(dashboard.dashboardId); - }, [dashboard]); + useEffect(() => { + if (!dashboard || !dashboard.dashboardId) return; + dashboardStore.fetch(dashboard.dashboardId); + }, [dashboard]); - const onAddWidgets = () => { - dashboardStore.initDashboard(dashboard); - showModal(, { right: true }); - }; - - const onEdit = (isTitle: boolean) => { - dashboardStore.initDashboard(dashboard); - setFocusedInput(isTitle); - setShowEditModal(true); - }; - - const onDelete = async () => { - if ( - await confirm({ - header: 'Confirm', - confirmButton: 'Yes, delete', - confirmation: `Are you sure you want to permanently delete this Dashboard?`, - }) - ) { - dashboardStore.deleteDashboard(dashboard).then(() => { - props.history.push(withSiteId(`/dashboard`, siteId)); - }); - } - }; - - if (!dashboard) return null; - - return ( - -
- setShowEditModal(false)} focusTitle={focusTitle} /> - -
-
- - {dashboard?.name} - - } - onDoubleClick={() => onEdit(true)} - className="mr-3 select-none border-b border-b-borderColor-transparent hover:border-dotted hover:border-gray-medium cursor-pointer" - actionButton={ - /* @ts-ignore */ - - setShowTooltip(false)}> - setShowTooltip(false)} isPopup siteId={siteId} /> - -
- } - > - - - } - /> -
-
-
- dashboardStore.setPeriod(period)} - right={true} - /> -
-
-
- -
-
-
-
- {/* @ts-ignore */} - -

onEdit(false)} - > - {dashboard?.description || 'Describe the purpose of this dashboard'} -

-
-
- - dashboardStore.updateKey('showAlertModal', false)} /> -
-
+ const onAddWidgets = () => { + dashboardStore.initDashboard(dashboard); + showModal( + , + { right: true } ); + }; + + const onEdit = (isTitle: boolean) => { + dashboardStore.initDashboard(dashboard); + setFocusedInput(isTitle); + setShowEditModal(true); + }; + + const onDelete = async () => { + if ( + await confirm({ + header: 'Confirm', + confirmButton: 'Yes, delete', + confirmation: `Are you sure you want to permanently delete this Dashboard?`, + }) + ) { + dashboardStore.deleteDashboard(dashboard).then(() => { + props.history.push(withSiteId(`/dashboard`, siteId)); + }); + } + }; + + if (!dashboard) return null; + + return ( + +
+ setShowEditModal(false)} + focusTitle={focusTitle} + /> + +
+
+ + {dashboard?.name} + + } + onDoubleClick={() => onEdit(true)} + className="mr-3 select-none border-b border-b-borderColor-transparent hover:border-dotted hover:border-gray-medium cursor-pointer" + actionButton={ + setShowTooltip(false)}> + + setShowTooltip(false)} + isPopup + siteId={siteId} + /> +
+ } + > + + + + } + /> +
+
+
+ dashboardStore.setPeriod(period)} + right={true} + /> +
+
+
+ +
+
+
+
+ {/* @ts-ignore */} + +

onEdit(false)} + > + {dashboard?.description || 'Describe the purpose of this dashboard'} +

+
+
+ + dashboardStore.updateKey('showAlertModal', false)} + /> +
+
+ ); } // @ts-ignore -export default withPageTitle('Dashboards - OpenReplay')(withReport(withRouter(withModal(observer(DashboardView))))); +export default withPageTitle('Dashboards - OpenReplay')( + withReport(withRouter(withModal(observer(DashboardView)))) +); diff --git a/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx b/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx index e6eb66b97..2869f5240 100644 --- a/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx +++ b/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx @@ -1,18 +1,18 @@ -import { useObserver } from 'mobx-react-lite'; +import { observer } from 'mobx-react-lite'; import React, { useEffect } from 'react'; import { NoContent, Pagination, Icon } from 'UI'; import { useStore } from 'App/mstore'; import { filterList } from 'App/utils'; import MetricListItem from '../MetricListItem'; import { sliceListPerPage } from 'App/utils'; -import { IWidget } from 'App/mstore/types/widget'; +import Widget from 'App/mstore/types/widget'; function MetricsList({ siteId }: { siteId: string }) { const { metricStore } = useStore(); - const metrics = useObserver(() => metricStore.metrics); - const metricsSearch = useObserver(() => metricStore.metricsSearch); + const metrics = metricStore.sortedWidgets; + const metricsSearch = metricStore.metricsSearch; - const filterByDashboard = (item: IWidget, searchRE: RegExp) => { + const filterByDashboard = (item: Widget, searchRE: RegExp) => { const dashboardsStr = item.dashboards.map((d: any) => d.name).join(' '); return searchRE.test(dashboardsStr); }; @@ -26,7 +26,7 @@ function MetricsList({ siteId }: { siteId: string }) { metricStore.updateKey('sessionsPage', 1); }, []); - return useObserver(() => ( + return (
- )); + ); } -export default MetricsList; +export default observer(MetricsList); diff --git a/frontend/app/components/Errors/Error/MainSection.js b/frontend/app/components/Errors/Error/MainSection.js index 35b1fef7e..d65c884cb 100644 --- a/frontend/app/components/Errors/Error/MainSection.js +++ b/frontend/app/components/Errors/Error/MainSection.js @@ -19,155 +19,138 @@ import SessionBar from './SessionBar'; @withSiteIdRouter @connect( - (state) => ({ - error: state.getIn(['errors', 'instance']), - trace: state.getIn(['errors', 'instanceTrace']), - sourcemapUploaded: state.getIn(['errors', 'sourcemapUploaded']), - resolveToggleLoading: state.getIn(['errors', 'resolve', 'loading']) || state.getIn(['errors', 'unresolve', 'loading']), - ignoreLoading: state.getIn(['errors', 'ignore', 'loading']), - toggleFavoriteLoading: state.getIn(['errors', 'toggleFavorite', 'loading']), - traceLoading: state.getIn(['errors', 'fetchTrace', 'loading']), - }), - { - resolve, - unresolve, - ignore, - toggleFavorite, - addFilterByKeyAndValue, - } + (state) => ({ + error: state.getIn(['errors', 'instance']), + trace: state.getIn(['errors', 'instanceTrace']), + sourcemapUploaded: state.getIn(['errors', 'sourcemapUploaded']), + resolveToggleLoading: + state.getIn(['errors', 'resolve', 'loading']) || + state.getIn(['errors', 'unresolve', 'loading']), + ignoreLoading: state.getIn(['errors', 'ignore', 'loading']), + toggleFavoriteLoading: state.getIn(['errors', 'toggleFavorite', 'loading']), + traceLoading: state.getIn(['errors', 'fetchTrace', 'loading']), + }), + { + resolve, + unresolve, + ignore, + toggleFavorite, + addFilterByKeyAndValue, + } ) export default class MainSection extends React.PureComponent { - resolve = () => { - const { error } = this.props; - this.props.resolve(error.errorId); - }; + resolve = () => { + const { error } = this.props; + this.props.resolve(error.errorId); + }; - unresolve = () => { - const { error } = this.props; - this.props.unresolve(error.errorId); - }; + unresolve = () => { + const { error } = this.props; + this.props.unresolve(error.errorId); + }; - ignore = () => { - const { error } = this.props; - this.props.ignore(error.errorId); - }; - bookmark = () => { - const { error } = this.props; - this.props.toggleFavorite(error.errorId); - }; + ignore = () => { + const { error } = this.props; + this.props.ignore(error.errorId); + }; + bookmark = () => { + const { error } = this.props; + this.props.toggleFavorite(error.errorId); + }; - findSessions = () => { - this.props.addFilterByKeyAndValue(FilterKey.ERROR, this.props.error.message); - this.props.history.push(sessionsRoute()); - }; + findSessions = () => { + this.props.addFilterByKeyAndValue(FilterKey.ERROR, this.props.error.message); + this.props.history.push(sessionsRoute()); + }; - render() { - const { error, trace, sourcemapUploaded, ignoreLoading, resolveToggleLoading, toggleFavoriteLoading, className, traceLoading } = this.props; - const isPlayer = window.location.pathname.includes('/session/') + render() { + const { + error, + trace, + sourcemapUploaded, + ignoreLoading, + resolveToggleLoading, + toggleFavoriteLoading, + className, + traceLoading, + } = this.props; + const isPlayer = window.location.pathname.includes('/session/'); - return ( -
-
- -
-
- {error.message} -
-
-
-
-
Over the past 30 days
-
-
-
- - {/* -
- { error.status === UNRESOLVED - ? - : - } - { error.status !== IGNORED && - - } - - - } - /> -
*/} - - {!isPlayer && ( -
-

Last session with this error

- {resentOrDate(error.lastOccurrence)} - - -
- )} - -
- - - -
+ return ( +
+
+ +
+
+ {error.message}
- ); - } +
+
+
+
Over the past 30 days
+
+
+
+ + +
+
+

Last session with this error

+ {resentOrDate(error.lastOccurrence)} + +
+ + {error.customTags.length > 0 ? ( +
+
+ More Info (most recent call) +
+
+ {error.customTags.map((tag) => ( +
+
{Object.entries(tag)[0][0]}
{Object.entries(tag)[0][1]}
+
+ ))} +
+
+ ) : null} +
+ +
+ + + +
+
+ ); + } } diff --git a/frontend/app/components/Errors/Error/SideSection.js b/frontend/app/components/Errors/Error/SideSection.js index da6e5803b..017387f26 100644 --- a/frontend/app/components/Errors/Error/SideSection.js +++ b/frontend/app/components/Errors/Error/SideSection.js @@ -31,12 +31,12 @@ function partitionsWrapper(partitions = [], mapCountry = false) { .sort((a, b) => b.count - a.count) .slice(0, showLength) .map(p => ({ - label: mapCountry - ? (countries[p.name] || "Unknown") + label: mapCountry + ? (countries[p.name] || "Unknown") : p.name, prc: p.count/sum * 100, })) - + if (otherPrcsSum > 0) { show.push({ label: "Other", @@ -47,9 +47,9 @@ function partitionsWrapper(partitions = [], mapCountry = false) { return show; } function tagsWrapper(tags = []) { - return tags.map(({ name, partitions }) => ({ - name, - partitions: partitionsWrapper(partitions, name === "country") + return tags.map(({ name, partitions }) => ({ + name, + partitions: partitionsWrapper(partitions, name === "country") })) } @@ -59,7 +59,7 @@ function dataWrapper(data = {}) { chart30: data.chart30 || [], tags: tagsWrapper(data.tags), }; -} +} @connect(state => ({ error: state.getIn([ "errors", "instance" ]) @@ -75,7 +75,7 @@ export default class SideSection extends React.PureComponent { } render() { - const { + const { className, error, data, @@ -96,20 +96,20 @@ export default class SideSection extends React.PureComponent { timeFormat={'l'} />
- - - { data.tags.length > 0 &&

Tags

} + { data.tags.length > 0 &&

Summary

} { data.tags.map(({ name, partitions }) => - -
{ topValue }
+
+
{ topValue }
{ bottomValue }
); @@ -12,4 +12,4 @@ function Label({ className, topValue, topValueSize = 'text-base', bottomValue, t Label.displayName = "Label"; -export default Label; \ No newline at end of file +export default Label; diff --git a/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx b/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx index b84fff021..813c394e3 100644 --- a/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx +++ b/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx @@ -1,83 +1,99 @@ import React from 'react'; import FunnelStepText from './FunnelStepText'; import { Icon, Popup } from 'UI'; +import { Tooltip } from 'react-tippy'; interface Props { - filter: any; + filter: any; } function FunnelBar(props: Props) { - const { filter } = props; - // const completedPercentage = calculatePercentage(filter.sessionsCount, filter.dropDueToIssues); + const { filter } = props; + // const completedPercentage = calculatePercentage(filter.sessionsCount, filter.dropDueToIssues); - return ( -
- -
-
-
{filter.completedPercentage}%
-
- {filter.dropDueToIssues > 0 && ( -
- -
{filter.dropDueToIssuesPercentage}%
-
-
- - )} -
-
-
- - {filter.sessionsCount} - Completed -
-
- - {filter.droppedCount} - Dropped -
-
+ return ( +
+ +
+
+
+ {/* {filter.completedPercentage}% */} +
- ); + {filter.dropDueToIssues > 0 && ( +
+
+ {/* @ts-ignore */} + +
+ +
+
+ )} +
+
+
+ + {filter.sessionsCount} + + ({filter.completedPercentage}%) Completed + + {/* Completed */} +
+
+ + {filter.droppedCount} + ({filter.droppedPercentage}%) Dropped +
+
+
+ ); } export default FunnelBar; const calculatePercentage = (completed: number, dropped: number) => { - const total = completed + dropped; - if (dropped === 0) return 100; - if (total === 0) return 0; + const total = completed + dropped; + if (dropped === 0) return 100; + if (total === 0) return 0; - return Math.round((completed / dropped) * 100); - -} \ No newline at end of file + return Math.round((completed / dropped) * 100); +}; diff --git a/frontend/app/components/Header/DefaultMenuView/DefaultMenuView.tsx b/frontend/app/components/Header/DefaultMenuView/DefaultMenuView.tsx new file mode 100644 index 000000000..9b5c74879 --- /dev/null +++ b/frontend/app/components/Header/DefaultMenuView/DefaultMenuView.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import { NavLink, withRouter } from 'react-router-dom'; +import { + sessions, + metrics, + assist, + client, + dashboard, + withSiteId, + CLIENT_DEFAULT_TAB, +} from 'App/routes'; +import SiteDropdown from '../SiteDropdown'; +import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; +import styles from '../header.module.css'; + +const DASHBOARD_PATH = dashboard(); +const METRICS_PATH = metrics(); +const SESSIONS_PATH = sessions(); +const ASSIST_PATH = assist(); + +interface Props { + siteId: any; +} +function DefaultMenuView(props: Props) { + const { siteId } = props; + return ( +
+ +
+
+ +
+
+ v{window.env.VERSION} +
+
+
+ + + + {'Sessions'} + + + {'Assist'} + + { + return ( + location.pathname.includes(DASHBOARD_PATH) || location.pathname.includes(METRICS_PATH) + ); + }} + > + {'Dashboards'} + +
+ ); +} + +export default DefaultMenuView; diff --git a/frontend/app/components/Header/DefaultMenuView/index.ts b/frontend/app/components/Header/DefaultMenuView/index.ts new file mode 100644 index 000000000..8b21e4cfb --- /dev/null +++ b/frontend/app/components/Header/DefaultMenuView/index.ts @@ -0,0 +1 @@ +export { default } from './DefaultMenuView' \ No newline at end of file diff --git a/frontend/app/components/Header/Header.js b/frontend/app/components/Header/Header.js index 60536ba24..bd2451571 100644 --- a/frontend/app/components/Header/Header.js +++ b/frontend/app/components/Header/Header.js @@ -2,57 +2,40 @@ import React, { useEffect, useState } from 'react'; import { connect } from 'react-redux'; import { NavLink, withRouter } from 'react-router-dom'; import cn from 'classnames'; -import { - sessions, - metrics, - assist, - client, - dashboard, - withSiteId, - CLIENT_DEFAULT_TAB, -} from 'App/routes'; +import { client, CLIENT_DEFAULT_TAB } from 'App/routes'; import { logout } from 'Duck/user'; import { Icon, Popup } from 'UI'; -import SiteDropdown from './SiteDropdown'; import styles from './header.module.css'; -import OnboardingExplore from './OnboardingExplore/OnboardingExplore' -import Announcements from '../Announcements'; +import OnboardingExplore from './OnboardingExplore/OnboardingExplore'; import Notifications from '../Alerts/Notifications'; import { init as initSite } from 'Duck/site'; +import { getInitials } from 'App/utils'; import ErrorGenPanel from 'App/dev/components'; import Alerts from '../Alerts/Alerts'; -import AnimatedSVG, { ICONS } from '../shared/AnimatedSVG/AnimatedSVG'; import { fetchListActive as fetchMetadata } from 'Duck/customField'; import { useStore } from 'App/mstore'; import { useObserver } from 'mobx-react-lite'; +import UserMenu from './UserMenu'; +import SettingsMenu from './SettingsMenu'; +import DefaultMenuView from './DefaultMenuView'; +import PreferencesView from './PreferencesView'; -const DASHBOARD_PATH = dashboard(); -const METRICS_PATH = metrics(); -const SESSIONS_PATH = sessions(); -const ASSIST_PATH = assist(); const CLIENT_PATH = client(CLIENT_DEFAULT_TAB); const Header = (props) => { - const { - sites, location, account, - onLogoutClick, siteId, - boardingCompletion = 100, showAlerts = false, - } = props; - - const name = account.get('name').split(" ")[0]; - const [hideDiscover, setHideDiscover] = useState(false) + const { sites, account, siteId, boardingCompletion = 100, showAlerts = false } = props; + + const name = account.get('name'); + const [hideDiscover, setHideDiscover] = useState(false); const { userStore, notificationStore } = useStore(); const initialDataFetched = useObserver(() => userStore.initialDataFetched); let activeSite = null; - - const onAccountClick = () => { - props.history.push(CLIENT_PATH); - } + const isPreferences = window.location.pathname.includes('/client/'); useEffect(() => { if (!account.id || initialDataFetched) return; - + setTimeout(() => { Promise.all([ userStore.fetchLimits(), @@ -65,90 +48,64 @@ const Header = (props) => { }, [account]); useEffect(() => { - activeSite = sites.find(s => s.id == siteId); + activeSite = sites.find((s) => s.id == siteId); props.initSite(activeSite); - }, [siteId]) + }, [siteId]); return ( -
- -
-
- -
-
v{window.env.VERSION}
-
-
- -
- - - { 'Sessions' } - - - { 'Assist' } - - { - return location.pathname.includes(DASHBOARD_PATH) || location.pathname.includes(METRICS_PATH); - }} - > - { 'Dashboards' } - -
- -
- - { (boardingCompletion < 100 && !hideDiscover) && ( - +
+ {!isPreferences && } + {isPreferences && } +
+ {boardingCompletion < 100 && !hideDiscover && ( + setHideDiscover(true)} /> -
)} - + -
- - - - -
-
+
+ +
+ + + + + +
+
+
+ +
-
{ name }
- +
+ {getInitials(name)} +
-
    -
  • -
  • -
+
+ + {}
- { } + {showAlerts && }
); }; -export default withRouter(connect( - state => ({ - account: state.getIn([ 'user', 'account' ]), - siteId: state.getIn([ 'site', 'siteId' ]), - sites: state.getIn([ 'site', 'list' ]), - showAlerts: state.getIn([ 'dashboard', 'showAlerts' ]), - boardingCompletion: state.getIn([ 'dashboard', 'boardingCompletion' ]) - }), - { onLogoutClick: logout, initSite, fetchMetadata }, -)(Header)); +export default withRouter( + connect( + (state) => ({ + account: state.getIn(['user', 'account']), + siteId: state.getIn(['site', 'siteId']), + sites: state.getIn(['site', 'list']), + showAlerts: state.getIn(['dashboard', 'showAlerts']), + boardingCompletion: state.getIn(['dashboard', 'boardingCompletion']), + }), + { onLogoutClick: logout, initSite, fetchMetadata } + )(Header) +); diff --git a/frontend/app/components/Header/NewProjectButton/NewProjectButton.tsx b/frontend/app/components/Header/NewProjectButton/NewProjectButton.tsx index 9c438c50f..a0dd30244 100644 --- a/frontend/app/components/Header/NewProjectButton/NewProjectButton.tsx +++ b/frontend/app/components/Header/NewProjectButton/NewProjectButton.tsx @@ -24,13 +24,17 @@ function NewProjectButton(props: Props) { }; return ( -
- - Add New Project -
+
  • + + Add Project +
  • + //
    + // + // Add New Project + //
    ); } diff --git a/frontend/app/components/Header/PreferencesView/PreferencesView.tsx b/frontend/app/components/Header/PreferencesView/PreferencesView.tsx new file mode 100644 index 000000000..a5282aea8 --- /dev/null +++ b/frontend/app/components/Header/PreferencesView/PreferencesView.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { Icon } from 'UI'; +import { withRouter } from 'react-router-dom'; +import ProjectCodeSnippet from 'App/components/Onboarding/components/OnboardingTabs/ProjectCodeSnippet'; + +interface Props { + history: any; +} +function PreferencesView(props: Props) { + const onExit = () => { + props.history.push('/'); + }; + return ( + <> +
    + + Exit Preferences +
    + +
    + + Changes applied at organization level +
    + + ); +} + +export default withRouter(PreferencesView); diff --git a/frontend/app/components/Header/PreferencesView/index.ts b/frontend/app/components/Header/PreferencesView/index.ts new file mode 100644 index 000000000..774801dcc --- /dev/null +++ b/frontend/app/components/Header/PreferencesView/index.ts @@ -0,0 +1 @@ +export { default } from './PreferencesView'; \ No newline at end of file diff --git a/frontend/app/components/Header/SettingsMenu/SettingsMenu.tsx b/frontend/app/components/Header/SettingsMenu/SettingsMenu.tsx new file mode 100644 index 000000000..6e9096109 --- /dev/null +++ b/frontend/app/components/Header/SettingsMenu/SettingsMenu.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import cn from 'classnames'; +import { Icon } from 'UI'; +import { CLIENT_TABS, client as clientRoute } from 'App/routes'; +import { withRouter, RouteComponentProps } from 'react-router'; + +interface Props { + history: any; + className: string; + account: any; +} +function SettingsMenu(props: RouteComponentProps) { + const { history, account, className }: any = props; + const isAdmin = account.admin || account.superAdmin; + const navigateTo = (path: any) => { + switch (path) { + case 'projects': + return history.push(clientRoute(CLIENT_TABS.SITES)); + case 'team': + return history.push(clientRoute(CLIENT_TABS.MANAGE_USERS)); + case 'metadata': + return history.push(clientRoute(CLIENT_TABS.CUSTOM_FIELDS)); + case 'webhooks': + return history.push(clientRoute(CLIENT_TABS.WEBHOOKS)); + case 'integrations': + return history.push(clientRoute(CLIENT_TABS.INTEGRATIONS)); + case 'notifications': + return history.push(clientRoute(CLIENT_TABS.NOTIFICATIONS)); + } + }; + return ( +
    + {isAdmin && ( + <> + navigateTo('projects')} label="Projects" icon="folder2" /> + navigateTo('team')} label="Team" icon="users" /> + + )} + navigateTo('metadata')} label="Metadata" icon="tags" /> + navigateTo('webhooks')} label="Webhooks" icon="link-45deg" /> + navigateTo('integrations')} label="Integrations" icon="puzzle" /> + navigateTo('notifications')} + label="Notifications" + icon="bell-slash" + /> +
    + ); +} + +export default withRouter(SettingsMenu); + +function MenuItem({ onClick, label, icon }: any) { + return ( +
    + + +
    + ); +} diff --git a/frontend/app/components/Header/SettingsMenu/index.ts b/frontend/app/components/Header/SettingsMenu/index.ts new file mode 100644 index 000000000..0133de70a --- /dev/null +++ b/frontend/app/components/Header/SettingsMenu/index.ts @@ -0,0 +1 @@ +export { default } from './SettingsMenu'; \ No newline at end of file diff --git a/frontend/app/components/Header/SiteDropdown.js b/frontend/app/components/Header/SiteDropdown.js index 7a0205be3..d93d0d799 100644 --- a/frontend/app/components/Header/SiteDropdown.js +++ b/frontend/app/components/Header/SiteDropdown.js @@ -3,7 +3,6 @@ import { connect } from 'react-redux'; import { setSiteId } from 'Duck/site'; import { withRouter } from 'react-router-dom'; import { hasSiteId, siteChangeAvaliable } from 'App/routes'; -import { STATUS_COLOR_MAP, GREEN } from 'Types/site'; import { Icon } from 'UI'; import { pushNewSite } from 'Duck/user'; import { init } from 'Duck/site'; @@ -13,89 +12,81 @@ import { clearSearch } from 'Duck/search'; import { clearSearch as clearSearchLive } from 'Duck/liveSearch'; import { fetchListActive as fetchIntegrationVariables } from 'Duck/customField'; import { withStore } from 'App/mstore'; -import AnimatedSVG, { ICONS } from '../shared/AnimatedSVG/AnimatedSVG'; import NewProjectButton from './NewProjectButton'; @withStore @withRouter @connect( - (state) => ({ - sites: state.getIn(['site', 'list']), - siteId: state.getIn(['site', 'siteId']), - account: state.getIn(['user', 'account']), - }), - { - setSiteId, - pushNewSite, - init, - clearSearch, - clearSearchLive, - fetchIntegrationVariables, - } + (state) => ({ + sites: state.getIn(['site', 'list']), + siteId: state.getIn(['site', 'siteId']), + account: state.getIn(['user', 'account']), + }), + { + setSiteId, + pushNewSite, + init, + clearSearch, + clearSearchLive, + fetchIntegrationVariables, + } ) export default class SiteDropdown extends React.PureComponent { - state = { showProductModal: false }; + state = { showProductModal: false }; - closeModal = (e, newSite) => { - this.setState({ showProductModal: false }); - }; + closeModal = (e, newSite) => { + this.setState({ showProductModal: false }); + }; - newSite = () => { - this.props.init({}); - this.setState({ showProductModal: true }); - }; + newSite = () => { + this.props.init({}); + this.setState({ showProductModal: true }); + }; - switchSite = (siteId) => { - const { mstore, location } = this.props; + switchSite = (siteId) => { + const { mstore, location } = this.props; - this.props.setSiteId(siteId); - this.props.fetchIntegrationVariables(); - this.props.clearSearch(location.pathname.includes('/sessions')); - this.props.clearSearchLive(); + this.props.setSiteId(siteId); + this.props.fetchIntegrationVariables(); + this.props.clearSearch(location.pathname.includes('/sessions')); + this.props.clearSearchLive(); - mstore.initClient(); + mstore.initClient(); + }; + + render() { + const { + sites, + siteId, + account, + location: { pathname }, + } = this.props; + const isAdmin = account.admin || account.superAdmin; + const activeSite = sites.find((s) => s.id == siteId); + const disabled = !siteChangeAvaliable(pathname); + const showCurrent = hasSiteId(pathname) || siteChangeAvaliable(pathname); + + return ( +
    +
    +
    + {showCurrent && activeSite ? activeSite.host : 'All Projects'} +
    + +
    +
      + {isAdmin && } +
      + {sites.map((site) => ( +
    • this.switchSite(site.id)}> + + {site.host} +
    • + ))} +
    +
    +
    +
    + ); } - - render() { - const { - sites, - siteId, - account, - location: { pathname }, - } = this.props; - const { showProductModal } = this.state; - const isAdmin = account.admin || account.superAdmin; - const activeSite = sites.find((s) => s.id == siteId); - const disabled = !siteChangeAvaliable(pathname); - const showCurrent = hasSiteId(pathname) || siteChangeAvaliable(pathname); - // const canAddSites = isAdmin && account.limits.projects && account.limits.projects.remaining !== 0; - - return ( -
    - {showCurrent ? ( - activeSite && activeSite.status === GREEN ? ( - - ) : ( - - ) - ) : ( - - )} -
    {showCurrent && activeSite ? activeSite.host : 'All Projects'}
    - -
    -
      - {!showCurrent &&
    • {'Project selection is not applicable.'}
    • } - {sites.map((site) => ( -
    • this.switchSite(site.id)}> -
      - {site.host} -
    • - ))} -
    - -
    -
    - ); - } } diff --git a/frontend/app/components/Header/UserMenu/UserMenu.tsx b/frontend/app/components/Header/UserMenu/UserMenu.tsx new file mode 100644 index 000000000..9c5fac9cb --- /dev/null +++ b/frontend/app/components/Header/UserMenu/UserMenu.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; +import { connect } from 'react-redux'; +import { logout } from 'Duck/user'; +import { client, CLIENT_DEFAULT_TAB } from 'App/routes'; +import { Icon } from 'UI'; +import cn from 'classnames'; +import { getInitials } from 'App/utils'; + +const CLIENT_PATH = client(CLIENT_DEFAULT_TAB); + +interface Props { + history: any; + onLogoutClick: any; + className: string; + account: any; +} +function UserMenu(props: RouteComponentProps) { + const { account, history, className, onLogoutClick }: any = props; + + const onAccountClick = () => { + history.push(CLIENT_PATH); + }; + return ( +
    +
    +
    + {getInitials(account.name)} +
    +
    +
    {account.name}
    +
    + {account.email} +
    +
    + {account.superAdmin ? 'Super Admin' : account.admin ? 'Admin' : 'Member'} +
    +
    +
    +
    +
    + + +
    +
    + + +
    +
    +
    + ); +} + +export default connect( + (state: any) => ({ + account: state.getIn(['user', 'account']), + }), + { onLogoutClick: logout } +)(withRouter(UserMenu)) as React.FunctionComponent>; + +// export default UserMenu; diff --git a/frontend/app/components/Header/UserMenu/index.ts b/frontend/app/components/Header/UserMenu/index.ts new file mode 100644 index 000000000..eeb4a1e9e --- /dev/null +++ b/frontend/app/components/Header/UserMenu/index.ts @@ -0,0 +1 @@ +export { default } from './UserMenu'; \ No newline at end of file diff --git a/frontend/app/components/Header/header.module.css b/frontend/app/components/Header/header.module.css index 9852b7436..0cecabb82 100644 --- a/frontend/app/components/Header/header.module.css +++ b/frontend/app/components/Header/header.module.css @@ -4,13 +4,13 @@ $height: 50px; .header { - position: fixed; - width: 100%; - display: flex; - justify-content: space-between; + /* position: fixed; */ + /* width: 100%; */ + /* display: flex; */ + /* justify-content: space-between; */ border-bottom: solid thin $gray-light; /* padding: 0 15px; */ - background: $white; + /* background: $white; */ z-index: $header; } @@ -45,7 +45,7 @@ $height: 50px; } .right { - margin-left: auto; + /* margin-left: auto; */ position: relative; cursor: default; display: flex; @@ -72,14 +72,13 @@ $height: 50px; .userDetails { display: flex; align-items: center; - justify-content: flex-end; + justify-content: center; position: relative; - padding: 0 5px 0 15px; + padding: 0 14px; transition: all 0.2s; - min-width: 100px; &:hover { - background-color: $gray-lightest; + background-color: $active-blue; transition: all 0.2s; & ul { display: block; @@ -102,7 +101,7 @@ $height: 50px; border-top: 1px solid $gray-light; } } - & a, & button { + /* & a, & button { color: $gray-darkest; display: block; cursor: pointer; @@ -113,7 +112,7 @@ $height: 50px; &:hover { background-color: $gray-lightest; } - } + } */ } .userIcon { @@ -128,8 +127,8 @@ $height: 50px; cursor: pointer; display: flex; align-items: center; - padding: 0 15px; - height: 50px; + padding: 0 6px; + height: 49px; transition: all 0.3s; &:hover { diff --git a/frontend/app/components/Header/siteDropdown.module.css b/frontend/app/components/Header/siteDropdown.module.css index 886c60016..428d9e055 100644 --- a/frontend/app/components/Header/siteDropdown.module.css +++ b/frontend/app/components/Header/siteDropdown.module.css @@ -1,16 +1,22 @@ .wrapper { display: flex; align-items: center; - border-left: solid thin $gray-light !important; - padding: 10px 10px; - min-width: 180px; + /* border-left: solid thin $gray-light !important; */ + padding: 10px; + width: fit-content; justify-content: flex-start; position: relative; user-select: none; - + height: 50px; + /* border: solid thin $gray-light; */ + border-radius: 3px; + margin: 0 10px; + cursor: pointer; + &:hover { - background-color: $gray-lightest; - & .drodownIcon { + background-color: $active-blue; + /* border: solid thin $active-blue-border; */ + & .dropdownIcon { transform: rotate(180deg); transition: all 0.2s; } @@ -19,15 +25,15 @@ } } - & .drodownIcon { + & .dropdownIcon { transition: all 0.4s; margin: 0; - margin-left: auto; + margin-left: 8px; } & [data-can-disable=true] { & > li { - &:first-child { + &:first-child { pointer-events: none; } &:not(:first-child) { @@ -44,33 +50,44 @@ background-color: white; min-width: 200px; z-index: 2; - border: 1px solid $gray-light; + border-radius: 3px; + border: 1px solid rgb(238, 238, 238); } & ul { margin: 0; max-height: 300px; overflow-y: auto; + padding: 10px; &::-webkit-scrollbar { - width: 2px; + width: 2px; } & li { display: flex; - align-items: center; + align-items: flex-start; cursor: pointer; - list-style-type: none; - border-bottom: 1px solid $gray-light; - border-top: none; - padding: 10px 15px; + list-style-type: none; + border: solid thin transparent; + /* border-top: none; */ + padding: 10px; transition: all 0.2s; + line-height: 17px; + border-radius: 3px; + text-transform: capitalize; + &:hover { - background-color: $gray-lightest; + background-color: $active-blue; + border-color: $active-blue-border; transition: all 0.2s; + color: $teal; + svg { + fill: $teal; + } } &:first-child { - border-top: 1px solid $gray-light; + /* border-top: 1px solid $gray-light; */ } } } @@ -80,7 +97,7 @@ border: none !important; display: flex !important; align-items: center; - border-radius: 0 !important; + border-radius: 0 !important; } .currentSite { @@ -88,6 +105,8 @@ text-overflow: ellipsis; overflow: hidden; white-space: nowrap; + font-weight: 500; + text-transform: capitalize; } .statusGreenIcon { @@ -122,4 +141,4 @@ .disabled { pointer-events: none; opacity: 0.5; -} \ No newline at end of file +} diff --git a/frontend/app/components/Session/Layout/Player/TimeTracker.js b/frontend/app/components/Session/Layout/Player/TimeTracker.js index 731f414ac..d9927a921 100644 --- a/frontend/app/components/Session/Layout/Player/TimeTracker.js +++ b/frontend/app/components/Session/Layout/Player/TimeTracker.js @@ -1,22 +1,21 @@ import React from 'react'; import { observer } from 'mobx-react-lite'; -import { connectPlayer } from 'Player'; import cls from './timeTracker.module.css'; function TimeTracker({ player, scale }) { - return ( + return ( <>
    ); } -export default observer(TimeTracker); \ No newline at end of file +export default observer(TimeTracker); diff --git a/frontend/app/components/Session/WebPlayer.js b/frontend/app/components/Session/WebPlayer.js index b5b1f86af..e9e32d057 100644 --- a/frontend/app/components/Session/WebPlayer.js +++ b/frontend/app/components/Session/WebPlayer.js @@ -1,17 +1,19 @@ import React, { useEffect, useState } from 'react'; import { connect } from 'react-redux'; -import { Loader } from 'UI'; +import { Loader, Modal } from 'UI'; import { toggleFullscreen, closeBottomBlock } from 'Duck/components/player'; import { fetchList } from 'Duck/integrations'; -import { PlayerProvider, connectPlayer, init as initPlayer, clean as cleanPlayer, Controls } from 'Player'; +import { PlayerProvider, injectNotes, connectPlayer, init as initPlayer, clean as cleanPlayer, Controls } from 'Player'; import cn from 'classnames'; import RightBlock from './RightBlock'; import withLocationHandlers from 'HOCs/withLocationHandlers'; - +import { useStore } from 'App/mstore' import PlayerBlockHeader from '../Session_/PlayerBlockHeader'; import PlayerBlock from '../Session_/PlayerBlock'; import styles from '../Session_/session.module.css'; import { countDaysFrom } from 'App/date'; +import ReadNote from '../Session_/Player/Controls/components/ReadNote'; +import { fetchList as fetchMembers } from 'Duck/member'; const TABS = { EVENTS: 'User Actions', @@ -62,12 +64,25 @@ function RightMenu({ live, tabs, activeTab, setActiveTab, fullscreen }) { function WebPlayer(props) { const { session, toggleFullscreen, closeBottomBlock, live, fullscreen, jwt, fetchList } = props; - + const { notesStore } = useStore() const [activeTab, setActiveTab] = useState(''); + const [showNoteModal, setShowNote] = useState(false) + const [noteItem, setNoteItem] = useState(null) useEffect(() => { fetchList('issues'); initPlayer(session, jwt); + props.fetchMembers() + + notesStore.fetchSessionNotes(session.sessionId).then(r => { + injectNotes(r) + const note = props.query.get('note'); + if (note) { + Controls.pause() + setNoteItem(notesStore.getNoteById(parseInt(note, 10), r)) + setShowNote(true) + } + }) const jumptTime = props.query.get('jumpto'); if (jumptTime) { @@ -86,11 +101,22 @@ function WebPlayer(props) { [] ); + const onNoteClose = () => {setShowNote(false); Controls.togglePlay()} return ( + + {showNoteModal ? ( + m.id === noteItem?.userId)?.email || ''} + note={noteItem} + onClose={onNoteClose} + notFound={!noteItem} + /> + ) : null} + ); @@ -102,10 +128,12 @@ export default connect( jwt: state.get('jwt'), fullscreen: state.getIn(['components', 'player', 'fullscreen']), showEvents: state.get('showEvents'), + members: state.getIn(['members', 'list']), }), { toggleFullscreen, closeBottomBlock, fetchList, + fetchMembers, } )(withLocationHandlers()(WebPlayer)); diff --git a/frontend/app/components/Session_/Autoscroll.tsx b/frontend/app/components/Session_/Autoscroll.tsx index 051f2024f..ad2e82e01 100644 --- a/frontend/app/components/Session_/Autoscroll.tsx +++ b/frontend/app/components/Session_/Autoscroll.tsx @@ -112,15 +112,15 @@ export default class Autoscroll extends React.PureComponent -
    - {/* */} + {/*
    + {navigation && ( <> )} -
    +
    */}
    ); diff --git a/frontend/app/components/Session_/BottomBlock/Header.js b/frontend/app/components/Session_/BottomBlock/Header.js index 15dd7a0c9..15cdf3365 100644 --- a/frontend/app/components/Session_/BottomBlock/Header.js +++ b/frontend/app/components/Session_/BottomBlock/Header.js @@ -11,9 +11,10 @@ const Header = ({ closeBottomBlock, onFilterChange, showClose = true, + customStyle, ...props }) => ( -
    +
    { children }
    { showClose && } diff --git a/frontend/app/components/Session_/BottomBlock/infoLine.module.css b/frontend/app/components/Session_/BottomBlock/infoLine.module.css index b6798d1bf..37e47f013 100644 --- a/frontend/app/components/Session_/BottomBlock/infoLine.module.css +++ b/frontend/app/components/Session_/BottomBlock/infoLine.module.css @@ -6,7 +6,7 @@ display: flex; align-items: center; & >.infoPoint { - font-size: 12px; + font-size: 14px; display: flex; align-items: center; &:not(:last-child):after { diff --git a/frontend/app/components/Session_/Console/ConsoleContent.js b/frontend/app/components/Session_/Console/ConsoleContent.js index 54a9745d0..a7482b69e 100644 --- a/frontend/app/components/Session_/Console/ConsoleContent.js +++ b/frontend/app/components/Session_/Console/ConsoleContent.js @@ -7,7 +7,8 @@ import { LEVEL } from 'Types/session/log'; import Autoscroll from '../Autoscroll'; import BottomBlock from '../BottomBlock'; import stl from './console.module.css'; -import { Duration } from 'luxon'; +import ConsoleRow from './ConsoleRow'; +// import { Duration } from 'luxon'; const ALL = 'ALL'; const INFO = 'INFO'; @@ -83,44 +84,34 @@ export default class ConsoleContent extends React.PureComponent {
    - + No Data -
    - } size="small" show={filtered.length === 0}> +
    + } + size="small" + show={filtered.length === 0} + > - {filtered.map((l, index) => ( -
    lastIndex, - 'cursor-pointer': !isResult, - })} - onClick={() => !isResult && jump(l.time)} - > -
    - -
    -
    - {Duration.fromMillis(l.time).toFormat('mm:ss.SSS')} -
    -
    -
    {renderWithNL(l.value)}
    -
    -
    + {filtered.map((l) => ( + ))}
    diff --git a/frontend/app/components/Session_/Console/ConsoleRow/ConsoleRow.tsx b/frontend/app/components/Session_/Console/ConsoleRow/ConsoleRow.tsx new file mode 100644 index 000000000..c87ff3f9c --- /dev/null +++ b/frontend/app/components/Session_/Console/ConsoleRow/ConsoleRow.tsx @@ -0,0 +1,48 @@ +import React, { useState } from 'react'; +import cn from 'classnames'; +import stl from '../console.module.css'; +import { Icon } from 'UI'; +import JumpButton from 'Shared/DevTools/JumpButton'; + +interface Props { + log: any; + iconProps: any; + jump?: any; + renderWithNL?: any; +} +function ConsoleRow(props: Props) { + const { log, iconProps, jump, renderWithNL } = props; + const [expanded, setExpanded] = useState(false); + const lines = log.value.split('\n').filter((l: any) => !!l); + const canExpand = lines.length > 1; + return ( +
    setExpanded(!expanded)} + > +
    + +
    + {/*
    + {Duration.fromMillis(log.time).toFormat('mm:ss.SSS')} +
    */} +
    +
    + {canExpand && ( + + )} + {renderWithNL(lines.pop())} +
    + {canExpand && expanded && lines.map((l: any) =>
    {l}
    )} +
    + jump(log.time)} /> +
    + ); +} + +export default ConsoleRow; diff --git a/frontend/app/components/Session_/Console/ConsoleRow/index.ts b/frontend/app/components/Session_/Console/ConsoleRow/index.ts new file mode 100644 index 000000000..c9140d748 --- /dev/null +++ b/frontend/app/components/Session_/Console/ConsoleRow/index.ts @@ -0,0 +1 @@ +export { default } from './ConsoleRow'; diff --git a/frontend/app/components/Session_/Console/console.module.css b/frontend/app/components/Session_/Console/console.module.css index 6f7079d94..2da78f540 100644 --- a/frontend/app/components/Session_/Console/console.module.css +++ b/frontend/app/components/Session_/Console/console.module.css @@ -15,6 +15,9 @@ display: flex; align-items: flex-start; border-bottom: solid thin $gray-light-shade; + &:hover { + background-coor: $active-blue !important; + } } .timestamp { diff --git a/frontend/app/components/Session_/EventsBlock/EventGroupWrapper.js b/frontend/app/components/Session_/EventsBlock/EventGroupWrapper.js index c68dac880..1ec053462 100644 --- a/frontend/app/components/Session_/EventsBlock/EventGroupWrapper.js +++ b/frontend/app/components/Session_/EventsBlock/EventGroupWrapper.js @@ -1,34 +1,47 @@ import React from 'react'; import cn from 'classnames'; - +import { connect } from 'react-redux' import { TextEllipsis } from 'UI'; import withToggle from 'HOCs/withToggle'; import { TYPES } from 'Types/session/event'; import Event from './Event' import stl from './eventGroupWrapper.module.css'; +import NoteEvent from './NoteEvent'; +import { setEditNoteTooltip } from 'Duck/sessions';; // TODO: incapsulate toggler in LocationEvent -@withToggle("showLoadInfo", "toggleLoadInfo") -class EventGroupWrapper extends React.PureComponent { - +@withToggle('showLoadInfo', 'toggleLoadInfo') +@connect( + (state) => ({ + members: state.getIn(['members', 'list']), + currentUserId: state.getIn(['user', 'account', 'id']), + }), + { setEditNoteTooltip } +) +class EventGroupWrapper extends React.Component { toggleLoadInfo = (e) => { e.stopPropagation(); this.props.toggleLoadInfo(); - } + }; componentDidUpdate(prevProps) { - if (prevProps.showLoadInfo !== this.props.showLoadInfo || prevProps.query !== this.props.query) { + if ( + prevProps.showLoadInfo !== this.props.showLoadInfo || + prevProps.query !== this.props.query || + prevProps.event.timestamp !== this.props.event.timestamp || + prevProps.isNote !== this.props.isNote + ) { this.props.mesureHeight(); } } componentDidMount() { - this.props.toggleLoadInfo(this.props.isFirst) + this.props.toggleLoadInfo(this.props.isFirst); this.props.mesureHeight(); } onEventClick = (e) => this.props.onEventClick(e, this.props.event); - onCheckboxClick = e => this.props.onCheckboxClick(e, this.props.event); + onCheckboxClick = (e) => this.props.onCheckboxClick(e, this.props.event); render() { const { @@ -42,58 +55,76 @@ class EventGroupWrapper extends React.PureComponent { showLoadInfo, isFirst, presentInSearch, + isNote, + filterOutNote, } = this.props; const isLocation = event.type === TYPES.LOCATION; - const whiteBg = isLastInGroup && event.type !== TYPES.LOCATION || (!isLastEvent && event.type !== TYPES.LOCATION) + const whiteBg = + (isLastInGroup && event.type !== TYPES.LOCATION) || + (!isLastEvent && event.type !== TYPES.LOCATION); const safeRef = String(event.referrer || ''); + return (
    - { isFirst && isLocation && event.referrer && -
    + {isFirst && isLocation && event.referrer && ( +
    Referrer: {safeRef}
    - } - { isLocation - ? - : - } + )} + {isNote ? ( + m.id === event.userId)?.email || event.userId} + note={event} + filterOutNote={filterOutNote} + onEdit={this.props.setEditNoteTooltip} + noEdit={this.props.currentUserId !== event.userId} + /> + ) : isLocation ? ( + + ) : ( + + )}
    - ) + ); } } diff --git a/frontend/app/components/Session_/EventsBlock/EventsBlock.js b/frontend/app/components/Session_/EventsBlock/EventsBlock.js index e690ce3cc..a1fd23b20 100644 --- a/frontend/app/components/Session_/EventsBlock/EventsBlock.js +++ b/frontend/app/components/Session_/EventsBlock/EventsBlock.js @@ -5,7 +5,7 @@ import { Icon } from 'UI'; import { List, AutoSizer, CellMeasurer, CellMeasurerCache } from "react-virtualized"; import { TYPES } from 'Types/session/event'; import { setSelected } from 'Duck/events'; -import { setEventFilter } from 'Duck/sessions'; +import { setEventFilter, filterOutNote } from 'Duck/sessions'; import { show as showTargetDefiner } from 'Duck/components/targetDefiner'; import EventGroupWrapper from './EventGroupWrapper'; import styles from './eventsBlock.module.css'; @@ -21,9 +21,10 @@ import EventSearch from './EventSearch/EventSearch'; }), { showTargetDefiner, setSelected, - setEventFilter + setEventFilter, + filterOutNote }) -export default class EventsBlock extends React.PureComponent { +export default class EventsBlock extends React.Component { state = { editingEvent: null, mouseOver: false, @@ -52,8 +53,9 @@ export default class EventsBlock extends React.PureComponent { const { filter } = this.state; this.setState({ query: '' }) this.props.setEventFilter({ query: '', filter }) - - this.scroller.current.forceUpdateGrid(); + if (this.scroller.current) { + this.scroller.current.forceUpdateGrid(); + } setTimeout(() => { if (!this.scroller.current) return; @@ -123,21 +125,28 @@ export default class EventsBlock extends React.PureComponent { onMouseOver = () => this.setState({ mouseOver: true }) onMouseLeave = () => this.setState({ mouseOver: false }) + get eventsList() { + const { session: { notesWithEvents }, filteredEvents } = this.props + const usedEvents = filteredEvents || notesWithEvents + + return usedEvents + } + renderGroup = ({ index, key, style, parent }) => { const { - session: { events }, selectedEvents, currentTimeEventIndex, testsAvaliable, playing, eventsIndex, - filteredEvents + filterOutNote, } = this.props; const { query } = this.state; - const _events = filteredEvents || events; + const _events = this.eventsList const isLastEvent = index === _events.size - 1; const isLastInGroup = isLastEvent || _events.get(index + 1).type === TYPES.LOCATION; const event = _events.get(index); + const isNote = !!event.noteId const isSelected = selectedEvents.includes(event); const isCurrent = index === currentTimeEventIndex; const isEditing = this.state.editingEvent === event; @@ -166,9 +175,11 @@ export default class EventsBlock extends React.PureComponent { isCurrent={ isCurrent } isEditing={ isEditing } showSelection={ testsAvaliable && !playing } + isNote={isNote} + filterOutNote={filterOutNote} /> -
    - )} +
    + )} ); } @@ -176,15 +187,13 @@ export default class EventsBlock extends React.PureComponent { render() { const { query } = this.state; const { - testsAvaliable, session: { events, }, - filteredEvents, setActiveTab, } = this.props; - const _events = filteredEvents || events; + const _events = this.eventsList const isEmptySearch = query && (_events.size === 0 || !_events) return ( diff --git a/frontend/app/components/Session_/EventsBlock/NoteEvent.tsx b/frontend/app/components/Session_/EventsBlock/NoteEvent.tsx new file mode 100644 index 000000000..622d13831 --- /dev/null +++ b/frontend/app/components/Session_/EventsBlock/NoteEvent.tsx @@ -0,0 +1,133 @@ +import React from 'react'; +import { Icon } from 'UI'; +import { tagProps, iTag, Note } from 'App/services/NotesService'; +import { formatTimeOrDate } from 'App/date'; +import { useStore } from 'App/mstore'; +import { observer } from 'mobx-react-lite'; +import { ItemMenu } from 'UI'; +import copy from 'copy-to-clipboard'; +import { toast } from 'react-toastify'; +import { session } from 'App/routes'; +import { confirm } from 'UI'; +import { filterOutNote as filterOutTimelineNote } from 'Player'; +import { TeamBadge } from 'Shared/SessionListContainer/components/Notes'; + +interface Props { + note: Note; + noEdit: boolean; + userEmail: string; + filterOutNote: (id: number) => void; + onEdit: (noteTooltipObj: Record) => void; +} + +function NoteEvent(props: Props) { + const { settingsStore, notesStore } = useStore(); + const { timezone } = settingsStore.sessionSettings; + + console.log(props.noEdit); + const onEdit = () => { + props.onEdit({ + isVisible: true, + isEdit: true, + time: props.note.timestamp, + note: { + timestamp: props.note.timestamp, + tag: props.note.tag, + isPublic: props.note.isPublic, + message: props.note.message, + sessionId: props.note.sessionId, + noteId: props.note.noteId, + }, + }); + }; + + const onCopy = () => { + copy( + `${window.location.origin}/${window.location.pathname.split('/')[1]}${session( + props.note.sessionId + )}${props.note.timestamp > 0 ? '?jumpto=' + props.note.timestamp : ''}` + ); + toast.success('Note URL copied to clipboard'); + }; + + const onDelete = async () => { + if ( + await confirm({ + header: 'Confirm', + confirmButton: 'Yes, delete', + confirmation: `Are you sure you want to delete this note?`, + }) + ) { + notesStore.deleteNote(props.note.noteId).then((r) => { + props.filterOutNote(props.note.noteId); + filterOutTimelineNote(props.note.noteId); + toast.success('Note deleted'); + }); + } + }; + const menuItems = [ + { icon: 'pencil', text: 'Edit', onClick: onEdit, disabled: props.noEdit }, + { icon: 'link-45deg', text: 'Copy URL', onClick: onCopy }, + { icon: 'trash', text: 'Delete', onClick: onDelete }, + ]; + return ( +
    +
    +
    + +
    +
    +
    + {props.userEmail}, {props.userEmail} +
    +
    + {formatTimeOrDate(props.note.createdAt as unknown as number, timezone)} +
    +
    +
    + +
    +
    +
    + {props.note.message} +
    +
    +
    + {props.note.tag ? ( +
    + {props.note.tag} +
    + ) : null} + {!props.note.isPublic ? null : } +
    +
    +
    + ); +} + +export default observer(NoteEvent); diff --git a/frontend/app/components/Session_/Exceptions/Exceptions.js b/frontend/app/components/Session_/Exceptions/Exceptions.js index b6c65b5ba..334e57688 100644 --- a/frontend/app/components/Session_/Exceptions/Exceptions.js +++ b/frontend/app/components/Session_/Exceptions/Exceptions.js @@ -22,7 +22,7 @@ import BottomBlock from '../BottomBlock'; @connectPlayer((state) => ({ logs: state.logListNow, exceptions: state.exceptionsList, - exceptionsNow: state.exceptionsListNow, + // exceptionsNow: state.exceptionsListNow, })) @connect( (state) => ({ @@ -55,15 +55,15 @@ export default class Exceptions extends React.PureComponent { const filtered = exceptions.filter((e) => filterRE.test(e.name) || filterRE.test(e.message)); - let lastIndex = -1; - filtered.forEach((item, index) => { - if ( - this.props.exceptionsNow.length > 0 && - item.time <= this.props.exceptionsNow[this.props.exceptionsNow.length - 1].time - ) { - lastIndex = index; - } - }); + // let lastIndex = -1; + // filtered.forEach((item, index) => { + // if ( + // this.props.exceptionsNow.length > 0 && + // item.time <= this.props.exceptionsNow[this.props.exceptionsNow.length - 1].time + // ) { + // lastIndex = index; + // } + // }); return ( <> @@ -113,6 +113,7 @@ export default class Exceptions extends React.PureComponent { iconPosition="left" name="filter" onChange={this.onFilterChange} + height={28} /> - + {filtered.map((e, index) => ( jump(e.time)} error={e} key={e.key} - selected={lastIndex === index} + // selected={lastIndex === index} // inactive={index > lastIndex} onErrorClick={(jsEvent) => { jsEvent.stopPropagation(); diff --git a/frontend/app/components/Session_/Fetch/Fetch.js b/frontend/app/components/Session_/Fetch/Fetch.js index fc7d7e76b..8aaadba6d 100644 --- a/frontend/app/components/Session_/Fetch/Fetch.js +++ b/frontend/app/components/Session_/Fetch/Fetch.js @@ -33,7 +33,8 @@ export default class Fetch extends React.PureComponent { hasPreviousError: false, }; - onFilterChange = (e, { value }) => { + onFilterChange = (e) => { + const value = e.target.value; const { list } = this.props; const filterRE = getRE(value, 'i'); const filtered = list.filter( diff --git a/frontend/app/components/Session_/Issues/Issues.js b/frontend/app/components/Session_/Issues/Issues.js index 200b5b278..30be2e5c6 100644 --- a/frontend/app/components/Session_/Issues/Issues.js +++ b/frontend/app/components/Session_/Issues/Issues.js @@ -5,24 +5,27 @@ import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv'; import IssuesModal from './IssuesModal'; import { fetchProjects, fetchMeta } from 'Duck/assignments'; import stl from './issues.module.css'; -import { Tooltip } from 'react-tippy' +import { Tooltip } from 'react-tippy'; -@connect(state => ({ - issues: state.getIn(['assignments', 'list']), - metaLoading: state.getIn(['assignments', 'fetchMeta', 'loading']), - projects: state.getIn(['assignments', 'projects']), - projectsFetched: state.getIn(['assignments', 'projectsFetched']), - activeIssue: state.getIn(['assignments', 'activeIssue']), - fetchIssueLoading: state.getIn(['assignments', 'fetchAssignment', 'loading']), - fetchIssuesLoading: state.getIn(['assignments', 'fetchAssignments', 'loading']), - projectsLoading: state.getIn(['assignments', 'fetchProjects', 'loading']), - issuesIntegration: state.getIn([ 'issues', 'list']).first() || {}, +@connect( + (state) => ({ + issues: state.getIn(['assignments', 'list']), + metaLoading: state.getIn(['assignments', 'fetchMeta', 'loading']), + projects: state.getIn(['assignments', 'projects']), + projectsFetched: state.getIn(['assignments', 'projectsFetched']), + activeIssue: state.getIn(['assignments', 'activeIssue']), + fetchIssueLoading: state.getIn(['assignments', 'fetchAssignment', 'loading']), + fetchIssuesLoading: state.getIn(['assignments', 'fetchAssignments', 'loading']), + projectsLoading: state.getIn(['assignments', 'fetchProjects', 'loading']), + issuesIntegration: state.getIn(['issues', 'list']).first() || {}, - jiraConfig: state.getIn([ 'issues', 'list' ]).first(), - issuesFetched: state.getIn([ 'issues', 'issuesFetched' ]), -}), { fetchMeta, fetchProjects }) + jiraConfig: state.getIn(['issues', 'list']).first(), + issuesFetched: state.getIn(['issues', 'issuesFetched']), + }), + { fetchMeta, fetchProjects } +) class Issues extends React.Component { - state = {showModal: false }; + state = { showModal: false }; constructor(props) { super(props); @@ -31,64 +34,79 @@ class Issues extends React.Component { closeModal = () => { this.setState({ showModal: false }); - } + }; showIssuesList = (e) => { e.preventDefault(); e.stopPropagation(); this.setState({ showModal: true }); - } + }; handleOpen = () => { this.setState({ showModal: true }); - if (!this.props.projectsFetched) { // cache projects fetch - this.props.fetchProjects().then(function() { - const { projects } = this.props; - if (projects && projects.first()) { - this.props.fetchMeta(projects.first().id) - } - }.bind(this)) + if (!this.props.projectsFetched) { + // cache projects fetch + this.props.fetchProjects().then( + function () { + const { projects } = this.props; + if (projects && projects.first()) { + this.props.fetchMeta(projects.first().id); + } + }.bind(this) + ); } - } + }; render() { const { - sessionId, isModalDisplayed, projectsLoading, metaLoading, fetchIssuesLoading, issuesIntegration + sessionId, + isModalDisplayed, + projectsLoading, + metaLoading, + fetchIssuesLoading, + issuesIntegration, } = this.props; - const provider = issuesIntegration.provider + const provider = issuesIntegration.provider; return ( -
    -
    +
    +
    + +
    - +
    } > -
    - - Create Issue -
    +
    + + Create Issue +
    -
    +
    +
    ); } -}; +} export default Issues; diff --git a/frontend/app/components/Session_/Network/NetworkContent.js b/frontend/app/components/Session_/Network/NetworkContent.js index 082c87aa0..e0f91587c 100644 --- a/frontend/app/components/Session_/Network/NetworkContent.js +++ b/frontend/app/components/Session_/Network/NetworkContent.js @@ -1,7 +1,7 @@ import React from 'react'; import cn from 'classnames'; // import { connectPlayer } from 'Player'; -import { QuestionMarkHint, Popup, Tabs, Input, NoContent, Icon, Button } from 'UI'; +import { QuestionMarkHint, Popup, Tabs, Input, NoContent, Icon, Toggler, Button } from 'UI'; import { getRE } from 'App/utils'; import { TYPES } from 'Types/session/resource'; import { formatBytes } from 'App/utils'; @@ -48,22 +48,17 @@ export function renderType(r) { export function renderName(r) { return ( - {r.url}
    } - > -
    {r.name}
    - + {r.url}
    }> +
    {r.name}
    + ); } export function renderStart(r) { return (
    - - {Duration.fromMillis(r.time).toFormat('mm:ss.SSS')} - - -
    - ) + */} +
    + ); } const renderXHRText = () => ( @@ -243,39 +238,45 @@ export default class NetworkContent extends React.PureComponent { iconPosition="left" name="filter" onChange={this.onFilterChange} + height={28} /> - - - 0} - /> - 0} - /> - - - - +
    +
    + {}} label="4xx-5xx Only" /> +
    + + + 0} + /> + 0} + /> + + + + +
    @@ -296,11 +297,11 @@ export default class NetworkContent extends React.PureComponent { activeIndex={lastIndex} > {[ - { - label: 'Start', - width: 120, - render: renderStart, - }, + // { + // label: 'Start', + // width: 120, + // render: renderStart, + // }, { label: 'Status', dataKey: 'status', diff --git a/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx b/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx index 2f1f75f2c..bd48c7a23 100644 --- a/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx +++ b/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx @@ -86,7 +86,7 @@ function OverviewPanel(props: Props) { -
    +
    ( - - - + + + ); - -const TOTAL_HEAP = "Allocated Heap"; -const USED_HEAP = "JS Heap"; -const FPS = "Framerate"; -const CPU = "CPU Load"; -const NODES_COUNT = "Nodes Сount"; - +const TOTAL_HEAP = 'Allocated Heap'; +const USED_HEAP = 'JS Heap'; +const FPS = 'Framerate'; +const CPU = 'CPU Load'; +const NODES_COUNT = 'Nodes Сount'; const FPSTooltip = ({ active, payload }) => { if (!active || !payload || payload.length < 3) { @@ -64,8 +59,8 @@ const FPSTooltip = ({ active, payload }) => { } if (payload[0].value === null) { return ( -
    - {"Page is not active. User switched the tab or hid the window."} +
    + {'Page is not active. User switched the tab or hid the window.'}
    ); } @@ -79,9 +74,9 @@ const FPSTooltip = ({ active, payload }) => { } return ( -
    - {`${ FPS }: `} - { Math.trunc(payload[0].value) } +
    + {`${FPS}: `} + {Math.trunc(payload[0].value)}
    ); }; @@ -91,47 +86,47 @@ const CPUTooltip = ({ active, payload }) => { return null; } return ( -
    - {`${ CPU }: `} - { payload[0].value - CPU_VISUAL_OFFSET } - {"%"} +
    + {`${CPU}: `} + {payload[0].value - CPU_VISUAL_OFFSET} + {'%'}
    ); }; -const HeapTooltip = ({ active, payload}) => { +const HeapTooltip = ({ active, payload }) => { if (!active || payload.length < 2) return null; return ( -
    +

    - {`${ TOTAL_HEAP }: `} - { formatBytes(payload[0].value)} + {`${TOTAL_HEAP}: `} + {formatBytes(payload[0].value)}

    - {`${ USED_HEAP }: `} - { formatBytes(payload[1].value)} + {`${USED_HEAP}: `} + {formatBytes(payload[1].value)}

    ); -} +}; -const NodesCountTooltip = ({ active, payload} ) => { +const NodesCountTooltip = ({ active, payload }) => { if (!active || !payload || payload.length === 0) return null; return ( -
    +

    - {`${ NODES_COUNT }: `} - { payload[0].value } + {`${NODES_COUNT}: `} + {payload[0].value}

    ); -} +}; const TICKS_COUNT = 10; function generateTicks(data: Array): Array { if (data.length === 0) return []; const minTime = data[0].time; - const maxTime = data[data.length-1].time; + const maxTime = data[data.length - 1].time; const ticks = []; const tickGap = (maxTime - minTime) / (TICKS_COUNT + 1); @@ -159,8 +154,9 @@ function addFpsMetadata(data) { } else if (point.fps < LOW_FPS) { fpsLowMarker = LOW_FPS_MARKER_VALUE; } - } - if (point.fps == null || + } + if ( + point.fps == null || (i > 0 && data[i - 1].fps == null) //|| //(i < data.length-1 && data[i + 1].fps == null) ) { @@ -174,17 +170,17 @@ function addFpsMetadata(data) { fpsLowMarker, fpsVeryLowMarker, hiddenScreenMarker, - } + }; }); } -@connect(state => ({ - userDeviceHeapSize: state.getIn([ "sessions", "current", "userDeviceHeapSize" ]), - userDeviceMemorySize: state.getIn([ "sessions", "current", "userDeviceMemorySize" ]), +@connect((state) => ({ + userDeviceHeapSize: state.getIn(['sessions', 'current', 'userDeviceHeapSize']), + userDeviceMemorySize: state.getIn(['sessions', 'current', 'userDeviceMemorySize']), })) export default class Performance extends React.PureComponent { - _timeTicks = generateTicks(this.props.performanceChartData) - _data = addFpsMetadata(this.props.performanceChartData) + _timeTicks = generateTicks(this.props.performanceChartData); + _data = addFpsMetadata(this.props.performanceChartData); // state = { // totalHeap: false, // usedHeap: true, @@ -197,7 +193,7 @@ export default class Performance extends React.PureComponent { if (!!point) { PlayerControls.jump(point.time); } - } + }; onChartClick = (e) => { if (e === null) return; @@ -206,10 +202,10 @@ export default class Performance extends React.PureComponent { if (!!point) { PlayerControls.jump(point.time); } - } + }; render() { - const { + const { userDeviceHeapSize, userDeviceMemorySize, connType, @@ -218,19 +214,19 @@ export default class Performance extends React.PureComponent { avaliability = {}, } = this.props; const { fps, cpu, heap, nodes } = avaliability; - const avaliableCount = [ fps, cpu, heap, nodes ].reduce((c, av) => av ? c + 1 : c, 0); - const height = avaliableCount === 0 ? "0" : `${100 / avaliableCount}%`; + const avaliableCount = [fps, cpu, heap, nodes].reduce((c, av) => (av ? c + 1 : c), 0); + const height = avaliableCount === 0 ? '0' : `${100 / avaliableCount}%`; return ( -
    - Performance +
    +
    Performance
    {/* */} = 1000 - ? `${ connBandwidth / 1000 } Mbps` - : `${ connBandwidth } Kbps` + value={ + connBandwidth >= 1000 ? `${connBandwidth / 1000} Mbps` : `${connBandwidth} Kbps` } - display={ connBandwidth != null } + display={connBandwidth != null} />
    - { fps && - + {fps && ( + - + {/* */} {/* */} - - - + {/* */} - + style: { cursor: 'pointer' }, + }} + isAnimationActive={false} + /> - {/* */} - + - } - { cpu && - + )} + {cpu && ( + - + {/* */} - ""} - domain={[0, 'dataMax']} - ticks={this._timeTicks} - > - - - - - - - - - } - - { heap && - - - - - - {/* */} - ""} // tick={false} + this._timeTicks to cartesian array + tickFormatter={() => ''} domain={[0, 'dataMax']} ticks={this._timeTicks} > - + + + + + + + + )} + + {heap && ( + + + + + + {/* */} + ''} // tick={false} + this._timeTicks to cartesian array + domain={[0, 'dataMax']} + ticks={this._timeTicks} + > + max*1.2]} + domain={[0, (max) => max * 1.2]} /> - - + - } - { nodes && - + )} + {nodes && ( + - + {/* */} - ""} - domain={[0, 'dataMax']} - ticks={this._timeTicks} - > - - } + )} ); } } -export const ConnectedPerformance = connectPlayer(state => ({ +export const ConnectedPerformance = connectPlayer((state) => ({ performanceChartTime: state.performanceChartTime, performanceChartData: state.performanceChartData, connType: state.connType, diff --git a/frontend/app/components/Session_/Performance/performance.module.css b/frontend/app/components/Session_/Performance/performance.module.css index 4bd075eec..5c2b85578 100644 --- a/frontend/app/components/Session_/Performance/performance.module.css +++ b/frontend/app/components/Session_/Performance/performance.module.css @@ -3,4 +3,5 @@ padding: 2px 5px; border-radius: 3px; border: 1px solid #ccc; + color: $gray-dark !important; } \ No newline at end of file diff --git a/frontend/app/components/Session_/Player/Controls/ControlButton.js b/frontend/app/components/Session_/Player/Controls/ControlButton.js index 31672c301..3c42895b6 100644 --- a/frontend/app/components/Session_/Player/Controls/ControlButton.js +++ b/frontend/app/components/Session_/Player/Controls/ControlButton.js @@ -8,7 +8,7 @@ const ControlButton = ({ icon = '', disabled = false, onClick, - count = 0, + // count = 0, hasErrors = false, active = false, size = 20, @@ -31,7 +31,7 @@ const ControlButton = ({ >
    {hasErrors &&
    } - {count > 0 &&
    {count}
    } + {/* {count > 0 &&
    {count}
    } */}
    {!noIcon && } {!noLabel && ( diff --git a/frontend/app/components/Session_/Player/Controls/Controls.js b/frontend/app/components/Session_/Player/Controls/Controls.js index 5109bf96b..0a9b57fb3 100644 --- a/frontend/app/components/Session_/Player/Controls/Controls.js +++ b/frontend/app/components/Session_/Player/Controls/Controls.js @@ -8,7 +8,7 @@ import { selectStorageListNow, } from 'Player/store'; import LiveTag from 'Shared/LiveTag'; -import { toggleTimetravel, jumpToLive } from 'Player'; +import { jumpToLive } from 'Player'; import { Icon } from 'UI'; import { toggleInspectorMode } from 'Player'; @@ -25,8 +25,6 @@ import { PROFILER, PERFORMANCE, GRAPHQL, - FETCH, - EXCEPTIONS, INSPECTOR, } from 'Duck/components/player'; import { AssistDuration } from './Time'; @@ -38,23 +36,6 @@ import styles from './controls.module.css'; import { Tooltip } from 'react-tippy'; import XRayButton from 'Shared/XRayButton'; -function getStorageIconName(type) { - switch (type) { - case STORAGE_TYPES.REDUX: - return 'vendors/redux'; - case STORAGE_TYPES.MOBX: - return 'vendors/mobx'; - case STORAGE_TYPES.VUEX: - return 'vendors/vuex'; - case STORAGE_TYPES.NGRX: - return 'vendors/ngrx'; - case STORAGE_TYPES.ZUSTAND: - return 'vendors/zustand'; - case STORAGE_TYPES.NONE: - return 'store'; - } -} - const SKIP_INTERVALS = { 2: 2e3, 5: 5e3, @@ -95,25 +76,22 @@ function getStorageName(type) { disabled: state.cssLoading || state.messagesLoading || state.inspectorMode || state.markedTargets, inspectorMode: state.inspectorMode, fullscreenDisabled: state.messagesLoading, - logCount: state.logListNow.length, - logRedCount: state.logRedCountNow, - resourceRedCount: state.resourceRedCountNow, - fetchRedCount: state.fetchRedCountNow, + // logCount: state.logList.length, + logRedCount: state.logRedCount, + resourceRedCount: state.resourceRedCount, + fetchRedCount: state.fetchRedCount, showStack: state.stackList.length > 0, - stackCount: state.stackListNow.length, - stackRedCount: state.stackRedCountNow, - profilesCount: state.profilesListNow.length, + stackCount: state.stackList.length, + stackRedCount: state.stackRedCount, + profilesCount: state.profilesList.length, storageCount: selectStorageListNow(state).length, storageType: selectStorageType(state), showStorage: selectStorageType(state) !== STORAGE_TYPES.NONE, showProfiler: state.profilesList.length > 0, showGraphql: state.graphqlList.length > 0, showFetch: state.fetchCount > 0, - fetchCount: state.fetchCountNow, - graphqlCount: state.graphqlListNow.length, - exceptionsCount: state.exceptionsListNow.length, - showExceptions: state.exceptionsList.length > 0, - showLongtasks: state.longtasksList.length > 0, + fetchCount: state.fetchCount, + graphqlCount: state.graphqlList.length, liveTimeTravel: state.liveTimeTravel, })) @connect( @@ -163,7 +141,7 @@ export default class Controls extends React.Component { nextProps.disabled !== this.props.disabled || nextProps.fullscreenDisabled !== this.props.fullscreenDisabled || // nextProps.inspectorMode !== this.props.inspectorMode || - nextProps.logCount !== this.props.logCount || + // nextProps.logCount !== this.props.logCount || nextProps.logRedCount !== this.props.logRedCount || nextProps.resourceRedCount !== this.props.resourceRedCount || nextProps.fetchRedCount !== this.props.fetchRedCount || @@ -179,9 +157,6 @@ export default class Controls extends React.Component { nextProps.showFetch !== this.props.showFetch || nextProps.fetchCount !== this.props.fetchCount || nextProps.graphqlCount !== this.props.graphqlCount || - nextProps.showExceptions !== this.props.showExceptions || - nextProps.exceptionsCount !== this.props.exceptionsCount || - nextProps.showLongtasks !== this.props.showLongtasks || nextProps.liveTimeTravel !== this.props.liveTimeTravel || nextProps.skipInterval !== this.props.skipInterval ) @@ -286,24 +261,14 @@ export default class Controls extends React.Component { skip, speed, disabled, - logCount, logRedCount, resourceRedCount, - fetchRedCount, showStack, - stackCount, stackRedCount, - profilesCount, - storageCount, showStorage, storageType, showProfiler, showGraphql, - showFetch, - fetchCount, - graphqlCount, - exceptionsCount, - showExceptions, fullscreen, inspectorMode, closedLive, @@ -380,7 +345,6 @@ export default class Controls extends React.Component { label="CONSOLE" noIcon labelClassName="!text-base font-semibold" - count={logCount} hasErrors={logRedCount > 0} containerClassName="mx-2" /> @@ -407,25 +371,11 @@ export default class Controls extends React.Component { containerClassName="mx-2" /> )} - {showFetch && ( - toggleBottomTools(FETCH)} - active={bottomBlock === FETCH && !inspectorMode} - hasErrors={fetchRedCount > 0} - count={fetchCount} - label="FETCH" - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - /> - )} {!live && showGraphql && ( toggleBottomTools(GRAPHQL)} active={bottomBlock === GRAPHQL && !inspectorMode} - count={graphqlCount} label="GRAPHQL" noIcon labelClassName="!text-base font-semibold" @@ -437,26 +387,12 @@ export default class Controls extends React.Component { disabled={disabled && !inspectorMode} onClick={() => toggleBottomTools(STORAGE)} active={bottomBlock === STORAGE && !inspectorMode} - count={storageCount} label={getStorageName(storageType)} noIcon labelClassName="!text-base font-semibold" containerClassName="mx-2" /> )} - {showExceptions && ( - toggleBottomTools(EXCEPTIONS)} - active={bottomBlock === EXCEPTIONS && !inspectorMode} - label="EXCEPTIONS" - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - count={exceptionsCount} - hasErrors={exceptionsCount > 0} - /> - )} {!live && showStack && ( 0} /> )} @@ -475,7 +410,6 @@ export default class Controls extends React.Component { disabled={disabled && !inspectorMode} onClick={() => toggleBottomTools(PROFILER)} active={bottomBlock === PROFILER && !inspectorMode} - count={profilesCount} label="PROFILER" noIcon labelClassName="!text-base font-semibold" diff --git a/frontend/app/components/Session_/Player/Controls/Time.js b/frontend/app/components/Session_/Player/Controls/Time.js index ca3c6ce4c..e8221b5b8 100644 --- a/frontend/app/components/Session_/Player/Controls/Time.js +++ b/frontend/app/components/Session_/Player/Controls/Time.js @@ -2,7 +2,6 @@ import React from 'react'; import { Duration } from 'luxon'; import { connectPlayer } from 'Player'; import styles from './time.module.css'; -import { Tooltip } from 'react-tippy'; const Time = ({ time, isCustom, format = 'm:ss', }) => (
    diff --git a/frontend/app/components/Session_/Player/Controls/TimeTooltip.tsx b/frontend/app/components/Session_/Player/Controls/TimeTooltip.tsx deleted file mode 100644 index fe22c4ea9..000000000 --- a/frontend/app/components/Session_/Player/Controls/TimeTooltip.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; -// @ts-ignore -import { Duration } from 'luxon'; -import { connect } from 'react-redux'; -// @ts-ignore -import stl from './timeline.module.css'; - -function TimeTooltip({ time, offset, isVisible, liveTimeTravel }: { time: number; offset: number; isVisible: boolean, liveTimeTravel: boolean }) { - const duration = Duration.fromMillis(time).toFormat(`${liveTimeTravel ? '-' : ''}mm:ss`); - return ( -
    - {!time ? 'Loading' : duration} -
    - ); -} - -export default connect((state) => { - const { time = 0, offset = 0, isVisible } = state.getIn(['sessions', 'timeLineTooltip']); - return { time, offset, isVisible }; -})(TimeTooltip); diff --git a/frontend/app/components/Session_/Player/Controls/Timeline.js b/frontend/app/components/Session_/Player/Controls/Timeline.js index e53f567f0..c63324fac 100644 --- a/frontend/app/components/Session_/Player/Controls/Timeline.js +++ b/frontend/app/components/Session_/Player/Controls/Timeline.js @@ -1,5 +1,6 @@ import React from 'react'; import { connect } from 'react-redux'; +import { Icon } from 'UI' import { connectPlayer, Controls, toggleTimetravel } from 'Player'; import TimeTracker from './TimeTracker'; import stl from './timeline.module.css'; @@ -30,22 +31,12 @@ let debounceTooltipChange = () => null; disabled: state.cssLoading || state.messagesLoading || state.markedTargets, endTime: state.endTime, live: state.live, - logList: state.logList, - exceptionsList: state.exceptionsList, - resourceList: state.resourceList, - stackList: state.stackList, - fetchList: state.fetchList, + notes: state.notes, })) @connect( (state) => ({ issues: state.getIn(['sessions', 'current', 'issues']), startedAt: state.getIn(['sessions', 'current', 'startedAt']), - clickRageTime: - state.getIn(['sessions', 'current', 'clickRage']) && - state.getIn(['sessions', 'current', 'clickRageTime']), - returningLocationTime: - state.getIn(['sessions', 'current', 'returningLocation']) && - state.getIn(['sessions', 'current', 'returningLocationTime']), tooltipVisible: state.getIn(['sessions', 'timeLineTooltip', 'isVisible']), }), { setTimelinePointer, setTimelineHoverTime } @@ -170,7 +161,7 @@ export default class Timeline extends React.PureComponent { }; render() { - const { events, skip, skipIntervals, disabled, endTime, live } = this.props; + const { events, skip, skipIntervals, disabled, endTime, live, notes } = this.props; const scale = 100 / endTime; @@ -228,6 +219,22 @@ export default class Timeline extends React.PureComponent { style={{ left: `${getTimelinePosition(e.time, scale)}%` }} /> ))} + {notes.map((note) => note.timestamp > 0 ? ( +
    + +
    + ) : null)}
    ); diff --git a/frontend/app/components/Session_/Player/Controls/components/CreateNote.tsx b/frontend/app/components/Session_/Player/Controls/components/CreateNote.tsx new file mode 100644 index 000000000..af95e5eea --- /dev/null +++ b/frontend/app/components/Session_/Player/Controls/components/CreateNote.tsx @@ -0,0 +1,253 @@ +import React from 'react'; +import { Icon, Button, Checkbox } from 'UI'; +import { Duration } from 'luxon'; +import { connect } from 'react-redux'; +import { WriteNote, tagProps, TAGS, iTag, Note } from 'App/services/NotesService'; +import { setCreateNoteTooltip, addNote, updateNote } from 'Duck/sessions'; +import stl from './styles.module.css'; +import { useStore } from 'App/mstore'; +import { toast } from 'react-toastify'; +import { injectNotes } from 'Player'; +import { fetchList as fetchSlack } from 'Duck/integrations/slack'; +import Select from 'Shared/Select'; +import { TeamBadge } from 'Shared/SessionListContainer/components/Notes' +import { List } from 'immutable'; + +interface Props { + isVisible: boolean; + time: number; + setCreateNoteTooltip: (state: any) => void; + addNote: (note: Note) => void; + updateNote: (note: Note) => void; + sessionId: string; + isEdit: string; + editNote: WriteNote; + slackChannels: List>; + fetchSlack: () => void; +} + +function CreateNote({ + isVisible, + time, + setCreateNoteTooltip, + sessionId, + addNote, + isEdit, + editNote, + updateNote, + slackChannels, + fetchSlack, +}: Props) { + const [text, setText] = React.useState(''); + const [channel, setChannel] = React.useState(''); + const [isPublic, setPublic] = React.useState(false); + const [tag, setTag] = React.useState(TAGS[0]); + const [useTimestamp, setUseTs] = React.useState(true); + const inputRef = React.createRef(); + const { notesStore } = useStore(); + + React.useEffect(() => { + if (isEdit) { + setTag(editNote.tag); + setText(editNote.message); + setPublic(editNote.isPublic); + if (editNote.timestamp > 0) { + setUseTs(true); + } + } + }, [isEdit]); + + React.useEffect(() => { + if (inputRef.current && isVisible) { + fetchSlack(); + inputRef.current.focus(); + } + }, [isVisible]); + + const duration = Duration.fromMillis(time).toFormat('mm:ss'); + + const onSubmit = () => { + if (text === '') return; + + const note: WriteNote = { + message: text, + tag, + timestamp: useTimestamp ? (isEdit ? editNote.timestamp : time) : -1, + isPublic, + }; + const onSuccess = (noteId: string) => { + if (channel) { + notesStore.sendSlackNotification(noteId, channel) + } + } + if (isEdit) { + return notesStore + .updateNote(editNote.noteId, note) + .then((r) => { + toast.success('Note updated'); + notesStore.fetchSessionNotes(sessionId).then((notes) => { + injectNotes(notes); + onSuccess(editNote.noteId) + updateNote(r); + }); + }) + .catch((e) => { + toast.error('Error updating note'); + console.error(e); + }) + .finally(() => { + setCreateNoteTooltip({ isVisible: false, time: 0 }); + setText(''); + setTag(undefined); + }); + } + + return notesStore + .addNote(sessionId, note) + .then((r) => { + onSuccess(r.noteId as unknown as string) + toast.success('Note added'); + notesStore.fetchSessionNotes(sessionId).then((notes) => { + injectNotes(notes); + addNote(r); + }); + }) + .catch((e) => { + toast.error('Error adding note'); + console.error(e); + }) + .finally(() => { + setCreateNoteTooltip({ isVisible: false, time: 0 }); + setText(''); + setTag(undefined); + }); + }; + + const closeTooltip = () => { + setCreateNoteTooltip({ isVisible: false, time: 100 }); + }; + + const tagActive = (noteTag: iTag) => tag === noteTag; + + const addTag = (tag: iTag) => { + setTag(tag); + }; + + const slackChannelsOptions = slackChannels.map(({ webhookId, name }) => ({ + value: webhookId, + label: name, + })).toJS() as unknown as { value: string, label: string }[] + + slackChannelsOptions.unshift({ value: null, label: 'Share to slack?' }) + + const changeChannel = ({ value, name }: { value: Record; name: string }) => { + setChannel(value.value); + }; + + return ( +
    0 ? -310 : 255, + width: 350, + left: 'calc(50% - 175px)', + display: isVisible ? 'flex' : 'none', + flexDirection: 'column', + gap: '1rem', + }} + onClick={(e) => e.stopPropagation()} + > +
    + +

    Add Note

    +
    setUseTs(!useTimestamp)}> + + {`at ${duration}`} +
    + +
    + +
    +
    + +
    +