diff --git a/.github/workflows/workers-ee.yaml b/.github/workflows/workers-ee.yaml index a61d75160..18fca8872 100644 --- a/.github/workflows/workers-ee.yaml +++ b/.github/workflows/workers-ee.yaml @@ -47,7 +47,13 @@ jobs: # # Getting the images to build # - git diff --name-only HEAD HEAD~1 | grep backend/services | cut -d '/' -f3 | uniq > backend/images_to_build.txt + { + git diff --name-only HEAD HEAD~1 | grep -E "backend/cmd|backend/services" | grep -vE ^ee/ | cut -d '/' -f3 + + git diff --name-only HEAD HEAD~1 | grep -E "backend/pkg|backend/internal" | grep -vE ^ee/ | cut -d '/' -f3 | uniq | while read -r pkg_name ; do + grep -rl "pkg/$pkg_name" backend/services backend/cmd | cut -d '/' -f3 + done + } | uniq > backend/images_to_build.txt [[ $(cat backend/images_to_build.txt) != "" ]] || (echo "Nothing to build here"; exit 0) # # Pushing image to registry diff --git a/.github/workflows/workers.yaml b/.github/workflows/workers.yaml index 49fd0948b..e6a91647d 100644 --- a/.github/workflows/workers.yaml +++ b/.github/workflows/workers.yaml @@ -49,10 +49,10 @@ jobs: # { - git diff --name-only HEAD HEAD~1 | grep backend/services | grep -vE ^ee/ | cut -d '/' -f3 + git diff --name-only HEAD HEAD~1 | grep -E "backend/cmd|backend/services" | grep -vE ^ee/ | cut -d '/' -f3 - git diff --name-only HEAD HEAD~1 | grep backend/pkg | grep -vE ^ee/ | cut -d '/' -f3 | uniq | while read -r pkg_name ; do - grep -rl "pkg/$pkg_name" backend/services | cut -d '/' -f3 + git diff --name-only HEAD HEAD~1 | grep -E "backend/pkg|backend/internal" | grep -vE ^ee/ | cut -d '/' -f3 | uniq | while read -r pkg_name ; do + grep -rl "pkg/$pkg_name" backend/services backend/cmd | cut -d '/' -f3 done } | uniq > backend/images_to_build.txt diff --git a/api/.dockerignore b/api/.dockerignore new file mode 100644 index 000000000..b6aaccd33 --- /dev/null +++ b/api/.dockerignore @@ -0,0 +1,6 @@ +# ignore .git and .cache folders +.git +.cache +**/build.sh +**/build_*.sh +**/*deploy.sh diff --git a/api/Dockerfile b/api/Dockerfile index 0673ab2b5..f3b5e85f5 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -11,8 +11,8 @@ RUN apt update && apt install -y curl && \ curl -fsSL https://deb.nodesource.com/setup_12.x | bash - && \ apt install -y nodejs && \ apt remove --purge -y curl && \ - rm -rf /var/lib/apt/lists/* && \ - cd sourcemap-reader && \ + rm -rf /var/lib/apt/lists/* +RUN cd sourcemap-reader && \ npm install # Add Tini @@ -23,4 +23,4 @@ ENV ENTERPRISE_BUILD ${envarg} ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini RUN chmod +x /tini ENTRYPOINT ["/tini", "--"] -CMD ./entrypoint.sh \ No newline at end of file +CMD ./entrypoint.sh diff --git a/api/build.sh b/api/build.sh index cec7525f5..8c735ff3c 100644 --- a/api/build.sh +++ b/api/build.sh @@ -12,9 +12,9 @@ envarg="default-foss" check_prereq() { which docker || { echo "Docker not installed, please install docker." - exit=1 + exit 1 } - [[ exit -eq 1 ]] && exit 1 + return } function build_api(){ @@ -32,9 +32,11 @@ function build_api(){ docker push ${DOCKER_REPO:-'local'}/chalice:${git_sha1} docker tag ${DOCKER_REPO:-'local'}/chalice:${git_sha1} ${DOCKER_REPO:-'local'}/chalice:${tag}latest docker push ${DOCKER_REPO:-'local'}/chalice:${tag}latest -} + } + echo "api docker build completed" } check_prereq build_api $1 -IMAGE_TAG=$IMAGE_TAG PUSH_IMAGE=$PUSH_IMAGE DOCKER_REPO=$DOCKER_REPO bash build_alerts.sh $1 \ No newline at end of file +echo buil_complete +IMAGE_TAG=$IMAGE_TAG PUSH_IMAGE=$PUSH_IMAGE DOCKER_REPO=$DOCKER_REPO bash build_alerts.sh $1 diff --git a/api/build_alerts.sh b/api/build_alerts.sh index f333c8dc8..2a7d88a1e 100644 --- a/api/build_alerts.sh +++ b/api/build_alerts.sh @@ -27,7 +27,7 @@ function make_submodule() { mkdir -p ./alerts/chalicelib/ cp -R ./chalicelib/__init__.py ./alerts/chalicelib/ mkdir -p ./alerts/chalicelib/core/ - cp -R ./chalicelib/core/{__init__,alerts_processor,alerts_listener,sessions,events,issues,sessions_metas,metadata,projects,users,authorizers,tenants,roles,assist,events_ios,sessions_mobs,errors,dashboard,sourcemaps,sourcemaps_parser,resources,performance_event,alerts,notifications,slack,collaboration_slack,webhook}.py ./alerts/chalicelib/core/ + cp -R ./chalicelib/core/{__init__,alerts_processor,alerts_listener,sessions,events,issues,sessions_metas,metadata,projects,users,authorizers,tenants,roles,assist,events_ios,sessions_mobs,errors,metrics,sourcemaps,sourcemaps_parser,resources,performance_event,alerts,notifications,slack,collaboration_slack,webhook}.py ./alerts/chalicelib/core/ mkdir -p ./alerts/chalicelib/utils/ cp -R ./chalicelib/utils/{__init__,TimeUTC,pg_client,helper,event_filter_definition,dev,SAML2_helper,email_helper,email_handler,smtp,s3,args_transformer,ch_client,metrics_helper}.py ./alerts/chalicelib/utils/ # -- end of generated part @@ -64,7 +64,8 @@ function build_api(){ docker tag ${DOCKER_REPO:-'local'}/alerts:${git_sha1} ${DOCKER_REPO:-'local'}/alerts:${tag}latest docker push ${DOCKER_REPO:-'local'}/alerts:${tag}latest } +echo "completed alerts build" } check_prereq -build_api $1 \ No newline at end of file +build_api $1 diff --git a/api/chalicelib/core/projects.py b/api/chalicelib/core/projects.py index f6b7704f5..0b0bd963f 100644 --- a/api/chalicelib/core/projects.py +++ b/api/chalicelib/core/projects.py @@ -41,26 +41,14 @@ def __create(tenant_id, name): return get_project(tenant_id=tenant_id, project_id=project_id, include_gdpr=True) -def get_projects(tenant_id, recording_state=False, gdpr=None, recorded=False, stack_integrations=False, version=False, - last_tracker_version=None): +def get_projects(tenant_id, recording_state=False, gdpr=None, recorded=False, stack_integrations=False): with pg_client.PostgresClient() as cur: - tracker_query = "" - if last_tracker_version is not None and len(last_tracker_version) > 0: - tracker_query = cur.mogrify( - """,(SELECT tracker_version FROM public.sessions - WHERE sessions.project_id = s.project_id - AND tracker_version=%(version)s AND tracker_version IS NOT NULL LIMIT 1) AS tracker_version""", - {"version": last_tracker_version}).decode('UTF-8') - elif version: - tracker_query = ",(SELECT tracker_version FROM public.sessions WHERE sessions.project_id = s.project_id ORDER BY start_ts DESC LIMIT 1) AS tracker_version" - cur.execute(f"""\ SELECT s.project_id, s.name, s.project_key, s.save_request_payloads {',s.gdpr' if gdpr else ''} {',COALESCE((SELECT TRUE FROM public.sessions WHERE sessions.project_id = s.project_id LIMIT 1), FALSE) AS recorded' if recorded else ''} {',stack_integrations.count>0 AS stack_integrations' if stack_integrations else ''} - {tracker_query} FROM public.projects AS s {'LEFT JOIN LATERAL (SELECT COUNT(*) AS count FROM public.integrations WHERE s.project_id = integrations.project_id LIMIT 1) AS stack_integrations ON TRUE' if stack_integrations else ''} WHERE s.deleted_at IS NULL @@ -90,19 +78,8 @@ def get_projects(tenant_id, recording_state=False, gdpr=None, recorded=False, st return helper.list_to_camel_case(rows) -def get_project(tenant_id, project_id, include_last_session=False, include_gdpr=None, version=False, - last_tracker_version=None): +def get_project(tenant_id, project_id, include_last_session=False, include_gdpr=None): with pg_client.PostgresClient() as cur: - tracker_query = "" - if last_tracker_version is not None and len(last_tracker_version) > 0: - tracker_query = cur.mogrify( - """,(SELECT tracker_version FROM public.sessions - WHERE sessions.project_id = s.project_id - AND tracker_version=%(version)s AND tracker_version IS NOT NULL LIMIT 1) AS tracker_version""", - {"version": last_tracker_version}).decode('UTF-8') - elif version: - tracker_query = ",(SELECT tracker_version FROM public.sessions WHERE sessions.project_id = s.project_id ORDER BY start_ts DESC LIMIT 1) AS tracker_version" - query = cur.mogrify(f"""\ SELECT s.project_id, @@ -111,7 +88,6 @@ def get_project(tenant_id, project_id, include_last_session=False, include_gdpr= s.save_request_payloads {",(SELECT max(ss.start_ts) FROM public.sessions AS ss WHERE ss.project_id = %(project_id)s) AS last_recorded_session_at" if include_last_session else ""} {',s.gdpr' if include_gdpr else ''} - {tracker_query} FROM public.projects AS s where s.project_id =%(project_id)s AND s.deleted_at IS NULL diff --git a/api/development.md b/api/development.md new file mode 100644 index 000000000..15e3c64a7 --- /dev/null +++ b/api/development.md @@ -0,0 +1,30 @@ +### Prerequisites + +- [Vagrant](../scripts/vagrant/README.md) +- Python 3.9 +- Pipenv + +### Development environment + +```bash +cd openreplay/api +# Make your own copy of .env file and edit it as you want +cp .env.dev .env + +# Create a .venv folder to contain all you dependencies +mkdir .venv + +# Installing dependencies (pipenv will detect the .venv folder and use it as a target) +pipenv install -r requirements.txt [--skip-lock] +``` + +### Building and deploying locally + +```bash +cd openreplay-contributions +vagrant ssh +cd openreplay-dev/openreplay/scripts/helmcharts +# For complete list of options +# bash local_deploy.sh help +bash local_deploy.sh api +``` diff --git a/api/routers/core_dynamic.py b/api/routers/core_dynamic.py index 790e65340..e7e87e76c 100644 --- a/api/routers/core_dynamic.py +++ b/api/routers/core_dynamic.py @@ -91,10 +91,9 @@ def get_projects_limit(context: schemas.CurrentContext = Depends(OR_context)): @app.get('/projects/{projectId}', tags=['projects']) -def get_project(projectId: int, last_tracker_version: Optional[str] = None, - context: schemas.CurrentContext = Depends(OR_context)): +def get_project(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): data = projects.get_project(tenant_id=context.tenant_id, project_id=projectId, include_last_session=True, - include_gdpr=True, last_tracker_version=last_tracker_version) + include_gdpr=True) if data is None: return {"errors": ["project not found"]} return {"data": data} @@ -223,7 +222,6 @@ def get_client(context: schemas.CurrentContext = Depends(OR_context)): @app.get('/projects', tags=['projects']) -def get_projects(last_tracker_version: Optional[str] = None, context: schemas.CurrentContext = Depends(OR_context)): +def get_projects(context: schemas.CurrentContext = Depends(OR_context)): return {"data": projects.get_projects(tenant_id=context.tenant_id, recording_state=True, gdpr=True, recorded=True, - stack_integrations=True, version=True, - last_tracker_version=last_tracker_version)} + stack_integrations=True)} diff --git a/backend/.dockerignore b/backend/.dockerignore new file mode 100644 index 000000000..b6aaccd33 --- /dev/null +++ b/backend/.dockerignore @@ -0,0 +1,6 @@ +# ignore .git and .cache folders +.git +.cache +**/build.sh +**/build_*.sh +**/*deploy.sh diff --git a/backend/Dockerfile.bundle b/backend/Dockerfile.bundle index efbcb2684..79ef57db5 100644 --- a/backend/Dockerfile.bundle +++ b/backend/Dockerfile.bundle @@ -1,4 +1,4 @@ -FROM golang:1.13-alpine3.10 AS prepare +FROM golang:1.18-alpine3.15 AS prepare RUN apk add --no-cache git openssh openssl-dev pkgconf gcc g++ make libc-dev bash @@ -13,7 +13,7 @@ FROM prepare AS build COPY pkg pkg COPY services services -RUN for name in alerts assets db ender http integrations sink storage;do CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o bin/$name -tags musl openreplay/backend/services/$name; done +RUN for name in assets db ender http integrations sink storage;do CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o bin/$name -tags musl openreplay/backend/services/$name; done FROM alpine @@ -26,8 +26,9 @@ ENV TZ=UTC \ MAXMINDDB_FILE=/root/geoip.mmdb \ UAPARSER_FILE=/root/regexes.yaml \ HTTP_PORT=80 \ - BEACON_SIZE_LIMIT=1000000 \ + BEACON_SIZE_LIMIT=7000000 \ KAFKA_USE_SSL=true \ + KAFKA_MAX_POLL_INTERVAL_MS=400000 \ REDIS_STREAMS_MAX_LEN=3000 \ TOPIC_RAW_WEB=raw \ TOPIC_RAW_IOS=raw-ios \ @@ -42,10 +43,10 @@ ENV TZ=UTC \ AWS_REGION_WEB=eu-central-1 \ AWS_REGION_IOS=eu-west-1 \ AWS_REGION_ASSETS=eu-central-1 \ - CACHE_ASSETS=false \ + CACHE_ASSETS=true \ ASSETS_SIZE_LIMIT=6291456 \ - FS_CLEAN_HRS=12 - + FS_CLEAN_HRS=12 \ + LOG_QUEUE_STATS_INTERVAL_SEC=60 RUN mkdir $FS_DIR #VOLUME [ $FS_DIR ] # Uncomment in case of using Bind mount. diff --git a/backend/build.sh b/backend/build.sh old mode 100644 new mode 100755 index c760c1b9b..84af1a919 --- a/backend/build.sh +++ b/backend/build.sh @@ -13,9 +13,31 @@ ee="false" check_prereq() { which docker || { echo "Docker not installed, please install docker." - exit=1 + exit 1 } - [[ exit -eq 1 ]] && exit 1 + return +} + + +function build_service() { + image="$1" + echo "BUILDING $image" + case "$image" in + http | db) + echo build http + docker build -t ${DOCKER_REPO:-'local'}/$image:${git_sha1} --platform linux/amd64 --build-arg SERVICE_NAME=$image -f ./cmd/Dockerfile . + [[ $PUSH_IMAGE -eq 1 ]] && { + docker push ${DOCKER_REPO:-'local'}/$image:${git_sha1} + } + ;; + *) + docker build -t ${DOCKER_REPO:-'local'}/$image:${git_sha1} --platform linux/amd64 --build-arg SERVICE_NAME=$image . + [[ $PUSH_IMAGE -eq 1 ]] && { + docker push ${DOCKER_REPO:-'local'}/$image:${git_sha1} + } + ;; + esac + return } function build_api(){ @@ -25,21 +47,15 @@ function build_api(){ ee="true" } [[ $2 != "" ]] && { - image="$2" - docker build -t ${DOCKER_REPO:-'local'}/$image:${git_sha1} --build-arg SERVICE_NAME=$image . - [[ $PUSH_IMAGE -eq 1 ]] && { - docker push ${DOCKER_REPO:-'local'}/$image:${git_sha1} - } + build_service $2 return } for image in $(ls services); do - docker build -t ${DOCKER_REPO:-'local'}/$image:${git_sha1} --build-arg SERVICE_NAME=$image . - [[ $PUSH_IMAGE -eq 1 ]] && { - docker push ${DOCKER_REPO:-'local'}/$image:${git_sha1} - } + build_service $image echo "::set-output name=image::${DOCKER_REPO:-'local'}/$image:${git_sha1}" done + echo "backend build completed" } check_prereq diff --git a/backend/cmd/Dockerfile b/backend/cmd/Dockerfile new file mode 100644 index 000000000..f36bfc99e --- /dev/null +++ b/backend/cmd/Dockerfile @@ -0,0 +1,60 @@ +FROM golang:1.18-alpine3.15 AS prepare + +RUN apk add --no-cache git openssh openssl-dev pkgconf gcc g++ make libc-dev bash + +WORKDIR /root + +COPY go.mod . +COPY go.sum . +RUN go mod download + + +FROM prepare AS build +COPY pkg pkg +COPY services services +COPY internal internal +COPY cmd cmd + +ARG SERVICE_NAME +RUN CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o service -tags musl openreplay/backend/cmd/$SERVICE_NAME + +FROM alpine AS entrypoint +RUN apk add --no-cache ca-certificates + +ENV TZ=UTC \ + FS_ULIMIT=1000 \ + FS_DIR=/mnt/efs \ + MAXMINDDB_FILE=/root/geoip.mmdb \ + UAPARSER_FILE=/root/regexes.yaml \ + HTTP_PORT=80 \ + BEACON_SIZE_LIMIT=7000000 \ + KAFKA_USE_SSL=true \ + KAFKA_MAX_POLL_INTERVAL_MS=400000 \ + REDIS_STREAMS_MAX_LEN=3000 \ + TOPIC_RAW_WEB=raw \ + TOPIC_RAW_IOS=raw-ios \ + TOPIC_CACHE=cache \ + TOPIC_ANALYTICS=analytics \ + TOPIC_TRIGGER=trigger \ + GROUP_SINK=sink \ + GROUP_STORAGE=storage \ + GROUP_DB=db \ + GROUP_ENDER=ender \ + GROUP_CACHE=cache \ + AWS_REGION_WEB=eu-central-1 \ + AWS_REGION_IOS=eu-west-1 \ + AWS_REGION_ASSETS=eu-central-1 \ + CACHE_ASSETS=true \ + ASSETS_SIZE_LIMIT=6291456 \ + FS_CLEAN_HRS=72 \ + LOG_QUEUE_STATS_INTERVAL_SEC=60 + + +ARG SERVICE_NAME +RUN if [ "$SERVICE_NAME" = "http" ]; then \ + wget https://raw.githubusercontent.com/ua-parser/uap-core/master/regexes.yaml -O "$UAPARSER_FILE" &&\ + wget https://static.openreplay.com/geoip/GeoLite2-Country.mmdb -O "$MAXMINDDB_FILE"; fi + + +COPY --from=build /root/service /root/service +ENTRYPOINT /root/service diff --git a/backend/cmd/db/main.go b/backend/cmd/db/main.go new file mode 100644 index 000000000..962057213 --- /dev/null +++ b/backend/cmd/db/main.go @@ -0,0 +1,117 @@ +package main + +import ( + "log" + "openreplay/backend/internal/config/db" + "openreplay/backend/internal/datasaver" + "openreplay/backend/internal/heuristics" + "time" + + "os" + "os/signal" + "syscall" + + "openreplay/backend/pkg/db/cache" + "openreplay/backend/pkg/db/postgres" + logger "openreplay/backend/pkg/log" + "openreplay/backend/pkg/messages" + "openreplay/backend/pkg/queue" + "openreplay/backend/pkg/queue/types" +) + +func main() { + log.SetFlags(log.LstdFlags | log.LUTC | log.Llongfile) + + cfg := db.New() + + // Init database + pg := cache.NewPGCache(postgres.NewConn(cfg.Postgres), cfg.ProjectExpirationTimeoutMs) + defer pg.Close() + + // Init modules + heurFinder := heuristics.NewHandler() + saver := datasaver.New(pg) + statsLogger := logger.NewQueueStats(cfg.LoggerTimeout) + + // Handler logic + handler := func(sessionID uint64, msg messages.Message, meta *types.Meta) { + statsLogger.Collect(sessionID, meta) + + // 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 + } + + // Try to get session from db for the following handlers + session, err := pg.GetSession(sessionID) + if err != nil { + // Might happen due to the assets-related message TODO: log only if session is necessary for this kind of message + 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 + heurFinder.HandleMessage(session, msg) + + // Process saved heuristics messages as usual messages above in the code + heurFinder.IterateSessionReadyMessages(sessionID, func(msg messages.Message) { + // TODO: DRY code (carefully with the return statement logic) + 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) + } + }) + } + + // Init consumer + consumer := queue.NewMessageConsumer( + cfg.GroupDB, + []string{ + cfg.TopicRawIOS, + cfg.TopicTrigger, + }, + handler, + false, + ) + + log.Printf("Db service started\n") + + sigchan := make(chan os.Signal, 1) + signal.Notify(sigchan, syscall.SIGINT, syscall.SIGTERM) + + tick := time.Tick(cfg.CommitBatchTimeout) + for { + select { + case sig := <-sigchan: + log.Printf("Caught signal %v: terminating\n", sig) + consumer.Close() + os.Exit(0) + case <-tick: + pg.CommitBatches() + // TODO?: separate stats & regular messages + if err := consumer.Commit(); err != nil { + log.Printf("Error on consumer commit: %v", err) + } + default: + err := consumer.ConsumeNext() + if err != nil { + log.Fatalf("Error on consumption: %v", err) // TODO: is always fatal? + } + } + } +} diff --git a/backend/services/http/README.md b/backend/cmd/http/README.md similarity index 100% rename from backend/services/http/README.md rename to backend/cmd/http/README.md diff --git a/backend/cmd/http/main.go b/backend/cmd/http/main.go new file mode 100644 index 000000000..541baab62 --- /dev/null +++ b/backend/cmd/http/main.go @@ -0,0 +1,62 @@ +package main + +import ( + "log" + "openreplay/backend/internal/config" + "openreplay/backend/internal/router" + "openreplay/backend/internal/server" + "openreplay/backend/internal/services" + "openreplay/backend/pkg/db/cache" + "openreplay/backend/pkg/db/postgres" + "openreplay/backend/pkg/pprof" + "openreplay/backend/pkg/queue" + "os" + "os/signal" + "syscall" +) + +func main() { + log.SetFlags(log.LstdFlags | log.LUTC | log.Llongfile) + pprof.StartProfilingServer() + + // Load configuration + cfg := config.New() + + // Connect to queue + producer := queue.NewProducer() + defer producer.Close(15000) + + // Connect to database + dbConn := cache.NewPGCache(postgres.NewConn(cfg.Postgres), 1000*60*20) + defer dbConn.Close() + + // Build all services + services := services.New(cfg, producer, dbConn) + + // Init server's routes + router, err := router.NewRouter(cfg, services) + if err != nil { + log.Fatalf("failed while creating engine: %s", err) + } + + // Init server + server, err := server.New(router.GetHandler(), cfg.HTTPHost, cfg.HTTPPort, cfg.HTTPTimeout) + if err != nil { + log.Fatalf("failed while creating server: %s", err) + } + + // Run server + go func() { + if err := server.Start(); err != nil { + log.Fatalf("Server error: %v\n", err) + } + }() + log.Printf("Server successfully started on port %v\n", cfg.HTTPPort) + + // Wait stop signal to shut down server gracefully + sigchan := make(chan os.Signal, 1) + signal.Notify(sigchan, syscall.SIGINT, syscall.SIGTERM) + <-sigchan + log.Printf("Shutting down the server\n") + server.Stop() +} diff --git a/backend/development.md b/backend/development.md new file mode 100644 index 000000000..1339a2456 --- /dev/null +++ b/backend/development.md @@ -0,0 +1,14 @@ +### Prerequisites + +- [Vagrant](../scripts/vagrant/README.md) + +### Building and deploying locally + +```bash +cd openreplay-contributions +vagrant ssh +cd openreplay-dev/openreplay/scripts/helmcharts +# For complete list of options +# bash local_deploy.sh help +bash local_deploy.sh +``` diff --git a/backend/internal/assetscache/assets.go b/backend/internal/assetscache/assets.go new file mode 100644 index 000000000..1ef70b56c --- /dev/null +++ b/backend/internal/assetscache/assets.go @@ -0,0 +1,83 @@ +package assetscache + +import ( + "openreplay/backend/internal/config" + "openreplay/backend/pkg/messages" + "openreplay/backend/pkg/queue/types" + "openreplay/backend/pkg/url/assets" +) + +type AssetsCache struct { + cfg *config.Config + rewriter *assets.Rewriter + producer types.Producer +} + +func New(cfg *config.Config, rewriter *assets.Rewriter, producer types.Producer) *AssetsCache { + return &AssetsCache{ + cfg: cfg, + rewriter: rewriter, + producer: producer, + } +} + +func (e *AssetsCache) ParseAssets(sessID uint64, msg messages.Message) messages.Message { + switch m := msg.(type) { + case *messages.SetNodeAttributeURLBased: + if m.Name == "src" || m.Name == "href" { + return &messages.SetNodeAttribute{ + ID: m.ID, + Name: m.Name, + Value: e.handleURL(sessID, m.BaseURL, m.Value), + } + } else if m.Name == "style" { + return &messages.SetNodeAttribute{ + ID: m.ID, + Name: m.Name, + Value: e.handleCSS(sessID, m.BaseURL, m.Value), + } + } + case *messages.SetCSSDataURLBased: + return &messages.SetCSSData{ + ID: m.ID, + Data: e.handleCSS(sessID, m.BaseURL, m.Data), + } + case *messages.CSSInsertRuleURLBased: + return &messages.CSSInsertRule{ + ID: m.ID, + Index: m.Index, + Rule: e.handleCSS(sessID, m.BaseURL, m.Rule), + } + } + return msg +} + +func (e *AssetsCache) sendAssetForCache(sessionID uint64, baseURL string, relativeURL string) { + if fullURL, cacheable := assets.GetFullCachableURL(baseURL, relativeURL); cacheable { + e.producer.Produce(e.cfg.TopicCache, sessionID, messages.Encode(&messages.AssetCache{ + URL: fullURL, + })) + } +} + +func (e *AssetsCache) sendAssetsForCacheFromCSS(sessionID uint64, baseURL string, css string) { + for _, u := range assets.ExtractURLsFromCSS(css) { // TODO: in one shot with rewriting + e.sendAssetForCache(sessionID, baseURL, u) + } +} + +func (e *AssetsCache) handleURL(sessionID uint64, baseURL string, url string) string { + if e.cfg.CacheAssets { + e.sendAssetForCache(sessionID, baseURL, url) + return e.rewriter.RewriteURL(sessionID, baseURL, url) + } + return assets.ResolveURL(baseURL, url) +} + +func (e *AssetsCache) handleCSS(sessionID uint64, baseURL string, css string) string { + if e.cfg.CacheAssets { + e.sendAssetsForCacheFromCSS(sessionID, baseURL, css) + return e.rewriter.RewriteCSS(sessionID, baseURL, css) + } + return assets.ResolveCSS(baseURL, css) +} diff --git a/backend/internal/config/config.go b/backend/internal/config/config.go new file mode 100644 index 000000000..5b55ba346 --- /dev/null +++ b/backend/internal/config/config.go @@ -0,0 +1,50 @@ +package config + +import ( + "openreplay/backend/pkg/env" + "time" +) + +type Config struct { + HTTPHost string + HTTPPort string + HTTPTimeout time.Duration + TopicRawWeb string + TopicRawIOS string + TopicCache string + CacheAssets bool + BeaconSizeLimit int64 + JsonSizeLimit int64 + FileSizeLimit int64 + AssetsOrigin string + AWSRegion string + S3BucketIOSImages string + Postgres string + TokenSecret string + UAParserFile string + MaxMinDBFile string + WorkerID uint16 +} + +func New() *Config { + return &Config{ + HTTPHost: "", // empty by default + HTTPPort: env.String("HTTP_PORT"), + HTTPTimeout: time.Second * 60, + TopicRawWeb: env.String("TOPIC_RAW_WEB"), + TopicRawIOS: env.String("TOPIC_RAW_IOS"), + TopicCache: env.String("TOPIC_CACHE"), + CacheAssets: env.Bool("CACHE_ASSETS"), + BeaconSizeLimit: int64(env.Uint64("BEACON_SIZE_LIMIT")), + JsonSizeLimit: 1e3, // 1Kb + FileSizeLimit: 1e7, // 10Mb + AssetsOrigin: env.String("ASSETS_ORIGIN"), + AWSRegion: env.String("AWS_REGION"), + S3BucketIOSImages: env.String("S3_BUCKET_IOS_IMAGES"), + Postgres: env.String("POSTGRES_STRING"), + TokenSecret: env.String("TOKEN_SECRET"), + UAParserFile: env.String("UAPARSER_FILE"), + MaxMinDBFile: env.String("MAXMINDDB_FILE"), + WorkerID: env.WorkerID(), + } +} diff --git a/backend/internal/config/db/config.go b/backend/internal/config/db/config.go new file mode 100644 index 000000000..fb35a199c --- /dev/null +++ b/backend/internal/config/db/config.go @@ -0,0 +1,28 @@ +package db + +import ( + "openreplay/backend/pkg/env" + "time" +) + +type Config struct { + Postgres string + ProjectExpirationTimeoutMs int64 + LoggerTimeout int + GroupDB string + TopicRawIOS string + TopicTrigger string + CommitBatchTimeout time.Duration +} + +func New() *Config { + return &Config{ + Postgres: env.String("POSTGRES_STRING"), + ProjectExpirationTimeoutMs: 1000 * 60 * 20, + LoggerTimeout: env.Int("LOG_QUEUE_STATS_INTERVAL_SEC"), + GroupDB: env.String("GROUP_DB"), + TopicRawIOS: env.String("TOPIC_RAW_IOS"), + TopicTrigger: env.String("TOPIC_TRIGGER"), + CommitBatchTimeout: 15 * time.Second, + } +} diff --git a/backend/internal/datasaver/messages.go b/backend/internal/datasaver/messages.go new file mode 100644 index 000000000..1e774888d --- /dev/null +++ b/backend/internal/datasaver/messages.go @@ -0,0 +1,66 @@ +package datasaver + +import ( + . "openreplay/backend/pkg/messages" +) + +func (mi *Saver) InsertMessage(sessionID uint64, msg Message) error { + switch m := msg.(type) { + // Common + case *Metadata: + return mi.pg.InsertMetadata(sessionID, m) + case *IssueEvent: + return mi.pg.InsertIssueEvent(sessionID, m) + //TODO: message adapter (transformer) (at the level of pkg/message) for types: *IOSMetadata, *IOSIssueEvent and others + + // Web + case *SessionStart: + return mi.pg.InsertWebSessionStart(sessionID, m) + case *SessionEnd: + return mi.pg.InsertWebSessionEnd(sessionID, m) + case *UserID: + return mi.pg.InsertWebUserID(sessionID, m) + case *UserAnonymousID: + return mi.pg.InsertWebUserAnonymousID(sessionID, m) + case *CustomEvent: + return mi.pg.InsertWebCustomEvent(sessionID, m) + case *ClickEvent: + return mi.pg.InsertWebClickEvent(sessionID, m) + case *InputEvent: + return mi.pg.InsertWebInputEvent(sessionID, m) + + // Unique Web messages + case *PageEvent: + return mi.pg.InsertWebPageEvent(sessionID, m) + case *ErrorEvent: + return mi.pg.InsertWebErrorEvent(sessionID, m) + case *FetchEvent: + return mi.pg.InsertWebFetchEvent(sessionID, m) + case *GraphQLEvent: + return mi.pg.InsertWebGraphQLEvent(sessionID, m) + + // IOS + case *IOSSessionStart: + return mi.pg.InsertIOSSessionStart(sessionID, m) + case *IOSSessionEnd: + return mi.pg.InsertIOSSessionEnd(sessionID, m) + case *IOSUserID: + return mi.pg.InsertIOSUserID(sessionID, m) + case *IOSUserAnonymousID: + return mi.pg.InsertIOSUserAnonymousID(sessionID, m) + case *IOSCustomEvent: + return mi.pg.InsertIOSCustomEvent(sessionID, m) + case *IOSClickEvent: + return mi.pg.InsertIOSClickEvent(sessionID, m) + case *IOSInputEvent: + return mi.pg.InsertIOSInputEvent(sessionID, m) + // Unique IOS messages + case *IOSNetworkCall: + return mi.pg.InsertIOSNetworkCall(sessionID, m) + case *IOSScreenEnter: + return mi.pg.InsertIOSScreenEnter(sessionID, m) + case *IOSCrash: + return mi.pg.InsertIOSCrash(sessionID, m) + } + return nil // "Not implemented" +} diff --git a/backend/internal/datasaver/saver.go b/backend/internal/datasaver/saver.go new file mode 100644 index 000000000..4cd742718 --- /dev/null +++ b/backend/internal/datasaver/saver.go @@ -0,0 +1,11 @@ +package datasaver + +import "openreplay/backend/pkg/db/cache" + +type Saver struct { + pg *cache.PGCache +} + +func New(pg *cache.PGCache) *Saver { + return &Saver{pg: pg} +} diff --git a/backend/internal/datasaver/stats.go b/backend/internal/datasaver/stats.go new file mode 100644 index 000000000..a57d91824 --- /dev/null +++ b/backend/internal/datasaver/stats.go @@ -0,0 +1,19 @@ +package datasaver + +import ( + . "openreplay/backend/pkg/db/types" + . "openreplay/backend/pkg/messages" +) + +func (si *Saver) InsertStats(session *Session, msg Message) error { + switch m := msg.(type) { + // Web + case *PerformanceTrackAggr: + 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 +} diff --git a/backend/services/http/geoip/geoip.go b/backend/internal/geoip/geoip.go similarity index 100% rename from backend/services/http/geoip/geoip.go rename to backend/internal/geoip/geoip.go diff --git a/backend/services/http/geoip/http.go b/backend/internal/geoip/http.go similarity index 100% rename from backend/services/http/geoip/http.go rename to backend/internal/geoip/http.go diff --git a/backend/services/db/heuristics/anr.go b/backend/internal/heuristics/anr.go similarity index 79% rename from backend/services/db/heuristics/anr.go rename to backend/internal/heuristics/anr.go index 266f882f9..7cec8fc97 100644 --- a/backend/services/db/heuristics/anr.go +++ b/backend/internal/heuristics/anr.go @@ -1,26 +1,23 @@ package heuristics import ( - . "openreplay/backend/pkg/messages" + . "openreplay/backend/pkg/messages" ) - const MIN_TIME_AFTER_LAST_HEARTBEAT = 60 * 1000 type anr struct { readyMessageStore - lastLabel string + lastLabel string lastHeartbeatTimestamp uint64 - lastHeartbeatIndex uint64 + lastHeartbeatIndex uint64 } func (h *anr) buildIf(timestamp uint64) { - if h.lastHeartbeatTimestamp != 0 && h.lastHeartbeatTimestamp + MIN_TIME_AFTER_LAST_HEARTBEAT <= timestamp { + if h.lastHeartbeatTimestamp != 0 && h.lastHeartbeatTimestamp+MIN_TIME_AFTER_LAST_HEARTBEAT <= timestamp { m := &IOSIssueEvent{ - Type: "anr", + Type: "anr", ContextString: h.lastLabel, - //Context: "{}", - //Payload: fmt.SPrint } m.Timestamp = h.lastHeartbeatTimestamp m.Index = h.lastHeartbeatIndex // Associated Index/ MessageID ? @@ -49,4 +46,4 @@ func (h *anr) HandleMessage(msg Message) { case *IOSSessionEnd: h.buildIf(m.Timestamp) } -} \ No newline at end of file +} diff --git a/backend/services/db/heuristics/clickrage.go b/backend/internal/heuristics/clickrage.go similarity index 77% rename from backend/services/db/heuristics/clickrage.go rename to backend/internal/heuristics/clickrage.go index 4dc86ee65..4d19bf92e 100644 --- a/backend/services/db/heuristics/clickrage.go +++ b/backend/internal/heuristics/clickrage.go @@ -1,29 +1,26 @@ package heuristics import ( - . "openreplay/backend/pkg/messages" + . "openreplay/backend/pkg/messages" ) - const CLICK_TIME_DIFF = 200 const MIN_CLICKS_IN_A_ROW = 3 type clickrage struct { readyMessageStore - lastTimestamp uint64 - lastLabel string + lastTimestamp uint64 + lastLabel string firstInARawTimestamp uint64 - firstInARawSeqIndex uint64 - countsInARow int + firstInARawSeqIndex uint64 + countsInARow int } func (h *clickrage) build() { if h.countsInARow >= MIN_CLICKS_IN_A_ROW { m := &IOSIssueEvent{ - Type: "click_rage", + Type: "click_rage", ContextString: h.lastLabel, - //Context: "{}", - //Payload: fmt.SPrint } m.Timestamp = h.firstInARawTimestamp m.Index = h.firstInARawSeqIndex // Associated Index/ MessageID ? @@ -39,7 +36,7 @@ func (h *clickrage) build() { func (h *clickrage) HandleMessage(msg Message) { switch m := msg.(type) { case *IOSClickEvent: - if h.lastTimestamp + CLICK_TIME_DIFF < m.Timestamp && h.lastLabel == m.Label { + if h.lastTimestamp+CLICK_TIME_DIFF < m.Timestamp && h.lastLabel == m.Label { h.lastTimestamp = m.Timestamp h.countsInARow += 1 return @@ -55,4 +52,4 @@ func (h *clickrage) HandleMessage(msg Message) { case *IOSSessionEnd: h.build() } -} \ No newline at end of file +} diff --git a/backend/services/db/heuristics/heuristics.go b/backend/internal/heuristics/heuristics.go similarity index 90% rename from backend/services/db/heuristics/heuristics.go rename to backend/internal/heuristics/heuristics.go index 7832e0a82..677574951 100644 --- a/backend/services/db/heuristics/heuristics.go +++ b/backend/internal/heuristics/heuristics.go @@ -1,8 +1,8 @@ package heuristics import ( - . "openreplay/backend/pkg/messages" - . "openreplay/backend/pkg/db/types" + . "openreplay/backend/pkg/db/types" + . "openreplay/backend/pkg/messages" ) type MessageHandler interface { @@ -19,7 +19,6 @@ type Handler interface { type mainHandler map[uint64]*sessHandler - func NewHandler() mainHandler { return make(mainHandler) } @@ -43,8 +42,10 @@ func (m mainHandler) HandleMessage(session *Session, msg Message) { } func (m mainHandler) IterateSessionReadyMessages(sessionID uint64, iter func(msg Message)) { - s, ok := m[ sessionID ] - if !ok { return } + s, ok := m[sessionID] + if !ok { + return + } s.IterateReadyMessages(iter) if s.IsEnded() { delete(m, sessionID) @@ -61,5 +62,3 @@ func (m mainHandler) IterateReadyMessages(iter func(sessionID uint64, msg Messag } } } - - diff --git a/backend/services/db/heuristics/performance.go b/backend/internal/heuristics/performance.go similarity index 87% rename from backend/services/db/heuristics/performance.go rename to backend/internal/heuristics/performance.go index 931d831e6..c7494a793 100644 --- a/backend/services/db/heuristics/performance.go +++ b/backend/internal/heuristics/performance.go @@ -1,31 +1,30 @@ package heuristics import ( - . "openreplay/backend/pkg/messages" + . "openreplay/backend/pkg/messages" ) - const AGGR_TIME = 15 * 60 * 1000 - type valueAggregator struct { - sum float64 + sum float64 count float64 } + func (va *valueAggregator) aggregate() uint64 { if va.count == 0 { return 0 } - return uint64(va.sum/va.count) + return uint64(va.sum / va.count) } type performanceAggregator struct { readyMessageStore - pa *IOSPerformanceAggregated - fps valueAggregator - cpu valueAggregator - memory valueAggregator - battery valueAggregator + pa *IOSPerformanceAggregated + fps valueAggregator + cpu valueAggregator + memory valueAggregator + battery valueAggregator } func (h *performanceAggregator) build(timestamp uint64) { @@ -56,7 +55,7 @@ func (h *performanceAggregator) HandleMessage(msg Message) { if h.pa.TimestampStart == 0 { h.pa.TimestampStart = m.Timestamp } - if h.pa.TimestampStart + AGGR_TIME <= m.Timestamp { + if h.pa.TimestampStart+AGGR_TIME <= m.Timestamp { h.build(m.Timestamp) } switch m.Name { @@ -96,8 +95,8 @@ func (h *performanceAggregator) HandleMessage(msg Message) { if m.Value > h.pa.MaxBattery { h.pa.MaxBattery = m.Value } - } + } case *IOSSessionEnd: h.build(m.Timestamp) } -} \ No newline at end of file +} diff --git a/backend/services/db/heuristics/readyMessageStore.go b/backend/internal/heuristics/readyMessageStore.go similarity index 88% rename from backend/services/db/heuristics/readyMessageStore.go rename to backend/internal/heuristics/readyMessageStore.go index 9c619e20b..bbe77585d 100644 --- a/backend/services/db/heuristics/readyMessageStore.go +++ b/backend/internal/heuristics/readyMessageStore.go @@ -1,10 +1,9 @@ package heuristics import ( - . "openreplay/backend/pkg/messages" + . "openreplay/backend/pkg/messages" ) - type readyMessageStore struct { store []Message } @@ -18,4 +17,4 @@ func (s *readyMessageStore) IterateReadyMessages(cb func(msg Message)) { cb(msg) } s.store = nil -} \ No newline at end of file +} diff --git a/backend/services/db/heuristics/session.go b/backend/internal/heuristics/session.go similarity index 86% rename from backend/services/db/heuristics/session.go rename to backend/internal/heuristics/session.go index d828ca478..3946bf918 100644 --- a/backend/services/db/heuristics/session.go +++ b/backend/internal/heuristics/session.go @@ -1,18 +1,16 @@ package heuristics import ( - . "openreplay/backend/pkg/messages" - . "openreplay/backend/pkg/db/types" + . "openreplay/backend/pkg/db/types" + . "openreplay/backend/pkg/messages" ) - type sessHandler struct { - session *Session + session *Session handlers []Handler - ended bool + ended bool } - func newSessHandler(session *Session) *sessHandler { return &sessHandler{ session: session, @@ -44,4 +42,4 @@ func (s *sessHandler) IterateReadyMessages(cb func(msg Message)) { func (s *sessHandler) IsEnded() bool { return s.ended -} \ No newline at end of file +} diff --git a/backend/internal/ios/ios-device.go b/backend/internal/ios/ios-device.go new file mode 100644 index 000000000..8df33035b --- /dev/null +++ b/backend/internal/ios/ios-device.go @@ -0,0 +1,138 @@ +package ios + +import ( + "strings" +) + +func MapIOSDevice(identifier string) string { + switch identifier { + case "iPod5,1": + return "iPod touch (5th generation)" + case "iPod7,1": + return "iPod touch (6th generation)" + case "iPod9,1": + return "iPod touch (7th generation)" + case "iPhone3,1", "iPhone3,2", "iPhone3,3": + return "iPhone 4" + case "iPhone4,1": + return "iPhone 4s" + case "iPhone5,1", "iPhone5,2": + return "iPhone 5" + case "iPhone5,3", "iPhone5,4": + return "iPhone 5c" + case "iPhone6,1", "iPhone6,2": + return "iPhone 5s" + case "iPhone7,2": + return "iPhone 6" + case "iPhone7,1": + return "iPhone 6 Plus" + case "iPhone8,1": + return "iPhone 6s" + case "iPhone8,2": + return "iPhone 6s Plus" + case "iPhone8,4": + return "iPhone SE" + case "iPhone9,1", "iPhone9,3": + return "iPhone 7" + case "iPhone9,2", "iPhone9,4": + return "iPhone 7 Plus" + case "iPhone10,1", "iPhone10,4": + return "iPhone 8" + case "iPhone10,2", "iPhone10,5": + return "iPhone 8 Plus" + case "iPhone10,3", "iPhone10,6": + return "iPhone X" + case "iPhone11,2": + return "iPhone XS" + case "iPhone11,4", "iPhone11,6": + return "iPhone XS Max" + case "iPhone11,8": + return "iPhone XR" + case "iPhone12,1": + return "iPhone 11" + case "iPhone12,3": + return "iPhone 11 Pro" + case "iPhone12,5": + return "iPhone 11 Pro Max" + case "iPhone12,8": + return "iPhone SE (2nd generation)" + case "iPhone13,1": + return "iPhone 12 mini" + case "iPhone13,2": + return "iPhone 12" + case "iPhone13,3": + return "iPhone 12 Pro" + case "iPhone13,4": + return "iPhone 12 Pro Max" + case "iPad2,1", "iPad2,2", "iPad2,3", "iPad2,4": + return "iPad 2" + case "iPad3,1", "iPad3,2", "iPad3,3": + return "iPad (3rd generation)" + case "iPad3,4", "iPad3,5", "iPad3,6": + return "iPad (4th generation)" + case "iPad6,11", "iPad6,12": + return "iPad (5th generation)" + case "iPad7,5", "iPad7,6": + return "iPad (6th generation)" + case "iPad7,11", "iPad7,12": + return "iPad (7th generation)" + case "iPad11,6", "iPad11,7": + return "iPad (8th generation)" + case "iPad4,1", "iPad4,2", "iPad4,3": + return "iPad Air" + case "iPad5,3", "iPad5,4": + return "iPad Air 2" + case "iPad11,3", "iPad11,4": + return "iPad Air (3rd generation)" + case "iPad13,1", "iPad13,2": + return "iPad Air (4th generation)" + case "iPad2,5", "iPad2,6", "iPad2,7": + return "iPad mini" + case "iPad4,4", "iPad4,5", "iPad4,6": + return "iPad mini 2" + case "iPad4,7", "iPad4,8", "iPad4,9": + return "iPad mini 3" + case "iPad5,1", "iPad5,2": + return "iPad mini 4" + case "iPad11,1", "iPad11,2": + return "iPad mini (5th generation)" + case "iPad6,3", "iPad6,4": + return "iPad Pro (9.7-inch)" + case "iPad7,3", "iPad7,4": + return "iPad Pro (10.5-inch)" + case "iPad8,1", "iPad8,2", "iPad8,3", "iPad8,4": + return "iPad Pro (11-inch) (1st generation)" + case "iPad8,9", "iPad8,10": + return "iPad Pro (11-inch) (2nd generation)" + case "iPad6,7", "iPad6,8": + return "iPad Pro (12.9-inch) (1st generation)" + case "iPad7,1", "iPad7,2": + return "iPad Pro (12.9-inch) (2nd generation)" + case "iPad8,5", "iPad8,6", "iPad8,7", "iPad8,8": + return "iPad Pro (12.9-inch) (3rd generation)" + case "iPad8,11", "iPad8,12": + return "iPad Pro (12.9-inch) (4th generation)" + case "AppleTV5,3": + return "Apple TV" + case "AppleTV6,2": + return "Apple TV 4K" + case "AudioAccessory1,1": + return "HomePod" + case "AudioAccessory5,1": + return "HomePod mini" + case "i386", "x86_64": + return "Simulator" + default: + return identifier + } +} + +func GetIOSDeviceType(identifier string) string { + if strings.Contains(identifier, "iPhone") { + return "mobile" //"phone" + } + if strings.Contains(identifier, "iPad") { + return "tablet" + } + return "other" +} diff --git a/backend/internal/router/handlers-ios.go b/backend/internal/router/handlers-ios.go new file mode 100644 index 000000000..50f92a6ad --- /dev/null +++ b/backend/internal/router/handlers-ios.go @@ -0,0 +1,172 @@ +package router + +import ( + "encoding/json" + "errors" + "log" + "math/rand" + "net/http" + "openreplay/backend/internal/ios" + "openreplay/backend/internal/uuid" + "strconv" + "time" + + "openreplay/backend/pkg/db/postgres" + . "openreplay/backend/pkg/messages" + "openreplay/backend/pkg/token" +) + +func (e *Router) startSessionHandlerIOS(w http.ResponseWriter, r *http.Request) { + startTime := time.Now() + req := &StartIOSSessionRequest{} + + if r.Body == nil { + ResponseWithError(w, http.StatusBadRequest, errors.New("request body is empty")) + return + } + body := http.MaxBytesReader(w, r.Body, e.cfg.JsonSizeLimit) + defer body.Close() + + if err := json.NewDecoder(body).Decode(req); err != nil { + ResponseWithError(w, http.StatusBadRequest, err) + return + } + + if req.ProjectKey == nil { + ResponseWithError(w, http.StatusForbidden, errors.New("ProjectKey value required")) + return + } + + p, err := e.services.Database.GetProjectByKey(*req.ProjectKey) + if err != nil { + if postgres.IsNoRowsErr(err) { + ResponseWithError(w, http.StatusNotFound, errors.New("Project doesn't exist or is not active")) + } else { + ResponseWithError(w, http.StatusInternalServerError, err) // TODO: send error here only on staging + } + return + } + userUUID := uuid.GetUUID(req.UserUUID) + tokenData, err := e.services.Tokenizer.Parse(req.Token) + + if err != nil { // Starting the new one + dice := byte(rand.Intn(100)) // [0, 100) + if dice >= p.SampleRate { + ResponseWithError(w, http.StatusForbidden, errors.New("cancel")) + return + } + + ua := e.services.UaParser.ParseFromHTTPRequest(r) + if ua == nil { + ResponseWithError(w, http.StatusForbidden, errors.New("browser not recognized")) + return + } + sessionID, err := e.services.Flaker.Compose(uint64(startTime.UnixMilli())) + 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{sessionID, 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{ + Timestamp: req.Timestamp, + ProjectID: uint64(p.ProjectID), + TrackerVersion: req.TrackerVersion, + RevID: req.RevID, + UserUUID: userUUID, + UserOS: "IOS", + UserOSVersion: req.UserOSVersion, + UserDevice: ios.MapIOSDevice(req.UserDevice), + UserDeviceType: ios.GetIOSDeviceType(req.UserDevice), + UserCountry: country, + })) + } + + ResponseWithJSON(w, &StartIOSSessionResponse{ + Token: e.services.Tokenizer.Compose(*tokenData), + UserUUID: userUUID, + SessionID: strconv.FormatUint(tokenData.ID, 10), + BeaconSizeLimit: e.cfg.BeaconSizeLimit, + }) +} + +func (e *Router) pushMessagesHandlerIOS(w http.ResponseWriter, r *http.Request) { + sessionData, err := e.services.Tokenizer.ParseFromHTTPRequest(r) + if err != nil { + ResponseWithError(w, http.StatusUnauthorized, err) + return + } + e.pushMessages(w, r, sessionData.ID, e.cfg.TopicRawIOS) +} + +func (e *Router) pushLateMessagesHandlerIOS(w http.ResponseWriter, r *http.Request) { + sessionData, err := e.services.Tokenizer.ParseFromHTTPRequest(r) + if err != nil && err != token.EXPIRED { + ResponseWithError(w, http.StatusUnauthorized, err) + return + } + // Check timestamps here? + e.pushMessages(w, r, sessionData.ID, e.cfg.TopicRawIOS) +} + +func (e *Router) imagesUploadHandlerIOS(w http.ResponseWriter, r *http.Request) { + log.Printf("recieved imagerequest") + + sessionData, err := e.services.Tokenizer.ParseFromHTTPRequest(r) + if err != nil { // Should accept expired token? + ResponseWithError(w, http.StatusUnauthorized, err) + return + } + + if r.Body == nil { + ResponseWithError(w, http.StatusBadRequest, errors.New("request body is empty")) + return + } + r.Body = http.MaxBytesReader(w, r.Body, e.cfg.FileSizeLimit) + defer r.Body.Close() + + err = r.ParseMultipartForm(1e6) // ~1Mb + if err == http.ErrNotMultipart || err == http.ErrMissingBoundary { + ResponseWithError(w, http.StatusUnsupportedMediaType, err) + return + // } else if err == multipart.ErrMessageTooLarge // if non-files part exceeds 10 MB + } else if err != nil { + ResponseWithError(w, http.StatusInternalServerError, err) // TODO: send error here only on staging + return + } + + if r.MultipartForm == nil { + ResponseWithError(w, http.StatusInternalServerError, errors.New("Multipart not parsed")) + return + } + + if len(r.MultipartForm.Value["projectKey"]) == 0 { + ResponseWithError(w, http.StatusBadRequest, errors.New("projectKey parameter missing")) // status for missing/wrong parameter? + return + } + + prefix := r.MultipartForm.Value["projectKey"][0] + "/" + strconv.FormatUint(sessionData.ID, 10) + "/" + + for _, fileHeaderList := range r.MultipartForm.File { + for _, fileHeader := range fileHeaderList { + file, err := fileHeader.Open() + if err != nil { + continue // TODO: send server error or accumulate successful files + } + key := prefix + fileHeader.Filename + log.Printf("Uploading image... %v", key) + go func() { //TODO: mime type from header + if err := e.services.Storage.Upload(file, key, "image/jpeg", false); err != nil { + log.Printf("Upload ios screen error. %v", err) + } + }() + } + } + + w.WriteHeader(http.StatusOK) +} diff --git a/backend/internal/router/handlers-web.go b/backend/internal/router/handlers-web.go new file mode 100644 index 000000000..fc7c6421d --- /dev/null +++ b/backend/internal/router/handlers-web.go @@ -0,0 +1,187 @@ +package router + +import ( + "bytes" + "encoding/json" + "errors" + "log" + "math/rand" + "net/http" + "openreplay/backend/internal/uuid" + "strconv" + "time" + + "openreplay/backend/pkg/db/postgres" + . "openreplay/backend/pkg/messages" + "openreplay/backend/pkg/token" +) + +func (e *Router) startSessionHandlerWeb(w http.ResponseWriter, r *http.Request) { + startTime := time.Now() + + // Check request body + if r.Body == nil { + ResponseWithError(w, http.StatusBadRequest, errors.New("request body is empty")) + return + } + body := http.MaxBytesReader(w, r.Body, e.cfg.JsonSizeLimit) + defer body.Close() + + // Parse request body + req := &StartSessionRequest{} + if err := json.NewDecoder(body).Decode(req); err != nil { + ResponseWithError(w, http.StatusBadRequest, err) + return + } + + // Handler's logic + if req.ProjectKey == nil { + ResponseWithError(w, http.StatusForbidden, errors.New("ProjectKey value required")) + return + } + + p, err := e.services.Database.GetProjectByKey(*req.ProjectKey) + if err != nil { + if postgres.IsNoRowsErr(err) { + ResponseWithError(w, http.StatusNotFound, errors.New("Project doesn't exist or capture limit has been reached")) + } else { + ResponseWithError(w, http.StatusInternalServerError, err) // TODO: send error here only on staging + } + return + } + + userUUID := uuid.GetUUID(req.UserUUID) + tokenData, err := e.services.Tokenizer.Parse(req.Token) + if err != nil || req.Reset { // Starting the new one + dice := byte(rand.Intn(100)) // [0, 100) + if dice >= p.SampleRate { + ResponseWithError(w, http.StatusForbidden, errors.New("cancel")) + return + } + + ua := e.services.UaParser.ParseFromHTTPRequest(r) + if ua == nil { + ResponseWithError(w, http.StatusForbidden, errors.New("browser not recognized")) + return + } + sessionID, err := e.services.Flaker.Compose(uint64(startTime.UnixNano() / 1e6)) + 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.UnixNano() / 1e6} + + e.services.Producer.Produce(e.cfg.TopicRawWeb, tokenData.ID, Encode(&SessionStart{ + Timestamp: req.Timestamp, + ProjectID: uint64(p.ProjectID), + TrackerVersion: req.TrackerVersion, + RevID: req.RevID, + UserUUID: userUUID, + UserAgent: r.Header.Get("User-Agent"), + UserOS: ua.OS, + UserOSVersion: ua.OSVersion, + UserBrowser: ua.Browser, + UserBrowserVersion: ua.BrowserVersion, + UserDevice: ua.Device, + UserDeviceType: ua.DeviceType, + UserCountry: e.services.GeoIP.ExtractISOCodeFromHTTPRequest(r), + UserDeviceMemorySize: req.DeviceMemory, + UserDeviceHeapSize: req.JsHeapSizeLimit, + UserID: req.UserID, + })) + } + + ResponseWithJSON(w, &StartSessionResponse{ + Token: e.services.Tokenizer.Compose(*tokenData), + UserUUID: userUUID, + SessionID: strconv.FormatUint(tokenData.ID, 10), + BeaconSizeLimit: e.cfg.BeaconSizeLimit, + }) +} + +func (e *Router) pushMessagesHandlerWeb(w http.ResponseWriter, r *http.Request) { + // Check authorization + sessionData, err := e.services.Tokenizer.ParseFromHTTPRequest(r) + if err != nil { + ResponseWithError(w, http.StatusUnauthorized, err) + return + } + + // Check request body + if r.Body == nil { + ResponseWithError(w, http.StatusBadRequest, errors.New("request body is empty")) + return + } + body := http.MaxBytesReader(w, r.Body, e.cfg.BeaconSizeLimit) + defer body.Close() + + var handledMessages bytes.Buffer + + // Process each message in request data + err = ReadBatchReader(body, func(msg Message) { + msg = e.services.Assets.ParseAssets(sessionData.ID, msg) + handledMessages.Write(msg.Encode()) + }) + if err != nil { + ResponseWithError(w, http.StatusForbidden, err) + return + } + + // Send processed messages to queue as array of bytes + err = e.services.Producer.Produce(e.cfg.TopicRawWeb, sessionData.ID, handledMessages.Bytes()) + if err != nil { + log.Printf("can't send processed messages to queue: %s", err) + } + + w.WriteHeader(http.StatusOK) +} + +func (e *Router) notStartedHandlerWeb(w http.ResponseWriter, r *http.Request) { + // Check request body + if r.Body == nil { + ResponseWithError(w, http.StatusBadRequest, errors.New("request body is empty")) + return + } + body := http.MaxBytesReader(w, r.Body, e.cfg.JsonSizeLimit) + defer body.Close() + + // Parse request body + req := &NotStartedRequest{} + if err := json.NewDecoder(body).Decode(req); err != nil { + ResponseWithError(w, http.StatusBadRequest, err) + return + } + + // Handler's logic + if req.ProjectKey == nil { + ResponseWithError(w, http.StatusForbidden, errors.New("ProjectKey value required")) + return + } + ua := e.services.UaParser.ParseFromHTTPRequest(r) // TODO?: insert anyway + if ua == nil { + ResponseWithError(w, http.StatusForbidden, errors.New("browser not recognized")) + return + } + country := e.services.GeoIP.ExtractISOCodeFromHTTPRequest(r) + err := e.services.Database.InsertUnstartedSession(postgres.UnstartedSession{ + ProjectKey: *req.ProjectKey, + TrackerVersion: req.TrackerVersion, + DoNotTrack: req.DoNotTrack, + Platform: "web", + UserAgent: r.Header.Get("User-Agent"), + UserOS: ua.OS, + UserOSVersion: ua.OSVersion, + UserBrowser: ua.Browser, + UserBrowserVersion: ua.BrowserVersion, + UserDevice: ua.Device, + UserDeviceType: ua.DeviceType, + UserCountry: country, + }) + if err != nil { + log.Printf("Unable to insert Unstarted Session: %v\n", err) + } + + w.WriteHeader(http.StatusOK) +} diff --git a/backend/services/http/handlers.go b/backend/internal/router/handlers.go similarity index 56% rename from backend/services/http/handlers.go rename to backend/internal/router/handlers.go index dd73925af..34a7a990d 100644 --- a/backend/services/http/handlers.go +++ b/backend/internal/router/handlers.go @@ -1,28 +1,27 @@ -package main +package router import ( + gzip "github.com/klauspost/pgzip" "io" "io/ioutil" "log" "net/http" - - gzip "github.com/klauspost/pgzip" ) -const JSON_SIZE_LIMIT int64 = 1e3 // 1Kb - -func pushMessages(w http.ResponseWriter, r *http.Request, sessionID uint64, topicName string) { - body := http.MaxBytesReader(w, r.Body, BEACON_SIZE_LIMIT) +func (e *Router) pushMessages(w http.ResponseWriter, r *http.Request, sessionID uint64, topicName string) { + body := http.MaxBytesReader(w, r.Body, e.cfg.BeaconSizeLimit) defer body.Close() + var reader io.ReadCloser var err error + switch r.Header.Get("Content-Encoding") { case "gzip": log.Println("Gzip", reader) reader, err = gzip.NewReader(body) if err != nil { - responseWithError(w, http.StatusInternalServerError, err) // TODO: stage-dependent responce + ResponseWithError(w, http.StatusInternalServerError, err) // TODO: stage-dependent response return } log.Println("Gzip reader init", reader) @@ -33,9 +32,9 @@ func pushMessages(w http.ResponseWriter, r *http.Request, sessionID uint64, topi log.Println("Reader after switch:", reader) buf, err := ioutil.ReadAll(reader) if err != nil { - responseWithError(w, http.StatusInternalServerError, err) // TODO: send error here only on staging + ResponseWithError(w, http.StatusInternalServerError, err) // TODO: send error here only on staging return } - producer.Produce(topicName, sessionID, buf) // What if not able to send? + e.services.Producer.Produce(topicName, sessionID, buf) // What if not able to send? w.WriteHeader(http.StatusOK) } diff --git a/backend/internal/router/model.go b/backend/internal/router/model.go new file mode 100644 index 000000000..b39c49688 --- /dev/null +++ b/backend/internal/router/model.go @@ -0,0 +1,49 @@ +package router + +type StartSessionRequest struct { + Token string `json:"token"` + UserUUID *string `json:"userUUID"` + RevID string `json:"revID"` + Timestamp uint64 `json:"timestamp"` + TrackerVersion string `json:"trackerVersion"` + IsSnippet bool `json:"isSnippet"` + DeviceMemory uint64 `json:"deviceMemory"` + JsHeapSizeLimit uint64 `json:"jsHeapSizeLimit"` + ProjectKey *string `json:"projectKey"` + Reset bool `json:"reset"` + UserID string `json:"userID"` +} + +type StartSessionResponse struct { + Timestamp int64 `json:"timestamp"` + Delay int64 `json:"delay"` + Token string `json:"token"` + UserUUID string `json:"userUUID"` + SessionID string `json:"sessionID"` + BeaconSizeLimit int64 `json:"beaconSizeLimit"` +} + +type NotStartedRequest struct { + ProjectKey *string `json:"projectKey"` + TrackerVersion string `json:"trackerVersion"` + DoNotTrack bool `json:"DoNotTrack"` +} + +type StartIOSSessionRequest struct { + Token string `json:"token"` + ProjectKey *string `json:"projectKey"` + TrackerVersion string `json:"trackerVersion"` + RevID string `json:"revID"` + UserUUID *string `json:"userUUID"` + UserOSVersion string `json:"userOSVersion"` + UserDevice string `json:"userDevice"` + Timestamp uint64 `json:"timestamp"` +} + +type StartIOSSessionResponse struct { + Token string `json:"token"` + ImagesHashList []string `json:"imagesHashList"` + UserUUID string `json:"userUUID"` + BeaconSizeLimit int64 `json:"beaconSizeLimit"` + SessionID string `json:"sessionID"` +} diff --git a/backend/services/http/response.go b/backend/internal/router/response.go similarity index 59% rename from backend/services/http/response.go rename to backend/internal/router/response.go index 11d9b328d..0b4725419 100644 --- a/backend/services/http/response.go +++ b/backend/internal/router/response.go @@ -1,4 +1,4 @@ -package main +package router import ( "encoding/json" @@ -6,7 +6,7 @@ import ( "net/http" ) -func responseWithJSON(w http.ResponseWriter, res interface{}) { +func ResponseWithJSON(w http.ResponseWriter, res interface{}) { body, err := json.Marshal(res) if err != nil { log.Println(err) @@ -15,10 +15,10 @@ func responseWithJSON(w http.ResponseWriter, res interface{}) { w.Write(body) } -func responseWithError(w http.ResponseWriter, code int, err error) { +func ResponseWithError(w http.ResponseWriter, code int, err error) { type response struct { Error string `json:"error"` } w.WriteHeader(code) - responseWithJSON(w, &response{err.Error()}) + ResponseWithJSON(w, &response{err.Error()}) } diff --git a/backend/internal/router/router.go b/backend/internal/router/router.go new file mode 100644 index 000000000..145630351 --- /dev/null +++ b/backend/internal/router/router.go @@ -0,0 +1,70 @@ +package router + +import ( + "github.com/gorilla/mux" + "log" + "net/http" + "openreplay/backend/internal/config" + http2 "openreplay/backend/internal/services" +) + +type Router struct { + router *mux.Router + cfg *config.Config + services *http2.ServicesBuilder +} + +func NewRouter(cfg *config.Config, services *http2.ServicesBuilder) (*Router, error) { + e := &Router{ + cfg: cfg, + services: services, + } + e.init() + return e, nil +} + +func (e *Router) init() { + e.router = mux.NewRouter() + // Root path + e.router.HandleFunc("/", e.root).Methods("POST") + + // Web handlers + e.router.HandleFunc("/v1/web/not-started", e.notStartedHandlerWeb).Methods("POST") + e.router.HandleFunc("/v1/web/start", e.startSessionHandlerWeb).Methods("POST") + e.router.HandleFunc("/v1/web/i", e.pushMessagesHandlerWeb).Methods("POST") + + // iOS handlers + e.router.HandleFunc("/v1/ios/start", e.startSessionHandlerIOS).Methods("POST") + e.router.HandleFunc("/v1/ios/i", e.pushMessagesHandlerIOS).Methods("POST") + e.router.HandleFunc("/v1/ios/late", e.pushLateMessagesHandlerIOS).Methods("POST") + e.router.HandleFunc("/v1/ios/images", e.imagesUploadHandlerIOS).Methods("POST") + + // CORS middleware + e.router.Use(e.corsMiddleware) +} + +func (e *Router) root(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) +} + +func (e *Router) corsMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Prepare headers for preflight requests + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Methods", "POST") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type,Authorization") + if r.Method == http.MethodOptions { + w.Header().Set("Cache-Control", "max-age=86400") + w.WriteHeader(http.StatusOK) + return + } + log.Printf("Request: %v - %v ", r.Method, r.URL.Path) + + // Serve request + next.ServeHTTP(w, r) + }) +} + +func (e *Router) GetHandler() http.Handler { + return e.router +} diff --git a/backend/internal/server/server.go b/backend/internal/server/server.go new file mode 100644 index 000000000..2670ba537 --- /dev/null +++ b/backend/internal/server/server.go @@ -0,0 +1,46 @@ +package server + +import ( + "context" + "errors" + "fmt" + "golang.org/x/net/http2" + "log" + "net/http" + "time" +) + +type Server struct { + server *http.Server +} + +func New(handler http.Handler, host, port string, timeout time.Duration) (*Server, error) { + switch { + case port == "": + return nil, errors.New("empty server port") + case handler == nil: + return nil, errors.New("empty handler") + case timeout < 1: + return nil, fmt.Errorf("invalid timeout %d", timeout) + } + server := &http.Server{ + Addr: fmt.Sprintf("%s:%s", host, port), + Handler: handler, + ReadTimeout: timeout, + WriteTimeout: timeout, + } + if err := http2.ConfigureServer(server, nil); err != nil { + log.Printf("can't configure http2 server: %s", err) + } + return &Server{ + server: server, + }, nil +} + +func (s *Server) Start() error { + return s.server.ListenAndServe() +} + +func (s *Server) Stop() { + s.server.Shutdown(context.Background()) +} diff --git a/backend/internal/services/services.go b/backend/internal/services/services.go new file mode 100644 index 000000000..5b84e1dfb --- /dev/null +++ b/backend/internal/services/services.go @@ -0,0 +1,39 @@ +package services + +import ( + "openreplay/backend/internal/assetscache" + "openreplay/backend/internal/config" + "openreplay/backend/internal/geoip" + "openreplay/backend/internal/uaparser" + "openreplay/backend/pkg/db/cache" + "openreplay/backend/pkg/flakeid" + "openreplay/backend/pkg/queue/types" + "openreplay/backend/pkg/storage" + "openreplay/backend/pkg/token" + "openreplay/backend/pkg/url/assets" +) + +type ServicesBuilder struct { + Database *cache.PGCache + Producer types.Producer + Assets *assetscache.AssetsCache + Flaker *flakeid.Flaker + UaParser *uaparser.UAParser + GeoIP *geoip.GeoIP + Tokenizer *token.Tokenizer + Storage *storage.S3 +} + +func New(cfg *config.Config, producer types.Producer, pgconn *cache.PGCache) *ServicesBuilder { + rewriter := assets.NewRewriter(cfg.AssetsOrigin) + return &ServicesBuilder{ + Database: pgconn, + Producer: producer, + Assets: assetscache.New(cfg, rewriter, producer), + Storage: storage.NewS3(cfg.AWSRegion, cfg.S3BucketIOSImages), + Tokenizer: token.NewTokenizer(cfg.TokenSecret), + UaParser: uaparser.NewUAParser(cfg.UAParserFile), + GeoIP: geoip.NewGeoIP(cfg.MaxMinDBFile), + Flaker: flakeid.NewFlaker(cfg.WorkerID), + } +} diff --git a/backend/services/http/uaparser/http.go b/backend/internal/uaparser/http.go similarity index 100% rename from backend/services/http/uaparser/http.go rename to backend/internal/uaparser/http.go diff --git a/backend/services/http/uaparser/uaparser.go b/backend/internal/uaparser/uaparser.go similarity index 100% rename from backend/services/http/uaparser/uaparser.go rename to backend/internal/uaparser/uaparser.go diff --git a/backend/services/http/uuid.go b/backend/internal/uuid/uuid.go similarity index 75% rename from backend/services/http/uuid.go rename to backend/internal/uuid/uuid.go index 13f57bff0..44dd76827 100644 --- a/backend/services/http/uuid.go +++ b/backend/internal/uuid/uuid.go @@ -1,10 +1,10 @@ -package main +package uuid import ( "github.com/google/uuid" ) -func getUUID(u *string) string { +func GetUUID(u *string) string { if u != nil { _, err := uuid.Parse(*u) if err == nil { @@ -12,4 +12,4 @@ func getUUID(u *string) string { } } return uuid.New().String() -} \ No newline at end of file +} diff --git a/backend/pkg/dev/profiling/profiling.go b/backend/pkg/dev/profiling/profiling.go index 139aaeac6..c05c47549 100644 --- a/backend/pkg/dev/profiling/profiling.go +++ b/backend/pkg/dev/profiling/profiling.go @@ -1,24 +1,23 @@ package profiling import ( - "log" - "net/http" - "github.com/gorilla/mux" - _ "net/http/pprof" -) + "github.com/gorilla/mux" + "log" + "net/http" + _ "net/http/pprof" +) func Profile() { go func() { - router := mux.NewRouter() - router.PathPrefix("/debug/pprof/").Handler(http.DefaultServeMux) - log.Println("Starting profiler...") - if err := http.ListenAndServe(":6060", router); err != nil { - panic(err) - } + router := mux.NewRouter() + router.PathPrefix("/debug/pprof/").Handler(http.DefaultServeMux) + log.Println("Starting profiler...") + if err := http.ListenAndServe(":6060", router); err != nil { + panic(err) + } }() } - /* docker run -p 6060:6060 -e REQUIRED_ENV=http://value -e ANOTHER_ENV=anothervalue workername @@ -34,4 +33,4 @@ go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30 THEN https://www.speedscope.app/ -*/ \ No newline at end of file +*/ diff --git a/backend/pkg/env/aws.go b/backend/pkg/env/aws.go index 6573c8551..cb7445797 100644 --- a/backend/pkg/env/aws.go +++ b/backend/pkg/env/aws.go @@ -19,7 +19,7 @@ func AWSSessionOnRegion(region string) *_session.Session { if AWS_ENDPOINT != "" { config.Endpoint = aws.String(AWS_ENDPOINT) config.DisableSSL = aws.Bool(true) - config.S3ForcePathStyle = aws.Bool(true) + config.S3ForcePathStyle = aws.Bool(true) } aws_session, err := _session.NewSession(config) if err != nil { diff --git a/backend/pkg/env/vars.go b/backend/pkg/env/vars.go index 33ae9da3c..eb88b3c6b 100644 --- a/backend/pkg/env/vars.go +++ b/backend/pkg/env/vars.go @@ -22,7 +22,7 @@ func Uint64(key string) uint64 { v := String(key) n, err := strconv.ParseUint(v, 10, 64) if err != nil { - log.Fatalln(key + " has a wrong value. ", err) + log.Fatalln(key+" has a wrong value. ", err) } return n } @@ -31,12 +31,13 @@ func Uint16(key string) uint16 { v := String(key) n, err := strconv.ParseUint(v, 10, 16) if err != nil { - log.Fatalln(key + " has a wrong value. ", err) + log.Fatalln(key+" has a wrong value. ", err) } return uint16(n) } const MAX_INT = uint64(^uint(0) >> 1) + func Int(key string) int { val := Uint64(key) if val > MAX_INT { @@ -54,4 +55,4 @@ func Bool(key string) bool { return true } return false -} \ No newline at end of file +} diff --git a/backend/pkg/env/worker-id.go b/backend/pkg/env/worker-id.go index 47fdffc43..22d077832 100644 --- a/backend/pkg/env/worker-id.go +++ b/backend/pkg/env/worker-id.go @@ -5,9 +5,9 @@ import ( ) func hashHostname(hostname string) uint16 { - var h uint16 ; + var h uint16 for i, b := range hostname { - h += uint16(i+1)*uint16(b) + h += uint16(i+1) * uint16(b) } return h } diff --git a/backend/pkg/flakeid/flakeid.go b/backend/pkg/flakeid/flakeid.go index 13e064896..c54b990a5 100644 --- a/backend/pkg/flakeid/flakeid.go +++ b/backend/pkg/flakeid/flakeid.go @@ -8,7 +8,7 @@ const ( TIMESTAMP_MAX = 1< m.Timestamp || prt.mints == 0 { + prt.mints = m.Timestamp + } + prt.lastts = m.Timestamp + prt.lastID = m.ID + prt.count += 1 } type queueStats struct { - prts map[int32]*partitionStats - tick <-chan time.Time + prts map[int32]*partitionStats + tick <-chan time.Time } -func NewQueueStats(sec int)*queueStats { - return &queueStats{ - prts: make(map[int32]*partitionStats), - tick: time.Tick(time.Duration(sec) * time.Second), - } +func NewQueueStats(sec int) *queueStats { + return &queueStats{ + prts: make(map[int32]*partitionStats), + tick: time.Tick(time.Duration(sec) * time.Second), + } } -func (qs *queueStats) HandleAndLog(sessionID uint64, m *types.Meta) { - prti := int32(sessionID % 16) // TODO use GetKeyPartition from kafka/key.go - prt, ok := qs.prts[prti] - if !ok { - qs.prts[prti] = &partitionStats{} - prt = qs.prts[prti] - } +// 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 + prt, ok := qs.prts[prti] + if !ok { + qs.prts[prti] = &partitionStats{} + prt = qs.prts[prti] + } + prt.update(m) - if prt.maxts < m.Timestamp { - prt.maxts = m.Timestamp - } - if prt.mints > m.Timestamp || prt.mints == 0 { - prt.mints = m.Timestamp - } - prt.lastts = m.Timestamp - prt.lastID = m.ID - prt.count += 1 - - - select { - case <-qs.tick: - qs.LogThenReset() - default: - } + select { + case <-qs.tick: + qs.log() + qs.reset() + default: + } } - -func (qs *queueStats) LogThenReset() { - s := "Queue Statistics: " - for i, p := range qs.prts { - s = fmt.Sprintf("%v | %v:: lastTS %v, lastID %v, count %v, maxTS %v, minTS %v", - s, i, p.lastts, p.lastID, p.count, p.maxts, p.mints) - } - log.Println(s) - // reset - qs.prts = make(map[int32]*partitionStats) +// Print to console collected statistics +func (qs *queueStats) log() { + s := "Queue Statistics: " + for i, p := range qs.prts { + s = fmt.Sprintf("%v | %v:: lastTS %v, lastID %v, count %v, maxTS %v, minTS %v", + s, i, p.lastts, p.lastID, p.count, p.maxts, p.mints) + } + log.Println(s) } - -// TODO: list of message id to log (mb filter function with callback in messages/utils.go or something) -func LogMessage(s string, sessionID uint64, msg messages.Message, m *types.Meta) { - log.Printf("%v | SessionID: %v, Queue info: %v, Message: %v", s, sessionID, m, msg) +// Clear all queue partitions +func (qs *queueStats) reset() { + qs.prts = make(map[int32]*partitionStats) } - diff --git a/backend/pkg/messages/batch.go b/backend/pkg/messages/batch.go index 9241672a3..850f22de9 100644 --- a/backend/pkg/messages/batch.go +++ b/backend/pkg/messages/batch.go @@ -1,17 +1,12 @@ package messages import ( - "bytes" "io" "github.com/pkg/errors" ) -func ReadBatch(b []byte, callback func(Message)) error { - return ReadBatchReader(bytes.NewReader(b), callback) -} - -func ReadBatchReader(reader io.Reader, callback func(Message)) error { +func ReadBatchReader(reader io.Reader, messageHandler func(Message)) error { var index uint64 var timestamp int64 for { @@ -21,7 +16,7 @@ func ReadBatchReader(reader io.Reader, callback func(Message)) error { } else if err != nil { return errors.Wrapf(err, "Batch Message decoding error on message with index %v", index) } - msg = transformDepricated(msg) + msg = transformDeprecated(msg) isBatchMeta := false switch m := msg.(type) { @@ -48,37 +43,11 @@ func ReadBatchReader(reader io.Reader, callback func(Message)) error { } msg.Meta().Index = index msg.Meta().Timestamp = timestamp - callback(msg) + + messageHandler(msg) if !isBatchMeta { // Without that indexes will be unique anyway, though shifted by 1 because BatchMeta is not counted in tracker index++ } } return errors.New("Error of the codeflow. (Should return on EOF)") } - -const AVG_MESSAGE_SIZE = 40 // TODO: calculate OR calculate dynamically -func WriteBatch(mList []Message) []byte { - batch := make([]byte, AVG_MESSAGE_SIZE*len(mList)) - p := 0 - for _, msg := range mList { - msgBytes := msg.Encode() - if len(batch) < p+len(msgBytes) { - newBatch := make([]byte, 2*len(batch)+len(msgBytes)) - copy(newBatch, batch) - batch = newBatch - } - copy(batch[p:], msgBytes) - p += len(msgBytes) - } - return batch[:p] -} - -func RewriteBatch(reader io.Reader, rewrite func(Message) Message) ([]byte, error) { - mList := make([]Message, 0, 10) // 10? - if err := ReadBatchReader(reader, func(m Message) { - mList = append(mList, rewrite(m)) - }); err != nil { - return nil, err - } - return WriteBatch(mList), nil -} diff --git a/backend/pkg/messages/facade.go b/backend/pkg/messages/facade.go index 91d896d19..5c024f2f6 100644 --- a/backend/pkg/messages/facade.go +++ b/backend/pkg/messages/facade.go @@ -36,6 +36,6 @@ func Encode(msg Message) []byte { // } func GetMessageTypeID(b []byte) (uint64, error) { - reader := bytes.NewReader(b) + reader := bytes.NewReader(b) return ReadUint(reader) } diff --git a/backend/pkg/messages/filters.go b/backend/pkg/messages/filters.go index f43f40142..a74d49eec 100644 --- a/backend/pkg/messages/filters.go +++ b/backend/pkg/messages/filters.go @@ -1,11 +1,10 @@ // Auto-generated, do not edit package messages - -func IsReplayerType(id uint64) bool { - return 0 == id || 2 == 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 || 69 == id || 70 == id || 90 == id || 93 == id || 96 == id || 100 == id || 102 == id || 103 == id || 105 == id +func IsReplayerType(id int) bool { + return 0 == id || 2 == 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 || 69 == id || 70 == id || 90 == id || 93 == id || 96 == id || 100 == id || 102 == id || 103 == id || 105 == id } -func IsIOSType(id uint64) bool { +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 } diff --git a/backend/pkg/messages/get-timestamp.go b/backend/pkg/messages/get-timestamp.go index c8e42f756..8b44764a7 100644 --- a/backend/pkg/messages/get-timestamp.go +++ b/backend/pkg/messages/get-timestamp.go @@ -1,65 +1,63 @@ // Auto-generated, do not edit package messages - func GetTimestamp(message Message) uint64 { - switch msg := message.(type) { - - case *IOSBatchMeta: - return msg.Timestamp - - case *IOSSessionStart: - return msg.Timestamp - - case *IOSSessionEnd: - return msg.Timestamp - - case *IOSMetadata: - return msg.Timestamp - - case *IOSCustomEvent: - return msg.Timestamp - - case *IOSUserID: - return msg.Timestamp - - case *IOSUserAnonymousID: - return msg.Timestamp - - case *IOSScreenChanges: - return msg.Timestamp - - case *IOSCrash: - return msg.Timestamp - - case *IOSScreenEnter: - return msg.Timestamp - - case *IOSScreenLeave: - return msg.Timestamp - - case *IOSClickEvent: - return msg.Timestamp - - case *IOSInputEvent: - return msg.Timestamp - - case *IOSPerformanceEvent: - return msg.Timestamp - - case *IOSLog: - return msg.Timestamp - - case *IOSInternalError: - return msg.Timestamp - - case *IOSNetworkCall: - return msg.Timestamp - - case *IOSIssueEvent: - return msg.Timestamp - - } - return uint64(message.Meta().Timestamp) -} + switch msg := message.(type) { + case *IOSBatchMeta: + return msg.Timestamp + + case *IOSSessionStart: + return msg.Timestamp + + case *IOSSessionEnd: + return msg.Timestamp + + case *IOSMetadata: + return msg.Timestamp + + case *IOSCustomEvent: + return msg.Timestamp + + case *IOSUserID: + return msg.Timestamp + + case *IOSUserAnonymousID: + return msg.Timestamp + + case *IOSScreenChanges: + return msg.Timestamp + + case *IOSCrash: + return msg.Timestamp + + case *IOSScreenEnter: + return msg.Timestamp + + case *IOSScreenLeave: + return msg.Timestamp + + case *IOSClickEvent: + return msg.Timestamp + + case *IOSInputEvent: + return msg.Timestamp + + case *IOSPerformanceEvent: + return msg.Timestamp + + case *IOSLog: + return msg.Timestamp + + case *IOSInternalError: + return msg.Timestamp + + case *IOSNetworkCall: + return msg.Timestamp + + case *IOSIssueEvent: + return msg.Timestamp + + } + return uint64(message.Meta().Timestamp) +} diff --git a/backend/pkg/messages/legacy-message-transform.go b/backend/pkg/messages/legacy-message-transform.go index 637f8d443..3a42cdab0 100644 --- a/backend/pkg/messages/legacy-message-transform.go +++ b/backend/pkg/messages/legacy-message-transform.go @@ -1,32 +1,14 @@ package messages - -func transformDepricated(msg Message) Message { +func transformDeprecated(msg Message) Message { switch m := msg.(type) { case *MouseClickDepricated: - meta := m.Meta() - meta.TypeID = 33 return &MouseClick{ - meta: meta, - ID: m.ID, + ID: m.ID, HesitationTime: m.HesitationTime, - Label: m.Label, - // Selector: '', + Label: m.Label, } - // case *FetchDepricated: - // return &Fetch { - // Method: m.Method, - // URL: m.URL, - // Request: m.Request, - // Response: m.Response, - // Status: m.Status, - // Timestamp: m.Timestamp, - // Duration: m.Duration, - // // Headers: '' - // } default: - return msg + return msg } } - - diff --git a/backend/pkg/messages/message.go b/backend/pkg/messages/message.go new file mode 100644 index 000000000..d701d474f --- /dev/null +++ b/backend/pkg/messages/message.go @@ -0,0 +1,16 @@ +package messages + +type message struct { + Timestamp int64 + Index uint64 +} + +func (m *message) Meta() *message { + return m +} + +type Message interface { + Encode() []byte + TypeID() int + Meta() *message +} diff --git a/backend/pkg/messages/messages.go b/backend/pkg/messages/messages.go index 38a1f61ba..82a0f4f97 100644 --- a/backend/pkg/messages/messages.go +++ b/backend/pkg/messages/messages.go @@ -1,1694 +1,2030 @@ // Auto-generated, do not edit package messages -type Message interface { - Encode() []byte - Meta() *meta -} - -type meta struct { - Timestamp int64 - Index uint64 - TypeID uint64 -} - -// Might also implement Encode() here (?) -func (m *meta) Meta() *meta { - return m -} - type BatchMeta struct { - *meta - PageNo uint64 - FirstIndex uint64 - Timestamp int64 + message + PageNo uint64 + FirstIndex uint64 + Timestamp int64 } func (msg *BatchMeta) Encode() []byte { - buf := make([]byte, 31) - buf[0] = 80 - p := 1 - p = WriteUint(msg.PageNo, buf, p) - p = WriteUint(msg.FirstIndex, buf, p) - p = WriteInt(msg.Timestamp, buf, p) - return buf[:p] + buf := make([]byte, 31) + buf[0] = 80 + p := 1 + p = WriteUint(msg.PageNo, buf, p) + p = WriteUint(msg.FirstIndex, buf, p) + p = WriteInt(msg.Timestamp, buf, p) + return buf[:p] +} + +func (msg *BatchMeta) TypeID() int { + return 80 } type Timestamp struct { - *meta - Timestamp uint64 + message + Timestamp uint64 } func (msg *Timestamp) Encode() []byte { - buf := make([]byte, 11) - buf[0] = 0 - p := 1 - p = WriteUint(msg.Timestamp, buf, p) - return buf[:p] + buf := make([]byte, 11) + buf[0] = 0 + p := 1 + p = WriteUint(msg.Timestamp, buf, p) + return buf[:p] +} + +func (msg *Timestamp) TypeID() int { + return 0 } type SessionStart struct { - *meta - 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 + 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 + UserDeviceMemorySize uint64 + UserDeviceHeapSize uint64 + UserCountry string + UserID string } func (msg *SessionStart) Encode() []byte { - buf := make([]byte, 161+len(msg.TrackerVersion)+len(msg.RevID)+len(msg.UserUUID)+len(msg.UserAgent)+len(msg.UserOS)+len(msg.UserOSVersion)+len(msg.UserBrowser)+len(msg.UserBrowserVersion)+len(msg.UserDevice)+len(msg.UserDeviceType)+len(msg.UserCountry)+len(msg.UserID)) - buf[0] = 1 - p := 1 - p = WriteUint(msg.Timestamp, buf, p) - p = WriteUint(msg.ProjectID, buf, p) - p = WriteString(msg.TrackerVersion, buf, p) - p = WriteString(msg.RevID, buf, p) - p = WriteString(msg.UserUUID, buf, p) - p = WriteString(msg.UserAgent, buf, p) - p = WriteString(msg.UserOS, buf, p) - p = WriteString(msg.UserOSVersion, buf, p) - p = WriteString(msg.UserBrowser, buf, p) - p = WriteString(msg.UserBrowserVersion, buf, p) - p = WriteString(msg.UserDevice, buf, p) - p = WriteString(msg.UserDeviceType, buf, p) - p = WriteUint(msg.UserDeviceMemorySize, buf, p) - p = WriteUint(msg.UserDeviceHeapSize, buf, p) - p = WriteString(msg.UserCountry, buf, p) - p = WriteString(msg.UserID, buf, p) - return buf[:p] + buf := make([]byte, 161+len(msg.TrackerVersion)+len(msg.RevID)+len(msg.UserUUID)+len(msg.UserAgent)+len(msg.UserOS)+len(msg.UserOSVersion)+len(msg.UserBrowser)+len(msg.UserBrowserVersion)+len(msg.UserDevice)+len(msg.UserDeviceType)+len(msg.UserCountry)+len(msg.UserID)) + buf[0] = 1 + p := 1 + p = WriteUint(msg.Timestamp, buf, p) + p = WriteUint(msg.ProjectID, buf, p) + p = WriteString(msg.TrackerVersion, buf, p) + p = WriteString(msg.RevID, buf, p) + p = WriteString(msg.UserUUID, buf, p) + p = WriteString(msg.UserAgent, buf, p) + p = WriteString(msg.UserOS, buf, p) + p = WriteString(msg.UserOSVersion, buf, p) + p = WriteString(msg.UserBrowser, buf, p) + p = WriteString(msg.UserBrowserVersion, buf, p) + p = WriteString(msg.UserDevice, buf, p) + p = WriteString(msg.UserDeviceType, buf, p) + p = WriteUint(msg.UserDeviceMemorySize, buf, p) + p = WriteUint(msg.UserDeviceHeapSize, buf, p) + p = WriteString(msg.UserCountry, buf, p) + p = WriteString(msg.UserID, buf, p) + return buf[:p] +} + +func (msg *SessionStart) TypeID() int { + return 1 } type SessionDisconnect struct { - *meta - Timestamp uint64 + message + Timestamp uint64 } func (msg *SessionDisconnect) Encode() []byte { - buf := make([]byte, 11) - buf[0] = 2 - p := 1 - p = WriteUint(msg.Timestamp, buf, p) - return buf[:p] + buf := make([]byte, 11) + buf[0] = 2 + p := 1 + p = WriteUint(msg.Timestamp, buf, p) + return buf[:p] +} + +func (msg *SessionDisconnect) TypeID() int { + return 2 } type SessionEnd struct { - *meta - Timestamp uint64 + message + Timestamp uint64 } func (msg *SessionEnd) Encode() []byte { - buf := make([]byte, 11) - buf[0] = 3 - p := 1 - p = WriteUint(msg.Timestamp, buf, p) - return buf[:p] + buf := make([]byte, 11) + buf[0] = 3 + p := 1 + p = WriteUint(msg.Timestamp, buf, p) + return buf[:p] +} + +func (msg *SessionEnd) TypeID() int { + return 3 } type SetPageLocation struct { - *meta - URL string - Referrer string - NavigationStart uint64 + message + URL string + Referrer string + NavigationStart uint64 } func (msg *SetPageLocation) Encode() []byte { - buf := make([]byte, 31+len(msg.URL)+len(msg.Referrer)) - buf[0] = 4 - p := 1 - p = WriteString(msg.URL, buf, p) - p = WriteString(msg.Referrer, buf, p) - p = WriteUint(msg.NavigationStart, buf, p) - return buf[:p] + buf := make([]byte, 31+len(msg.URL)+len(msg.Referrer)) + buf[0] = 4 + p := 1 + p = WriteString(msg.URL, buf, p) + p = WriteString(msg.Referrer, buf, p) + p = WriteUint(msg.NavigationStart, buf, p) + return buf[:p] +} + +func (msg *SetPageLocation) TypeID() int { + return 4 } type SetViewportSize struct { - *meta - Width uint64 - Height uint64 + message + Width uint64 + Height uint64 } func (msg *SetViewportSize) Encode() []byte { - buf := make([]byte, 21) - buf[0] = 5 - p := 1 - p = WriteUint(msg.Width, buf, p) - p = WriteUint(msg.Height, buf, p) - return buf[:p] + buf := make([]byte, 21) + buf[0] = 5 + p := 1 + p = WriteUint(msg.Width, buf, p) + p = WriteUint(msg.Height, buf, p) + return buf[:p] +} + +func (msg *SetViewportSize) TypeID() int { + return 5 } type SetViewportScroll struct { - *meta - X int64 - Y int64 + message + X int64 + Y int64 } func (msg *SetViewportScroll) Encode() []byte { - buf := make([]byte, 21) - buf[0] = 6 - p := 1 - p = WriteInt(msg.X, buf, p) - p = WriteInt(msg.Y, buf, p) - return buf[:p] + buf := make([]byte, 21) + buf[0] = 6 + p := 1 + p = WriteInt(msg.X, buf, p) + p = WriteInt(msg.Y, buf, p) + return buf[:p] +} + +func (msg *SetViewportScroll) TypeID() int { + return 6 } type CreateDocument struct { - *meta + message } func (msg *CreateDocument) Encode() []byte { - buf := make([]byte, 1) - buf[0] = 7 - p := 1 + buf := make([]byte, 1) + buf[0] = 7 + p := 1 - return buf[:p] + return buf[:p] +} + +func (msg *CreateDocument) TypeID() int { + return 7 } type CreateElementNode struct { - *meta - ID uint64 - ParentID uint64 - index uint64 - Tag string - SVG bool + message + ID uint64 + ParentID uint64 + index uint64 + Tag string + SVG bool } func (msg *CreateElementNode) Encode() []byte { - buf := make([]byte, 51+len(msg.Tag)) - buf[0] = 8 - p := 1 - p = WriteUint(msg.ID, buf, p) - p = WriteUint(msg.ParentID, buf, p) - p = WriteUint(msg.index, buf, p) - p = WriteString(msg.Tag, buf, p) - p = WriteBoolean(msg.SVG, buf, p) - return buf[:p] + buf := make([]byte, 51+len(msg.Tag)) + buf[0] = 8 + p := 1 + p = WriteUint(msg.ID, buf, p) + p = WriteUint(msg.ParentID, buf, p) + p = WriteUint(msg.index, buf, p) + p = WriteString(msg.Tag, buf, p) + p = WriteBoolean(msg.SVG, buf, p) + return buf[:p] +} + +func (msg *CreateElementNode) TypeID() int { + return 8 } type CreateTextNode struct { - *meta - ID uint64 - ParentID uint64 - Index uint64 + message + ID uint64 + ParentID uint64 + Index uint64 } func (msg *CreateTextNode) Encode() []byte { - buf := make([]byte, 31) - buf[0] = 9 - p := 1 - p = WriteUint(msg.ID, buf, p) - p = WriteUint(msg.ParentID, buf, p) - p = WriteUint(msg.Index, buf, p) - return buf[:p] + buf := make([]byte, 31) + buf[0] = 9 + p := 1 + p = WriteUint(msg.ID, buf, p) + p = WriteUint(msg.ParentID, buf, p) + p = WriteUint(msg.Index, buf, p) + return buf[:p] +} + +func (msg *CreateTextNode) TypeID() int { + return 9 } type MoveNode struct { - *meta - ID uint64 - ParentID uint64 - Index uint64 + message + ID uint64 + ParentID uint64 + Index uint64 } func (msg *MoveNode) Encode() []byte { - buf := make([]byte, 31) - buf[0] = 10 - p := 1 - p = WriteUint(msg.ID, buf, p) - p = WriteUint(msg.ParentID, buf, p) - p = WriteUint(msg.Index, buf, p) - return buf[:p] + buf := make([]byte, 31) + buf[0] = 10 + p := 1 + p = WriteUint(msg.ID, buf, p) + p = WriteUint(msg.ParentID, buf, p) + p = WriteUint(msg.Index, buf, p) + return buf[:p] +} + +func (msg *MoveNode) TypeID() int { + return 10 } type RemoveNode struct { - *meta - ID uint64 + message + ID uint64 } func (msg *RemoveNode) Encode() []byte { - buf := make([]byte, 11) - buf[0] = 11 - p := 1 - p = WriteUint(msg.ID, buf, p) - return buf[:p] + buf := make([]byte, 11) + buf[0] = 11 + p := 1 + p = WriteUint(msg.ID, buf, p) + return buf[:p] +} + +func (msg *RemoveNode) TypeID() int { + return 11 } type SetNodeAttribute struct { - *meta - ID uint64 - Name string - Value string + message + ID uint64 + Name string + Value string } func (msg *SetNodeAttribute) Encode() []byte { - buf := make([]byte, 31+len(msg.Name)+len(msg.Value)) - buf[0] = 12 - p := 1 - p = WriteUint(msg.ID, buf, p) - p = WriteString(msg.Name, buf, p) - p = WriteString(msg.Value, buf, p) - return buf[:p] + buf := make([]byte, 31+len(msg.Name)+len(msg.Value)) + buf[0] = 12 + p := 1 + p = WriteUint(msg.ID, buf, p) + p = WriteString(msg.Name, buf, p) + p = WriteString(msg.Value, buf, p) + return buf[:p] +} + +func (msg *SetNodeAttribute) TypeID() int { + return 12 } type RemoveNodeAttribute struct { - *meta - ID uint64 - Name string + message + ID uint64 + Name string } func (msg *RemoveNodeAttribute) Encode() []byte { - buf := make([]byte, 21+len(msg.Name)) - buf[0] = 13 - p := 1 - p = WriteUint(msg.ID, buf, p) - p = WriteString(msg.Name, buf, p) - return buf[:p] + buf := make([]byte, 21+len(msg.Name)) + buf[0] = 13 + p := 1 + p = WriteUint(msg.ID, buf, p) + p = WriteString(msg.Name, buf, p) + return buf[:p] +} + +func (msg *RemoveNodeAttribute) TypeID() int { + return 13 } type SetNodeData struct { - *meta - ID uint64 - Data string + message + ID uint64 + Data string } func (msg *SetNodeData) Encode() []byte { - buf := make([]byte, 21+len(msg.Data)) - buf[0] = 14 - p := 1 - p = WriteUint(msg.ID, buf, p) - p = WriteString(msg.Data, buf, p) - return buf[:p] + buf := make([]byte, 21+len(msg.Data)) + buf[0] = 14 + p := 1 + p = WriteUint(msg.ID, buf, p) + p = WriteString(msg.Data, buf, p) + return buf[:p] +} + +func (msg *SetNodeData) TypeID() int { + return 14 } type SetCSSData struct { - *meta - ID uint64 - Data string + message + ID uint64 + Data string } func (msg *SetCSSData) Encode() []byte { - buf := make([]byte, 21+len(msg.Data)) - buf[0] = 15 - p := 1 - p = WriteUint(msg.ID, buf, p) - p = WriteString(msg.Data, buf, p) - return buf[:p] + buf := make([]byte, 21+len(msg.Data)) + buf[0] = 15 + p := 1 + p = WriteUint(msg.ID, buf, p) + p = WriteString(msg.Data, buf, p) + return buf[:p] +} + +func (msg *SetCSSData) TypeID() int { + return 15 } type SetNodeScroll struct { - *meta - ID uint64 - X int64 - Y int64 + message + ID uint64 + X int64 + Y int64 } func (msg *SetNodeScroll) Encode() []byte { - buf := make([]byte, 31) - buf[0] = 16 - p := 1 - p = WriteUint(msg.ID, buf, p) - p = WriteInt(msg.X, buf, p) - p = WriteInt(msg.Y, buf, p) - return buf[:p] + buf := make([]byte, 31) + buf[0] = 16 + p := 1 + p = WriteUint(msg.ID, buf, p) + p = WriteInt(msg.X, buf, p) + p = WriteInt(msg.Y, buf, p) + return buf[:p] +} + +func (msg *SetNodeScroll) TypeID() int { + return 16 } type SetInputTarget struct { - *meta - ID uint64 - Label string + message + ID uint64 + Label string } func (msg *SetInputTarget) Encode() []byte { - buf := make([]byte, 21+len(msg.Label)) - buf[0] = 17 - p := 1 - p = WriteUint(msg.ID, buf, p) - p = WriteString(msg.Label, buf, p) - return buf[:p] + buf := make([]byte, 21+len(msg.Label)) + buf[0] = 17 + p := 1 + p = WriteUint(msg.ID, buf, p) + p = WriteString(msg.Label, buf, p) + return buf[:p] +} + +func (msg *SetInputTarget) TypeID() int { + return 17 } type SetInputValue struct { - *meta - ID uint64 - Value string - Mask int64 + message + ID uint64 + Value string + Mask int64 } func (msg *SetInputValue) Encode() []byte { - buf := make([]byte, 31+len(msg.Value)) - buf[0] = 18 - p := 1 - p = WriteUint(msg.ID, buf, p) - p = WriteString(msg.Value, buf, p) - p = WriteInt(msg.Mask, buf, p) - return buf[:p] + buf := make([]byte, 31+len(msg.Value)) + buf[0] = 18 + p := 1 + p = WriteUint(msg.ID, buf, p) + p = WriteString(msg.Value, buf, p) + p = WriteInt(msg.Mask, buf, p) + return buf[:p] +} + +func (msg *SetInputValue) TypeID() int { + return 18 } type SetInputChecked struct { - *meta - ID uint64 - Checked bool + message + ID uint64 + Checked bool } func (msg *SetInputChecked) Encode() []byte { - buf := make([]byte, 21) - buf[0] = 19 - p := 1 - p = WriteUint(msg.ID, buf, p) - p = WriteBoolean(msg.Checked, buf, p) - return buf[:p] + buf := make([]byte, 21) + buf[0] = 19 + p := 1 + p = WriteUint(msg.ID, buf, p) + p = WriteBoolean(msg.Checked, buf, p) + return buf[:p] +} + +func (msg *SetInputChecked) TypeID() int { + return 19 } type MouseMove struct { - *meta - X uint64 - Y uint64 + message + X uint64 + Y uint64 } func (msg *MouseMove) Encode() []byte { - buf := make([]byte, 21) - buf[0] = 20 - p := 1 - p = WriteUint(msg.X, buf, p) - p = WriteUint(msg.Y, buf, p) - return buf[:p] + buf := make([]byte, 21) + buf[0] = 20 + p := 1 + p = WriteUint(msg.X, buf, p) + p = WriteUint(msg.Y, buf, p) + return buf[:p] +} + +func (msg *MouseMove) TypeID() int { + return 20 } type MouseClickDepricated struct { - *meta - ID uint64 - HesitationTime uint64 - Label string + 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] + 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) TypeID() int { + return 21 } type ConsoleLog struct { - *meta - Level string - Value string + message + Level string + Value string } func (msg *ConsoleLog) Encode() []byte { - buf := make([]byte, 21+len(msg.Level)+len(msg.Value)) - buf[0] = 22 - p := 1 - p = WriteString(msg.Level, buf, p) - p = WriteString(msg.Value, buf, p) - return buf[:p] + buf := make([]byte, 21+len(msg.Level)+len(msg.Value)) + buf[0] = 22 + p := 1 + p = WriteString(msg.Level, buf, p) + p = WriteString(msg.Value, buf, p) + return buf[:p] +} + +func (msg *ConsoleLog) TypeID() int { + return 22 } type PageLoadTiming struct { - *meta - RequestStart uint64 - ResponseStart uint64 - ResponseEnd uint64 - DomContentLoadedEventStart uint64 - DomContentLoadedEventEnd uint64 - LoadEventStart uint64 - LoadEventEnd uint64 - FirstPaint uint64 - FirstContentfulPaint uint64 + message + RequestStart uint64 + ResponseStart uint64 + ResponseEnd uint64 + DomContentLoadedEventStart uint64 + DomContentLoadedEventEnd uint64 + LoadEventStart uint64 + LoadEventEnd uint64 + FirstPaint uint64 + FirstContentfulPaint uint64 } func (msg *PageLoadTiming) Encode() []byte { - buf := make([]byte, 91) - buf[0] = 23 - p := 1 - p = WriteUint(msg.RequestStart, buf, p) - p = WriteUint(msg.ResponseStart, buf, p) - p = WriteUint(msg.ResponseEnd, buf, p) - p = WriteUint(msg.DomContentLoadedEventStart, buf, p) - p = WriteUint(msg.DomContentLoadedEventEnd, buf, p) - p = WriteUint(msg.LoadEventStart, buf, p) - p = WriteUint(msg.LoadEventEnd, buf, p) - p = WriteUint(msg.FirstPaint, buf, p) - p = WriteUint(msg.FirstContentfulPaint, buf, p) - return buf[:p] + buf := make([]byte, 91) + buf[0] = 23 + p := 1 + p = WriteUint(msg.RequestStart, buf, p) + p = WriteUint(msg.ResponseStart, buf, p) + p = WriteUint(msg.ResponseEnd, buf, p) + p = WriteUint(msg.DomContentLoadedEventStart, buf, p) + p = WriteUint(msg.DomContentLoadedEventEnd, buf, p) + p = WriteUint(msg.LoadEventStart, buf, p) + p = WriteUint(msg.LoadEventEnd, buf, p) + p = WriteUint(msg.FirstPaint, buf, p) + p = WriteUint(msg.FirstContentfulPaint, buf, p) + return buf[:p] +} + +func (msg *PageLoadTiming) TypeID() int { + return 23 } type PageRenderTiming struct { - *meta - SpeedIndex uint64 - VisuallyComplete uint64 - TimeToInteractive uint64 + message + SpeedIndex uint64 + VisuallyComplete uint64 + TimeToInteractive uint64 } func (msg *PageRenderTiming) Encode() []byte { - buf := make([]byte, 31) - buf[0] = 24 - p := 1 - p = WriteUint(msg.SpeedIndex, buf, p) - p = WriteUint(msg.VisuallyComplete, buf, p) - p = WriteUint(msg.TimeToInteractive, buf, p) - return buf[:p] + buf := make([]byte, 31) + buf[0] = 24 + p := 1 + p = WriteUint(msg.SpeedIndex, buf, p) + p = WriteUint(msg.VisuallyComplete, buf, p) + p = WriteUint(msg.TimeToInteractive, buf, p) + return buf[:p] +} + +func (msg *PageRenderTiming) TypeID() int { + return 24 } type JSException struct { - *meta - Name string - Message string - Payload string + message + Name string + Message string + Payload string } func (msg *JSException) Encode() []byte { - buf := make([]byte, 31+len(msg.Name)+len(msg.Message)+len(msg.Payload)) - buf[0] = 25 - p := 1 - p = WriteString(msg.Name, buf, p) - p = WriteString(msg.Message, buf, p) - p = WriteString(msg.Payload, buf, p) - return buf[:p] + buf := make([]byte, 31+len(msg.Name)+len(msg.Message)+len(msg.Payload)) + buf[0] = 25 + p := 1 + p = WriteString(msg.Name, buf, p) + p = WriteString(msg.Message, buf, p) + p = WriteString(msg.Payload, buf, p) + return buf[:p] +} + +func (msg *JSException) TypeID() int { + return 25 } type RawErrorEvent struct { - *meta - Timestamp uint64 - Source string - Name string - Message string - Payload string + message + Timestamp uint64 + Source string + Name string + Message string + Payload string } func (msg *RawErrorEvent) Encode() []byte { - buf := make([]byte, 51+len(msg.Source)+len(msg.Name)+len(msg.Message)+len(msg.Payload)) - buf[0] = 26 - p := 1 - 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] + buf := make([]byte, 51+len(msg.Source)+len(msg.Name)+len(msg.Message)+len(msg.Payload)) + buf[0] = 26 + p := 1 + 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 *RawErrorEvent) TypeID() int { + return 26 } type RawCustomEvent struct { - *meta - Name string - Payload string + message + Name string + Payload string } func (msg *RawCustomEvent) Encode() []byte { - buf := make([]byte, 21+len(msg.Name)+len(msg.Payload)) - buf[0] = 27 - p := 1 - p = WriteString(msg.Name, buf, p) - p = WriteString(msg.Payload, buf, p) - return buf[:p] + buf := make([]byte, 21+len(msg.Name)+len(msg.Payload)) + buf[0] = 27 + p := 1 + p = WriteString(msg.Name, buf, p) + p = WriteString(msg.Payload, buf, p) + return buf[:p] +} + +func (msg *RawCustomEvent) TypeID() int { + return 27 } type UserID struct { - *meta - ID string + message + ID string } func (msg *UserID) Encode() []byte { - buf := make([]byte, 11+len(msg.ID)) - buf[0] = 28 - p := 1 - p = WriteString(msg.ID, buf, p) - return buf[:p] + buf := make([]byte, 11+len(msg.ID)) + buf[0] = 28 + p := 1 + p = WriteString(msg.ID, buf, p) + return buf[:p] +} + +func (msg *UserID) TypeID() int { + return 28 } type UserAnonymousID struct { - *meta - ID string + message + ID string } func (msg *UserAnonymousID) Encode() []byte { - buf := make([]byte, 11+len(msg.ID)) - buf[0] = 29 - p := 1 - p = WriteString(msg.ID, buf, p) - return buf[:p] + buf := make([]byte, 11+len(msg.ID)) + buf[0] = 29 + p := 1 + p = WriteString(msg.ID, buf, p) + return buf[:p] +} + +func (msg *UserAnonymousID) TypeID() int { + return 29 } type Metadata struct { - *meta - Key string - Value string + message + Key string + Value string } func (msg *Metadata) Encode() []byte { - buf := make([]byte, 21+len(msg.Key)+len(msg.Value)) - buf[0] = 30 - p := 1 - p = WriteString(msg.Key, buf, p) - p = WriteString(msg.Value, buf, p) - return buf[:p] + buf := make([]byte, 21+len(msg.Key)+len(msg.Value)) + buf[0] = 30 + p := 1 + p = WriteString(msg.Key, buf, p) + p = WriteString(msg.Value, buf, p) + return buf[:p] +} + +func (msg *Metadata) TypeID() int { + return 30 } type PageEvent struct { - *meta - 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 + message + 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 } func (msg *PageEvent) Encode() []byte { - buf := make([]byte, 171+len(msg.URL)+len(msg.Referrer)) - buf[0] = 31 - p := 1 - p = WriteUint(msg.MessageID, buf, p) - p = WriteUint(msg.Timestamp, buf, p) - p = WriteString(msg.URL, buf, p) - p = WriteString(msg.Referrer, buf, p) - p = WriteBoolean(msg.Loaded, buf, p) - p = WriteUint(msg.RequestStart, buf, p) - p = WriteUint(msg.ResponseStart, buf, p) - p = WriteUint(msg.ResponseEnd, buf, p) - p = WriteUint(msg.DomContentLoadedEventStart, buf, p) - p = WriteUint(msg.DomContentLoadedEventEnd, buf, p) - p = WriteUint(msg.LoadEventStart, buf, p) - p = WriteUint(msg.LoadEventEnd, buf, p) - p = WriteUint(msg.FirstPaint, buf, p) - p = WriteUint(msg.FirstContentfulPaint, buf, p) - p = WriteUint(msg.SpeedIndex, buf, p) - p = WriteUint(msg.VisuallyComplete, buf, p) - p = WriteUint(msg.TimeToInteractive, buf, p) - return buf[:p] + buf := make([]byte, 171+len(msg.URL)+len(msg.Referrer)) + buf[0] = 31 + p := 1 + p = WriteUint(msg.MessageID, buf, p) + p = WriteUint(msg.Timestamp, buf, p) + p = WriteString(msg.URL, buf, p) + p = WriteString(msg.Referrer, buf, p) + p = WriteBoolean(msg.Loaded, buf, p) + p = WriteUint(msg.RequestStart, buf, p) + p = WriteUint(msg.ResponseStart, buf, p) + p = WriteUint(msg.ResponseEnd, buf, p) + p = WriteUint(msg.DomContentLoadedEventStart, buf, p) + p = WriteUint(msg.DomContentLoadedEventEnd, buf, p) + p = WriteUint(msg.LoadEventStart, buf, p) + p = WriteUint(msg.LoadEventEnd, buf, p) + p = WriteUint(msg.FirstPaint, buf, p) + p = WriteUint(msg.FirstContentfulPaint, buf, p) + p = WriteUint(msg.SpeedIndex, buf, p) + p = WriteUint(msg.VisuallyComplete, buf, p) + p = WriteUint(msg.TimeToInteractive, buf, p) + return buf[:p] +} + +func (msg *PageEvent) TypeID() int { + return 31 } type InputEvent struct { - *meta - MessageID uint64 - Timestamp uint64 - Value string - ValueMasked bool - Label string + message + MessageID uint64 + Timestamp uint64 + Value string + ValueMasked bool + Label string } func (msg *InputEvent) Encode() []byte { - buf := make([]byte, 51+len(msg.Value)+len(msg.Label)) - buf[0] = 32 - p := 1 - p = WriteUint(msg.MessageID, buf, p) - p = WriteUint(msg.Timestamp, buf, p) - p = WriteString(msg.Value, buf, p) - p = WriteBoolean(msg.ValueMasked, buf, p) - p = WriteString(msg.Label, buf, p) - return buf[:p] + buf := make([]byte, 51+len(msg.Value)+len(msg.Label)) + buf[0] = 32 + p := 1 + p = WriteUint(msg.MessageID, buf, p) + p = WriteUint(msg.Timestamp, buf, p) + p = WriteString(msg.Value, buf, p) + p = WriteBoolean(msg.ValueMasked, buf, p) + p = WriteString(msg.Label, buf, p) + return buf[:p] +} + +func (msg *InputEvent) TypeID() int { + return 32 } type ClickEvent struct { - *meta - MessageID uint64 - Timestamp uint64 - HesitationTime uint64 - Label string - Selector string + message + MessageID uint64 + Timestamp uint64 + HesitationTime uint64 + Label string + Selector string } func (msg *ClickEvent) Encode() []byte { - buf := make([]byte, 51+len(msg.Label)+len(msg.Selector)) - buf[0] = 33 - p := 1 - p = WriteUint(msg.MessageID, buf, p) - p = WriteUint(msg.Timestamp, buf, p) - p = WriteUint(msg.HesitationTime, buf, p) - p = WriteString(msg.Label, buf, p) - p = WriteString(msg.Selector, buf, p) - return buf[:p] + buf := make([]byte, 51+len(msg.Label)+len(msg.Selector)) + buf[0] = 33 + p := 1 + p = WriteUint(msg.MessageID, buf, p) + p = WriteUint(msg.Timestamp, buf, p) + p = WriteUint(msg.HesitationTime, buf, p) + p = WriteString(msg.Label, buf, p) + p = WriteString(msg.Selector, buf, p) + return buf[:p] +} + +func (msg *ClickEvent) TypeID() int { + return 33 } type ErrorEvent struct { - *meta - MessageID uint64 - Timestamp uint64 - Source string - Name string - Message string - Payload string + 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] + 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) TypeID() int { + return 34 } type ResourceEvent struct { - *meta - MessageID uint64 - Timestamp uint64 - Duration uint64 - TTFB uint64 - HeaderSize uint64 - EncodedBodySize uint64 - DecodedBodySize uint64 - URL string - Type string - Success bool - Method string - Status uint64 + message + MessageID uint64 + Timestamp uint64 + Duration uint64 + TTFB uint64 + HeaderSize uint64 + EncodedBodySize uint64 + DecodedBodySize uint64 + URL string + Type string + Success bool + Method string + Status uint64 } func (msg *ResourceEvent) Encode() []byte { - buf := make([]byte, 121+len(msg.URL)+len(msg.Type)+len(msg.Method)) - buf[0] = 35 - p := 1 - p = WriteUint(msg.MessageID, buf, p) - p = WriteUint(msg.Timestamp, buf, p) - p = WriteUint(msg.Duration, buf, p) - p = WriteUint(msg.TTFB, buf, p) - p = WriteUint(msg.HeaderSize, buf, p) - p = WriteUint(msg.EncodedBodySize, buf, p) - p = WriteUint(msg.DecodedBodySize, buf, p) - p = WriteString(msg.URL, buf, p) - p = WriteString(msg.Type, buf, p) - p = WriteBoolean(msg.Success, buf, p) - p = WriteString(msg.Method, buf, p) - p = WriteUint(msg.Status, buf, p) - return buf[:p] + buf := make([]byte, 121+len(msg.URL)+len(msg.Type)+len(msg.Method)) + buf[0] = 35 + p := 1 + p = WriteUint(msg.MessageID, buf, p) + p = WriteUint(msg.Timestamp, buf, p) + p = WriteUint(msg.Duration, buf, p) + p = WriteUint(msg.TTFB, buf, p) + p = WriteUint(msg.HeaderSize, buf, p) + p = WriteUint(msg.EncodedBodySize, buf, p) + p = WriteUint(msg.DecodedBodySize, buf, p) + p = WriteString(msg.URL, buf, p) + p = WriteString(msg.Type, buf, p) + p = WriteBoolean(msg.Success, buf, p) + p = WriteString(msg.Method, buf, p) + p = WriteUint(msg.Status, buf, p) + return buf[:p] +} + +func (msg *ResourceEvent) TypeID() int { + return 35 } type CustomEvent struct { - *meta - MessageID uint64 - Timestamp uint64 - Name string - Payload string + message + MessageID uint64 + Timestamp uint64 + Name string + Payload string } func (msg *CustomEvent) Encode() []byte { - buf := make([]byte, 41+len(msg.Name)+len(msg.Payload)) - buf[0] = 36 - p := 1 - p = WriteUint(msg.MessageID, buf, p) - p = WriteUint(msg.Timestamp, buf, p) - p = WriteString(msg.Name, buf, p) - p = WriteString(msg.Payload, buf, p) - return buf[:p] + buf := make([]byte, 41+len(msg.Name)+len(msg.Payload)) + buf[0] = 36 + p := 1 + p = WriteUint(msg.MessageID, buf, p) + p = WriteUint(msg.Timestamp, buf, p) + p = WriteString(msg.Name, buf, p) + p = WriteString(msg.Payload, buf, p) + return buf[:p] +} + +func (msg *CustomEvent) TypeID() int { + return 36 } type CSSInsertRule struct { - *meta - ID uint64 - Rule string - Index uint64 + message + ID uint64 + Rule string + Index uint64 } func (msg *CSSInsertRule) Encode() []byte { - buf := make([]byte, 31+len(msg.Rule)) - buf[0] = 37 - p := 1 - p = WriteUint(msg.ID, buf, p) - p = WriteString(msg.Rule, buf, p) - p = WriteUint(msg.Index, buf, p) - return buf[:p] + buf := make([]byte, 31+len(msg.Rule)) + buf[0] = 37 + p := 1 + p = WriteUint(msg.ID, buf, p) + p = WriteString(msg.Rule, buf, p) + p = WriteUint(msg.Index, buf, p) + return buf[:p] +} + +func (msg *CSSInsertRule) TypeID() int { + return 37 } type CSSDeleteRule struct { - *meta - ID uint64 - Index uint64 + message + ID uint64 + Index uint64 } func (msg *CSSDeleteRule) Encode() []byte { - buf := make([]byte, 21) - buf[0] = 38 - p := 1 - p = WriteUint(msg.ID, buf, p) - p = WriteUint(msg.Index, buf, p) - return buf[:p] + buf := make([]byte, 21) + buf[0] = 38 + p := 1 + p = WriteUint(msg.ID, buf, p) + p = WriteUint(msg.Index, buf, p) + return buf[:p] +} + +func (msg *CSSDeleteRule) TypeID() int { + return 38 } type Fetch struct { - *meta - Method string - URL string - Request string - Response string - Status uint64 - Timestamp uint64 - Duration uint64 + message + Method string + URL string + Request string + Response string + Status uint64 + Timestamp uint64 + Duration uint64 } func (msg *Fetch) Encode() []byte { - buf := make([]byte, 71+len(msg.Method)+len(msg.URL)+len(msg.Request)+len(msg.Response)) - buf[0] = 39 - p := 1 - p = WriteString(msg.Method, buf, p) - p = WriteString(msg.URL, buf, p) - p = WriteString(msg.Request, buf, p) - p = WriteString(msg.Response, buf, p) - p = WriteUint(msg.Status, buf, p) - p = WriteUint(msg.Timestamp, buf, p) - p = WriteUint(msg.Duration, buf, p) - return buf[:p] + buf := make([]byte, 71+len(msg.Method)+len(msg.URL)+len(msg.Request)+len(msg.Response)) + buf[0] = 39 + p := 1 + p = WriteString(msg.Method, buf, p) + p = WriteString(msg.URL, buf, p) + p = WriteString(msg.Request, buf, p) + p = WriteString(msg.Response, buf, p) + p = WriteUint(msg.Status, buf, p) + p = WriteUint(msg.Timestamp, buf, p) + p = WriteUint(msg.Duration, buf, p) + return buf[:p] +} + +func (msg *Fetch) TypeID() int { + return 39 } type Profiler struct { - *meta - Name string - Duration uint64 - Args string - Result string + message + Name string + Duration uint64 + Args string + Result string } func (msg *Profiler) Encode() []byte { - buf := make([]byte, 41+len(msg.Name)+len(msg.Args)+len(msg.Result)) - buf[0] = 40 - p := 1 - p = WriteString(msg.Name, buf, p) - p = WriteUint(msg.Duration, buf, p) - p = WriteString(msg.Args, buf, p) - p = WriteString(msg.Result, buf, p) - return buf[:p] + buf := make([]byte, 41+len(msg.Name)+len(msg.Args)+len(msg.Result)) + buf[0] = 40 + p := 1 + p = WriteString(msg.Name, buf, p) + p = WriteUint(msg.Duration, buf, p) + p = WriteString(msg.Args, buf, p) + p = WriteString(msg.Result, buf, p) + return buf[:p] +} + +func (msg *Profiler) TypeID() int { + return 40 } type OTable struct { - *meta - Key string - Value string + message + Key string + Value string } func (msg *OTable) Encode() []byte { - buf := make([]byte, 21+len(msg.Key)+len(msg.Value)) - buf[0] = 41 - p := 1 - p = WriteString(msg.Key, buf, p) - p = WriteString(msg.Value, buf, p) - return buf[:p] + buf := make([]byte, 21+len(msg.Key)+len(msg.Value)) + buf[0] = 41 + p := 1 + p = WriteString(msg.Key, buf, p) + p = WriteString(msg.Value, buf, p) + return buf[:p] +} + +func (msg *OTable) TypeID() int { + return 41 } type StateAction struct { - *meta - Type string + message + Type string } func (msg *StateAction) Encode() []byte { - buf := make([]byte, 11+len(msg.Type)) - buf[0] = 42 - p := 1 - p = WriteString(msg.Type, buf, p) - return buf[:p] + buf := make([]byte, 11+len(msg.Type)) + buf[0] = 42 + p := 1 + p = WriteString(msg.Type, buf, p) + return buf[:p] +} + +func (msg *StateAction) TypeID() int { + return 42 } type StateActionEvent struct { - *meta - MessageID uint64 - Timestamp uint64 - Type string + message + MessageID uint64 + Timestamp uint64 + Type string } func (msg *StateActionEvent) Encode() []byte { - buf := make([]byte, 31+len(msg.Type)) - buf[0] = 43 - p := 1 - p = WriteUint(msg.MessageID, buf, p) - p = WriteUint(msg.Timestamp, buf, p) - p = WriteString(msg.Type, buf, p) - return buf[:p] + buf := make([]byte, 31+len(msg.Type)) + buf[0] = 43 + p := 1 + p = WriteUint(msg.MessageID, buf, p) + p = WriteUint(msg.Timestamp, buf, p) + p = WriteString(msg.Type, buf, p) + return buf[:p] +} + +func (msg *StateActionEvent) TypeID() int { + return 43 } type Redux struct { - *meta - Action string - State string - Duration uint64 + message + Action string + State string + Duration uint64 } func (msg *Redux) Encode() []byte { - buf := make([]byte, 31+len(msg.Action)+len(msg.State)) - buf[0] = 44 - p := 1 - p = WriteString(msg.Action, buf, p) - p = WriteString(msg.State, buf, p) - p = WriteUint(msg.Duration, buf, p) - return buf[:p] + buf := make([]byte, 31+len(msg.Action)+len(msg.State)) + buf[0] = 44 + p := 1 + p = WriteString(msg.Action, buf, p) + p = WriteString(msg.State, buf, p) + p = WriteUint(msg.Duration, buf, p) + return buf[:p] +} + +func (msg *Redux) TypeID() int { + return 44 } type Vuex struct { - *meta - Mutation string - State string + message + Mutation string + State string } func (msg *Vuex) Encode() []byte { - buf := make([]byte, 21+len(msg.Mutation)+len(msg.State)) - buf[0] = 45 - p := 1 - p = WriteString(msg.Mutation, buf, p) - p = WriteString(msg.State, buf, p) - return buf[:p] + buf := make([]byte, 21+len(msg.Mutation)+len(msg.State)) + buf[0] = 45 + p := 1 + p = WriteString(msg.Mutation, buf, p) + p = WriteString(msg.State, buf, p) + return buf[:p] +} + +func (msg *Vuex) TypeID() int { + return 45 } type MobX struct { - *meta - Type string - Payload string + message + Type string + Payload string } func (msg *MobX) Encode() []byte { - buf := make([]byte, 21+len(msg.Type)+len(msg.Payload)) - buf[0] = 46 - p := 1 - p = WriteString(msg.Type, buf, p) - p = WriteString(msg.Payload, buf, p) - return buf[:p] + buf := make([]byte, 21+len(msg.Type)+len(msg.Payload)) + buf[0] = 46 + p := 1 + p = WriteString(msg.Type, buf, p) + p = WriteString(msg.Payload, buf, p) + return buf[:p] +} + +func (msg *MobX) TypeID() int { + return 46 } type NgRx struct { - *meta - Action string - State string - Duration uint64 + message + Action string + State string + Duration uint64 } func (msg *NgRx) Encode() []byte { - buf := make([]byte, 31+len(msg.Action)+len(msg.State)) - buf[0] = 47 - p := 1 - p = WriteString(msg.Action, buf, p) - p = WriteString(msg.State, buf, p) - p = WriteUint(msg.Duration, buf, p) - return buf[:p] + buf := make([]byte, 31+len(msg.Action)+len(msg.State)) + buf[0] = 47 + p := 1 + p = WriteString(msg.Action, buf, p) + p = WriteString(msg.State, buf, p) + p = WriteUint(msg.Duration, buf, p) + return buf[:p] +} + +func (msg *NgRx) TypeID() int { + return 47 } type GraphQL struct { - *meta - OperationKind string - OperationName string - Variables string - Response string + message + OperationKind string + OperationName string + Variables string + Response string } func (msg *GraphQL) Encode() []byte { - buf := make([]byte, 41+len(msg.OperationKind)+len(msg.OperationName)+len(msg.Variables)+len(msg.Response)) - buf[0] = 48 - p := 1 - p = WriteString(msg.OperationKind, buf, p) - p = WriteString(msg.OperationName, buf, p) - p = WriteString(msg.Variables, buf, p) - p = WriteString(msg.Response, buf, p) - return buf[:p] + buf := make([]byte, 41+len(msg.OperationKind)+len(msg.OperationName)+len(msg.Variables)+len(msg.Response)) + buf[0] = 48 + p := 1 + p = WriteString(msg.OperationKind, buf, p) + p = WriteString(msg.OperationName, buf, p) + p = WriteString(msg.Variables, buf, p) + p = WriteString(msg.Response, buf, p) + return buf[:p] +} + +func (msg *GraphQL) TypeID() int { + return 48 } type PerformanceTrack struct { - *meta - Frames int64 - Ticks int64 - TotalJSHeapSize uint64 - UsedJSHeapSize uint64 + message + Frames int64 + Ticks int64 + TotalJSHeapSize uint64 + UsedJSHeapSize uint64 } func (msg *PerformanceTrack) Encode() []byte { - buf := make([]byte, 41) - buf[0] = 49 - p := 1 - p = WriteInt(msg.Frames, buf, p) - p = WriteInt(msg.Ticks, buf, p) - p = WriteUint(msg.TotalJSHeapSize, buf, p) - p = WriteUint(msg.UsedJSHeapSize, buf, p) - return buf[:p] + buf := make([]byte, 41) + buf[0] = 49 + p := 1 + p = WriteInt(msg.Frames, buf, p) + p = WriteInt(msg.Ticks, buf, p) + p = WriteUint(msg.TotalJSHeapSize, buf, p) + p = WriteUint(msg.UsedJSHeapSize, buf, p) + return buf[:p] +} + +func (msg *PerformanceTrack) TypeID() int { + return 49 } type GraphQLEvent struct { - *meta - MessageID uint64 - Timestamp uint64 - OperationKind string - OperationName string - Variables string - Response string + message + MessageID uint64 + Timestamp uint64 + OperationKind string + OperationName string + Variables string + Response string } func (msg *GraphQLEvent) Encode() []byte { - buf := make([]byte, 61+len(msg.OperationKind)+len(msg.OperationName)+len(msg.Variables)+len(msg.Response)) - buf[0] = 50 - p := 1 - p = WriteUint(msg.MessageID, buf, p) - p = WriteUint(msg.Timestamp, buf, p) - p = WriteString(msg.OperationKind, buf, p) - p = WriteString(msg.OperationName, buf, p) - p = WriteString(msg.Variables, buf, p) - p = WriteString(msg.Response, buf, p) - return buf[:p] + buf := make([]byte, 61+len(msg.OperationKind)+len(msg.OperationName)+len(msg.Variables)+len(msg.Response)) + buf[0] = 50 + p := 1 + p = WriteUint(msg.MessageID, buf, p) + p = WriteUint(msg.Timestamp, buf, p) + p = WriteString(msg.OperationKind, buf, p) + p = WriteString(msg.OperationName, buf, p) + p = WriteString(msg.Variables, buf, p) + p = WriteString(msg.Response, buf, p) + return buf[:p] +} + +func (msg *GraphQLEvent) TypeID() int { + return 50 } type FetchEvent struct { - *meta - MessageID uint64 - Timestamp uint64 - Method string - URL string - Request string - Response string - Status uint64 - Duration uint64 + message + MessageID uint64 + Timestamp uint64 + Method string + URL string + Request string + Response string + Status uint64 + Duration uint64 } func (msg *FetchEvent) Encode() []byte { - buf := make([]byte, 81+len(msg.Method)+len(msg.URL)+len(msg.Request)+len(msg.Response)) - buf[0] = 51 - p := 1 - p = WriteUint(msg.MessageID, buf, p) - p = WriteUint(msg.Timestamp, buf, p) - p = WriteString(msg.Method, buf, p) - p = WriteString(msg.URL, buf, p) - p = WriteString(msg.Request, buf, p) - p = WriteString(msg.Response, buf, p) - p = WriteUint(msg.Status, buf, p) - p = WriteUint(msg.Duration, buf, p) - return buf[:p] + buf := make([]byte, 81+len(msg.Method)+len(msg.URL)+len(msg.Request)+len(msg.Response)) + buf[0] = 51 + p := 1 + p = WriteUint(msg.MessageID, buf, p) + p = WriteUint(msg.Timestamp, buf, p) + p = WriteString(msg.Method, buf, p) + p = WriteString(msg.URL, buf, p) + p = WriteString(msg.Request, buf, p) + p = WriteString(msg.Response, buf, p) + p = WriteUint(msg.Status, buf, p) + p = WriteUint(msg.Duration, buf, p) + return buf[:p] +} + +func (msg *FetchEvent) TypeID() int { + return 51 } type DOMDrop struct { - *meta - Timestamp uint64 + message + Timestamp uint64 } func (msg *DOMDrop) Encode() []byte { - buf := make([]byte, 11) - buf[0] = 52 - p := 1 - p = WriteUint(msg.Timestamp, buf, p) - return buf[:p] + buf := make([]byte, 11) + buf[0] = 52 + p := 1 + p = WriteUint(msg.Timestamp, buf, p) + return buf[:p] +} + +func (msg *DOMDrop) TypeID() int { + return 52 } type ResourceTiming struct { - *meta - Timestamp uint64 - Duration uint64 - TTFB uint64 - HeaderSize uint64 - EncodedBodySize uint64 - DecodedBodySize uint64 - URL string - Initiator string + message + Timestamp uint64 + Duration uint64 + TTFB uint64 + HeaderSize uint64 + EncodedBodySize uint64 + DecodedBodySize uint64 + URL string + Initiator string } func (msg *ResourceTiming) Encode() []byte { - buf := make([]byte, 81+len(msg.URL)+len(msg.Initiator)) - buf[0] = 53 - p := 1 - p = WriteUint(msg.Timestamp, buf, p) - p = WriteUint(msg.Duration, buf, p) - p = WriteUint(msg.TTFB, buf, p) - p = WriteUint(msg.HeaderSize, buf, p) - p = WriteUint(msg.EncodedBodySize, buf, p) - p = WriteUint(msg.DecodedBodySize, buf, p) - p = WriteString(msg.URL, buf, p) - p = WriteString(msg.Initiator, buf, p) - return buf[:p] + buf := make([]byte, 81+len(msg.URL)+len(msg.Initiator)) + buf[0] = 53 + p := 1 + p = WriteUint(msg.Timestamp, buf, p) + p = WriteUint(msg.Duration, buf, p) + p = WriteUint(msg.TTFB, buf, p) + p = WriteUint(msg.HeaderSize, buf, p) + p = WriteUint(msg.EncodedBodySize, buf, p) + p = WriteUint(msg.DecodedBodySize, buf, p) + p = WriteString(msg.URL, buf, p) + p = WriteString(msg.Initiator, buf, p) + return buf[:p] +} + +func (msg *ResourceTiming) TypeID() int { + return 53 } type ConnectionInformation struct { - *meta - Downlink uint64 - Type string + message + Downlink uint64 + Type string } func (msg *ConnectionInformation) Encode() []byte { - buf := make([]byte, 21+len(msg.Type)) - buf[0] = 54 - p := 1 - p = WriteUint(msg.Downlink, buf, p) - p = WriteString(msg.Type, buf, p) - return buf[:p] + buf := make([]byte, 21+len(msg.Type)) + buf[0] = 54 + p := 1 + p = WriteUint(msg.Downlink, buf, p) + p = WriteString(msg.Type, buf, p) + return buf[:p] +} + +func (msg *ConnectionInformation) TypeID() int { + return 54 } type SetPageVisibility struct { - *meta - hidden bool + message + hidden bool } func (msg *SetPageVisibility) Encode() []byte { - buf := make([]byte, 11) - buf[0] = 55 - p := 1 - p = WriteBoolean(msg.hidden, buf, p) - return buf[:p] + buf := make([]byte, 11) + buf[0] = 55 + p := 1 + p = WriteBoolean(msg.hidden, buf, p) + return buf[:p] +} + +func (msg *SetPageVisibility) TypeID() int { + return 55 } type PerformanceTrackAggr struct { - *meta - 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 + message + 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 } func (msg *PerformanceTrackAggr) Encode() []byte { - buf := make([]byte, 141) - buf[0] = 56 - p := 1 - p = WriteUint(msg.TimestampStart, buf, p) - p = WriteUint(msg.TimestampEnd, buf, p) - p = WriteUint(msg.MinFPS, buf, p) - p = WriteUint(msg.AvgFPS, buf, p) - p = WriteUint(msg.MaxFPS, buf, p) - p = WriteUint(msg.MinCPU, buf, p) - p = WriteUint(msg.AvgCPU, buf, p) - p = WriteUint(msg.MaxCPU, buf, p) - p = WriteUint(msg.MinTotalJSHeapSize, buf, p) - p = WriteUint(msg.AvgTotalJSHeapSize, buf, p) - p = WriteUint(msg.MaxTotalJSHeapSize, buf, p) - p = WriteUint(msg.MinUsedJSHeapSize, buf, p) - p = WriteUint(msg.AvgUsedJSHeapSize, buf, p) - p = WriteUint(msg.MaxUsedJSHeapSize, buf, p) - return buf[:p] + buf := make([]byte, 141) + buf[0] = 56 + p := 1 + p = WriteUint(msg.TimestampStart, buf, p) + p = WriteUint(msg.TimestampEnd, buf, p) + p = WriteUint(msg.MinFPS, buf, p) + p = WriteUint(msg.AvgFPS, buf, p) + p = WriteUint(msg.MaxFPS, buf, p) + p = WriteUint(msg.MinCPU, buf, p) + p = WriteUint(msg.AvgCPU, buf, p) + p = WriteUint(msg.MaxCPU, buf, p) + p = WriteUint(msg.MinTotalJSHeapSize, buf, p) + p = WriteUint(msg.AvgTotalJSHeapSize, buf, p) + p = WriteUint(msg.MaxTotalJSHeapSize, buf, p) + p = WriteUint(msg.MinUsedJSHeapSize, buf, p) + p = WriteUint(msg.AvgUsedJSHeapSize, buf, p) + p = WriteUint(msg.MaxUsedJSHeapSize, buf, p) + return buf[:p] +} + +func (msg *PerformanceTrackAggr) TypeID() int { + return 56 } type LongTask struct { - *meta - Timestamp uint64 - Duration uint64 - Context uint64 - ContainerType uint64 - ContainerSrc string - ContainerId string - ContainerName string + message + Timestamp uint64 + Duration uint64 + Context uint64 + ContainerType uint64 + ContainerSrc string + ContainerId string + ContainerName string } func (msg *LongTask) Encode() []byte { - buf := make([]byte, 71+len(msg.ContainerSrc)+len(msg.ContainerId)+len(msg.ContainerName)) - buf[0] = 59 - p := 1 - p = WriteUint(msg.Timestamp, buf, p) - p = WriteUint(msg.Duration, buf, p) - p = WriteUint(msg.Context, buf, p) - p = WriteUint(msg.ContainerType, buf, p) - p = WriteString(msg.ContainerSrc, buf, p) - p = WriteString(msg.ContainerId, buf, p) - p = WriteString(msg.ContainerName, buf, p) - return buf[:p] + buf := make([]byte, 71+len(msg.ContainerSrc)+len(msg.ContainerId)+len(msg.ContainerName)) + buf[0] = 59 + p := 1 + p = WriteUint(msg.Timestamp, buf, p) + p = WriteUint(msg.Duration, buf, p) + p = WriteUint(msg.Context, buf, p) + p = WriteUint(msg.ContainerType, buf, p) + p = WriteString(msg.ContainerSrc, buf, p) + p = WriteString(msg.ContainerId, buf, p) + p = WriteString(msg.ContainerName, buf, p) + return buf[:p] +} + +func (msg *LongTask) TypeID() int { + return 59 } type SetNodeAttributeURLBased struct { - *meta - ID uint64 - Name string - Value string - BaseURL string + message + ID uint64 + Name string + Value string + BaseURL string } func (msg *SetNodeAttributeURLBased) Encode() []byte { - buf := make([]byte, 41+len(msg.Name)+len(msg.Value)+len(msg.BaseURL)) - buf[0] = 60 - p := 1 - p = WriteUint(msg.ID, buf, p) - p = WriteString(msg.Name, buf, p) - p = WriteString(msg.Value, buf, p) - p = WriteString(msg.BaseURL, buf, p) - return buf[:p] + buf := make([]byte, 41+len(msg.Name)+len(msg.Value)+len(msg.BaseURL)) + buf[0] = 60 + p := 1 + p = WriteUint(msg.ID, buf, p) + p = WriteString(msg.Name, buf, p) + p = WriteString(msg.Value, buf, p) + p = WriteString(msg.BaseURL, buf, p) + return buf[:p] +} + +func (msg *SetNodeAttributeURLBased) TypeID() int { + return 60 } type SetCSSDataURLBased struct { - *meta - ID uint64 - Data string - BaseURL string + message + ID uint64 + Data string + BaseURL string } func (msg *SetCSSDataURLBased) Encode() []byte { - buf := make([]byte, 31+len(msg.Data)+len(msg.BaseURL)) - buf[0] = 61 - p := 1 - p = WriteUint(msg.ID, buf, p) - p = WriteString(msg.Data, buf, p) - p = WriteString(msg.BaseURL, buf, p) - return buf[:p] + buf := make([]byte, 31+len(msg.Data)+len(msg.BaseURL)) + buf[0] = 61 + p := 1 + p = WriteUint(msg.ID, buf, p) + p = WriteString(msg.Data, buf, p) + p = WriteString(msg.BaseURL, buf, p) + return buf[:p] +} + +func (msg *SetCSSDataURLBased) TypeID() int { + return 61 } type IssueEvent struct { - *meta - MessageID uint64 - Timestamp uint64 - Type string - ContextString string - Context string - Payload string + message + MessageID uint64 + Timestamp uint64 + Type string + ContextString string + Context string + Payload string } func (msg *IssueEvent) Encode() []byte { - buf := make([]byte, 61+len(msg.Type)+len(msg.ContextString)+len(msg.Context)+len(msg.Payload)) - buf[0] = 62 - p := 1 - p = WriteUint(msg.MessageID, buf, p) - p = WriteUint(msg.Timestamp, buf, p) - p = WriteString(msg.Type, buf, p) - p = WriteString(msg.ContextString, buf, p) - p = WriteString(msg.Context, buf, p) - p = WriteString(msg.Payload, buf, p) - return buf[:p] + buf := make([]byte, 61+len(msg.Type)+len(msg.ContextString)+len(msg.Context)+len(msg.Payload)) + buf[0] = 62 + p := 1 + p = WriteUint(msg.MessageID, buf, p) + p = WriteUint(msg.Timestamp, buf, p) + p = WriteString(msg.Type, buf, p) + p = WriteString(msg.ContextString, buf, p) + p = WriteString(msg.Context, buf, p) + p = WriteString(msg.Payload, buf, p) + return buf[:p] +} + +func (msg *IssueEvent) TypeID() int { + return 62 } type TechnicalInfo struct { - *meta - Type string - Value string + message + Type string + Value string } func (msg *TechnicalInfo) Encode() []byte { - buf := make([]byte, 21+len(msg.Type)+len(msg.Value)) - buf[0] = 63 - p := 1 - p = WriteString(msg.Type, buf, p) - p = WriteString(msg.Value, buf, p) - return buf[:p] + buf := make([]byte, 21+len(msg.Type)+len(msg.Value)) + buf[0] = 63 + p := 1 + p = WriteString(msg.Type, buf, p) + p = WriteString(msg.Value, buf, p) + return buf[:p] +} + +func (msg *TechnicalInfo) TypeID() int { + return 63 } type CustomIssue struct { - *meta - Name string - Payload string + message + Name string + Payload string } func (msg *CustomIssue) Encode() []byte { - buf := make([]byte, 21+len(msg.Name)+len(msg.Payload)) - buf[0] = 64 - p := 1 - p = WriteString(msg.Name, buf, p) - p = WriteString(msg.Payload, buf, p) - return buf[:p] + buf := make([]byte, 21+len(msg.Name)+len(msg.Payload)) + buf[0] = 64 + p := 1 + p = WriteString(msg.Name, buf, p) + p = WriteString(msg.Payload, buf, p) + return buf[:p] +} + +func (msg *CustomIssue) TypeID() int { + return 64 } type PageClose struct { - *meta + message } func (msg *PageClose) Encode() []byte { - buf := make([]byte, 1) - buf[0] = 65 - p := 1 + buf := make([]byte, 1) + buf[0] = 65 + p := 1 - return buf[:p] + return buf[:p] +} + +func (msg *PageClose) TypeID() int { + return 65 } type AssetCache struct { - *meta - URL string + message + URL string } func (msg *AssetCache) Encode() []byte { - buf := make([]byte, 11+len(msg.URL)) - buf[0] = 66 - p := 1 - p = WriteString(msg.URL, buf, p) - return buf[:p] + buf := make([]byte, 11+len(msg.URL)) + buf[0] = 66 + p := 1 + p = WriteString(msg.URL, buf, p) + return buf[:p] +} + +func (msg *AssetCache) TypeID() int { + return 66 } type CSSInsertRuleURLBased struct { - *meta - ID uint64 - Rule string - Index uint64 - BaseURL string + message + ID uint64 + Rule string + Index uint64 + BaseURL string } func (msg *CSSInsertRuleURLBased) Encode() []byte { - buf := make([]byte, 41+len(msg.Rule)+len(msg.BaseURL)) - buf[0] = 67 - p := 1 - p = WriteUint(msg.ID, buf, p) - p = WriteString(msg.Rule, buf, p) - p = WriteUint(msg.Index, buf, p) - p = WriteString(msg.BaseURL, buf, p) - return buf[:p] + buf := make([]byte, 41+len(msg.Rule)+len(msg.BaseURL)) + buf[0] = 67 + p := 1 + p = WriteUint(msg.ID, buf, p) + p = WriteString(msg.Rule, buf, p) + p = WriteUint(msg.Index, buf, p) + p = WriteString(msg.BaseURL, buf, p) + return buf[:p] +} + +func (msg *CSSInsertRuleURLBased) TypeID() int { + return 67 } type MouseClick struct { - *meta - ID uint64 - HesitationTime uint64 - Label string - Selector string + message + ID uint64 + HesitationTime uint64 + Label string + Selector string } func (msg *MouseClick) Encode() []byte { - buf := make([]byte, 41+len(msg.Label)+len(msg.Selector)) - buf[0] = 69 - p := 1 - p = WriteUint(msg.ID, buf, p) - p = WriteUint(msg.HesitationTime, buf, p) - p = WriteString(msg.Label, buf, p) - p = WriteString(msg.Selector, buf, p) - return buf[:p] + buf := make([]byte, 41+len(msg.Label)+len(msg.Selector)) + buf[0] = 69 + p := 1 + p = WriteUint(msg.ID, buf, p) + p = WriteUint(msg.HesitationTime, buf, p) + p = WriteString(msg.Label, buf, p) + p = WriteString(msg.Selector, buf, p) + return buf[:p] +} + +func (msg *MouseClick) TypeID() int { + return 69 } type CreateIFrameDocument struct { - *meta - FrameID uint64 - ID uint64 + message + FrameID uint64 + ID uint64 } func (msg *CreateIFrameDocument) Encode() []byte { - buf := make([]byte, 21) - buf[0] = 70 - p := 1 - p = WriteUint(msg.FrameID, buf, p) - p = WriteUint(msg.ID, buf, p) - return buf[:p] + buf := make([]byte, 21) + buf[0] = 70 + p := 1 + p = WriteUint(msg.FrameID, buf, p) + p = WriteUint(msg.ID, buf, p) + return buf[:p] +} + +func (msg *CreateIFrameDocument) TypeID() int { + return 70 } type IOSBatchMeta struct { - *meta - Timestamp uint64 - Length uint64 - FirstIndex uint64 + message + Timestamp uint64 + Length uint64 + FirstIndex uint64 } func (msg *IOSBatchMeta) Encode() []byte { - buf := make([]byte, 31) - buf[0] = 107 - p := 1 - p = WriteUint(msg.Timestamp, buf, p) - p = WriteUint(msg.Length, buf, p) - p = WriteUint(msg.FirstIndex, buf, p) - return buf[:p] + buf := make([]byte, 31) + buf[0] = 107 + p := 1 + p = WriteUint(msg.Timestamp, buf, p) + p = WriteUint(msg.Length, buf, p) + p = WriteUint(msg.FirstIndex, buf, p) + return buf[:p] +} + +func (msg *IOSBatchMeta) TypeID() int { + return 107 } type IOSSessionStart struct { - *meta - Timestamp uint64 - ProjectID uint64 - TrackerVersion string - RevID string - UserUUID string - UserOS string - UserOSVersion string - UserDevice string - UserDeviceType string - UserCountry string + message + Timestamp uint64 + ProjectID uint64 + TrackerVersion string + RevID string + UserUUID string + UserOS string + UserOSVersion string + UserDevice string + UserDeviceType string + UserCountry string } func (msg *IOSSessionStart) Encode() []byte { - buf := make([]byte, 101+len(msg.TrackerVersion)+len(msg.RevID)+len(msg.UserUUID)+len(msg.UserOS)+len(msg.UserOSVersion)+len(msg.UserDevice)+len(msg.UserDeviceType)+len(msg.UserCountry)) - buf[0] = 90 - p := 1 - p = WriteUint(msg.Timestamp, buf, p) - p = WriteUint(msg.ProjectID, buf, p) - p = WriteString(msg.TrackerVersion, buf, p) - p = WriteString(msg.RevID, buf, p) - p = WriteString(msg.UserUUID, buf, p) - p = WriteString(msg.UserOS, buf, p) - p = WriteString(msg.UserOSVersion, buf, p) - p = WriteString(msg.UserDevice, buf, p) - p = WriteString(msg.UserDeviceType, buf, p) - p = WriteString(msg.UserCountry, buf, p) - return buf[:p] + buf := make([]byte, 101+len(msg.TrackerVersion)+len(msg.RevID)+len(msg.UserUUID)+len(msg.UserOS)+len(msg.UserOSVersion)+len(msg.UserDevice)+len(msg.UserDeviceType)+len(msg.UserCountry)) + buf[0] = 90 + p := 1 + p = WriteUint(msg.Timestamp, buf, p) + p = WriteUint(msg.ProjectID, buf, p) + p = WriteString(msg.TrackerVersion, buf, p) + p = WriteString(msg.RevID, buf, p) + p = WriteString(msg.UserUUID, buf, p) + p = WriteString(msg.UserOS, buf, p) + p = WriteString(msg.UserOSVersion, buf, p) + p = WriteString(msg.UserDevice, buf, p) + p = WriteString(msg.UserDeviceType, buf, p) + p = WriteString(msg.UserCountry, buf, p) + return buf[:p] +} + +func (msg *IOSSessionStart) TypeID() int { + return 90 } type IOSSessionEnd struct { - *meta - Timestamp uint64 + message + Timestamp uint64 } func (msg *IOSSessionEnd) Encode() []byte { - buf := make([]byte, 11) - buf[0] = 91 - p := 1 - p = WriteUint(msg.Timestamp, buf, p) - return buf[:p] + buf := make([]byte, 11) + buf[0] = 91 + p := 1 + p = WriteUint(msg.Timestamp, buf, p) + return buf[:p] +} + +func (msg *IOSSessionEnd) TypeID() int { + return 91 } type IOSMetadata struct { - *meta - Timestamp uint64 - Length uint64 - Key string - Value string + message + Timestamp uint64 + Length uint64 + Key string + Value string } func (msg *IOSMetadata) Encode() []byte { - buf := make([]byte, 41+len(msg.Key)+len(msg.Value)) - buf[0] = 92 - p := 1 - p = WriteUint(msg.Timestamp, buf, p) - p = WriteUint(msg.Length, buf, p) - p = WriteString(msg.Key, buf, p) - p = WriteString(msg.Value, buf, p) - return buf[:p] + buf := make([]byte, 41+len(msg.Key)+len(msg.Value)) + buf[0] = 92 + p := 1 + p = WriteUint(msg.Timestamp, buf, p) + p = WriteUint(msg.Length, buf, p) + p = WriteString(msg.Key, buf, p) + p = WriteString(msg.Value, buf, p) + return buf[:p] +} + +func (msg *IOSMetadata) TypeID() int { + return 92 } type IOSCustomEvent struct { - *meta - Timestamp uint64 - Length uint64 - Name string - Payload string + message + Timestamp uint64 + Length uint64 + Name string + Payload string } func (msg *IOSCustomEvent) Encode() []byte { - buf := make([]byte, 41+len(msg.Name)+len(msg.Payload)) - buf[0] = 93 - p := 1 - p = WriteUint(msg.Timestamp, buf, p) - p = WriteUint(msg.Length, buf, p) - p = WriteString(msg.Name, buf, p) - p = WriteString(msg.Payload, buf, p) - return buf[:p] + buf := make([]byte, 41+len(msg.Name)+len(msg.Payload)) + buf[0] = 93 + p := 1 + p = WriteUint(msg.Timestamp, buf, p) + p = WriteUint(msg.Length, buf, p) + p = WriteString(msg.Name, buf, p) + p = WriteString(msg.Payload, buf, p) + return buf[:p] +} + +func (msg *IOSCustomEvent) TypeID() int { + return 93 } type IOSUserID struct { - *meta - Timestamp uint64 - Length uint64 - Value string + message + Timestamp uint64 + Length uint64 + Value string } func (msg *IOSUserID) Encode() []byte { - buf := make([]byte, 31+len(msg.Value)) - buf[0] = 94 - p := 1 - p = WriteUint(msg.Timestamp, buf, p) - p = WriteUint(msg.Length, buf, p) - p = WriteString(msg.Value, buf, p) - return buf[:p] + buf := make([]byte, 31+len(msg.Value)) + buf[0] = 94 + p := 1 + p = WriteUint(msg.Timestamp, buf, p) + p = WriteUint(msg.Length, buf, p) + p = WriteString(msg.Value, buf, p) + return buf[:p] +} + +func (msg *IOSUserID) TypeID() int { + return 94 } type IOSUserAnonymousID struct { - *meta - Timestamp uint64 - Length uint64 - Value string + message + Timestamp uint64 + Length uint64 + Value string } func (msg *IOSUserAnonymousID) Encode() []byte { - buf := make([]byte, 31+len(msg.Value)) - buf[0] = 95 - p := 1 - p = WriteUint(msg.Timestamp, buf, p) - p = WriteUint(msg.Length, buf, p) - p = WriteString(msg.Value, buf, p) - return buf[:p] + buf := make([]byte, 31+len(msg.Value)) + buf[0] = 95 + p := 1 + p = WriteUint(msg.Timestamp, buf, p) + p = WriteUint(msg.Length, buf, p) + p = WriteString(msg.Value, buf, p) + return buf[:p] +} + +func (msg *IOSUserAnonymousID) TypeID() int { + return 95 } type IOSScreenChanges struct { - *meta - Timestamp uint64 - Length uint64 - X uint64 - Y uint64 - Width uint64 - Height uint64 + message + Timestamp uint64 + Length uint64 + X uint64 + Y uint64 + Width uint64 + Height uint64 } func (msg *IOSScreenChanges) Encode() []byte { - buf := make([]byte, 61) - buf[0] = 96 - p := 1 - p = WriteUint(msg.Timestamp, buf, p) - p = WriteUint(msg.Length, buf, p) - p = WriteUint(msg.X, buf, p) - p = WriteUint(msg.Y, buf, p) - p = WriteUint(msg.Width, buf, p) - p = WriteUint(msg.Height, buf, p) - return buf[:p] + buf := make([]byte, 61) + buf[0] = 96 + p := 1 + p = WriteUint(msg.Timestamp, buf, p) + p = WriteUint(msg.Length, buf, p) + p = WriteUint(msg.X, buf, p) + p = WriteUint(msg.Y, buf, p) + p = WriteUint(msg.Width, buf, p) + p = WriteUint(msg.Height, buf, p) + return buf[:p] +} + +func (msg *IOSScreenChanges) TypeID() int { + return 96 } type IOSCrash struct { - *meta - Timestamp uint64 - Length uint64 - Name string - Reason string - Stacktrace string + message + Timestamp uint64 + Length uint64 + Name string + Reason string + Stacktrace string } func (msg *IOSCrash) Encode() []byte { - buf := make([]byte, 51+len(msg.Name)+len(msg.Reason)+len(msg.Stacktrace)) - buf[0] = 97 - p := 1 - p = WriteUint(msg.Timestamp, buf, p) - p = WriteUint(msg.Length, buf, p) - p = WriteString(msg.Name, buf, p) - p = WriteString(msg.Reason, buf, p) - p = WriteString(msg.Stacktrace, buf, p) - return buf[:p] + buf := make([]byte, 51+len(msg.Name)+len(msg.Reason)+len(msg.Stacktrace)) + buf[0] = 97 + p := 1 + p = WriteUint(msg.Timestamp, buf, p) + p = WriteUint(msg.Length, buf, p) + p = WriteString(msg.Name, buf, p) + p = WriteString(msg.Reason, buf, p) + p = WriteString(msg.Stacktrace, buf, p) + return buf[:p] +} + +func (msg *IOSCrash) TypeID() int { + return 97 } type IOSScreenEnter struct { - *meta - Timestamp uint64 - Length uint64 - Title string - ViewName string + message + Timestamp uint64 + Length uint64 + Title string + ViewName string } func (msg *IOSScreenEnter) Encode() []byte { - buf := make([]byte, 41+len(msg.Title)+len(msg.ViewName)) - buf[0] = 98 - p := 1 - p = WriteUint(msg.Timestamp, buf, p) - p = WriteUint(msg.Length, buf, p) - p = WriteString(msg.Title, buf, p) - p = WriteString(msg.ViewName, buf, p) - return buf[:p] + buf := make([]byte, 41+len(msg.Title)+len(msg.ViewName)) + buf[0] = 98 + p := 1 + p = WriteUint(msg.Timestamp, buf, p) + p = WriteUint(msg.Length, buf, p) + p = WriteString(msg.Title, buf, p) + p = WriteString(msg.ViewName, buf, p) + return buf[:p] +} + +func (msg *IOSScreenEnter) TypeID() int { + return 98 } type IOSScreenLeave struct { - *meta - Timestamp uint64 - Length uint64 - Title string - ViewName string + message + Timestamp uint64 + Length uint64 + Title string + ViewName string } func (msg *IOSScreenLeave) Encode() []byte { - buf := make([]byte, 41+len(msg.Title)+len(msg.ViewName)) - buf[0] = 99 - p := 1 - p = WriteUint(msg.Timestamp, buf, p) - p = WriteUint(msg.Length, buf, p) - p = WriteString(msg.Title, buf, p) - p = WriteString(msg.ViewName, buf, p) - return buf[:p] + buf := make([]byte, 41+len(msg.Title)+len(msg.ViewName)) + buf[0] = 99 + p := 1 + p = WriteUint(msg.Timestamp, buf, p) + p = WriteUint(msg.Length, buf, p) + p = WriteString(msg.Title, buf, p) + p = WriteString(msg.ViewName, buf, p) + return buf[:p] +} + +func (msg *IOSScreenLeave) TypeID() int { + return 99 } type IOSClickEvent struct { - *meta - Timestamp uint64 - Length uint64 - Label string - X uint64 - Y uint64 + message + Timestamp uint64 + Length uint64 + Label string + X uint64 + Y uint64 } func (msg *IOSClickEvent) Encode() []byte { - buf := make([]byte, 51+len(msg.Label)) - buf[0] = 100 - p := 1 - p = WriteUint(msg.Timestamp, buf, p) - p = WriteUint(msg.Length, buf, p) - p = WriteString(msg.Label, buf, p) - p = WriteUint(msg.X, buf, p) - p = WriteUint(msg.Y, buf, p) - return buf[:p] + buf := make([]byte, 51+len(msg.Label)) + buf[0] = 100 + p := 1 + p = WriteUint(msg.Timestamp, buf, p) + p = WriteUint(msg.Length, buf, p) + p = WriteString(msg.Label, buf, p) + p = WriteUint(msg.X, buf, p) + p = WriteUint(msg.Y, buf, p) + return buf[:p] +} + +func (msg *IOSClickEvent) TypeID() int { + return 100 } type IOSInputEvent struct { - *meta - Timestamp uint64 - Length uint64 - Value string - ValueMasked bool - Label string + message + Timestamp uint64 + Length uint64 + Value string + ValueMasked bool + Label string } func (msg *IOSInputEvent) Encode() []byte { - buf := make([]byte, 51+len(msg.Value)+len(msg.Label)) - buf[0] = 101 - p := 1 - p = WriteUint(msg.Timestamp, buf, p) - p = WriteUint(msg.Length, buf, p) - p = WriteString(msg.Value, buf, p) - p = WriteBoolean(msg.ValueMasked, buf, p) - p = WriteString(msg.Label, buf, p) - return buf[:p] + buf := make([]byte, 51+len(msg.Value)+len(msg.Label)) + buf[0] = 101 + p := 1 + p = WriteUint(msg.Timestamp, buf, p) + p = WriteUint(msg.Length, buf, p) + p = WriteString(msg.Value, buf, p) + p = WriteBoolean(msg.ValueMasked, buf, p) + p = WriteString(msg.Label, buf, p) + return buf[:p] +} + +func (msg *IOSInputEvent) TypeID() int { + return 101 } type IOSPerformanceEvent struct { - *meta - Timestamp uint64 - Length uint64 - Name string - Value uint64 + message + Timestamp uint64 + Length uint64 + Name string + Value uint64 } func (msg *IOSPerformanceEvent) Encode() []byte { - buf := make([]byte, 41+len(msg.Name)) - buf[0] = 102 - p := 1 - p = WriteUint(msg.Timestamp, buf, p) - p = WriteUint(msg.Length, buf, p) - p = WriteString(msg.Name, buf, p) - p = WriteUint(msg.Value, buf, p) - return buf[:p] + buf := make([]byte, 41+len(msg.Name)) + buf[0] = 102 + p := 1 + p = WriteUint(msg.Timestamp, buf, p) + p = WriteUint(msg.Length, buf, p) + p = WriteString(msg.Name, buf, p) + p = WriteUint(msg.Value, buf, p) + return buf[:p] +} + +func (msg *IOSPerformanceEvent) TypeID() int { + return 102 } type IOSLog struct { - *meta - Timestamp uint64 - Length uint64 - Severity string - Content string + message + Timestamp uint64 + Length uint64 + Severity string + Content string } func (msg *IOSLog) Encode() []byte { - buf := make([]byte, 41+len(msg.Severity)+len(msg.Content)) - buf[0] = 103 - p := 1 - p = WriteUint(msg.Timestamp, buf, p) - p = WriteUint(msg.Length, buf, p) - p = WriteString(msg.Severity, buf, p) - p = WriteString(msg.Content, buf, p) - return buf[:p] + buf := make([]byte, 41+len(msg.Severity)+len(msg.Content)) + buf[0] = 103 + p := 1 + p = WriteUint(msg.Timestamp, buf, p) + p = WriteUint(msg.Length, buf, p) + p = WriteString(msg.Severity, buf, p) + p = WriteString(msg.Content, buf, p) + return buf[:p] +} + +func (msg *IOSLog) TypeID() int { + return 103 } type IOSInternalError struct { - *meta - Timestamp uint64 - Length uint64 - Content string + message + Timestamp uint64 + Length uint64 + Content string } func (msg *IOSInternalError) Encode() []byte { - buf := make([]byte, 31+len(msg.Content)) - buf[0] = 104 - p := 1 - p = WriteUint(msg.Timestamp, buf, p) - p = WriteUint(msg.Length, buf, p) - p = WriteString(msg.Content, buf, p) - return buf[:p] + buf := make([]byte, 31+len(msg.Content)) + buf[0] = 104 + p := 1 + p = WriteUint(msg.Timestamp, buf, p) + p = WriteUint(msg.Length, buf, p) + p = WriteString(msg.Content, buf, p) + return buf[:p] +} + +func (msg *IOSInternalError) TypeID() int { + return 104 } type IOSNetworkCall struct { - *meta - Timestamp uint64 - Length uint64 - Duration uint64 - Headers string - Body string - URL string - Success bool - Method string - Status uint64 + message + Timestamp uint64 + Length uint64 + Duration uint64 + Headers string + Body string + URL string + Success bool + Method string + Status uint64 } func (msg *IOSNetworkCall) Encode() []byte { - buf := make([]byte, 91+len(msg.Headers)+len(msg.Body)+len(msg.URL)+len(msg.Method)) - buf[0] = 105 - p := 1 - p = WriteUint(msg.Timestamp, buf, p) - p = WriteUint(msg.Length, buf, p) - p = WriteUint(msg.Duration, buf, p) - p = WriteString(msg.Headers, buf, p) - p = WriteString(msg.Body, buf, p) - p = WriteString(msg.URL, buf, p) - p = WriteBoolean(msg.Success, buf, p) - p = WriteString(msg.Method, buf, p) - p = WriteUint(msg.Status, buf, p) - return buf[:p] + buf := make([]byte, 91+len(msg.Headers)+len(msg.Body)+len(msg.URL)+len(msg.Method)) + buf[0] = 105 + p := 1 + p = WriteUint(msg.Timestamp, buf, p) + p = WriteUint(msg.Length, buf, p) + p = WriteUint(msg.Duration, buf, p) + p = WriteString(msg.Headers, buf, p) + p = WriteString(msg.Body, buf, p) + p = WriteString(msg.URL, buf, p) + p = WriteBoolean(msg.Success, buf, p) + p = WriteString(msg.Method, buf, p) + p = WriteUint(msg.Status, buf, p) + return buf[:p] +} + +func (msg *IOSNetworkCall) TypeID() int { + return 105 } type IOSPerformanceAggregated struct { - *meta - 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 + 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 } func (msg *IOSPerformanceAggregated) Encode() []byte { - buf := make([]byte, 141) - buf[0] = 110 - p := 1 - p = WriteUint(msg.TimestampStart, buf, p) - p = WriteUint(msg.TimestampEnd, buf, p) - p = WriteUint(msg.MinFPS, buf, p) - p = WriteUint(msg.AvgFPS, buf, p) - p = WriteUint(msg.MaxFPS, buf, p) - p = WriteUint(msg.MinCPU, buf, p) - p = WriteUint(msg.AvgCPU, buf, p) - p = WriteUint(msg.MaxCPU, buf, p) - p = WriteUint(msg.MinMemory, buf, p) - p = WriteUint(msg.AvgMemory, buf, p) - p = WriteUint(msg.MaxMemory, buf, p) - p = WriteUint(msg.MinBattery, buf, p) - p = WriteUint(msg.AvgBattery, buf, p) - p = WriteUint(msg.MaxBattery, buf, p) - return buf[:p] + buf := make([]byte, 141) + buf[0] = 110 + p := 1 + p = WriteUint(msg.TimestampStart, buf, p) + p = WriteUint(msg.TimestampEnd, buf, p) + p = WriteUint(msg.MinFPS, buf, p) + p = WriteUint(msg.AvgFPS, buf, p) + p = WriteUint(msg.MaxFPS, buf, p) + p = WriteUint(msg.MinCPU, buf, p) + p = WriteUint(msg.AvgCPU, buf, p) + p = WriteUint(msg.MaxCPU, buf, p) + p = WriteUint(msg.MinMemory, buf, p) + p = WriteUint(msg.AvgMemory, buf, p) + p = WriteUint(msg.MaxMemory, buf, p) + p = WriteUint(msg.MinBattery, buf, p) + p = WriteUint(msg.AvgBattery, buf, p) + p = WriteUint(msg.MaxBattery, buf, p) + return buf[:p] +} + +func (msg *IOSPerformanceAggregated) TypeID() int { + return 110 } type IOSIssueEvent struct { - *meta - Timestamp uint64 - Type string - ContextString string - Context string - Payload string + message + Timestamp uint64 + Type string + ContextString string + Context string + Payload string } func (msg *IOSIssueEvent) Encode() []byte { - buf := make([]byte, 51+len(msg.Type)+len(msg.ContextString)+len(msg.Context)+len(msg.Payload)) - buf[0] = 111 - p := 1 - p = WriteUint(msg.Timestamp, buf, p) - p = WriteString(msg.Type, buf, p) - p = WriteString(msg.ContextString, buf, p) - p = WriteString(msg.Context, buf, p) - p = WriteString(msg.Payload, buf, p) - return buf[:p] + buf := make([]byte, 51+len(msg.Type)+len(msg.ContextString)+len(msg.Context)+len(msg.Payload)) + buf[0] = 111 + p := 1 + p = WriteUint(msg.Timestamp, buf, p) + p = WriteString(msg.Type, buf, p) + p = WriteString(msg.ContextString, buf, p) + p = WriteString(msg.Context, buf, p) + p = WriteString(msg.Payload, buf, p) + return buf[:p] +} + +func (msg *IOSIssueEvent) TypeID() int { + return 111 } diff --git a/backend/pkg/messages/performance/performance.go b/backend/pkg/messages/performance/performance.go index 4cfb28045..27e28215e 100644 --- a/backend/pkg/messages/performance/performance.go +++ b/backend/pkg/messages/performance/performance.go @@ -4,7 +4,6 @@ import ( "math" ) - func TimeDiff(t1 uint64, t2 uint64) uint64 { if t1 < t2 { return 0 @@ -30,4 +29,4 @@ func CPURateFromTickRate(tickRate float64) uint64 { func CPURate(ticks int64, dt uint64) uint64 { return CPURateFromTickRate(TickRate(ticks, dt)) -} \ No newline at end of file +} diff --git a/backend/pkg/messages/primitives.go b/backend/pkg/messages/primitives.go index 70952eeab..8687ef413 100644 --- a/backend/pkg/messages/primitives.go +++ b/backend/pkg/messages/primitives.go @@ -1,9 +1,9 @@ package messages import ( + "encoding/json" "errors" "io" - "encoding/json" "log" ) @@ -37,7 +37,7 @@ func ReadData(reader io.Reader) ([]byte, error) { } return p, nil } - + func ReadUint(reader io.Reader) (uint64, error) { var x uint64 var s uint @@ -152,4 +152,4 @@ func WriteJson(v interface{}, buf []byte, p int) int { return WriteString("null", buf, p) } return WriteData(data, buf, p) -} \ No newline at end of file +} diff --git a/backend/pkg/messages/read-message.go b/backend/pkg/messages/read-message.go index 31512c9c8..59c8e739c 100644 --- a/backend/pkg/messages/read-message.go +++ b/backend/pkg/messages/read-message.go @@ -2,1430 +2,1430 @@ package messages import ( - "fmt" - "io" + "fmt" + "io" ) func ReadMessage(reader io.Reader) (Message, error) { - t, err := ReadUint(reader) - if err != nil { - return nil, err - } - switch t { - - case 80: - msg := &BatchMeta{meta: &meta{TypeID: 80}} - 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, nil - - case 0: - msg := &Timestamp{meta: &meta{TypeID: 0}} - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, nil - - case 1: - msg := &SessionStart{meta: &meta{TypeID: 1}} - 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, nil - - case 2: - msg := &SessionDisconnect{meta: &meta{TypeID: 2}} - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, nil - - case 3: - msg := &SessionEnd{meta: &meta{TypeID: 3}} - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, nil - - case 4: - msg := &SetPageLocation{meta: &meta{TypeID: 4}} - 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, nil - - case 5: - msg := &SetViewportSize{meta: &meta{TypeID: 5}} - if msg.Width, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Height, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, nil - - case 6: - msg := &SetViewportScroll{meta: &meta{TypeID: 6}} - if msg.X, err = ReadInt(reader); err != nil { - return nil, err - } - if msg.Y, err = ReadInt(reader); err != nil { - return nil, err - } - return msg, nil - - case 7: - msg := &CreateDocument{meta: &meta{TypeID: 7}} - - return msg, nil - - case 8: - msg := &CreateElementNode{meta: &meta{TypeID: 8}} - 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, nil - - case 9: - msg := &CreateTextNode{meta: &meta{TypeID: 9}} - 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, nil - - case 10: - msg := &MoveNode{meta: &meta{TypeID: 10}} - 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, nil - - case 11: - msg := &RemoveNode{meta: &meta{TypeID: 11}} - if msg.ID, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, nil - - case 12: - msg := &SetNodeAttribute{meta: &meta{TypeID: 12}} - 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, nil - - case 13: - msg := &RemoveNodeAttribute{meta: &meta{TypeID: 13}} - if msg.ID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Name, err = ReadString(reader); err != nil { - return nil, err - } - return msg, nil - - case 14: - msg := &SetNodeData{meta: &meta{TypeID: 14}} - if msg.ID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Data, err = ReadString(reader); err != nil { - return nil, err - } - return msg, nil - - case 15: - msg := &SetCSSData{meta: &meta{TypeID: 15}} - if msg.ID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Data, err = ReadString(reader); err != nil { - return nil, err - } - return msg, nil - - case 16: - msg := &SetNodeScroll{meta: &meta{TypeID: 16}} - 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, nil - - case 17: - msg := &SetInputTarget{meta: &meta{TypeID: 17}} - if msg.ID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Label, err = ReadString(reader); err != nil { - return nil, err - } - return msg, nil - - case 18: - msg := &SetInputValue{meta: &meta{TypeID: 18}} - 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, nil - - case 19: - msg := &SetInputChecked{meta: &meta{TypeID: 19}} - if msg.ID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Checked, err = ReadBoolean(reader); err != nil { - return nil, err - } - return msg, nil - - case 20: - msg := &MouseMove{meta: &meta{TypeID: 20}} - if msg.X, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Y, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, nil - - case 21: - msg := &MouseClickDepricated{meta: &meta{TypeID: 21}} - 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, nil - - case 22: - msg := &ConsoleLog{meta: &meta{TypeID: 22}} - if msg.Level, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Value, err = ReadString(reader); err != nil { - return nil, err - } - return msg, nil - - case 23: - msg := &PageLoadTiming{meta: &meta{TypeID: 23}} - 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, nil - - case 24: - msg := &PageRenderTiming{meta: &meta{TypeID: 24}} - 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, nil - - case 25: - msg := &JSException{meta: &meta{TypeID: 25}} - 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, nil - - case 26: - msg := &RawErrorEvent{meta: &meta{TypeID: 26}} - 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, nil - - case 27: - msg := &RawCustomEvent{meta: &meta{TypeID: 27}} - if msg.Name, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Payload, err = ReadString(reader); err != nil { - return nil, err - } - return msg, nil - - case 28: - msg := &UserID{meta: &meta{TypeID: 28}} - if msg.ID, err = ReadString(reader); err != nil { - return nil, err - } - return msg, nil - - case 29: - msg := &UserAnonymousID{meta: &meta{TypeID: 29}} - if msg.ID, err = ReadString(reader); err != nil { - return nil, err - } - return msg, nil - - case 30: - msg := &Metadata{meta: &meta{TypeID: 30}} - if msg.Key, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Value, err = ReadString(reader); err != nil { - return nil, err - } - return msg, nil - - case 31: - msg := &PageEvent{meta: &meta{TypeID: 31}} - 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, nil - - case 32: - msg := &InputEvent{meta: &meta{TypeID: 32}} - 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, nil - - case 33: - msg := &ClickEvent{meta: &meta{TypeID: 33}} - 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, nil - - case 34: - msg := &ErrorEvent{meta: &meta{TypeID: 34}} - 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, nil - - case 35: - msg := &ResourceEvent{meta: &meta{TypeID: 35}} - 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, nil - - case 36: - msg := &CustomEvent{meta: &meta{TypeID: 36}} - 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, nil - - case 37: - msg := &CSSInsertRule{meta: &meta{TypeID: 37}} - 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, nil - - case 38: - msg := &CSSDeleteRule{meta: &meta{TypeID: 38}} - if msg.ID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Index, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, nil - - case 39: - msg := &Fetch{meta: &meta{TypeID: 39}} - 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, nil - - case 40: - msg := &Profiler{meta: &meta{TypeID: 40}} - 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, nil - - case 41: - msg := &OTable{meta: &meta{TypeID: 41}} - if msg.Key, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Value, err = ReadString(reader); err != nil { - return nil, err - } - return msg, nil - - case 42: - msg := &StateAction{meta: &meta{TypeID: 42}} - if msg.Type, err = ReadString(reader); err != nil { - return nil, err - } - return msg, nil - - case 43: - msg := &StateActionEvent{meta: &meta{TypeID: 43}} - 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, nil - - case 44: - msg := &Redux{meta: &meta{TypeID: 44}} - 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, nil - - case 45: - msg := &Vuex{meta: &meta{TypeID: 45}} - if msg.Mutation, err = ReadString(reader); err != nil { - return nil, err - } - if msg.State, err = ReadString(reader); err != nil { - return nil, err - } - return msg, nil - - case 46: - msg := &MobX{meta: &meta{TypeID: 46}} - if msg.Type, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Payload, err = ReadString(reader); err != nil { - return nil, err - } - return msg, nil - - case 47: - msg := &NgRx{meta: &meta{TypeID: 47}} - 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, nil - - case 48: - msg := &GraphQL{meta: &meta{TypeID: 48}} - 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, nil - - case 49: - msg := &PerformanceTrack{meta: &meta{TypeID: 49}} - 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, nil - - case 50: - msg := &GraphQLEvent{meta: &meta{TypeID: 50}} - 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, nil - - case 51: - msg := &FetchEvent{meta: &meta{TypeID: 51}} - 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, nil - - case 52: - msg := &DOMDrop{meta: &meta{TypeID: 52}} - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, nil - - case 53: - msg := &ResourceTiming{meta: &meta{TypeID: 53}} - 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, nil - - case 54: - msg := &ConnectionInformation{meta: &meta{TypeID: 54}} - if msg.Downlink, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.Type, err = ReadString(reader); err != nil { - return nil, err - } - return msg, nil - - case 55: - msg := &SetPageVisibility{meta: &meta{TypeID: 55}} - if msg.hidden, err = ReadBoolean(reader); err != nil { - return nil, err - } - return msg, nil - - case 56: - msg := &PerformanceTrackAggr{meta: &meta{TypeID: 56}} - 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, nil - - case 59: - msg := &LongTask{meta: &meta{TypeID: 59}} - 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, nil - - case 60: - msg := &SetNodeAttributeURLBased{meta: &meta{TypeID: 60}} - 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, nil - - case 61: - msg := &SetCSSDataURLBased{meta: &meta{TypeID: 61}} - 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, nil - - case 62: - msg := &IssueEvent{meta: &meta{TypeID: 62}} - 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, nil - - case 63: - msg := &TechnicalInfo{meta: &meta{TypeID: 63}} - if msg.Type, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Value, err = ReadString(reader); err != nil { - return nil, err - } - return msg, nil - - case 64: - msg := &CustomIssue{meta: &meta{TypeID: 64}} - if msg.Name, err = ReadString(reader); err != nil { - return nil, err - } - if msg.Payload, err = ReadString(reader); err != nil { - return nil, err - } - return msg, nil - - case 65: - msg := &PageClose{meta: &meta{TypeID: 65}} - - return msg, nil - - case 66: - msg := &AssetCache{meta: &meta{TypeID: 66}} - if msg.URL, err = ReadString(reader); err != nil { - return nil, err - } - return msg, nil - - case 67: - msg := &CSSInsertRuleURLBased{meta: &meta{TypeID: 67}} - 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, nil - - case 69: - msg := &MouseClick{meta: &meta{TypeID: 69}} - 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, nil - - case 70: - msg := &CreateIFrameDocument{meta: &meta{TypeID: 70}} - if msg.FrameID, err = ReadUint(reader); err != nil { - return nil, err - } - if msg.ID, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, nil - - case 107: - msg := &IOSBatchMeta{meta: &meta{TypeID: 107}} - 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, nil - - case 90: - msg := &IOSSessionStart{meta: &meta{TypeID: 90}} - 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, nil - - case 91: - msg := &IOSSessionEnd{meta: &meta{TypeID: 91}} - if msg.Timestamp, err = ReadUint(reader); err != nil { - return nil, err - } - return msg, nil - - case 92: - msg := &IOSMetadata{meta: &meta{TypeID: 92}} - 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, nil - - case 93: - msg := &IOSCustomEvent{meta: &meta{TypeID: 93}} - 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, nil - - case 94: - msg := &IOSUserID{meta: &meta{TypeID: 94}} - 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, nil - - case 95: - msg := &IOSUserAnonymousID{meta: &meta{TypeID: 95}} - 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, nil - - case 96: - msg := &IOSScreenChanges{meta: &meta{TypeID: 96}} - 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, nil - - case 97: - msg := &IOSCrash{meta: &meta{TypeID: 97}} - 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, nil - - case 98: - msg := &IOSScreenEnter{meta: &meta{TypeID: 98}} - 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, nil - - case 99: - msg := &IOSScreenLeave{meta: &meta{TypeID: 99}} - 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, nil - - case 100: - msg := &IOSClickEvent{meta: &meta{TypeID: 100}} - 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, nil - - case 101: - msg := &IOSInputEvent{meta: &meta{TypeID: 101}} - 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, nil - - case 102: - msg := &IOSPerformanceEvent{meta: &meta{TypeID: 102}} - 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, nil - - case 103: - msg := &IOSLog{meta: &meta{TypeID: 103}} - 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, nil - - case 104: - msg := &IOSInternalError{meta: &meta{TypeID: 104}} - 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, nil - - case 105: - msg := &IOSNetworkCall{meta: &meta{TypeID: 105}} - 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, nil - - case 110: - msg := &IOSPerformanceAggregated{meta: &meta{TypeID: 110}} - 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, nil - - case 111: - msg := &IOSIssueEvent{meta: &meta{TypeID: 111}} - 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, nil - - } - return nil, fmt.Errorf("Unknown message code: %v", t) + t, err := ReadUint(reader) + if err != nil { + return nil, err + } + switch t { + + case 80: + 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, nil + + case 0: + msg := &Timestamp{} + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, nil + + case 1: + 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, nil + + case 2: + msg := &SessionDisconnect{} + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, nil + + case 3: + msg := &SessionEnd{} + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, nil + + case 4: + 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, nil + + case 5: + 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, nil + + case 6: + 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, nil + + case 7: + msg := &CreateDocument{} + + return msg, nil + + case 8: + 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, nil + + case 9: + 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, nil + + case 10: + 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, nil + + case 11: + msg := &RemoveNode{} + if msg.ID, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, nil + + case 12: + 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, nil + + case 13: + 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, nil + + case 14: + 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, nil + + case 15: + 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, nil + + case 16: + 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, nil + + case 17: + 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, nil + + case 18: + 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, nil + + case 19: + 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, nil + + case 20: + 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, nil + + case 21: + 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, nil + + case 22: + 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, nil + + case 23: + 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, nil + + case 24: + 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, nil + + case 25: + 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, nil + + case 26: + msg := &RawErrorEvent{} + 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, nil + + case 27: + 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, nil + + case 28: + msg := &UserID{} + if msg.ID, err = ReadString(reader); err != nil { + return nil, err + } + return msg, nil + + case 29: + msg := &UserAnonymousID{} + if msg.ID, err = ReadString(reader); err != nil { + return nil, err + } + return msg, nil + + case 30: + 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, nil + + case 31: + 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, nil + + case 32: + 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, nil + + case 33: + 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, nil + + case 34: + 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, nil + + case 35: + 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, nil + + case 36: + 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, nil + + case 37: + 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, nil + + case 38: + 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, nil + + case 39: + 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, nil + + case 40: + 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, nil + + case 41: + 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, nil + + case 42: + msg := &StateAction{} + if msg.Type, err = ReadString(reader); err != nil { + return nil, err + } + return msg, nil + + case 43: + 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, nil + + case 44: + 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, nil + + case 45: + 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, nil + + case 46: + 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, nil + + case 47: + 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, nil + + case 48: + 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, nil + + case 49: + 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, nil + + case 50: + 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, nil + + case 51: + 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, nil + + case 52: + msg := &DOMDrop{} + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, nil + + case 53: + 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, nil + + case 54: + 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, nil + + case 55: + msg := &SetPageVisibility{} + if msg.hidden, err = ReadBoolean(reader); err != nil { + return nil, err + } + return msg, nil + + case 56: + 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, nil + + case 59: + 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, nil + + case 60: + 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, nil + + case 61: + 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, nil + + case 62: + 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, nil + + case 63: + 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, nil + + case 64: + 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, nil + + case 65: + msg := &PageClose{} + + return msg, nil + + case 66: + msg := &AssetCache{} + if msg.URL, err = ReadString(reader); err != nil { + return nil, err + } + return msg, nil + + case 67: + 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, nil + + case 69: + 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, nil + + case 70: + 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, nil + + case 107: + 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, nil + + case 90: + 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, nil + + case 91: + msg := &IOSSessionEnd{} + if msg.Timestamp, err = ReadUint(reader); err != nil { + return nil, err + } + return msg, nil + + case 92: + 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, nil + + case 93: + 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, nil + + case 94: + 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, nil + + case 95: + 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, nil + + case 96: + 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, nil + + case 97: + 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, nil + + case 98: + 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, nil + + case 99: + 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, nil + + case 100: + 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, nil + + case 101: + 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, nil + + case 102: + 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, nil + + case 103: + 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, nil + + case 104: + 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, nil + + case 105: + 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, nil + + case 110: + 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, nil + + case 111: + 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, nil + + } + return nil, fmt.Errorf("Unknown message code: %v", t) } diff --git a/backend/pkg/pprof/pprof.go b/backend/pkg/pprof/pprof.go index a05080178..8ea1c1b5f 100644 --- a/backend/pkg/pprof/pprof.go +++ b/backend/pkg/pprof/pprof.go @@ -8,6 +8,6 @@ import ( func StartProfilingServer() { go func() { - log.Println(http.ListenAndServe("localhost:6060", nil)) + log.Println(http.ListenAndServe(":6060", nil)) }() } diff --git a/backend/pkg/queue/messages.go b/backend/pkg/queue/messages.go index 0ab184ee6..9b9ff43a5 100644 --- a/backend/pkg/queue/messages.go +++ b/backend/pkg/queue/messages.go @@ -1,6 +1,7 @@ package queue import ( + "bytes" "log" "openreplay/backend/pkg/messages" @@ -9,7 +10,7 @@ import ( func NewMessageConsumer(group string, topics []string, handler types.DecodedMessageHandler, autoCommit bool) types.Consumer { return NewConsumer(group, topics, func(sessionID uint64, value []byte, meta *types.Meta) { - if err := messages.ReadBatch(value, func(msg messages.Message) { + if err := messages.ReadBatchReader(bytes.NewReader(value), func(msg messages.Message) { handler(sessionID, msg, meta) }); err != nil { log.Printf("Decode error: %v\n", err) diff --git a/backend/pkg/redisstream/producer.go b/backend/pkg/redisstream/producer.go index e67200a4f..d5afc63b3 100644 --- a/backend/pkg/redisstream/producer.go +++ b/backend/pkg/redisstream/producer.go @@ -6,25 +6,24 @@ import ( "openreplay/backend/pkg/env" ) - type Producer struct { - redis *redis.Client - maxLenApprox int64 + redis *redis.Client + maxLenApprox int64 } func NewProducer() *Producer { return &Producer{ - redis: getRedisClient(), + redis: getRedisClient(), maxLenApprox: int64(env.Uint64("REDIS_STREAMS_MAX_LEN")), } } func (p *Producer) Produce(topic string, key uint64, value []byte) error { - args := &redis.XAddArgs{ + args := &redis.XAddArgs{ Stream: topic, Values: map[string]interface{}{ "sessionID": key, - "value": value, + "value": value, }, } args.MaxLenApprox = p.maxLenApprox @@ -35,7 +34,7 @@ func (p *Producer) Produce(topic string, key uint64, value []byte) error { } return nil } - + func (p *Producer) Close(_ int) { // noop } diff --git a/backend/pkg/redisstream/redis.go b/backend/pkg/redisstream/redis.go index dea4afe9b..7dba0b537 100644 --- a/backend/pkg/redisstream/redis.go +++ b/backend/pkg/redisstream/redis.go @@ -2,15 +2,13 @@ package redisstream import ( "log" - + "github.com/go-redis/redis" "openreplay/backend/pkg/env" ) - -var redisClient *redis.Client - +var redisClient *redis.Client func getRedisClient() *redis.Client { if redisClient != nil { @@ -23,4 +21,4 @@ func getRedisClient() *redis.Client { log.Fatalln(err) } return redisClient -} \ No newline at end of file +} diff --git a/backend/pkg/storage/s3.go b/backend/pkg/storage/s3.go index 0f55e3851..408dc1864 100644 --- a/backend/pkg/storage/s3.go +++ b/backend/pkg/storage/s3.go @@ -2,8 +2,8 @@ package storage import ( "io" - "strconv" "sort" + "strconv" _s3 "github.com/aws/aws-sdk-go/service/s3" "github.com/aws/aws-sdk-go/service/s3/s3manager" @@ -12,18 +12,17 @@ import ( ) type S3 struct { - uploader *s3manager.Uploader - svc *_s3.S3 - bucket *string + uploader *s3manager.Uploader + svc *_s3.S3 + bucket *string } - func NewS3(region string, bucket string) *S3 { sess := env.AWSSessionOnRegion(region) return &S3{ uploader: s3manager.NewUploader(sess), - svc: _s3.New(sess), // AWS Docs: "These clients are safe to use concurrently." - bucket: &bucket, + svc: _s3.New(sess), // AWS Docs: "These clients are safe to use concurrently." + bucket: &bucket, } } @@ -35,14 +34,14 @@ func (s3 *S3) Upload(reader io.Reader, key string, contentType string, gzipped b contentEncoding = &gzipStr } _, err := s3.uploader.Upload(&s3manager.UploadInput{ - Body: reader, - Bucket: s3.bucket, - Key: &key, - ContentType: &contentType, - CacheControl: &cacheControl, + Body: reader, + Bucket: s3.bucket, + Key: &key, + ContentType: &contentType, + CacheControl: &cacheControl, ContentEncoding: contentEncoding, - }) - return err + }) + return err } func (s3 *S3) Get(key string) (io.ReadCloser, error) { @@ -67,8 +66,8 @@ func (s3 *S3) Exists(key string) bool { return false } - const MAX_RETURNING_COUNT = 40 + func (s3 *S3) GetFrequentlyUsedKeys(projectID uint64) ([]string, error) { prefix := strconv.FormatUint(projectID, 10) + "/" output, err := s3.svc.ListObjectsV2(&_s3.ListObjectsV2Input{ @@ -82,7 +81,7 @@ func (s3 *S3) GetFrequentlyUsedKeys(projectID uint64) ([]string, error) { list := output.Contents max := len(list) - if (max > MAX_RETURNING_COUNT) { + if max > MAX_RETURNING_COUNT { max = MAX_RETURNING_COUNT sort.Slice(list, func(i, j int) bool { return list[i].LastModified.After(*(list[j].LastModified)) @@ -91,8 +90,8 @@ func (s3 *S3) GetFrequentlyUsedKeys(projectID uint64) ([]string, error) { var keyList []string s := len(prefix) - for _, obj := range list[:max] { - keyList = append(keyList, (*obj.Key)[s:]) - } - return keyList, nil -} \ No newline at end of file + for _, obj := range list[:max] { + keyList = append(keyList, (*obj.Key)[s:]) + } + return keyList, nil +} diff --git a/backend/pkg/url/assets/css.go b/backend/pkg/url/assets/css.go index 3bd486bc7..dda8755d7 100644 --- a/backend/pkg/url/assets/css.go +++ b/backend/pkg/url/assets/css.go @@ -39,7 +39,7 @@ func unquote(str string) (string, string) { } func ExtractURLsFromCSS(css string) []string { - indexes := cssUrlsIndex(css) + indexes := cssUrlsIndex(css) urls := make([]string, len(indexes)) for _, idx := range indexes { diff --git a/backend/pkg/url/method.go b/backend/pkg/url/method.go index e7dd9eb49..31e654fde 100644 --- a/backend/pkg/url/method.go +++ b/backend/pkg/url/method.go @@ -1,12 +1,12 @@ package url -var METHODS = []string{ "GET", "HEAD", "POST" , "PUT" , "DELETE" , "CONNECT" , "OPTIONS" , "TRACE" , "PATCH" } +var METHODS = []string{"GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH"} func EnsureMethod(method string) string { for _, m := range METHODS { - if m == method { - return method - } + if m == method { + return method + } } return "" -} \ No newline at end of file +} diff --git a/backend/services/assets/jsexception.go b/backend/services/assets/jsexception.go index ce5852bd5..c0b26e0db 100644 --- a/backend/services/assets/jsexception.go +++ b/backend/services/assets/jsexception.go @@ -1,16 +1,14 @@ -package main +package main import ( "encoding/json" "strings" ) - type frame struct { FileName string `json:"fileName"` } - func extractJSExceptionSources(payload *string) ([]string, error) { var frameList []frame err := json.Unmarshal([]byte(*payload), &frameList) @@ -25,8 +23,8 @@ func extractJSExceptionSources(payload *string) ([]string, error) { fn := strings.Split(f.FileName, "?")[0] if strings.HasPrefix(fn, "http") && !presentedFileName[fn] { fileNamesList = append(fileNamesList, f.FileName) - presentedFileName[fn] = true + presentedFileName[fn] = true } } return fileNamesList, nil -} \ No newline at end of file +} diff --git a/backend/services/assets/main.go b/backend/services/assets/main.go index 664dc5b09..259918395 100644 --- a/backend/services/assets/main.go +++ b/backend/services/assets/main.go @@ -66,6 +66,7 @@ func main() { os.Exit(0) case err := <-cacher.Errors: log.Printf("Error while caching: %v", err) + // TODO: notify user case <-tick: cacher.UpdateTimeouts() default: diff --git a/backend/services/db/build_hack b/backend/services/db/build_hack new file mode 100644 index 000000000..e69de29bb diff --git a/backend/services/db/main.go b/backend/services/db/main.go deleted file mode 100644 index 2ad6e4aa8..000000000 --- a/backend/services/db/main.go +++ /dev/null @@ -1,109 +0,0 @@ -package main - -import ( - "log" - "time" - - "os" - "os/signal" - "syscall" - - "openreplay/backend/pkg/db/cache" - "openreplay/backend/pkg/db/postgres" - "openreplay/backend/pkg/env" - logger "openreplay/backend/pkg/log" - "openreplay/backend/pkg/messages" - "openreplay/backend/pkg/queue" - "openreplay/backend/pkg/queue/types" - "openreplay/backend/services/db/heuristics" -) - -var pg *cache.PGCache - -func main() { - log.SetFlags(log.LstdFlags | log.LUTC | log.Llongfile) - - initStats() - pg = cache.NewPGCache(postgres.NewConn(env.String("POSTGRES_STRING")), 1000*60*20) - defer pg.Close() - - heurFinder := heuristics.NewHandler() - - statsLogger := logger.NewQueueStats(env.Int("LOG_QUEUE_STATS_INTERVAL_SEC")) - - consumer := queue.NewMessageConsumer( - env.String("GROUP_DB"), - []string{ - env.String("TOPIC_RAW_IOS"), - env.String("TOPIC_TRIGGER"), - }, - func(sessionID uint64, msg messages.Message, meta *types.Meta) { - statsLogger.HandleAndLog(sessionID, meta) - - if err := 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 err != nil { - // Might happen due to the assets-related message TODO: log only if session is necessary for this kind of message - log.Printf("Error on session retrieving from cache: %v, SessionID: %v, Message: %v", err, sessionID, msg) - return - } - - err = insertStats(session, msg) - if err != nil { - log.Printf("Stats Insertion Error %v; Session: %v, Message: %v", err, session, msg) - } - - heurFinder.HandleMessage(session, msg) - heurFinder.IterateSessionReadyMessages(sessionID, func(msg messages.Message) { - // TODO: DRY code (carefully with the return statement logic) - if err := 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 := insertStats(session, msg); err != nil { - log.Printf("Stats Insertion Error %v; Session: %v, Message %v", err, session, msg) - } - }) - }, - false, - ) - - sigchan := make(chan os.Signal, 1) - signal.Notify(sigchan, syscall.SIGINT, syscall.SIGTERM) - - tick := time.Tick(15 * time.Second) - - log.Printf("Db service started\n") - for { - select { - case sig := <-sigchan: - log.Printf("Caught signal %v: terminating\n", sig) - consumer.Close() - os.Exit(0) - case <-tick: - pg.CommitBatches() - if err := commitStats(); err != nil { - log.Printf("Error on stats commit: %v", err) - } - // TODO?: separate stats & regular messages - if err := consumer.Commit(); err != nil { - log.Printf("Error on consumer commit: %v", err) - } - default: - err := consumer.ConsumeNext() - if err != nil { - log.Fatalf("Error on consumption: %v", err) // TODO: is always fatal? - } - } - } - -} diff --git a/backend/services/db/messages.go b/backend/services/db/messages.go deleted file mode 100644 index d3e4ae1ed..000000000 --- a/backend/services/db/messages.go +++ /dev/null @@ -1,68 +0,0 @@ -package main - -import ( - . "openreplay/backend/pkg/messages" -) - -func insertMessage(sessionID uint64, msg Message) error { - switch m := msg.(type) { - // Common - case *Metadata: - return pg.InsertMetadata(sessionID, m) - case *IssueEvent: - return pg.InsertIssueEvent(sessionID, m) - //TODO: message adapter (transformer) (at the level of pkg/message) for types: - // case *IOSMetadata, *IOSIssueEvent and others - - // Web - case *SessionStart: - return pg.InsertWebSessionStart(sessionID, m) - case *SessionEnd: - return pg.InsertWebSessionEnd(sessionID, m) - case *UserID: - return pg.InsertWebUserID(sessionID, m) - case *UserAnonymousID: - return pg.InsertWebUserAnonymousID(sessionID, m) - case *CustomEvent: - return pg.InsertWebCustomEvent(sessionID, m) - case *ClickEvent: - return pg.InsertWebClickEvent(sessionID, m) - case *InputEvent: - return pg.InsertWebInputEvent(sessionID, m) - // Unique Web messages - // case *ResourceEvent: - // return pg.InsertWebResourceEvent(sessionID, m) - case *PageEvent: - return pg.InsertWebPageEvent(sessionID, m) - case *ErrorEvent: - return pg.InsertWebErrorEvent(sessionID, m) - case *FetchEvent: - return pg.InsertWebFetchEvent(sessionID, m) - case *GraphQLEvent: - return pg.InsertWebGraphQLEvent(sessionID, m) - - // IOS - case *IOSSessionStart: - return pg.InsertIOSSessionStart(sessionID, m) - case *IOSSessionEnd: - return pg.InsertIOSSessionEnd(sessionID, m) - case *IOSUserID: - return pg.InsertIOSUserID(sessionID, m) - case *IOSUserAnonymousID: - return pg.InsertIOSUserAnonymousID(sessionID, m) - case *IOSCustomEvent: - return pg.InsertIOSCustomEvent(sessionID, m) - case *IOSClickEvent: - return pg.InsertIOSClickEvent(sessionID, m) - case *IOSInputEvent: - return pg.InsertIOSInputEvent(sessionID, m) - // Unique IOS messages - case *IOSNetworkCall: - return pg.InsertIOSNetworkCall(sessionID, m) - case *IOSScreenEnter: - return pg.InsertIOSScreenEnter(sessionID, m) - case *IOSCrash: - return pg.InsertIOSCrash(sessionID, m) - } - return nil // "Not implemented" -} diff --git a/backend/services/db/stats.go b/backend/services/db/stats.go deleted file mode 100644 index 81abf1b91..000000000 --- a/backend/services/db/stats.go +++ /dev/null @@ -1,35 +0,0 @@ -package main - -import ( - - . "openreplay/backend/pkg/messages" - . "openreplay/backend/pkg/db/types" -) - -func initStats() { - // noop -} - - -func insertStats(session *Session, msg Message) error { - switch m := msg.(type) { - // Web - case *PerformanceTrackAggr: - return pg.InsertWebStatsPerformance(session.SessionID, m) - case *ResourceEvent: - return pg.InsertWebStatsResourceEvent(session.SessionID, m) - case *LongTask: - return pg.InsertWebStatsLongtask(session.SessionID, m) - - // IOS - // case *IOSPerformanceAggregated: - // return pg.InsertIOSPerformanceAggregated(session, m) - // case *IOSNetworkCall: - // return pg.InsertIOSNetworkCall(session, m) - } - return nil -} - -func commitStats() error { - return nil -} diff --git a/backend/services/ender/builder/builder.go b/backend/services/ender/builder/builder.go index 9c2067985..1a89f67b6 100644 --- a/backend/services/ender/builder/builder.go +++ b/backend/services/ender/builder/builder.go @@ -218,14 +218,16 @@ func (b *builder) handleMessage(message Message, messageID uint64) { Type: tp, Success: success, }) - if !success && tp == "fetch" { + if !success { + issueType := "missing_resource" + if tp == "fetch" { + issueType = "bad_request" + } b.appendReadyMessage(&IssueEvent{ - Type: "bad_request", + Type: issueType, MessageID: messageID, Timestamp: msg.Timestamp, ContextString: msg.URL, - Context: "", - Payload: "", }) } case *RawCustomEvent: @@ -254,6 +256,14 @@ func (b *builder) handleMessage(message Message, messageID uint64) { Status: msg.Status, Duration: msg.Duration, }) + if msg.Status >= 400 { + b.appendReadyMessage(&IssueEvent{ + Type: "bad_request", + MessageID: messageID, + Timestamp: msg.Timestamp, + ContextString: msg.URL, + }) + } case *GraphQL: b.appendReadyMessage(&GraphQLEvent{ MessageID: messageID, diff --git a/backend/services/ender/builder/builderMap.go b/backend/services/ender/builder/builderMap.go index 6ab3c3ac7..3f3e4d6e3 100644 --- a/backend/services/ender/builder/builderMap.go +++ b/backend/services/ender/builder/builderMap.go @@ -6,7 +6,6 @@ import ( type builderMap map[uint64]*builder - func NewBuilderMap() builderMap { return make(builderMap) } @@ -28,8 +27,10 @@ func (m builderMap) HandleMessage(sessionID uint64, msg Message, messageID uint6 } func (m builderMap) IterateSessionReadyMessages(sessionID uint64, operatingTs int64, iter func(msg Message)) { - b, ok := m[ sessionID ] - if !ok { return } + b, ok := m[sessionID] + if !ok { + return + } sessionEnded := b.checkTimeouts(operatingTs) b.iterateReadyMessage(iter) if sessionEnded { @@ -48,5 +49,3 @@ func (m builderMap) IterateReadyMessages(operatingTs int64, iter func(sessionID } } } - - diff --git a/backend/services/ender/builder/clikRageDetector.go b/backend/services/ender/builder/clikRageDetector.go index 116d57071..f25efbcd9 100644 --- a/backend/services/ender/builder/clikRageDetector.go +++ b/backend/services/ender/builder/clikRageDetector.go @@ -1,34 +1,32 @@ package builder import ( - "encoding/json" + "encoding/json" . "openreplay/backend/pkg/messages" ) - const CLICK_TIME_DIFF = 300 const MIN_CLICKS_IN_A_ROW = 3 type clickRageDetector struct { - lastTimestamp uint64 - lastLabel string + lastTimestamp uint64 + lastLabel string firstInARawTimestamp uint64 firstInARawMessageId uint64 - countsInARow int + countsInARow int } - func (crd *clickRageDetector) Build() *IssueEvent { var i *IssueEvent if crd.countsInARow >= MIN_CLICKS_IN_A_ROW { - payload, _ := json.Marshal(struct{Count int }{crd.countsInARow,}) + payload, _ := json.Marshal(struct{ Count int }{crd.countsInARow}) i = &IssueEvent{ - Type: "click_rage", + Type: "click_rage", ContextString: crd.lastLabel, - Payload: string(payload), // TODO: json encoder - Timestamp: crd.firstInARawTimestamp, - MessageID: crd.firstInARawMessageId, + Payload: string(payload), // TODO: json encoder + Timestamp: crd.firstInARawTimestamp, + MessageID: crd.firstInARawMessageId, } } crd.lastTimestamp = 0 @@ -39,8 +37,8 @@ func (crd *clickRageDetector) Build() *IssueEvent { return i } -func (crd *clickRageDetector) HandleMouseClick(msg *MouseClick, messageID uint64, timestamp uint64) *IssueEvent { - if crd.lastTimestamp + CLICK_TIME_DIFF > timestamp && crd.lastLabel == msg.Label { +func (crd *clickRageDetector) HandleMouseClick(msg *MouseClick, messageID uint64, timestamp uint64) *IssueEvent { + if crd.lastTimestamp+CLICK_TIME_DIFF > timestamp && crd.lastLabel == msg.Label { crd.lastTimestamp = timestamp crd.countsInARow += 1 return nil @@ -54,4 +52,4 @@ func (crd *clickRageDetector) HandleMouseClick(msg *MouseClick, messageID uint6 crd.countsInARow = 1 } return i -} \ No newline at end of file +} diff --git a/backend/services/ender/builder/cpuIssueFinder.go b/backend/services/ender/builder/cpuIssueFinder.go index be02c280f..1af867ea3 100644 --- a/backend/services/ender/builder/cpuIssueFinder.go +++ b/backend/services/ender/builder/cpuIssueFinder.go @@ -3,20 +3,19 @@ package builder import ( "encoding/json" - "openreplay/backend/pkg/messages/performance" . "openreplay/backend/pkg/messages" + "openreplay/backend/pkg/messages/performance" ) -const CPU_THRESHOLD = 70 // % out of 100 +const CPU_THRESHOLD = 70 // % out of 100 const CPU_MIN_DURATION_TRIGGER = 6 * 1000 - type cpuIssueFinder struct { startTimestamp uint64 startMessageID uint64 - lastTimestamp uint64 - maxRate uint64 - contextString string + lastTimestamp uint64 + maxRate uint64 + contextString string } func (f *cpuIssueFinder) Build() *IssueEvent { @@ -35,16 +34,16 @@ func (f *cpuIssueFinder) Build() *IssueEvent { return nil } - payload, _ := json.Marshal(struct{ + payload, _ := json.Marshal(struct { Duration uint64 - Rate uint64 - }{duration,maxRate}) + Rate uint64 + }{duration, maxRate}) return &IssueEvent{ - Type: "cpu", - Timestamp: timestamp, - MessageID: messageID, + Type: "cpu", + Timestamp: timestamp, + MessageID: messageID, ContextString: f.contextString, - Payload: string(payload), + Payload: string(payload), } } @@ -52,8 +51,6 @@ func (f *cpuIssueFinder) HandleSetPageLocation(msg *SetPageLocation) { f.contextString = msg.URL } - - func (f *cpuIssueFinder) HandlePerformanceTrack(msg *PerformanceTrack, messageID uint64, timestamp uint64) *IssueEvent { dt := performance.TimeDiff(timestamp, f.lastTimestamp) if dt == 0 { @@ -82,5 +79,3 @@ func (f *cpuIssueFinder) HandlePerformanceTrack(msg *PerformanceTrack, messageID return nil } - - diff --git a/backend/services/ender/builder/deadClickDetector.go b/backend/services/ender/builder/deadClickDetector.go index 725b025cb..de977b7bd 100644 --- a/backend/services/ender/builder/deadClickDetector.go +++ b/backend/services/ender/builder/deadClickDetector.go @@ -4,25 +4,23 @@ import ( . "openreplay/backend/pkg/messages" ) - const CLICK_RELATION_TIME = 1400 type deadClickDetector struct { - lastMouseClick *MouseClick - lastTimestamp uint64 - lastMessageID uint64 - inputIDSet map[uint64]bool + lastMouseClick *MouseClick + lastTimestamp uint64 + lastMessageID uint64 + inputIDSet map[uint64]bool } - func (d *deadClickDetector) HandleReaction(timestamp uint64) *IssueEvent { var i *IssueEvent - if d.lastMouseClick != nil && d.lastTimestamp + CLICK_RELATION_TIME < timestamp { + if d.lastMouseClick != nil && d.lastTimestamp+CLICK_RELATION_TIME < timestamp { i = &IssueEvent{ - Type: "dead_click", + Type: "dead_click", ContextString: d.lastMouseClick.Label, - Timestamp: d.lastTimestamp, - MessageID: d.lastMessageID, + Timestamp: d.lastTimestamp, + MessageID: d.lastMessageID, } } d.inputIDSet = nil @@ -53,8 +51,8 @@ func (d *deadClickDetector) HandleMessage(msg Message, messageID uint64, timesta d.lastMouseClick = m d.lastTimestamp = timestamp d.lastMessageID = messageID - case *SetNodeAttribute, - *RemoveNodeAttribute, + case *SetNodeAttribute, + *RemoveNodeAttribute, *CreateElementNode, *CreateTextNode, *MoveNode, @@ -66,5 +64,3 @@ func (d *deadClickDetector) HandleMessage(msg Message, messageID uint64, timesta } return i } - - diff --git a/backend/services/ender/builder/domDropDetector.go b/backend/services/ender/builder/domDropDetector.go index 3366a0163..3643038c1 100644 --- a/backend/services/ender/builder/domDropDetector.go +++ b/backend/services/ender/builder/domDropDetector.go @@ -4,14 +4,13 @@ import ( . "openreplay/backend/pkg/messages" ) - type domDropDetector struct { - removedCount int + removedCount int lastDropTimestamp uint64 } -const DROP_WINDOW = 200 //ms -const CRITICAL_COUNT = 1 // Our login page contains 20. But on crush it removes only roots (1-3 nodes). +const DROP_WINDOW = 200 //ms +const CRITICAL_COUNT = 1 // Our login page contains 20. But on crush it removes only roots (1-3 nodes). func (dd *domDropDetector) HandleNodeCreation() { dd.removedCount = 0 @@ -19,7 +18,7 @@ func (dd *domDropDetector) HandleNodeCreation() { } func (dd *domDropDetector) HandleNodeRemoval(ts uint64) { - if dd.lastDropTimestamp + DROP_WINDOW > ts { + if dd.lastDropTimestamp+DROP_WINDOW > ts { dd.removedCount += 1 } else { dd.removedCount = 1 @@ -27,7 +26,6 @@ func (dd *domDropDetector) HandleNodeRemoval(ts uint64) { dd.lastDropTimestamp = ts } - func (dd *domDropDetector) Build() *DOMDrop { var domDrop *DOMDrop if dd.removedCount >= CRITICAL_COUNT { @@ -39,4 +37,3 @@ func (dd *domDropDetector) Build() *DOMDrop { dd.lastDropTimestamp = 0 return domDrop } - diff --git a/backend/services/ender/builder/inputEventBuilder.go b/backend/services/ender/builder/inputEventBuilder.go index 98c7ebaf6..ce1b710ca 100644 --- a/backend/services/ender/builder/inputEventBuilder.go +++ b/backend/services/ender/builder/inputEventBuilder.go @@ -7,9 +7,9 @@ import ( type inputLabels map[uint64]string type inputEventBuilder struct { - inputEvent *InputEvent - inputLabels inputLabels - inputID uint64 + inputEvent *InputEvent + inputLabels inputLabels + inputID uint64 } func NewInputEventBuilder() *inputEventBuilder { @@ -18,7 +18,6 @@ func NewInputEventBuilder() *inputEventBuilder { return ieBuilder } - func (b *inputEventBuilder) ClearLabels() { b.inputLabels = make(inputLabels) } @@ -57,11 +56,11 @@ func (b *inputEventBuilder) HasInstance() bool { return b.inputEvent != nil } -func (b * inputEventBuilder) GetTimestamp() uint64 { +func (b *inputEventBuilder) GetTimestamp() uint64 { if b.inputEvent == nil { return 0 } - return b.inputEvent.Timestamp; + return b.inputEvent.Timestamp } func (b *inputEventBuilder) Build() *InputEvent { diff --git a/backend/services/ender/builder/memoryIssueFinder.go b/backend/services/ender/builder/memoryIssueFinder.go index a2702e505..0d6d71420 100644 --- a/backend/services/ender/builder/memoryIssueFinder.go +++ b/backend/services/ender/builder/memoryIssueFinder.go @@ -1,21 +1,21 @@ package builder import ( - "math" "encoding/json" - + "math" + . "openreplay/backend/pkg/messages" ) const MIN_COUNT = 3 -const MEM_RATE_THRESHOLD = 300 // % to average +const MEM_RATE_THRESHOLD = 300 // % to average type memoryIssueFinder struct { startMessageID uint64 startTimestamp uint64 rate int count float64 - sum float64 + sum float64 contextString string } @@ -23,13 +23,13 @@ func (f *memoryIssueFinder) Build() *IssueEvent { if f.startTimestamp == 0 { return nil } - payload, _ := json.Marshal(struct{Rate int }{f.rate - 100,}) + payload, _ := json.Marshal(struct{ Rate int }{f.rate - 100}) i := &IssueEvent{ - Type: "memory", - Timestamp: f.startTimestamp, - MessageID: f.startMessageID, + Type: "memory", + Timestamp: f.startTimestamp, + MessageID: f.startMessageID, ContextString: f.contextString, - Payload: string(payload), + Payload: string(payload), } f.startTimestamp = 0 f.startMessageID = 0 @@ -48,8 +48,8 @@ func (f *memoryIssueFinder) HandlePerformanceTrack(msg *PerformanceTrack, messag return nil } - average := f.sum/f.count - rate := int(math.Round(float64(msg.UsedJSHeapSize)/average * 100)) + average := f.sum / f.count + rate := int(math.Round(float64(msg.UsedJSHeapSize) / average * 100)) f.sum += float64(msg.UsedJSHeapSize) f.count++ @@ -68,5 +68,3 @@ func (f *memoryIssueFinder) HandlePerformanceTrack(msg *PerformanceTrack, messag return nil } - - diff --git a/backend/services/ender/builder/pageEventBuilder.go b/backend/services/ender/builder/pageEventBuilder.go index db602a996..2b0665894 100644 --- a/backend/services/ender/builder/pageEventBuilder.go +++ b/backend/services/ender/builder/pageEventBuilder.go @@ -5,8 +5,8 @@ import ( ) type pageEventBuilder struct { - pageEvent *PageEvent - firstTimingHandled bool + pageEvent *PageEvent + firstTimingHandled bool } func (b *pageEventBuilder) buildIfTimingsComplete() *PageEvent { @@ -28,7 +28,7 @@ func (b *pageEventBuilder) HandleSetPageLocation(msg *SetPageLocation, messageID } } -func (b * pageEventBuilder) HandlePageLoadTiming(msg *PageLoadTiming) *PageEvent { +func (b *pageEventBuilder) HandlePageLoadTiming(msg *PageLoadTiming) *PageEvent { if !b.HasInstance() { return nil } @@ -62,7 +62,7 @@ func (b * pageEventBuilder) HandlePageLoadTiming(msg *PageLoadTiming) *PageEvent return b.buildIfTimingsComplete() } -func (b * pageEventBuilder) HandlePageRenderTiming(msg *PageRenderTiming) *PageEvent { +func (b *pageEventBuilder) HandlePageRenderTiming(msg *PageRenderTiming) *PageEvent { if !b.HasInstance() { return nil } @@ -76,16 +76,16 @@ func (b *pageEventBuilder) HasInstance() bool { return b.pageEvent != nil } -func (b * pageEventBuilder) GetTimestamp() uint64 { +func (b *pageEventBuilder) GetTimestamp() uint64 { if b.pageEvent == nil { return 0 } - return b.pageEvent.Timestamp; + return b.pageEvent.Timestamp } -func (b * pageEventBuilder) Build() *PageEvent { +func (b *pageEventBuilder) Build() *PageEvent { pageEvent := b.pageEvent b.pageEvent = nil b.firstTimingHandled = false return pageEvent -} \ No newline at end of file +} diff --git a/backend/services/ender/builder/performanceTrackAggrBuilder.go b/backend/services/ender/builder/performanceTrackAggrBuilder.go index b24090ff9..70b751f55 100644 --- a/backend/services/ender/builder/performanceTrackAggrBuilder.go +++ b/backend/services/ender/builder/performanceTrackAggrBuilder.go @@ -3,22 +3,20 @@ package builder import ( "math" - "openreplay/backend/pkg/messages/performance" . "openreplay/backend/pkg/messages" + "openreplay/backend/pkg/messages/performance" ) - type performanceTrackAggrBuilder struct { - performanceTrackAggr *PerformanceTrackAggr - lastTimestamp uint64 - count float64 - sumFrameRate float64 - sumTickRate float64 - sumTotalJSHeapSize float64 - sumUsedJSHeapSize float64 + performanceTrackAggr *PerformanceTrackAggr + lastTimestamp uint64 + count float64 + sumFrameRate float64 + sumTickRate float64 + sumTotalJSHeapSize float64 + sumUsedJSHeapSize float64 } - func (b *performanceTrackAggrBuilder) start(timestamp uint64) { b.performanceTrackAggr = &PerformanceTrackAggr{ TimestampStart: timestamp, @@ -39,7 +37,7 @@ func (b *performanceTrackAggrBuilder) HandlePerformanceTrack(msg *PerformanceTra } frameRate := performance.FrameRate(msg.Frames, dt) - tickRate := performance.TickRate(msg.Ticks, dt) + tickRate := performance.TickRate(msg.Ticks, dt) fps := uint64(math.Round(frameRate)) cpu := performance.CPURateFromTickRate(tickRate) @@ -84,7 +82,7 @@ func (b *performanceTrackAggrBuilder) GetStartTimestamp() uint64 { if b.performanceTrackAggr == nil { return 0 } - return b.performanceTrackAggr.TimestampStart; + return b.performanceTrackAggr.TimestampStart } func (b *performanceTrackAggrBuilder) Build() *PerformanceTrackAggr { @@ -106,4 +104,3 @@ func (b *performanceTrackAggrBuilder) Build() *PerformanceTrackAggr { b.lastTimestamp = 0 return performanceTrackAggr } - diff --git a/backend/services/ender/main.go b/backend/services/ender/main.go index f2430f3a0..4170a178e 100644 --- a/backend/services/ender/main.go +++ b/backend/services/ender/main.go @@ -35,7 +35,7 @@ func main() { env.String("TOPIC_RAW_IOS"), }, func(sessionID uint64, msg messages.Message, meta *types.Meta) { - statsLogger.HandleAndLog(sessionID, meta) + statsLogger.Collect(sessionID, meta) builderMap.HandleMessage(sessionID, msg, msg.Meta().Index) }, false, diff --git a/backend/services/http/assets.go b/backend/services/http/assets.go deleted file mode 100644 index cc055087a..000000000 --- a/backend/services/http/assets.go +++ /dev/null @@ -1,36 +0,0 @@ -package main - -import ( - "openreplay/backend/pkg/url/assets" - "openreplay/backend/pkg/messages" -) - -func sendAssetForCache(sessionID uint64, baseURL string, relativeURL string) { - if fullURL, cacheable := assets.GetFullCachableURL(baseURL, relativeURL); cacheable { - producer.Produce(TOPIC_CACHE, sessionID, messages.Encode(&messages.AssetCache{ - URL: fullURL, - })) - } -} - -func sendAssetsForCacheFromCSS(sessionID uint64, baseURL string, css string) { - for _, u := range assets.ExtractURLsFromCSS(css) { // TODO: in one shot with rewriting - sendAssetForCache(sessionID, baseURL, u) - } -} - -func handleURL(sessionID uint64, baseURL string, url string) string { - if CACHE_ASSESTS { - sendAssetForCache(sessionID, baseURL, url) - return rewriter.RewriteURL(sessionID, baseURL, url) - } - return assets.ResolveURL(baseURL, url) -} - -func handleCSS(sessionID uint64, baseURL string, css string) string { - if CACHE_ASSESTS { - sendAssetsForCacheFromCSS(sessionID, baseURL, css) - return rewriter.RewriteCSS(sessionID, baseURL, css) - } - return assets.ResolveCSS(baseURL, css) -} \ No newline at end of file diff --git a/backend/services/http/build_hack b/backend/services/http/build_hack new file mode 100644 index 000000000..e69de29bb diff --git a/backend/services/http/handlers-depricated.go b/backend/services/http/handlers-depricated.go deleted file mode 100644 index 85f0393b7..000000000 --- a/backend/services/http/handlers-depricated.go +++ /dev/null @@ -1 +0,0 @@ -package main \ No newline at end of file diff --git a/backend/services/http/handlers-ios.go b/backend/services/http/handlers-ios.go deleted file mode 100644 index 8116980e1..000000000 --- a/backend/services/http/handlers-ios.go +++ /dev/null @@ -1,195 +0,0 @@ -package main - -import ( - "encoding/json" - "errors" - "log" - "math/rand" - "net/http" - "strconv" - "time" - - "openreplay/backend/pkg/db/postgres" - . "openreplay/backend/pkg/messages" - "openreplay/backend/pkg/token" -) - -const FILES_SIZE_LIMIT int64 = 1e7 // 10Mb - -func startSessionHandlerIOS(w http.ResponseWriter, r *http.Request) { - type request struct { - Token string `json:"token"` - ProjectKey *string `json:"projectKey"` - TrackerVersion string `json:"trackerVersion"` - RevID string `json:"revID"` - UserUUID *string `json:"userUUID"` - //UserOS string `json"userOS"` //hardcoded 'MacOS' - UserOSVersion string `json:"userOSVersion"` - UserDevice string `json:"userDevice"` - Timestamp uint64 `json:"timestamp"` - // UserDeviceType uint 0:phone 1:pad 2:tv 3:carPlay 5:mac - // “performances”:{ - // “activeProcessorCount”:8, - // “isLowPowerModeEnabled”:0, - // “orientation”:0, - // “systemUptime”:585430, - // “batteryState”:0, - // “thermalState”:0, - // “batteryLevel”:0, - // “processorCount”:8, - // “physicalMemory”:17179869184 - // }, - } - type response struct { - Token string `json:"token"` - ImagesHashList []string `json:"imagesHashList"` - UserUUID string `json:"userUUID"` - BeaconSizeLimit int64 `json:"beaconSizeLimit"` - SessionID string `json:"sessionID"` - } - startTime := time.Now() - req := &request{} - body := http.MaxBytesReader(w, r.Body, JSON_SIZE_LIMIT) - defer body.Close() - if err := json.NewDecoder(body).Decode(req); err != nil { - responseWithError(w, http.StatusBadRequest, err) - return - } - - if req.ProjectKey == nil { - responseWithError(w, http.StatusForbidden, errors.New("ProjectKey value required")) - return - } - - p, err := pgconn.GetProjectByKey(*req.ProjectKey) - if err != nil { - if postgres.IsNoRowsErr(err) { - responseWithError(w, http.StatusNotFound, errors.New("Project doesn't exist or is not active")) - } else { - responseWithError(w, http.StatusInternalServerError, err) // TODO: send error here only on staging - } - return - } - userUUID := getUUID(req.UserUUID) - tokenData, err := tokenizer.Parse(req.Token) - - if err != nil { // Starting the new one - dice := byte(rand.Intn(100)) // [0, 100) - if dice >= p.SampleRate { - responseWithError(w, http.StatusForbidden, errors.New("cancel")) - return - } - - ua := uaParser.ParseFromHTTPRequest(r) - if ua == nil { - responseWithError(w, http.StatusForbidden, errors.New("browser not recognized")) - return - } - sessionID, err := flaker.Compose(uint64(startTime.UnixMilli())) - 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{sessionID, expTime.UnixMilli()} - - country := geoIP.ExtractISOCodeFromHTTPRequest(r) - - // The difference with web is mostly here: - producer.Produce(TOPIC_RAW_IOS, tokenData.ID, Encode(&IOSSessionStart{ - Timestamp: req.Timestamp, - ProjectID: uint64(p.ProjectID), - TrackerVersion: req.TrackerVersion, - RevID: req.RevID, - UserUUID: userUUID, - UserOS: "IOS", - UserOSVersion: req.UserOSVersion, - UserDevice: MapIOSDevice(req.UserDevice), - UserDeviceType: GetIOSDeviceType(req.UserDevice), - UserCountry: country, - })) - } - - // imagesHashList, err := s3.GetFrequentlyUsedKeys(*(req.EncodedProjectID)) // TODO: reuse index: ~ frequency * size - // if err != nil { - // responseWithError(w, http.StatusInternalServerError, err) - // return - // } - - responseWithJSON(w, &response{ - // ImagesHashList: imagesHashList, - Token: tokenizer.Compose(*tokenData), - UserUUID: userUUID, - SessionID: strconv.FormatUint(tokenData.ID, 10), - BeaconSizeLimit: BEACON_SIZE_LIMIT, - }) -} - -func pushMessagesHandlerIOS(w http.ResponseWriter, r *http.Request) { - sessionData, err := tokenizer.ParseFromHTTPRequest(r) - if err != nil { - responseWithError(w, http.StatusUnauthorized, err) - return - } - pushMessages(w, r, sessionData.ID, TOPIC_RAW_IOS) -} - -func pushLateMessagesHandlerIOS(w http.ResponseWriter, r *http.Request) { - sessionData, err := tokenizer.ParseFromHTTPRequest(r) - if err != nil && err != token.EXPIRED { - responseWithError(w, http.StatusUnauthorized, err) - return - } - // Check timestamps here? - pushMessages(w, r, sessionData.ID, TOPIC_RAW_IOS) -} - -func imagesUploadHandlerIOS(w http.ResponseWriter, r *http.Request) { - log.Printf("recieved imagerequest") - - sessionData, err := tokenizer.ParseFromHTTPRequest(r) - if err != nil { // Should accept expired token? - responseWithError(w, http.StatusUnauthorized, err) - return - } - - r.Body = http.MaxBytesReader(w, r.Body, FILES_SIZE_LIMIT) - defer r.Body.Close() - err = r.ParseMultipartForm(1e6) // ~1Mb - if err == http.ErrNotMultipart || err == http.ErrMissingBoundary { - responseWithError(w, http.StatusUnsupportedMediaType, err) - // } else if err == multipart.ErrMessageTooLarge // if non-files part exceeds 10 MB - } else if err != nil { - responseWithError(w, http.StatusInternalServerError, err) // TODO: send error here only on staging - } - - if r.MultipartForm == nil { - responseWithError(w, http.StatusInternalServerError, errors.New("Multipart not parsed")) - } - - if len(r.MultipartForm.Value["projectKey"]) == 0 { - responseWithError(w, http.StatusBadRequest, errors.New("projectKey parameter missing")) // status for missing/wrong parameter? - return - } - - prefix := r.MultipartForm.Value["projectKey"][0] + "/" + strconv.FormatUint(sessionData.ID, 10) + "/" - - for _, fileHeaderList := range r.MultipartForm.File { - for _, fileHeader := range fileHeaderList { - file, err := fileHeader.Open() - if err != nil { - continue // TODO: send server error or accumulate successful files - } - key := prefix + fileHeader.Filename - log.Printf("Uploading image... %v", key) - go func() { //TODO: mime type from header - if err := s3.Upload(file, key, "image/jpeg", false); err != nil { - log.Printf("Upload ios screen error. %v", err) - } - }() - } - } - - w.WriteHeader(http.StatusOK) -} diff --git a/backend/services/http/handlers-web.go b/backend/services/http/handlers-web.go deleted file mode 100644 index 7aab5bfbc..000000000 --- a/backend/services/http/handlers-web.go +++ /dev/null @@ -1,241 +0,0 @@ -package main - -import ( - "encoding/json" - "errors" - "log" - "math/rand" - "net/http" - "strconv" - "time" - - "openreplay/backend/pkg/db/postgres" - . "openreplay/backend/pkg/messages" - "openreplay/backend/pkg/token" -) - -func startSessionHandlerWeb(w http.ResponseWriter, r *http.Request) { - type request struct { - Token string `json:"token"` - UserUUID *string `json:"userUUID"` - RevID string `json:"revID"` - Timestamp uint64 `json:"timestamp"` - TrackerVersion string `json:"trackerVersion"` - IsSnippet bool `json:"isSnippet"` - DeviceMemory uint64 `json:"deviceMemory"` - JsHeapSizeLimit uint64 `json:"jsHeapSizeLimit"` - ProjectKey *string `json:"projectKey"` - Reset bool `json:"reset"` - UserID string `json:"userID"` - } - type response struct { - Timestamp int64 `json:"timestamp"` - Delay int64 `json:"delay"` - Token string `json:"token"` - UserUUID string `json:"userUUID"` - SessionID string `json:"sessionID"` - BeaconSizeLimit int64 `json:"beaconSizeLimit"` - } - - startTime := time.Now() - req := &request{} - body := http.MaxBytesReader(w, r.Body, JSON_SIZE_LIMIT) // what if Body == nil?? // use r.ContentLength to return specific error? - defer body.Close() - if err := json.NewDecoder(body).Decode(req); err != nil { - responseWithError(w, http.StatusBadRequest, err) - return - } - - if req.ProjectKey == nil { - responseWithError(w, http.StatusForbidden, errors.New("ProjectKey value required")) - return - } - - p, err := pgconn.GetProjectByKey(*req.ProjectKey) - if err != nil { - if postgres.IsNoRowsErr(err) { - responseWithError(w, http.StatusNotFound, errors.New("Project doesn't exist or capture limit has been reached")) - } else { - responseWithError(w, http.StatusInternalServerError, err) // TODO: send error here only on staging - } - return - } - - userUUID := getUUID(req.UserUUID) - tokenData, err := tokenizer.Parse(req.Token) - if err != nil || req.Reset { // Starting the new one - dice := byte(rand.Intn(100)) // [0, 100) - if dice >= p.SampleRate { - responseWithError(w, http.StatusForbidden, errors.New("cancel")) - return - } - - ua := uaParser.ParseFromHTTPRequest(r) - if ua == nil { - responseWithError(w, http.StatusForbidden, errors.New("browser not recognized")) - return - } - sessionID, err := flaker.Compose(uint64(startTime.UnixNano() / 1e6)) - 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{sessionID, expTime.UnixNano() / 1e6} - - country := geoIP.ExtractISOCodeFromHTTPRequest(r) - producer.Produce(TOPIC_RAW_WEB, tokenData.ID, Encode(&SessionStart{ - Timestamp: req.Timestamp, - ProjectID: uint64(p.ProjectID), - TrackerVersion: req.TrackerVersion, - RevID: req.RevID, - UserUUID: userUUID, - UserAgent: r.Header.Get("User-Agent"), - UserOS: ua.OS, - UserOSVersion: ua.OSVersion, - UserBrowser: ua.Browser, - UserBrowserVersion: ua.BrowserVersion, - UserDevice: ua.Device, - UserDeviceType: ua.DeviceType, - UserCountry: country, - UserDeviceMemorySize: req.DeviceMemory, - UserDeviceHeapSize: req.JsHeapSizeLimit, - UserID: req.UserID, - })) - } - - //delayDuration := time.Now().Sub(startTime) - responseWithJSON(w, &response{ - //Timestamp: startTime.UnixNano() / 1e6, - //Delay: delayDuration.Nanoseconds() / 1e6, - Token: tokenizer.Compose(*tokenData), - UserUUID: userUUID, - SessionID: strconv.FormatUint(tokenData.ID, 10), - BeaconSizeLimit: BEACON_SIZE_LIMIT, - }) -} - -func pushMessagesHandlerWeb(w http.ResponseWriter, r *http.Request) { - sessionData, err := tokenizer.ParseFromHTTPRequest(r) - if err != nil { - responseWithError(w, http.StatusUnauthorized, err) - return - } - body := http.MaxBytesReader(w, r.Body, BEACON_SIZE_LIMIT) - defer body.Close() - - rewritenBuf, err := RewriteBatch(body, func(msg Message) Message { - switch m := msg.(type) { - case *SetNodeAttributeURLBased: - if m.Name == "src" || m.Name == "href" { - msg = &SetNodeAttribute{ - ID: m.ID, - Name: m.Name, - Value: handleURL(sessionData.ID, m.BaseURL, m.Value), - } - } else if m.Name == "style" { - msg = &SetNodeAttribute{ - ID: m.ID, - Name: m.Name, - Value: handleCSS(sessionData.ID, m.BaseURL, m.Value), - } - } - case *SetCSSDataURLBased: - msg = &SetCSSData{ - ID: m.ID, - Data: handleCSS(sessionData.ID, m.BaseURL, m.Data), - } - case *CSSInsertRuleURLBased: - msg = &CSSInsertRule{ - ID: m.ID, - Index: m.Index, - Rule: handleCSS(sessionData.ID, m.BaseURL, m.Rule), - } - } - - // switch msg.(type) { - // case *BatchMeta, // TODO: watchout! Meta().Index'es are changed here (though it is still unique for the topic-session pair) - // *SetPageLocation, - // *PageLoadTiming, - // *PageRenderTiming, - // *PerformanceTrack, - // *SetInputTarget, - // *SetInputValue, - // *MouseClick, - // *RawErrorEvent, - // *JSException, - // *ResourceTiming, - // *RawCustomEvent, - // *CustomIssue, - // *Fetch, - // *StateAction, - // *GraphQL, - // *CreateElementNode, - // *CreateTextNode, - // *RemoveNode, - // *CreateDocument, - // *RemoveNodeAttribute, - // *MoveNode, - // *SetCSSData, - // *CSSInsertRule, - // *CSSDeleteRule: - // analyticsMessages = append(analyticsMessages, msg) - //} - - return msg - }) - if err != nil { - responseWithError(w, http.StatusForbidden, err) - return - } - producer.Produce(TOPIC_RAW_WEB, sessionData.ID, rewritenBuf) - //producer.Produce(TOPIC_ANALYTICS, sessionData.ID, WriteBatch(analyticsMessages)) - //duration := time.Now().Sub(startTime) - //log.Printf("Sended batch within %v nsec; %v nsek/byte", duration.Nanoseconds(), duration.Nanoseconds()/int64(len(buf))) - w.WriteHeader(http.StatusOK) -} - -func notStartedHandlerWeb(w http.ResponseWriter, r *http.Request) { - type request struct { - ProjectKey *string `json:"projectKey"` - TrackerVersion string `json:"trackerVersion"` - DoNotTrack bool `json:"DoNotTrack"` - // RevID string `json:"revID"` - } - req := &request{} - body := http.MaxBytesReader(w, r.Body, JSON_SIZE_LIMIT) - defer body.Close() - if err := json.NewDecoder(body).Decode(req); err != nil { - responseWithError(w, http.StatusBadRequest, err) - return - } - if req.ProjectKey == nil { - responseWithError(w, http.StatusForbidden, errors.New("ProjectKey value required")) - return - } - ua := uaParser.ParseFromHTTPRequest(r) // TODO?: insert anyway - if ua == nil { - responseWithError(w, http.StatusForbidden, errors.New("browser not recognized")) - return - } - country := geoIP.ExtractISOCodeFromHTTPRequest(r) - err := pgconn.InsertUnstartedSession(postgres.UnstartedSession{ - ProjectKey: *req.ProjectKey, - TrackerVersion: req.TrackerVersion, - DoNotTrack: req.DoNotTrack, - Platform: "web", - UserAgent: r.Header.Get("User-Agent"), - UserOS: ua.OS, - UserOSVersion: ua.OSVersion, - UserBrowser: ua.Browser, - UserBrowserVersion: ua.BrowserVersion, - UserDevice: ua.Device, - UserDeviceType: ua.DeviceType, - UserCountry: country, - }) - if err != nil { - log.Printf("Unable to insert Unstarted Session: %v\n", err) - } - w.WriteHeader(http.StatusOK) -} diff --git a/backend/services/http/ios-device.go b/backend/services/http/ios-device.go deleted file mode 100644 index bec1f3b36..000000000 --- a/backend/services/http/ios-device.go +++ /dev/null @@ -1,138 +0,0 @@ -package main - -import ( - "strings" -) - -func MapIOSDevice(identifier string) string { - switch identifier { - case "iPod5,1": - return "iPod touch (5th generation)" - case "iPod7,1": - return "iPod touch (6th generation)" - case "iPod9,1": - return "iPod touch (7th generation)" - case "iPhone3,1", "iPhone3,2", "iPhone3,3": - return "iPhone 4" - case "iPhone4,1": - return "iPhone 4s" - case "iPhone5,1", "iPhone5,2": - return "iPhone 5" - case "iPhone5,3", "iPhone5,4": - return "iPhone 5c" - case "iPhone6,1", "iPhone6,2": - return "iPhone 5s" - case "iPhone7,2": - return "iPhone 6" - case "iPhone7,1": - return "iPhone 6 Plus" - case "iPhone8,1": - return "iPhone 6s" - case "iPhone8,2": - return "iPhone 6s Plus" - case "iPhone8,4": - return "iPhone SE" - case "iPhone9,1", "iPhone9,3": - return "iPhone 7" - case "iPhone9,2", "iPhone9,4": - return "iPhone 7 Plus" - case "iPhone10,1", "iPhone10,4": - return "iPhone 8" - case "iPhone10,2", "iPhone10,5": - return "iPhone 8 Plus" - case "iPhone10,3", "iPhone10,6": - return "iPhone X" - case "iPhone11,2": - return "iPhone XS" - case "iPhone11,4", "iPhone11,6": - return "iPhone XS Max" - case "iPhone11,8": - return "iPhone XR" - case "iPhone12,1": - return "iPhone 11" - case "iPhone12,3": - return "iPhone 11 Pro" - case "iPhone12,5": - return "iPhone 11 Pro Max" - case "iPhone12,8": - return "iPhone SE (2nd generation)" - case "iPhone13,1": - return "iPhone 12 mini" - case "iPhone13,2": - return "iPhone 12" - case "iPhone13,3": - return "iPhone 12 Pro" - case "iPhone13,4": - return "iPhone 12 Pro Max" - case "iPad2,1", "iPad2,2", "iPad2,3", "iPad2,4": - return "iPad 2" - case "iPad3,1", "iPad3,2", "iPad3,3": - return "iPad (3rd generation)" - case "iPad3,4", "iPad3,5", "iPad3,6": - return "iPad (4th generation)" - case "iPad6,11", "iPad6,12": - return "iPad (5th generation)" - case "iPad7,5", "iPad7,6": - return "iPad (6th generation)" - case "iPad7,11", "iPad7,12": - return "iPad (7th generation)" - case "iPad11,6", "iPad11,7": - return "iPad (8th generation)" - case "iPad4,1", "iPad4,2", "iPad4,3": - return "iPad Air" - case "iPad5,3", "iPad5,4": - return "iPad Air 2" - case "iPad11,3", "iPad11,4": - return "iPad Air (3rd generation)" - case "iPad13,1", "iPad13,2": - return "iPad Air (4th generation)" - case "iPad2,5", "iPad2,6", "iPad2,7": - return "iPad mini" - case "iPad4,4", "iPad4,5", "iPad4,6": - return "iPad mini 2" - case "iPad4,7", "iPad4,8", "iPad4,9": - return "iPad mini 3" - case "iPad5,1", "iPad5,2": - return "iPad mini 4" - case "iPad11,1", "iPad11,2": - return "iPad mini (5th generation)" - case "iPad6,3", "iPad6,4": - return "iPad Pro (9.7-inch)" - case "iPad7,3", "iPad7,4": - return "iPad Pro (10.5-inch)" - case "iPad8,1", "iPad8,2", "iPad8,3", "iPad8,4": - return "iPad Pro (11-inch) (1st generation)" - case "iPad8,9", "iPad8,10": - return "iPad Pro (11-inch) (2nd generation)" - case "iPad6,7", "iPad6,8": - return "iPad Pro (12.9-inch) (1st generation)" - case "iPad7,1", "iPad7,2": - return "iPad Pro (12.9-inch) (2nd generation)" - case "iPad8,5", "iPad8,6", "iPad8,7", "iPad8,8": - return "iPad Pro (12.9-inch) (3rd generation)" - case "iPad8,11", "iPad8,12": - return "iPad Pro (12.9-inch) (4th generation)" - case "AppleTV5,3": - return "Apple TV" - case "AppleTV6,2": - return "Apple TV 4K" - case "AudioAccessory1,1": - return "HomePod" - case "AudioAccessory5,1": - return "HomePod mini" - case "i386", "x86_64": - return "Simulator" - default: - return identifier - } -} - -func GetIOSDeviceType(identifier string) string { - if strings.Contains(identifier, "iPhone") { - return "mobile" //"phone" - } - if strings.Contains(identifier, "iPad") { - return "tablet" - } - return "other" -} diff --git a/backend/services/http/main.go b/backend/services/http/main.go deleted file mode 100644 index 1f3bc93b3..000000000 --- a/backend/services/http/main.go +++ /dev/null @@ -1,156 +0,0 @@ -package main - -import ( - "context" - "log" - "net/http" - "os" - "os/signal" - "syscall" - - "golang.org/x/net/http2" - - "openreplay/backend/pkg/db/cache" - "openreplay/backend/pkg/db/postgres" - "openreplay/backend/pkg/env" - "openreplay/backend/pkg/flakeid" - "openreplay/backend/pkg/queue" - "openreplay/backend/pkg/queue/types" - "openreplay/backend/pkg/storage" - "openreplay/backend/pkg/token" - "openreplay/backend/pkg/url/assets" - "openreplay/backend/services/http/geoip" - "openreplay/backend/services/http/uaparser" - - "openreplay/backend/pkg/pprof" -) - -var rewriter *assets.Rewriter -var producer types.Producer -var pgconn *cache.PGCache -var flaker *flakeid.Flaker -var uaParser *uaparser.UAParser -var geoIP *geoip.GeoIP -var tokenizer *token.Tokenizer -var s3 *storage.S3 - -var TOPIC_RAW_WEB string -var TOPIC_RAW_IOS string -var TOPIC_CACHE string -var TOPIC_TRIGGER string - -//var TOPIC_ANALYTICS string -var CACHE_ASSESTS bool -var BEACON_SIZE_LIMIT int64 - -func main() { - log.SetFlags(log.LstdFlags | log.LUTC | log.Llongfile) - pprof.StartProfilingServer() - - producer = queue.NewProducer() - defer producer.Close(15000) - TOPIC_RAW_WEB = env.String("TOPIC_RAW_WEB") - TOPIC_RAW_IOS = env.String("TOPIC_RAW_IOS") - TOPIC_CACHE = env.String("TOPIC_CACHE") - TOPIC_TRIGGER = env.String("TOPIC_TRIGGER") - //TOPIC_ANALYTICS = env.String("TOPIC_ANALYTICS") - rewriter = assets.NewRewriter(env.String("ASSETS_ORIGIN")) - pgconn = cache.NewPGCache(postgres.NewConn(env.String("POSTGRES_STRING")), 1000*60*20) - defer pgconn.Close() - s3 = storage.NewS3(env.String("AWS_REGION"), env.String("S3_BUCKET_IOS_IMAGES")) - tokenizer = token.NewTokenizer(env.String("TOKEN_SECRET")) - uaParser = uaparser.NewUAParser(env.String("UAPARSER_FILE")) - geoIP = geoip.NewGeoIP(env.String("MAXMINDDB_FILE")) - flaker = flakeid.NewFlaker(env.WorkerID()) - CACHE_ASSESTS = env.Bool("CACHE_ASSETS") - BEACON_SIZE_LIMIT = int64(env.Uint64("BEACON_SIZE_LIMIT")) - - HTTP_PORT := env.String("HTTP_PORT") - - server := &http.Server{ - Addr: ":" + HTTP_PORT, - Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - - // TODO: agree with specification - w.Header().Set("Access-Control-Allow-Origin", "*") - w.Header().Set("Access-Control-Allow-Methods", "POST") - w.Header().Set("Access-Control-Allow-Headers", "Content-Type,Authorization") - if r.Method == http.MethodOptions { - w.Header().Set("Cache-Control", "max-age=86400") - w.WriteHeader(http.StatusOK) - return - } - - log.Printf("Request: %v - %v ", r.Method, r.URL.Path) - - switch r.URL.Path { - case "/": - w.WriteHeader(http.StatusOK) - case "/v1/web/not-started": - switch r.Method { - case http.MethodPost: - notStartedHandlerWeb(w, r) - default: - w.WriteHeader(http.StatusMethodNotAllowed) - } - case "/v1/web/start": - switch r.Method { - case http.MethodPost: - startSessionHandlerWeb(w, r) - default: - w.WriteHeader(http.StatusMethodNotAllowed) - } - case "/v1/web/i": - switch r.Method { - case http.MethodPost: - pushMessagesHandlerWeb(w, r) - default: - w.WriteHeader(http.StatusMethodNotAllowed) - } - case "/v1/ios/start": - switch r.Method { - case http.MethodPost: - startSessionHandlerIOS(w, r) - default: - w.WriteHeader(http.StatusMethodNotAllowed) - } - case "/v1/ios/i": - switch r.Method { - case http.MethodPost: - pushMessagesHandlerIOS(w, r) - default: - w.WriteHeader(http.StatusMethodNotAllowed) - } - case "/v1/ios/late": - switch r.Method { - case http.MethodPost: - pushLateMessagesHandlerIOS(w, r) - default: - w.WriteHeader(http.StatusMethodNotAllowed) - } - case "/v1/ios/images": - switch r.Method { - case http.MethodPost: - imagesUploadHandlerIOS(w, r) - default: - w.WriteHeader(http.StatusMethodNotAllowed) - } - default: - w.WriteHeader(http.StatusNotFound) - } - }), - } - http2.ConfigureServer(server, nil) - go func() { - if err := server.ListenAndServe(); err != nil { - log.Printf("Server error: %v\n", err) - log.Fatal("Server error") - } - }() - log.Printf("Server successfully started on port %v\n", HTTP_PORT) - sigchan := make(chan os.Signal, 1) - signal.Notify(sigchan, syscall.SIGINT, syscall.SIGTERM) - <-sigchan - log.Printf("Shutting down the server\n") - server.Shutdown(context.Background()) -} diff --git a/backend/services/integrations/clientManager/manager.go b/backend/services/integrations/clientManager/manager.go index 39cd8dd90..a671a6266 100644 --- a/backend/services/integrations/clientManager/manager.go +++ b/backend/services/integrations/clientManager/manager.go @@ -7,38 +7,36 @@ import ( "openreplay/backend/services/integrations/integration" ) - type manager struct { - clientMap integration.ClientMap - Events chan *integration.SessionErrorEvent - Errors chan error - RequestDataUpdates chan postgres.Integration // not pointer because it could change in other thread + clientMap integration.ClientMap + Events chan *integration.SessionErrorEvent + Errors chan error + RequestDataUpdates chan postgres.Integration // not pointer because it could change in other thread } - func NewManager() *manager { - return &manager { - clientMap: make(integration.ClientMap), + return &manager{ + clientMap: make(integration.ClientMap), RequestDataUpdates: make(chan postgres.Integration, 100), - Events: make(chan *integration.SessionErrorEvent, 100), - Errors: make(chan error, 100), + Events: make(chan *integration.SessionErrorEvent, 100), + Errors: make(chan error, 100), } } -func (m* manager) Update(i *postgres.Integration) error { +func (m *manager) Update(i *postgres.Integration) error { key := strconv.Itoa(int(i.ProjectID)) + i.Provider if i.Options == nil { delete(m.clientMap, key) return nil } - c, exists := m.clientMap[ key ] + c, exists := m.clientMap[key] if !exists { c, err := integration.NewClient(i, m.RequestDataUpdates, m.Events, m.Errors) if err != nil { return err } - m.clientMap[ key ] = c + m.clientMap[key] = c return nil } return c.Update(i) diff --git a/backend/services/integrations/integration/cloudwatch.go b/backend/services/integrations/integration/cloudwatch.go index fa2210138..9974f485b 100644 --- a/backend/services/integrations/integration/cloudwatch.go +++ b/backend/services/integrations/integration/cloudwatch.go @@ -2,43 +2,40 @@ package integration import ( "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/cloudwatchlogs" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/cloudwatchlogs" - "strings" - "regexp" "openreplay/backend/pkg/messages" + "regexp" + "strings" ) - var reIsException = regexp.MustCompile(`(?i)exception|error`) type cloudwatch struct { - AwsAccessKeyId string // `json:"aws_access_key_id"` - AwsSecretAccessKey string // `json:"aws_secret_access_key"` - LogGroupName string // `json:"log_group_name"` - Region string // `json:"region"` + AwsAccessKeyId string // `json:"aws_access_key_id"` + AwsSecretAccessKey string // `json:"aws_secret_access_key"` + LogGroupName string // `json:"log_group_name"` + Region string // `json:"region"` } - func (cw *cloudwatch) Request(c *client) error { - startTs := int64(c.getLastMessageTimestamp() + 1) // From next millisecond + startTs := int64(c.getLastMessageTimestamp() + 1) // From next millisecond //endTs := utils.CurrentTimestamp() sess, err := session.NewSession(aws.NewConfig(). - WithRegion(cw.Region). - WithCredentials( - credentials.NewStaticCredentials(cw.AwsAccessKeyId, cw.AwsSecretAccessKey, ""), - ), + WithRegion(cw.Region). + WithCredentials( + credentials.NewStaticCredentials(cw.AwsAccessKeyId, cw.AwsSecretAccessKey, ""), + ), ) if err != nil { return err } svc := cloudwatchlogs.New(sess) - filterOptions := new(cloudwatchlogs.FilterLogEventsInput). - SetStartTime(startTs). // Inclusively both startTime and endTime + SetStartTime(startTs). // Inclusively both startTime and endTime // SetEndTime(endTs). // Default nil? // SetLimit(10000). // Default 10000 SetLogGroupName(cw.LogGroupName). @@ -56,7 +53,7 @@ func (cw *cloudwatch) Request(c *client) error { } if !reIsException.MatchString(*e.Message) { // too weak condition ? continue - } + } token, err := GetToken(*e.Message) if err != nil { c.errChan <- err @@ -72,18 +69,18 @@ func (cw *cloudwatch) Request(c *client) error { //SessionID: sessionID, Token: token, RawErrorEvent: &messages.RawErrorEvent{ - Source: "cloudwatch", - Timestamp: timestamp, // e.IngestionTime ?? - Name: name, - Payload: strings.ReplaceAll(e.String(), "\n", ""), + Source: "cloudwatch", + Timestamp: timestamp, // e.IngestionTime ?? + Name: name, + Payload: strings.ReplaceAll(e.String(), "\n", ""), }, } } if output.NextToken == nil { - break; + break } filterOptions.NextToken = output.NextToken } return nil -} \ No newline at end of file +} diff --git a/backend/services/integrations/integration/elasticsearch.go b/backend/services/integrations/integration/elasticsearch.go index dd6f5d5f9..6b8181073 100644 --- a/backend/services/integrations/integration/elasticsearch.go +++ b/backend/services/integrations/integration/elasticsearch.go @@ -53,14 +53,14 @@ func (es *elasticsearch) Request(c *client) error { "query": map[string]interface{}{ "bool": map[string]interface{}{ "filter": []map[string]interface{}{ - map[string]interface{}{ + { "match": map[string]interface{}{ "message": map[string]interface{}{ "query": "openReplaySessionToken=", // asayer_session_id= }, }, }, - map[string]interface{}{ + { "range": map[string]interface{}{ "utc_time": map[string]interface{}{ "gte": strconv.FormatUint(gteTs, 10), @@ -68,7 +68,7 @@ func (es *elasticsearch) Request(c *client) error { }, }, }, - map[string]interface{}{ + { "term": map[string]interface{}{ "tags": "error", }, diff --git a/backend/services/integrations/integration/rollbar.go b/backend/services/integrations/integration/rollbar.go index 369ee31f9..53a5c6d5b 100644 --- a/backend/services/integrations/integration/rollbar.go +++ b/backend/services/integrations/integration/rollbar.go @@ -1,15 +1,15 @@ package integration import ( - "net/http" "encoding/json" + "errors" "fmt" - "time" - "strings" - "strconv" "io" - "io/ioutil" - "errors" + "io/ioutil" + "net/http" + "strconv" + "strings" + "time" "openreplay/backend/pkg/messages" ) @@ -17,42 +17,42 @@ import ( // Old name: asayerSessionId // QUERY: what can be modified? -const RB_QUERY = - "SELECT item.id, item.title,body.message.openReplaySessionToken,item.level,"+ - " item.counter,item.environment,body.crash_report.raw,body.message.body,timestamp"+ - " FROM item_occurrence"+ - " WHERE body.message.openReplaySessionToken != null"+ - " AND timestamp>= %v"+ - " AND item.level>30"+ - " ORDER BY timestamp"+ +const RB_QUERY = "SELECT item.id, item.title,body.message.openReplaySessionToken,item.level," + + " item.counter,item.environment,body.crash_report.raw,body.message.body,timestamp" + + " FROM item_occurrence" + + " WHERE body.message.openReplaySessionToken != null" + + " AND timestamp>= %v" + + " AND item.level>30" + + " ORDER BY timestamp" + " LIMIT 1000" + // ASC by default // \n\t symbols can spoil the request body, so it wouldn't work (OR probably it happend because of job hashing) /* - - `read` Access Token required - - timstamp in seconds + - `read` Access Token required + - timstamp in seconds */ type rollbar struct { - AccessToken string // `json:"access_token"` + AccessToken string // `json:"access_token"` } type rollbarJobResponce struct { - Err int + Err int Message string - Result struct { + Result struct { Id int } } type rollbarJobStatusResponce struct { - Err int + Err int Result struct { Status string Result struct { - Rows [][] json.Number - Columns[] string + Rows [][]json.Number + Columns []string } } } @@ -65,7 +65,7 @@ type rollbarEvent map[string]string */ func (rb *rollbar) Request(c *client) error { fromTs := c.getLastMessageTimestamp() + 1000 // From next second - c.setLastMessageTimestamp(fromTs) // anti-job-hashing + c.setLastMessageTimestamp(fromTs) // anti-job-hashing fromTsSec := fromTs / 1e3 query := fmt.Sprintf(RB_QUERY, fromTsSec) jsonBody := fmt.Sprintf(`{ @@ -111,7 +111,7 @@ func (rb *rollbar) Request(c *client) error { tick := time.Tick(5 * time.Second) for { - <- tick + <-tick resp, err = http.DefaultClient.Do(req) if err != nil { return err // continue + timeout/maxAttempts @@ -131,14 +131,14 @@ func (rb *rollbar) Request(c *client) error { e := make(rollbarEvent) for i, col := range jobStatus.Result.Result.Columns { //if len(row) <= i { error } - e[ col ] = row[ i ].String() // here I make them all string. That's not good + e[col] = row[i].String() // here I make them all string. That's not good } // sessionID, err := strconv.ParseUint(e[ "body.message.asayerSessionId" ], 10, 64) // if err != nil { // c.errChan <- err // continue // } - if e[ "body.message.openReplaySessionToken" ] == "" { + if e["body.message.openReplaySessionToken"] == "" { c.errChan <- errors.New("Token is empty!") continue } @@ -147,7 +147,7 @@ func (rb *rollbar) Request(c *client) error { c.errChan <- err continue } - timestampSec, err := strconv.ParseUint(e[ "timestamp" ], 10, 64) + timestampSec, err := strconv.ParseUint(e["timestamp"], 10, 64) if err != nil { c.errChan <- err continue @@ -155,22 +155,22 @@ func (rb *rollbar) Request(c *client) error { timestamp := timestampSec * 1000 c.setLastMessageTimestamp(timestamp) c.evChan <- &SessionErrorEvent{ - Token: e[ "body.message.openReplaySessionToken" ], + Token: e["body.message.openReplaySessionToken"], RawErrorEvent: &messages.RawErrorEvent{ - Source: "rollbar", + Source: "rollbar", Timestamp: timestamp, - Name: e[ "item.title" ], - Payload: string(payload), + Name: e["item.title"], + Payload: string(payload), }, } } break } - if jobStatus.Result.Status != "new" && + if jobStatus.Result.Status != "new" && jobStatus.Result.Status != "running" { // error break } } return nil -} \ No newline at end of file +} diff --git a/backend/services/integrations/integration/utils.go b/backend/services/integrations/integration/utils.go index 396a177bd..36a473c02 100644 --- a/backend/services/integrations/integration/utils.go +++ b/backend/services/integrations/integration/utils.go @@ -1,34 +1,37 @@ package integration import ( + "fmt" "regexp" "strconv" "strings" - "fmt" ) var reSessionID = regexp.MustCompile(`(?i)asayer_session_id=([0-9]+)`) -func GetAsayerSessionId(s string) (uint64, error) { + +func GetAsayerSessionId(s string) (uint64, error) { matches := reSessionID.FindStringSubmatch(s) if len(matches) < 2 { return 0, fmt.Errorf("'asayer_session_id' not found in '%v' ", s) } - return strconv.ParseUint(matches[ 1 ], 10, 64) + return strconv.ParseUint(matches[1], 10, 64) } func GetLinkFromAngularBrackets(s string) string { beg := strings.Index(s, "<") + 1 end := strings.Index(s, ">") - if end < 0 { return "" } + if end < 0 { + return "" + } return strings.TrimSpace(s[beg:end]) } - var reToken = regexp.MustCompile(`(?i)openReplaySessionToken=([0-9a-zA-Z\.]+)`) -func GetToken(s string) (string, error) { + +func GetToken(s string) (string, error) { matches := reToken.FindStringSubmatch(s) if len(matches) < 2 { return "", fmt.Errorf("'openReplaySessionToken' not found in '%v' ", s) } - return matches[ 1 ], nil -} \ No newline at end of file + return matches[1], nil +} diff --git a/backend/services/sink/main.go b/backend/services/sink/main.go index a649bb6ef..1a23919c6 100644 --- a/backend/services/sink/main.go +++ b/backend/services/sink/main.go @@ -34,12 +34,7 @@ func main() { env.String("TOPIC_RAW_IOS"), }, func(sessionID uint64, message Message, _ *types.Meta) { - //typeID, err := GetMessageTypeID(value) - // if err != nil { - // log.Printf("Message type decoding error: %v", err) - // return - // } - typeID := message.Meta().TypeID + typeID := message.TypeID() if !IsReplayerType(typeID) { return } diff --git a/backend/services/storage/gzip.go b/backend/services/storage/gzip.go index d574ec4ae..0e662efaa 100644 --- a/backend/services/storage/gzip.go +++ b/backend/services/storage/gzip.go @@ -1,19 +1,18 @@ package main import ( - "io" gzip "github.com/klauspost/pgzip" + "io" ) - -func gzipFile(file io.ReadSeeker) io.Reader { +func gzipFile(file io.Reader) io.Reader { reader, writer := io.Pipe() - go func() { - gw, _ := gzip.NewWriterLevel(writer, gzip.BestSpeed) - io.Copy(gw, file) + go func() { + gw, _ := gzip.NewWriterLevel(writer, gzip.BestSpeed) + io.Copy(gw, file) - gw.Close() - writer.Close() - }() - return reader -} \ No newline at end of file + gw.Close() + writer.Close() + }() + return reader +} diff --git a/backend/services/storage/main.go b/backend/services/storage/main.go index 9579fbe4f..95c5c6d17 100644 --- a/backend/services/storage/main.go +++ b/backend/services/storage/main.go @@ -6,6 +6,8 @@ import ( "strconv" "time" + "bytes" + "os/signal" "syscall" @@ -16,6 +18,10 @@ import ( "openreplay/backend/pkg/storage" ) +const RetryTimeout = 2 * time.Minute + +const SESSION_FILE_SPLIT_SIZE = 200000 // ~200 kB + func main() { log.SetFlags(log.LstdFlags | log.LUTC | log.Llongfile) @@ -28,16 +34,30 @@ func main() { if retryCount <= 0 { return } + file, err := os.Open(FS_DIR + "/" + key) - defer file.Close() if err != nil { log.Printf("File error: %v; Will retry %v more time(s)\n", err, retryCount) - time.AfterFunc(2*time.Minute, func() { + time.AfterFunc(RetryTimeout, func() { uploadKey(key, retryCount-1) }) - } else { - if err := storage.Upload(gzipFile(file), key, "application/octet-stream", true); err != nil { - log.Fatalf("Storage upload error: %v\n", err) + return + } + defer file.Close() + + startBytes := make([]byte, SESSION_FILE_SPLIT_SIZE) + nRead, err := file.Read(startBytes) + if err != nil { + log.Printf("File read error: %f", err) + return + } + startReader := bytes.NewBuffer(startBytes) + if err := storage.Upload(gzipFile(startReader), key, "application/octet-stream", true); err != nil { + log.Fatalf("Storage: start upload failed. %v\n", err) + } + if nRead == SESSION_FILE_SPLIT_SIZE { + if err := storage.Upload(gzipFile(file), key+"e", "application/octet-stream", true); err != nil { + log.Fatalf("Storage: end upload failed. %v\n", err) } } } diff --git a/ee/api/chalicelib/core/errors.py b/ee/api/chalicelib/core/errors.py index c7e066f8b..ecf1aeda2 100644 --- a/ee/api/chalicelib/core/errors.py +++ b/ee/api/chalicelib/core/errors.py @@ -272,6 +272,7 @@ def get_details(project_id, error_id, user_id, **data): INNER JOIN events.errors AS ee USING (error_id) INNER JOIN public.sessions USING (session_id) WHERE pe.project_id = %(project_id)s + AND sessions.project_id = %(project_id)s AND error_id = %(error_id)s ORDER BY start_ts DESC LIMIT 1;""", diff --git a/ee/api/chalicelib/core/metrics.py b/ee/api/chalicelib/core/metrics.py index 65889e28d..668ce4760 100644 --- a/ee/api/chalicelib/core/metrics.py +++ b/ee/api/chalicelib/core/metrics.py @@ -2465,13 +2465,16 @@ def get_user_activity_avg_visited_pages(project_id, startTimestamp=TimeUTC.now(d def __get_user_activity_avg_visited_pages(ch, project_id, startTimestamp, endTimestamp, **args): - ch_sub_query = __get_basic_constraints(table_name="sessions", data=args) + ch_sub_query = __get_basic_constraints(table_name="pages", data=args) meta_condition = __get_meta_constraint(args) ch_sub_query += meta_condition - ch_sub_query.append("sessions.pages_count>0") - ch_query = f"""SELECT COALESCE(CEIL(avgOrNull(sessions.pages_count)),0) AS value - FROM sessions {"INNER JOIN sessions_metadata USING(session_id)" if len(meta_condition) > 0 else ""} - WHERE {" AND ".join(ch_sub_query)};""" + + ch_query = f"""SELECT COALESCE(CEIL(avgOrNull(count)),0) AS value + FROM (SELECT COUNT(session_id) AS count + FROM pages {"INNER JOIN sessions_metadata USING(session_id)" if len(meta_condition) > 0 else ""} + WHERE {" AND ".join(ch_sub_query)} + GROUP BY session_id) AS groupped_data + WHERE count>0;""" params = {"project_id": project_id, "startTimestamp": startTimestamp, "endTimestamp": endTimestamp, **__get_constraint_values(args)} @@ -2482,19 +2485,22 @@ def __get_user_activity_avg_visited_pages(ch, project_id, startTimestamp, endTim def __get_user_activity_avg_visited_pages_chart(ch, project_id, startTimestamp, endTimestamp, density=20, **args): step_size = __get_step_size(endTimestamp=endTimestamp, startTimestamp=startTimestamp, density=density) - ch_sub_query_chart = __get_basic_constraints(table_name="sessions", round_start=True, data=args) + ch_sub_query_chart = __get_basic_constraints(table_name="pages", round_start=True, data=args) meta_condition = __get_meta_constraint(args) ch_sub_query_chart += meta_condition params = {"step_size": step_size, "project_id": project_id, "startTimestamp": startTimestamp, "endTimestamp": endTimestamp} - ch_sub_query_chart.append("sessions.pages_count>0") - ch_query = f"""SELECT toUnixTimestamp(toStartOfInterval(sessions.datetime, INTERVAL %(step_size)s second ))*1000 AS timestamp, - COALESCE(avgOrNull(sessions.pages_count),0) AS value - FROM sessions {"INNER JOIN sessions_metadata USING(session_id)" if len(meta_condition) > 0 else ""} - WHERE {" AND ".join(ch_sub_query_chart)} - GROUP BY timestamp - ORDER BY timestamp;""" + ch_query = f"""SELECT timestamp, COALESCE(avgOrNull(count), 0) AS value + FROM (SELECT toUnixTimestamp(toStartOfInterval(pages.datetime, INTERVAL %(step_size)s second ))*1000 AS timestamp, + session_id, COUNT(pages.session_id) AS count + FROM pages {"INNER JOIN sessions_metadata USING(session_id)" if len(meta_condition) > 0 else ""} + WHERE {" AND ".join(ch_sub_query_chart)} + GROUP BY timestamp,session_id + ORDER BY timestamp) AS groupped_data + WHERE count>0 + GROUP BY timestamp + ORDER BY timestamp;""" rows = ch.execute(query=ch_query, params={**params, **__get_constraint_values(args)}) rows = __complete_missing_steps(rows=rows, start_time=startTimestamp, end_time=endTimestamp, diff --git a/ee/api/chalicelib/core/projects.py b/ee/api/chalicelib/core/projects.py index af6decffd..6a06e8230 100644 --- a/ee/api/chalicelib/core/projects.py +++ b/ee/api/chalicelib/core/projects.py @@ -41,19 +41,8 @@ def __create(tenant_id, name): return get_project(tenant_id=tenant_id, project_id=project_id, include_gdpr=True) -def get_projects(tenant_id, recording_state=False, gdpr=None, recorded=False, stack_integrations=False, version=False, - last_tracker_version=None, user_id=None): +def get_projects(tenant_id, recording_state=False, gdpr=None, recorded=False, stack_integrations=False, user_id=None): with pg_client.PostgresClient() as cur: - tracker_query = "" - if last_tracker_version is not None and len(last_tracker_version) > 0: - tracker_query = cur.mogrify( - """,(SELECT tracker_version FROM public.sessions - WHERE sessions.project_id = s.project_id - AND tracker_version=%(version)s AND tracker_version IS NOT NULL LIMIT 1) AS tracker_version""", - {"version": last_tracker_version}).decode('UTF-8') - elif version: - tracker_query = ",(SELECT tracker_version FROM public.sessions WHERE sessions.project_id = s.project_id ORDER BY start_ts DESC LIMIT 1) AS tracker_version" - role_query = """INNER JOIN LATERAL (SELECT 1 FROM users INNER JOIN roles USING (role_id) @@ -70,7 +59,6 @@ def get_projects(tenant_id, recording_state=False, gdpr=None, recorded=False, st {',s.gdpr' if gdpr else ''} {',COALESCE((SELECT TRUE FROM public.sessions WHERE sessions.project_id = s.project_id LIMIT 1), FALSE) AS recorded' if recorded else ''} {',stack_integrations.count>0 AS stack_integrations' if stack_integrations else ''} - {tracker_query} FROM public.projects AS s {'LEFT JOIN LATERAL (SELECT COUNT(*) AS count FROM public.integrations WHERE s.project_id = integrations.project_id LIMIT 1) AS stack_integrations ON TRUE' if stack_integrations else ''} {role_query if user_id is not None else ""} @@ -104,19 +92,8 @@ def get_projects(tenant_id, recording_state=False, gdpr=None, recorded=False, st return helper.list_to_camel_case(rows) -def get_project(tenant_id, project_id, include_last_session=False, include_gdpr=None, version=False, - last_tracker_version=None): +def get_project(tenant_id, project_id, include_last_session=False, include_gdpr=None): with pg_client.PostgresClient() as cur: - tracker_query = "" - if last_tracker_version is not None and len(last_tracker_version) > 0: - tracker_query = cur.mogrify( - """,(SELECT tracker_version FROM public.sessions - WHERE sessions.project_id = s.project_id - AND tracker_version=%(version)s AND tracker_version IS NOT NULL LIMIT 1) AS tracker_version""", - {"version": last_tracker_version}).decode('UTF-8') - elif version: - tracker_query = ",(SELECT tracker_version FROM public.sessions WHERE sessions.project_id = s.project_id ORDER BY start_ts DESC LIMIT 1) AS tracker_version" - query = cur.mogrify(f"""\ SELECT s.project_id, @@ -125,7 +102,6 @@ def get_project(tenant_id, project_id, include_last_session=False, include_gdpr= s.save_request_payloads {",(SELECT max(ss.start_ts) FROM public.sessions AS ss WHERE ss.project_id = %(project_id)s) AS last_recorded_session_at" if include_last_session else ""} {',s.gdpr' if include_gdpr else ''} - {tracker_query} FROM public.projects AS s where s.tenant_id =%(tenant_id)s AND s.project_id =%(project_id)s diff --git a/ee/api/clean.sh b/ee/api/clean.sh new file mode 100755 index 000000000..59f723c80 --- /dev/null +++ b/ee/api/clean.sh @@ -0,0 +1,85 @@ +#!/bin/bash + +rm -rf ./chalicelib/core/alerts.py +rm -rf ./chalicelib/core/alerts_processor.py +rm -rf ./chalicelib/core/announcements.py +rm -rf ./chalicelib/core/collaboration_slack.py +rm -rf ./chalicelib/core/errors_favorite_viewed.py +rm -rf ./chalicelib/core/events.py +rm -rf ./chalicelib/core/events_ios.py +rm -rf ./chalicelib/core/dashboards.py +rm -rf ./chalicelib/core/funnels.py +rm -rf ./chalicelib/core/integration_base.py +rm -rf ./chalicelib/core/integration_base_issue.py +rm -rf ./chalicelib/core/integration_github.py +rm -rf ./chalicelib/core/integration_github_issue.py +rm -rf ./chalicelib/core/integration_jira_cloud.py +rm -rf ./chalicelib/core/integration_jira_cloud_issue.py +rm -rf ./chalicelib/core/integrations_manager.py +rm -rf ./chalicelib/core/issues.py +rm -rf ./chalicelib/core/jobs.py +rm -rf ./chalicelib/core/log_tool_bugsnag.py +rm -rf ./chalicelib/core/log_tool_cloudwatch.py +rm -rf ./chalicelib/core/log_tool_datadog.py +rm -rf ./chalicelib/core/log_tool_elasticsearch.py +rm -rf ./chalicelib/core/log_tool_newrelic.py +rm -rf ./chalicelib/core/log_tool_rollbar.py +rm -rf ./chalicelib/core/log_tool_sentry.py +rm -rf ./chalicelib/core/log_tool_stackdriver.py +rm -rf ./chalicelib/core/log_tool_sumologic.py +rm -rf ./chalicelib/core/metadata.py +rm -rf ./chalicelib/core/mobile.py +rm -rf ./chalicelib/core/sessions.py +rm -rf ./chalicelib/core/sessions_assignments.py +rm -rf ./chalicelib/core/sessions_favorite_viewed.py +rm -rf ./chalicelib/core/sessions_metas.py +rm -rf ./chalicelib/core/sessions_mobs.py +rm -rf ./chalicelib/core/significance.py +rm -rf ./chalicelib/core/slack.py +rm -rf ./chalicelib/core/socket_ios.py +rm -rf ./chalicelib/core/sourcemaps.py +rm -rf ./chalicelib/core/sourcemaps_parser.py +rm -rf ./chalicelib/core/weekly_report.py +rm -rf ./chalicelib/saml +rm -rf ./chalicelib/utils/html/ +rm -rf ./chalicelib/utils/__init__.py +rm -rf ./chalicelib/utils/args_transformer.py +rm -rf ./chalicelib/utils/captcha.py +rm -rf ./chalicelib/utils/dev.py +rm -rf ./chalicelib/utils/email_handler.py +rm -rf ./chalicelib/utils/email_helper.py +rm -rf ./chalicelib/utils/event_filter_definition.py +rm -rf ./chalicelib/utils/github_client_v3.py +rm -rf ./chalicelib/utils/helper.py +rm -rf ./chalicelib/utils/jira_client.py +rm -rf ./chalicelib/utils/metrics_helper.py +rm -rf ./chalicelib/utils/pg_client.py +rm -rf ./chalicelib/utils/s3.py +rm -rf ./chalicelib/utils/smtp.py +rm -rf ./chalicelib/utils/strings.py +rm -rf ./chalicelib/utils/TimeUTC.py +rm -rf ./routers/app/__init__.py +rm -rf ./routers/crons/__init__.py +rm -rf ./routers/subs/__init__.py +rm -rf ./routers/__init__.py +rm -rf ./chalicelib/core/assist.py +rm -rf ./auth/auth_apikey.py +rm -rf ./auth/auth_jwt.py +rm -rf ./build.sh +rm -rf ./routers/core.py +rm -rf ./routers/crons/core_crons.py +rm -rf ./routers/subs/dashboard.py +rm -rf ./db_changes.sql +rm -rf ./Dockerfile.bundle +rm -rf ./entrypoint.bundle.sh +rm -rf ./entrypoint.sh +rm -rf ./chalicelib/core/heatmaps.py +rm -rf ./routers/subs/insights.py +rm -rf ./schemas.py +rm -rf ./routers/subs/v1_api.py +rm -rf ./routers/subs/metrics.py +rm -rf ./chalicelib/core/custom_metrics.py +rm -rf ./chalicelib/core/performance_event.py +rm -rf ./chalicelib/core/saved_search.py +rm -rf ./app_alerts.py +rm -rf ./build_alerts.sh diff --git a/ee/api/development.md b/ee/api/development.md new file mode 100644 index 000000000..d980a24d7 --- /dev/null +++ b/ee/api/development.md @@ -0,0 +1,36 @@ +### Prerequisites + +- [Vagrant](../scripts/vagrant/README.md) +- Python 3.9 +- Pipenv + +### Development environment + +```bash +cd openreplay/ee/api +# Make your own copy of .env file and edit it as you want +cp .env.dev .env + +# Create a .venv folder to contain all you dependencies +mkdir .venv + +# Installing dependencies (pipenv will detect the .venv folder and use it as a target) +pipenv install -r requirements.txt [--skip-lock] + +# These commands must bu used everytime you make changes to FOSS. +# To clean the unused files before getting new ones +bash clean.sh +# To copy commun files from FOSS +bash prepare-dev.sh +``` + +### Building and deploying locally + +```bash +cd openreplay-contributions +vagrant ssh +cd openreplay-dev/openreplay/scripts/helmcharts +# For complete list of options +# bash local_deploy.sh help +bash local_deploy.sh api +``` diff --git a/ee/api/prepare-local.sh b/ee/api/prepare-dev.sh similarity index 100% rename from ee/api/prepare-local.sh rename to ee/api/prepare-dev.sh diff --git a/ee/api/routers/core_dynamic.py b/ee/api/routers/core_dynamic.py index 561100ff2..31ed1d099 100644 --- a/ee/api/routers/core_dynamic.py +++ b/ee/api/routers/core_dynamic.py @@ -97,10 +97,9 @@ def get_projects_limit(context: schemas.CurrentContext = Depends(OR_context)): @app.get('/projects/{projectId}', tags=['projects']) -def get_project(projectId: int, last_tracker_version: Optional[str] = None, - context: schemas.CurrentContext = Depends(OR_context)): +def get_project(projectId: int, context: schemas.CurrentContext = Depends(OR_context)): data = projects.get_project(tenant_id=context.tenant_id, project_id=projectId, include_last_session=True, - include_gdpr=True, last_tracker_version=last_tracker_version) + include_gdpr=True) if data is None: return {"errors": ["project not found"]} return {"data": data} @@ -227,15 +226,13 @@ def get_client(context: schemas.CurrentContext = Depends(OR_context)): r = tenants.get_by_tenant_id(context.tenant_id) if r is not None: r.pop("createdAt") - r["projects"] = projects.get_projects(tenant_id=context.tenant_id, recording_state=True, recorded=True, - stack_integrations=True, version=True, user_id=context.user_id) + return { 'data': r } @app.get('/projects', tags=['projects']) -def get_projects(last_tracker_version: Optional[str] = None, context: schemas.CurrentContext = Depends(OR_context)): +def get_projects(context: schemas.CurrentContext = Depends(OR_context)): return {"data": projects.get_projects(tenant_id=context.tenant_id, recording_state=True, gdpr=True, recorded=True, - stack_integrations=True, version=True, - last_tracker_version=last_tracker_version, user_id=context.user_id)} + stack_integrations=True, user_id=context.user_id)} diff --git a/ee/scripts/helm/db/init_dbs/postgresql/1.6.0/1.6.0.sql b/ee/scripts/helm/db/init_dbs/postgresql/1.6.0/1.6.0.sql index d7eeff911..bb0d7b7c0 100644 --- a/ee/scripts/helm/db/init_dbs/postgresql/1.6.0/1.6.0.sql +++ b/ee/scripts/helm/db/init_dbs/postgresql/1.6.0/1.6.0.sql @@ -305,7 +305,7 @@ VALUES ('Captured sessions', 'overview', '{ "position": 0 }', true, true, true, 'missing_resources', 'predefined', 'table'), ('Slowest Resources', 'resources', '{ - "col": 2, + "col": 4, "row": 2, "position": 0 }', true, true, true, 'slowest_resources', 'predefined', 'table'), diff --git a/ee/utilities/servers/websocket-cluster.js b/ee/utilities/servers/websocket-cluster.js index 904aaea17..0b8a56699 100644 --- a/ee/utilities/servers/websocket-cluster.js +++ b/ee/utilities/servers/websocket-cluster.js @@ -270,8 +270,13 @@ function extractSessionInfo(socket) { socket.handshake.query.sessionInfo.userCountry = null; if (geoip() !== null) { debug && console.log(`looking for location of ${socket.handshake.headers['x-forwarded-for'] || socket.handshake.address}`); - let country = geoip().country(socket.handshake.headers['x-forwarded-for'] || socket.handshake.address); - socket.handshake.query.sessionInfo.userCountry = country.country.isoCode; + try { + let country = geoip().country(socket.handshake.headers['x-forwarded-for'] || socket.handshake.address); + socket.handshake.query.sessionInfo.userCountry = country.country.isoCode; + } catch (e) { + debug && console.log("geoip-country failed"); + debug && console.log(e); + } } } } diff --git a/ee/utilities/servers/websocket.js b/ee/utilities/servers/websocket.js index a20f23078..51fa4cc41 100644 --- a/ee/utilities/servers/websocket.js +++ b/ee/utilities/servers/websocket.js @@ -249,8 +249,13 @@ function extractSessionInfo(socket) { socket.handshake.query.sessionInfo.userCountry = null; if (geoip() !== null) { debug && console.log(`looking for location of ${socket.handshake.headers['x-forwarded-for'] || socket.handshake.address}`); - let country = geoip().country(socket.handshake.headers['x-forwarded-for'] || socket.handshake.address); - socket.handshake.query.sessionInfo.userCountry = country.country.isoCode; + try { + let country = geoip().country(socket.handshake.headers['x-forwarded-for'] || socket.handshake.address); + socket.handshake.query.sessionInfo.userCountry = country.country.isoCode; + } catch (e) { + debug && console.log("geoip-country failed"); + debug && console.log(e); + } } } } diff --git a/frontend/app/Router.js b/frontend/app/Router.js index cc31ea640..314f36a6e 100644 --- a/frontend/app/Router.js +++ b/frontend/app/Router.js @@ -27,7 +27,6 @@ import { fetchList as fetchMetadata } from 'Duck/customField'; import { fetchList as fetchSiteList } from 'Duck/site'; import { fetchList as fetchAnnouncements } from 'Duck/announcements'; import { fetchList as fetchAlerts } from 'Duck/alerts'; -import { fetchWatchdogStatus } from 'Duck/watchdogs'; import { dashboardService } from "App/services"; import { withStore } from 'App/mstore' @@ -109,7 +108,6 @@ const ONBOARDING_REDIRECT_PATH = routes.onboarding(OB_DEFAULT_TAB); fetchSiteList, fetchAnnouncements, fetchAlerts, - fetchWatchdogStatus, }) class Router extends React.Component { state = { @@ -134,7 +132,6 @@ class Router extends React.Component { this.props.fetchMetadata() this.props.fetchAnnouncements(); this.props.fetchAlerts(); - this.props.fetchWatchdogStatus(); }, 100); }) }) diff --git a/frontend/app/components/BugFinder/BugFinder.js b/frontend/app/components/BugFinder/BugFinder.js index 3ce09d23b..6e4f589a6 100644 --- a/frontend/app/components/BugFinder/BugFinder.js +++ b/frontend/app/components/BugFinder/BugFinder.js @@ -20,7 +20,6 @@ import { LAST_7_DAYS } from 'Types/app/period'; import { resetFunnel } from 'Duck/funnels'; import { resetFunnelFilters } from 'Duck/funnelFilters' import NoSessionsMessage from 'Shared/NoSessionsMessage'; -// import TrackerUpdateMessage from 'Shared/TrackerUpdateMessage'; import SessionSearch from 'Shared/SessionSearch'; import MainSearchBar from 'Shared/MainSearchBar'; import { clearSearch, fetchSessions } from 'Duck/search'; @@ -130,7 +129,6 @@ export default class BugFinder extends React.PureComponent { />
- {/* */}
diff --git a/frontend/app/components/BugFinder/SessionsMenu/SessionsMenu.js b/frontend/app/components/BugFinder/SessionsMenu/SessionsMenu.js index fa0594316..d81ff1a7f 100644 --- a/frontend/app/components/BugFinder/SessionsMenu/SessionsMenu.js +++ b/frontend/app/components/BugFinder/SessionsMenu/SessionsMenu.js @@ -3,29 +3,28 @@ import { connect } from 'react-redux'; import cn from 'classnames'; import { SideMenuitem, SavedSearchList, Progress, Popup } from 'UI' import stl from './sessionMenu.css'; -import { fetchWatchdogStatus } from 'Duck/watchdogs'; import { clearEvents } from 'Duck/filters'; import { issues_types } from 'Types/session/issue' import { fetchList as fetchSessionList } from 'Duck/sessions'; +import { useModal } from 'App/components/Modal'; +import SessionSettings from 'Shared/SessionSettings/SessionSettings' function SessionsMenu(props) { - const { activeTab, keyMap, wdTypeCount, toggleRehydratePanel } = props; + const { activeTab } = props; + const { showModal } = useModal(); const onMenuItemClick = (filter) => { props.onMenuItemClick(filter) } - - const capturingAll = props.captureRate && props.captureRate.get('captureAll'); - return (
Sessions
- {capturingAll && Manage} - { !capturingAll && ( + showModal(, { right: true })}>Manage + {/* { !capturingAll && ( - )} + )} */}
@@ -87,5 +86,5 @@ export default connect(state => ({ filters: state.getIn([ 'filters', 'appliedFilter' ]), sessionsLoading: state.getIn([ 'sessions', 'fetchLiveListRequest', 'loading' ]), }), { - fetchWatchdogStatus, clearEvents, fetchSessionList + clearEvents, fetchSessionList })(SessionsMenu); diff --git a/frontend/app/components/Client/Client.js b/frontend/app/components/Client/Client.js index cb55d933d..518148a5c 100644 --- a/frontend/app/components/Client/Client.js +++ b/frontend/app/components/Client/Client.js @@ -7,6 +7,7 @@ import { fetchList as fetchMemberList } from 'Duck/member'; import ProfileSettings from './ProfileSettings'; import Integrations from './Integrations'; import ManageUsers from './ManageUsers'; +import UserView from './Users/UsersView'; import Sites from './Sites'; import CustomFields from './CustomFields'; import Webhooks from './Webhooks'; @@ -25,7 +26,7 @@ import Roles from './Roles'; export default class Client extends React.PureComponent { constructor(props){ super(props); - props.fetchMemberList(); + // props.fetchMemberList(); } setTab = (tab) => { @@ -36,7 +37,7 @@ export default class Client extends React.PureComponent { - + diff --git a/frontend/app/components/Client/CustomFields/CustomFields.js b/frontend/app/components/Client/CustomFields/CustomFields.js index b46994f0b..cd4e06da0 100644 --- a/frontend/app/components/Client/CustomFields/CustomFields.js +++ b/frontend/app/components/Client/CustomFields/CustomFields.js @@ -100,7 +100,7 @@ class CustomFields extends React.Component { title="No data available." size="small" show={ fields.size === 0 } - icon + animatedIcon="empty-state" >
{ fields.filter(i => i.index).map(field => ( diff --git a/frontend/app/components/Client/Sites/NewSiteForm.js b/frontend/app/components/Client/Sites/NewSiteForm.js index 4195c8c63..e7cd88f12 100644 --- a/frontend/app/components/Client/Sites/NewSiteForm.js +++ b/frontend/app/components/Client/Sites/NewSiteForm.js @@ -1,10 +1,11 @@ import { connect } from 'react-redux'; -import { Input, Button, Label } from 'UI'; -import { save, edit, update , fetchList } from 'Duck/site'; +import { Input, Button, Icon } from 'UI'; +import { save, edit, update , fetchList, remove } from 'Duck/site'; import { pushNewSite } from 'Duck/user'; import { setSiteId } from 'Duck/site'; import { withRouter } from 'react-router-dom'; import styles from './siteForm.css'; +import { confirm } from 'UI/Confirmation'; @connect(state => ({ site: state.getIn([ 'site', 'instance' ]), @@ -13,6 +14,7 @@ import styles from './siteForm.css'; loading: state.getIn([ 'site', 'save', 'loading' ]), }), { save, + remove, edit, update, pushNewSite, @@ -42,7 +44,6 @@ export default class NewSiteForm extends React.PureComponent { const { sites } = this.props; const site = sites.last(); if (!pathname.includes('/client')) { - console.log('site', site) this.props.setSiteId(site.get('id')) } this.props.onClose(null, site) @@ -53,6 +54,17 @@ export default class NewSiteForm extends React.PureComponent { } } + remove = async (site) => { + if (await confirm({ + header: 'Projects', + confirmation: `Are you sure you want to delete this Project? We won't be able to record anymore sessions.` + })) { + this.props.remove(site.id).then(() => { + this.props.onClose(null) + }); + } + }; + edit = ({ target: { name, value } }) => { this.setState({ existsError: false }); this.props.edit({ [ name ]: value }); @@ -73,7 +85,7 @@ export default class NewSiteForm extends React.PureComponent { className={ styles.input } />
-
+
+ +
{ this.state.existsError &&
{ "Site exists already. Please choose another one." } diff --git a/frontend/app/components/Client/Sites/SiteSearch/SiteSearch.tsx b/frontend/app/components/Client/Sites/SiteSearch/SiteSearch.tsx new file mode 100644 index 000000000..1a4c2b881 --- /dev/null +++ b/frontend/app/components/Client/Sites/SiteSearch/SiteSearch.tsx @@ -0,0 +1,34 @@ +import React, { useEffect } from 'react'; +import { Icon } from 'UI'; +import { debounce } from 'App/utils'; + +let debounceUpdate: any = () => {} +interface Props { + onChange: (value: string) => void; +} +function SiteSearch(props: Props) { + const { onChange } = props; + + useEffect(() => { + debounceUpdate = debounce((value) => onChange(value), 500); + }, []) + + const write = ({ target: { name, value } }) => { + debounceUpdate(value); + } + + return ( +
+ + +
+ ); +} + +export default SiteSearch; \ No newline at end of file diff --git a/frontend/app/components/Client/Sites/SiteSearch/index.ts b/frontend/app/components/Client/Sites/SiteSearch/index.ts new file mode 100644 index 000000000..54376cf15 --- /dev/null +++ b/frontend/app/components/Client/Sites/SiteSearch/index.ts @@ -0,0 +1 @@ +export { default } from './SiteSearch'; \ No newline at end of file diff --git a/frontend/app/components/Client/Sites/Sites.js b/frontend/app/components/Client/Sites/Sites.js index 4769815e5..ba3c72e6e 100644 --- a/frontend/app/components/Client/Sites/Sites.js +++ b/frontend/app/components/Client/Sites/Sites.js @@ -10,6 +10,7 @@ import GDPRForm from './GDPRForm'; import TrackingCodeModal from 'Shared/TrackingCodeModal'; import BlockedIps from './BlockedIps'; import { confirm } from 'UI/Confirmation'; +import SiteSearch from './SiteSearch'; const STATUS_MESSAGE_MAP = { [ RED ]: ' There seems to be an issue (please verify your installation)', @@ -43,6 +44,7 @@ class Sites extends React.PureComponent { showTrackingCode: false, modalContent: NONE, detailContent: NONE, + searchQuery: '', }; toggleBlockedIp = () => { @@ -85,7 +87,7 @@ class Sites extends React.PureComponent { getModalTitle() { switch (this.state.modalContent) { case NEW_SITE_FORM: - return 'New Project'; + return this.props.site.exists() ? 'Update Project' : 'New Project'; case GDPR_FORM: return 'Project Settings'; default: @@ -119,6 +121,7 @@ class Sites extends React.PureComponent { const isAdmin = user.admin || user.superAdmin; const canAddSites = isAdmin && account.limits.projects && account.limits.projects.remaining !== 0; const canDeleteSites = sites.size > 1 && isAdmin; + const filteredSites = sites.filter(site => site.name.toLowerCase().includes(this.state.searchQuery.toLowerCase())); return ( @@ -159,54 +162,71 @@ class Sites extends React.PureComponent { position="top left" /> - +
+ + this.setState({ searchQuery: value })} /> +
+
+
Name
+
Key
+
+ +
{ - sites.map(_site => ( -
-
- - + filteredSites.map(_site => ( + //
+
+
+
+ + +
+ } + content={ STATUS_MESSAGE_MAP[ _site.status ] } + inverted + position="top center" + /> + { _site.host }
- } - content={ STATUS_MESSAGE_MAP[ _site.status ] } - inverted - position="top center" - /> -
+
+
+ {_site.projectKey} +
+ {/*
{ _site.host }
{_site.projectKey}
+
*/} +
+
+ {/* */} + + {/* */}
-
- - -
- {/* */} -
-
+ //
)) }
diff --git a/frontend/app/components/Client/Users/UsersView.tsx b/frontend/app/components/Client/Users/UsersView.tsx new file mode 100644 index 000000000..37e6fdbcd --- /dev/null +++ b/frontend/app/components/Client/Users/UsersView.tsx @@ -0,0 +1,80 @@ +import React, { useEffect } from 'react'; +import UserList from './components/UserList'; +import { PageTitle, Popup, IconButton } from 'UI'; +import { useStore } from 'App/mstore'; +import { useObserver } from 'mobx-react-lite'; +import UserSearch from './components/UserSearch'; +import { useModal } from 'App/components/Modal'; +import UserForm from './components/UserForm'; +import { connect } from 'react-redux'; + +const PERMISSION_WARNING = 'You don’t have the permissions to perform this action.'; +const LIMIT_WARNING = 'You have reached users limit.'; +interface Props { + account: any; + isEnterprise: boolean; + limits: any; +} +function UsersView(props: Props) { + const { account, limits, isEnterprise } = props; + const { userStore, roleStore } = useStore(); + const userCount = useObserver(() => userStore.list.length); + const roles = useObserver(() => roleStore.list); + const { showModal } = useModal(); + + const reachedLimit = (limits.remaining + userStore.modifiedCount) <= 0; + const isAdmin = account.admin || account.superAdmin; + + const editHandler = (user = null) => { + userStore.initUser(user).then(() => { + showModal(, {}); + }); + } + + useEffect(() => { + if (roles.length === 0 && isEnterprise) { + roleStore.fetchRoles(); + } + }, []); + + return ( +
+
+ Team {userCount}
} + actionButton={( + + editHandler(null) } + /> +
+ } + content={ `${ !isAdmin ? PERMISSION_WARNING : (reachedLimit ? LIMIT_WARNING : 'Add team member') }` } + size="tiny" + inverted + position="top left" + /> + )} + /> +
+ +
+
+ +
+ ); +} + +export default connect(state => ({ + account: state.getIn([ 'user', 'account' ]), + isEnterprise: state.getIn([ 'user', 'client', 'edition' ]) === 'ee', + limits: state.getIn([ 'user', 'account', 'limits', 'teamMember' ]), +}))(UsersView); \ No newline at end of file diff --git a/frontend/app/components/Client/Users/components/UserForm/UserForm.tsx b/frontend/app/components/Client/Users/components/UserForm/UserForm.tsx new file mode 100644 index 000000000..25f35097c --- /dev/null +++ b/frontend/app/components/Client/Users/components/UserForm/UserForm.tsx @@ -0,0 +1,154 @@ +import React from 'react'; +import { Input, CopyButton, Button, Icon } from 'UI' +import cn from 'classnames'; +import { useStore } from 'App/mstore'; +import { useObserver } from 'mobx-react-lite'; +import { useModal } from 'App/components/Modal'; +import Select from 'Shared/Select'; +import { confirm } from 'UI/Confirmation'; +interface Props { + isSmtp?: boolean; + isEnterprise?: boolean; +} +function UserForm(props: Props) { + const { isSmtp = false, isEnterprise = false } = props; + const { hideModal } = useModal(); + const { userStore, roleStore } = useStore(); + const isSaving = useObserver(() => userStore.saving); + const user: any = useObserver(() => userStore.instance); + const roles = useObserver(() => roleStore.list.filter(r => r.isProtected ? user.isSuperAdmin : true).map(r => ({ label: r.name, value: r.roleId }))); + + const onChangeCheckbox = (e: any) => { + user.updateKey('isAdmin', !user.isAdmin); + } + + const onSave = () => { + userStore.saveUser(user).then(() => { + hideModal(); + }); + } + + const write = ({ target: { name, value } }) => { + user.updateKey(name, value); + } + + const deleteHandler = async () => { + if (await confirm({ + header: 'Confirm', + confirmButton: 'Yes, delete', + confirmation: `Are you sure you want to permanently delete this user?` + })) { + userStore.deleteUser(user.userId).then(() => { + hideModal(); + }); + } + } + + return useObserver(() => ( +
+
+

{`${user.exists() ? 'Update' : 'Invite'} User`}

+
+
+
+ + +
+ +
+ + +
+ { !isSmtp && +
+ SMTP is not configured (see here how to set it up). You can still add new users, but you’d have to manually copy then send them the invitation link. +
+ } +
+ +
+ + { !isEnterprise && ( +
+ + +
+ )); +} + +export default UserSearch; \ No newline at end of file diff --git a/frontend/app/components/Client/Users/components/UserSearch/index.ts b/frontend/app/components/Client/Users/components/UserSearch/index.ts new file mode 100644 index 000000000..3810a4030 --- /dev/null +++ b/frontend/app/components/Client/Users/components/UserSearch/index.ts @@ -0,0 +1 @@ +export { default } from './UserSearch'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricOverviewChart/CustomMetricOverviewChart.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricOverviewChart/CustomMetricOverviewChart.tsx index 1f65e1c81..b6d0ff995 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricOverviewChart/CustomMetricOverviewChart.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricOverviewChart/CustomMetricOverviewChart.tsx @@ -33,7 +33,7 @@ function CustomMetricOverviewChart(props: Props) { {gradientDef} diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTime/ResponseTime.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTime/ResponseTime.tsx index e1af351ee..47e47e12e 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTime/ResponseTime.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTime/ResponseTime.tsx @@ -47,7 +47,7 @@ function ResponseTime(props: Props) { /> */}
- + + + + + + diff --git a/frontend/app/components/Dashboard/components/DashboardForm/DashboardForm.tsx b/frontend/app/components/Dashboard/components/DashboardForm/DashboardForm.tsx index 9255663e9..9e7bd54f6 100644 --- a/frontend/app/components/Dashboard/components/DashboardForm/DashboardForm.tsx +++ b/frontend/app/components/Dashboard/components/DashboardForm/DashboardForm.tsx @@ -21,7 +21,7 @@ function DashboardForm(props: Props) {
- +
diff --git a/frontend/app/components/Dashboard/components/DashboardModal/DashboardModal.tsx b/frontend/app/components/Dashboard/components/DashboardModal/DashboardModal.tsx index 575cb7de8..d19556857 100644 --- a/frontend/app/components/Dashboard/components/DashboardModal/DashboardModal.tsx +++ b/frontend/app/components/Dashboard/components/DashboardModal/DashboardModal.tsx @@ -38,7 +38,7 @@ function DashboardModal(props) { return useObserver(() => (
diff --git a/frontend/app/components/Dashboard/components/DashboardOptions/DashboardOptions.tsx b/frontend/app/components/Dashboard/components/DashboardOptions/DashboardOptions.tsx new file mode 100644 index 000000000..b616d2e50 --- /dev/null +++ b/frontend/app/components/Dashboard/components/DashboardOptions/DashboardOptions.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { ItemMenu } from 'UI'; +import { connect } from 'react-redux'; + +interface Props { + editHandler: any + deleteHandler: any + renderReport: any + isEnterprise: boolean +} +function DashboardOptions(props: Props) { + const { editHandler, deleteHandler, renderReport, isEnterprise } = props; + const menuItems = [ + { icon: 'pencil', text: 'Rename', onClick: editHandler }, + { icon: 'text-paragraph', text: 'Add Description', onClick: editHandler }, + { icon: 'users', text: 'Visibility & Access', onClick: editHandler }, + { icon: 'trash', text: 'Delete', onClick: deleteHandler }, + ] + if (isEnterprise) { + menuItems.unshift({ icon: 'pdf-download', text: 'Download Report', onClick: renderReport }); + } + + return ( + + ); +} + +export default connect(state => ({ + isEnterprise: state.getIn([ 'user', 'client', 'edition' ]) === 'ee', +}))(DashboardOptions); \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/DashboardOptions/index.ts b/frontend/app/components/Dashboard/components/DashboardOptions/index.ts new file mode 100644 index 000000000..09297ef57 --- /dev/null +++ b/frontend/app/components/Dashboard/components/DashboardOptions/index.ts @@ -0,0 +1 @@ +export { default } from './DashboardOptions'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx index 16803e34c..083e55d1a 100644 --- a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx +++ b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx @@ -1,8 +1,8 @@ import React, { useEffect } from 'react'; -import { observer, useObserver } from 'mobx-react-lite'; +import { useObserver } from 'mobx-react-lite'; import { useStore } from 'App/mstore'; -import { Button, PageTitle, Link, Loader, NoContent, ItemMenu } from 'UI'; -import { withSiteId, dashboardMetricCreate, dashboardSelected, dashboard } from 'App/routes'; +import { Button, PageTitle, Loader, NoContent } from 'UI'; +import { withSiteId } from 'App/routes'; import withModal from 'App/components/Modal/withModal'; import DashboardWidgetGrid from '../DashboardWidgetGrid'; import { confirm } from 'UI/Confirmation'; @@ -13,12 +13,15 @@ import DashboardEditModal from '../DashboardEditModal'; import DateRange from 'Shared/DateRange'; import AlertFormModal from 'App/components/Alerts/AlertFormModal'; import withPageTitle from 'HOCs/withPageTitle'; +import withReport from 'App/components/hocs/withReport'; +import DashboardOptions from '../DashboardOptions'; interface Props { siteId: number; history: any match: any dashboardId: any + renderReport?: any } function DashboardView(props: Props) { const { siteId, dashboardId } = props; @@ -84,8 +87,15 @@ function DashboardView(props: Props) { />
- - + Add Metric + } + /> +
@@ -101,12 +111,10 @@ function DashboardView(props: Props) {
-
@@ -115,6 +123,7 @@ function DashboardView(props: Props) { siteId={siteId} dashboardId={dashboardId} onEditHandler={onAddWidgets} + id="report" /> void; + id?: string; } function DashboardWidgetGrid(props) { const { dashboardId, siteId } = props; const { dashboardStore } = useStore(); const loading = useObserver(() => dashboardStore.isLoading); - const dashbaord: any = dashboardStore.selectedDashboard; - const list: any = useObserver(() => dashbaord?.widgets); + const dashboard: any = dashboardStore.selectedDashboard; + const list: any = useObserver(() => dashboard?.widgets); return useObserver(() => ( @@ -29,13 +30,13 @@ function DashboardWidgetGrid(props) {
} > -
+
{list && list.map((item, index) => ( dashbaord.swapWidgetPosition(dragIndex, hoverIndex)} + moveListItem={(dragIndex, hoverIndex) => dashboard.swapWidgetPosition(dragIndex, hoverIndex)} dashboardId={dashboardId} siteId={siteId} isWidget={true} diff --git a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx index b29ab800c..8f2d5b051 100644 --- a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx +++ b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx @@ -86,14 +86,15 @@ function WidgetWrapper(props: Props) { }} ref={dragDropRef} onClick={props.onClick ? props.onClick : () => {}} + id={`widget-${widget.widgetId}`} > {isTemplate && }
-

{widget.name}

+
{widget.name}
{isWidget && ( -
+
{!isPredefined && ( <> @@ -118,11 +119,11 @@ function WidgetWrapper(props: Props) { )}
- + {/* */}
-
+ {/*
*/}
)); } diff --git a/frontend/app/components/Header/Header.js b/frontend/app/components/Header/Header.js index 4982a302e..d852e1c6a 100644 --- a/frontend/app/components/Header/Header.js +++ b/frontend/app/components/Header/Header.js @@ -98,7 +98,6 @@ const Header = (props) => { className={ styles.nav } activeClassName={ styles.active } > - { 'Errors' } + {component} , document.querySelector("#modal-root"), diff --git a/frontend/app/components/Modal/ModalOverlay.css b/frontend/app/components/Modal/ModalOverlay.css index e3e33562a..806b59137 100644 --- a/frontend/app/components/Modal/ModalOverlay.css +++ b/frontend/app/components/Modal/ModalOverlay.css @@ -7,13 +7,26 @@ /* transition: all 0.3s ease-in-out; */ animation: fade 1s forwards; } + .slide { position: absolute; + /* left: -100%; */ + /* -webkit-animation: slide 0.5s forwards; + animation: slide 0.5s forwards; */ +} + +.slideLeft { left: -100%; -webkit-animation: slide 0.5s forwards; animation: slide 0.5s forwards; } +.slideRight { + right: -100%; + -webkit-animation: slideRight 0.5s forwards; + animation: slideRight 0.5s forwards; +} + @keyframes fade { 0% { opacity: 0; @@ -29,4 +42,12 @@ @keyframes slide { 100% { left: 0; } +} + +@-webkit-keyframes slideRight { + 100% { right: 0; } +} + +@keyframes slideRight { + 100% { right: 0%; } } \ No newline at end of file diff --git a/frontend/app/components/Modal/ModalOverlay.tsx b/frontend/app/components/Modal/ModalOverlay.tsx index 4dad0ec61..270840a65 100644 --- a/frontend/app/components/Modal/ModalOverlay.tsx +++ b/frontend/app/components/Modal/ModalOverlay.tsx @@ -1,18 +1,19 @@ import React from 'react'; import { useModal } from 'App/components/Modal'; import stl from './ModalOverlay.css' +import cn from 'classnames'; -function ModalOverlay({ children }) { +function ModalOverlay({ children, left = false, right = false }) { let modal = useModal(); return ( -
+
modal.hideModal()} className={stl.overlay} style={{ background: "rgba(0,0,0,0.5)" }} /> -
{children}
+
{children}
); } diff --git a/frontend/app/components/Modal/index.tsx b/frontend/app/components/Modal/index.tsx index a653ed24f..339a79fbd 100644 --- a/frontend/app/components/Modal/index.tsx +++ b/frontend/app/components/Modal/index.tsx @@ -4,7 +4,9 @@ import Modal from './Modal'; const ModalContext = createContext({ component: null, - props: {}, + props: { + right: false, + }, showModal: (component: any, props: any) => {}, hideModal: () => {} }); diff --git a/frontend/app/components/Session_/Player/Controls/Controls.js b/frontend/app/components/Session_/Player/Controls/Controls.js index ab1baba3e..abbf40803 100644 --- a/frontend/app/components/Session_/Player/Controls/Controls.js +++ b/frontend/app/components/Session_/Player/Controls/Controls.js @@ -276,13 +276,13 @@ export default class Controls extends React.Component { label="Back" icon="replay-10" /> - + /> */}
)} diff --git a/frontend/app/components/Session_/Player/Controls/DraggableCircle.tsx b/frontend/app/components/Session_/Player/Controls/DraggableCircle.tsx index f4ebd6abf..385707879 100644 --- a/frontend/app/components/Session_/Player/Controls/DraggableCircle.tsx +++ b/frontend/app/components/Session_/Player/Controls/DraggableCircle.tsx @@ -19,7 +19,7 @@ function getStyles( // because IE will ignore our custom "empty image" drag preview. opacity: isDragging ? 0 : 1, height: isDragging ? 0 : '', - zIndex: '99', + zIndex: 99, cursor: 'move' } } diff --git a/frontend/app/components/hocs/withReport.tsx b/frontend/app/components/hocs/withReport.tsx new file mode 100644 index 000000000..be95952be --- /dev/null +++ b/frontend/app/components/hocs/withReport.tsx @@ -0,0 +1,162 @@ +import React, { useEffect } from 'react'; +import { convertElementToImage } from 'App/utils'; +import { jsPDF } from "jspdf"; +import { useStore } from 'App/mstore'; +import { observer, useObserver } from 'mobx-react-lite'; +import { connect } from 'react-redux'; +import { fileNameFormat } from 'App/utils'; +import { toast } from 'react-toastify'; +interface Props { + site: any +} +export default function withReport

( + WrappedComponent: React.ComponentType

, +) { + const ComponentWithReport = (props: P) => { + const [rendering, setRendering] = React.useState(false); + const { site } = props; + const { dashboardStore } = useStore(); + const dashboard: any = useObserver(() => dashboardStore.selectedDashboard); + const widgets: any = useObserver(() => dashboard?.widgets); + const period = useObserver(() => dashboardStore.period); + + const addFooters = (doc) => { + const pageCount = doc.internal.getNumberOfPages(); + for(var i = 1; i <= pageCount; i++) { + doc.setPage(i); + doc.setFontSize(8); + doc.setTextColor(136,136,136); + doc.text('Page ' + String(i) + ' of ' + String(pageCount), 200,290,null,null,"right"); + doc.addImage('/logo-open-replay-grey.png', 'png', 10, 288, 20, 0); + } + } + + const renderPromise = async (): Promise => { + const promise = new Promise((resolve, reject) => { + renderReport(resolve); + }); + toast.promise(promise, { + pending: 'Generating report...', + success: 'Report generated successfully', + }) + } + + const renderReport = async (cb) => { + document.body.scrollIntoView(); + const doc = new jsPDF('p', 'mm', 'a4'); + const now = new Date().toISOString(); + + doc.addMetadata('Author', 'OpenReplay'); + doc.addMetadata('Title', 'OpenReplay Report'); + doc.addMetadata('Subject', 'OpenReplay Report'); + doc.addMetadata('Keywords', 'OpenReplay Report'); + doc.addMetadata('Creator', 'OpenReplay'); + doc.addMetadata('Producer', 'OpenReplay'); + doc.addMetadata('CreationDate', now); + + + const parentElement = document.getElementById('report') as HTMLElement; + const pageHeight = 1200; + const pagesCount = parentElement.offsetHeight / pageHeight; + const pages: Array = []; + for(let i = 0; i < pagesCount; i++) { + const page = document.createElement('div'); + page.classList.add('page'); + page.style.height = `${pageHeight}px`; + page.style.whiteSpace = 'no-wrap !important'; + + const childrens = Array.from(parentElement.children).filter((child) => { + const rect = child.getBoundingClientRect(); + const parentRect = parentElement.getBoundingClientRect(); + const top = rect.top - parentRect.top; + return top >= i * pageHeight && top < (i + 1) * pageHeight; + }); + if (childrens.length > 0) { + pages.push(childrens); + } + } + + const rportLayer = document.getElementById("report-layer"); + + pages.forEach(async (page, index) => { + const pageDiv = document.createElement('div'); + pageDiv.classList.add('grid', 'gap-4', 'grid-cols-4', 'items-start', 'pb-10', 'auto-rows-min', 'printable-report'); + pageDiv.id = `page-${index}`; + pageDiv.style.backgroundColor = '#f6f6f6'; + pageDiv.style.gridAutoRows = 'min-content'; + pageDiv.style.padding = '50px'; + pageDiv.style.height = '490mm'; + + if (index > 0) { + pageDiv.style.paddingTop = '100px'; + } + + if (index === 0) { + const header = document.getElementById('report-header')?.cloneNode(true) as HTMLElement; + header.classList.add('col-span-4'); + header.style.display = 'block'; + pageDiv.appendChild(header); + } + page.forEach((child) => { + pageDiv.appendChild(child.cloneNode(true)); + }) + rportLayer?.appendChild(pageDiv); + }) + + setTimeout(async () => { + for (let i = 0; i < pages.length; i++) { + const pageDiv = document.getElementById(`page-${i}`) as HTMLElement; + const pageImage = await convertElementToImage(pageDiv); + doc.addImage(pageImage, 'PNG', 0, 0, 210, 0); + if (i === pages.length - 1) { + addFooters(doc); + doc.save(fileNameFormat(dashboard.name + '_Report_' + Date.now(), '.pdf')); + rportLayer!.innerHTML = ''; + cb(); + } else { + doc.addPage(); + } + } + }, 100) + } + + return ( + <> +

+
+
+ +
REPORT
+
+
+ Project: {site && site.name} +
+
+
+
{dashboard && dashboard.name}
+
+ {period && (period.range.start.format('MMM Do YY') + ' - ' + period.range.end.format('MMM Do YY'))} +
+
+ {dashboard && dashboard.description &&
{dashboard.description}
} +
+ +
+ + + ) + } + + return connect(state => ({ + site: state.getIn(['site', 'instance']), + }))(ComponentWithReport); +} \ No newline at end of file diff --git a/frontend/app/components/shared/Select/Select.tsx b/frontend/app/components/shared/Select/Select.tsx new file mode 100644 index 000000000..307fbb0ea --- /dev/null +++ b/frontend/app/components/shared/Select/Select.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import Select from 'react-select'; + +interface Props { + options: any[]; + isSearchable?: boolean; + defaultValue?: string; + plain?: boolean; + [x:string]: any; +} +export default function({ plain = false, options, isSearchable = false, defaultValue = '', ...rest }: Props) { + const customStyles = { + option: (provided, state) => ({ + ...provided, + whiteSpace: 'nowrap', + }), + menu: (provided, state) => ({ + ...provided, + top: 31, + }), + control: (provided) => { + const obj = { + ...provided, + border: 'solid thin #ddd' + } + if (plain) { + obj['border'] = '1px solid transparent' + } + return obj; + }, + valueContainer: (provided) => ({ + ...provided, + paddingRight: '0px', + }), + singleValue: (provided, state) => { + const opacity = state.isDisabled ? 0.5 : 1; + const transition = 'opacity 300ms'; + + return { ...provided, opacity, transition }; + } + } + const defaultSelected = defaultValue ? options.find(x => x.value === defaultValue) : null; + return ( + { + setCaptureRate(value) + setChanged(true); + }} + disabled={captureAll} + min={0} + minValue={0} + /> + +
+ of the sessions + +
+ + )); +} + +export default CaptureRate; \ No newline at end of file diff --git a/frontend/app/components/shared/SessionSettings/components/DefaultPlaying.tsx b/frontend/app/components/shared/SessionSettings/components/DefaultPlaying.tsx new file mode 100644 index 000000000..cb80c30b2 --- /dev/null +++ b/frontend/app/components/shared/SessionSettings/components/DefaultPlaying.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { Toggler } from 'UI'; +import { useStore } from 'App/mstore'; +import { useObserver } from 'mobx-react-lite'; + +function DefaultPlaying(props) { + const { settingsStore } = useStore(); + const sessionSettings = useObserver(() => settingsStore.sessionSettings) + + return useObserver(() => ( + <> +

Default Playing Options

+
Always start playing the session from the first issue.
+
+ sessionSettings.updateKey('skipToIssue', !sessionSettings.skipToIssue)} + /> +
+ + )); +} + +export default DefaultPlaying; \ No newline at end of file diff --git a/frontend/app/components/shared/SessionSettings/components/DefaultTimezone.tsx b/frontend/app/components/shared/SessionSettings/components/DefaultTimezone.tsx new file mode 100644 index 000000000..efb91fed9 --- /dev/null +++ b/frontend/app/components/shared/SessionSettings/components/DefaultTimezone.tsx @@ -0,0 +1,46 @@ +import React, { useEffect } from 'react'; +import { Toggler, Button } from 'UI'; +import Select from 'Shared/Select'; +import { useStore } from 'App/mstore'; +import { useObserver } from 'mobx-react-lite'; + +const str = new Date().toString().match(/([A-Z]+[\+-][0-9]+)/) +const d = str && str[1] || 'UTC'; +const timezoneOptions = [ + { label: d, value: 'local' }, + { label: 'UTC', value: 'UTC' }, +] + +function DefaultTimezone(props) { + const [changed, setChanged] = React.useState(false); + const { settingsStore } = useStore(); + const [timezone, setTimezone] = React.useState(settingsStore.sessionSettings.timezone); + const sessionSettings = useObserver(() => settingsStore.sessionSettings) + + return ( + <> +

Default Timezone

+
Session Time
+
+ { + setDurationSettings({ ...durationSettings, operator: value }); + setChanged(true); + }} + /> +
+
+ { + setDurationSettings({ ...durationSettings, count: value }); + setChanged(true); + }} + /> +
+
+ +
+ ); +} + +ToggleButton.defaultProps = { + icons: { + checked: , + unchecked: + } +}; + +ToggleButton.propTypes = { + disabled: PropTypes.bool, + defaultChecked: PropTypes.bool, + className: PropTypes.string, + onChange: PropTypes.func, + icons: PropTypes.oneOfType([ + PropTypes.bool, + PropTypes.shape({ + checked: PropTypes.node, + unchecked: PropTypes.node + }) + ]) +}; + +export default ToggleButton; \ No newline at end of file diff --git a/frontend/app/components/ui/Toggler/Toggler.js b/frontend/app/components/ui/Toggler/Toggler.js index ef6097936..1cf3ba78a 100644 --- a/frontend/app/components/ui/Toggler/Toggler.js +++ b/frontend/app/components/ui/Toggler/Toggler.js @@ -3,18 +3,22 @@ import styles from './toggler.css'; export default ({ onChange, name, - className, + className = '', checked, + label = '', }) => (
-
); diff --git a/frontend/app/components/ui/Toggler/toggler.css b/frontend/app/components/ui/Toggler/toggler.css index 171272d67..cf9cf0838 100644 --- a/frontend/app/components/ui/Toggler/toggler.css +++ b/frontend/app/components/ui/Toggler/toggler.css @@ -6,6 +6,16 @@ height: 16px; } +.label { + display: flex; + align-items: center; + cursor: pointer; + + & span { + padding-left: 10px; + color: $gray-medium; + } +} .switch input { display:none; } @@ -29,18 +39,23 @@ width: 20px; left: 0; bottom: -2px; - background-color: white; + /* background-color: white; */ transition: .4s; border-radius: 50%; border: solid 1px rgba(0, 0, 0, 0.2); + + background: #394EFF; + box-shadow: 0px 2px 1px -1px rgba(0, 0, 0, 0.2), 0px 1px 1px rgba(0, 0, 0, 0.14), 0px 1px 3px rgba(0, 0, 0, 0.12); } .slider.checked { - background-color: $teal !important; + /* background-color: $teal !important; */ + background-color: #b2bcff !important; } .slider.checked:before { border: solid 1px $teal; + transform: translateX(15px); } .slider.checked:before { diff --git a/frontend/app/components/ui/index.js b/frontend/app/components/ui/index.js index e6a4253e0..193d24384 100644 --- a/frontend/app/components/ui/index.js +++ b/frontend/app/components/ui/index.js @@ -58,5 +58,6 @@ export { default as HelpText } from './HelpText'; export { default as SideMenuHeader } from './SideMenuHeader'; export { default as PageTitle } from './PageTitle'; export { default as Pagination } from './Pagination'; +export { default as Toggler } from './Toggler'; export { Input, Modal, Form, Message, Card } from 'semantic-ui-react'; diff --git a/frontend/app/constants/index.js b/frontend/app/constants/index.js index 239c7478a..30c5c5095 100644 --- a/frontend/app/constants/index.js +++ b/frontend/app/constants/index.js @@ -20,3 +20,4 @@ export { WEBHOOK as CHANNEL_WEBHOOK } from './schedule'; export { default } from './filterOptions'; +export { default as storageKeys } from './storageKeys'; \ No newline at end of file diff --git a/frontend/app/constants/storageKeys.ts b/frontend/app/constants/storageKeys.ts new file mode 100644 index 000000000..ee39d53d6 --- /dev/null +++ b/frontend/app/constants/storageKeys.ts @@ -0,0 +1,3 @@ +export const SKIP_TO_ISSUE = "__$session-skipToIssue$__" +export const TIMEZONE = "__$session-timezone$__" +export const DURATION_FILTER = "__$session-durationFilter$__" \ No newline at end of file diff --git a/frontend/app/date.js b/frontend/app/date.ts similarity index 94% rename from frontend/app/date.js rename to frontend/app/date.ts index 089c48362..8a9501a86 100644 --- a/frontend/app/date.js +++ b/frontend/app/date.ts @@ -61,7 +61,7 @@ export const getDateFromMill = date => * @param {Date} Date to be checked. * @return {Boolean} */ -export const isToday = (date: Date):boolean => date.hasSame(new Date(), 'day'); +export const isToday = (date: DateTime):boolean => date.hasSame(new Date(), 'day'); export function formatDateTimeDefault(timestamp: number): string { @@ -113,4 +113,8 @@ export const formatMs = (ms: number): string => ms < 1000 ? `${ Math.trunc(ms) } export const convertTimestampToUtcTimestamp = (timestamp: number): number => { return DateTime.fromMillis(timestamp).toUTC().toMillis(); -} \ No newline at end of file +} + +export const nowFormatted = (format?: string): string => { + return DateTime.local().toFormat(format || 'LLL dd, yyyy, hh:mm a'); +} diff --git a/frontend/app/duck/errors.js b/frontend/app/duck/errors.js index 2d54b3a0a..65bb70bc1 100644 --- a/frontend/app/duck/errors.js +++ b/frontend/app/duck/errors.js @@ -59,7 +59,12 @@ function reducer(state = initialState, action = {}) { case EDIT_OPTIONS: return state.mergeIn(["options"], action.instance); case success(FETCH): - return state.set("instance", ErrorInfo(action.data)); + if (state.get("list").find(e => e.get("errorId") === action.id)) { + return updateItemInList(state, { errorId: action.data.errorId, viewed: true }) + .set("instance", ErrorInfo(action.data)); + } else { + return state.set("instance", ErrorInfo(action.data)); + } case success(FETCH_TRACE): return state.set("instanceTrace", List(action.data.trace)).set('sourcemapUploaded', action.data.sourcemapUploaded); case success(FETCH_LIST): diff --git a/frontend/app/duck/search.js b/frontend/app/duck/search.js index dd879592c..c003515a9 100644 --- a/frontend/app/duck/search.js +++ b/frontend/app/duck/search.js @@ -9,6 +9,7 @@ import { fetchList as fetchSessionList } from './sessions'; import { fetchList as fetchErrorsList } from './errors'; import { FilterCategory, FilterKey, IssueType } from 'Types/filter/filterType'; import { filtersMap, liveFiltersMap, generateFilterOptions, generateLiveFilterOptions } from 'Types/filter/newFilter'; +import { DURATION_FILTER } from 'App/constants/storageKeys' const ERRORS_ROUTE = errorsRoute(); @@ -149,6 +150,28 @@ export const reduceThenFetchResource = actionCreator => (...args) => (dispatch, filter.limit = 10; filter.page = getState().getIn([ 'search', 'currentPage']); + // duration filter from local storage + if (!filter.filters.find(f => f.type === FilterKey.DURATION)) { + const durationFilter = JSON.parse(localStorage.getItem(DURATION_FILTER) || '{"count": 0}'); + let durationValue = parseInt(durationFilter.count) + if (durationValue > 0) { + const value = [0]; + durationValue = durationFilter.countType === 'min' ? durationValue * 60 * 1000 : durationValue * 1000; + if (durationFilter.operator === '<') { + value[0] = durationValue; + } else if (durationFilter.operator === '>') { + value[1] = durationValue; + } + + filter.filters = filter.filters.concat({ + type: FilterKey.DURATION, + operator: 'is', + value, + }); + } + } + + return isRoute(ERRORS_ROUTE, window.location.pathname) ? dispatch(fetchErrorsList(filter)) : dispatch(fetchSessionList(filter)); diff --git a/frontend/app/mstore/index.tsx b/frontend/app/mstore/index.tsx index 864740955..f29b5cedf 100644 --- a/frontend/app/mstore/index.tsx +++ b/frontend/app/mstore/index.tsx @@ -1,19 +1,28 @@ import React from 'react'; import DashboardStore, { IDashboardSotre } from './dashboardStore'; import MetricStore, { IMetricStore } from './metricStore'; +import UserStore from './userStore'; +import RoleStore from './roleStore'; import APIClient from 'App/api_client'; -import { dashboardService, funnelService, metricService } from 'App/services'; import FunnelStore from './funnelStore'; +import { dashboardService, metricService, funnelService, sessionService, userService } from 'App/services'; +import SettingsStore from './settingsStore'; export class RootStore { dashboardStore: IDashboardSotre; metricStore: IMetricStore; funnelStore: FunnelStore; + settingsStore: SettingsStore; + userStore: UserStore; + roleStore: RoleStore; constructor() { this.dashboardStore = new DashboardStore(); this.metricStore = new MetricStore(); this.funnelStore = new FunnelStore(); + this.settingsStore = new SettingsStore(); + this.userStore = new UserStore(); + this.roleStore = new RoleStore(); } initClient() { @@ -21,6 +30,8 @@ export class RootStore { dashboardService.initClient(client) metricService.initClient(client) funnelService.initClient(client) + sessionService.initClient(client) + userService.initClient(client) } } diff --git a/frontend/app/mstore/roleStore.ts b/frontend/app/mstore/roleStore.ts new file mode 100644 index 000000000..6f87b4bcb --- /dev/null +++ b/frontend/app/mstore/roleStore.ts @@ -0,0 +1,31 @@ +import { makeAutoObservable, observable, action } from "mobx" +import { userService } from "App/services"; +import Role, { IRole } from "./types/role"; + +export default class UserStore { + list: IRole[] = []; + loading: boolean = false; + + constructor() { + makeAutoObservable(this, { + list: observable, + loading: observable, + }) + } + + fetchRoles(): Promise { + this.loading = true; + return new Promise((resolve, reject) => { + userService.getRoles() + .then(response => { + this.list = response.map((role: any) => new Role().fromJson(role)); + resolve(response); + }).catch(error => { + this.loading = false; + reject(error); + }).finally(() => { + this.loading = false; + }); + }); + } +} \ No newline at end of file diff --git a/frontend/app/mstore/settingsStore.ts b/frontend/app/mstore/settingsStore.ts new file mode 100644 index 000000000..d3da8fd10 --- /dev/null +++ b/frontend/app/mstore/settingsStore.ts @@ -0,0 +1,42 @@ +import { makeAutoObservable, observable, action } from "mobx" +import SessionSettings from "./types/sessionSettings" +import { sessionService } from "App/services" +import { toast } from 'react-toastify'; + +export default class SettingsStore { + loadingCaptureRate: boolean = false; + sessionSettings: SessionSettings = new SessionSettings() + captureRateFetched: boolean = false; + constructor() { + makeAutoObservable(this, { + sessionSettings: observable, + }) + } + + saveCaptureRate(data: any) { + return sessionService.saveCaptureRate(data) + .then(data => { + this.sessionSettings.merge({ + captureRate: data.rate, + captureAll: data.captureAll + }) + toast.success("Capture rate saved successfully"); + }).catch(err => { + toast.error("Error saving capture rate"); + }) + } + + fetchCaptureRate(): Promise { + this.loadingCaptureRate = true; + return sessionService.fetchCaptureRate() + .then(data => { + this.sessionSettings.merge({ + captureRate: data.rate, + captureAll: data.captureAll + }) + this.captureRateFetched = true; + }).finally(() => { + this.loadingCaptureRate = false; + }) + } +} \ No newline at end of file diff --git a/frontend/app/mstore/types/dashboard.ts b/frontend/app/mstore/types/dashboard.ts index 57b191252..c4ee376c6 100644 --- a/frontend/app/mstore/types/dashboard.ts +++ b/frontend/app/mstore/types/dashboard.ts @@ -6,6 +6,7 @@ import { toast } from 'react-toastify'; export interface IDashboard { dashboardId: any name: string + description: string isPublic: boolean widgets: IWidget[] metrics: any[] @@ -35,6 +36,7 @@ export default class Dashboard implements IDashboard { public static get ID_KEY():string { return "dashboardId" } dashboardId: any = undefined name: string = "New Dashboard" + description: string = "" isPublic: boolean = true widgets: IWidget[] = [] metrics: any[] = [] @@ -46,6 +48,7 @@ export default class Dashboard implements IDashboard { constructor() { makeAutoObservable(this, { name: observable, + description: observable, isPublic: observable, widgets: observable, isValid: observable, @@ -85,7 +88,8 @@ export default class Dashboard implements IDashboard { isPublic: this.isPublic, // widgets: this.widgets.map(w => w.toJson()) // widgets: this.widgets - metrics: this.metrics + metrics: this.metrics, + description: this.description, } } @@ -93,6 +97,7 @@ export default class Dashboard implements IDashboard { runInAction(() => { this.dashboardId = json.dashboardId this.name = json.name + this.description = json.description this.isPublic = json.isPublic this.isPinned = json.isPinned this.widgets = json.widgets ? json.widgets.map(w => new Widget().fromJson(w)).sort((a, b) => a.position - b.position) : [] @@ -185,4 +190,4 @@ export default class Dashboard implements IDashboard { this.metrics.push(metricId) } } -} \ No newline at end of file +} diff --git a/frontend/app/mstore/types/role.ts b/frontend/app/mstore/types/role.ts new file mode 100644 index 000000000..5d8da871a --- /dev/null +++ b/frontend/app/mstore/types/role.ts @@ -0,0 +1,45 @@ +import { makeAutoObservable, observable, runInAction } from "mobx"; + +export interface IRole { + roleId: string; + name: string; + description: string; + isProtected: boolean; + + fromJson(json: any); + toJson(): any; +} + +export default class Role implements IRole { + roleId: string = ''; + name: string = ''; + description: string = ''; + isProtected: boolean = false; + + + constructor() { + makeAutoObservable(this, { + roleId: observable, + name: observable, + description: observable, + }) + } + + fromJson(json: any) { + runInAction(() => { + this.roleId = json.roleId; + this.name = json.name; + this.description = json.description; + this.isProtected = json.protected; + }) + return this; + } + + toJson() { + return { + id: this.roleId, + name: this.name, + description: this.description, + } + } +} \ No newline at end of file diff --git a/frontend/app/mstore/types/sessionSettings.ts b/frontend/app/mstore/types/sessionSettings.ts new file mode 100644 index 000000000..9374b0df1 --- /dev/null +++ b/frontend/app/mstore/types/sessionSettings.ts @@ -0,0 +1,38 @@ +import { makeAutoObservable, runInAction, observable, action, reaction } from "mobx" +import { SKIP_TO_ISSUE, TIMEZONE, DURATION_FILTER } from 'App/constants/storageKeys' + +export default class SessionSettings { + skipToIssue: boolean = localStorage.getItem(SKIP_TO_ISSUE) === 'true'; + timezone: string = localStorage.getItem(TIMEZONE) || 'UTC'; + durationFilter: any = JSON.parse(localStorage.getItem(DURATION_FILTER) || '{}'); + captureRate: number = 0 + captureAll: boolean = false + + constructor() { + makeAutoObservable(this, { + updateKey: action + }) + } + + merge(settings: any) { + for (const key in settings) { + if (settings.hasOwnProperty(key)) { + this.updateKey(key, settings[key]); + } + } + } + + updateKey(key: string, value: any) { + runInAction(() => { + this[key] = value + }) + + if (key === 'captureRate' || key === 'captureAll') return + + if (key === 'durationFilter') { + localStorage.setItem(`__$session-${key}$__`, JSON.stringify(value)); + } else { + localStorage.setItem(`__$session-${key}$__`, value); + } + } +} diff --git a/frontend/app/mstore/types/user.ts b/frontend/app/mstore/types/user.ts new file mode 100644 index 000000000..c69b31067 --- /dev/null +++ b/frontend/app/mstore/types/user.ts @@ -0,0 +1,105 @@ +import { runInAction, makeAutoObservable, observable } from 'mobx' +import { DateTime } from 'luxon'; +import { validateEmail, validateName } from 'App/validate'; + +export interface IUser { + userId: string + email: string + createdAt: string + isAdmin: boolean + isSuperAdmin: boolean + isJoined: boolean + isExpiredInvite: boolean + roleId: string + roleName: string + invitationLink: string + + + updateKey(key: string, value: any): void + fromJson(json: any): IUser + toJson(): any + toSave(): any +} + +export default class User implements IUser { + userId: string = ''; + name: string = ''; + email: string = ''; + createdAt: string = ''; + isAdmin: boolean = false; + isSuperAdmin: boolean = false; + isJoined: boolean = false; + isExpiredInvite: boolean = false; + roleId: string = ''; + roleName: string = ''; + invitationLink: string = ''; + + constructor() { + makeAutoObservable(this, { + userId: observable, + email: observable, + createdAt: observable, + isAdmin: observable, + isSuperAdmin: observable, + isJoined: observable, + isExpiredInvite: observable, + roleId: observable, + roleName: observable, + invitationLink: observable, + }) + } + + updateKey(key: string, value: any) { + runInAction(() => { + this[key] = value + }) + } + + fromJson(json: any) { + runInAction(() => { + this.userId = json.userId || json.id; // TODO api returning id + this.name = json.name; + this.email = json.email; + this.createdAt = json.createdAt && DateTime.fromISO(json.createdAt || 0) + this.isAdmin = json.admin + this.isSuperAdmin = json.superAdmin + this.isJoined = json.joined + this.isExpiredInvite = json.expiredInvitation + this.roleId = json.roleId + this.roleName = json.roleName + this.invitationLink = json.invitationLink + }) + return this; + } + + toJson() { + return { + userId: this.userId, + name: this.name, + email: this.email, + admin: this.isAdmin, + superAdmin: this.isSuperAdmin, + roleId: this.roleId, + joined: this.isJoined, + invitationLink: this.invitationLink, + expiredInvitation: this.isExpiredInvite, + } + } + + toSave() { + return { + name: this.name, + email: this.email, + admin: this.isAdmin, + roleId: this.roleId, + } + } + + valid() { + return validateName(this.name, { empty: false }) && validateEmail(this.email) && !!this.roleId; + } + + exists() { + return !!this.userId; + } +} \ No newline at end of file diff --git a/frontend/app/mstore/userStore.ts b/frontend/app/mstore/userStore.ts new file mode 100644 index 000000000..d0633a037 --- /dev/null +++ b/frontend/app/mstore/userStore.ts @@ -0,0 +1,162 @@ +import { makeAutoObservable, observable, action } from "mobx" +import User, { IUser } from "./types/user"; +import { userService } from "App/services"; +import { toast } from 'react-toastify'; +import copy from 'copy-to-clipboard'; + +export default class UserStore { + list: IUser[] = []; + instance: IUser|null = null; + page: number = 1; + pageSize: number = 10; + searchQuery: string = ""; + modifiedCount: number = 0; + + loading: boolean = false; + saving: boolean = false; + + constructor() { + makeAutoObservable(this, { + instance: observable, + updateUser: action, + updateKey: action, + initUser: action, + }) + } + + initUser(user?: any ): Promise { + return new Promise((resolve, reject) => { + if (user) { + this.instance = new User().fromJson(user.toJson()); + } else { + this.instance = new User(); + } + resolve(); + }) + } + + updateKey(key: string, value: any) { + this[key] = value + + if (key === 'searchQuery') { + this.page = 1 + } + } + + updateUser(user: IUser) { + const index = this.list.findIndex(u => u.userId === user.userId); + if (index > -1) { + this.list[index] = user; + } + } + + fetchUser(userId: string): Promise { + this.loading = true; + return new Promise((resolve, reject) => { + userService.one(userId) + .then(response => { + this.instance = new User().fromJson(response.data); + resolve(response); + }).catch(error => { + this.loading = false; + reject(error); + }).finally(() => { + this.loading = false; + }); + }); + } + + fetchUsers(): Promise { + this.loading = true; + return new Promise((resolve, reject) => { + userService.all() + .then(response => { + this.list = response.map(user => new User().fromJson(user)); + resolve(response); + }).catch(error => { + this.loading = false; + reject(error); + }).finally(() => { + this.loading = false; + }); + }); + } + + saveUser(user: IUser): Promise { + this.saving = true; + const wasCreating = !user.userId; + return new Promise((resolve, reject) => { + userService.save(user).then(response => { + const newUser = new User().fromJson(response); + if (wasCreating) { + this.modifiedCount -= 1; + this.list.push(new User().fromJson(newUser)); + toast.success('User created successfully'); + } else { + this.updateUser(newUser); + toast.success('User updated successfully'); + } + resolve(response); + }).catch(error => { + this.saving = false; + reject(error); + }).finally(() => { + this.saving = false; + }); + }); + } + + deleteUser(userId: string): Promise { + this.saving = true; + return new Promise((resolve, reject) => { + userService.delete(userId) + .then(response => { + this.modifiedCount += 1; + this.list = this.list.filter(user => user.userId !== userId); + resolve(response); + }).catch(error => { + this.saving = false; + reject(error); + }).finally(() => { + this.saving = false; + }); + }); + } + + copyInviteCode(userId: string): void { + const content = this.list.find(u => u.userId === userId)?.invitationLink; + if (content) { + copy(content); + toast.success('Invite code copied successfully'); + } else { + toast.error('Invite code not found'); + } + } + + generateInviteCode(userId: string): Promise { + this.saving = true; + const promise = new Promise((resolve, reject) => { + userService.generateInviteCode(userId) + .then(response => { + const index = this.list.findIndex(u => u.userId === userId); + if (index > -1) { + this.list[index].updateKey('isExpiredInvite', false); + this.list[index].updateKey('invitationLink', response.invitationLink); + } + resolve(response); + }).catch(error => { + this.saving = false; + reject(error); + }).finally(() => { + this.saving = false; + }); + }); + + toast.promise(promise, { + pending: 'Generating an invite code...', + success: 'Invite code generated successfully', + }) + + return promise; + } +} \ No newline at end of file diff --git a/frontend/app/player/MessageDistributor/MessageDistributor.ts b/frontend/app/player/MessageDistributor/MessageDistributor.ts index 68ae14c72..653b05f54 100644 --- a/frontend/app/player/MessageDistributor/MessageDistributor.ts +++ b/frontend/app/player/MessageDistributor/MessageDistributor.ts @@ -8,11 +8,11 @@ import Profile from 'Types/session/profile'; import ReduxAction from 'Types/session/reduxAction'; import { update } from '../store'; -import { +import { init as initListsDepr, append as listAppend, - setStartTime as setListsStartTime - } from '../lists'; + setStartTime as setListsStartTime +} from '../lists'; import StatedScreen from './StatedScreen/StatedScreen'; @@ -26,6 +26,7 @@ import ActivityManager from './managers/ActivityManager'; import AssistManager from './managers/AssistManager'; import MFileReader from './messages/MFileReader'; +import loadFiles from './network/loadFiles'; import { INITIAL_STATE as SUPER_INITIAL_STATE, State as SuperState } from './StatedScreen/StatedScreen'; import { INITIAL_STATE as ASSIST_INITIAL_STATE, State as AssistState } from './managers/AssistManager'; @@ -33,7 +34,7 @@ import { INITIAL_STATE as ASSIST_INITIAL_STATE, State as AssistState } from './m import type { PerformanceChartPoint } from './managers/PerformanceTrackManager'; import type { SkipInterval } from './managers/ActivityManager'; -const LIST_NAMES = [ "redux", "mobx", "vuex", "ngrx", "graphql", "exceptions", "profiles", "longtasks" ] as const; +const LIST_NAMES = ["redux", "mobx", "vuex", "ngrx", "graphql", "exceptions", "profiles", "longtasks"] as const; const LISTS_INITIAL_STATE = {}; LIST_NAMES.forEach(name => { LISTS_INITIAL_STATE[`${name}ListNow`] = []; @@ -65,15 +66,15 @@ type ListsObject = { } function initLists(): ListsObject { - const lists: Partial = {} ; + const lists: Partial = {}; for (var i = 0; i < LIST_NAMES.length; i++) { - lists[ LIST_NAMES[i] ] = new ListWalker(); + lists[LIST_NAMES[i]] = new ListWalker(); } return lists as ListsObject; } -import type { +import type { Message, SetPageLocation, ConnectionInformation, @@ -110,7 +111,7 @@ export default class MessageDistributor extends StatedScreen { private navigationStartOffset: number = 0; private lastMessageTime: number = 0; - constructor(private readonly session: any /*Session*/, jwt: string, config, live: boolean) { + constructor(private readonly session: any /*Session*/, jwt: string, config, live: boolean) { super(); this.pagesManager = new PagesManager(this, this.session.isMobile) this.mouseManager = new MouseManager(this); @@ -128,7 +129,7 @@ export default class MessageDistributor extends StatedScreen { /* == REFACTOR_ME == */ const eventList = this.session.events.toJSON(); initListsDepr({ - event: eventList, + event: eventList, stack: this.session.stackEvents.toJSON(), resource: this.session.resources.toJSON(), }); @@ -146,96 +147,83 @@ export default class MessageDistributor extends StatedScreen { } } - - // subscribeOnMessages(sockUrl) { - // this.setMessagesLoading(true); - // const socket = new WebSocket(sockUrl); - // socket.binaryType = 'arraybuffer'; - // socket.onerror = (e) => { - // // TODO: reconnect - // update({ error: true }); - // } - // socket.onmessage = (socketMessage) => { - // const data = new Uint8Array(socketMessage.data); - // const msgs = []; - // messageGenerator // parseBuffer(msgs, data); - // // TODO: count indexes. Now will not work due to wrong indexes - // //msgs.forEach(this.distributeMessage); - // this.setMessagesLoading(false); - // this.setDisconnected(false); - // } - // this._socket = socket; - // } - + private waitingForFiles: boolean = false private loadMessages(): void { - const fileUrl: string = this.session.mobsUrl; - this.setMessagesLoading(true); - window.fetch(fileUrl) - .then(r => r.arrayBuffer()) - .then(b => { - const r = new MFileReader(new Uint8Array(b), this.sessionStart); - const msgs: Array = []; + this.setMessagesLoading(true) + this.waitingForFiles = true - while (r.hasNext()) { - const next = r.next(); - if (next != null) { - this.distributeMessage(next[0], next[1]); - msgs.push(next[0]); + const r = new MFileReader(new Uint8Array(), this.sessionStart) + const msgs: Array = [] + loadFiles(this.session.mobsUrl, + b => { + r.append(b) + let next: ReturnType + while (next = r.next()) { + const [msg, index] = next + this.distributeMessage(msg, index) + this.lastMessageTime = Math.max(msg.time, this.lastMessageTime) + + msgs.push(msg) } - } - // @ts-ignore Hack for upet (TODO: fix ordering in one mutation (removes first)) - const headChildrenIds = msgs.filter(m => m.parentID === 1).map(m => m.id); - //const createNodeTypes = ["create_text_node", "create_element_node"]; - this.pagesManager.sort((m1, m2) =>{ - if (m1.time === m2.time) { - if (m1.tp === "remove_node" && m2.tp !== "remove_node") { - if (headChildrenIds.includes(m1.id)) { - return -1; - } - } else if (m2.tp === "remove_node" && m1.tp !== "remove_node") { - if (headChildrenIds.includes(m2.id)) { - return 1; - } - } else if (m2.tp === "remove_node" && m1.tp === "remove_node") { - const m1FromHead = headChildrenIds.includes(m1.id); - const m2FromHead = headChildrenIds.includes(m2.id); - if (m1FromHead && !m2FromHead) { - return -1; - } else if (m2FromHead && !m1FromHead) { - return 1; + logger.info("Messages count: ", msgs.length, msgs) + + // @ts-ignore Hack for upet (TODO: fix ordering in one mutation in tracker(removes first)) + const headChildrenIds = msgs.filter(m => m.parentID === 1).map(m => m.id); + this.pagesManager.sort((m1, m2) => { + if (m1.time === m2.time) { + if (m1.tp === "remove_node" && m2.tp !== "remove_node") { + if (headChildrenIds.includes(m1.id)) { + return -1; + } + } else if (m2.tp === "remove_node" && m1.tp !== "remove_node") { + if (headChildrenIds.includes(m2.id)) { + return 1; + } + } else if (m2.tp === "remove_node" && m1.tp === "remove_node") { + const m1FromHead = headChildrenIds.includes(m1.id); + const m2FromHead = headChildrenIds.includes(m2.id); + if (m1FromHead && !m2FromHead) { + return -1; + } else if (m2FromHead && !m1FromHead) { + return 1; + } } } - } - return 0; - }) - + return 0; + }) - logger.info("Messages count: ", msgs.length, msgs); - - const stateToUpdate: {[key:string]: any} = { - performanceChartData: this.performanceTrackManager.chartData, - performanceAvaliability: this.performanceTrackManager.avaliability, - }; - this.activirtManager?.end(); - stateToUpdate.skipIntervals = this.activirtManager?.list || []; - LIST_NAMES.forEach(key => { - stateToUpdate[ `${ key }List` ] = this.lists[ key ].list; - }); - update(stateToUpdate); - - this.windowNodeCounter.reset(); - - this.setMessagesLoading(false); + const stateToUpdate: {[key:string]: any} = { + performanceChartData: this.performanceTrackManager.chartData, + performanceAvaliability: this.performanceTrackManager.avaliability, + } + LIST_NAMES.forEach(key => { + stateToUpdate[ `${ key }List` ] = this.lists[ key ].list + }) + update(stateToUpdate) + this.setMessagesLoading(false) + } + ) + .then(() => { + this.windowNodeCounter.reset() + if (this.activirtManager) { + this.activirtManager.end() + update({ + skipIntervals: this.activirtManager.list + }) + } + this.waitingForFiles = false + this.setMessagesLoading(false) + }) + .catch(e => { + logger.error(e) + this.waitingForFiles = false + this.setMessagesLoading(false) + update({ error: true }) }) - .catch((e) => { - logger.error(e); - this.setMessagesLoading(false); - update({ error: true }); - }); } - move(t: number, index?: number):void { + move(t: number, index?: number): void { const stateToUpdate: Partial = {}; /* == REFACTOR_ME == */ const lastLoadedLocationMsg = this.loadedLocationManager.moveToLast(t, index); @@ -248,7 +236,7 @@ export default class MessageDistributor extends StatedScreen { if (llEvent.domContentLoadedTime != null) { stateToUpdate.domContentLoadedTime = { time: llEvent.domContentLoadedTime + this.navigationStartOffset, //TODO: predefined list of load event for the network tab (merge events & SetPageLocation: add navigationStart to db) - value: llEvent.domContentLoadedTime, + value: llEvent.domContentLoadedTime, } } if (llEvent.loadTime != null) { @@ -277,9 +265,9 @@ export default class MessageDistributor extends StatedScreen { } LIST_NAMES.forEach(key => { - const lastMsg = this.lists[ key ].moveToLast(t, key === 'exceptions' ? undefined : index); + const lastMsg = this.lists[key].moveToLast(t, key === 'exceptions' ? undefined : index); if (lastMsg != null) { - stateToUpdate[`${key}ListNow`] = this.lists[ key ].listNow; + stateToUpdate[`${key}ListNow`] = this.lists[key].listNow; } }); @@ -298,21 +286,25 @@ export default class MessageDistributor extends StatedScreen { this.window.scrollTo(lastScroll.x, lastScroll.y); } // Moving mouse and setting :hover classes on ready view - this.mouseManager.move(t); + this.mouseManager.move(t); const lastClick = this.clickManager.moveToLast(t); if (!!lastClick && t - lastClick.time < 600) { // happend during last 600ms this.cursor.click(); } // After all changes - redraw the marker //this.marker.redraw(); - }) + }) + + if (this.waitingForFiles && this.lastMessageTime <= t) { + this.setMessagesLoading(true) + } } _decodeMessage(msg, keys: Array) { const decoded = {}; try { keys.forEach(key => { - decoded[ key ] = this.decoder.decode(msg[ key ]); + decoded[key] = this.decoder.decode(msg[key]); }); } catch (e) { logger.error("Error on message decoding: ", e, msg); @@ -323,15 +315,13 @@ export default class MessageDistributor extends StatedScreen { /* Binded */ distributeMessage = (msg: Message, index: number): void => { - this.lastMessageTime = msg.time; - - if ([ + if ([ "mouse_move", "mouse_click", "create_element_node", // not a user activity, though visual change "set_input_value", "set_input_checked", - "set_viewport_size", + "set_viewport_size", "set_viewport_scroll", ].includes(msg.tp)) { this.activirtManager?.updateAcctivity(msg.time); @@ -343,13 +333,13 @@ export default class MessageDistributor extends StatedScreen { /* Lists: */ case "console_log": if (msg.level === 'debug') break; - listAppend("log", Log({ + listAppend("log", Log({ level: msg.level, value: msg.value, - time, + time, index, })); - break; + break; case "fetch": listAppend("fetch", Resource({ method: msg.method, @@ -362,118 +352,117 @@ export default class MessageDistributor extends StatedScreen { time: msg.timestamp - this.sessionStart, //~ index, })); - break; + break; /* */ case "set_page_location": this.locationManager.add(msg); if (msg.navigationStart > 0) { this.loadedLocationManager.add(msg); } - break; + break; case "set_viewport_size": this.resizeManager.add(msg); - break; + break; case "mouse_move": this.mouseManager.add(msg); - break; + break; case "mouse_click": this.clickManager.add(msg); - break; + break; case "set_viewport_scroll": this.scrollManager.add(msg); - break; + break; case "performance_track": this.performanceTrackManager.add(msg); - break; + break; case "set_page_visibility": this.performanceTrackManager.handleVisibility(msg) - break; + break; case "connection_information": this.connectionInfoManger.add(msg); - break; + break; case "o_table": this.decoder.set(msg.key, msg.value); - break; + break; case "redux": decoded = this._decodeMessage(msg, ["state", "action"]); logger.log(decoded) if (decoded != null) { this.lists.redux.add(decoded); } - break; + break; case "ng_rx": decoded = this._decodeMessage(msg, ["state", "action"]); logger.log(decoded) if (decoded != null) { this.lists.ngrx.add(decoded); - } - break; + } + break; case "vuex": decoded = this._decodeMessage(msg, ["state", "mutation"]); logger.log(decoded) if (decoded != null) { this.lists.vuex.add(decoded); - } - break; + } + break; case "mob_x": decoded = this._decodeMessage(msg, ["payload"]); logger.log(decoded) if (decoded != null) { this.lists.mobx.add(decoded); - } - break; + } + break; case "graph_ql": // @ts-ignore some hack? TODO: remove msg.duration = 0; this.lists.graphql.add(msg); - break; + break; case "profiler": this.lists.profiles.add(msg); - break; + break; case "long_task": this.lists.longtasks.add({ ...msg, time: msg.timestamp - this.sessionStart, }); - break; + break; default: - switch (msg.tp){ + switch (msg.tp) { case "create_document": this.windowNodeCounter.reset(); this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count); - break; + break; case "create_text_node": case "create_element_node": this.windowNodeCounter.addNode(msg.id, msg.parentID); this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count); - break; + break; case "move_node": this.windowNodeCounter.moveNode(msg.id, msg.parentID); this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count); - break; + break; case "remove_node": this.windowNodeCounter.removeNode(msg.id); this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count); - break; + break; } this.pagesManager.add(msg); - break; + break; } } - getLastMessageTime():number { + getLastMessageTime(): number { return this.lastMessageTime; } - getFirstMessageTime():number { + getFirstMessageTime(): number { return 0; //this.pagesManager.minTime; } // TODO: clean managers? clean() { super.clean(); - //if (this._socket) this._socket.close(); update(INITIAL_STATE); this.assistManager.clear(); } diff --git a/frontend/app/player/MessageDistributor/StatedScreen/Screen/Cursor.ts b/frontend/app/player/MessageDistributor/StatedScreen/Screen/Cursor.ts index 1b74ab027..b030403b8 100644 --- a/frontend/app/player/MessageDistributor/StatedScreen/Screen/Cursor.ts +++ b/frontend/app/player/MessageDistributor/StatedScreen/Screen/Cursor.ts @@ -27,7 +27,6 @@ export default class Cursor { } click() { - console.log("clickong ", styles.clicked) this.cursor.classList.add(styles.clicked) setTimeout(() => { this.cursor.classList.remove(styles.clicked) diff --git a/frontend/app/player/MessageDistributor/managers/AssistManager.ts b/frontend/app/player/MessageDistributor/managers/AssistManager.ts index 0cd752f1d..dc48b03f4 100644 --- a/frontend/app/player/MessageDistributor/managers/AssistManager.ts +++ b/frontend/app/player/MessageDistributor/managers/AssistManager.ts @@ -172,8 +172,20 @@ export default class AssistManager { socket.on('SESSION_RECONNECTED', () => { showDisconnectTimeout && clearTimeout(showDisconnectTimeout) }) + + socket.on('UPDATE_SESSION', ({ active }) => { + showDisconnectTimeout && clearTimeout(showDisconnectTimeout) + // if (typeof active === "boolean") { + // if (active) { + // + // } else { + // this.setStatus(ConnectionStatus.Inactive) + // } + // } + }) socket.on('SESSION_DISCONNECTED', e => { waitingForMessages = true + showDisconnectTimeout && clearTimeout(showDisconnectTimeout) showDisconnectTimeout = setTimeout(() => { if (this.cleaned) { return } this.setStatus(ConnectionStatus.Disconnected) diff --git a/frontend/app/player/MessageDistributor/messages/MFileReader.ts b/frontend/app/player/MessageDistributor/messages/MFileReader.ts index 0204259e5..a4df3e2f8 100644 --- a/frontend/app/player/MessageDistributor/messages/MFileReader.ts +++ b/frontend/app/player/MessageDistributor/messages/MFileReader.ts @@ -7,62 +7,65 @@ import RawMessageReader from './RawMessageReader'; // needSkipMessage() and next() methods here use buf and p protected properties, // which should be probably somehow incapsulated export default class MFileReader extends RawMessageReader { - private pLastMessageID: number = 0; - private currentTime: number = 0; - public error: boolean = false; + private pLastMessageID: number = 0 + private currentTime: number = 0 + public error: boolean = false constructor(data: Uint8Array, private readonly startTime: number) { - super(data); + super(data) } private needSkipMessage(): boolean { - if (this.p === 0) return false; + if (this.p === 0) return false for (let i = 7; i >= 0; i--) { if (this.buf[ this.p + i ] !== this.buf[ this.pLastMessageID + i ]) { - return this.buf[ this.p + i ] - this.buf[ this.pLastMessageID + i ] < 0; + return this.buf[ this.p + i ] - this.buf[ this.pLastMessageID + i ] < 0 } } - return true; + return true } private readRawMessage(): RawMessage | null { - this.skip(8); + this.skip(8) try { - return super.readMessage(); + const msg = super.readMessage() + if (!msg) { + this.skip(-8) + } + return msg } catch (e) { - this.error = true; - logger.error("Read message error:", e); - return null; + this.error = true + logger.error("Read message error:", e) + return null } } - hasNext():boolean { - return !this.error && this.hasNextByte(); - } - next(): [ Message, number] | null { - if (!this.hasNext()) { - return null; + if (this.error || !this.hasNextByte()) { + return null } while (this.needSkipMessage()) { - this.readRawMessage(); + if (!this.readRawMessage()) { + return null + } } - this.pLastMessageID = this.p; - const rMsg = this.readRawMessage(); + this.pLastMessageID = this.p + + const rMsg = this.readRawMessage() if (!rMsg) { - return null; + return null } if (rMsg.tp === "timestamp") { - this.currentTime = rMsg.timestamp - this.startTime; - } else { - const msg = Object.assign(rMsg, { - time: this.currentTime, - _index: this.pLastMessageID, - }) - return [msg, this.pLastMessageID]; - } - return null; + this.currentTime = rMsg.timestamp - this.startTime + return this.next() + } + + const msg = Object.assign(rMsg, { + time: this.currentTime, + _index: this.pLastMessageID, + }) + return [msg, this.pLastMessageID] } } \ No newline at end of file diff --git a/frontend/app/player/MessageDistributor/messages/urlResolve.ts b/frontend/app/player/MessageDistributor/messages/urlResolve.ts index b80ff4f9a..44298ec08 100644 --- a/frontend/app/player/MessageDistributor/messages/urlResolve.ts +++ b/frontend/app/player/MessageDistributor/messages/urlResolve.ts @@ -6,7 +6,6 @@ export function resolveURL(baseURL: string, relURL: string): string { } -var match = /bar/.exec("foobar"); const re1 = /url\(("[^"]*"|'[^']*'|[^)]*)\)/g const re2 = /@import "(.*?)"/g function cssUrlsIndex(css: string): Array<[number, number]> { diff --git a/frontend/app/player/MessageDistributor/network/loadFiles.ts b/frontend/app/player/MessageDistributor/network/loadFiles.ts new file mode 100644 index 000000000..5deecf472 --- /dev/null +++ b/frontend/app/player/MessageDistributor/network/loadFiles.ts @@ -0,0 +1,49 @@ +const NO_NTH_FILE = "nnf" + +export default function load( + urls: string[], + onData: (Uint8Array) => void, +): Promise { + const firstFileURL = urls.shift() + if (!firstFileURL) { + return Promise.reject("No urls provided") + } + return window.fetch(firstFileURL) + .then(r => { + if (r.status >= 400) { + throw new Error(`no start file. status code ${ r.status }`) + } + return r.arrayBuffer() + }) + .then(b => new Uint8Array(b)) + .then(onData) + .then(() => + urls.reduce((p, url) => + p.then(() => + window.fetch(url) + .then(r => { + return new Promise((res, rej) => { + if (r.status == 404) { + rej(NO_NTH_FILE) + return + } + if (r.status >= 400) { + rej(`Bad endfile status code ${r.status}`) + return + } + res(r.arrayBuffer()) + }) + }) + .then(b => new Uint8Array(b)) + .then(onData) + ), + Promise.resolve(), + ) + ) + .catch(e => { + if (e === NO_NTH_FILE) { + return + } + throw e + }) +} diff --git a/frontend/app/player/Player.ts b/frontend/app/player/Player.ts index f1f305868..358ed52c4 100644 --- a/frontend/app/player/Player.ts +++ b/frontend/app/player/Player.ts @@ -25,7 +25,7 @@ const HIGHEST_SPEED = 16; const SPEED_STORAGE_KEY = "__$player-speed$__"; const SKIP_STORAGE_KEY = "__$player-skip$__"; -const SKIP_TO_ISSUE_STORAGE_KEY = "__$player-skip-to-issue$__"; +const SKIP_TO_ISSUE_STORAGE_KEY = "__$session-skipToIssue$__"; const AUTOPLAY_STORAGE_KEY = "__$player-autoplay$__"; const SHOW_EVENTS_STORAGE_KEY = "__$player-show-events$__"; const storedSpeed: number = parseInt(localStorage.getItem(SPEED_STORAGE_KEY) || "") ; diff --git a/frontend/app/services/SessionService.ts b/frontend/app/services/SessionService.ts new file mode 100644 index 000000000..a7940edc1 --- /dev/null +++ b/frontend/app/services/SessionService.ts @@ -0,0 +1,23 @@ +import APIClient from 'App/api_client'; + +export default class SettingsService { + private client: APIClient; + + constructor(client?: APIClient) { + this.client = client ? client : new APIClient(); + } + + initClient(client?: APIClient) { + this.client = client || new APIClient(); + } + + saveCaptureRate(data: any) { + return this.client.post('/sample_rate', data); + } + + fetchCaptureRate() { + return this.client.get('/sample_rate') + .then(response => response.json()) + .then(response => response.data || 0); + } +} \ No newline at end of file diff --git a/frontend/app/services/UserService.ts b/frontend/app/services/UserService.ts new file mode 100644 index 000000000..615368db6 --- /dev/null +++ b/frontend/app/services/UserService.ts @@ -0,0 +1,57 @@ +import APIClient from 'App/api_client'; +import { IUser } from 'App/mstore/types/user' + +export default class UserService { + private client: APIClient; + + constructor(client?: APIClient) { + this.client = client ? client : new APIClient(); + } + + initClient(client?: APIClient) { + this.client = client || new APIClient(); + } + + all() { + return this.client.get('/client/members') + .then(response => response.json()) + .then(response => response.data || []); + } + + one(userId: string) { + return this.client.get('/users/' + userId) + .then(response => response.json()) + .then(response => response.data || {}); + } + + save(user: IUser): Promise { + const data = user.toSave(); + if (user.userId) { + return this.client.put('/client/members/' + user.userId, data) + .then(response => response.json()) + .then(response => response.data || {}) + } else { + return this.client.post('/client/members', data) + .then(response => response.json()) + .then(response => response.data || {}); + } + } + + generateInviteCode(userId: any): Promise { + return this.client.get(`/client/members/${userId}/reset`) + .then(response => response.json()) + .then(response => response.data || {}); + } + + delete(userId: string) { + return this.client.delete('/client/members/' + userId) + .then(response => response.json()) + .then(response => response.data || {}); + } + + getRoles() { + return this.client.get('/client/roles') + .then(response => response.json()) + .then(response => response.data || []); + } +} \ No newline at end of file diff --git a/frontend/app/services/index.ts b/frontend/app/services/index.ts index 2d7261f76..7ddcac0ff 100644 --- a/frontend/app/services/index.ts +++ b/frontend/app/services/index.ts @@ -1,7 +1,11 @@ import DashboardService, { IDashboardService } from "./DashboardService"; import MetricService, { IMetricService } from "./MetricService"; import FunnelService, { IFunnelService } from "./FunnelService"; +import SessionSerivce from "./SessionService"; +import UserService from "./UserService"; export const dashboardService: IDashboardService = new DashboardService(); export const metricService: IMetricService = new MetricService(); -export const funnelService: IFunnelService = new FunnelService(); \ No newline at end of file +export const sessionService: SessionSerivce = new SessionSerivce(); +export const userService: UserService = new UserService(); +export const funnelService: IFunnelService = new FunnelService(); diff --git a/frontend/app/styles/general.css b/frontend/app/styles/general.css index ecd424721..a679a9460 100644 --- a/frontend/app/styles/general.css +++ b/frontend/app/styles/general.css @@ -261,8 +261,6 @@ p { } } - - .tippy-tooltip.openreplay-theme { background-color: $tealx; color: white; @@ -274,4 +272,29 @@ p { .tippy-tooltip.openreplay-theme .tippy-backdrop { background-color: $tealx; +} + +@media print { + .no-print { + display:none !important; + } +} + +.printable-report * { + white-space: nowrap !important; +} +.recharts-default-legend { + display: flex !important; + align-items: center; + justify-content: center; +} + +.recharts-legend-item { + display: flex !important; + align-items: center !important; + white-space: nowrap !important; +} + +.recharts-legend-item-text { + white-space: nowrap !important; } \ No newline at end of file diff --git a/frontend/app/styles/main.css b/frontend/app/styles/main.css index 4258e43d7..3b7f5fe4b 100644 --- a/frontend/app/styles/main.css +++ b/frontend/app/styles/main.css @@ -147,13 +147,4 @@ height: 100vh; overflow-y: hidden; padding-right: 15px; -} - -/* .svg-map__location { - fill: #EEE !important; - cursor: pointer; - - &:hover { - fill: #fff !important; - } -} */ \ No newline at end of file +} \ No newline at end of file diff --git a/frontend/app/svg/icons/graph-up-arrow.svg b/frontend/app/svg/icons/graph-up-arrow.svg index fd582e467..9a54cd2de 100644 --- a/frontend/app/svg/icons/graph-up-arrow.svg +++ b/frontend/app/svg/icons/graph-up-arrow.svg @@ -1,3 +1,3 @@ - + \ No newline at end of file diff --git a/frontend/app/svg/icons/hash.svg b/frontend/app/svg/icons/hash.svg index 4621b1dac..17e32e5db 100644 --- a/frontend/app/svg/icons/hash.svg +++ b/frontend/app/svg/icons/hash.svg @@ -1,3 +1,3 @@ - + \ No newline at end of file diff --git a/frontend/app/svg/icons/pdf-download.svg b/frontend/app/svg/icons/pdf-download.svg new file mode 100644 index 000000000..ef3610238 --- /dev/null +++ b/frontend/app/svg/icons/pdf-download.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/app/svg/icons/percent.svg b/frontend/app/svg/icons/percent.svg new file mode 100644 index 000000000..6904d8218 --- /dev/null +++ b/frontend/app/svg/icons/percent.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/app/svg/icons/table.svg b/frontend/app/svg/icons/table.svg index 5e70d22c4..db76c22cf 100644 --- a/frontend/app/svg/icons/table.svg +++ b/frontend/app/svg/icons/table.svg @@ -1,3 +1,3 @@ - + \ No newline at end of file diff --git a/frontend/app/svg/icons/text-paragraph.svg b/frontend/app/svg/icons/text-paragraph.svg new file mode 100644 index 000000000..9779beabf --- /dev/null +++ b/frontend/app/svg/icons/text-paragraph.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/app/utils.js b/frontend/app/utils.js index 5f7e446fa..bdece4058 100644 --- a/frontend/app/utils.js +++ b/frontend/app/utils.js @@ -1,5 +1,6 @@ import JSBI from 'jsbi'; import chroma from "chroma-js"; +import * as htmlToImage from 'html-to-image'; export function debounce(callback, wait, context = this) { let timeout = null; @@ -26,6 +27,11 @@ export function randomInt(a, b) { return Math.round(rand); } +export const fileNameFormat = (str = '', ext = '') => { + const name = str.replace(/[^a-zA-Z0-9]/g, '_'); + return `${name}${ext}`; +}; + export const toUnderscore = s => s.split(/(?=[A-Z])/).join('_').toLowerCase(); export const getUniqueFilter = keys => @@ -246,4 +252,16 @@ export const positionOfTheNumber = (min, max, value, length) => { const interval = (max - min) / length; const position = Math.round((value - min) / interval); return position; +} + +export const convertElementToImage = async (el) => { + const fontEmbedCss = await htmlToImage.getFontEmbedCSS(el); + const image = await htmlToImage.toJpeg(el, { + pixelRatio: 2, + fontEmbedCss, + filter: function (node) { + return node.id !== 'no-print'; + }, + }); + return image; } \ No newline at end of file diff --git a/frontend/build.sh b/frontend/build.sh index 7b656bc8f..652be2acf 100644 --- a/frontend/build.sh +++ b/frontend/build.sh @@ -20,6 +20,7 @@ check_prereq() { function build(){ # Run docker as the same user, else we'll run in to permission issues. docker run --rm -v /etc/passwd:/etc/passwd -u `id -u`:`id -g` -v $(pwd):/home/${USER} -w /home/${USER} --name node_build node:14-stretch-slim /bin/bash -c "npm install && npm run build:oss" + echo "frotend build completed" } check_prereq diff --git a/frontend/dev-init.sh b/frontend/dev-init.sh new file mode 100644 index 000000000..e32647ff0 --- /dev/null +++ b/frontend/dev-init.sh @@ -0,0 +1,2 @@ +#!/bin/bash +npm install --legacy-peer-deps diff --git a/frontend/development.md b/frontend/development.md new file mode 100644 index 000000000..904c85f46 --- /dev/null +++ b/frontend/development.md @@ -0,0 +1,23 @@ +### Prerequisites + +- [Vagrant](../scripts/vagrant/README.md) +- Node Version 17 +- npm + +### Development environment + +```bash +cd openreplay/frontend +# Change endpoints to local openreplay installation +sed -i 's#PRODUCTION: true#PRODUCTION: false#g' env.js +sed -i "s#API_EDP: .*#API_EDP: 'http://openreplay.local/api',#g" env.js +sed -i "s#ASSETS_HOST: .*#ASSETS_HOST: 'http://openreplay.local/assets',#g" env.js + +# Installing dependencies +npm install + +# Generating assets +npm run gen:css-types +npm run gen:icons +npm run gen:colors +``` diff --git a/frontend/env.js b/frontend/env.js index 27700a8a5..d85afd233 100644 --- a/frontend/env.js +++ b/frontend/env.js @@ -1,6 +1,6 @@ require('dotenv').config() -// TODO: (the problem is during the build time the frontend is isolated, as far as I remember) +// TODO: (the problem is during the build time the frontend is isolated) //const trackerInfo = require('../tracker/tracker/package.json'); const oss = { @@ -21,7 +21,7 @@ const oss = { MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY, MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY, ICE_SERVERS: process.env.ICE_SERVERS, - TRACKER_VERSION: '3.5.9' // trackerInfo.version, + TRACKER_VERSION: '3.5.10' // trackerInfo.version, } module.exports = { diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 75fcde026..32a3b81fe 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -15,9 +15,11 @@ "codemirror": "^5.62.3", "copy-to-clipboard": "^3.3.1", "deep-diff": "^1.0.2", + "html-to-image": "^1.9.0", "immutable": "^4.0.0-rc.12", "jsbi": "^4.1.0", "jshint": "^2.11.1", + "jspdf": "^2.5.1", "luxon": "^1.24.1", "mobx": "^6.3.8", "mobx-react-lite": "^3.1.6", @@ -42,9 +44,10 @@ "react-redux": "^5.1.2", "react-router": "^4.3.1", "react-router-dom": "^4.3.1", + "react-select": "^5.3.1", "react-svg-map": "^2.2.0", "react-tippy": "^1.4.0", - "react-toastify": "^5.5.0", + "react-toastify": "^8.2.0", "react-virtualized": "^9.22.2", "recharts": "^2.1.9", "redux": "^4.0.5", @@ -111,31 +114,31 @@ } }, "node_modules/@ampproject/remapping": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz", - "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", "dev": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.0" + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" }, "engines": { "node": ">=6.0.0" } }, "node_modules/@babel/cli": { - "version": "7.17.6", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.17.6.tgz", - "integrity": "sha512-l4w608nsDNlxZhiJ5tE3DbNmr61fIKMZ6fTBo171VEFuFMIYuJ3mHRhTLEkKKyvx2Mizkkv/0a8OJOnZqkKYNA==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.17.10.tgz", + "integrity": "sha512-OygVO1M2J4yPMNOW9pb+I6kFGpQK77HmG44Oz3hg8xQIl5L/2zq+ZohwAdSaqYgVwM0SfmPHZHphH4wR8qzVYw==", "dev": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.4", + "@jridgewell/trace-mapping": "^0.3.8", "commander": "^4.0.1", "convert-source-map": "^1.1.0", "fs-readdir-recursive": "^1.1.0", "glob": "^7.0.0", "make-dir": "^2.1.0", - "slash": "^2.0.0", - "source-map": "^0.5.0" + "slash": "^2.0.0" }, "bin": { "babel": "bin/babel.js", @@ -152,20 +155,10 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/cli/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/@babel/code-frame": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", - "dev": true, "dependencies": { "@babel/highlight": "^7.16.7" }, @@ -174,30 +167,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.17.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.7.tgz", - "integrity": "sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.10.tgz", + "integrity": "sha512-GZt/TCsG70Ms19gfZO1tM4CVnXsPgEPBCpJu+Qz3L0LUDsY5nZqFZglIoPC1kIYOtNBZlrnFT+klg12vFGZXrw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.17.9", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.9.tgz", - "integrity": "sha512-5ug+SfZCpDAkVp9SFIZAzlW18rlzsOcJGaetCjkySnrXXDUw9AR8cDUm1iByTmdWM6yxX6/zycaV76w3YTF2gw==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.10.tgz", + "integrity": "sha512-liKoppandF3ZcBnIYFjfSDHZLKdLHGJRkoWtG8zQyGJBQfIYobpnVGI5+pLBNtS6psFLDzyq8+h5HiVljW9PNA==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.17.9", - "@babel/helper-compilation-targets": "^7.17.7", + "@babel/generator": "^7.17.10", + "@babel/helper-compilation-targets": "^7.17.10", "@babel/helper-module-transforms": "^7.17.7", "@babel/helpers": "^7.17.9", - "@babel/parser": "^7.17.9", + "@babel/parser": "^7.17.10", "@babel/template": "^7.16.7", - "@babel/traverse": "^7.17.9", - "@babel/types": "^7.17.0", + "@babel/traverse": "^7.17.10", + "@babel/types": "^7.17.10", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -213,28 +206,19 @@ } }, "node_modules/@babel/generator": { - "version": "7.17.9", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.9.tgz", - "integrity": "sha512-rAdDousTwxbIxbz5I7GEQ3lUip+xVCXooZNbsydCWs3xA7ZsYOv+CFRdzGxRX78BmQHu9B1Eso59AOZQOJDEdQ==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.10.tgz", + "integrity": "sha512-46MJZZo9y3o4kmhBVc7zW7i8dtR1oIK/sdO5NcfcZRhTGYi+KKJRtHNgsU6c4VUcJmUNV/LQdebD/9Dlv4K+Tg==", "dev": true, "dependencies": { - "@babel/types": "^7.17.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" + "@babel/types": "^7.17.10", + "@jridgewell/gen-mapping": "^0.1.0", + "jsesc": "^2.5.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/generator/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/@babel/helper-annotate-as-pure": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", @@ -261,14 +245,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.17.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz", - "integrity": "sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.10.tgz", + "integrity": "sha512-gh3RxjWbauw/dFiU/7whjd0qN9K6nPJMqe6+Er7rOavFh0CQUSwhAE3IcTho2rywPJFxej6TUUHDkWcYI6gGqQ==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.17.7", + "@babel/compat-data": "^7.17.10", "@babel/helper-validator-option": "^7.16.7", - "browserslist": "^4.17.5", + "browserslist": "^4.20.2", "semver": "^6.3.0" }, "engines": { @@ -399,7 +383,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", - "dev": true, "dependencies": { "@babel/types": "^7.16.7" }, @@ -442,7 +425,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -517,7 +499,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -564,7 +545,6 @@ "version": "7.17.9", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.17.9.tgz", "integrity": "sha512-J9PfEKCbFIv2X5bjTMiZu6Vf341N05QIY+d6FvVKynkG1S7G0j3I0QoRtWIrXhZ+/Nlb5Q0MzqL7TokEJ5BNHg==", - "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.16.7", "chalk": "^2.0.0", @@ -575,9 +555,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.17.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.9.tgz", - "integrity": "sha512-vqUSBLP8dQHFPdPi9bc5GK9vRkYHJ49fsZdtoJ8EQ8ibpwk5rPKfvNIwChB0KVXcIjcepEBBd2VHC5r9Gy8ueg==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.10.tgz", + "integrity": "sha512-n2Q6i+fnJqzOaq2VkdXxy2TCPCWQZHiCo0XqmrCvDWcZQKRyZzYi4Z0yxlBuN0w+r2ZHmre+Q087DSrw3pbJDQ==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -1038,7 +1018,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz", "integrity": "sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.16.7" }, @@ -1152,9 +1131,9 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz", - "integrity": "sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.17.10.tgz", + "integrity": "sha512-xJefea1DWXW09pW4Tm9bjwVlPDyYA2it3fWlmEjpYz6alPvTUjL0EOzNzI/FEOyI3r4/J7uVH5UqKgl1TQ5hqQ==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.16.7" @@ -1476,12 +1455,12 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.16.8.tgz", - "integrity": "sha512-j3Jw+n5PvpmhRR+mrgIh04puSANCk/T/UA3m3P1MjJkhlK906+ApHhDIqBQDdOgL/r1UYpz4GNclTXxyZrYGSw==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.17.10.tgz", + "integrity": "sha512-v54O6yLaJySCs6mGzaVOUw9T967GnH38T6CQSAtnzdNPwu84l2qAjssKzo/WSO8Yi7NF+7ekm5cVbF/5qiIgNA==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.16.7" + "@babel/helper-create-regexp-features-plugin": "^7.17.0" }, "engines": { "node": ">=6.9.0" @@ -1771,27 +1750,27 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.16.11", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.16.11.tgz", - "integrity": "sha512-qcmWG8R7ZW6WBRPZK//y+E3Cli151B20W1Rv7ln27vuPaXU/8TKms6jFdiJtF7UDTxcrb7mZd88tAeK9LjdT8g==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.17.10.tgz", + "integrity": "sha512-YNgyBHZQpeoBSRBg0xixsZzfT58Ze1iZrajvv0lJc70qDDGuGfonEnMGfWeSY0mQ3JTuCWFbMkzFRVafOyJx4g==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.16.8", - "@babel/helper-compilation-targets": "^7.16.7", + "@babel/compat-data": "^7.17.10", + "@babel/helper-compilation-targets": "^7.17.10", "@babel/helper-plugin-utils": "^7.16.7", "@babel/helper-validator-option": "^7.16.7", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.16.7", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.16.7", "@babel/plugin-proposal-async-generator-functions": "^7.16.8", "@babel/plugin-proposal-class-properties": "^7.16.7", - "@babel/plugin-proposal-class-static-block": "^7.16.7", + "@babel/plugin-proposal-class-static-block": "^7.17.6", "@babel/plugin-proposal-dynamic-import": "^7.16.7", "@babel/plugin-proposal-export-namespace-from": "^7.16.7", "@babel/plugin-proposal-json-strings": "^7.16.7", "@babel/plugin-proposal-logical-assignment-operators": "^7.16.7", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.7", "@babel/plugin-proposal-numeric-separator": "^7.16.7", - "@babel/plugin-proposal-object-rest-spread": "^7.16.7", + "@babel/plugin-proposal-object-rest-spread": "^7.17.3", "@babel/plugin-proposal-optional-catch-binding": "^7.16.7", "@babel/plugin-proposal-optional-chaining": "^7.16.7", "@babel/plugin-proposal-private-methods": "^7.16.11", @@ -1817,7 +1796,7 @@ "@babel/plugin-transform-block-scoping": "^7.16.7", "@babel/plugin-transform-classes": "^7.16.7", "@babel/plugin-transform-computed-properties": "^7.16.7", - "@babel/plugin-transform-destructuring": "^7.16.7", + "@babel/plugin-transform-destructuring": "^7.17.7", "@babel/plugin-transform-dotall-regex": "^7.16.7", "@babel/plugin-transform-duplicate-keys": "^7.16.7", "@babel/plugin-transform-exponentiation-operator": "^7.16.7", @@ -1826,15 +1805,15 @@ "@babel/plugin-transform-literals": "^7.16.7", "@babel/plugin-transform-member-expression-literals": "^7.16.7", "@babel/plugin-transform-modules-amd": "^7.16.7", - "@babel/plugin-transform-modules-commonjs": "^7.16.8", - "@babel/plugin-transform-modules-systemjs": "^7.16.7", + "@babel/plugin-transform-modules-commonjs": "^7.17.9", + "@babel/plugin-transform-modules-systemjs": "^7.17.8", "@babel/plugin-transform-modules-umd": "^7.16.7", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.16.8", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.17.10", "@babel/plugin-transform-new-target": "^7.16.7", "@babel/plugin-transform-object-super": "^7.16.7", "@babel/plugin-transform-parameters": "^7.16.7", "@babel/plugin-transform-property-literals": "^7.16.7", - "@babel/plugin-transform-regenerator": "^7.16.7", + "@babel/plugin-transform-regenerator": "^7.17.9", "@babel/plugin-transform-reserved-words": "^7.16.7", "@babel/plugin-transform-shorthand-properties": "^7.16.7", "@babel/plugin-transform-spread": "^7.16.7", @@ -1844,11 +1823,11 @@ "@babel/plugin-transform-unicode-escapes": "^7.16.7", "@babel/plugin-transform-unicode-regex": "^7.16.7", "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.16.8", + "@babel/types": "^7.17.10", "babel-plugin-polyfill-corejs2": "^0.3.0", "babel-plugin-polyfill-corejs3": "^0.5.0", "babel-plugin-polyfill-regenerator": "^0.3.0", - "core-js-compat": "^3.20.2", + "core-js-compat": "^3.22.1", "semver": "^6.3.0" }, "engines": { @@ -1986,19 +1965,19 @@ } }, "node_modules/@babel/traverse": { - "version": "7.17.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.9.tgz", - "integrity": "sha512-PQO8sDIJ8SIwipTPiR71kJQCKQYB5NGImbOviK8K+kg5xkNSYXLBupuX9QhatFowrsvo9Hj8WgArg3W7ijNAQw==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.10.tgz", + "integrity": "sha512-VmbrTHQteIdUUQNTb+zE12SHS/xQVIShmBPhlNP12hD5poF2pbITW1Z4172d03HegaQWhLffdkRJYtAzp0AGcw==", "dev": true, "dependencies": { "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.17.9", + "@babel/generator": "^7.17.10", "@babel/helper-environment-visitor": "^7.16.7", "@babel/helper-function-name": "^7.17.9", "@babel/helper-hoist-variables": "^7.16.7", "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.17.9", - "@babel/types": "^7.17.0", + "@babel/parser": "^7.17.10", + "@babel/types": "^7.17.10", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -2007,10 +1986,9 @@ } }, "node_modules/@babel/types": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", - "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", - "dev": true, + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.10.tgz", + "integrity": "sha512-9O26jG0mBYfGkUYCYZRnBwbVLd1UZOICEr2Em6InB6jVfsAv1GKgwXHmrSg+WFWDmeKTA6vyTZiN8tCSM5Oo3A==", "dependencies": { "@babel/helper-validator-identifier": "^7.16.7", "to-fast-properties": "^2.0.0" @@ -2054,6 +2032,99 @@ "node": ">=10.0.0" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.9.2", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.9.2.tgz", + "integrity": "sha512-Pr/7HGH6H6yKgnVFNEj2MVlreu3ADqftqjqwUvDy/OJzKFgxKeTQ+eeUf20FOTuHVkDON2iNa25rAXVYtWJCjw==", + "dependencies": { + "@babel/helper-module-imports": "^7.12.13", + "@babel/plugin-syntax-jsx": "^7.12.13", + "@babel/runtime": "^7.13.10", + "@emotion/hash": "^0.8.0", + "@emotion/memoize": "^0.7.5", + "@emotion/serialize": "^1.0.2", + "babel-plugin-macros": "^2.6.1", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.0.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/@emotion/memoize": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.5.tgz", + "integrity": "sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ==" + }, + "node_modules/@emotion/babel-plugin/node_modules/@emotion/serialize": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.3.tgz", + "integrity": "sha512-2mSSvgLfyV3q+iVh3YWgNlUc2a9ZlDU7DjuP5MjK3AXRR0dYigCrP99aeFtaB2L/hjfEZdSThn5dsZ0ufqbvsA==", + "dependencies": { + "@emotion/hash": "^0.8.0", + "@emotion/memoize": "^0.7.4", + "@emotion/unitless": "^0.7.5", + "@emotion/utils": "^1.0.0", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/@emotion/utils": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.1.0.tgz", + "integrity": "sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ==" + }, + "node_modules/@emotion/babel-plugin/node_modules/babel-plugin-macros": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz", + "integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==", + "dependencies": { + "@babel/runtime": "^7.7.2", + "cosmiconfig": "^6.0.0", + "resolve": "^1.12.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/csstype": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", + "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==" + }, + "node_modules/@emotion/babel-plugin/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@emotion/cache": { "version": "10.0.29", "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.29.tgz", @@ -2097,8 +2168,7 @@ "node_modules/@emotion/hash": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", - "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", - "dev": true + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" }, "node_modules/@emotion/is-prop-valid": { "version": "0.8.8", @@ -2112,8 +2182,72 @@ "node_modules/@emotion/memoize": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", - "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", - "dev": true + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==" + }, + "node_modules/@emotion/react": { + "version": "11.9.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.9.0.tgz", + "integrity": "sha512-lBVSF5d0ceKtfKCDQJveNAtkC7ayxpVlgOohLgXqRwqWr9bOf4TZAFFyIcNngnV6xK6X4x2ZeXq7vliHkoVkxQ==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@emotion/babel-plugin": "^11.7.1", + "@emotion/cache": "^11.7.1", + "@emotion/serialize": "^1.0.3", + "@emotion/utils": "^1.1.0", + "@emotion/weak-memoize": "^0.2.5", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/react/node_modules/@emotion/cache": { + "version": "11.7.1", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.7.1.tgz", + "integrity": "sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A==", + "dependencies": { + "@emotion/memoize": "^0.7.4", + "@emotion/sheet": "^1.1.0", + "@emotion/utils": "^1.0.0", + "@emotion/weak-memoize": "^0.2.5", + "stylis": "4.0.13" + } + }, + "node_modules/@emotion/react/node_modules/@emotion/serialize": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.3.tgz", + "integrity": "sha512-2mSSvgLfyV3q+iVh3YWgNlUc2a9ZlDU7DjuP5MjK3AXRR0dYigCrP99aeFtaB2L/hjfEZdSThn5dsZ0ufqbvsA==", + "dependencies": { + "@emotion/hash": "^0.8.0", + "@emotion/memoize": "^0.7.4", + "@emotion/unitless": "^0.7.5", + "@emotion/utils": "^1.0.0", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/react/node_modules/@emotion/sheet": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.1.0.tgz", + "integrity": "sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g==" + }, + "node_modules/@emotion/react/node_modules/@emotion/utils": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.1.0.tgz", + "integrity": "sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ==" + }, + "node_modules/@emotion/react/node_modules/csstype": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", + "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==" }, "node_modules/@emotion/serialize": { "version": "0.11.16", @@ -2173,8 +2307,7 @@ "node_modules/@emotion/unitless": { "version": "0.7.5", "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", - "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", - "dev": true + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" }, "node_modules/@emotion/utils": { "version": "0.11.3", @@ -2185,8 +2318,7 @@ "node_modules/@emotion/weak-memoize": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz", - "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==", - "dev": true + "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" }, "node_modules/@fullhuman/postcss-purgecss": { "version": "2.3.0", @@ -2261,10 +2393,32 @@ "node": ">=8" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz", - "integrity": "sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.6.tgz", + "integrity": "sha512-R7xHtBSNm+9SyvpJkdQl+qrM3Hm2fea3Ef197M3mUug+v+yR+Rhfbs7PBtcBUVnIWJ4JcAdjvij+c8hXS9p5aw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.0.tgz", + "integrity": "sha512-SfJxIxNVYLTsKwzB3MoOQ1yxf4w/E6MdkvTgrgAt1bfxjSrLUoHMKrDOykwN14q65waezZIdqDneUIPh4/sKxg==", "dev": true, "engines": { "node": ">=6.0.0" @@ -2708,18 +2862,10 @@ "node": ">=6" } }, - "node_modules/@socket.io/base64-arraybuffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@socket.io/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", - "integrity": "sha512-dOlCBKnDw4iShaIsH/bxujKTM18+2TOAsYz+KSc11Am38H4q5Xw8Bbz97ZYdrVNM+um3p7w86Bvvmcn9q+5+eQ==", - "engines": { - "node": ">= 0.6.0" - } - }, "node_modules/@socket.io/component-emitter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.0.0.tgz", - "integrity": "sha512-2pTGuibAXJswAPJjaKisthqS/NOK5ypG4LYT6tEAV0S/mxW0zOIvYvGK0V8w8+SHxAm6vRMSjqSalFXeBAqs+Q==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" }, "node_modules/@storybook/addons": { "version": "6.4.22", @@ -4676,9 +4822,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "14.18.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.13.tgz", - "integrity": "sha512-Z6/KzgyWOga3pJNS42A+zayjhPbf2zM3hegRQaOPnLOzEi86VV++6FLDWgR1LGrVCRufP/ph2daa3tEa5br1zA==", + "version": "14.18.16", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.16.tgz", + "integrity": "sha512-X3bUMdK/VmvrWdoTkz+VCn6nwKwrKCFTHtqwBIaQJNx4RUIBBUFXM00bqPz/DsDd+Icjmzm6/tyYZzeGVqb6/Q==", "dev": true }, "node_modules/@types/node-fetch": { @@ -4712,8 +4858,7 @@ "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "dev": true + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" }, "node_modules/@types/parse5": { "version": "5.0.3", @@ -4730,8 +4875,7 @@ "node_modules/@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", - "dev": true + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" }, "node_modules/@types/q": { "version": "1.5.5", @@ -4745,11 +4889,16 @@ "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", "dev": true }, + "node_modules/@types/raf": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.0.tgz", + "integrity": "sha512-taW5/WYqo36N7V39oYyHP9Ipfd5pNFvGTIQsNGj86xV88YQ7GnI30/yMfKDF7Zgin0m3e+ikX88FvImnK4RjGw==", + "optional": true + }, "node_modules/@types/react": { - "version": "18.0.6", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.6.tgz", - "integrity": "sha512-bPqwzJRzKtfI0mVYr5R+1o9BOE8UEXefwc1LwcBtfnaAn6OoqMhLa/91VA8aeWfDPJt1kHvYKI8RHcQybZLHHA==", - "dev": true, + "version": "18.0.8", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.8.tgz", + "integrity": "sha512-+j2hk9BzCOrrOSJASi5XiOyBbERk9jG5O73Ya4M0env5Ixi6vUNli4qy994AINcEF+1IEHISYFfIT4zwr++LKw==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -4765,11 +4914,18 @@ "@types/react": "*" } }, + "node_modules/@types/react-transition-group": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.4.tgz", + "integrity": "sha512-7gAPz7anVK5xzbeQW9wFBDg7G++aPLAFY0QaSMOou9rJZpbuI58WAuJrgu+qR92l61grlnCUe7AFX8KGahAgug==", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/react/node_modules/csstype": { "version": "3.0.11", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", - "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==", - "dev": true + "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==" }, "node_modules/@types/resize-observer-browser": { "version": "0.1.7", @@ -4779,8 +4935,7 @@ "node_modules/@types/scheduler": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", - "dev": true + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" }, "node_modules/@types/source-list-map": { "version": "0.1.2", @@ -5103,12 +5258,12 @@ } }, "node_modules/address": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/address/-/address-1.1.2.tgz", - "integrity": "sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/address/-/address-1.2.0.tgz", + "integrity": "sha512-tNEZYz5G/zYunxFm7sfhAxkXEuLj3K6BKwv6ZURlsF6yiUQ65z0Q2wZW9L5cPUl9ocofGvXOdFYbFHp0+6MOig==", "dev": true, "engines": { - "node": ">= 0.12.0" + "node": ">= 10.0.0" } }, "node_modules/aggregate-error": { @@ -5625,7 +5780,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true, "bin": { "atob": "bin/atob.js" }, @@ -5700,9 +5854,9 @@ } }, "node_modules/aws-sdk": { - "version": "2.1119.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1119.0.tgz", - "integrity": "sha512-f9xoIWo0/kLVfQ65UCkQ7G+Oxl/mjmdGQNPDcylU8dOjXGfc4cgWxTLThRLCsf9l/4yUjWgo+KtJgY6BWc9UjA==", + "version": "2.1125.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1125.0.tgz", + "integrity": "sha512-2syNkKDqDcDmB/chc61a5xx+KYzaarLs1/KshE0b1Opp2oSq2FARyUBbk59HgwKaDUB61uPF33ZG9sHiIVx2hQ==", "dev": true, "dependencies": { "buffer": "4.9.2", @@ -6084,11 +6238,6 @@ "babylon": "bin/babylon.js" } }, - "node_modules/backo2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" - }, "node_modules/bail": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", @@ -6139,6 +6288,15 @@ "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz", "integrity": "sha1-4pf2DX7BAUp6lxo568ipjAtoHnA=" }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "optional": true, + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -6290,24 +6448,27 @@ "dev": true }, "node_modules/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", + "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", "dev": true, "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.8.1", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.9.7", - "raw-body": "2.4.3", - "type-is": "~1.6.18" + "on-finished": "2.4.1", + "qs": "6.10.3", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, "node_modules/body-parser/node_modules/bytes": { @@ -6334,18 +6495,6 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, - "node_modules/body-parser/node_modules/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==", - "dev": true, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/bonjour": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", @@ -6596,9 +6745,9 @@ } }, "node_modules/browserslist": { - "version": "4.20.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.2.tgz", - "integrity": "sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA==", + "version": "4.20.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.3.tgz", + "integrity": "sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg==", "funding": [ { "type": "opencollective", @@ -6610,10 +6759,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001317", - "electron-to-chromium": "^1.4.84", + "caniuse-lite": "^1.0.30001332", + "electron-to-chromium": "^1.4.118", "escalade": "^3.1.1", - "node-releases": "^2.0.2", + "node-releases": "^2.0.3", "picocolors": "^1.0.0" }, "bin": { @@ -6623,6 +6772,17 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/btoa": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", + "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==", + "bin": { + "btoa": "bin/btoa.js" + }, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/buffer": { "version": "4.9.2", "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", @@ -6846,7 +7006,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "engines": { "node": ">=6" } @@ -6894,9 +7053,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001332", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001332.tgz", - "integrity": "sha512-10T30NYOEQtN6C11YGg411yebhvpnC6Z102+B95eAsN0oB6KUs01ivE8u+G6FMIRtIrVlYXhL+LUwQ3/hXwDWw==", + "version": "1.0.30001335", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001335.tgz", + "integrity": "sha512-ddP1Tgm7z2iIxu6QTtbZUv6HJxSaV/PZeSrWFZtbY4JZ69tOeNhBCl3HyRQgeNZKE5AOn1kpV7fhljigy0Ty3w==", "funding": [ { "type": "opencollective", @@ -6908,6 +7067,25 @@ } ] }, + "node_modules/canvg": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz", + "integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "@types/raf": "^3.4.0", + "core-js": "^3.8.3", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.7", + "rgbcolor": "^1.0.1", + "stackblur-canvas": "^2.0.0", + "svg-pathdata": "^6.0.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/case-sensitive-paths-webpack-plugin": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", @@ -7333,9 +7511,9 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/color-string": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.0.tgz", - "integrity": "sha512-9Mrz2AQLefkH1UvASKj6v6hj/7eWgjnT/cVsR8CumieLoT+g900exWeNogqtweI8dxloXN9BDQTYro1oWu/5CQ==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" @@ -7610,15 +7788,14 @@ "version": "1.8.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dev": true, "dependencies": { "safe-buffer": "~5.1.1" } }, "node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", "dev": true, "engines": { "node": ">= 0.6" @@ -7831,10 +8008,10 @@ } }, "node_modules/core-js": { - "version": "3.22.2", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.22.2.tgz", - "integrity": "sha512-Z5I2vzDnEIqO2YhELVMFcL1An2CIsFe9Q7byZhs8c/QxummxZlAHw33TUHbIte987LkisOgL0LwQ1P9D6VISnA==", - "dev": true, + "version": "3.22.3", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.22.3.tgz", + "integrity": "sha512-1t+2a/d2lppW1gkLXx3pKPVGbBdxXAkqztvWb1EJ8oF8O2gIGiytzflNiFEehYwVK/t2ryUsGBoOFFvNx95mbg==", + "devOptional": true, "hasInstallScript": true, "funding": { "type": "opencollective", @@ -7842,12 +8019,12 @@ } }, "node_modules/core-js-compat": { - "version": "3.22.2", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.22.2.tgz", - "integrity": "sha512-Fns9lU06ZJ07pdfmPMu7OnkIKGPKDzXKIiuGlSvHHapwqMUF2QnnsWwtueFZtSyZEilP0o6iUeHQwpn7LxtLUw==", + "version": "3.22.3", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.22.3.tgz", + "integrity": "sha512-wliMbvPI2idgFWpFe7UEyHMvu6HWgW8WA+HnDRtgzoSDYvXFMpoGX1H3tPDDXrcfUSyXafCLDd7hOeMQHEZxGw==", "dev": true, "dependencies": { - "browserslist": "^4.20.2", + "browserslist": "^4.20.3", "semver": "7.0.0" }, "funding": { @@ -7865,9 +8042,9 @@ } }, "node_modules/core-js-pure": { - "version": "3.22.2", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.22.2.tgz", - "integrity": "sha512-Lb+/XT4WC4PaCWWtZpNPaXmjiNDUe5CJuUtbkMrIM1kb1T/jJoAIp+bkVP/r5lHzMr+ZAAF8XHp7+my6Ol0ysQ==", + "version": "3.22.3", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.22.3.tgz", + "integrity": "sha512-oN88zz7nmKROMy8GOjs+LN+0LedIvbMdnB5XsTlhcOg1WGARt9l0LFg0zohdoFmCsEZ1h2ZbSQ6azj3M+vhzwQ==", "dev": true, "hasInstallScript": true, "funding": { @@ -8333,6 +8510,15 @@ "postcss": "^8.0.9" } }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "optional": true, + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/css-loader": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.6.0.tgz", @@ -9113,12 +9299,12 @@ "dev": true }, "node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "dev": true, "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/deploy-aws-s3-cloudfront": { @@ -9151,10 +9337,14 @@ } }, "node_modules/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", - "dev": true + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } }, "node_modules/detab": { "version": "2.0.4", @@ -9309,9 +9499,9 @@ } }, "node_modules/dom-align": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.12.2.tgz", - "integrity": "sha512-pHuazgqrsTFrGU2WLDdXxCFabkdQDx72ddkraZNih1KsMcN5qsRSTR9O4VJRlwTPCPb5COYg3LOfiMHHcPInHg==" + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.12.3.tgz", + "integrity": "sha512-Gj9hZN3a07cbR6zviMUBOMPdWxYhbMI+x+WS0NAIu2zFZmbK8ys9R79g+iG9qLnlCwpFoaB+fKy8Pdv470GsPA==" }, "node_modules/dom-converter": { "version": "0.2.0", @@ -9385,6 +9575,12 @@ "domelementtype": "1" } }, + "node_modules/dompurify": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.6.tgz", + "integrity": "sha512-OFP2u/3T1R5CEgWCEONuJ1a5+MFKnOYpkywpUSxv/dj1LeBT1erK+JwM7zK0ROy2BRhqVCf0LRw/kHqKuMkVGg==", + "optional": true + }, "node_modules/domutils": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", @@ -9525,9 +9721,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.118", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.118.tgz", - "integrity": "sha512-maZIKjnYDvF7Fs35nvVcyr44UcKNwybr93Oba2n3HkKDFAtk0svERkLN/HyczJDS3Fo4wU9th9fUQd09ZLtj1w==" + "version": "1.4.129", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.129.tgz", + "integrity": "sha512-GgtN6bsDtHdtXJtlMYZWGB/uOyjZWjmRDumXTas7dGBaB9zUyCjzHet1DY2KhyHN8R0GLbzZWqm4efeddqqyRQ==" }, "node_modules/element-resize-detector": { "version": "1.2.4", @@ -9625,19 +9821,15 @@ } }, "node_modules/engine.io-client": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.1.1.tgz", - "integrity": "sha512-V05mmDo4gjimYW+FGujoGmmmxRaDsrVr7AXA3ZIfa04MWM1jOfZfUwou0oNqhNwy/votUDvGDt4JA4QF4e0b4g==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.2.2.tgz", + "integrity": "sha512-8ZQmx0LQGRTYkHuogVZuGSpDqYZtCM/nv8zQ68VZ+JkOpazJ7ICdsSpaO6iXwvaU30oFg5QJOJWj8zWqhbKjkQ==", "dependencies": { - "@socket.io/component-emitter": "~3.0.0", + "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1", - "engine.io-parser": "~5.0.0", - "has-cors": "1.1.0", - "parseqs": "0.0.6", - "parseuri": "0.0.6", + "engine.io-parser": "~5.0.3", "ws": "~8.2.3", - "xmlhttprequest-ssl": "~2.0.0", - "yeast": "0.1.2" + "xmlhttprequest-ssl": "~2.0.0" } }, "node_modules/engine.io-client/node_modules/ws": { @@ -9661,12 +9853,9 @@ } }, "node_modules/engine.io-parser": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.3.tgz", - "integrity": "sha512-BtQxwF27XUNnSafQLvDi0dQ8s3i6VgzSoQMJacpIcGNrlUdfHSKbgm3jmjCVvQluGzqwujQMPAoMai3oYSTurg==", - "dependencies": { - "@socket.io/base64-arraybuffer": "~1.0.2" - }, + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.4.tgz", + "integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==", "engines": { "node": ">=10.0.0" } @@ -9721,7 +9910,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, "dependencies": { "is-arrayish": "^0.2.1" } @@ -9841,9 +10029,9 @@ } }, "node_modules/es5-shim": { - "version": "4.6.5", - "resolved": "https://registry.npmjs.org/es5-shim/-/es5-shim-4.6.5.tgz", - "integrity": "sha512-vfQ4UAai8szn0sAubCy97xnZ4sJVDD1gt/Grn736hg8D7540wemIb1YPrYZSTqlM2H69EQX1or4HU/tSwRTI3w==", + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/es5-shim/-/es5-shim-4.6.6.tgz", + "integrity": "sha512-Ay5QQE78I2WKUoZVZjL0AIuiIjsmXwZGkyCTH9+n6J1anPbb0ymDA27ASa2Lt0rhOpAlEKy2W0d17gJ1XOQ5eQ==", "dev": true, "engines": { "node": ">=0.4.0" @@ -10554,38 +10742,39 @@ } }, "node_modules/express": { - "version": "4.17.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz", - "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", + "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", "dev": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.19.2", + "body-parser": "1.20.0", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.2", + "cookie": "0.5.0", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "~1.1.2", + "depd": "2.0.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "~1.1.2", + "finalhandler": "1.2.0", "fresh": "0.5.2", + "http-errors": "2.0.0", "merge-descriptors": "1.0.1", "methods": "~1.1.2", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", "proxy-addr": "~2.0.7", - "qs": "6.9.7", + "qs": "6.10.3", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.17.2", - "serve-static": "1.14.2", + "send": "0.18.0", + "serve-static": "1.15.0", "setprototypeof": "1.2.0", - "statuses": "~1.5.0", + "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -10609,18 +10798,6 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, - "node_modules/express/node_modules/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==", - "dev": true, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/express/node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -10858,6 +11035,11 @@ "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", "dev": true }, + "node_modules/fflate": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", + "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==" + }, "node_modules/figgy-pudding": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", @@ -10945,17 +11127,17 @@ } }, "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", "dev": true, "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", - "statuses": "~1.5.0", + "statuses": "2.0.1", "unpipe": "~1.0.0" }, "engines": { @@ -11052,8 +11234,7 @@ "node_modules/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", - "dev": true + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" }, "node_modules/find-up": { "version": "5.0.0", @@ -11369,9 +11550,9 @@ } }, "node_modules/fork-ts-checker-webpack-plugin": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.1.tgz", - "integrity": "sha512-x1wumpHOEf4gDROmKTaB6i4/Q6H3LwmjVO7fIX47vBwlZbtPjU33hgoMuD/Q/y6SU8bnuYSoN6ZQOLshGp0T/g==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.2.tgz", + "integrity": "sha512-m5cUmF30xkZ7h4tWUgTAcEaKmUW7tfyUyTqNNOz7OxWJ0v1VWKTcOvH8FWHUwSjlW/356Ijc9vi3XfcPstpQKA==", "dev": true, "dependencies": { "@babel/code-frame": "^7.8.3", @@ -12064,11 +12245,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-cors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", - "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" - }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -12655,6 +12831,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/html-to-image": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/html-to-image/-/html-to-image-1.9.0.tgz", + "integrity": "sha512-9gaDCIYg62Ek07F2pBk76AHgYZ2gxq2YALU7rK3gNCqXuhu6cWzsOQqM7qGbjZiOzxGzrU1deDqZpAod2NEwbA==" + }, "node_modules/html-void-elements": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-1.0.5.tgz", @@ -12726,6 +12907,19 @@ "object-assign": "^4.0.1" } }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "optional": true, + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/htmlparser2": { "version": "3.8.3", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", @@ -12750,19 +12944,19 @@ "dev": true }, "node_modules/http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "dev": true, "dependencies": { - "depd": "~1.1.2", + "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", + "statuses": "2.0.1", "toidentifier": "1.0.1" }, "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/http-parser-js": { @@ -13020,7 +13214,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -13036,7 +13229,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "engines": { "node": ">=4" } @@ -13331,8 +13523,7 @@ "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" }, "node_modules/is-bigint": { "version": "1.0.4", @@ -14059,8 +14250,7 @@ "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, "node_modules/json-schema-traverse": { "version": "0.4.1", @@ -14098,10 +14288,27 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jspdf": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.1.tgz", + "integrity": "sha512-hXObxz7ZqoyhxET78+XR34Xu2qFGrJJ2I2bE5w4SM8eFaFEkW2xcGRVUss360fYelwRSid/jT078kbNvmoW0QA==", + "dependencies": { + "@babel/runtime": "^7.14.0", + "atob": "^2.1.2", + "btoa": "^1.2.1", + "fflate": "^0.4.8" + }, + "optionalDependencies": { + "canvg": "^3.0.6", + "core-js": "^3.6.0", + "dompurify": "^2.2.0", + "html2canvas": "^1.0.0-rc.5" + } + }, "node_modules/jsx-ast-utils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.2.tgz", - "integrity": "sha512-HDAyJ4MNQBboGpUnHAVUNJs6X0lh058s6FuixsFGP7MgJYpD6Vasd6nzSG5iIfXu1zAYlHJ/zsOKNlrenTUBnw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.0.tgz", + "integrity": "sha512-XzO9luP6L0xkxwhIJMTJQpZo/eeN60K08jHdexfD569AGxeNug6UketeHXEhROoM8aR7EcUoOQmIhcJQjcuq8Q==", "dev": true, "dependencies": { "array-includes": "^3.1.4", @@ -14227,8 +14434,7 @@ "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, "node_modules/loader-runner": { "version": "2.4.0", @@ -14301,6 +14507,12 @@ "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", "dev": true }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", + "dev": true + }, "node_modules/lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", @@ -14554,6 +14766,11 @@ "node": ">= 4.0.0" } }, + "node_modules/memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" + }, "node_modules/memoizerific": { "version": "1.11.3", "resolved": "https://registry.npmjs.org/memoizerific/-/memoizerific-1.11.3.tgz", @@ -14806,9 +15023,9 @@ "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, "node_modules/minio": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/minio/-/minio-7.0.27.tgz", - "integrity": "sha512-0eFFob4ZzqsK0lsuM2cRddDkdFrfQGKCpD77XbFzFClhQL4w85CXC3UwfIe0rBRmLzFMZ5Y9bph8Dpny/COrQQ==", + "version": "7.0.28", + "resolved": "https://registry.npmjs.org/minio/-/minio-7.0.28.tgz", + "integrity": "sha512-4Oua0R73oCxxmxhh2NiXDJo4Md159I/mdG8ybu6351leMQoB2Sy8S4HmgG6CxuPlEJ0h9M8/WyaI2CARDeeDTQ==", "dev": true, "dependencies": { "async": "^3.1.0", @@ -14830,7 +15047,7 @@ "xml2js": "^0.4.15" }, "engines": { - "node": ">8 <16.8.0" + "node": ">8 <=18" } }, "node_modules/minipass": { @@ -15344,9 +15561,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.3.tgz", - "integrity": "sha512-maHFz6OLqYxz+VQyCAtA3PTX4UP/53pa05fyDNc9CwjvJ0yEh6+xBwKsgCxMNhS8taUKBFYxfuiaD9U/55iFaw==" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.4.tgz", + "integrity": "sha512-gbMzqQtTtDz/00jQzZ21PQzdI9PyLYqUSvD0p3naOhX4odFji0ZxYdnVwPTxmSwkmxhcFImpozceidSG+AgoPQ==" }, "node_modules/normalize-package-data": { "version": "2.5.0", @@ -15725,9 +15942,9 @@ "dev": true }, "node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dev": true, "dependencies": { "ee-first": "1.1.1" @@ -16039,7 +16256,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "dependencies": { "callsites": "^3.0.0" }, @@ -16082,7 +16298,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -16111,16 +16326,6 @@ "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", "dev": true }, - "node_modules/parseqs": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz", - "integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==" - }, - "node_modules/parseuri": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz", - "integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==" - }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -16233,7 +16438,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, "engines": { "node": ">=8" } @@ -18379,13 +18583,13 @@ } }, "node_modules/raw-body": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", - "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", "dev": true, "dependencies": { "bytes": "3.1.2", - "http-errors": "1.8.1", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" }, @@ -18720,12 +18924,12 @@ } }, "node_modules/react-draggable": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.4.tgz", - "integrity": "sha512-6e0WdcNLwpBx/YIDpoyd2Xb04PB0elrDrulKUgdrIlwuYvxh5Ok9M+F8cljm8kPXXs43PmMzek9RrB1b7mLMqA==", + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.5.tgz", + "integrity": "sha512-OMHzJdyJbYTZo4uQE393fHcqqPYsEtkjfMgvCHr6rejT+Ezn4OZbNyGH50vv+SunC1RMvwOTSWkEODQLzw1M9g==", "dependencies": { "clsx": "^1.1.1", - "prop-types": "^15.6.0" + "prop-types": "^15.8.1" }, "peerDependencies": { "react": ">= 16.3.0", @@ -18860,9 +19064,9 @@ } }, "node_modules/react-popper-tooltip/node_modules/react-popper": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.2.5.tgz", - "integrity": "sha512-kxGkS80eQGtLl18+uig1UIf9MKixFSyPxglsgLBxlYnyDf65BiY9B3nZSc6C9XUNDgStROB0fMQlTEz1KxGddw==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz", + "integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==", "dev": true, "dependencies": { "react-fast-compare": "^3.0.1", @@ -18870,7 +19074,8 @@ }, "peerDependencies": { "@popperjs/core": "^2.0.0", - "react": "^16.8.0 || ^17" + "react": "^16.8.0 || ^17 || ^18", + "react-dom": "^16.8.0 || ^17 || ^18" } }, "node_modules/react-redux": { @@ -18996,6 +19201,46 @@ "isarray": "0.0.1" } }, + "node_modules/react-select": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.3.1.tgz", + "integrity": "sha512-Y195MmhDoDAj/8gTDyYZU1Raf7tmZd81wxM6RkFko4pqJ4Xv0/ilqUMtSn+GYkwmSlTWeMlzh+e+t7PJgtuXPw==", + "dependencies": { + "@babel/runtime": "^7.12.0", + "@emotion/cache": "^11.4.0", + "@emotion/react": "^11.8.1", + "@types/react-transition-group": "^4.4.0", + "memoize-one": "^5.0.0", + "prop-types": "^15.6.0", + "react-transition-group": "^4.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-select/node_modules/@emotion/cache": { + "version": "11.7.1", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.7.1.tgz", + "integrity": "sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A==", + "dependencies": { + "@emotion/memoize": "^0.7.4", + "@emotion/sheet": "^1.1.0", + "@emotion/utils": "^1.0.0", + "@emotion/weak-memoize": "^0.2.5", + "stylis": "4.0.13" + } + }, + "node_modules/react-select/node_modules/@emotion/sheet": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.1.0.tgz", + "integrity": "sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g==" + }, + "node_modules/react-select/node_modules/@emotion/utils": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.1.0.tgz", + "integrity": "sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ==" + }, "node_modules/react-sizeme": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/react-sizeme/-/react-sizeme-3.0.2.tgz", @@ -19099,18 +19344,15 @@ } }, "node_modules/react-toastify": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-5.5.0.tgz", - "integrity": "sha512-jsVme7jALIFGRyQsri/g4YTsRuaaGI70T6/ikjwZMB4mwTZaCWqj5NqxhGrRStKlJc5npXKKvKeqTiRGQl78LQ==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-8.2.0.tgz", + "integrity": "sha512-Pg2Ju7NngAamarFvLwqrFomJ57u/Ay6i6zfLurt/qPynWkAkOthu6vxfqYpJCyNhHRhR4hu7+bySSeWWJu6PAg==", "dependencies": { - "@babel/runtime": "^7.4.2", - "classnames": "^2.2.6", - "prop-types": "^15.7.2", - "react-transition-group": "^4" + "clsx": "^1.1.1" }, "peerDependencies": { - "react": ">=15.0.0", - "react-dom": ">=15.0.0" + "react": ">=16", + "react-dom": ">=16" } }, "node_modules/react-transition-group": { @@ -19986,6 +20228,15 @@ "node": ">=0.10.0" } }, + "node_modules/rgbcolor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", + "integrity": "sha1-1lBezbMEplldom+ktDMHMGd1lF0=", + "optional": true, + "engines": { + "node": ">= 0.8.15" + } + }, "node_modules/rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -20055,8 +20306,7 @@ "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/safe-regex": { "version": "1.1.0", @@ -20170,24 +20420,24 @@ } }, "node_modules/send": { - "version": "0.17.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", - "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "dev": true, "dependencies": { "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", + "depd": "2.0.0", + "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "1.8.1", + "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "statuses": "2.0.1" }, "engines": { "node": ">= 0.8.0" @@ -20278,6 +20528,15 @@ "ms": "2.0.0" } }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/serve-index/node_modules/http-errors": { "version": "1.6.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", @@ -20311,16 +20570,25 @@ "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", "dev": true }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/serve-static": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", - "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", "dev": true, "dependencies": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.2" + "send": "0.18.0" }, "engines": { "node": ">= 0.8.0" @@ -20688,27 +20956,25 @@ } }, "node_modules/socket.io-client": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.4.1.tgz", - "integrity": "sha512-N5C/L5fLNha5Ojd7Yeb/puKcPWWcoB/A09fEjjNsg91EDVr5twk/OEyO6VT9dlLSUNY85NpW6KBhVMvaLKQ3vQ==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.5.0.tgz", + "integrity": "sha512-HW61c1G7OrYGxaI79WRn17+b03iBCdvhBj4iqyXHBoL5M8w2MSO/vChsjA93knG4GYEai1/vbXWJna9dzxXtSg==", "dependencies": { - "@socket.io/component-emitter": "~3.0.0", - "backo2": "~1.0.2", + "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.2", - "engine.io-client": "~6.1.1", - "parseuri": "0.0.6", - "socket.io-parser": "~4.1.1" + "engine.io-client": "~6.2.1", + "socket.io-parser": "~4.2.0" }, "engines": { "node": ">=10.0.0" } }, "node_modules/socket.io-parser": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.1.2.tgz", - "integrity": "sha512-j3kk71QLJuyQ/hh5F/L2t1goqzdTL0gvDzuhTuNSwihfuFUrcSji0qFZmJJPtG6Rmug153eOPsUizeirf1IIog==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.0.tgz", + "integrity": "sha512-tLfmEwcEwnlQTxFB7jibL/q2+q8dlVQzj4JdRLJ/W/G1+Fu9VSxCx1Lo+n1HvXxKnM//dUuD0xgiA7tQf57Vng==", "dependencies": { - "@socket.io/component-emitter": "~3.0.0", + "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1" }, "engines": { @@ -20986,6 +21252,15 @@ "node": "*" } }, + "node_modules/stackblur-canvas": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.5.0.tgz", + "integrity": "sha512-EeNzTVfj+1In7aSLPKDD03F/ly4RxEuF/EX0YcOG0cKoPXs+SLZxDawQbexQDBzwROs4VKLWTOaZQlZkGBFEIQ==", + "optional": true, + "engines": { + "node": ">=0.1.14" + } + }, "node_modules/stackframe": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.1.tgz", @@ -21105,12 +21380,12 @@ } }, "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "dev": true, "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/store2": { @@ -21456,6 +21731,11 @@ "postcss": "^8.2.15" } }, + "node_modules/stylis": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz", + "integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==" + }, "node_modules/sugarss": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/sugarss/-/sugarss-2.0.0.tgz", @@ -21487,6 +21767,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svg-pathdata": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", + "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", + "optional": true, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/svgo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", @@ -21807,14 +22096,14 @@ } }, "node_modules/terser": { - "version": "5.12.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.12.1.tgz", - "integrity": "sha512-NXbs+7nisos5E+yXwAD+y7zrcTkMqb0dEJxIGtSKPdCBzopf7ni4odPul2aechpV7EXNvOudYOX2bb5tln1jbQ==", + "version": "5.13.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.13.1.tgz", + "integrity": "sha512-hn4WKOfwnwbYfe48NgrQjqNOH9jzLqRcIfbYytOXCOv46LBfWr9bDS17MQqOi+BWGD0sJK3Sj5NC/gJjiojaoA==", "dev": true, "dependencies": { "acorn": "^8.5.0", "commander": "^2.20.0", - "source-map": "~0.7.2", + "source-map": "~0.8.0-beta.0", "source-map-support": "~0.5.20" }, "bin": { @@ -22091,9 +22380,9 @@ "dev": true }, "node_modules/terser/node_modules/acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", + "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -22108,6 +22397,44 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, + "node_modules/terser/node_modules/source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "dev": true, + "dependencies": { + "whatwg-url": "^7.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/terser/node_modules/tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/terser/node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + }, + "node_modules/terser/node_modules/whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -22128,6 +22455,15 @@ "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", "dev": true }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "optional": true, + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/throttle-debounce": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-3.0.1.tgz", @@ -22228,7 +22564,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true, "engines": { "node": ">=4" } @@ -22363,9 +22698,9 @@ } }, "node_modules/ts-loader": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.3.0.tgz", - "integrity": "sha512-MgGly4I6cStsJy27ViE32UoqxPTN9Xly4anxxVyaIWR+9BGxboV4EyJBGfR3RePV7Ksjj3rHmPZJeIt+7o4Vag==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.4.0.tgz", + "integrity": "sha512-6nFY3IZ2//mrPc+ImY3hNWx1vCHyEhl6V+wLmL4CZcm6g1CqX7UKrkc6y0i4FwcfOhxyMPCfaEvh20f4r9GNpw==", "dev": true, "dependencies": { "chalk": "^4.1.0", @@ -22547,9 +22882,9 @@ } }, "node_modules/typed-css-modules": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/typed-css-modules/-/typed-css-modules-0.7.1.tgz", - "integrity": "sha512-WaWHnLa3HBTCuOCDRDB3wfqoH9ouTxdLGQwzVBxV5x+nbPIr5ZkCrCb4yfU4D1bkzNprrTKTU2ETkYVOLGFmVA==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/typed-css-modules/-/typed-css-modules-0.7.2.tgz", + "integrity": "sha512-R3guXrQ8ry/yhlfvNmkVY4J3+FtKaEdwqrvgSvFpVY0ieYQHqhhBW0RwfE4hnG4m29Ef/4IE0tBsk/UKplmJkA==", "dev": true, "dependencies": { "@types/css-modules-loader-core": "^1.1.0", @@ -22795,14 +23130,14 @@ } }, "node_modules/unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", "dev": true, "dependencies": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", "which-boxed-primitive": "^1.0.2" }, "funding": { @@ -22810,9 +23145,9 @@ } }, "node_modules/underscore": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.2.tgz", - "integrity": "sha512-ekY1NhRzq0B08g4bGuX4wd2jZx5GnKz6mKSqFL4nqBlfyMGiG10gDFhDTMEfYmDL6Jy0FUIZp7wiRB+0BP7J2g==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.3.tgz", + "integrity": "sha512-QvjkYpiD+dJJraRA8+dGAU4i7aBbb2s0S3jA45TFOvg2VgqvdCDd/3N6CqA8gluk1W91GLoXg5enMUx560QzuA==", "dev": true }, "node_modules/unfetch": { @@ -23240,14 +23575,14 @@ } }, "node_modules/use-latest": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.2.0.tgz", - "integrity": "sha512-d2TEuG6nSLKQLAfW3By8mKr8HurOlTkul0sOpxbClIv4SQ4iOd7BYr7VIzdbktUCnv7dua/60xzd8igMU6jmyw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.2.1.tgz", + "integrity": "sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw==", "dependencies": { - "use-isomorphic-layout-effect": "^1.0.0" + "use-isomorphic-layout-effect": "^1.1.1" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -23299,6 +23634,15 @@ "node": ">= 0.4.0" } }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "optional": true, + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, "node_modules/uuid": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", @@ -25538,9 +25882,9 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "node_modules/ws": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", - "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.6.0.tgz", + "integrity": "sha512-AzmM3aH3gk0aX7/rZLYvjdvZooofDu3fFOzGqcSnQ1tOcTWwhM/o+q++E8mAyVVIyUdajrkzWUGftaVSDLn1bw==", "dev": true, "engines": { "node": ">=10.0.0" @@ -25615,7 +25959,6 @@ "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, "engines": { "node": ">= 6" } @@ -25665,11 +26008,6 @@ "node": ">=12" } }, - "node_modules/yeast": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", - "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -25695,21 +26033,22 @@ }, "dependencies": { "@ampproject/remapping": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz", - "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", "dev": true, "requires": { - "@jridgewell/trace-mapping": "^0.3.0" + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" } }, "@babel/cli": { - "version": "7.17.6", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.17.6.tgz", - "integrity": "sha512-l4w608nsDNlxZhiJ5tE3DbNmr61fIKMZ6fTBo171VEFuFMIYuJ3mHRhTLEkKKyvx2Mizkkv/0a8OJOnZqkKYNA==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.17.10.tgz", + "integrity": "sha512-OygVO1M2J4yPMNOW9pb+I6kFGpQK77HmG44Oz3hg8xQIl5L/2zq+ZohwAdSaqYgVwM0SfmPHZHphH4wR8qzVYw==", "dev": true, "requires": { - "@jridgewell/trace-mapping": "^0.3.4", + "@jridgewell/trace-mapping": "^0.3.8", "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3", "chokidar": "^3.4.0", "commander": "^4.0.1", @@ -25717,49 +26056,39 @@ "fs-readdir-recursive": "^1.1.0", "glob": "^7.0.0", "make-dir": "^2.1.0", - "slash": "^2.0.0", - "source-map": "^0.5.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } + "slash": "^2.0.0" } }, "@babel/code-frame": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", - "dev": true, "requires": { "@babel/highlight": "^7.16.7" } }, "@babel/compat-data": { - "version": "7.17.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.7.tgz", - "integrity": "sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.10.tgz", + "integrity": "sha512-GZt/TCsG70Ms19gfZO1tM4CVnXsPgEPBCpJu+Qz3L0LUDsY5nZqFZglIoPC1kIYOtNBZlrnFT+klg12vFGZXrw==", "dev": true }, "@babel/core": { - "version": "7.17.9", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.9.tgz", - "integrity": "sha512-5ug+SfZCpDAkVp9SFIZAzlW18rlzsOcJGaetCjkySnrXXDUw9AR8cDUm1iByTmdWM6yxX6/zycaV76w3YTF2gw==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.10.tgz", + "integrity": "sha512-liKoppandF3ZcBnIYFjfSDHZLKdLHGJRkoWtG8zQyGJBQfIYobpnVGI5+pLBNtS6psFLDzyq8+h5HiVljW9PNA==", "dev": true, "requires": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.17.9", - "@babel/helper-compilation-targets": "^7.17.7", + "@babel/generator": "^7.17.10", + "@babel/helper-compilation-targets": "^7.17.10", "@babel/helper-module-transforms": "^7.17.7", "@babel/helpers": "^7.17.9", - "@babel/parser": "^7.17.9", + "@babel/parser": "^7.17.10", "@babel/template": "^7.16.7", - "@babel/traverse": "^7.17.9", - "@babel/types": "^7.17.0", + "@babel/traverse": "^7.17.10", + "@babel/types": "^7.17.10", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -25768,22 +26097,14 @@ } }, "@babel/generator": { - "version": "7.17.9", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.9.tgz", - "integrity": "sha512-rAdDousTwxbIxbz5I7GEQ3lUip+xVCXooZNbsydCWs3xA7ZsYOv+CFRdzGxRX78BmQHu9B1Eso59AOZQOJDEdQ==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.10.tgz", + "integrity": "sha512-46MJZZo9y3o4kmhBVc7zW7i8dtR1oIK/sdO5NcfcZRhTGYi+KKJRtHNgsU6c4VUcJmUNV/LQdebD/9Dlv4K+Tg==", "dev": true, "requires": { - "@babel/types": "^7.17.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } + "@babel/types": "^7.17.10", + "@jridgewell/gen-mapping": "^0.1.0", + "jsesc": "^2.5.1" } }, "@babel/helper-annotate-as-pure": { @@ -25806,14 +26127,14 @@ } }, "@babel/helper-compilation-targets": { - "version": "7.17.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz", - "integrity": "sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.10.tgz", + "integrity": "sha512-gh3RxjWbauw/dFiU/7whjd0qN9K6nPJMqe6+Er7rOavFh0CQUSwhAE3IcTho2rywPJFxej6TUUHDkWcYI6gGqQ==", "dev": true, "requires": { - "@babel/compat-data": "^7.17.7", + "@babel/compat-data": "^7.17.10", "@babel/helper-validator-option": "^7.16.7", - "browserslist": "^4.17.5", + "browserslist": "^4.20.2", "semver": "^6.3.0" } }, @@ -25908,7 +26229,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", - "dev": true, "requires": { "@babel/types": "^7.16.7" } @@ -25941,8 +26261,7 @@ "@babel/helper-plugin-utils": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true + "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==" }, "@babel/helper-remap-async-to-generator": { "version": "7.16.8", @@ -25998,8 +26317,7 @@ "@babel/helper-validator-identifier": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==" }, "@babel/helper-validator-option": { "version": "7.16.7", @@ -26034,7 +26352,6 @@ "version": "7.17.9", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.17.9.tgz", "integrity": "sha512-J9PfEKCbFIv2X5bjTMiZu6Vf341N05QIY+d6FvVKynkG1S7G0j3I0QoRtWIrXhZ+/Nlb5Q0MzqL7TokEJ5BNHg==", - "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.16.7", "chalk": "^2.0.0", @@ -26042,9 +26359,9 @@ } }, "@babel/parser": { - "version": "7.17.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.9.tgz", - "integrity": "sha512-vqUSBLP8dQHFPdPi9bc5GK9vRkYHJ49fsZdtoJ8EQ8ibpwk5rPKfvNIwChB0KVXcIjcepEBBd2VHC5r9Gy8ueg==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.10.tgz", + "integrity": "sha512-n2Q6i+fnJqzOaq2VkdXxy2TCPCWQZHiCo0XqmrCvDWcZQKRyZzYi4Z0yxlBuN0w+r2ZHmre+Q087DSrw3pbJDQ==", "dev": true }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { @@ -26343,7 +26660,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz", "integrity": "sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.16.7" } @@ -26421,9 +26737,9 @@ } }, "@babel/plugin-syntax-typescript": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz", - "integrity": "sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.17.10.tgz", + "integrity": "sha512-xJefea1DWXW09pW4Tm9bjwVlPDyYA2it3fWlmEjpYz6alPvTUjL0EOzNzI/FEOyI3r4/J7uVH5UqKgl1TQ5hqQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.16.7" @@ -26625,12 +26941,12 @@ } }, "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.16.8.tgz", - "integrity": "sha512-j3Jw+n5PvpmhRR+mrgIh04puSANCk/T/UA3m3P1MjJkhlK906+ApHhDIqBQDdOgL/r1UYpz4GNclTXxyZrYGSw==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.17.10.tgz", + "integrity": "sha512-v54O6yLaJySCs6mGzaVOUw9T967GnH38T6CQSAtnzdNPwu84l2qAjssKzo/WSO8Yi7NF+7ekm5cVbF/5qiIgNA==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.16.7" + "@babel/helper-create-regexp-features-plugin": "^7.17.0" } }, "@babel/plugin-transform-new-target": { @@ -26806,27 +27122,27 @@ } }, "@babel/preset-env": { - "version": "7.16.11", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.16.11.tgz", - "integrity": "sha512-qcmWG8R7ZW6WBRPZK//y+E3Cli151B20W1Rv7ln27vuPaXU/8TKms6jFdiJtF7UDTxcrb7mZd88tAeK9LjdT8g==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.17.10.tgz", + "integrity": "sha512-YNgyBHZQpeoBSRBg0xixsZzfT58Ze1iZrajvv0lJc70qDDGuGfonEnMGfWeSY0mQ3JTuCWFbMkzFRVafOyJx4g==", "dev": true, "requires": { - "@babel/compat-data": "^7.16.8", - "@babel/helper-compilation-targets": "^7.16.7", + "@babel/compat-data": "^7.17.10", + "@babel/helper-compilation-targets": "^7.17.10", "@babel/helper-plugin-utils": "^7.16.7", "@babel/helper-validator-option": "^7.16.7", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.16.7", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.16.7", "@babel/plugin-proposal-async-generator-functions": "^7.16.8", "@babel/plugin-proposal-class-properties": "^7.16.7", - "@babel/plugin-proposal-class-static-block": "^7.16.7", + "@babel/plugin-proposal-class-static-block": "^7.17.6", "@babel/plugin-proposal-dynamic-import": "^7.16.7", "@babel/plugin-proposal-export-namespace-from": "^7.16.7", "@babel/plugin-proposal-json-strings": "^7.16.7", "@babel/plugin-proposal-logical-assignment-operators": "^7.16.7", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.7", "@babel/plugin-proposal-numeric-separator": "^7.16.7", - "@babel/plugin-proposal-object-rest-spread": "^7.16.7", + "@babel/plugin-proposal-object-rest-spread": "^7.17.3", "@babel/plugin-proposal-optional-catch-binding": "^7.16.7", "@babel/plugin-proposal-optional-chaining": "^7.16.7", "@babel/plugin-proposal-private-methods": "^7.16.11", @@ -26852,7 +27168,7 @@ "@babel/plugin-transform-block-scoping": "^7.16.7", "@babel/plugin-transform-classes": "^7.16.7", "@babel/plugin-transform-computed-properties": "^7.16.7", - "@babel/plugin-transform-destructuring": "^7.16.7", + "@babel/plugin-transform-destructuring": "^7.17.7", "@babel/plugin-transform-dotall-regex": "^7.16.7", "@babel/plugin-transform-duplicate-keys": "^7.16.7", "@babel/plugin-transform-exponentiation-operator": "^7.16.7", @@ -26861,15 +27177,15 @@ "@babel/plugin-transform-literals": "^7.16.7", "@babel/plugin-transform-member-expression-literals": "^7.16.7", "@babel/plugin-transform-modules-amd": "^7.16.7", - "@babel/plugin-transform-modules-commonjs": "^7.16.8", - "@babel/plugin-transform-modules-systemjs": "^7.16.7", + "@babel/plugin-transform-modules-commonjs": "^7.17.9", + "@babel/plugin-transform-modules-systemjs": "^7.17.8", "@babel/plugin-transform-modules-umd": "^7.16.7", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.16.8", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.17.10", "@babel/plugin-transform-new-target": "^7.16.7", "@babel/plugin-transform-object-super": "^7.16.7", "@babel/plugin-transform-parameters": "^7.16.7", "@babel/plugin-transform-property-literals": "^7.16.7", - "@babel/plugin-transform-regenerator": "^7.16.7", + "@babel/plugin-transform-regenerator": "^7.17.9", "@babel/plugin-transform-reserved-words": "^7.16.7", "@babel/plugin-transform-shorthand-properties": "^7.16.7", "@babel/plugin-transform-spread": "^7.16.7", @@ -26879,11 +27195,11 @@ "@babel/plugin-transform-unicode-escapes": "^7.16.7", "@babel/plugin-transform-unicode-regex": "^7.16.7", "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.16.8", + "@babel/types": "^7.17.10", "babel-plugin-polyfill-corejs2": "^0.3.0", "babel-plugin-polyfill-corejs3": "^0.5.0", "babel-plugin-polyfill-regenerator": "^0.3.0", - "core-js-compat": "^3.20.2", + "core-js-compat": "^3.22.1", "semver": "^6.3.0" } }, @@ -26979,28 +27295,27 @@ } }, "@babel/traverse": { - "version": "7.17.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.9.tgz", - "integrity": "sha512-PQO8sDIJ8SIwipTPiR71kJQCKQYB5NGImbOviK8K+kg5xkNSYXLBupuX9QhatFowrsvo9Hj8WgArg3W7ijNAQw==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.10.tgz", + "integrity": "sha512-VmbrTHQteIdUUQNTb+zE12SHS/xQVIShmBPhlNP12hD5poF2pbITW1Z4172d03HegaQWhLffdkRJYtAzp0AGcw==", "dev": true, "requires": { "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.17.9", + "@babel/generator": "^7.17.10", "@babel/helper-environment-visitor": "^7.16.7", "@babel/helper-function-name": "^7.17.9", "@babel/helper-hoist-variables": "^7.16.7", "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.17.9", - "@babel/types": "^7.17.0", + "@babel/parser": "^7.17.10", + "@babel/types": "^7.17.10", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", - "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", - "dev": true, + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.10.tgz", + "integrity": "sha512-9O26jG0mBYfGkUYCYZRnBwbVLd1UZOICEr2Em6InB6jVfsAv1GKgwXHmrSg+WFWDmeKTA6vyTZiN8tCSM5Oo3A==", "requires": { "@babel/helper-validator-identifier": "^7.16.7", "to-fast-properties": "^2.0.0" @@ -27035,6 +27350,86 @@ "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", "dev": true }, + "@emotion/babel-plugin": { + "version": "11.9.2", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.9.2.tgz", + "integrity": "sha512-Pr/7HGH6H6yKgnVFNEj2MVlreu3ADqftqjqwUvDy/OJzKFgxKeTQ+eeUf20FOTuHVkDON2iNa25rAXVYtWJCjw==", + "requires": { + "@babel/helper-module-imports": "^7.12.13", + "@babel/plugin-syntax-jsx": "^7.12.13", + "@babel/runtime": "^7.13.10", + "@emotion/hash": "^0.8.0", + "@emotion/memoize": "^0.7.5", + "@emotion/serialize": "^1.0.2", + "babel-plugin-macros": "^2.6.1", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.0.13" + }, + "dependencies": { + "@emotion/memoize": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.5.tgz", + "integrity": "sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ==" + }, + "@emotion/serialize": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.3.tgz", + "integrity": "sha512-2mSSvgLfyV3q+iVh3YWgNlUc2a9ZlDU7DjuP5MjK3AXRR0dYigCrP99aeFtaB2L/hjfEZdSThn5dsZ0ufqbvsA==", + "requires": { + "@emotion/hash": "^0.8.0", + "@emotion/memoize": "^0.7.4", + "@emotion/unitless": "^0.7.5", + "@emotion/utils": "^1.0.0", + "csstype": "^3.0.2" + } + }, + "@emotion/utils": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.1.0.tgz", + "integrity": "sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ==" + }, + "babel-plugin-macros": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz", + "integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==", + "requires": { + "@babel/runtime": "^7.7.2", + "cosmiconfig": "^6.0.0", + "resolve": "^1.12.0" + } + }, + "cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + } + }, + "csstype": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", + "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==" + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + } + } + }, "@emotion/cache": { "version": "10.0.29", "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.29.tgz", @@ -27075,8 +27470,7 @@ "@emotion/hash": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", - "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", - "dev": true + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" }, "@emotion/is-prop-valid": { "version": "0.8.8", @@ -27090,8 +27484,62 @@ "@emotion/memoize": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", - "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", - "dev": true + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==" + }, + "@emotion/react": { + "version": "11.9.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.9.0.tgz", + "integrity": "sha512-lBVSF5d0ceKtfKCDQJveNAtkC7ayxpVlgOohLgXqRwqWr9bOf4TZAFFyIcNngnV6xK6X4x2ZeXq7vliHkoVkxQ==", + "requires": { + "@babel/runtime": "^7.13.10", + "@emotion/babel-plugin": "^11.7.1", + "@emotion/cache": "^11.7.1", + "@emotion/serialize": "^1.0.3", + "@emotion/utils": "^1.1.0", + "@emotion/weak-memoize": "^0.2.5", + "hoist-non-react-statics": "^3.3.1" + }, + "dependencies": { + "@emotion/cache": { + "version": "11.7.1", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.7.1.tgz", + "integrity": "sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A==", + "requires": { + "@emotion/memoize": "^0.7.4", + "@emotion/sheet": "^1.1.0", + "@emotion/utils": "^1.0.0", + "@emotion/weak-memoize": "^0.2.5", + "stylis": "4.0.13" + } + }, + "@emotion/serialize": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.3.tgz", + "integrity": "sha512-2mSSvgLfyV3q+iVh3YWgNlUc2a9ZlDU7DjuP5MjK3AXRR0dYigCrP99aeFtaB2L/hjfEZdSThn5dsZ0ufqbvsA==", + "requires": { + "@emotion/hash": "^0.8.0", + "@emotion/memoize": "^0.7.4", + "@emotion/unitless": "^0.7.5", + "@emotion/utils": "^1.0.0", + "csstype": "^3.0.2" + } + }, + "@emotion/sheet": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.1.0.tgz", + "integrity": "sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g==" + }, + "@emotion/utils": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.1.0.tgz", + "integrity": "sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ==" + }, + "csstype": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", + "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==" + } + } }, "@emotion/serialize": { "version": "0.11.16", @@ -27143,8 +27591,7 @@ "@emotion/unitless": { "version": "0.7.5", "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", - "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", - "dev": true + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" }, "@emotion/utils": { "version": "0.11.3", @@ -27155,8 +27602,7 @@ "@emotion/weak-memoize": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz", - "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==", - "dev": true + "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" }, "@fullhuman/postcss-purgecss": { "version": "2.3.0", @@ -27213,10 +27659,26 @@ "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true }, + "@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "@jridgewell/resolve-uri": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz", - "integrity": "sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.6.tgz", + "integrity": "sha512-R7xHtBSNm+9SyvpJkdQl+qrM3Hm2fea3Ef197M3mUug+v+yR+Rhfbs7PBtcBUVnIWJ4JcAdjvij+c8hXS9p5aw==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.0.tgz", + "integrity": "sha512-SfJxIxNVYLTsKwzB3MoOQ1yxf4w/E6MdkvTgrgAt1bfxjSrLUoHMKrDOykwN14q65waezZIdqDneUIPh4/sKxg==", "dev": true }, "@jridgewell/sourcemap-codec": { @@ -27543,15 +28005,10 @@ "tslib": "^1.9.3" } }, - "@socket.io/base64-arraybuffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@socket.io/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", - "integrity": "sha512-dOlCBKnDw4iShaIsH/bxujKTM18+2TOAsYz+KSc11Am38H4q5Xw8Bbz97ZYdrVNM+um3p7w86Bvvmcn9q+5+eQ==" - }, "@socket.io/component-emitter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.0.0.tgz", - "integrity": "sha512-2pTGuibAXJswAPJjaKisthqS/NOK5ypG4LYT6tEAV0S/mxW0zOIvYvGK0V8w8+SHxAm6vRMSjqSalFXeBAqs+Q==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" }, "@storybook/addons": { "version": "6.4.22", @@ -29060,9 +29517,9 @@ "dev": true }, "@types/node": { - "version": "14.18.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.13.tgz", - "integrity": "sha512-Z6/KzgyWOga3pJNS42A+zayjhPbf2zM3hegRQaOPnLOzEi86VV++6FLDWgR1LGrVCRufP/ph2daa3tEa5br1zA==", + "version": "14.18.16", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.16.tgz", + "integrity": "sha512-X3bUMdK/VmvrWdoTkz+VCn6nwKwrKCFTHtqwBIaQJNx4RUIBBUFXM00bqPz/DsDd+Icjmzm6/tyYZzeGVqb6/Q==", "dev": true }, "@types/node-fetch": { @@ -29096,8 +29553,7 @@ "@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "dev": true + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" }, "@types/parse5": { "version": "5.0.3", @@ -29114,8 +29570,7 @@ "@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", - "dev": true + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" }, "@types/q": { "version": "1.5.5", @@ -29129,11 +29584,16 @@ "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", "dev": true }, + "@types/raf": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.0.tgz", + "integrity": "sha512-taW5/WYqo36N7V39oYyHP9Ipfd5pNFvGTIQsNGj86xV88YQ7GnI30/yMfKDF7Zgin0m3e+ikX88FvImnK4RjGw==", + "optional": true + }, "@types/react": { - "version": "18.0.6", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.6.tgz", - "integrity": "sha512-bPqwzJRzKtfI0mVYr5R+1o9BOE8UEXefwc1LwcBtfnaAn6OoqMhLa/91VA8aeWfDPJt1kHvYKI8RHcQybZLHHA==", - "dev": true, + "version": "18.0.8", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.8.tgz", + "integrity": "sha512-+j2hk9BzCOrrOSJASi5XiOyBbERk9jG5O73Ya4M0env5Ixi6vUNli4qy994AINcEF+1IEHISYFfIT4zwr++LKw==", "requires": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -29143,8 +29603,7 @@ "csstype": { "version": "3.0.11", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", - "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==", - "dev": true + "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==" } } }, @@ -29157,6 +29616,14 @@ "@types/react": "*" } }, + "@types/react-transition-group": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.4.tgz", + "integrity": "sha512-7gAPz7anVK5xzbeQW9wFBDg7G++aPLAFY0QaSMOou9rJZpbuI58WAuJrgu+qR92l61grlnCUe7AFX8KGahAgug==", + "requires": { + "@types/react": "*" + } + }, "@types/resize-observer-browser": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/@types/resize-observer-browser/-/resize-observer-browser-0.1.7.tgz", @@ -29165,8 +29632,7 @@ "@types/scheduler": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", - "dev": true + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" }, "@types/source-list-map": { "version": "0.1.2", @@ -29475,9 +29941,9 @@ } }, "address": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/address/-/address-1.1.2.tgz", - "integrity": "sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/address/-/address-1.2.0.tgz", + "integrity": "sha512-tNEZYz5G/zYunxFm7sfhAxkXEuLj3K6BKwv6ZURlsF6yiUQ65z0Q2wZW9L5cPUl9ocofGvXOdFYbFHp0+6MOig==", "dev": true }, "aggregate-error": { @@ -29531,15 +29997,13 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", - "dev": true, - "requires": {} + "dev": true }, "ajv-keywords": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "requires": {} + "dev": true }, "ansi-align": { "version": "3.0.1", @@ -29891,8 +30355,7 @@ "atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" }, "autoprefixer": { "version": "7.2.6", @@ -29944,9 +30407,9 @@ "dev": true }, "aws-sdk": { - "version": "2.1119.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1119.0.tgz", - "integrity": "sha512-f9xoIWo0/kLVfQ65UCkQ7G+Oxl/mjmdGQNPDcylU8dOjXGfc4cgWxTLThRLCsf9l/4yUjWgo+KtJgY6BWc9UjA==", + "version": "2.1125.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1125.0.tgz", + "integrity": "sha512-2syNkKDqDcDmB/chc61a5xx+KYzaarLs1/KshE0b1Opp2oSq2FARyUBbk59HgwKaDUB61uPF33ZG9sHiIVx2hQ==", "dev": true, "requires": { "buffer": "4.9.2", @@ -30169,8 +30632,7 @@ "version": "0.3.8", "resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.8.tgz", "integrity": "sha512-WXiAc++qo7XcJ1ZnTYGtLxmBCVbddAml3CEXgWaBzNzLNoxtQ8AiGEFDMOhot9XjTCQbvP5E77Fj9Gk924f00Q==", - "dev": true, - "requires": {} + "dev": true }, "babel-plugin-polyfill-corejs2": { "version": "0.3.1", @@ -30257,11 +30719,6 @@ "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", "dev": true }, - "backo2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" - }, "bail": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", @@ -30304,6 +30761,12 @@ "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz", "integrity": "sha1-4pf2DX7BAUp6lxo568ipjAtoHnA=" }, + "base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "optional": true + }, "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -30414,21 +30877,23 @@ "dev": true }, "body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", + "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", "dev": true, "requires": { "bytes": "3.1.2", "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.8.1", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.9.7", - "raw-body": "2.4.3", - "type-is": "~1.6.18" + "on-finished": "2.4.1", + "qs": "6.10.3", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, "dependencies": { "bytes": { @@ -30451,12 +30916,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true - }, - "qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==", - "dev": true } } }, @@ -30672,17 +31131,22 @@ } }, "browserslist": { - "version": "4.20.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.2.tgz", - "integrity": "sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA==", + "version": "4.20.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.3.tgz", + "integrity": "sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg==", "requires": { - "caniuse-lite": "^1.0.30001317", - "electron-to-chromium": "^1.4.84", + "caniuse-lite": "^1.0.30001332", + "electron-to-chromium": "^1.4.118", "escalade": "^3.1.1", - "node-releases": "^2.0.2", + "node-releases": "^2.0.3", "picocolors": "^1.0.0" } }, + "btoa": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", + "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==" + }, "buffer": { "version": "4.9.2", "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", @@ -30870,8 +31334,7 @@ "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" }, "camel-case": { "version": "3.0.0", @@ -30907,9 +31370,25 @@ } }, "caniuse-lite": { - "version": "1.0.30001332", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001332.tgz", - "integrity": "sha512-10T30NYOEQtN6C11YGg411yebhvpnC6Z102+B95eAsN0oB6KUs01ivE8u+G6FMIRtIrVlYXhL+LUwQ3/hXwDWw==" + "version": "1.0.30001335", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001335.tgz", + "integrity": "sha512-ddP1Tgm7z2iIxu6QTtbZUv6HJxSaV/PZeSrWFZtbY4JZ69tOeNhBCl3HyRQgeNZKE5AOn1kpV7fhljigy0Ty3w==" + }, + "canvg": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz", + "integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==", + "optional": true, + "requires": { + "@babel/runtime": "^7.12.5", + "@types/raf": "^3.4.0", + "core-js": "^3.8.3", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.7", + "rgbcolor": "^1.0.1", + "stackblur-canvas": "^2.0.0", + "svg-pathdata": "^6.0.3" + } }, "case-sensitive-paths-webpack-plugin": { "version": "2.4.0", @@ -31010,8 +31489,7 @@ "version": "5.2.2", "resolved": "https://registry.npmjs.org/circular-dependency-plugin/-/circular-dependency-plugin-5.2.2.tgz", "integrity": "sha512-g38K9Cm5WRwlaH6g03B9OEz/0qRizI+2I7n+Gz+L5DxXJAPAiWQvwlYNm1V1jkdpUv95bOe/ASm2vfi/G560jQ==", - "dev": true, - "requires": {} + "dev": true }, "class-utils": { "version": "0.3.6", @@ -31249,9 +31727,9 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "color-string": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.0.tgz", - "integrity": "sha512-9Mrz2AQLefkH1UvASKj6v6hj/7eWgjnT/cVsR8CumieLoT+g900exWeNogqtweI8dxloXN9BDQTYro1oWu/5CQ==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", "requires": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" @@ -31474,15 +31952,14 @@ "version": "1.8.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dev": true, "requires": { "safe-buffer": "~5.1.1" } }, "cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", "dev": true }, "cookie-signature": { @@ -31653,18 +32130,18 @@ } }, "core-js": { - "version": "3.22.2", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.22.2.tgz", - "integrity": "sha512-Z5I2vzDnEIqO2YhELVMFcL1An2CIsFe9Q7byZhs8c/QxummxZlAHw33TUHbIte987LkisOgL0LwQ1P9D6VISnA==", - "dev": true + "version": "3.22.3", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.22.3.tgz", + "integrity": "sha512-1t+2a/d2lppW1gkLXx3pKPVGbBdxXAkqztvWb1EJ8oF8O2gIGiytzflNiFEehYwVK/t2ryUsGBoOFFvNx95mbg==", + "devOptional": true }, "core-js-compat": { - "version": "3.22.2", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.22.2.tgz", - "integrity": "sha512-Fns9lU06ZJ07pdfmPMu7OnkIKGPKDzXKIiuGlSvHHapwqMUF2QnnsWwtueFZtSyZEilP0o6iUeHQwpn7LxtLUw==", + "version": "3.22.3", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.22.3.tgz", + "integrity": "sha512-wliMbvPI2idgFWpFe7UEyHMvu6HWgW8WA+HnDRtgzoSDYvXFMpoGX1H3tPDDXrcfUSyXafCLDd7hOeMQHEZxGw==", "dev": true, "requires": { - "browserslist": "^4.20.2", + "browserslist": "^4.20.3", "semver": "7.0.0" }, "dependencies": { @@ -31677,9 +32154,9 @@ } }, "core-js-pure": { - "version": "3.22.2", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.22.2.tgz", - "integrity": "sha512-Lb+/XT4WC4PaCWWtZpNPaXmjiNDUe5CJuUtbkMrIM1kb1T/jJoAIp+bkVP/r5lHzMr+ZAAF8XHp7+my6Ol0ysQ==", + "version": "3.22.3", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.22.3.tgz", + "integrity": "sha512-oN88zz7nmKROMy8GOjs+LN+0LedIvbMdnB5XsTlhcOg1WGARt9l0LFg0zohdoFmCsEZ1h2ZbSQ6azj3M+vhzwQ==", "dev": true }, "core-util-is": { @@ -32070,8 +32547,16 @@ "version": "6.2.2", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.2.2.tgz", "integrity": "sha512-Ufadglr88ZLsrvS11gjeu/40Lw74D9Am/Jpr3LlYm5Q4ZP5KdlUhG+6u2EjyXeZcxmZ2h1ebCKngDjolpeLHpg==", - "dev": true, - "requires": {} + "dev": true + }, + "css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "optional": true, + "requires": { + "utrie": "^1.0.2" + } }, "css-loader": { "version": "3.6.0", @@ -32382,8 +32867,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", - "dev": true, - "requires": {} + "dev": true }, "csso": { "version": "4.2.0", @@ -32708,9 +33192,9 @@ "dev": true }, "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "dev": true }, "deploy-aws-s3-cloudfront": { @@ -32740,9 +33224,9 @@ } }, "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "dev": true }, "detab": { @@ -32876,9 +33360,9 @@ } }, "dom-align": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.12.2.tgz", - "integrity": "sha512-pHuazgqrsTFrGU2WLDdXxCFabkdQDx72ddkraZNih1KsMcN5qsRSTR9O4VJRlwTPCPb5COYg3LOfiMHHcPInHg==" + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.12.3.tgz", + "integrity": "sha512-Gj9hZN3a07cbR6zviMUBOMPdWxYhbMI+x+WS0NAIu2zFZmbK8ys9R79g+iG9qLnlCwpFoaB+fKy8Pdv470GsPA==" }, "dom-converter": { "version": "0.2.0", @@ -32946,6 +33430,12 @@ "domelementtype": "1" } }, + "dompurify": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.6.tgz", + "integrity": "sha512-OFP2u/3T1R5CEgWCEONuJ1a5+MFKnOYpkywpUSxv/dj1LeBT1erK+JwM7zK0ROy2BRhqVCf0LRw/kHqKuMkVGg==", + "optional": true + }, "domutils": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", @@ -33082,9 +33572,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.4.118", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.118.tgz", - "integrity": "sha512-maZIKjnYDvF7Fs35nvVcyr44UcKNwybr93Oba2n3HkKDFAtk0svERkLN/HyczJDS3Fo4wU9th9fUQd09ZLtj1w==" + "version": "1.4.129", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.129.tgz", + "integrity": "sha512-GgtN6bsDtHdtXJtlMYZWGB/uOyjZWjmRDumXTas7dGBaB9zUyCjzHet1DY2KhyHN8R0GLbzZWqm4efeddqqyRQ==" }, "element-resize-detector": { "version": "1.2.4", @@ -33174,36 +33664,28 @@ } }, "engine.io-client": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.1.1.tgz", - "integrity": "sha512-V05mmDo4gjimYW+FGujoGmmmxRaDsrVr7AXA3ZIfa04MWM1jOfZfUwou0oNqhNwy/votUDvGDt4JA4QF4e0b4g==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.2.2.tgz", + "integrity": "sha512-8ZQmx0LQGRTYkHuogVZuGSpDqYZtCM/nv8zQ68VZ+JkOpazJ7ICdsSpaO6iXwvaU30oFg5QJOJWj8zWqhbKjkQ==", "requires": { - "@socket.io/component-emitter": "~3.0.0", + "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1", - "engine.io-parser": "~5.0.0", - "has-cors": "1.1.0", - "parseqs": "0.0.6", - "parseuri": "0.0.6", + "engine.io-parser": "~5.0.3", "ws": "~8.2.3", - "xmlhttprequest-ssl": "~2.0.0", - "yeast": "0.1.2" + "xmlhttprequest-ssl": "~2.0.0" }, "dependencies": { "ws": { "version": "8.2.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", - "requires": {} + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==" } } }, "engine.io-parser": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.3.tgz", - "integrity": "sha512-BtQxwF27XUNnSafQLvDi0dQ8s3i6VgzSoQMJacpIcGNrlUdfHSKbgm3jmjCVvQluGzqwujQMPAoMai3oYSTurg==", - "requires": { - "@socket.io/base64-arraybuffer": "~1.0.2" - } + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.4.tgz", + "integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==" }, "enhanced-resolve": { "version": "4.5.0", @@ -33243,7 +33725,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, "requires": { "is-arrayish": "^0.2.1" } @@ -33346,9 +33827,9 @@ } }, "es5-shim": { - "version": "4.6.5", - "resolved": "https://registry.npmjs.org/es5-shim/-/es5-shim-4.6.5.tgz", - "integrity": "sha512-vfQ4UAai8szn0sAubCy97xnZ4sJVDD1gt/Grn736hg8D7540wemIb1YPrYZSTqlM2H69EQX1or4HU/tSwRTI3w==", + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/es5-shim/-/es5-shim-4.6.6.tgz", + "integrity": "sha512-Ay5QQE78I2WKUoZVZjL0AIuiIjsmXwZGkyCTH9+n6J1anPbb0ymDA27ASa2Lt0rhOpAlEKy2W0d17gJ1XOQ5eQ==", "dev": true }, "es6-error": { @@ -33912,38 +34393,39 @@ } }, "express": { - "version": "4.17.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz", - "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", + "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", "dev": true, "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.19.2", + "body-parser": "1.20.0", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.2", + "cookie": "0.5.0", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "~1.1.2", + "depd": "2.0.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "~1.1.2", + "finalhandler": "1.2.0", "fresh": "0.5.2", + "http-errors": "2.0.0", "merge-descriptors": "1.0.1", "methods": "~1.1.2", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", "proxy-addr": "~2.0.7", - "qs": "6.9.7", + "qs": "6.10.3", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.17.2", - "serve-static": "1.14.2", + "send": "0.18.0", + "serve-static": "1.15.0", "setprototypeof": "1.2.0", - "statuses": "~1.5.0", + "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -33964,12 +34446,6 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, - "qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==", - "dev": true - }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -34167,6 +34643,11 @@ "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", "dev": true }, + "fflate": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", + "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==" + }, "figgy-pudding": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", @@ -34240,17 +34721,17 @@ } }, "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", "dev": true, "requires": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", - "statuses": "~1.5.0", + "statuses": "2.0.1", "unpipe": "~1.0.0" }, "dependencies": { @@ -34330,8 +34811,7 @@ "find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", - "dev": true + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" }, "find-up": { "version": "5.0.0", @@ -34585,9 +35065,9 @@ } }, "fork-ts-checker-webpack-plugin": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.1.tgz", - "integrity": "sha512-x1wumpHOEf4gDROmKTaB6i4/Q6H3LwmjVO7fIX47vBwlZbtPjU33hgoMuD/Q/y6SU8bnuYSoN6ZQOLshGp0T/g==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.2.tgz", + "integrity": "sha512-m5cUmF30xkZ7h4tWUgTAcEaKmUW7tfyUyTqNNOz7OxWJ0v1VWKTcOvH8FWHUwSjlW/356Ijc9vi3XfcPstpQKA==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", @@ -35105,11 +35585,6 @@ "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", "dev": true }, - "has-cors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", - "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" - }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -35585,6 +36060,11 @@ "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.2.0.tgz", "integrity": "sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==" }, + "html-to-image": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/html-to-image/-/html-to-image-1.9.0.tgz", + "integrity": "sha512-9gaDCIYg62Ek07F2pBk76AHgYZ2gxq2YALU7rK3gNCqXuhu6cWzsOQqM7qGbjZiOzxGzrU1deDqZpAod2NEwbA==" + }, "html-void-elements": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-1.0.5.tgz", @@ -35638,6 +36118,16 @@ } } }, + "html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "optional": true, + "requires": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + } + }, "htmlparser2": { "version": "3.8.3", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", @@ -35664,15 +36154,15 @@ "dev": true }, "http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "dev": true, "requires": { - "depd": "~1.1.2", + "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", + "statuses": "2.0.1", "toidentifier": "1.0.1" } }, @@ -35896,7 +36386,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, "requires": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -35905,8 +36394,7 @@ "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" } } }, @@ -36135,8 +36623,7 @@ "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" }, "is-bigint": { "version": "1.0.4", @@ -36638,8 +37125,7 @@ "json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, "json-schema-traverse": { "version": "0.4.1", @@ -36669,10 +37155,25 @@ "universalify": "^2.0.0" } }, + "jspdf": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.1.tgz", + "integrity": "sha512-hXObxz7ZqoyhxET78+XR34Xu2qFGrJJ2I2bE5w4SM8eFaFEkW2xcGRVUss360fYelwRSid/jT078kbNvmoW0QA==", + "requires": { + "@babel/runtime": "^7.14.0", + "atob": "^2.1.2", + "btoa": "^1.2.1", + "canvg": "^3.0.6", + "core-js": "^3.6.0", + "dompurify": "^2.2.0", + "fflate": "^0.4.8", + "html2canvas": "^1.0.0-rc.5" + } + }, "jsx-ast-utils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.2.tgz", - "integrity": "sha512-HDAyJ4MNQBboGpUnHAVUNJs6X0lh058s6FuixsFGP7MgJYpD6Vasd6nzSG5iIfXu1zAYlHJ/zsOKNlrenTUBnw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.0.tgz", + "integrity": "sha512-XzO9luP6L0xkxwhIJMTJQpZo/eeN60K08jHdexfD569AGxeNug6UketeHXEhROoM8aR7EcUoOQmIhcJQjcuq8Q==", "dev": true, "requires": { "array-includes": "^3.1.4", @@ -36774,8 +37275,7 @@ "lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, "loader-runner": { "version": "2.4.0", @@ -36836,6 +37336,12 @@ "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", "dev": true }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", + "dev": true + }, "lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", @@ -36948,8 +37454,7 @@ "version": "7.1.7", "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.1.7.tgz", "integrity": "sha512-VI3TyyHlGkO8uFle0IOibzpO1c1iJDcXcS/zBrQrXQQvJ2tpdwVzVZ7XdKsyRz1NdRmre4dqQkMZzUHaKIG/1w==", - "dev": true, - "requires": {} + "dev": true }, "md5-file": { "version": "5.0.0", @@ -37029,6 +37534,11 @@ "fs-monkey": "1.0.3" } }, + "memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" + }, "memoizerific": { "version": "1.11.3", "resolved": "https://registry.npmjs.org/memoizerific/-/memoizerific-1.11.3.tgz", @@ -37239,9 +37749,9 @@ "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, "minio": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/minio/-/minio-7.0.27.tgz", - "integrity": "sha512-0eFFob4ZzqsK0lsuM2cRddDkdFrfQGKCpD77XbFzFClhQL4w85CXC3UwfIe0rBRmLzFMZ5Y9bph8Dpny/COrQQ==", + "version": "7.0.28", + "resolved": "https://registry.npmjs.org/minio/-/minio-7.0.28.tgz", + "integrity": "sha512-4Oua0R73oCxxmxhh2NiXDJo4Md159I/mdG8ybu6351leMQoB2Sy8S4HmgG6CxuPlEJ0h9M8/WyaI2CARDeeDTQ==", "dev": true, "requires": { "async": "^3.1.0", @@ -37406,8 +37916,7 @@ "mobx-react-lite": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/mobx-react-lite/-/mobx-react-lite-3.3.0.tgz", - "integrity": "sha512-U/kMSFtV/bNVgY01FuiGWpRkaQVHozBq5CEBZltFvPt4FcV111hEWkgwqVg9GPPZSEuEdV438PEz8mk8mKpYlA==", - "requires": {} + "integrity": "sha512-U/kMSFtV/bNVgY01FuiGWpRkaQVHozBq5CEBZltFvPt4FcV111hEWkgwqVg9GPPZSEuEdV438PEz8mk8mKpYlA==" }, "moment": { "version": "2.29.3", @@ -37689,9 +38198,9 @@ } }, "node-releases": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.3.tgz", - "integrity": "sha512-maHFz6OLqYxz+VQyCAtA3PTX4UP/53pa05fyDNc9CwjvJ0yEh6+xBwKsgCxMNhS8taUKBFYxfuiaD9U/55iFaw==" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.4.tgz", + "integrity": "sha512-gbMzqQtTtDz/00jQzZ21PQzdI9PyLYqUSvD0p3naOhX4odFji0ZxYdnVwPTxmSwkmxhcFImpozceidSG+AgoPQ==" }, "normalize-package-data": { "version": "2.5.0", @@ -37985,9 +38494,9 @@ "dev": true }, "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dev": true, "requires": { "ee-first": "1.1.1" @@ -38237,7 +38746,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "requires": { "callsites": "^3.0.0" } @@ -38273,7 +38781,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -38293,16 +38800,6 @@ "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", "dev": true }, - "parseqs": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz", - "integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==" - }, - "parseuri": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz", - "integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==" - }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -38401,8 +38898,7 @@ "path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" }, "pbkdf2": { "version": "3.1.2", @@ -38692,29 +39188,25 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.1.tgz", "integrity": "sha512-5JscyFmvkUxz/5/+TB3QTTT9Gi9jHkcn8dcmmuN68JQcv3aQg4y88yEHHhwFB52l/NkaJ43O0dbksGMAo49nfQ==", - "dev": true, - "requires": {} + "dev": true }, "postcss-discard-duplicates": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", - "dev": true, - "requires": {} + "dev": true }, "postcss-discard-empty": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", - "dev": true, - "requires": {} + "dev": true }, "postcss-discard-overridden": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", - "dev": true, - "requires": {} + "dev": true }, "postcss-flexbugs-fixes": { "version": "4.2.1", @@ -39388,8 +39880,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", - "dev": true, - "requires": {} + "dev": true }, "postcss-normalize-display-values": { "version": "5.1.0", @@ -40097,13 +40588,13 @@ "dev": true }, "raw-body": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", - "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", "dev": true, "requires": { "bytes": "3.1.2", - "http-errors": "1.8.1", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" }, @@ -40238,27 +40729,23 @@ "react-circular-progressbar": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/react-circular-progressbar/-/react-circular-progressbar-2.0.4.tgz", - "integrity": "sha512-OfX0ThSxRYEVGaQSt0DlXfyl5w4DbXHsXetyeivmoQrh9xA9bzVPHNf8aAhOIiwiaxX2WYWpLDB3gcpsDJ9oww==", - "requires": {} + "integrity": "sha512-OfX0ThSxRYEVGaQSt0DlXfyl5w4DbXHsXetyeivmoQrh9xA9bzVPHNf8aAhOIiwiaxX2WYWpLDB3gcpsDJ9oww==" }, "react-codemirror2": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/react-codemirror2/-/react-codemirror2-5.1.0.tgz", - "integrity": "sha512-Cksbgbviuf2mJfMyrKmcu7ycK6zX/ukuQO8dvRZdFWqATf5joalhjFc6etnBdGCcPA2LbhIwz+OPnQxLN/j1Fw==", - "requires": {} + "integrity": "sha512-Cksbgbviuf2mJfMyrKmcu7ycK6zX/ukuQO8dvRZdFWqATf5joalhjFc6etnBdGCcPA2LbhIwz+OPnQxLN/j1Fw==" }, "react-colorful": { "version": "5.5.1", "resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.5.1.tgz", "integrity": "sha512-M1TJH2X3RXEt12sWkpa6hLc/bbYS0H6F4rIqjQZ+RxNBstpY67d9TrFXtqdZwhpmBXcCwEi7stKqFue3ZRkiOg==", - "dev": true, - "requires": {} + "dev": true }, "react-confirm": { "version": "0.1.24", "resolved": "https://registry.npmjs.org/react-confirm/-/react-confirm-0.1.24.tgz", - "integrity": "sha512-96qA+mbZyBRmh/3Y5aDgrYLwLndbaRjkP3GlXQtPEQbIH0P66xGcHJ7ui6y/MN85AZWq/V3drA1fJOiEcVkAVA==", - "requires": {} + "integrity": "sha512-96qA+mbZyBRmh/3Y5aDgrYLwLndbaRjkP3GlXQtPEQbIH0P66xGcHJ7ui6y/MN85AZWq/V3drA1fJOiEcVkAVA==" }, "react-datepicker": { "version": "2.16.0", @@ -40351,8 +40838,7 @@ "version": "2.2.2", "resolved": "https://registry.npmjs.org/react-docgen-typescript/-/react-docgen-typescript-2.2.2.tgz", "integrity": "sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg==", - "dev": true, - "requires": {} + "dev": true }, "react-dom": { "version": "16.14.0", @@ -40366,12 +40852,12 @@ } }, "react-draggable": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.4.tgz", - "integrity": "sha512-6e0WdcNLwpBx/YIDpoyd2Xb04PB0elrDrulKUgdrIlwuYvxh5Ok9M+F8cljm8kPXXs43PmMzek9RrB1b7mLMqA==", + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.5.tgz", + "integrity": "sha512-OMHzJdyJbYTZo4uQE393fHcqqPYsEtkjfMgvCHr6rejT+Ezn4OZbNyGH50vv+SunC1RMvwOTSWkEODQLzw1M9g==", "requires": { "clsx": "^1.1.1", - "prop-types": "^15.6.0" + "prop-types": "^15.8.1" } }, "react-fast-compare": { @@ -40430,8 +40916,7 @@ "react-lazyload": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/react-lazyload/-/react-lazyload-3.2.0.tgz", - "integrity": "sha512-zJlrG8QyVZz4+xkYZH5v1w3YaP5wEFaYSUWC4CT9UXfK75IfRAIEdnyIUF+dXr3kX2MOtL1lUaZmaQZqrETwgw==", - "requires": {} + "integrity": "sha512-zJlrG8QyVZz4+xkYZH5v1w3YaP5wEFaYSUWC4CT9UXfK75IfRAIEdnyIUF+dXr3kX2MOtL1lUaZmaQZqrETwgw==" }, "react-lifecycles-compat": { "version": "3.0.4", @@ -40441,8 +40926,7 @@ "react-onclickoutside": { "version": "6.12.1", "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.12.1.tgz", - "integrity": "sha512-a5Q7CkWznBRUWPmocCvE8b6lEYw1s6+opp/60dCunhO+G6E4tDTO2Sd2jKE+leEnnrLAE2Wj5DlDHNqj5wPv1Q==", - "requires": {} + "integrity": "sha512-a5Q7CkWznBRUWPmocCvE8b6lEYw1s6+opp/60dCunhO+G6E4tDTO2Sd2jKE+leEnnrLAE2Wj5DlDHNqj5wPv1Q==" }, "react-popper": { "version": "1.3.11", @@ -40470,9 +40954,9 @@ }, "dependencies": { "react-popper": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.2.5.tgz", - "integrity": "sha512-kxGkS80eQGtLl18+uig1UIf9MKixFSyPxglsgLBxlYnyDf65BiY9B3nZSc6C9XUNDgStROB0fMQlTEz1KxGddw==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz", + "integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==", "dev": true, "requires": { "react-fast-compare": "^3.0.1", @@ -40593,6 +41077,44 @@ } } }, + "react-select": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.3.1.tgz", + "integrity": "sha512-Y195MmhDoDAj/8gTDyYZU1Raf7tmZd81wxM6RkFko4pqJ4Xv0/ilqUMtSn+GYkwmSlTWeMlzh+e+t7PJgtuXPw==", + "requires": { + "@babel/runtime": "^7.12.0", + "@emotion/cache": "^11.4.0", + "@emotion/react": "^11.8.1", + "@types/react-transition-group": "^4.4.0", + "memoize-one": "^5.0.0", + "prop-types": "^15.6.0", + "react-transition-group": "^4.3.0" + }, + "dependencies": { + "@emotion/cache": { + "version": "11.7.1", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.7.1.tgz", + "integrity": "sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A==", + "requires": { + "@emotion/memoize": "^0.7.4", + "@emotion/sheet": "^1.1.0", + "@emotion/utils": "^1.0.0", + "@emotion/weak-memoize": "^0.2.5", + "stylis": "4.0.13" + } + }, + "@emotion/sheet": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.1.0.tgz", + "integrity": "sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g==" + }, + "@emotion/utils": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.1.0.tgz", + "integrity": "sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ==" + } + } + }, "react-sizeme": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/react-sizeme/-/react-sizeme-3.0.2.tgz", @@ -40676,14 +41198,11 @@ } }, "react-toastify": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-5.5.0.tgz", - "integrity": "sha512-jsVme7jALIFGRyQsri/g4YTsRuaaGI70T6/ikjwZMB4mwTZaCWqj5NqxhGrRStKlJc5npXKKvKeqTiRGQl78LQ==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-8.2.0.tgz", + "integrity": "sha512-Pg2Ju7NngAamarFvLwqrFomJ57u/Ay6i6zfLurt/qPynWkAkOthu6vxfqYpJCyNhHRhR4hu7+bySSeWWJu6PAg==", "requires": { - "@babel/runtime": "^7.4.2", - "classnames": "^2.2.6", - "prop-types": "^15.7.2", - "react-transition-group": "^4" + "clsx": "^1.1.1" } }, "react-transition-group": { @@ -40882,14 +41401,12 @@ "redux-immutable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/redux-immutable/-/redux-immutable-4.0.0.tgz", - "integrity": "sha1-Ohoy32Y2ZGK2NpHw4dw15HK7yfM=", - "requires": {} + "integrity": "sha1-Ohoy32Y2ZGK2NpHw4dw15HK7yfM=" }, "redux-thunk": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz", - "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==", - "requires": {} + "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==" }, "refractor": { "version": "3.6.0", @@ -41376,6 +41893,12 @@ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true }, + "rgbcolor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", + "integrity": "sha1-1lBezbMEplldom+ktDMHMGd1lF0=", + "optional": true + }, "rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -41424,8 +41947,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", @@ -41524,24 +42046,24 @@ "dev": true }, "send": { - "version": "0.17.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", - "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "dev": true, "requires": { "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", + "depd": "2.0.0", + "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "1.8.1", + "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "statuses": "2.0.1" }, "dependencies": { "debug": { @@ -41629,6 +42151,12 @@ "ms": "2.0.0" } }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, "http-errors": { "version": "1.6.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", @@ -41658,19 +42186,25 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", "dev": true + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true } } }, "serve-static": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", - "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", "dev": true, "requires": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.2" + "send": "0.18.0" } }, "set-blocking": { @@ -41977,24 +42511,22 @@ } }, "socket.io-client": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.4.1.tgz", - "integrity": "sha512-N5C/L5fLNha5Ojd7Yeb/puKcPWWcoB/A09fEjjNsg91EDVr5twk/OEyO6VT9dlLSUNY85NpW6KBhVMvaLKQ3vQ==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.5.0.tgz", + "integrity": "sha512-HW61c1G7OrYGxaI79WRn17+b03iBCdvhBj4iqyXHBoL5M8w2MSO/vChsjA93knG4GYEai1/vbXWJna9dzxXtSg==", "requires": { - "@socket.io/component-emitter": "~3.0.0", - "backo2": "~1.0.2", + "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.2", - "engine.io-client": "~6.1.1", - "parseuri": "0.0.6", - "socket.io-parser": "~4.1.1" + "engine.io-client": "~6.2.1", + "socket.io-parser": "~4.2.0" } }, "socket.io-parser": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.1.2.tgz", - "integrity": "sha512-j3kk71QLJuyQ/hh5F/L2t1goqzdTL0gvDzuhTuNSwihfuFUrcSji0qFZmJJPtG6Rmug153eOPsUizeirf1IIog==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.0.tgz", + "integrity": "sha512-tLfmEwcEwnlQTxFB7jibL/q2+q8dlVQzj4JdRLJ/W/G1+Fu9VSxCx1Lo+n1HvXxKnM//dUuD0xgiA7tQf57Vng==", "requires": { - "@socket.io/component-emitter": "~3.0.0", + "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1" } }, @@ -42227,6 +42759,12 @@ "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", "dev": true }, + "stackblur-canvas": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.5.0.tgz", + "integrity": "sha512-EeNzTVfj+1In7aSLPKDD03F/ly4RxEuF/EX0YcOG0cKoPXs+SLZxDawQbexQDBzwROs4VKLWTOaZQlZkGBFEIQ==", + "optional": true + }, "stackframe": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.1.tgz", @@ -42324,9 +42862,9 @@ } }, "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "dev": true }, "store2": { @@ -42617,6 +43155,11 @@ "postcss-selector-parser": "^6.0.4" } }, + "stylis": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz", + "integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==" + }, "sugarss": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/sugarss/-/sugarss-2.0.0.tgz", @@ -42639,6 +43182,12 @@ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" }, + "svg-pathdata": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", + "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", + "optional": true + }, "svgo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", @@ -42892,21 +43441,21 @@ } }, "terser": { - "version": "5.12.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.12.1.tgz", - "integrity": "sha512-NXbs+7nisos5E+yXwAD+y7zrcTkMqb0dEJxIGtSKPdCBzopf7ni4odPul2aechpV7EXNvOudYOX2bb5tln1jbQ==", + "version": "5.13.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.13.1.tgz", + "integrity": "sha512-hn4WKOfwnwbYfe48NgrQjqNOH9jzLqRcIfbYytOXCOv46LBfWr9bDS17MQqOi+BWGD0sJK3Sj5NC/gJjiojaoA==", "dev": true, "requires": { "acorn": "^8.5.0", "commander": "^2.20.0", - "source-map": "~0.7.2", + "source-map": "~0.8.0-beta.0", "source-map-support": "~0.5.20" }, "dependencies": { "acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", + "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", "dev": true }, "commander": { @@ -42914,6 +43463,41 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true + }, + "source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "dev": true, + "requires": { + "whatwg-url": "^7.0.0" + } + }, + "tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + }, + "whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } } } }, @@ -43125,6 +43709,15 @@ "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", "dev": true }, + "text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "optional": true, + "requires": { + "utrie": "^1.0.2" + } + }, "throttle-debounce": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-3.0.1.tgz", @@ -43203,8 +43796,7 @@ "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" }, "to-object-path": { "version": "0.3.0", @@ -43312,9 +43904,9 @@ "dev": true }, "ts-loader": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.3.0.tgz", - "integrity": "sha512-MgGly4I6cStsJy27ViE32UoqxPTN9Xly4anxxVyaIWR+9BGxboV4EyJBGfR3RePV7Ksjj3rHmPZJeIt+7o4Vag==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.4.0.tgz", + "integrity": "sha512-6nFY3IZ2//mrPc+ImY3hNWx1vCHyEhl6V+wLmL4CZcm6g1CqX7UKrkc6y0i4FwcfOhxyMPCfaEvh20f4r9GNpw==", "dev": true, "requires": { "chalk": "^4.1.0", @@ -43446,9 +44038,9 @@ } }, "typed-css-modules": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/typed-css-modules/-/typed-css-modules-0.7.1.tgz", - "integrity": "sha512-WaWHnLa3HBTCuOCDRDB3wfqoH9ouTxdLGQwzVBxV5x+nbPIr5ZkCrCb4yfU4D1bkzNprrTKTU2ETkYVOLGFmVA==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/typed-css-modules/-/typed-css-modules-0.7.2.tgz", + "integrity": "sha512-R3guXrQ8ry/yhlfvNmkVY4J3+FtKaEdwqrvgSvFpVY0ieYQHqhhBW0RwfE4hnG4m29Ef/4IE0tBsk/UKplmJkA==", "dev": true, "requires": { "@types/css-modules-loader-core": "^1.1.0", @@ -43621,21 +44213,21 @@ "optional": true }, "unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", "dev": true, "requires": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", "which-boxed-primitive": "^1.0.2" } }, "underscore": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.2.tgz", - "integrity": "sha512-ekY1NhRzq0B08g4bGuX4wd2jZx5GnKz6mKSqFL4nqBlfyMGiG10gDFhDTMEfYmDL6Jy0FUIZp7wiRB+0BP7J2g==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.3.tgz", + "integrity": "sha512-QvjkYpiD+dJJraRA8+dGAU4i7aBbb2s0S3jA45TFOvg2VgqvdCDd/3N6CqA8gluk1W91GLoXg5enMUx560QzuA==", "dev": true }, "unfetch": { @@ -43947,21 +44539,19 @@ "use-composed-ref": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.3.0.tgz", - "integrity": "sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ==", - "requires": {} + "integrity": "sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ==" }, "use-isomorphic-layout-effect": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", - "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==", - "requires": {} + "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==" }, "use-latest": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.2.0.tgz", - "integrity": "sha512-d2TEuG6nSLKQLAfW3By8mKr8HurOlTkul0sOpxbClIv4SQ4iOd7BYr7VIzdbktUCnv7dua/60xzd8igMU6jmyw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.2.1.tgz", + "integrity": "sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw==", "requires": { - "use-isomorphic-layout-effect": "^1.0.0" + "use-isomorphic-layout-effect": "^1.1.1" } }, "util": { @@ -44005,6 +44595,15 @@ "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", "dev": true }, + "utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "optional": true, + "requires": { + "base64-arraybuffer": "^1.0.2" + } + }, "uuid": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", @@ -45519,8 +46118,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/webpack-filter-warnings-plugin/-/webpack-filter-warnings-plugin-1.2.1.tgz", "integrity": "sha512-Ez6ytc9IseDMLPo0qCuNNYzgtUl8NovOqjIq4uAU8LTD4uoa1w1KpZyyzFtLTEMZpkkOkLfL9eN+KGYdk1Qtwg==", - "dev": true, - "requires": {} + "dev": true }, "webpack-hot-middleware": { "version": "2.25.1", @@ -45828,11 +46426,10 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "ws": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", - "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", - "dev": true, - "requires": {} + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.6.0.tgz", + "integrity": "sha512-AzmM3aH3gk0aX7/rZLYvjdvZooofDu3fFOzGqcSnQ1tOcTWwhM/o+q++E8mAyVVIyUdajrkzWUGftaVSDLn1bw==", + "dev": true }, "xml": { "version": "1.0.1", @@ -45881,8 +46478,7 @@ "yaml": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" }, "yargs": { "version": "17.4.1", @@ -45919,11 +46515,6 @@ "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true }, - "yeast": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", - "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" - }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 7f51ba174..6a8b35749 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -22,9 +22,11 @@ "codemirror": "^5.62.3", "copy-to-clipboard": "^3.3.1", "deep-diff": "^1.0.2", + "html-to-image": "^1.9.0", "immutable": "^4.0.0-rc.12", "jsbi": "^4.1.0", "jshint": "^2.11.1", + "jspdf": "^2.5.1", "luxon": "^1.24.1", "mobx": "^6.3.8", "mobx-react-lite": "^3.1.6", @@ -49,9 +51,10 @@ "react-redux": "^5.1.2", "react-router": "^4.3.1", "react-router-dom": "^4.3.1", + "react-select": "^5.3.1", "react-svg-map": "^2.2.0", "react-tippy": "^1.4.0", - "react-toastify": "^5.5.0", + "react-toastify": "^8.2.0", "react-virtualized": "^9.22.2", "recharts": "^2.1.9", "redux": "^4.0.5", diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index 04c57948e..a9aa4b8e8 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -55,6 +55,7 @@ module.exports = { 'height', 'inset', 'justifyContent', + 'justifySelf', 'letterSpacing', 'lineHeight', // 'listStylePosition', diff --git a/peers/.dockerignore b/peers/.dockerignore new file mode 100644 index 000000000..b6aaccd33 --- /dev/null +++ b/peers/.dockerignore @@ -0,0 +1,6 @@ +# ignore .git and .cache folders +.git +.cache +**/build.sh +**/build_*.sh +**/*deploy.sh diff --git a/peers/build.sh b/peers/build.sh index c15921ea8..381189927 100644 --- a/peers/build.sh +++ b/peers/build.sh @@ -10,9 +10,8 @@ git_sha1=${IMAGE_TAG:-$(git rev-parse HEAD)} check_prereq() { which docker || { echo "Docker not installed, please install docker." - exit=1 + exit 1 } - [[ exit -eq 1 ]] && exit 1 } function build_api(){ @@ -27,6 +26,7 @@ function build_api(){ docker tag ${DOCKER_REPO:-'local'}/peers:${git_sha1} ${DOCKER_REPO:-'local'}/peers:latest docker push ${DOCKER_REPO:-'local'}/peers:latest } + echo "peer docker build complted" } check_prereq diff --git a/scripts/helmcharts/build_deploy.sh b/scripts/helmcharts/build_deploy.sh new file mode 100644 index 000000000..7a75fad8b --- /dev/null +++ b/scripts/helmcharts/build_deploy.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -e + +# This script will build and push docker image to registry + +# Usage: IMAGE_TAG=latest DOCKER_REPO=rg.fr-par.scw.cloud/foss bash build_deploy.sh + +echo $DOCKER_REPO +[[ -z DOCKER_REPO ]] && { + echo Set DOCKER_REPO="your docker registry" + exit 1 +} || { + docker login $DOCKER_REPO + cd ../../api + PUSH_IMAGE=1 bash build.sh $@ + cd ../backend + PUSH_IMAGE=1 bash build.sh $@ + cd ../utilities + PUSH_IMAGE=1 bash build.sh $@ + cd ../peers + PUSH_IMAGE=1 bash build.sh $@ +} diff --git a/scripts/helmcharts/init.sh b/scripts/helmcharts/init.sh index 7c846356f..bf7c18f32 100644 --- a/scripts/helmcharts/init.sh +++ b/scripts/helmcharts/init.sh @@ -20,9 +20,9 @@ usr=`whoami` # Installing k3s curl -sL https://get.k3s.io | sudo K3S_KUBECONFIG_MODE="644" INSTALL_K3S_VERSION='v1.22.8+k3s1' INSTALL_K3S_EXEC="--no-deploy=traefik" sh - -mkdir ~/.kube +[[ -d ~/.kube ]] || mkdir ~/.kube sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config -chmod 0644 ~/.kube/config +sudo chmod 0644 ~/.kube/config sudo chown -R $usr ~/.kube/config diff --git a/scripts/helmcharts/local_deploy.sh b/scripts/helmcharts/local_deploy.sh new file mode 100644 index 000000000..c8c82ceb4 --- /dev/null +++ b/scripts/helmcharts/local_deploy.sh @@ -0,0 +1,105 @@ +#!/bin/bash +set -e + +# This script will build and push docker image to registry + +# Usage: IMAGE_TAG=latest DOCKER_REPO=rg.fr-par.scw.cloud/foss bash build_deploy.sh + +export DOCKER_REPO="rg.fr-par.scw.cloud/foss" +export IMAGE_TAG=`grep fromVersion vars.yaml | awk '{print $NF}'|xargs` + + +apps=( + api + assets + db + ender + http + integrations + sink + storage + assist + peers + all +) +help(){ + cat < +💡 As of today, VirtualBox is not supported on Mac M1. You can try with VMWare as the Vagrant backend, but this has not been tested. + + +### Installation + +- Vagrant: [https://www.vagrantup.com/downloads](https://www.vagrantup.com/downloads) +- VirtualBox: [https://www.virtualbox.org/wiki/Downloads](https://www.virtualbox.org/wiki/Downloads) + +### Configuration + +```bash +mkdir openreplay-contributions +cd openreplay-contributions +git clone https://github.com/openreplay/openreplay -b dev +cp -rf openreplay/scripts/vagrant/ . +vagrant up +``` + +### To access OpenReplay instance + +```bash +Add ip address from about output to your local resolver + +## Mac/Linux + +Copy paste the command from the vagrant output + +## Windows + +Use the following instructions if you’re running Windows 10 or Windows 8: + Press the Windows key. + Type Notepad in the search field. + In the search results, right-click Notepad and select Run as administrator. + From Notepad, open the following file: + c:\Windows\System32\Drivers\etc\hosts + add the below line in the hosts file + openreplay.local + Select File > Save to save your changes. + +**Open the below URL and create an account** +http://openreplay.local/signup +``` + +### To start developing + +- [Frontend](../../frontend/development.md) +- [API](../../api/development.md) +- [Backend](../../backend/development.md) + +### Notes + +It’ll be a good practice to take a snapshot once the initial setup is complete, so that if something is not working as expected, you can always fall back to a stable known version. +```bash +cd openreplay-dev +vagrant snapshot save +# For example +vagrant snapshot save openreplay-160-base +``` + +```bash +# To restore the snapshot +cd openreplay-dev +vagrant snapshot restore openreplay-160-base +``` + + diff --git a/scripts/vagrant/Vagrantfile b/scripts/vagrant/Vagrantfile new file mode 100644 index 000000000..341d9792c --- /dev/null +++ b/scripts/vagrant/Vagrantfile @@ -0,0 +1,129 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +# All Vagrant configuration is done below. The "2" in Vagrant.configure +# configures the configuration version (we support older styles for +# backwards compatibility). Please don't change it unless you know what +# you're doing. +Vagrant.configure("2") do |config| + # The most common configuration options are documented and commented below. + # For a complete reference, please see the online documentation at + # https://docs.vagrantup.com. + + # Every Vagrant development environment requires a box. You can search for + # boxes at https://vagrantcloud.com/search. + config.vm.box = "peru/ubuntu-20.04-server-amd64" + config.vm.define "openreplay-dev" + + # Disable automatic box update checking. If you disable this, then + # boxes will only be checked for updates when the user runs + # `vagrant box outdated`. This is not recommended. + # config.vm.box_check_update = false + + # Create a forwarded port mapping which allows access to a specific port + # within the machine from a port on the host machine. In the example below, + # accessing "localhost:8080" will access port 80 on the guest machine. + # NOTE: This will enable public access to the opened port + # config.vm.network "forwarded_port", guest: 80, host: 8080 + + # Create a forwarded port mapping which allows access to a specific port + # within the machine from a port on the host machine and only allow access + # via 127.0.0.1 to disable public access + # config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1" + + # Create a private network, which allows host-only access to the machine + # using a specific IP. + config.vm.network "private_network", type: "dhcp" + + # Create a public network, which generally matched to bridged network. + # Bridged networks make the machine appear as another physical device on + # your network. + # config.vm.network "public_network" + + # Share an additional folder to the guest VM. The first argument is + # the path on the host to the actual folder. The second argument is + # the path on the guest to mount the folder. And the optional third + # argument is a set of non-required options. + config.vm.synced_folder "./", "/home/vagrant/openreplay-dev/" + + # Provider-specific configuration so you can fine-tune various + # backing providers for Vagrant. These expose provider-specific options. + # Example for VirtualBox: + # + config.vm.provider "virtualbox" do |vb| + # Display the VirtualBox GUI when booting the machine + vb.gui = false + + # Customize the amount of memory on the VM: + vb.cpus = "2" + vb.memory = "4096" + end + # + # View the documentation for the provider you are using for more + # information on available options. + + # Enable provisioning with a shell script. Additional provisioners such as + # Ansible, Chef, Docker, Puppet and Salt are also available. Please see the + # documentation for more information about their specific syntax and use. + config.vm.provision "shell", inline: <<-SHELL + set -x + + IP_ADDR=`ip r | tail -n1 | awk '{print $NF}'` + + # Updating host domainName + grep -q openreplay.local /etc/hosts || echo $IP_ADDR openreplay.local >> /etc/hosts && sudo sed -i "s/.*openreplay.local/${IP_ADDR} openreplay.local/g" /etc/hosts; grep openreplay.local /etc/hosts + + apt-get update + apt-get install -y git curl + curl -fsSL https://get.docker.com | sh - + usermod -aG docker vagrant + + git clone https://github.com/openreplay/openreplay infra + cd infra/scripts/helmcharts + + # changing container runtime for k3s to docker + sudo -u vagrant git checkout -- init.sh + sed -i 's/INSTALL_K3S_EXEC=\\(.*\\)\\\"/INSTALL_K3S_EXEC=\\1 --docker\\\"/g' init.sh + + DOMAIN_NAME=openreplay.local bash init.sh + cp -rf /root/.kube /home/vagrant/ + cp -rf /home/vagrant/infra/scripts/helmcharts/vars.yaml /home/vagrant/openreplay-dev/openreplay/scripts/helmcharts/vars.yaml + chown -R vagrant:vagrant /home/vagrant + + cat <> /etc/hosts && sudo sed -i "s/.*openreplay.local/${IP_ADDR} openreplay.local/g" /etc/hosts; grep openreplay.local /etc/hosts' + + ## Linux (Paste the following command in terminal) + + sudo -- sh -c 'grep -q openreplay.local /etc/hosts || echo $IP_ADDR openreplay.local >> /etc/hosts && sudo sed -i "s/.*openreplay.local/${IP_ADDR} openreplay.local/g" /etc/hosts; grep openreplay.local /etc/hosts' + + ## Windows + + Use the following instructions if you’re running Windows 10 or Windows 8: + + Press the Windows key. + Type Notepad in the search field. + In the search results, right-click Notepad and select Run as administrator. + From Notepad, open the following file: + c:\\Windows\\System32\\Drivers\\etc\\hosts + add the below line in the hosts file + $IP_ADDR openreplay.local + Select File > Save to save your changes. + + To Access Openreplay: + - Open your browser and go to "http://openreplay.local" + + EOF + SHELL +end diff --git a/third-party.md b/third-party.md index 580e4c576..aceb11f08 100644 --- a/third-party.md +++ b/third-party.md @@ -1,4 +1,4 @@ -## Licenses (as of March 19, 2022) +## Licenses (as of April 25, 2022) Below is the list of dependencies used in OpenReplay software. Licenses may change between versions, so please keep this up to date with every new library you use. @@ -98,6 +98,8 @@ Below is the list of dependencies used in OpenReplay software. Licenses may chan | geoip-lite | Apache2 | JavaScript | | ua-parser-js | MIT | JavaScript | | express | MIT | JavaScript | +| jspdf | MIT | JavaScript | +| html-to-image | MIT | JavaScript | | kafka | Apache2 | Infrastructure | | stern | Apache2 | Infrastructure | | k9s | Apache2 | Infrastructure | @@ -107,4 +109,5 @@ Below is the list of dependencies used in OpenReplay software. Licenses may chan | k3s | Apache2 | Infrastructure | | nginx | BSD2 | Infrastructure | | clickhouse | Apache2 | Infrastructure | -| redis | BSD3 | Infrastructure | \ No newline at end of file +| redis | BSD3 | Infrastructure | +| yq | MIT | Infrastructure | diff --git a/tracker/README.md b/tracker/README.md new file mode 100644 index 000000000..f283ee3a8 --- /dev/null +++ b/tracker/README.md @@ -0,0 +1,31 @@ +## Local build + + +In order to build locally any of the javascript packages located under this directory, go to the corresponding folder first: + +```sh +cd tracker # or any tracker-* plugin + +``` +Then run +```sh +yarn +yarn build +``` +OR + +```sh +npm i +npm run build +``` + +You can then use it as a local javascript package by executing the folowing line under your local project location: + +```sh +yarn add file:../path/to/openreplay/monorepo/tracker/tracker +```` +OR +```sh +npm install --save ../path/to/openreplay/monorepo/tracker/tracker +``` + diff --git a/tracker/tracker/package.json b/tracker/tracker/package.json index 0c5b06634..555efe07d 100644 --- a/tracker/tracker/package.json +++ b/tracker/tracker/package.json @@ -1,7 +1,7 @@ { "name": "@openreplay/tracker", "description": "The OpenReplay tracker main package", - "version": "3.5.9", + "version": "3.5.10", "keywords": [ "logging", "replay" diff --git a/tracker/tracker/src/main/app/index.ts b/tracker/tracker/src/main/app/index.ts index 8fd2cb4ac..ace8ecf6e 100644 --- a/tracker/tracker/src/main/app/index.ts +++ b/tracker/tracker/src/main/app/index.ts @@ -13,16 +13,8 @@ import { deviceMemory, jsHeapSizeLimit } from "../modules/performance.js"; import type { Options as ObserverOptions } from "./observer/top_observer.js"; import type { Options as SanitizerOptions } from "./sanitizer.js"; import type { Options as LoggerOptions } from "./logger.js" - - import type { Options as WebworkerOptions, WorkerMessageData } from "../../webworker/types.js"; -export interface OnStartInfo { - sessionID: string, - sessionToken: string, - userUUID: string, -} - // TODO: Unify and clearly describe options logic export interface StartOptions { @@ -31,6 +23,20 @@ export interface StartOptions { forceNew?: boolean, } +export interface OnStartInfo { + sessionID: string, + sessionToken: string, + userUUID: string, +} + +type StartCallback = (i: OnStartInfo) => void +type CommitCallback = (messages: Array) => void +enum ActivityState { + NotActive, + Starting, + Active, +} + type AppOptions = { revID: string; node_id: string; @@ -47,19 +53,11 @@ type AppOptions = { __debug__?: LoggerOptions; // @deprecated - onStart?: (info: OnStartInfo) => void; + onStart?: StartCallback; } & WebworkerOptions; export type Options = AppOptions & ObserverOptions & SanitizerOptions -type Callback = () => void -type CommitCallback = (messages: Array) => void -enum ActivityState { - NotActive, - Starting, - Active, -} - export const CANCELED = "canceled" // TODO: use backendHost only @@ -75,8 +73,8 @@ export default class App { readonly session: Session; private readonly messages: Array = []; private readonly observer: Observer; - private readonly startCallbacks: Array = []; - private readonly stopCallbacks: Array = []; + private readonly startCallbacks: Array = []; + private readonly stopCallbacks: Array = []; private readonly commitCallbacks: Array = []; private readonly options: AppOptions; private readonly revID: string; @@ -167,20 +165,14 @@ export default class App { this.debug.error("OpenReplay error: ", context, e) } - private readonly preStartMessages: Message[] = [] send(message: Message, urgent = false): void { - if (this.activityState === ActivityState.NotActive) { - return; - } - if (this.activityState === ActivityState.Starting) { - this.preStartMessages.push(message); - } - if (this.preStartMessages.length) { - this.messages.push(...this.preStartMessages); - this.preStartMessages.length = 0 - } + if (this.activityState === ActivityState.NotActive) { return } this.messages.push(message); - if (urgent) { + // TODO: commit on start if there were `urgent` sends; + // Clearify where urgent can be used for; + // Clearify workflow for each type of message in case it was sent before start + // (like Fetch before start; maybe add an option "preCapture: boolean" or sth alike) + if (this.activityState === ActivityState.Active && urgent) { this.commit(); } } @@ -211,11 +203,10 @@ export default class App { attachCommitCallback(cb: CommitCallback): void { this.commitCallbacks.push(cb) } - - attachStartCallback(cb: Callback): void { + attachStartCallback(cb: StartCallback): void { this.startCallbacks.push(cb); } - attachStopCallback(cb: Callback): void { + attachStopCallback(cb: Function): void { this.stopCallbacks.push(cb); } attachEventListener( @@ -394,13 +385,15 @@ export default class App { beaconSizeLimit } this.worker.postMessage(startWorkerMsg) - this.startCallbacks.forEach((cb) => cb()); + + const onStartInfo = { sessionToken: token, userUUID, sessionID }; + + this.startCallbacks.forEach((cb) => cb(onStartInfo)); this.observer.observe(); this.ticker.start(); this.notify.log("OpenReplay tracking started."); // TODO: get rid of onStart - const onStartInfo = { sessionToken: token, userUUID, sessionID }; if (typeof this.options.onStart === 'function') { this.options.onStart(onStartInfo); } diff --git a/tracker/tracker/src/main/modules/timing.ts b/tracker/tracker/src/main/modules/timing.ts index 60e30019f..033741838 100644 --- a/tracker/tracker/src/main/modules/timing.ts +++ b/tracker/tracker/src/main/modules/timing.ts @@ -107,18 +107,6 @@ export default function (app: App, opts: Partial): void { } if (!options.captureResourceTimings) { return } // Resources are necessary for all timings - const mQueue: Message[] = [] - function sendOnStart(m: Message) { - if (app.active()) { - app.send(m) - } else { - mQueue.push(m) - } - } - app.attachStartCallback(function() { - mQueue.forEach(m => app.send(m)) - }) - let resources: ResourcesTimeMap | null = {} function resourceTiming(entry: PerformanceResourceTiming): void { @@ -126,7 +114,7 @@ export default function (app: App, opts: Partial): void { if (resources !== null) { resources[entry.name] = entry.startTime + entry.duration; } - sendOnStart(new + app.send(new ResourceTiming( entry.startTime + performance.timing.navigationStart, entry.duration, @@ -147,8 +135,19 @@ export default function (app: App, opts: Partial): void { const observer: PerformanceObserver = new PerformanceObserver( (list) => list.getEntries().forEach(resourceTiming), ) - performance.getEntriesByType('resource').forEach(resourceTiming) - observer.observe({ entryTypes: ['resource'] }) + + let prevSessionID: string | undefined + app.attachStartCallback(function({ sessionID }) { + if (sessionID !== prevSessionID) { // Send past page resources on a newly started session + performance.getEntriesByType('resource').forEach(resourceTiming) + prevSessionID = sessionID + } + observer.observe({ entryTypes: ['resource'] }) + }) + + app.attachStopCallback(function() { + observer.disconnect() + }) let firstPaint = 0, diff --git a/utilities/.dockerignore b/utilities/.dockerignore new file mode 100644 index 000000000..b6aaccd33 --- /dev/null +++ b/utilities/.dockerignore @@ -0,0 +1,6 @@ +# ignore .git and .cache folders +.git +.cache +**/build.sh +**/build_*.sh +**/*deploy.sh diff --git a/utilities/build.sh b/utilities/build.sh index f7d003ed3..4a290768d 100644 --- a/utilities/build.sh +++ b/utilities/build.sh @@ -10,9 +10,8 @@ git_sha1=${IMAGE_TAG:-$(git rev-parse HEAD)} check_prereq() { which docker || { echo "Docker not installed, please install docker." - exit=1 + exit 1 } - [[ exit -eq 1 ]] && exit 1 } function build_api(){ @@ -26,6 +25,7 @@ function build_api(){ docker tag ${DOCKER_REPO:-'local'}/assist:${git_sha1} ${DOCKER_REPO:-'local'}/assist:latest docker push ${DOCKER_REPO:-'local'}/assist:latest } + echo "build completed for assist" } check_prereq diff --git a/utilities/servers/websocket.js b/utilities/servers/websocket.js index 2d5b6fc4e..d2399477e 100644 --- a/utilities/servers/websocket.js +++ b/utilities/servers/websocket.js @@ -220,8 +220,13 @@ function extractSessionInfo(socket) { socket.handshake.query.sessionInfo.userCountry = null; if (geoip() !== null) { debug && console.log(`looking for location of ${socket.handshake.headers['x-forwarded-for'] || socket.handshake.address}`); - let country = geoip().country(socket.handshake.headers['x-forwarded-for'] || socket.handshake.address); - socket.handshake.query.sessionInfo.userCountry = country.country.isoCode; + try { + let country = geoip().country(socket.handshake.headers['x-forwarded-for'] || socket.handshake.address); + socket.handshake.query.sessionInfo.userCountry = country.country.isoCode; + } catch (e) { + debug && console.log("geoip-country failed"); + debug && console.log(e); + } } } }