diff --git a/.github/composite-actions/update-keys/action.yml b/.github/composite-actions/update-keys/action.yml index 3de586420..2f1f4f9d9 100644 --- a/.github/composite-actions/update-keys/action.yml +++ b/.github/composite-actions/update-keys/action.yml @@ -47,6 +47,7 @@ runs: "JWT_SECRET:.global.jwtSecret" "JWT_SPOT_REFRESH_SECRET:.chalice.env.JWT_SPOT_REFRESH_SECRET" "JWT_SPOT_SECRET:.global.jwtSpotSecret" + "JWT_SECRET:.global.tokenSecret" "LICENSE_KEY:.global.enterpriseEditionLicense" "MINIO_ACCESS_KEY:.global.s3.accessKey" "MINIO_SECRET_KEY:.global.s3.secretKey" diff --git a/.github/workflows/assist-server-ee.yaml b/.github/workflows/assist-server-ee.yaml new file mode 100644 index 000000000..62f857f9d --- /dev/null +++ b/.github/workflows/assist-server-ee.yaml @@ -0,0 +1,122 @@ +# This action will push the assist changes to aws +on: + workflow_dispatch: + inputs: + skip_security_checks: + description: "Skip Security checks if there is a unfixable vuln or error. Value: true/false" + required: false + default: "false" + push: + branches: + - dev + paths: + - "ee/assist-server/**" + +name: Build and Deploy Assist-Server EE + +jobs: + deploy: + name: Deploy + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + # We need to diff with old commit + # to see which workers got changed. + fetch-depth: 2 + + - uses: ./.github/composite-actions/update-keys + with: + assist_jwt_secret: ${{ secrets.ASSIST_JWT_SECRET }} + assist_key: ${{ secrets.ASSIST_KEY }} + domain_name: ${{ secrets.EE_DOMAIN_NAME }} + jwt_refresh_secret: ${{ secrets.JWT_REFRESH_SECRET }} + jwt_secret: ${{ secrets.EE_JWT_SECRET }} + jwt_spot_refresh_secret: ${{ secrets.JWT_SPOT_REFRESH_SECRET }} + jwt_spot_secret: ${{ secrets.JWT_SPOT_SECRET }} + license_key: ${{ secrets.EE_LICENSE_KEY }} + minio_access_key: ${{ secrets.EE_MINIO_ACCESS_KEY }} + minio_secret_key: ${{ secrets.EE_MINIO_SECRET_KEY }} + pg_password: ${{ secrets.EE_PG_PASSWORD }} + registry_url: ${{ secrets.OSS_REGISTRY_URL }} + name: Update Keys + + - name: Docker login + run: | + docker login ${{ secrets.EE_REGISTRY_URL }} -u ${{ secrets.EE_DOCKER_USERNAME }} -p "${{ secrets.EE_REGISTRY_TOKEN }}" + + - uses: azure/k8s-set-context@v1 + with: + method: kubeconfig + kubeconfig: ${{ secrets.EE_KUBECONFIG }} # Use content of kubeconfig in secret. + id: setcontext + + - name: Building and Pushing Assist-Server image + id: build-image + env: + DOCKER_REPO: ${{ secrets.EE_REGISTRY_URL }} + IMAGE_TAG: ${{ github.ref_name }}_${{ github.sha }}-ee + ENVIRONMENT: staging + run: | + skip_security_checks=${{ github.event.inputs.skip_security_checks }} + cd assist-server + PUSH_IMAGE=0 bash -x ./build.sh ee + [[ "x$skip_security_checks" == "xtrue" ]] || { + curl -L https://github.com/aquasecurity/trivy/releases/download/v0.56.2/trivy_0.56.2_Linux-64bit.tar.gz | tar -xzf - -C ./ + images=("assist-server") + for image in ${images[*]};do + ./trivy image --db-repository ghcr.io/aquasecurity/trivy-db:2 --db-repository public.ecr.aws/aquasecurity/trivy-db:2 --exit-code 1 --security-checks vuln --vuln-type os,library --severity "HIGH,CRITICAL" --ignore-unfixed $DOCKER_REPO/$image:$IMAGE_TAG + done + err_code=$? + [[ $err_code -ne 0 ]] && { + exit $err_code + } + } && { + echo "Skipping Security Checks" + } + images=("assist-server") + for image in ${images[*]};do + docker push $DOCKER_REPO/$image:$IMAGE_TAG + done + - name: Creating old image input + run: | + # + # Create yaml with existing image tags + # + kubectl get pods -n app -o jsonpath="{.items[*].spec.containers[*].image}" |\ + tr -s '[[:space:]]' '\n' | sort | uniq -c | grep '/foss/' | cut -d '/' -f3 > /tmp/image_tag.txt + + echo > /tmp/image_override.yaml + + for line in `cat /tmp/image_tag.txt`; + do + image_array=($(echo "$line" | tr ':' '\n')) + cat <> /tmp/image_override.yaml + ${image_array[0]}: + image: + # We've to strip off the -ee, as helm will append it. + tag: `echo ${image_array[1]} | cut -d '-' -f 1` + EOF + done + - name: Deploy to kubernetes + run: | + pwd + cd scripts/helmcharts/ + + # Update changed image tag + sed -i "/assist-server/{n;n;n;s/.*/ tag: ${IMAGE_TAG}/}" /tmp/image_override.yaml + + cat /tmp/image_override.yaml + # Deploy command + mkdir -p /tmp/charts + mv openreplay/charts/{ingress-nginx,assist-server,quickwit,connector} /tmp/charts/ + rm -rf openreplay/charts/* + mv /tmp/charts/* openreplay/charts/ + helm template openreplay -n app openreplay -f vars.yaml -f /tmp/image_override.yaml --set ingress-nginx.enabled=false --set skipMigration=true --no-hooks --kube-version=$k_version | kubectl apply -f - + env: + DOCKER_REPO: ${{ secrets.EE_REGISTRY_URL }} + # We're not passing -ee flag, because helm will add that. + IMAGE_TAG: ${{ github.ref_name }}_${{ github.sha }} + ENVIRONMENT: staging diff --git a/.github/workflows/patch-build-old.yaml b/.github/workflows/patch-build-old.yaml new file mode 100644 index 000000000..719f2bfdb --- /dev/null +++ b/.github/workflows/patch-build-old.yaml @@ -0,0 +1,185 @@ +# Ref: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions + +on: + workflow_dispatch: + inputs: + services: + description: 'Comma separated names of services to build(in small letters).' + required: true + default: 'chalice,frontend' + tag: + description: 'Tag to build patches from.' + required: true + type: string + +name: Build patches from tag, rewrite commit HEAD to older timestamp, and Push the tag + +jobs: + deploy: + name: Build Patch from old tag + runs-on: ubuntu-latest + env: + DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }} + DEPOT_PROJECT_ID: ${{ secrets.DEPOT_PROJECT_ID }} + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 4 + ref: ${{ github.event.inputs.tag }} + + - name: Set Remote with GITHUB_TOKEN + run: | + git config --unset http.https://github.com/.extraheader + git remote set-url origin https://x-access-token:${{ secrets.ACTIONS_COMMMIT_TOKEN }}@github.com/${{ github.repository }}.git + + - name: Create backup tag with timestamp + run: | + set -e # Exit immediately if a command exits with a non-zero status + TIMESTAMP=$(date +%Y%m%d%H%M%S) + BACKUP_TAG="${{ github.event.inputs.tag }}-backup-${TIMESTAMP}" + echo "BACKUP_TAG=${BACKUP_TAG}" >> $GITHUB_ENV + echo "INPUT_TAG=${{ github.event.inputs.tag }}" >> $GITHUB_ENV + git tag $BACKUP_TAG || { echo "Failed to create backup tag"; exit 1; } + git push origin $BACKUP_TAG || { echo "Failed to push backup tag"; exit 1; } + echo "Created backup tag: $BACKUP_TAG" + + # Get the oldest commit date from the last 3 commits in raw format + OLDEST_COMMIT_TIMESTAMP=$(git log -3 --pretty=format:"%at" | tail -1) + echo "Oldest commit timestamp: $OLDEST_COMMIT_TIMESTAMP" + # Add 1 second to the timestamp + NEW_TIMESTAMP=$((OLDEST_COMMIT_TIMESTAMP + 1)) + echo "NEW_TIMESTAMP=$NEW_TIMESTAMP" >> $GITHUB_ENV + + + - name: Setup yq + uses: mikefarah/yq@master + + # Configure AWS credentials for the first registry + - name: Configure AWS credentials for RELEASE_ARM_REGISTRY + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_DEPOT_ACCESS_KEY }} + aws-secret-access-key: ${{ secrets.AWS_DEPOT_SECRET_KEY }} + aws-region: ${{ secrets.AWS_DEPOT_DEFAULT_REGION }} + + - name: Login to Amazon ECR for RELEASE_ARM_REGISTRY + id: login-ecr-arm + run: | + aws ecr get-login-password --region ${{ secrets.AWS_DEPOT_DEFAULT_REGION }} | docker login --username AWS --password-stdin ${{ secrets.RELEASE_ARM_REGISTRY }} + aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin ${{ secrets.RELEASE_OSS_REGISTRY }} + + - uses: depot/setup-action@v1 + - name: Get HEAD Commit ID + run: echo "HEAD_COMMIT_ID=$(git rev-parse HEAD)" >> $GITHUB_ENV + - name: Define Branch Name + run: echo "BRANCH_NAME=patch/main/${HEAD_COMMIT_ID}" >> $GITHUB_ENV + + - name: Build + id: build-image + env: + DOCKER_REPO_ARM: ${{ secrets.RELEASE_ARM_REGISTRY }} + DOCKER_REPO_OSS: ${{ secrets.RELEASE_OSS_REGISTRY }} + MSAAS_REPO_CLONE_TOKEN: ${{ secrets.MSAAS_REPO_CLONE_TOKEN }} + MSAAS_REPO_URL: ${{ secrets.MSAAS_REPO_URL }} + MSAAS_REPO_FOLDER: /tmp/msaas + run: | + set -exo pipefail + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git checkout -b $BRANCH_NAME + working_dir=$(pwd) + function image_version(){ + local service=$1 + chart_path="$working_dir/scripts/helmcharts/openreplay/charts/$service/Chart.yaml" + current_version=$(yq eval '.AppVersion' $chart_path) + new_version=$(echo $current_version | awk -F. '{$NF += 1 ; print $1"."$2"."$3}') + echo $new_version + # yq eval ".AppVersion = \"$new_version\"" -i $chart_path + } + function clone_msaas() { + [ -d $MSAAS_REPO_FOLDER ] || { + git clone -b $INPUT_TAG --recursive https://x-access-token:$MSAAS_REPO_CLONE_TOKEN@$MSAAS_REPO_URL $MSAAS_REPO_FOLDER + cd $MSAAS_REPO_FOLDER + cd openreplay && git fetch origin && git checkout $INPUT_TAG + git log -1 + cd $MSAAS_REPO_FOLDER + bash git-init.sh + git checkout + } + } + function build_managed() { + local service=$1 + local version=$2 + echo building managed + clone_msaas + if [[ $service == 'chalice' ]]; then + cd $MSAAS_REPO_FOLDER/openreplay/api + else + cd $MSAAS_REPO_FOLDER/openreplay/$service + fi + IMAGE_TAG=$version DOCKER_RUNTIME="depot" DOCKER_BUILD_ARGS="--push" ARCH=arm64 DOCKER_REPO=$DOCKER_REPO_ARM PUSH_IMAGE=0 bash build.sh >> /tmp/arm.txt + } + # Checking for backend images + ls backend/cmd >> /tmp/backend.txt + echo Services: "${{ github.event.inputs.services }}" + IFS=',' read -ra SERVICES <<< "${{ github.event.inputs.services }}" + BUILD_SCRIPT_NAME="build.sh" + # Build FOSS + for SERVICE in "${SERVICES[@]}"; do + # Check if service is backend + if grep -q $SERVICE /tmp/backend.txt; then + cd backend + foss_build_args="nil $SERVICE" + ee_build_args="ee $SERVICE" + else + [[ $SERVICE == 'chalice' || $SERVICE == 'alerts' || $SERVICE == 'crons' ]] && cd $working_dir/api || cd $SERVICE + [[ $SERVICE == 'alerts' || $SERVICE == 'crons' ]] && BUILD_SCRIPT_NAME="build_${SERVICE}.sh" + ee_build_args="ee" + fi + version=$(image_version $SERVICE) + echo IMAGE_TAG=$version DOCKER_RUNTIME="depot" DOCKER_BUILD_ARGS="--push" ARCH=amd64 DOCKER_REPO=$DOCKER_REPO_OSS PUSH_IMAGE=0 bash ${BUILD_SCRIPT_NAME} $foss_build_args + IMAGE_TAG=$version DOCKER_RUNTIME="depot" DOCKER_BUILD_ARGS="--push" ARCH=amd64 DOCKER_REPO=$DOCKER_REPO_OSS PUSH_IMAGE=0 bash ${BUILD_SCRIPT_NAME} $foss_build_args + echo IMAGE_TAG=$version-ee DOCKER_RUNTIME="depot" DOCKER_BUILD_ARGS="--push" ARCH=amd64 DOCKER_REPO=$DOCKER_REPO_OSS PUSH_IMAGE=0 bash ${BUILD_SCRIPT_NAME} $ee_build_args + IMAGE_TAG=$version-ee DOCKER_RUNTIME="depot" DOCKER_BUILD_ARGS="--push" ARCH=amd64 DOCKER_REPO=$DOCKER_REPO_OSS PUSH_IMAGE=0 bash ${BUILD_SCRIPT_NAME} $ee_build_args + if [[ "$SERVICE" != "chalice" && "$SERVICE" != "frontend" ]]; then + IMAGE_TAG=$version DOCKER_RUNTIME="depot" DOCKER_BUILD_ARGS="--push" ARCH=arm64 DOCKER_REPO=$DOCKER_REPO_ARM PUSH_IMAGE=0 bash ${BUILD_SCRIPT_NAME} $foss_build_args + echo IMAGE_TAG=$version DOCKER_RUNTIME="depot" DOCKER_BUILD_ARGS="--push" ARCH=arm64 DOCKER_REPO=$DOCKER_REPO_ARM PUSH_IMAGE=0 bash ${BUILD_SCRIPT_NAME} $foss_build_args + else + build_managed $SERVICE $version + fi + cd $working_dir + chart_path="$working_dir/scripts/helmcharts/openreplay/charts/$SERVICE/Chart.yaml" + yq eval ".AppVersion = \"$version\"" -i $chart_path + git add $chart_path + git commit -m "Increment $SERVICE chart version" + done + + - name: Change commit timestamp + run: | + # Convert the timestamp to a date format git can understand + NEW_DATE=$(perl -le 'print scalar gmtime($ARGV[0])." +0000"' $NEW_TIMESTAMP) + echo "Setting commit date to: $NEW_DATE" + + # Amend the commit with the new date + GIT_COMMITTER_DATE="$NEW_DATE" git commit --amend --no-edit --date="$NEW_DATE" + + # Verify the change + git log -1 --pretty=format:"Commit now dated: %cD" + + # git tag and push + git tag $INPUT_TAG -f + git push origin $INPUT_TAG -f + + + # - name: Debug Job + # if: ${{ failure() }} + # uses: mxschmitt/action-tmate@v3 + # env: + # DOCKER_REPO_ARM: ${{ secrets.RELEASE_ARM_REGISTRY }} + # DOCKER_REPO_OSS: ${{ secrets.RELEASE_OSS_REGISTRY }} + # MSAAS_REPO_CLONE_TOKEN: ${{ secrets.MSAAS_REPO_CLONE_TOKEN }} + # MSAAS_REPO_URL: ${{ secrets.MSAAS_REPO_URL }} + # MSAAS_REPO_FOLDER: /tmp/msaas + # with: + # limit-access-to-actor: true diff --git a/.github/workflows/tracker-tests.yaml b/.github/workflows/tracker-tests.yaml index 084ea8a94..6a7edca57 100644 --- a/.github/workflows/tracker-tests.yaml +++ b/.github/workflows/tracker-tests.yaml @@ -22,22 +22,14 @@ jobs: - name: Cache tracker modules uses: actions/cache@v3 with: - path: tracker/tracker/node_modules - key: ${{ runner.OS }}-test_tracker_build-${{ hashFiles('**/bun.lockb') }} - restore-keys: | - test_tracker_build{{ runner.OS }}-build- - test_tracker_build{{ runner.OS }}- - - name: Cache tracker-assist modules - uses: actions/cache@v3 - with: - path: tracker/tracker-assist/node_modules - key: ${{ runner.OS }}-test_tracker_build-${{ hashFiles('**/bun.lockb') }} + path: tracker/node_modules + key: ${{ runner.OS }}-test_tracker_build-${{ hashFiles('**/bun.lock') }} restore-keys: | test_tracker_build{{ runner.OS }}-build- test_tracker_build{{ runner.OS }}- - name: Setup Testing packages run: | - cd tracker/tracker + cd tracker bun install - name: Jest tests run: | @@ -47,10 +39,6 @@ jobs: run: | cd tracker/tracker bun run build - - name: (TA) Setup Testing packages - run: | - cd tracker/tracker-assist - bun install - name: (TA) Jest tests run: | cd tracker/tracker-assist diff --git a/.gitignore b/.gitignore index e363c25f0..f2ffa7a2c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ node_modules **/*.envrc .idea *.mob* +install-state.gz diff --git a/api/Pipfile b/api/Pipfile index b47121d50..c20a3c247 100644 --- a/api/Pipfile +++ b/api/Pipfile @@ -6,16 +6,15 @@ name = "pypi" [packages] urllib3 = "==2.3.0" requests = "==2.32.3" -boto3 = "==1.36.12" +boto3 = "==1.37.21" pyjwt = "==2.10.1" psycopg2-binary = "==2.9.10" -psycopg = {extras = ["pool", "binary"], version = "==3.2.4"} -clickhouse-driver = {extras = ["lz4"], version = "==0.2.9"} +psycopg = {extras = ["pool", "binary"], version = "==3.2.6"} clickhouse-connect = "==0.8.15" -elasticsearch = "==8.17.1" +elasticsearch = "==8.17.2" jira = "==3.8.0" -cachetools = "==5.5.1" -fastapi = "==0.115.8" +cachetools = "==5.5.2" +fastapi = "==0.115.12" uvicorn = {extras = ["standard"], version = "==0.34.0"} python-decouple = "==3.8" pydantic = {extras = ["email"], version = "==2.10.6"} diff --git a/api/app.py b/api/app.py index d7e5215a5..491a2f533 100644 --- a/api/app.py +++ b/api/app.py @@ -16,7 +16,7 @@ from chalicelib.utils import helper from chalicelib.utils import pg_client, ch_client from crons import core_crons, core_dynamic_crons from routers import core, core_dynamic -from routers.subs import insights, metrics, v1_api, health, usability_tests, spot, product_anaytics +from routers.subs import insights, metrics, v1_api, health, usability_tests, spot, product_analytics loglevel = config("LOGLEVEL", default=logging.WARNING) print(f">Loglevel set to: {loglevel}") @@ -129,6 +129,6 @@ app.include_router(spot.public_app) app.include_router(spot.app) app.include_router(spot.app_apikey) -app.include_router(product_anaytics.public_app) -app.include_router(product_anaytics.app) -app.include_router(product_anaytics.app_apikey) +app.include_router(product_analytics.public_app, prefix="/pa") +app.include_router(product_analytics.app, prefix="/pa") +app.include_router(product_analytics.app_apikey, prefix="/pa") diff --git a/api/chalicelib/core/metadata.py b/api/chalicelib/core/metadata.py index e761ab4f4..1e7fcea00 100644 --- a/api/chalicelib/core/metadata.py +++ b/api/chalicelib/core/metadata.py @@ -241,3 +241,25 @@ def get_colname_by_key(project_id, key): return None return index_to_colname(meta_keys[key]) + + +def get_for_filters(project_id): + with pg_client.PostgresClient() as cur: + query = cur.mogrify(f"""SELECT {",".join(column_names())} + FROM public.projects + WHERE project_id = %(project_id)s + AND deleted_at ISNULL + LIMIT 1;""", {"project_id": project_id}) + cur.execute(query=query) + metas = cur.fetchone() + results = [] + if metas is not None: + for i, k in enumerate(metas.keys()): + if metas[k] is not None: + results.append({"id": f"meta_{i}", + "name": k, + "displayName": metas[k], + "possibleTypes": ["String"], + "autoCaptured": False, + "icon": None}) + return {"total": len(results), "list": results} diff --git a/api/chalicelib/core/metrics/funnels.py b/api/chalicelib/core/metrics/funnels.py index 40643f8d1..4969403ea 100644 --- a/api/chalicelib/core/metrics/funnels.py +++ b/api/chalicelib/core/metrics/funnels.py @@ -6,7 +6,7 @@ from chalicelib.utils import helper from chalicelib.utils import sql_helper as sh -def filter_stages(stages: List[schemas.SessionSearchEventSchema2]): +def filter_stages(stages: List[schemas.SessionSearchEventSchema]): ALLOW_TYPES = [schemas.EventType.CLICK, schemas.EventType.INPUT, schemas.EventType.LOCATION, schemas.EventType.CUSTOM, schemas.EventType.CLICK_MOBILE, schemas.EventType.INPUT_MOBILE, @@ -15,10 +15,10 @@ def filter_stages(stages: List[schemas.SessionSearchEventSchema2]): def __parse_events(f_events: List[dict]): - return [schemas.SessionSearchEventSchema2.parse_obj(e) for e in f_events] + return [schemas.SessionSearchEventSchema.parse_obj(e) for e in f_events] -def __fix_stages(f_events: List[schemas.SessionSearchEventSchema2]): +def __fix_stages(f_events: List[schemas.SessionSearchEventSchema]): if f_events is None: return events = [] diff --git a/api/chalicelib/core/metrics/heatmaps/heatmaps.py b/api/chalicelib/core/metrics/heatmaps/heatmaps.py index 092b908ab..ab49783a4 100644 --- a/api/chalicelib/core/metrics/heatmaps/heatmaps.py +++ b/api/chalicelib/core/metrics/heatmaps/heatmaps.py @@ -160,7 +160,7 @@ s.start_ts, s.duration""" -def __get_1_url(location_condition: schemas.SessionSearchEventSchema2 | None, session_id: str, project_id: int, +def __get_1_url(location_condition: schemas.SessionSearchEventSchema | None, session_id: str, project_id: int, start_time: int, end_time: int) -> str | None: full_args = { @@ -240,13 +240,13 @@ def search_short_session(data: schemas.HeatMapSessionsSearch, project_id, user_i value=[schemas.PlatformType.DESKTOP], operator=schemas.SearchEventOperator.IS)) if not location_condition: - data.events.append(schemas.SessionSearchEventSchema2(type=schemas.EventType.LOCATION, - value=[], - operator=schemas.SearchEventOperator.IS_ANY)) + data.events.append(schemas.SessionSearchEventSchema(type=schemas.EventType.LOCATION, + value=[], + operator=schemas.SearchEventOperator.IS_ANY)) if no_click: - data.events.append(schemas.SessionSearchEventSchema2(type=schemas.EventType.CLICK, - value=[], - operator=schemas.SearchEventOperator.IS_ANY)) + data.events.append(schemas.SessionSearchEventSchema(type=schemas.EventType.CLICK, + value=[], + operator=schemas.SearchEventOperator.IS_ANY)) data.filters.append(schemas.SessionSearchFilterSchema(type=schemas.FilterType.EVENTS_COUNT, value=[0], diff --git a/api/chalicelib/core/metrics/heatmaps/heatmaps_ch.py b/api/chalicelib/core/metrics/heatmaps/heatmaps_ch.py index 93afb78cc..519f6ddcb 100644 --- a/api/chalicelib/core/metrics/heatmaps/heatmaps_ch.py +++ b/api/chalicelib/core/metrics/heatmaps/heatmaps_ch.py @@ -24,8 +24,9 @@ def get_by_url(project_id, data: schemas.GetHeatMapPayloadSchema): "main_events.`$event_name` = 'CLICK'", "isNotNull(JSON_VALUE(CAST(main_events.`$properties` AS String), '$.normalized_x'))" ] - - if data.operator == schemas.SearchEventOperator.IS: + if data.operator == schemas.SearchEventOperator.PATTERN: + constraints.append("match(main_events.`$properties`.url_path'.:String,%(url)s)") + elif data.operator == schemas.SearchEventOperator.IS: constraints.append("JSON_VALUE(CAST(main_events.`$properties` AS String), '$.url_path') = %(url)s") else: constraints.append("JSON_VALUE(CAST(main_events.`$properties` AS String), '$.url_path') ILIKE %(url)s") @@ -179,7 +180,7 @@ toUnixTimestamp(s.datetime)*1000 AS start_ts, s.duration AS duration""" -def __get_1_url(location_condition: schemas.SessionSearchEventSchema2 | None, session_id: str, project_id: int, +def __get_1_url(location_condition: schemas.SessionSearchEventSchema | None, session_id: str, project_id: int, start_time: int, end_time: int) -> str | None: full_args = { @@ -262,13 +263,13 @@ def search_short_session(data: schemas.HeatMapSessionsSearch, project_id, user_i value=[schemas.PlatformType.DESKTOP], operator=schemas.SearchEventOperator.IS)) if not location_condition: - data.events.append(schemas.SessionSearchEventSchema2(type=schemas.EventType.LOCATION, - value=[], - operator=schemas.SearchEventOperator.IS_ANY)) + data.events.append(schemas.SessionSearchEventSchema(type=schemas.EventType.LOCATION, + value=[], + operator=schemas.SearchEventOperator.IS_ANY)) if no_click: - data.events.append(schemas.SessionSearchEventSchema2(type=schemas.EventType.CLICK, - value=[], - operator=schemas.SearchEventOperator.IS_ANY)) + data.events.append(schemas.SessionSearchEventSchema(type=schemas.EventType.CLICK, + value=[], + operator=schemas.SearchEventOperator.IS_ANY)) data.filters.append(schemas.SessionSearchFilterSchema(type=schemas.FilterType.EVENTS_COUNT, value=[0], diff --git a/api/chalicelib/core/metrics/modules/significance/significance.py b/api/chalicelib/core/metrics/modules/significance/significance.py index 48162836d..38815c806 100644 --- a/api/chalicelib/core/metrics/modules/significance/significance.py +++ b/api/chalicelib/core/metrics/modules/significance/significance.py @@ -241,7 +241,7 @@ def get_simple_funnel(filter_d: schemas.CardSeriesFilterSchema, project: schemas :return: """ - stages: List[schemas.SessionSearchEventSchema2] = filter_d.events + stages: List[schemas.SessionSearchEventSchema] = filter_d.events filters: List[schemas.SessionSearchFilterSchema] = filter_d.filters stage_constraints = ["main.timestamp <= %(endTimestamp)s"] diff --git a/api/chalicelib/core/metrics/modules/significance/significance_ch.py b/api/chalicelib/core/metrics/modules/significance/significance_ch.py index c547f9a6b..0cceaf928 100644 --- a/api/chalicelib/core/metrics/modules/significance/significance_ch.py +++ b/api/chalicelib/core/metrics/modules/significance/significance_ch.py @@ -15,7 +15,7 @@ logger = logging.getLogger(__name__) def get_simple_funnel(filter_d: schemas.CardSeriesFilterSchema, project: schemas.ProjectContext, metric_format: schemas.MetricExtendedFormatType) -> List[RealDictRow]: - stages: List[schemas.SessionSearchEventSchema2] = filter_d.events + stages: List[schemas.SessionSearchEventSchema] = filter_d.events filters: List[schemas.SessionSearchFilterSchema] = filter_d.filters platform = project.platform constraints = ["e.project_id = %(project_id)s", diff --git a/api/chalicelib/core/metrics/product_anaytics2.py b/api/chalicelib/core/metrics/product_anaytics2.py deleted file mode 100644 index 9e32e088d..000000000 --- a/api/chalicelib/core/metrics/product_anaytics2.py +++ /dev/null @@ -1,14 +0,0 @@ -from chalicelib.utils.ch_client import ClickHouseClient - - -def search_events(project_id: int, data: dict): - with ClickHouseClient() as ch_client: - r = ch_client.format( - """SELECT * - FROM taha.events - WHERE project_id=%(project_id)s - ORDER BY created_at;""", - params={"project_id": project_id}) - x = ch_client.execute(r) - - return x diff --git a/api/chalicelib/core/product_analytics/__init__.py b/api/chalicelib/core/product_analytics/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/api/chalicelib/core/product_analytics/events.py b/api/chalicelib/core/product_analytics/events.py new file mode 100644 index 000000000..41363e7c6 --- /dev/null +++ b/api/chalicelib/core/product_analytics/events.py @@ -0,0 +1,139 @@ +import logging + +import schemas +from chalicelib.utils import helper +from chalicelib.utils import sql_helper as sh +from chalicelib.utils.ch_client import ClickHouseClient +from chalicelib.utils.exp_ch_helper import get_sub_condition + +logger = logging.getLogger(__name__) + + +def get_events(project_id: int, page: schemas.PaginatedSchema): + with ClickHouseClient() as ch_client: + r = ch_client.format( + """SELECT DISTINCT ON(event_name,auto_captured) + COUNT(1) OVER () AS total, + event_name AS name, display_name, description, + auto_captured + FROM product_analytics.all_events + WHERE project_id=%(project_id)s + ORDER BY auto_captured,display_name + LIMIT %(limit)s OFFSET %(offset)s;""", + parameters={"project_id": project_id, "limit": page.limit, "offset": (page.page - 1) * page.limit}) + rows = ch_client.execute(r) + if len(rows) == 0: + return {"total": 0, "list": []} + total = rows[0]["total"] + for i, row in enumerate(rows): + row["id"] = f"event_{i}" + row["icon"] = None + row["possibleTypes"] = ["string"] + row.pop("total") + return {"total": total, "list": helper.list_to_camel_case(rows)} + + +def search_events(project_id: int, data: schemas.EventsSearchPayloadSchema): + with ClickHouseClient() as ch_client: + full_args = {"project_id": project_id, "startDate": data.startTimestamp, "endDate": data.endTimestamp, + "projectId": project_id, "limit": data.limit, "offset": (data.page - 1) * data.limit} + + constraints = ["project_id = %(projectId)s", + "created_at >= toDateTime(%(startDate)s/1000)", + "created_at <= toDateTime(%(endDate)s/1000)"] + ev_constraints = [] + for i, f in enumerate(data.filters): + if not f.is_event: + f.value = helper.values_for_operator(value=f.value, op=f.operator) + f_k = f"f_value{i}" + full_args = {**full_args, f_k: sh.single_value(f.value), **sh.multi_values(f.value, value_key=f_k)} + is_any = sh.isAny_opreator(f.operator) + is_undefined = sh.isUndefined_operator(f.operator) + full_args = {**full_args, f_k: sh.single_value(f.value), **sh.multi_values(f.value, value_key=f_k)} + if f.is_predefined: + column = f.name + else: + column = f"properties.{f.name}" + + if is_any: + condition = f"notEmpty{column})" + elif is_undefined: + condition = f"empty({column})" + else: + condition = sh.multi_conditions( + get_sub_condition(col_name=column, val_name=f_k, operator=f.operator), + values=f.value, value_key=f_k) + constraints.append(condition) + + else: + e_k = f"e_value{i}" + full_args = {**full_args, e_k: f.name} + condition = f"`$event_name` = %({e_k})s" + sub_conditions = [] + for j, ef in enumerate(f.properties.filters): + p_k = f"e_{i}_p_{j}" + full_args = {**full_args, **sh.multi_values(ef.value, value_key=p_k)} + if ef.is_predefined: + sub_condition = get_sub_condition(col_name=ef.name, val_name=p_k, operator=ef.operator) + else: + sub_condition = get_sub_condition(col_name=f"properties.{ef.name}", + val_name=p_k, operator=ef.operator) + sub_conditions.append(sh.multi_conditions(sub_condition, ef.value, value_key=p_k)) + if len(sub_conditions) > 0: + condition += " AND (" + (" " + f.properties.operator + " ").join(sub_conditions) + ")" + + ev_constraints.append(condition) + + constraints.append("(" + " OR ".join(ev_constraints) + ")") + query = ch_client.format( + f"""SELECT COUNT(1) OVER () AS total, + event_id, + `$event_name`, + created_at, + `distinct_id`, + `$browser`, + `$import`, + `$os`, + `$country`, + `$state`, + `$city`, + `$screen_height`, + `$screen_width`, + `$source`, + `$user_id`, + `$device` + FROM product_analytics.events + WHERE {" AND ".join(constraints)} + ORDER BY created_at + LIMIT %(limit)s OFFSET %(offset)s;""", + parameters=full_args) + rows = ch_client.execute(query) + if len(rows) == 0: + return {"total": 0, "rows": [], "src": 2} + total = rows[0]["total"] + for r in rows: + r.pop("total") + return {"total": total, "rows": rows, "src": 2} + + +def get_lexicon(project_id: int, page: schemas.PaginatedSchema): + with ClickHouseClient() as ch_client: + r = ch_client.format( + """SELECT COUNT(1) OVER () AS total, + all_events.event_name AS name, + * + FROM product_analytics.all_events + WHERE project_id=%(project_id)s + ORDER BY display_name + LIMIT %(limit)s OFFSET %(offset)s;""", + parameters={"project_id": project_id, "limit": page.limit, "offset": (page.page - 1) * page.limit}) + rows = ch_client.execute(r) + if len(rows) == 0: + return {"total": 0, "list": []} + total = rows[0]["total"] + for i, row in enumerate(rows): + row["id"] = f"event_{i}" + row["icon"] = None + row["possibleTypes"] = ["string"] + row.pop("total") + return {"total": total, "list": helper.list_to_camel_case(rows)} diff --git a/api/chalicelib/core/product_analytics/properties.py b/api/chalicelib/core/product_analytics/properties.py new file mode 100644 index 000000000..dcdfc954a --- /dev/null +++ b/api/chalicelib/core/product_analytics/properties.py @@ -0,0 +1,83 @@ +from chalicelib.utils import helper, exp_ch_helper +from chalicelib.utils.ch_client import ClickHouseClient +import schemas + + +def get_all_properties(project_id: int, page: schemas.PaginatedSchema): + with ClickHouseClient() as ch_client: + r = ch_client.format( + """SELECT COUNT(1) OVER () AS total, + property_name AS name, display_name, + array_agg(DISTINCT event_properties.value_type) AS possible_types + FROM product_analytics.all_properties + LEFT JOIN product_analytics.event_properties USING (project_id, property_name) + WHERE all_properties.project_id=%(project_id)s + GROUP BY property_name,display_name + ORDER BY display_name + LIMIT %(limit)s OFFSET %(offset)s;""", + parameters={"project_id": project_id, + "limit": page.limit, + "offset": (page.page - 1) * page.limit}) + properties = ch_client.execute(r) + if len(properties) == 0: + return {"total": 0, "list": []} + total = properties[0]["total"] + properties = helper.list_to_camel_case(properties) + for i, p in enumerate(properties): + p["id"] = f"prop_{i}" + p["icon"] = None + p["possibleTypes"] = exp_ch_helper.simplify_clickhouse_types(p["possibleTypes"]) + p.pop("total") + return {"total": total, "list": properties} + + +def get_event_properties(project_id: int, event_name): + with ClickHouseClient() as ch_client: + r = ch_client.format( + """SELECT all_properties.property_name, + all_properties.display_name + FROM product_analytics.event_properties + INNER JOIN product_analytics.all_properties USING (property_name) + WHERE event_properties.project_id=%(project_id)s + AND all_properties.project_id=%(project_id)s + AND event_properties.event_name=%(event_name)s + ORDER BY created_at;""", + parameters={"project_id": project_id, "event_name": event_name}) + properties = ch_client.execute(r) + + return helper.list_to_camel_case(properties) + + +def get_lexicon(project_id: int, page: schemas.PaginatedSchema): + with ClickHouseClient() as ch_client: + r = ch_client.format( + """SELECT COUNT(1) OVER () AS total, + all_properties.property_name AS name, + all_properties.*, + possible_types.values AS possible_types, + possible_values.values AS sample_values + FROM product_analytics.all_properties + LEFT JOIN (SELECT project_id, property_name, array_agg(DISTINCT value_type) AS values + FROM product_analytics.event_properties + WHERE project_id=%(project_id)s + GROUP BY 1, 2) AS possible_types + USING (project_id, property_name) + LEFT JOIN (SELECT project_id, property_name, array_agg(DISTINCT value) AS values + FROM product_analytics.property_values_samples + WHERE project_id=%(project_id)s + GROUP BY 1, 2) AS possible_values USING (project_id, property_name) + WHERE project_id=%(project_id)s + ORDER BY display_name + LIMIT %(limit)s OFFSET %(offset)s;""", + parameters={"project_id": project_id, + "limit": page.limit, + "offset": (page.page - 1) * page.limit}) + properties = ch_client.execute(r) + if len(properties) == 0: + return {"total": 0, "list": []} + total = properties[0]["total"] + for i, p in enumerate(properties): + p["id"] = f"prop_{i}" + p["icon"] = None + p.pop("total") + return {"total": total, "list": helper.list_to_camel_case(properties)} diff --git a/api/chalicelib/core/sessions/__init__.py b/api/chalicelib/core/sessions/__init__.py index 5d9ffc497..75797c824 100644 --- a/api/chalicelib/core/sessions/__init__.py +++ b/api/chalicelib/core/sessions/__init__.py @@ -6,8 +6,18 @@ logger = logging.getLogger(__name__) from . import sessions_pg from . import sessions_pg as sessions_legacy from . import sessions_ch +from . import sessions_search_pg +from . import sessions_search_pg as sessions_search_legacy -if config("EXP_METRICS", cast=bool, default=False): +if config("EXP_SESSIONS_SEARCH", cast=bool, default=False): + logger.info(">>> Using experimental sessions search") from . import sessions_ch as sessions + from . import sessions_search_ch as sessions_search else: from . import sessions_pg as sessions + from . import sessions_search_pg as sessions_search + +# if config("EXP_METRICS", cast=bool, default=False): +# from . import sessions_ch as sessions +# else: +# from . import sessions_pg as sessions diff --git a/api/chalicelib/core/sessions/sessions_ch.py b/api/chalicelib/core/sessions/sessions_ch.py index 04503edfe..7ba259b80 100644 --- a/api/chalicelib/core/sessions/sessions_ch.py +++ b/api/chalicelib/core/sessions/sessions_ch.py @@ -6,6 +6,7 @@ from chalicelib.core import events, metadata from . import performance_event, sessions_legacy from chalicelib.utils import pg_client, helper, metrics_helper, ch_client, exp_ch_helper from chalicelib.utils import sql_helper as sh +from chalicelib.utils.exp_ch_helper import get_sub_condition logger = logging.getLogger(__name__) @@ -48,8 +49,8 @@ def search2_series(data: schemas.SessionsSearchPayloadSchema, project_id: int, d query = f"""SELECT gs.generate_series AS timestamp, COALESCE(COUNT(DISTINCT processed_sessions.user_id),0) AS count FROM generate_series(%(startDate)s, %(endDate)s, %(step_size)s) AS gs - LEFT JOIN (SELECT multiIf(s.user_id IS NOT NULL AND s.user_id != '', s.user_id, - s.user_anonymous_id IS NOT NULL AND s.user_anonymous_id != '', + LEFT JOIN (SELECT multiIf(isNotNull(s.user_id) AND notEmpty(s.user_id), s.user_id, + isNotNull(s.user_anonymous_id) AND notEmpty(s.user_anonymous_id), s.user_anonymous_id, toString(s.user_uuid)) AS user_id, s.datetime AS datetime {query_part}) AS processed_sessions ON(TRUE) @@ -148,7 +149,7 @@ def search2_table(data: schemas.SessionsSearchPayloadSchema, project_id: int, de for e in data.events: if e.type == schemas.EventType.LOCATION: if e.operator not in extra_conditions: - extra_conditions[e.operator] = schemas.SessionSearchEventSchema2.model_validate({ + extra_conditions[e.operator] = schemas.SessionSearchEventSchema.model_validate({ "type": e.type, "isEvent": True, "value": [], @@ -173,7 +174,7 @@ def search2_table(data: schemas.SessionsSearchPayloadSchema, project_id: int, de for e in data.events: if e.type == schemas.EventType.REQUEST_DETAILS: if e.operator not in extra_conditions: - extra_conditions[e.operator] = schemas.SessionSearchEventSchema2.model_validate({ + extra_conditions[e.operator] = schemas.SessionSearchEventSchema.model_validate({ "type": e.type, "isEvent": True, "value": [], @@ -253,7 +254,7 @@ def search2_table(data: schemas.SessionsSearchPayloadSchema, project_id: int, de FROM (SELECT s.user_id AS user_id {extra_col} {query_part} WHERE isNotNull(user_id) - AND user_id != '') AS filtred_sessions + AND notEmpty(user_id)) AS filtred_sessions {extra_where} GROUP BY {main_col} ORDER BY total DESC @@ -277,7 +278,7 @@ def search2_table(data: schemas.SessionsSearchPayloadSchema, project_id: int, de return sessions -def __is_valid_event(is_any: bool, event: schemas.SessionSearchEventSchema2): +def __is_valid_event(is_any: bool, event: schemas.SessionSearchEventSchema): return not (not is_any and len(event.value) == 0 and event.type not in [schemas.EventType.REQUEST_DETAILS, schemas.EventType.GRAPHQL] \ or event.type in [schemas.PerformanceEventType.LOCATION_DOM_COMPLETE, @@ -330,7 +331,11 @@ def json_condition(table_alias, json_column, json_key, op, values, value_key, ch extract_func = "JSONExtractFloat" if numeric_type == "float" else "JSONExtractInt" condition = f"{extract_func}(toString({table_alias}.`{json_column}`), '{json_key}') {op} %({value_key})s" else: - condition = f"JSONExtractString(toString({table_alias}.`{json_column}`), '{json_key}') {op} %({value_key})s" + # condition = f"JSONExtractString(toString({table_alias}.`{json_column}`), '{json_key}') {op} %({value_key})s" + condition = get_sub_condition( + col_name=f"JSONExtractString(toString({table_alias}.`{json_column}`), '{json_key}')", + val_name=value_key, operator=op + ) conditions.append(sh.multi_conditions(condition, values, value_key=value_key)) @@ -660,7 +665,8 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu event.value = helper.values_for_operator(value=event.value, op=event.operator) full_args = {**full_args, **sh.multi_values(event.value, value_key=e_k), - **sh.multi_values(event.source, value_key=s_k)} + **sh.multi_values(event.source, value_key=s_k), + e_k: event.value[0] if len(event.value) > 0 else event.value} if event_type == events.EventType.CLICK.ui_type: event_from = event_from % f"{MAIN_EVENTS_TABLE} AS main " @@ -671,24 +677,44 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu events_conditions.append({"type": event_where[-1]}) if not is_any: if schemas.ClickEventExtraOperator.has_value(event.operator): - event_where.append(json_condition( - "main", - "$properties", - "selector", op, event.value, e_k) + # event_where.append(json_condition( + # "main", + # "$properties", + # "selector", op, event.value, e_k) + # ) + event_where.append( + sh.multi_conditions( + get_sub_condition(col_name=f"main.`$properties`.selector", + val_name=e_k, operator=event.operator), + event.value, value_key=e_k) ) events_conditions[-1]["condition"] = event_where[-1] else: if is_not: - event_where.append(json_condition( - "sub", "$properties", _column, op, event.value, e_k - )) + # event_where.append(json_condition( + # "sub", "$properties", _column, op, event.value, e_k + # )) + event_where.append( + sh.multi_conditions( + get_sub_condition(col_name=f"sub.`$properties`.{_column}", + val_name=e_k, operator=event.operator), + event.value, value_key=e_k) + ) events_conditions_not.append( { - "type": f"sub.`$event_name`='{exp_ch_helper.get_event_type(event_type, platform=platform)}'"}) + "type": f"sub.`$event_name`='{exp_ch_helper.get_event_type(event_type, platform=platform)}'" + } + ) events_conditions_not[-1]["condition"] = event_where[-1] else: + # event_where.append( + # json_condition("main", "$properties", _column, op, event.value, e_k) + # ) event_where.append( - json_condition("main", "$properties", _column, op, event.value, e_k) + sh.multi_conditions( + get_sub_condition(col_name=f"main.`$properties`.{_column}", + val_name=e_k, operator=event.operator), + event.value, value_key=e_k) ) events_conditions[-1]["condition"] = event_where[-1] else: @@ -870,12 +896,15 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu events_conditions[-1]["condition"] = [] if not is_any and event.value not in [None, "*", ""]: event_where.append( - sh.multi_conditions(f"(toString(main1.`$properties`.message) {op} %({e_k})s OR toString(main1.`$properties`.name) {op} %({e_k})s)", - event.value, value_key=e_k)) + sh.multi_conditions( + f"(toString(main1.`$properties`.message) {op} %({e_k})s OR toString(main1.`$properties`.name) {op} %({e_k})s)", + event.value, value_key=e_k)) events_conditions[-1]["condition"].append(event_where[-1]) events_extra_join += f" AND {event_where[-1]}" if len(event.source) > 0 and event.source[0] not in [None, "*", ""]: - event_where.append(sh.multi_conditions(f"toString(main1.`$properties`.source) = %({s_k})s", event.source, value_key=s_k)) + event_where.append( + sh.multi_conditions(f"toString(main1.`$properties`.source) = %({s_k})s", event.source, + value_key=s_k)) events_conditions[-1]["condition"].append(event_where[-1]) events_extra_join += f" AND {event_where[-1]}" @@ -1191,8 +1220,35 @@ def search_query_parts_ch(data: schemas.SessionsSearchPayloadSchema, error_statu else: logging.warning(f"undefined GRAPHQL filter: {f.type}") events_conditions[-1]["condition"] = " AND ".join(events_conditions[-1]["condition"]) + elif event_type == schemas.EventType.EVENT: + event_from = event_from % f"{MAIN_EVENTS_TABLE} AS main " + _column = events.EventType.CLICK.column + event_where.append(f"main.`$event_name`=%({e_k})s AND main.session_id>0") + events_conditions.append({"type": event_where[-1], "condition": ""}) + else: continue + if event.properties is not None and len(event.properties.filters) > 0: + sub_conditions = [] + for l, property in enumerate(event.properties.filters): + a_k = f"{e_k}_att_{l}" + full_args = {**full_args, + **sh.multi_values(property.value, value_key=a_k)} + + if property.is_predefined: + condition = get_sub_condition(col_name=f"main.{property.name}", + val_name=a_k, operator=property.operator) + else: + condition = get_sub_condition(col_name=f"main.properties.{property.name}", + val_name=a_k, operator=property.operator) + event_where.append( + sh.multi_conditions(condition, property.value, value_key=a_k) + ) + sub_conditions.append(event_where[-1]) + if len(sub_conditions) > 0: + sub_conditions = (" " + event.properties.operator + " ").join(sub_conditions) + events_conditions[-1]["condition"] += " AND " if len(events_conditions[-1]["condition"]) > 0 else "" + events_conditions[-1]["condition"] += "(" + sub_conditions + ")" if event_index == 0 or or_events: event_where += ss_constraints if is_not: diff --git a/ee/api/chalicelib/core/sessions/sessions_legacy_mobil.py b/api/chalicelib/core/sessions/sessions_legacy_mobil.py similarity index 99% rename from ee/api/chalicelib/core/sessions/sessions_legacy_mobil.py rename to api/chalicelib/core/sessions/sessions_legacy_mobil.py index 84b3187ed..69044812c 100644 --- a/ee/api/chalicelib/core/sessions/sessions_legacy_mobil.py +++ b/api/chalicelib/core/sessions/sessions_legacy_mobil.py @@ -1,6 +1,5 @@ import ast import logging -from typing import List, Union import schemas from chalicelib.core import events, metadata, projects @@ -219,7 +218,7 @@ def search_sessions(data: schemas.SessionsSearchPayloadSchema, project_id, user_ } -def __is_valid_event(is_any: bool, event: schemas.SessionSearchEventSchema2): +def __is_valid_event(is_any: bool, event: schemas.SessionSearchEventSchema): return not (not is_any and len(event.value) == 0 and event.type not in [schemas.EventType.REQUEST_DETAILS, schemas.EventType.GRAPHQL] \ or event.type in [schemas.PerformanceEventType.LOCATION_DOM_COMPLETE, diff --git a/api/chalicelib/core/sessions/sessions_pg.py b/api/chalicelib/core/sessions/sessions_pg.py index ba549d55f..3032affcb 100644 --- a/api/chalicelib/core/sessions/sessions_pg.py +++ b/api/chalicelib/core/sessions/sessions_pg.py @@ -143,7 +143,7 @@ def search2_table(data: schemas.SessionsSearchPayloadSchema, project_id: int, de for e in data.events: if e.type == schemas.EventType.LOCATION: if e.operator not in extra_conditions: - extra_conditions[e.operator] = schemas.SessionSearchEventSchema2.model_validate({ + extra_conditions[e.operator] = schemas.SessionSearchEventSchema.model_validate({ "type": e.type, "isEvent": True, "value": [], @@ -160,7 +160,7 @@ def search2_table(data: schemas.SessionsSearchPayloadSchema, project_id: int, de for e in data.events: if e.type == schemas.EventType.REQUEST_DETAILS: if e.operator not in extra_conditions: - extra_conditions[e.operator] = schemas.SessionSearchEventSchema2.model_validate({ + extra_conditions[e.operator] = schemas.SessionSearchEventSchema.model_validate({ "type": e.type, "isEvent": True, "value": [], @@ -273,7 +273,7 @@ def search2_table(data: schemas.SessionsSearchPayloadSchema, project_id: int, de return sessions -def __is_valid_event(is_any: bool, event: schemas.SessionSearchEventSchema2): +def __is_valid_event(is_any: bool, event: schemas.SessionSearchEventSchema): return not (not is_any and len(event.value) == 0 and event.type not in [schemas.EventType.REQUEST_DETAILS, schemas.EventType.GRAPHQL] \ or event.type in [schemas.PerformanceEventType.LOCATION_DOM_COMPLETE, diff --git a/ee/api/chalicelib/core/sessions/sessions_search_exp.py b/api/chalicelib/core/sessions/sessions_search_ch.py similarity index 98% rename from ee/api/chalicelib/core/sessions/sessions_search_exp.py rename to api/chalicelib/core/sessions/sessions_search_ch.py index 234d0fd26..24d03f62c 100644 --- a/ee/api/chalicelib/core/sessions/sessions_search_exp.py +++ b/api/chalicelib/core/sessions/sessions_search_ch.py @@ -175,11 +175,11 @@ def search_sessions(data: schemas.SessionsSearchPayloadSchema, project: schemas. ORDER BY sort_key {data.order} LIMIT %(sessions_limit)s OFFSET %(sessions_limit_s)s) AS sorted_sessions;""", parameters=full_args) - logging.debug("--------------------") - logging.debug(main_query) - logging.debug("--------------------") + try: + logging.debug("--------------------") sessions_list = cur.execute(main_query) + logging.debug("--------------------") except Exception as err: logging.warning("--------- SESSIONS-CH SEARCH QUERY EXCEPTION -----------") logging.warning(main_query) @@ -262,7 +262,7 @@ def search_by_metadata(tenant_id, user_id, m_key, m_value, project_id=None): FROM public.user_favorite_sessions WHERE user_favorite_sessions.user_id = %(userId)s ) AS favorite_sessions USING (session_id) - WHERE s.project_id = %(id)s AND s.duration IS NOT NULL AND s.{col_name} = %(value)s + WHERE s.project_id = %(id)s AND isNotNull(s.duration) AND s.{col_name} = %(value)s ) AS full_sessions ORDER BY favorite DESC, issue_score DESC LIMIT 10 diff --git a/api/chalicelib/core/sessions/sessions_search.py b/api/chalicelib/core/sessions/sessions_search_pg.py similarity index 100% rename from api/chalicelib/core/sessions/sessions_search.py rename to api/chalicelib/core/sessions/sessions_search_pg.py diff --git a/api/chalicelib/utils/__init__.py b/api/chalicelib/utils/__init__.py index 54e0b4c65..df64e4775 100644 --- a/api/chalicelib/utils/__init__.py +++ b/api/chalicelib/utils/__init__.py @@ -11,9 +11,3 @@ if smtp.has_smtp(): logger.info("valid SMTP configuration found") else: logger.info("no SMTP configuration found or SMTP validation failed") - -if config("EXP_CH_DRIVER", cast=bool, default=True): - logging.info(">>> Using new CH driver") - from . import ch_client_exp as ch_client -else: - from . import ch_client diff --git a/api/chalicelib/utils/ch_client.py b/api/chalicelib/utils/ch_client.py index 5fbaa5752..bfc62b919 100644 --- a/api/chalicelib/utils/ch_client.py +++ b/api/chalicelib/utils/ch_client.py @@ -1,73 +1,185 @@ import logging +import threading +import time +from functools import wraps +from queue import Queue, Empty -import clickhouse_driver +import clickhouse_connect +from clickhouse_connect.driver.query import QueryContext from decouple import config logger = logging.getLogger(__name__) +_CH_CONFIG = {"host": config("ch_host"), + "user": config("ch_user", default="default"), + "password": config("ch_password", default=""), + "port": config("ch_port_http", cast=int), + "client_name": config("APP_NAME", default="PY")} +CH_CONFIG = dict(_CH_CONFIG) + settings = {} if config('ch_timeout', cast=int, default=-1) > 0: - logger.info(f"CH-max_execution_time set to {config('ch_timeout')}s") + logging.info(f"CH-max_execution_time set to {config('ch_timeout')}s") settings = {**settings, "max_execution_time": config('ch_timeout', cast=int)} if config('ch_receive_timeout', cast=int, default=-1) > 0: - logger.info(f"CH-receive_timeout set to {config('ch_receive_timeout')}s") + logging.info(f"CH-receive_timeout set to {config('ch_receive_timeout')}s") settings = {**settings, "receive_timeout": config('ch_receive_timeout', cast=int)} +extra_args = {} +if config("CH_COMPRESSION", cast=bool, default=True): + extra_args["compression"] = "lz4" + + +def transform_result(self, original_function): + @wraps(original_function) + def wrapper(*args, **kwargs): + if kwargs.get("parameters"): + if config("LOCAL_DEV", cast=bool, default=False): + logger.debug(self.format(query=kwargs.get("query", ""), parameters=kwargs.get("parameters"))) + else: + logger.debug( + str.encode(self.format(query=kwargs.get("query", ""), parameters=kwargs.get("parameters")))) + elif len(args) > 0: + if config("LOCAL_DEV", cast=bool, default=False): + logger.debug(args[0]) + else: + logger.debug(str.encode(args[0])) + result = original_function(*args, **kwargs) + if isinstance(result, clickhouse_connect.driver.query.QueryResult): + column_names = result.column_names + result = result.result_rows + result = [dict(zip(column_names, row)) for row in result] + + return result + + return wrapper + + +class ClickHouseConnectionPool: + def __init__(self, min_size, max_size): + self.min_size = min_size + self.max_size = max_size + self.pool = Queue() + self.lock = threading.Lock() + self.total_connections = 0 + + # Initialize the pool with min_size connections + for _ in range(self.min_size): + client = clickhouse_connect.get_client(**CH_CONFIG, + database=config("ch_database", default="default"), + settings=settings, + **extra_args) + self.pool.put(client) + self.total_connections += 1 + + def get_connection(self): + try: + # Try to get a connection without blocking + client = self.pool.get_nowait() + return client + except Empty: + with self.lock: + if self.total_connections < self.max_size: + client = clickhouse_connect.get_client(**CH_CONFIG, + database=config("ch_database", default="default"), + settings=settings, + **extra_args) + self.total_connections += 1 + return client + # If max_size reached, wait until a connection is available + client = self.pool.get() + return client + + def release_connection(self, client): + self.pool.put(client) + + def close_all(self): + with self.lock: + while not self.pool.empty(): + client = self.pool.get() + client.close() + self.total_connections = 0 + + +CH_pool: ClickHouseConnectionPool = None + +RETRY_MAX = config("CH_RETRY_MAX", cast=int, default=50) +RETRY_INTERVAL = config("CH_RETRY_INTERVAL", cast=int, default=2) +RETRY = 0 + + +def make_pool(): + if not config('CH_POOL', cast=bool, default=True): + return + global CH_pool + global RETRY + if CH_pool is not None: + try: + CH_pool.close_all() + except Exception as error: + logger.error("Error while closing all connexions to CH", exc_info=error) + try: + CH_pool = ClickHouseConnectionPool(min_size=config("CH_MINCONN", cast=int, default=4), + max_size=config("CH_MAXCONN", cast=int, default=8)) + if CH_pool is not None: + logger.info("Connection pool created successfully for CH") + except ConnectionError as error: + logger.error("Error while connecting to CH", exc_info=error) + if RETRY < RETRY_MAX: + RETRY += 1 + logger.info(f"waiting for {RETRY_INTERVAL}s before retry n°{RETRY}") + time.sleep(RETRY_INTERVAL) + make_pool() + else: + raise error + class ClickHouseClient: __client = None def __init__(self, database=None): - extra_args = {} - if config("CH_COMPRESSION", cast=bool, default=True): - extra_args["compression"] = "lz4" - self.__client = clickhouse_driver.Client(host=config("ch_host"), - database=database if database else config("ch_database", - default="default"), - user=config("ch_user", default="default"), - password=config("ch_password", default=""), - port=config("ch_port", cast=int), - settings=settings, - **extra_args) \ - if self.__client is None else self.__client + if self.__client is None: + if database is not None or not config('CH_POOL', cast=bool, default=True): + self.__client = clickhouse_connect.get_client(**CH_CONFIG, + database=database if database else config("ch_database", + default="default"), + settings=settings, + **extra_args) + + else: + self.__client = CH_pool.get_connection() + + self.__client.execute = transform_result(self, self.__client.query) + self.__client.format = self.format def __enter__(self): - return self - - def execute(self, query, parameters=None, **args): - try: - results = self.__client.execute(query=query, params=parameters, with_column_types=True, **args) - keys = tuple(x for x, y in results[1]) - return [dict(zip(keys, i)) for i in results[0]] - except Exception as err: - logger.error("--------- CH EXCEPTION -----------", exc_info=err) - logger.error("--------- CH QUERY EXCEPTION -----------") - logger.error(self.format(query=query, parameters=parameters) - .replace('\n', '\\n') - .replace(' ', ' ') - .replace(' ', ' ')) - logger.error("--------------------") - raise err - - def insert(self, query, params=None, **args): - return self.__client.execute(query=query, params=params, **args) - - def client(self): return self.__client - def format(self, query, parameters): - if parameters is None: - return query - return self.__client.substitute_params(query, parameters, self.__client.connection.context) + def format(self, query, parameters=None): + if parameters: + ctx = QueryContext(query=query, parameters=parameters) + return ctx.final_query + return query def __exit__(self, *args): - pass + if config('CH_POOL', cast=bool, default=True): + CH_pool.release_connection(self.__client) + else: + self.__client.close() async def init(): - logger.info(f">CH_POOL:not defined") + logger.info(f">use CH_POOL:{config('CH_POOL', default=True)}") + if config('CH_POOL', cast=bool, default=True): + make_pool() async def terminate(): - pass + global CH_pool + if CH_pool is not None: + try: + CH_pool.close_all() + logger.info("Closed all connexions to CH") + except Exception as error: + logger.error("Error while closing all connexions to CH", exc_info=error) diff --git a/api/chalicelib/utils/ch_client_exp.py b/api/chalicelib/utils/ch_client_exp.py deleted file mode 100644 index afed93457..000000000 --- a/api/chalicelib/utils/ch_client_exp.py +++ /dev/null @@ -1,178 +0,0 @@ -import logging -import threading -import time -from functools import wraps -from queue import Queue, Empty - -import clickhouse_connect -from clickhouse_connect.driver.query import QueryContext -from decouple import config - -logger = logging.getLogger(__name__) - -_CH_CONFIG = {"host": config("ch_host"), - "user": config("ch_user", default="default"), - "password": config("ch_password", default=""), - "port": config("ch_port_http", cast=int), - "client_name": config("APP_NAME", default="PY")} -CH_CONFIG = dict(_CH_CONFIG) - -settings = {} -if config('ch_timeout', cast=int, default=-1) > 0: - logging.info(f"CH-max_execution_time set to {config('ch_timeout')}s") - settings = {**settings, "max_execution_time": config('ch_timeout', cast=int)} - -if config('ch_receive_timeout', cast=int, default=-1) > 0: - logging.info(f"CH-receive_timeout set to {config('ch_receive_timeout')}s") - settings = {**settings, "receive_timeout": config('ch_receive_timeout', cast=int)} - -extra_args = {} -if config("CH_COMPRESSION", cast=bool, default=True): - extra_args["compression"] = "lz4" - - -def transform_result(self, original_function): - @wraps(original_function) - def wrapper(*args, **kwargs): - if kwargs.get("parameters"): - logger.debug(str.encode(self.format(query=kwargs.get("query", ""), parameters=kwargs.get("parameters")))) - elif len(args) > 0: - logger.debug(str.encode(args[0])) - result = original_function(*args, **kwargs) - if isinstance(result, clickhouse_connect.driver.query.QueryResult): - column_names = result.column_names - result = result.result_rows - result = [dict(zip(column_names, row)) for row in result] - - return result - - return wrapper - - -class ClickHouseConnectionPool: - def __init__(self, min_size, max_size): - self.min_size = min_size - self.max_size = max_size - self.pool = Queue() - self.lock = threading.Lock() - self.total_connections = 0 - - # Initialize the pool with min_size connections - for _ in range(self.min_size): - client = clickhouse_connect.get_client(**CH_CONFIG, - database=config("ch_database", default="default"), - settings=settings, - **extra_args) - self.pool.put(client) - self.total_connections += 1 - - def get_connection(self): - try: - # Try to get a connection without blocking - client = self.pool.get_nowait() - return client - except Empty: - with self.lock: - if self.total_connections < self.max_size: - client = clickhouse_connect.get_client(**CH_CONFIG, - database=config("ch_database", default="default"), - settings=settings, - **extra_args) - self.total_connections += 1 - return client - # If max_size reached, wait until a connection is available - client = self.pool.get() - return client - - def release_connection(self, client): - self.pool.put(client) - - def close_all(self): - with self.lock: - while not self.pool.empty(): - client = self.pool.get() - client.close() - self.total_connections = 0 - - -CH_pool: ClickHouseConnectionPool = None - -RETRY_MAX = config("CH_RETRY_MAX", cast=int, default=50) -RETRY_INTERVAL = config("CH_RETRY_INTERVAL", cast=int, default=2) -RETRY = 0 - - -def make_pool(): - if not config('CH_POOL', cast=bool, default=True): - return - global CH_pool - global RETRY - if CH_pool is not None: - try: - CH_pool.close_all() - except Exception as error: - logger.error("Error while closing all connexions to CH", exc_info=error) - try: - CH_pool = ClickHouseConnectionPool(min_size=config("CH_MINCONN", cast=int, default=4), - max_size=config("CH_MAXCONN", cast=int, default=8)) - if CH_pool is not None: - logger.info("Connection pool created successfully for CH") - except ConnectionError as error: - logger.error("Error while connecting to CH", exc_info=error) - if RETRY < RETRY_MAX: - RETRY += 1 - logger.info(f"waiting for {RETRY_INTERVAL}s before retry n°{RETRY}") - time.sleep(RETRY_INTERVAL) - make_pool() - else: - raise error - - -class ClickHouseClient: - __client = None - - def __init__(self, database=None): - if self.__client is None: - if database is not None or not config('CH_POOL', cast=bool, default=True): - self.__client = clickhouse_connect.get_client(**CH_CONFIG, - database=database if database else config("ch_database", - default="default"), - settings=settings, - **extra_args) - - else: - self.__client = CH_pool.get_connection() - - self.__client.execute = transform_result(self, self.__client.query) - self.__client.format = self.format - - def __enter__(self): - return self.__client - - def format(self, query, parameters=None): - if parameters: - ctx = QueryContext(query=query, parameters=parameters) - return ctx.final_query - return query - - def __exit__(self, *args): - if config('CH_POOL', cast=bool, default=True): - CH_pool.release_connection(self.__client) - else: - self.__client.close() - - -async def init(): - logger.info(f">use CH_POOL:{config('CH_POOL', default=True)}") - if config('CH_POOL', cast=bool, default=True): - make_pool() - - -async def terminate(): - global CH_pool - if CH_pool is not None: - try: - CH_pool.close_all() - logger.info("Closed all connexions to CH") - except Exception as error: - logger.error("Error while closing all connexions to CH", exc_info=error) diff --git a/api/chalicelib/utils/exp_ch_helper.py b/api/chalicelib/utils/exp_ch_helper.py index 15c4986d8..b2c061533 100644 --- a/api/chalicelib/utils/exp_ch_helper.py +++ b/api/chalicelib/utils/exp_ch_helper.py @@ -1,7 +1,10 @@ +import logging +import re from typing import Union import schemas -import logging +from chalicelib.utils import sql_helper as sh +from schemas import SearchEventOperator logger = logging.getLogger(__name__) @@ -66,3 +69,94 @@ def get_event_type(event_type: Union[schemas.EventType, schemas.PerformanceEvent if event_type not in defs: raise Exception(f"unsupported EventType:{event_type}") return defs.get(event_type) + + +# AI generated +def simplify_clickhouse_type(ch_type: str) -> str: + """ + Simplify a ClickHouse data type name to a broader category like: + int, float, decimal, datetime, string, uuid, enum, array, tuple, map, nested, etc. + """ + + # 1) Strip out common wrappers like Nullable(...) or LowCardinality(...) + # Possibly multiple wrappers: e.g. "LowCardinality(Nullable(Int32))" + pattern_wrappers = re.compile(r'(Nullable|LowCardinality)\((.*)\)') + while True: + match = pattern_wrappers.match(ch_type) + if match: + ch_type = match.group(2) + else: + break + + # 2) Normalize (lowercase) for easier checks + normalized_type = ch_type.lower() + + # 3) Use pattern matching or direct checks for known categories + # (You can adapt this as you see fit for your environment.) + + # Integers: Int8, Int16, Int32, Int64, Int128, Int256, UInt8, UInt16, ... + if re.match(r'^(u?int)(8|16|32|64|128|256)$', normalized_type): + return "int" + + # Floats: Float32, Float64 + if re.match(r'^float(32|64)$', normalized_type): + return "float" + + # Decimal: Decimal(P, S) + if normalized_type.startswith("decimal"): + return "decimal" + + # Date/DateTime + if normalized_type.startswith("date"): + return "datetime" + if normalized_type.startswith("datetime"): + return "datetime" + + # Strings: String, FixedString(N) + if normalized_type.startswith("string"): + return "string" + if normalized_type.startswith("fixedstring"): + return "string" + + # UUID + if normalized_type.startswith("uuid"): + return "uuid" + + # Enums: Enum8(...) or Enum16(...) + if normalized_type.startswith("enum8") or normalized_type.startswith("enum16"): + return "enum" + + # Arrays: Array(T) + if normalized_type.startswith("array"): + return "array" + + # Tuples: Tuple(T1, T2, ...) + if normalized_type.startswith("tuple"): + return "tuple" + + # Map(K, V) + if normalized_type.startswith("map"): + return "map" + + # Nested(...) + if normalized_type.startswith("nested"): + return "nested" + + # If we didn't match above, just return the original type in lowercase + return normalized_type + + +def simplify_clickhouse_types(ch_types: list[str]) -> list[str]: + """ + Takes a list of ClickHouse types and returns a list of simplified types + by calling `simplify_clickhouse_type` on each. + """ + return list(set([simplify_clickhouse_type(t) for t in ch_types])) + + +def get_sub_condition(col_name: str, val_name: str, + operator: Union[schemas.SearchEventOperator, schemas.MathOperator]): + if operator == SearchEventOperator.PATTERN: + return f"match({col_name}, %({val_name})s)" + op = sh.get_sql_operator(operator) + return f"{col_name} {op} %({val_name})s" diff --git a/api/chalicelib/utils/sql_helper.py b/api/chalicelib/utils/sql_helper.py index 1de16c70f..521050634 100644 --- a/api/chalicelib/utils/sql_helper.py +++ b/api/chalicelib/utils/sql_helper.py @@ -14,6 +14,9 @@ def get_sql_operator(op: Union[schemas.SearchEventOperator, schemas.ClickEventEx schemas.SearchEventOperator.NOT_CONTAINS: "NOT ILIKE", schemas.SearchEventOperator.STARTS_WITH: "ILIKE", schemas.SearchEventOperator.ENDS_WITH: "ILIKE", + # this is not used as an operator, it is used in order to maintain a valid value for conditions + schemas.SearchEventOperator.PATTERN: "regex", + # Selector operators: schemas.ClickEventExtraOperator.IS: "=", schemas.ClickEventExtraOperator.IS_NOT: "!=", @@ -72,4 +75,3 @@ def single_value(values): if isinstance(v, Enum): values[i] = v.value return values - diff --git a/api/env.default b/api/env.default index 947e3ad12..383e74273 100644 --- a/api/env.default +++ b/api/env.default @@ -74,4 +74,5 @@ EXP_CH_DRIVER=true EXP_AUTOCOMPLETE=true EXP_ALERTS=true EXP_ERRORS_SEARCH=true -EXP_METRICS=true \ No newline at end of file +EXP_METRICS=true +EXP_SESSIONS_SEARCH=true \ No newline at end of file diff --git a/api/requirements-alerts.txt b/api/requirements-alerts.txt index 62ccaa740..d4cd202c3 100644 --- a/api/requirements-alerts.txt +++ b/api/requirements-alerts.txt @@ -1,16 +1,15 @@ urllib3==2.3.0 requests==2.32.3 -boto3==1.36.12 +boto3==1.37.21 pyjwt==2.10.1 psycopg2-binary==2.9.10 -psycopg[pool,binary]==3.2.4 -clickhouse-driver[lz4]==0.2.9 +psycopg[pool,binary]==3.2.6 clickhouse-connect==0.8.15 -elasticsearch==8.17.1 +elasticsearch==8.17.2 jira==3.8.0 -cachetools==5.5.1 +cachetools==5.5.2 -fastapi==0.115.8 +fastapi==0.115.12 uvicorn[standard]==0.34.0 python-decouple==3.8 pydantic[email]==2.10.6 diff --git a/api/requirements.txt b/api/requirements.txt index 0d65cca6d..dca445128 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -1,16 +1,15 @@ urllib3==2.3.0 requests==2.32.3 -boto3==1.36.12 +boto3==1.37.21 pyjwt==2.10.1 psycopg2-binary==2.9.10 -psycopg[pool,binary]==3.2.4 -clickhouse-driver[lz4]==0.2.9 +psycopg[pool,binary]==3.2.6 clickhouse-connect==0.8.15 -elasticsearch==8.17.1 +elasticsearch==8.17.2 jira==3.8.0 -cachetools==5.5.1 +cachetools==5.5.2 -fastapi==0.115.8 +fastapi==0.115.12 uvicorn[standard]==0.34.0 python-decouple==3.8 pydantic[email]==2.10.6 diff --git a/api/routers/subs/product_analytics.py b/api/routers/subs/product_analytics.py new file mode 100644 index 000000000..5b18ca93e --- /dev/null +++ b/api/routers/subs/product_analytics.py @@ -0,0 +1,55 @@ +from typing import Annotated + +from fastapi import Body, Depends, Query + +import schemas +from chalicelib.core import metadata +from chalicelib.core.product_analytics import events, properties +from or_dependencies import OR_context +from routers.base import get_routers + +public_app, app, app_apikey = get_routers() + + +@app.get('/{projectId}/filters', tags=["product_analytics"]) +def get_all_filters(projectId: int, filter_query: Annotated[schemas.PaginatedSchema, Query()], + context: schemas.CurrentContext = Depends(OR_context)): + return { + "data": { + "events": events.get_events(project_id=projectId, page=filter_query), + "filters": properties.get_all_properties(project_id=projectId, page=filter_query), + "metadata": metadata.get_for_filters(project_id=projectId) + } + } + + +@app.get('/{projectId}/events/names', tags=["product_analytics"]) +def get_all_events(projectId: int, filter_query: Annotated[schemas.PaginatedSchema, Query()], + context: schemas.CurrentContext = Depends(OR_context)): + return {"data": events.get_events(project_id=projectId, page=filter_query)} + + +@app.get('/{projectId}/properties/search', tags=["product_analytics"]) +def get_event_properties(projectId: int, event_name: str = None, + context: schemas.CurrentContext = Depends(OR_context)): + if not event_name or len(event_name) == 0: + return {"data": []} + return {"data": properties.get_event_properties(project_id=projectId, event_name=event_name)} + + +@app.post('/{projectId}/events/search', tags=["product_analytics"]) +def search_events(projectId: int, data: schemas.EventsSearchPayloadSchema = Body(...), + context: schemas.CurrentContext = Depends(OR_context)): + return {"data": events.search_events(project_id=projectId, data=data)} + + +@app.get('/{projectId}/lexicon/events', tags=["product_analytics", "lexicon"]) +def get_all_lexicon_events(projectId: int, filter_query: Annotated[schemas.PaginatedSchema, Query()], + context: schemas.CurrentContext = Depends(OR_context)): + return {"data": events.get_lexicon(project_id=projectId, page=filter_query)} + + +@app.get('/{projectId}/lexicon/properties', tags=["product_analytics", "lexicon"]) +def get_all_lexicon_properties(projectId: int, filter_query: Annotated[schemas.PaginatedSchema, Query()], + context: schemas.CurrentContext = Depends(OR_context)): + return {"data": properties.get_lexicon(project_id=projectId, page=filter_query)} diff --git a/api/routers/subs/product_anaytics.py b/api/routers/subs/product_anaytics.py deleted file mode 100644 index 95851c253..000000000 --- a/api/routers/subs/product_anaytics.py +++ /dev/null @@ -1,15 +0,0 @@ -import schemas -from chalicelib.core.metrics import product_anaytics2 -from fastapi import Depends -from or_dependencies import OR_context -from routers.base import get_routers - - -public_app, app, app_apikey = get_routers() - - -@app.post('/{projectId}/events/search', tags=["dashboard"]) -def search_events(projectId: int, - # data: schemas.CreateDashboardSchema = Body(...), - context: schemas.CurrentContext = Depends(OR_context)): - return product_anaytics2.search_events(project_id=projectId, data={}) diff --git a/api/routers/subs/usability_tests.py b/api/routers/subs/usability_tests.py index 4db0e5817..3dc01c9b0 100644 --- a/api/routers/subs/usability_tests.py +++ b/api/routers/subs/usability_tests.py @@ -1,10 +1,12 @@ -from fastapi import Body, Depends +from typing import Annotated +from fastapi import Body, Depends, Query + +import schemas from chalicelib.core.usability_testing import service from chalicelib.core.usability_testing.schema import UTTestCreate, UTTestUpdate, UTTestSearch from or_dependencies import OR_context from routers.base import get_routers -from schemas import schemas public_app, app, app_apikey = get_routers() tags = ["usability-tests"] @@ -77,9 +79,8 @@ async def update_ut_test(projectId: int, test_id: int, test_update: UTTestUpdate @app.get('/{projectId}/usability-tests/{test_id}/sessions', tags=tags) -async def get_sessions(projectId: int, test_id: int, page: int = 1, limit: int = 10, - live: bool = False, - user_id: str = None): +async def get_sessions(projectId: int, test_id: int, filter_query: Annotated[schemas.PaginatedSchema, Query()], + live: bool = False, user_id: str = None): """ Get sessions related to a specific UT test. @@ -88,20 +89,21 @@ async def get_sessions(projectId: int, test_id: int, page: int = 1, limit: int = """ if live: - return service.ut_tests_sessions_live(projectId, test_id, page, limit) + return service.ut_tests_sessions_live(projectId, test_id, filter_query.page, filter_query.limit) else: - return service.ut_tests_sessions(projectId, test_id, page, limit, user_id, live) + return service.ut_tests_sessions(projectId, test_id, filter_query.page, filter_query.limit, user_id, live) @app.get('/{projectId}/usability-tests/{test_id}/responses/{task_id}', tags=tags) -async def get_responses(projectId: int, test_id: int, task_id: int, page: int = 1, limit: int = 10, query: str = None): +async def get_responses(projectId: int, test_id: int, task_id: int, + filter_query: Annotated[schemas.PaginatedSchema, Query()], query: str = None): """ Get responses related to a specific UT test. - **project_id**: The unique identifier of the project. - **test_id**: The unique identifier of the UT test. """ - return service.get_responses(test_id, task_id, page, limit, query) + return service.get_responses(test_id, task_id, filter_query.page, filter_query.limit, query) @app.get('/{projectId}/usability-tests/{test_id}/statistics', tags=tags) diff --git a/api/schemas/__init__.py b/api/schemas/__init__.py index 6013d7c2b..ffb620095 100644 --- a/api/schemas/__init__.py +++ b/api/schemas/__init__.py @@ -1,2 +1,4 @@ from .schemas import * +from .product_analytics import * from . import overrides as _overrides +from .schemas import _PaginatedSchema as PaginatedSchema diff --git a/api/schemas/product_analytics.py b/api/schemas/product_analytics.py new file mode 100644 index 000000000..9b4a275d1 --- /dev/null +++ b/api/schemas/product_analytics.py @@ -0,0 +1,22 @@ +from typing import Optional, List, Literal, Union, Annotated +from pydantic import Field + +from .overrides import BaseModel +from .schemas import EventPropertiesSchema, SortOrderType, _TimedSchema, \ + _PaginatedSchema, PropertyFilterSchema + + +class EventSearchSchema(BaseModel): + is_event: Literal[True] = True + name: str = Field(...) + properties: Optional[EventPropertiesSchema] = Field(default=None) + + +ProductAnalyticsGroupedFilter = Annotated[Union[EventSearchSchema, PropertyFilterSchema], \ + Field(discriminator='is_event')] + + +class EventsSearchPayloadSchema(_TimedSchema, _PaginatedSchema): + filters: List[ProductAnalyticsGroupedFilter] = Field(...) + sort: str = Field(default="startTs") + order: SortOrderType = Field(default=SortOrderType.DESC) diff --git a/api/schemas/schemas.py b/api/schemas/schemas.py index 579713097..d921fad2b 100644 --- a/api/schemas/schemas.py +++ b/api/schemas/schemas.py @@ -404,6 +404,7 @@ class EventType(str, Enum): REQUEST_MOBILE = "requestMobile" ERROR_MOBILE = "errorMobile" SWIPE_MOBILE = "swipeMobile" + EVENT = "event" class PerformanceEventType(str, Enum): @@ -464,6 +465,7 @@ class SearchEventOperator(str, Enum): NOT_CONTAINS = "notContains" STARTS_WITH = "startsWith" ENDS_WITH = "endsWith" + PATTERN = "regex" class ClickEventExtraOperator(str, Enum): @@ -545,7 +547,66 @@ class RequestGraphqlFilterSchema(BaseModel): return values -class SessionSearchEventSchema2(BaseModel): +class EventPredefinedPropertyType(str, Enum): + TIME = "$time" + SOURCE = "$source" + DURATION_S = "$duration_s" + DESCRIPTION = "description" + AUTO_CAPTURED = "$auto_captured" + SDK_EDITION = "$sdk_edition" + SDK_VERSION = "$sdk_version" + DEVICE_ID = "$device_id" + OS = "$os" + OS_VERSION = "$os_version" + BROWSER = "$browser" + BROWSER_VERSION = "$browser_version" + DEVICE = "$device" + SCREEN_HEIGHT = "$screen_height" + SCREEN_WIDTH = "$screen_width" + CURRENT_URL = "$current_url" + INITIAL_REFERRER = "$initial_referrer" + REFERRING_DOMAIN = "$referring_domain" + REFERRER = "$referrer" + INITIAL_REFERRING_DOMAIN = "$initial_referring_domain" + SEARCH_ENGINE = "$search_engine" + SEARCH_ENGINE_KEYWORD = "$search_engine_keyword" + UTM_SOURCE = "utm_source" + UTM_MEDIUM = "utm_medium" + UTM_CAMPAIGN = "utm_campaign" + COUNTRY = "$country" + STATE = "$state" + CITY = "$city" + ISSUE_TYPE = "issue_type" + TAGS = "$tags" + IMPORT = "$import" + + +class PropertyFilterSchema(BaseModel): + is_event: Literal[False] = False + name: Union[EventPredefinedPropertyType, str] = Field(...) + operator: Union[SearchEventOperator, MathOperator] = Field(...) + value: List[Union[int, str]] = Field(...) + + # property_type: Optional[Literal["string", "number", "date"]] = Field(default=None) + + @computed_field + @property + def is_predefined(self) -> bool: + return EventPredefinedPropertyType.has_value(self.name) + + @model_validator(mode="after") + def transform_name(self): + if isinstance(self.name, Enum): + self.name = self.name.value + return self + + +class EventPropertiesSchema(BaseModel): + operator: Literal["and", "or"] = Field(...) + filters: List[PropertyFilterSchema] = Field(...) + + +class SessionSearchEventSchema(BaseModel): is_event: Literal[True] = True value: List[Union[str, int]] = Field(...) type: Union[EventType, PerformanceEventType] = Field(...) @@ -553,6 +614,7 @@ class SessionSearchEventSchema2(BaseModel): source: Optional[List[Union[ErrorSource, int, str]]] = Field(default=None) sourceOperator: Optional[MathOperator] = Field(default=None) filters: Optional[List[RequestGraphqlFilterSchema]] = Field(default_factory=list) + properties: Optional[EventPropertiesSchema] = Field(default=None) _remove_duplicate_values = field_validator('value', mode='before')(remove_duplicate_values) _single_to_list_values = field_validator('value', mode='before')(single_to_list) @@ -660,12 +722,12 @@ def add_missing_is_event(values: dict): # this type is created to allow mixing events&filters and specifying a discriminator -GroupedFilterType = Annotated[Union[SessionSearchFilterSchema, SessionSearchEventSchema2], +GroupedFilterType = Annotated[Union[SessionSearchFilterSchema, SessionSearchEventSchema], Field(discriminator='is_event'), BeforeValidator(add_missing_is_event)] class SessionsSearchPayloadSchema(_TimedSchema, _PaginatedSchema): - events: List[SessionSearchEventSchema2] = Field(default_factory=list, doc_hidden=True) + events: List[SessionSearchEventSchema] = Field(default_factory=list, doc_hidden=True) filters: List[GroupedFilterType] = Field(default_factory=list) sort: str = Field(default="startTs") order: SortOrderType = Field(default=SortOrderType.DESC) @@ -690,6 +752,8 @@ class SessionsSearchPayloadSchema(_TimedSchema, _PaginatedSchema): def add_missing_attributes(cls, values): # in case isEvent is wrong: for f in values.get("filters") or []: + if f.get("type") is None: + continue if EventType.has_value(f["type"]) and not f.get("isEvent"): f["isEvent"] = True elif FilterType.has_value(f["type"]) and f.get("isEvent"): @@ -715,6 +779,15 @@ class SessionsSearchPayloadSchema(_TimedSchema, _PaginatedSchema): f["value"] = vals return values + @model_validator(mode="after") + def check_pa_event_filter(self): + for v in self.filters + self.events: + if v.type == EventType.EVENT: + assert v.operator in (SearchEventOperator.IS, MathOperator.EQUAL), \ + "operator must be {SearchEventOperator.IS} or {MathOperator.EQUAL} for EVENT type" + assert len(v.value) == 1, "value must have 1 single value for EVENT type" + return self + @model_validator(mode="after") def split_filters_events(self): n_filters = [] @@ -1404,7 +1477,7 @@ class MetricSearchSchema(_PaginatedSchema): mine_only: bool = Field(default=False) -class _HeatMapSearchEventRaw(SessionSearchEventSchema2): +class _HeatMapSearchEventRaw(SessionSearchEventSchema): type: Literal[EventType.LOCATION] = Field(...) @@ -1529,3 +1602,30 @@ class TagCreate(TagUpdate): class ScopeSchema(BaseModel): scope: int = Field(default=1, ge=1, le=2) + + +class SessionModel(BaseModel): + duration: int + errorsCount: int + eventsCount: int + favorite: bool = Field(default=False) + issueScore: int + issueTypes: List[IssueType] = Field(default=[]) + metadata: dict = Field(default={}) + pagesCount: int + platform: str + projectId: int + sessionId: str + startTs: int + timezone: Optional[str] + userAnonymousId: Optional[str] + userBrowser: str + userCity: str + userCountry: str + userDevice: Optional[str] + userDeviceType: str + userId: Optional[str] + userOs: str + userState: str + userUuid: str + viewed: bool = Field(default=False) diff --git a/assist-server/build.sh b/assist-server/build.sh new file mode 100644 index 000000000..abd26d5bc --- /dev/null +++ b/assist-server/build.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +# Usage: IMAGE_TAG=latest DOCKER_REPO=myDockerHubID bash build.sh + +ARCH=${ARCH:-amd64} +git_sha=$(git rev-parse --short HEAD) +image_tag=${IMAGE_TAG:-git_sha} +check_prereq() { + which docker || { + echo "Docker not installed, please install docker." + exit 1 + } +} +source ../scripts/lib/_docker.sh + +[[ $PATCH -eq 1 ]] && { + image_tag="$(grep -ER ^.ppVersion ../scripts/helmcharts/openreplay/charts/$chart | xargs | awk '{print $2}' | awk -F. -v OFS=. '{$NF += 1 ; print}')" + image_tag="${image_tag}-ee" +} +update_helm_release() { + chart=$1 + HELM_TAG="$(grep -iER ^version ../scripts/helmcharts/openreplay/charts/$chart | awk '{print $2}' | awk -F. -v OFS=. '{$NF += 1 ; print}')" + # Update the chart version + sed -i "s#^version.*#version: $HELM_TAG# g" ../scripts/helmcharts/openreplay/charts/$chart/Chart.yaml + # Update image tags + sed -i "s#ppVersion.*#ppVersion: \"$image_tag\"#g" ../scripts/helmcharts/openreplay/charts/$chart/Chart.yaml + # Commit the changes + git add ../scripts/helmcharts/openreplay/charts/$chart/Chart.yaml + git commit -m "chore(helm): Updating $chart image release" +} + +function build_api() { + destination="_assist-server_ee" + [[ -d ../${destination} ]] && { + echo "Removing previous build cache" + rm -rf ../${destination} + } + cp -R ../assist-server ../${destination} + cd ../${destination} || exit 1 + cp -rf ../ee/assist-server/* ./ + + docker build -f ./Dockerfile --build-arg GIT_SHA=$git_sha -t ${DOCKER_REPO:-'local'}/assist-server:${image_tag} . + + cd ../assist-server || exit 1 + rm -rf ../${destination} + [[ $PUSH_IMAGE -eq 1 ]] && { + docker push ${DOCKER_REPO:-'local'}/assist-server:${image_tag} + docker tag ${DOCKER_REPO:-'local'}/assist-server:${image_tag} ${DOCKER_REPO:-'local'}/assist-server:latest + docker push ${DOCKER_REPO:-'local'}/assist-server:latest + } + [[ $SIGN_IMAGE -eq 1 ]] && { + cosign sign --key $SIGN_KEY ${DOCKER_REPO:-'local'}/assist-server:${image_tag} + } + echo "build completed for assist-server" +} + +check_prereq +build_api $1 +if [[ $PATCH -eq 1 ]]; then + update_helm_release assist-server +fi diff --git a/backend/cmd/analytics/main.go b/backend/cmd/analytics/main.go index df4b5e660..8a7b95c29 100644 --- a/backend/cmd/analytics/main.go +++ b/backend/cmd/analytics/main.go @@ -2,44 +2,71 @@ package main import ( "context" + "os" + "os/signal" + "syscall" - analyticsConfig "openreplay/backend/internal/config/analytics" - "openreplay/backend/pkg/analytics" - "openreplay/backend/pkg/db/postgres/pool" "openreplay/backend/pkg/logger" - "openreplay/backend/pkg/metrics" - "openreplay/backend/pkg/metrics/database" - "openreplay/backend/pkg/metrics/web" - "openreplay/backend/pkg/server" - "openreplay/backend/pkg/server/api" ) func main() { ctx := context.Background() log := logger.New() - cfg := analyticsConfig.New(log) - // Observability - webMetrics := web.New("analytics") - dbMetrics := database.New("analytics") - metrics.New(log, append(webMetrics.List(), dbMetrics.List()...)) + log.Info(ctx, "Cacher service started") - pgConn, err := pool.New(dbMetrics, cfg.Postgres.String()) - if err != nil { - log.Fatal(ctx, "can't init postgres connection: %s", err) + sigchan := make(chan os.Signal, 1) + signal.Notify(sigchan, syscall.SIGINT, syscall.SIGTERM) + + for { + select { + case sig := <-sigchan: + log.Error(ctx, "Caught signal %v: terminating", sig) + os.Exit(0) + } } - defer pgConn.Close() - - builder, err := analytics.NewServiceBuilder(log, cfg, webMetrics, dbMetrics, pgConn) - if err != nil { - log.Fatal(ctx, "can't init services: %s", err) - } - - router, err := api.NewRouter(&cfg.HTTP, log) - if err != nil { - log.Fatal(ctx, "failed while creating router: %s", err) - } - router.AddHandlers(api.NoPrefix, builder.CardsAPI, builder.DashboardsAPI, builder.ChartsAPI) - router.AddMiddlewares(builder.Auth.Middleware, builder.RateLimiter.Middleware, builder.AuditTrail.Middleware) - - server.Run(ctx, log, &cfg.HTTP, router) } + +// +//import ( +// "context" +// +// analyticsConfig "openreplay/backend/internal/config/analytics" +// "openreplay/backend/pkg/analytics" +// "openreplay/backend/pkg/db/postgres/pool" +// "openreplay/backend/pkg/logger" +// "openreplay/backend/pkg/metrics" +// "openreplay/backend/pkg/metrics/database" +// "openreplay/backend/pkg/metrics/web" +// "openreplay/backend/pkg/server" +// "openreplay/backend/pkg/server/api" +//) +// +//func main() { +// ctx := context.Background() +// log := logger.New() +// cfg := analyticsConfig.New(log) +// // Observability +// webMetrics := web.New("analytics") +// dbMetrics := database.New("analytics") +// metrics.New(log, append(webMetrics.List(), dbMetrics.List()...)) +// +// pgConn, err := pool.New(dbMetrics, cfg.Postgres.String()) +// if err != nil { +// log.Fatal(ctx, "can't init postgres connection: %s", err) +// } +// defer pgConn.Close() +// +// builder, err := analytics.NewServiceBuilder(log, cfg, webMetrics, dbMetrics, pgConn) +// if err != nil { +// log.Fatal(ctx, "can't init services: %s", err) +// } +// +// router, err := api.NewRouter(&cfg.HTTP, log) +// if err != nil { +// log.Fatal(ctx, "failed while creating router: %s", err) +// } +// router.AddHandlers(api.NoPrefix, builder.CardsAPI, builder.DashboardsAPI, builder.ChartsAPI) +// router.AddMiddlewares(builder.Auth.Middleware, builder.RateLimiter.Middleware, builder.AuditTrail.Middleware) +// +// server.Run(ctx, log, &cfg.HTTP, router) +//} diff --git a/backend/pkg/db/clickhouse/connector.go b/backend/pkg/db/clickhouse/connector.go index ed67b5c24..25ebc8a1e 100644 --- a/backend/pkg/db/clickhouse/connector.go +++ b/backend/pkg/db/clickhouse/connector.go @@ -111,12 +111,12 @@ var batches = map[string]string{ "pages": `INSERT INTO product_analytics.events (session_id, project_id, event_id, "$event_name", created_at, "$time", distinct_id, "$auto_captured", "$device", "$os_version", "$current_url", "$properties") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, "clicks": `INSERT INTO product_analytics.events (session_id, project_id, event_id, "$event_name", created_at, "$time", distinct_id, "$auto_captured", "$device", "$os_version", "$current_url", "$properties") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, "inputs": `INSERT INTO product_analytics.events (session_id, project_id, event_id, "$event_name", created_at, "$time", distinct_id, "$auto_captured", "$device", "$os_version", "$duration_s", "$properties") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, - "errors": `INSERT INTO product_analytics.events (session_id, project_id, event_id, "$event_name", created_at, "$time", distinct_id, "$auto_captured", "$device", "$os_version", error_id, "$properties") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + "errors": `INSERT INTO product_analytics.events (session_id, project_id, event_id, "$event_name", created_at, "$time", distinct_id, "$auto_captured", "$device", "$os_version", error_id, "$current_url", "$properties") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, "performance": `INSERT INTO product_analytics.events (session_id, project_id, event_id, "$event_name", created_at, "$time", distinct_id, "$auto_captured", "$device", "$os_version", "$properties") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, "requests": `INSERT INTO product_analytics.events (session_id, project_id, event_id, "$event_name", created_at, "$time", distinct_id, "$auto_captured", "$device", "$os_version", "$duration_s", "$properties") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, "custom": `INSERT INTO product_analytics.events (session_id, project_id, event_id, "$event_name", created_at, "$time", distinct_id, "$auto_captured", "$device", "$os_version", "$properties") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, "graphql": `INSERT INTO product_analytics.events (session_id, project_id, event_id, "$event_name", created_at, "$time", distinct_id, "$auto_captured", "$device", "$os_version", "$properties") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, - "issuesEvents": `INSERT INTO product_analytics.events (session_id, project_id, event_id, "$event_name", created_at, "$time", distinct_id, "$auto_captured", "$device", "$os_version", issue_type, issue_id, "$properties") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + "issuesEvents": `INSERT INTO product_analytics.events (session_id, project_id, event_id, "$event_name", created_at, "$time", distinct_id, "$auto_captured", "$device", "$os_version", issue_type, issue_id, "$current_url", "$properties") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, "issues": "INSERT INTO experimental.issues (project_id, issue_id, type, context_string) VALUES (?, ?, ?, ?)", "mobile_sessions": "INSERT INTO experimental.sessions (session_id, project_id, user_id, user_uuid, user_os, user_os_version, user_device, user_device_type, user_country, user_state, user_city, datetime, duration, pages_count, events_count, errors_count, issue_score, referrer, issue_types, tracker_version, user_browser, user_browser_version, metadata_1, metadata_2, metadata_3, metadata_4, metadata_5, metadata_6, metadata_7, metadata_8, metadata_9, metadata_10, platform, timezone) VALUES (?, ?, SUBSTR(?, 1, 8000), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, SUBSTR(?, 1, 8000), ?, ?, ?, ?, SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), SUBSTR(?, 1, 8000), ?, ?)", "mobile_custom": `INSERT INTO product_analytics.events (session_id, project_id, event_id, "$event_name", created_at, "$time", distinct_id, "$auto_captured", "$device", "$os_version", "$properties") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, @@ -309,6 +309,7 @@ func (c *connectorImpl) InsertMouseThrashing(session *sessions.Session, msg *mes session.UserOSVersion, "mouse_thrashing", issueID, + cropString(msg.Url), jsonString, ); err != nil { c.checkError("issuesEvents", err) @@ -365,6 +366,7 @@ func (c *connectorImpl) InsertIssue(session *sessions.Session, msg *messages.Iss session.UserOSVersion, msg.Type, issueID, + cropString(msg.Url), jsonString, ); err != nil { c.checkError("issuesEvents", err) @@ -552,6 +554,7 @@ func (c *connectorImpl) InsertWebErrorEvent(session *sessions.Session, msg *type session.Platform, session.UserOSVersion, msgID, + cropString(msg.Url), jsonString, ); err != nil { c.checkError("errors", err) diff --git a/backend/pkg/db/postgres/pool/pool.go b/backend/pkg/db/postgres/pool/pool.go index f3e46d03b..f6d82e6c3 100644 --- a/backend/pkg/db/postgres/pool/pool.go +++ b/backend/pkg/db/postgres/pool/pool.go @@ -84,7 +84,10 @@ func (p *poolImpl) Begin() (*Tx, error) { tx, err := p.conn.Begin(context.Background()) p.metrics.RecordRequestDuration(float64(time.Now().Sub(start).Milliseconds()), "begin", "") p.metrics.IncreaseTotalRequests("begin", "") - return &Tx{tx, p.metrics}, err + return &Tx{ + origTx: tx, + metrics: p.metrics, + }, err } func (p *poolImpl) Close() { @@ -94,13 +97,13 @@ func (p *poolImpl) Close() { // TX - start type Tx struct { - pgx.Tx + origTx pgx.Tx metrics database.Database } func (tx *Tx) TxExec(sql string, args ...interface{}) error { start := time.Now() - _, err := tx.Exec(context.Background(), sql, args...) + _, err := tx.origTx.Exec(context.Background(), sql, args...) method, table := methodName(sql) tx.metrics.RecordRequestDuration(float64(time.Now().Sub(start).Milliseconds()), method, table) tx.metrics.IncreaseTotalRequests(method, table) @@ -109,7 +112,7 @@ func (tx *Tx) TxExec(sql string, args ...interface{}) error { func (tx *Tx) TxQueryRow(sql string, args ...interface{}) pgx.Row { start := time.Now() - res := tx.QueryRow(context.Background(), sql, args...) + res := tx.origTx.QueryRow(context.Background(), sql, args...) method, table := methodName(sql) tx.metrics.RecordRequestDuration(float64(time.Now().Sub(start).Milliseconds()), method, table) tx.metrics.IncreaseTotalRequests(method, table) @@ -118,7 +121,7 @@ func (tx *Tx) TxQueryRow(sql string, args ...interface{}) pgx.Row { func (tx *Tx) TxRollback() error { start := time.Now() - err := tx.Rollback(context.Background()) + err := tx.origTx.Rollback(context.Background()) tx.metrics.RecordRequestDuration(float64(time.Now().Sub(start).Milliseconds()), "rollback", "") tx.metrics.IncreaseTotalRequests("rollback", "") return err @@ -126,7 +129,7 @@ func (tx *Tx) TxRollback() error { func (tx *Tx) TxCommit() error { start := time.Now() - err := tx.Commit(context.Background()) + err := tx.origTx.Commit(context.Background()) tx.metrics.RecordRequestDuration(float64(time.Now().Sub(start).Milliseconds()), "commit", "") tx.metrics.IncreaseTotalRequests("commit", "") return err diff --git a/backend/pkg/db/types/error-event.go b/backend/pkg/db/types/error-event.go index df137347a..550174cde 100644 --- a/backend/pkg/db/types/error-event.go +++ b/backend/pkg/db/types/error-event.go @@ -5,10 +5,11 @@ import ( "encoding/hex" "encoding/json" "fmt" - "github.com/google/uuid" "hash/fnv" "strconv" + "github.com/google/uuid" + . "openreplay/backend/pkg/messages" ) @@ -23,41 +24,7 @@ type ErrorEvent struct { Payload string Tags map[string]*string OriginType int -} - -func unquote(s string) string { - if s[0] == '"' { - return s[1 : len(s)-1] - } - return s -} -func parseTags(tagsJSON string) (tags map[string]*string, err error) { - if len(tagsJSON) == 0 { - return nil, fmt.Errorf("empty tags") - } - if tagsJSON[0] == '[' { - var tagsArr []json.RawMessage - if err = json.Unmarshal([]byte(tagsJSON), &tagsArr); err != nil { - return - } - - tags = make(map[string]*string) - for _, keyBts := range tagsArr { - tags[unquote(string(keyBts))] = nil - } - } else if tagsJSON[0] == '{' { - var tagsObj map[string]json.RawMessage - if err = json.Unmarshal([]byte(tagsJSON), &tagsObj); err != nil { - return - } - - tags = make(map[string]*string) - for key, valBts := range tagsObj { - val := unquote(string(valBts)) - tags[key] = &val - } - } - return + Url string } func WrapJSException(m *JSException) (*ErrorEvent, error) { @@ -69,6 +36,7 @@ func WrapJSException(m *JSException) (*ErrorEvent, error) { Message: m.Message, Payload: m.Payload, OriginType: m.TypeID(), + Url: m.Url, }, nil } @@ -81,6 +49,7 @@ func WrapIntegrationEvent(m *IntegrationEvent) *ErrorEvent { Message: m.Message, Payload: m.Payload, OriginType: m.TypeID(), + Url: m.Url, } } diff --git a/backend/pkg/sessions/api/web/handlers.go b/backend/pkg/sessions/api/web/handlers.go index 4ca3fec32..af509d8f6 100644 --- a/backend/pkg/sessions/api/web/handlers.go +++ b/backend/pkg/sessions/api/web/handlers.go @@ -135,11 +135,6 @@ func (e *handlersImpl) startSessionHandlerWeb(w http.ResponseWriter, r *http.Req // Add tracker version to context r = r.WithContext(context.WithValue(r.Context(), "tracker", req.TrackerVersion)) - if err := validateTrackerVersion(req.TrackerVersion); err != nil { - e.log.Error(r.Context(), "unsupported tracker version: %s, err: %s", req.TrackerVersion, err) - e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusUpgradeRequired, errors.New("please upgrade the tracker version"), startTime, r.URL.Path, bodySize) - return - } // Handler's logic if req.ProjectKey == nil { @@ -162,6 +157,13 @@ func (e *handlersImpl) startSessionHandlerWeb(w http.ResponseWriter, r *http.Req // Add projectID to context r = r.WithContext(context.WithValue(r.Context(), "projectID", fmt.Sprintf("%d", p.ProjectID))) + // Validate tracker version + if err := validateTrackerVersion(req.TrackerVersion); err != nil { + e.log.Error(r.Context(), "unsupported tracker version: %s, err: %s", req.TrackerVersion, err) + e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusUpgradeRequired, errors.New("please upgrade the tracker version"), startTime, r.URL.Path, bodySize) + return + } + // Check if the project supports mobile sessions if !p.IsWeb() { e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusForbidden, errors.New("project doesn't support web sessions"), startTime, r.URL.Path, bodySize) diff --git a/backend/pkg/spot/transcoder/tasks.go b/backend/pkg/spot/transcoder/tasks.go index 761f1a679..69c5e76dc 100644 --- a/backend/pkg/spot/transcoder/tasks.go +++ b/backend/pkg/spot/transcoder/tasks.go @@ -29,7 +29,7 @@ type Task struct { Duration int Status string Path string - tx pool.Tx + tx *pool.Tx } func (t *Task) HasToTrim() bool { @@ -65,7 +65,7 @@ func (t *tasksImpl) Get() (task *Task, err error) { } }() - task = &Task{tx: pool.Tx{Tx: tx}} + task = &Task{tx: tx} sql := `SELECT spot_id, crop, duration FROM spots.tasks WHERE status = 'pending' ORDER BY added_time FOR UPDATE SKIP LOCKED LIMIT 1` err = tx.TxQueryRow(sql).Scan(&task.SpotID, &task.Crop, &task.Duration) if err != nil { diff --git a/backend/pkg/spot/transcoder/transcoder.go b/backend/pkg/spot/transcoder/transcoder.go index 184e7618d..fcd4d0799 100644 --- a/backend/pkg/spot/transcoder/transcoder.go +++ b/backend/pkg/spot/transcoder/transcoder.go @@ -52,6 +52,7 @@ func NewTranscoder(cfg *spot.Config, log logger.Logger, objStorage objectstorage tasks: NewTasks(conn), streams: NewStreams(log, conn, objStorage), spots: spots, + metrics: metrics, } tnsc.prepareWorkers = workers.NewPool(2, 4, tnsc.prepare) tnsc.transcodeWorkers = workers.NewPool(2, 4, tnsc.transcode) diff --git a/codecov.yml b/codecov.yml index 8a484e684..5f6e2ef12 100644 --- a/codecov.yml +++ b/codecov.yml @@ -8,7 +8,6 @@ ignore: - "**/*/build/**" - "**/*/.test.*" - "**/*/version.ts" -review: - poem: false - review_status: false - collapse_walkthrough: true \ No newline at end of file +comment: + layout: "condensed_header, condensed_files, condensed_footer" + hide_project_coverage: TRUE diff --git a/ee/api/.gitignore b/ee/api/.gitignore index ebb54e743..de55d4df4 100644 --- a/ee/api/.gitignore +++ b/ee/api/.gitignore @@ -223,10 +223,14 @@ Pipfile.lock /chalicelib/core/sessions/performance_event.py /chalicelib/core/sessions/sessions_viewed/sessions_viewed.py /chalicelib/core/sessions/unprocessed_sessions.py +/chalicelib/core/sessions/__init__.py +/chalicelib/core/sessions/sessions_legacy_mobil.py +/chalicelib/core/sessions/sessions_search_exp.py /chalicelib/core/metrics/modules /chalicelib/core/socket_ios.py /chalicelib/core/sourcemaps /chalicelib/core/tags.py +/chalicelib/core/product_analytics /chalicelib/saml /chalicelib/utils/__init__.py /chalicelib/utils/args_transformer.py @@ -289,3 +293,5 @@ Pipfile.lock /chalicelib/core/errors/errors_ch.py /chalicelib/core/errors/errors_details.py /chalicelib/utils/contextual_validators.py +/routers/subs/product_analytics.py +/schemas/product_analytics.py diff --git a/ee/api/Pipfile b/ee/api/Pipfile index 512465c00..f8986ff2b 100644 --- a/ee/api/Pipfile +++ b/ee/api/Pipfile @@ -6,25 +6,23 @@ name = "pypi" [packages] urllib3 = "==2.3.0" requests = "==2.32.3" -boto3 = "==1.36.12" +boto3 = "==1.37.21" pyjwt = "==2.10.1" psycopg2-binary = "==2.9.10" -psycopg = {extras = ["pool", "binary"], version = "==3.2.4"} -clickhouse-driver = {extras = ["lz4"], version = "==0.2.9"} +psycopg = {extras = ["pool", "binary"], version = "==3.2.6"} clickhouse-connect = "==0.8.15" -elasticsearch = "==8.17.1" +elasticsearch = "==8.17.2" jira = "==3.8.0" -cachetools = "==5.5.1" -fastapi = "==0.115.8" +cachetools = "==5.5.2" +fastapi = "==0.115.12" uvicorn = {extras = ["standard"], version = "==0.34.0"} gunicorn = "==23.0.0" python-decouple = "==3.8" pydantic = {extras = ["email"], version = "==2.10.6"} apscheduler = "==3.11.0" -python3-saml = "==1.16.0" python-multipart = "==0.0.20" redis = "==5.2.1" -azure-storage-blob = "==12.24.1" +azure-storage-blob = "==12.25.0" [dev-packages] diff --git a/ee/api/app.py b/ee/api/app.py index 7ad085882..5b3af9d80 100644 --- a/ee/api/app.py +++ b/ee/api/app.py @@ -21,7 +21,7 @@ from chalicelib.utils import pg_client, ch_client from crons import core_crons, ee_crons, core_dynamic_crons from routers import core, core_dynamic from routers import ee -from routers.subs import insights, metrics, v1_api, health, usability_tests, spot, product_anaytics +from routers.subs import insights, metrics, v1_api, health, usability_tests, spot, product_analytics from routers.subs import v1_api_ee if config("ENABLE_SSO", cast=bool, default=True): @@ -150,9 +150,9 @@ app.include_router(spot.public_app) app.include_router(spot.app) app.include_router(spot.app_apikey) -app.include_router(product_anaytics.public_app) -app.include_router(product_anaytics.app) -app.include_router(product_anaytics.app_apikey) +app.include_router(product_analytics.public_app, prefix="/ap") +app.include_router(product_analytics.app, prefix="/ap") +app.include_router(product_analytics.app_apikey, prefix="/ap") if config("ENABLE_SSO", cast=bool, default=True): app.include_router(saml.public_app) diff --git a/ee/api/chalicelib/core/sessions/__init__.py b/ee/api/chalicelib/core/sessions/__init__.py deleted file mode 100644 index ebcb11644..000000000 --- a/ee/api/chalicelib/core/sessions/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -import logging - -from decouple import config - -logger = logging.getLogger(__name__) -from . import sessions_pg -from . import sessions_pg as sessions_legacy -from . import sessions_ch -from . import sessions_search as sessions_search_legacy - -if config("EXP_SESSIONS_SEARCH", cast=bool, default=False): - logger.info(">>> Using experimental sessions search") - from . import sessions_ch as sessions - from . import sessions_search_exp as sessions_search -else: - from . import sessions_pg as sessions - from . import sessions_search as sessions_search diff --git a/ee/api/clean-dev.sh b/ee/api/clean-dev.sh index 357421c10..b64c7ac97 100755 --- a/ee/api/clean-dev.sh +++ b/ee/api/clean-dev.sh @@ -44,11 +44,15 @@ rm -rf ./chalicelib/core/sessions/sessions_search.py rm -rf ./chalicelib/core/sessions/performance_event.py rm -rf ./chalicelib/core/sessions/sessions_viewed/sessions_viewed.py rm -rf ./chalicelib/core/sessions/unprocessed_sessions.py +rm -rf ./chalicelib/core/sessions/__init__.py +rm -rf ./chalicelib/core/sessions/sessions_legacy_mobil.py +rm -rf ./chalicelib/core/sessions/sessions_search_exp.py rm -rf ./chalicelib/core/metrics/modules rm -rf ./chalicelib/core/socket_ios.py rm -rf ./chalicelib/core/sourcemaps rm -rf ./chalicelib/core/user_testing.py rm -rf ./chalicelib/core/tags.py +rm -rf ./chalicelib/core/product_analytics rm -rf ./chalicelib/saml rm -rf ./chalicelib/utils/__init__.py rm -rf ./chalicelib/utils/args_transformer.py @@ -109,3 +113,5 @@ rm -rf ./chalicelib/core/errors/errors_pg.py rm -rf ./chalicelib/core/errors/errors_ch.py rm -rf ./chalicelib/core/errors/errors_details.py rm -rf ./chalicelib/utils/contextual_validators.py +rm -rf ./routers/subs/product_analytics.py +rm -rf ./schemas/product_analytics.py \ No newline at end of file diff --git a/ee/api/requirements-alerts.txt b/ee/api/requirements-alerts.txt index 2f9765929..f07a5a381 100644 --- a/ee/api/requirements-alerts.txt +++ b/ee/api/requirements-alerts.txt @@ -1,19 +1,18 @@ urllib3==2.3.0 requests==2.32.3 -boto3==1.36.12 +boto3==1.37.21 pyjwt==2.10.1 psycopg2-binary==2.9.10 -psycopg[pool,binary]==3.2.4 -clickhouse-driver[lz4]==0.2.9 +psycopg[pool,binary]==3.2.6 clickhouse-connect==0.8.15 -elasticsearch==8.17.1 +elasticsearch==8.17.2 jira==3.8.0 -cachetools==5.5.1 +cachetools==5.5.2 -fastapi==0.115.8 +fastapi==0.115.12 uvicorn[standard]==0.34.0 python-decouple==3.8 pydantic[email]==2.10.6 apscheduler==3.11.0 -azure-storage-blob==12.24.1 +azure-storage-blob==12.25.0 diff --git a/ee/api/requirements-crons.txt b/ee/api/requirements-crons.txt index 19de71806..5c51cabf6 100644 --- a/ee/api/requirements-crons.txt +++ b/ee/api/requirements-crons.txt @@ -1,19 +1,18 @@ urllib3==2.3.0 requests==2.32.3 -boto3==1.36.12 +boto3==1.37.21 pyjwt==2.10.1 psycopg2-binary==2.9.10 -psycopg[pool,binary]==3.2.4 -clickhouse-driver[lz4]==0.2.9 +psycopg[pool,binary]==3.2.6 clickhouse-connect==0.8.15 -elasticsearch==8.17.1 +elasticsearch==8.17.2 jira==3.8.0 -cachetools==5.5.1 +cachetools==5.5.2 -fastapi==0.115.8 +fastapi==0.115.12 python-decouple==3.8 pydantic[email]==2.10.6 apscheduler==3.11.0 redis==5.2.1 -azure-storage-blob==12.24.1 +azure-storage-blob==12.25.0 diff --git a/ee/api/requirements.txt b/ee/api/requirements.txt index 65000f637..b10f9e692 100644 --- a/ee/api/requirements.txt +++ b/ee/api/requirements.txt @@ -1,16 +1,15 @@ urllib3==2.3.0 requests==2.32.3 -boto3==1.36.12 +boto3==1.37.21 pyjwt==2.10.1 psycopg2-binary==2.9.10 -psycopg[pool,binary]==3.2.4 -clickhouse-driver[lz4]==0.2.9 +psycopg[pool,binary]==3.2.6 clickhouse-connect==0.8.15 -elasticsearch==8.17.1 +elasticsearch==8.17.2 jira==3.8.0 -cachetools==5.5.1 +cachetools==5.5.2 -fastapi==0.115.8 +fastapi==0.115.12 uvicorn[standard]==0.34.0 gunicorn==23.0.0 python-decouple==3.8 @@ -19,10 +18,9 @@ apscheduler==3.11.0 # TODO: enable after xmlsec fix https://github.com/xmlsec/python-xmlsec/issues/252 #--no-binary is used to avoid libxml2 library version incompatibilities between xmlsec and lxml -python3-saml==1.16.0 ---no-binary=lxml + python-multipart==0.0.20 redis==5.2.1 #confluent-kafka==2.1.0 -azure-storage-blob==12.24.1 +azure-storage-blob==12.25.0 diff --git a/ee/api/schemas/__init__.py b/ee/api/schemas/__init__.py index 37ecaab44..4089e6867 100644 --- a/ee/api/schemas/__init__.py +++ b/ee/api/schemas/__init__.py @@ -1,4 +1,5 @@ from .schemas import * from .schemas_ee import * from .assist_stats_schema import * +from .product_analytics import * from . import overrides as _overrides diff --git a/ee/api/schemas/schemas_ee.py b/ee/api/schemas/schemas_ee.py index a5cc5c78f..394f88859 100644 --- a/ee/api/schemas/schemas_ee.py +++ b/ee/api/schemas/schemas_ee.py @@ -4,7 +4,7 @@ from pydantic import Field, EmailStr, field_validator, model_validator from chalicelib.utils.TimeUTC import TimeUTC from . import schemas -from .overrides import BaseModel, Enum, ORUnion +from .overrides import BaseModel, Enum from .transformers_validators import remove_whitespace @@ -91,33 +91,6 @@ class TrailSearchPayloadSchema(schemas._PaginatedSchema): return values -class SessionModel(BaseModel): - duration: int - errorsCount: int - eventsCount: int - favorite: bool = Field(default=False) - issueScore: int - issueTypes: List[schemas.IssueType] = Field(default=[]) - metadata: dict = Field(default={}) - pagesCount: int - platform: str - projectId: int - sessionId: str - startTs: int - timezone: Optional[str] - userAnonymousId: Optional[str] - userBrowser: str - userCity: str - userCountry: str - userDevice: Optional[str] - userDeviceType: str - userId: Optional[str] - userOs: str - userState: str - userUuid: str - viewed: bool = Field(default=False) - - class AssistRecordUpdatePayloadSchema(BaseModel): name: str = Field(..., min_length=1) _transform_name = field_validator('name', mode="before")(remove_whitespace) diff --git a/ee/assist-server/.gitignore b/ee/assist-server/.gitignore new file mode 100644 index 000000000..e0fd21e8f --- /dev/null +++ b/ee/assist-server/.gitignore @@ -0,0 +1,5 @@ +.idea +node_modules +npm-debug.log +.cache +*.mmdb \ No newline at end of file diff --git a/ee/assist-server/Dockerfile b/ee/assist-server/Dockerfile new file mode 100644 index 000000000..f30f5dc1d --- /dev/null +++ b/ee/assist-server/Dockerfile @@ -0,0 +1,24 @@ +ARG ARCH=amd64 + +FROM --platform=linux/$ARCH node:23-alpine +LABEL Maintainer="Zavorotynskiy Alexander " +RUN apk add --no-cache tini git libc6-compat +ARG envarg +ENV ENTERPRISE_BUILD=${envarg} \ + MAXMINDDB_FILE=/home/openreplay/geoip.mmdb \ + PRIVATE_ENDPOINTS=false \ + LISTEN_PORT=9001 \ + ERROR=1 \ + NODE_ENV=production +WORKDIR /work +COPY package.json . +COPY package-lock.json . +RUN npm install +COPY . . + +RUN adduser -u 1001 openreplay -D +USER 1001 +ADD --chown=1001 https://static.openreplay.com/geoip/GeoLite2-City.mmdb $MAXMINDDB_FILE + +ENTRYPOINT ["/sbin/tini", "--"] +CMD npm start diff --git a/ee/assist-server/app/assist.js b/ee/assist-server/app/assist.js new file mode 100644 index 000000000..a394f4908 --- /dev/null +++ b/ee/assist-server/app/assist.js @@ -0,0 +1,168 @@ +const jwt = require('jsonwebtoken'); +const uaParser = require('ua-parser-js'); +const {geoip} = require('./geoIP'); +const {logger} = require('./logger'); + +let PROJECT_KEY_LENGTH = parseInt(process.env.PROJECT_KEY_LENGTH) || 20; + +const IDENTITIES = {agent: 'agent', session: 'session'}; +const EVENTS_DEFINITION = { + listen: { + UPDATE_EVENT: "UPDATE_SESSION", // tab become active/inactive, page title change, changed session object (rare case), call start/end + CONNECT_ERROR: "connect_error", + CONNECT_FAILED: "connect_failed", + ERROR: "error" + }, + //The following list of events will be only emitted by the server + server: { + UPDATE_SESSION: "SERVER_UPDATE_SESSION" + } +}; +EVENTS_DEFINITION.emit = { + NEW_AGENT: "NEW_AGENT", + NO_AGENTS: "NO_AGENT", + AGENT_DISCONNECT: "AGENT_DISCONNECTED", + AGENTS_CONNECTED: "AGENTS_CONNECTED", + NO_SESSIONS: "SESSION_DISCONNECTED", + SESSION_ALREADY_CONNECTED: "SESSION_ALREADY_CONNECTED", + SESSION_RECONNECTED: "SESSION_RECONNECTED", + UPDATE_EVENT: EVENTS_DEFINITION.listen.UPDATE_EVENT +}; + +const BASE_sessionInfo = { + "pageTitle": "Page", + "active": false, + "live": true, + "sessionID": "0", + "metadata": {}, + "userID": "", + "userUUID": "", + "projectKey": "", + "revID": "", + "timestamp": 0, + "trackerVersion": "", + "isSnippet": true, + "userOs": "", + "userBrowser": "", + "userBrowserVersion": "", + "userDevice": "", + "userDeviceType": "", + "userCountry": "", + "userState": "", + "userCity": "", + "projectId": 0 +}; + +const extractPeerId = (peerId) => { + const parts = peerId.split("-"); + if (parts.length < 2 || parts.length > 3) { + logger.debug(`Invalid peerId format: ${peerId}`); + return {}; + } + if (PROJECT_KEY_LENGTH > 0 && parts[0].length !== PROJECT_KEY_LENGTH) { + logger.debug(`Invalid project key length in peerId: ${peerId}`); + return {}; + } + const [projectKey, sessionId, tabId = generateRandomTabId()] = parts; + return { projectKey, sessionId, tabId }; +}; + +const generateRandomTabId = () => (Math.random() + 1).toString(36).substring(2); + +function processPeerInfo(socket) { + socket._connectedAt = new Date(); + const { projectKey, sessionId, tabId } = extractPeerId(socket.handshake.query.peerId || ""); + Object.assign(socket.handshake.query, { + roomId: projectKey && sessionId ? `${projectKey}-${sessionId}` : null, + projectKey, + sessId: sessionId, + tabId + }); + logger.debug(`Connection details: projectKey:${projectKey}, sessionId:${sessionId}, tabId:${tabId}, roomId:${socket.handshake.query.roomId}`); +} + +/** + * extracts and populate socket with information + * @Param {socket} used socket + * */ +const extractSessionInfo = function (socket) { + if (socket.handshake.query.sessionInfo !== undefined) { + logger.debug(`received headers: ${socket.handshake.headers}`); + + socket.handshake.query.sessionInfo = JSON.parse(socket.handshake.query.sessionInfo); + socket.handshake.query.sessionInfo = {...BASE_sessionInfo, ...socket.handshake.query.sessionInfo}; + + let ua = uaParser(socket.handshake.headers['user-agent']); + socket.handshake.query.sessionInfo.userOs = ua.os.name || null; + socket.handshake.query.sessionInfo.userBrowser = ua.browser.name || null; + socket.handshake.query.sessionInfo.userBrowserVersion = ua.browser.version || null; + socket.handshake.query.sessionInfo.userDevice = ua.device.model || null; + socket.handshake.query.sessionInfo.userDeviceType = ua.device.type || 'desktop'; + socket.handshake.query.sessionInfo.userCountry = null; + socket.handshake.query.sessionInfo.userState = null; + socket.handshake.query.sessionInfo.userCity = null; + if (geoip() !== null) { + logger.debug(`looking for location of ${socket.handshake.headers['x-forwarded-for'] || socket.handshake.address}`); + try { + let ip = socket.handshake.headers['x-forwarded-for'] || socket.handshake.address; + ip = ip.split(",")[0]; + let info = geoip().city(ip); + socket.handshake.query.sessionInfo.userCountry = info.country.isoCode; + socket.handshake.query.sessionInfo.userCity = info.city.names.en; + socket.handshake.query.sessionInfo.userState = info.subdivisions.length > 0 ? info.subdivisions[0].names.en : null; + } catch (e) { + logger.debug(`geoip-country failed: ${e}`); + } + } + } +} + +function errorHandler(listenerName, error) { + logger.error(`Error detected from ${listenerName}\n${error}`); +} + +const JWT_TOKEN_PREFIX = "Bearer "; + +function check(socket, next) { + if (socket.handshake.query.identity === IDENTITIES.session) { + return next(); + } + if (socket.handshake.query.peerId && socket.handshake.auth && socket.handshake.auth.token) { + let token = socket.handshake.auth.token; + if (token.startsWith(JWT_TOKEN_PREFIX)) { + token = token.substring(JWT_TOKEN_PREFIX.length); + } + jwt.verify(token, process.env.ASSIST_JWT_SECRET, (err, decoded) => { + logger.debug(`JWT payload: ${decoded}`); + if (err) { + logger.debug(err); + return next(new Error('Authentication error')); + } + const {projectKey, sessionId} = extractPeerId(socket.handshake.query.peerId); + if (!projectKey || !sessionId) { + logger.debug(`Missing attribute: projectKey:${projectKey}, sessionId:${sessionId}`); + return next(new Error('Authentication error')); + } + if (String(projectKey) !== String(decoded.projectKey) || String(sessionId) !== String(decoded.sessionId)) { + logger.debug(`Trying to access projectKey:${projectKey} instead of ${decoded.projectKey} or + to sessionId:${sessionId} instead of ${decoded.sessionId}`); + return next(new Error('Authorization error')); + } + socket.decoded = decoded; + return next(); + }); + } else { + logger.debug(`something missing in handshake: ${socket.handshake}`); + return next(new Error('Authentication error')); + } +} + +module.exports = { + processPeerInfo, + extractPeerId, + extractSessionInfo, + EVENTS_DEFINITION, + IDENTITIES, + errorHandler, + authorizer: {check} +}; \ No newline at end of file diff --git a/ee/assist-server/app/cache.js b/ee/assist-server/app/cache.js new file mode 100644 index 000000000..8a07e08dc --- /dev/null +++ b/ee/assist-server/app/cache.js @@ -0,0 +1,109 @@ +const {logger} = require('./logger'); +const {createClient} = require("redis"); +const crypto = require("crypto"); + +let redisClient; +const REDIS_URL = (process.env.REDIS_URL || "localhost:6379").replace(/((^\w+:|^)\/\/|^)/, 'redis://'); +redisClient = createClient({url: REDIS_URL}); +redisClient.on("error", (error) => logger.error(`Redis cache error : ${error}`)); +void redisClient.connect(); + +function generateNodeID() { + const buffer = crypto.randomBytes(8); + return "node_"+buffer.readBigUInt64BE(0).toString(); +} + +const PING_INTERVAL = parseInt(process.env.PING_INTERVAL_SECONDS) || 25; +const CACHE_REFRESH_INTERVAL = parseInt(process.env.CACHE_REFRESH_INTERVAL_SECONDS) || 10; +const pingInterval = PING_INTERVAL + PING_INTERVAL/2; +const cacheRefreshInterval = CACHE_REFRESH_INTERVAL + CACHE_REFRESH_INTERVAL/2; +const cacheRefreshIntervalMs = CACHE_REFRESH_INTERVAL * 1000; +let lastCacheUpdateTime = 0; +let cacheRefresher = null; +const nodeID = process.env.HOSTNAME || generateNodeID(); + +const addSessionToCache = async function (sessionID, sessionData) { + try { + await redisClient.set(`active_sessions:${sessionID}`, JSON.stringify(sessionData), 'EX', pingInterval); + logger.debug(`Session ${sessionID} stored in Redis`); + } catch (error) { + logger.error(error); + } +} + +const renewSession = async function (sessionID){ + try { + await redisClient.expire(`active_sessions:${sessionID}`, pingInterval); + logger.debug(`Session ${sessionID} renewed in Redis`); + } catch (error) { + logger.error(error); + } +} + +const getSessionFromCache = async function (sessionID) { + try { + const sessionData = await redisClient.get(`active_sessions:${sessionID}`); + if (sessionData) { + logger.debug(`Session ${sessionID} retrieved from Redis`); + return JSON.parse(sessionData); + } + return null; + } catch (error) { + logger.error(error); + return null; + } +} + +const removeSessionFromCache = async function (sessionID) { + try { + await redisClient.del(`active_sessions:${sessionID}`); + logger.debug(`Session ${sessionID} removed from Redis`); + } catch (error) { + logger.error(error); + } +} + +const setNodeSessions = async function (nodeID, sessionIDs) { + try { + await redisClient.set(`node:${nodeID}:sessions`, JSON.stringify(sessionIDs), 'EX', cacheRefreshInterval); + logger.debug(`Node ${nodeID} sessions stored in Redis`); + } catch (error) { + logger.error(error); + } +} + +function startCacheRefresher(io) { + if (cacheRefresher) clearInterval(cacheRefresher); + + cacheRefresher = setInterval(async () => { + const now = Date.now(); + if (now - lastCacheUpdateTime < cacheRefreshIntervalMs) { + return; + } + logger.debug('Background refresh triggered'); + try { + const startTime = performance.now(); + const sessionIDs = new Set(); + const result = await io.fetchSockets(); + result.forEach((socket) => { + if (socket.handshake.query.sessId) { + sessionIDs.add(socket.handshake.query.sessId); + } + }) + await setNodeSessions(nodeID, Array.from(sessionIDs)); + lastCacheUpdateTime = now; + const duration = performance.now() - startTime; + logger.info(`Background refresh complete: ${duration}ms, ${result.length} sockets`); + } catch (error) { + logger.error(`Background refresh error: ${error}`); + } + }, cacheRefreshIntervalMs / 2); +} + +module.exports = { + addSessionToCache, + renewSession, + getSessionFromCache, + removeSessionFromCache, + startCacheRefresher, +} \ No newline at end of file diff --git a/ee/assist-server/app/geoIP.js b/ee/assist-server/app/geoIP.js new file mode 100644 index 000000000..69445c4fe --- /dev/null +++ b/ee/assist-server/app/geoIP.js @@ -0,0 +1,21 @@ +const geoip2Reader = require('@maxmind/geoip2-node').Reader; +const {logger} = require('./logger'); + +let geoip = null; +if (process.env.MAXMINDDB_FILE !== undefined) { + geoip2Reader.open(process.env.MAXMINDDB_FILE, {}) + .then(reader => { + geoip = reader; + }) + .catch(error => { + logger.error(`Error while opening the MAXMINDDB_FILE, err: ${error}`); + }); +} else { + logger.error("!!! please provide a valid value for MAXMINDDB_FILE env var."); +} + +module.exports = { + geoip: () => { + return geoip; + } +} \ No newline at end of file diff --git a/ee/assist-server/app/logger.js b/ee/assist-server/app/logger.js new file mode 100644 index 000000000..c7a541dfd --- /dev/null +++ b/ee/assist-server/app/logger.js @@ -0,0 +1,23 @@ +const winston = require('winston'); + +const isDebugMode = process.env.debug === "1"; +const logLevel = isDebugMode ? 'debug' : 'info'; + +const logger = winston.createLogger({ + level: logLevel, + format: winston.format.combine( + winston.format.timestamp({ + format: 'YYYY-MM-DD HH:mm:ss.SSS' // The same format as in backend services + }), + winston.format.errors({stack: true}), + winston.format.json() + ), + defaultMeta: {service: process.env.SERVICE_NAME || 'assist'}, + transports: [ + new winston.transports.Console(), + ], +}); + +module.exports = { + logger, +} diff --git a/ee/assist-server/app/socket.js b/ee/assist-server/app/socket.js new file mode 100644 index 000000000..28005a2fc --- /dev/null +++ b/ee/assist-server/app/socket.js @@ -0,0 +1,254 @@ +const { + processPeerInfo, + IDENTITIES, + EVENTS_DEFINITION, + extractSessionInfo, + errorHandler +} = require("./assist"); +const { + addSessionToCache, + renewSession, + removeSessionFromCache +} = require('./cache'); +const { + logger +} = require('./logger'); +const deepMerge = require('@fastify/deepmerge')({all: true}); + +let io; + +const setSocketIOServer = function (server) { + io = server; +} + +function sendFrom(from, to, eventName, ...data) { + from.to(to).emit(eventName, ...data); +} + +function sendTo(to, eventName, ...data) { + sendFrom(io, to, eventName, ...data); +} + +const fetchSockets = async function (roomID) { + if (!io) { + return []; + } + try { + if (roomID) { + return await io.in(roomID).fetchSockets(); + } else { + return await io.fetchSockets(); + } + } catch (error) { + logger.error('Error fetching sockets:', error); + return []; + } +} + +const findSessionSocketId = async (roomId, tabId) => { + let pickFirstSession = tabId === undefined; + const connected_sockets = await fetchSockets(roomId); + for (let socket of connected_sockets) { + if (socket.handshake.query.identity === IDENTITIES.session) { + if (pickFirstSession) { + return socket.id; + } else if (socket.handshake.query.tabId === tabId) { + return socket.id; + } + } + } + return null; +}; + +async function getRoomData(roomID) { + let tabsCount = 0, agentsCount = 0, tabIDs = [], agentIDs = []; + const connected_sockets = await fetchSockets(roomID); + if (connected_sockets.length > 0) { + for (let socket of connected_sockets) { + if (socket.handshake.query.identity === IDENTITIES.session) { + tabsCount++; + tabIDs.push(socket.handshake.query.tabId); + } else { + agentsCount++; + agentIDs.push(socket.id); + } + } + } else { + tabsCount = -1; + agentsCount = -1; + } + return {tabsCount, agentsCount, tabIDs, agentIDs}; +} + +async function onConnect(socket) { + logger.debug(`A new client:${socket.id}, Query:${JSON.stringify(socket.handshake.query)}`); + // Drop unknown socket.io connections + if (socket.handshake.query.identity === undefined || socket.handshake.query.peerId === undefined || socket.handshake.query.sessionInfo === undefined) { + logger.debug(`something is undefined, refusing connexion`); + return socket.disconnect(); + } + processPeerInfo(socket); + + const {tabsCount, agentsCount, tabIDs, agentIDs} = await getRoomData(socket.handshake.query.roomId); + + if (socket.handshake.query.identity === IDENTITIES.session) { + // Check if session with the same tabID already connected, if so, refuse new connexion + if (tabsCount > 0) { + for (let tab of tabIDs) { + if (tab === socket.handshake.query.tabId) { + logger.debug(`session already connected, refusing new connexion, peerId: ${socket.handshake.query.peerId}`); + sendTo(socket.id, EVENTS_DEFINITION.emit.SESSION_ALREADY_CONNECTED); + return socket.disconnect(); + } + } + } + extractSessionInfo(socket); + if (tabsCount < 0) { + // New session creates new room + } + // Inform all connected agents about reconnected session + if (agentsCount > 0) { + logger.debug(`notifying new session about agent-existence`); + sendTo(socket.id, EVENTS_DEFINITION.emit.AGENTS_CONNECTED, agentIDs); + sendFrom(socket, socket.handshake.query.roomId, EVENTS_DEFINITION.emit.SESSION_RECONNECTED, socket.id); + } + } else if (tabsCount <= 0) { + logger.debug(`notifying new agent about no SESSIONS with peerId:${socket.handshake.query.peerId}`); + sendTo(socket.id, EVENTS_DEFINITION.emit.NO_SESSIONS); + } + + await socket.join(socket.handshake.query.roomId); + logger.debug(`${socket.id} joined room:${socket.handshake.query.roomId}, as:${socket.handshake.query.identity}, connections:${agentsCount + tabsCount + 1}`) + + // Add session to cache + if (socket.handshake.query.identity === IDENTITIES.session) { + await addSessionToCache(socket.handshake.query.sessId, socket.handshake.query.sessionInfo); + } + + if (socket.handshake.query.identity === IDENTITIES.agent) { + if (socket.handshake.query.agentInfo !== undefined) { + socket.handshake.query.agentInfo = JSON.parse(socket.handshake.query.agentInfo); + socket.handshake.query.agentID = socket.handshake.query.agentInfo.id; + } + sendFrom(socket, socket.handshake.query.roomId, EVENTS_DEFINITION.emit.NEW_AGENT, socket.id, socket.handshake.query.agentInfo); + } + + socket.conn.on("packet", (packet) => { + if (packet.type === 'pong') { + renewSession(socket.handshake.query.sessId); + } + }); + + // Set disconnect handler + socket.on('disconnect', () => onDisconnect(socket)); + + // Handle update event + socket.on(EVENTS_DEFINITION.listen.UPDATE_EVENT, (...args) => onUpdateEvent(socket, ...args)); + + // Handle webrtc events + socket.on(EVENTS_DEFINITION.listen.WEBRTC_AGENT_CALL, (...args) => onWebrtcAgentHandler(socket, ...args)); + + // Handle errors + socket.on(EVENTS_DEFINITION.listen.ERROR, err => errorHandler(EVENTS_DEFINITION.listen.ERROR, err)); + socket.on(EVENTS_DEFINITION.listen.CONNECT_ERROR, err => errorHandler(EVENTS_DEFINITION.listen.CONNECT_ERROR, err)); + socket.on(EVENTS_DEFINITION.listen.CONNECT_FAILED, err => errorHandler(EVENTS_DEFINITION.listen.CONNECT_FAILED, err)); + + // Handle all other events (usually dom's mutations and user's actions) + socket.onAny((eventName, ...args) => onAny(socket, eventName, ...args)); +} + +async function onDisconnect(socket) { + logger.debug(`${socket.id} disconnected from ${socket.handshake.query.roomId}`); + + if (socket.handshake.query.identity === IDENTITIES.agent) { + sendFrom(socket, socket.handshake.query.roomId, EVENTS_DEFINITION.emit.AGENT_DISCONNECT, socket.id); + } + logger.debug("checking for number of connected agents and sessions"); + let {tabsCount, agentsCount, tabIDs, agentIDs} = await getRoomData(socket.handshake.query.roomId); + + if (tabsCount <= 0) { + await removeSessionFromCache(socket.handshake.query.sessId); + } + + if (tabsCount === -1 && agentsCount === -1) { + logger.debug(`room not found: ${socket.handshake.query.roomId}`); + return; + } + if (tabsCount === 0) { + logger.debug(`notifying everyone in ${socket.handshake.query.roomId} about no SESSIONS`); + sendFrom(socket, socket.handshake.query.roomId, EVENTS_DEFINITION.emit.NO_SESSIONS); + } + if (agentsCount === 0) { + logger.debug(`notifying everyone in ${socket.handshake.query.roomId} about no AGENTS`); + sendFrom(socket, socket.handshake.query.roomId, EVENTS_DEFINITION.emit.NO_AGENTS); + } +} + +async function onUpdateEvent(socket, ...args) { + logger.debug(`${socket.id} sent update event.`); + if (socket.handshake.query.identity !== IDENTITIES.session) { + logger.debug('Ignoring update event.'); + return + } + + args[0] = updateSessionData(socket, args[0]) + socket.handshake.query.sessionInfo = deepMerge(socket.handshake.query.sessionInfo, args[0]?.data, {tabId: args[0]?.meta?.tabId}); + + // update session cache + await addSessionToCache(socket.handshake.query.sessId, socket.handshake.query.sessionInfo); + + // Update sessionInfo for all agents in the room + const connected_sockets = await fetchSockets(socket.handshake.query.roomId); + for (let item of connected_sockets) { + if (item.handshake.query.identity === IDENTITIES.session && item.handshake.query.sessionInfo) { + item.handshake.query.sessionInfo = deepMerge(item.handshake.query.sessionInfo, args[0]?.data, {tabId: args[0]?.meta?.tabId}); + } else if (item.handshake.query.identity === IDENTITIES.agent) { + sendFrom(socket, item.id, EVENTS_DEFINITION.emit.UPDATE_EVENT, args[0]); + } + } +} + +async function onWebrtcAgentHandler(socket, ...args) { + if (socket.handshake.query.identity === IDENTITIES.agent) { + const agentIdToConnect = args[0]?.data?.toAgentId; + logger.debug(`${socket.id} sent webrtc event to agent:${agentIdToConnect}`); + if (agentIdToConnect && socket.handshake.sessionData.AGENTS_CONNECTED.includes(agentIdToConnect)) { + sendFrom(socket, agentIdToConnect, EVENTS_DEFINITION.listen.WEBRTC_AGENT_CALL, args[0]); + } + } +} + +async function onAny(socket, eventName, ...args) { + if (Object.values(EVENTS_DEFINITION.listen).indexOf(eventName) >= 0) { + logger.debug(`received event:${eventName}, should be handled by another listener, stopping onAny.`); + return + } + args[0] = updateSessionData(socket, args[0]) + if (socket.handshake.query.identity === IDENTITIES.session) { + logger.debug(`received event:${eventName}, from:${socket.handshake.query.identity}, sending message to room:${socket.handshake.query.roomId}`); + sendFrom(socket, socket.handshake.query.roomId, eventName, args[0]); + } else { + logger.debug(`received event:${eventName}, from:${socket.handshake.query.identity}, sending message to session of room:${socket.handshake.query.roomId}`); + let socketId = await findSessionSocketId(socket.handshake.query.roomId, args[0]?.meta?.tabId); + if (socketId === null) { + logger.debug(`session not found for:${socket.handshake.query.roomId}`); + sendTo(socket.id, EVENTS_DEFINITION.emit.NO_SESSIONS); + } else { + logger.debug("message sent"); + sendTo(socket.id, eventName, socket.id, args[0]); + } + } +} + +// Back compatibility (add top layer with meta information) +function updateSessionData(socket, sessionData) { + if (sessionData?.meta === undefined && socket.handshake.query.identity === IDENTITIES.session) { + sessionData = {meta: {tabId: socket.handshake.query.tabId, version: 1}, data: sessionData}; + } + return sessionData +} + +module.exports = { + onConnect, + setSocketIOServer, +} diff --git a/ee/assist-server/package-lock.json b/ee/assist-server/package-lock.json new file mode 100644 index 000000000..8d8f17e93 --- /dev/null +++ b/ee/assist-server/package-lock.json @@ -0,0 +1,1761 @@ +{ + "name": "assist-server", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "assist-server", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@fastify/deepmerge": "^3.0.0", + "@maxmind/geoip2-node": "^6.0.0", + "express": "^4.21.2", + "jsonwebtoken": "^9.0.2", + "redis": "^4.7.0", + "socket.io": "^4.8.1", + "socket.io-client": "^4.8.1", + "ua-parser-js": "^2.0.3", + "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.51.0", + "winston": "^3.17.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@fastify/deepmerge": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@fastify/deepmerge/-/deepmerge-3.0.0.tgz", + "integrity": "sha512-VW7srTEkCzbfqoRBn+k7s/nP9WVFmKSfeHBbMZLLrOYDQoShHWuEi5V6Jgz1Q0fm3pMYsiC+EVMvhZyI3hKzkw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] + }, + "node_modules/@maxmind/geoip2-node": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@maxmind/geoip2-node/-/geoip2-node-6.0.0.tgz", + "integrity": "sha512-b1zcdxRm13HDgNfHnRpET7E3r1NlSK+VpQhN9dKn8aUUH71Ug6rNmgASYh7WrHDZLDqSXMHryfs5Aw0ufMtBYw==", + "dependencies": { + "maxmind": "^4.2.0" + } + }, + "node_modules/@redis/bloom": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", + "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/client": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.0.tgz", + "integrity": "sha512-aR0uffYI700OEEH4gYnitAnv3vzVGXCFvYfdpu/CJKvk4pHfLPEy/JSZyrpQ+15WhXe1yJRXLtfQ84s4mEXnPg==", + "dependencies": { + "cluster-key-slot": "1.1.2", + "generic-pool": "3.9.0", + "yallist": "4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@redis/graph": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.1.tgz", + "integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/json": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.7.tgz", + "integrity": "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/search": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.2.0.tgz", + "integrity": "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/time-series": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.1.0.tgz", + "integrity": "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "22.13.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.14.tgz", + "integrity": "sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w==", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/color-string": { + "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" + } + }, + "node_modules/colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "dependencies": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-europe-js": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/detect-europe-js/-/detect-europe-js-0.1.2.tgz", + "integrity": "sha512-lgdERlL3u0aUdHocoouzT10d9I89VVhk0qNRmll7mXdGfJT1/wqZ2ZLA4oJAjeACPY5fT1wsbq2AT+GkuInsow==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + } + ] + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/engine.io": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", + "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", + "dependencies": { + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-client": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz", + "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io-client/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generic-pool": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", + "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, + "node_modules/is-standalone-pwa": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-standalone-pwa/-/is-standalone-pwa-0.1.1.tgz", + "integrity": "sha512-9Cbovsa52vNQCjdXOzeQq5CnCbAcRk05aU62K20WO372NrTv0NxibLFCK6lQ4/iZEFdEA3p3t2VNOn8AJ53F5g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + } + ] + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/logform/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/maxmind": { + "version": "4.3.24", + "resolved": "https://registry.npmjs.org/maxmind/-/maxmind-4.3.24.tgz", + "integrity": "sha512-dexrLcjfS2xDGOvdV8XcfQYmyQVpGidMwEG2ld19lXlsB+i+lXRWPzQi81HfwRXR4hxzFr5gT0oAIFyqAAb/Ww==", + "dependencies": { + "mmdb-lib": "2.1.1", + "tiny-lru": "11.2.11" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mmdb-lib": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/mmdb-lib/-/mmdb-lib-2.1.1.tgz", + "integrity": "sha512-yx8H/1H5AfnufiLnzzPqPf4yr/dKU9IFT1rPVwSkrKWHsQEeVVd6+X+L0nUbXhlEFTu3y/7hu38CFmEVgzvyeg==", + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "dependencies": { + "fn.name": "1.x.x" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/redis": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.7.0.tgz", + "integrity": "sha512-zvmkHEAdGMn+hMRXuMBtu4Vo5P6rHQjLoHftu+lBqq8ZTA3RCVC/WzD790bkKKiNFp7d5/9PcSD19fJyyRvOdQ==", + "dependencies": { + "@redis/bloom": "1.2.0", + "@redis/client": "1.6.0", + "@redis/graph": "1.1.1", + "@redis/json": "1.0.7", + "@redis/search": "1.2.0", + "@redis/time-series": "1.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dependencies": { + "debug": "2.6.9", + "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": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/socket.io": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-adapter/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-adapter/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/socket.io-client": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-client/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/socket.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "engines": { + "node": "*" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, + "node_modules/tiny-lru": { + "version": "11.2.11", + "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-11.2.11.tgz", + "integrity": "sha512-27BIW0dIWTYYoWNnqSmoNMKe5WIbkXsc0xaCQHd3/3xT2XMuMJrzHdrO9QBFR14emBz1Bu0dOAs2sCBBrvgPQA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ua-is-frozen": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ua-is-frozen/-/ua-is-frozen-0.1.2.tgz", + "integrity": "sha512-RwKDW2p3iyWn4UbaxpP2+VxwqXh0jpvdxsYpZ5j/MLLiQOfbsV5shpgQiw93+KMYQPcteeMQ289MaAFzs3G9pw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + } + ] + }, + "node_modules/ua-parser-js": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-2.0.3.tgz", + "integrity": "sha512-LZyXZdNttONW8LjzEH3Z8+6TE7RfrEiJqDKyh0R11p/kxvrV2o9DrT2FGZO+KVNs3k+drcIQ6C3En6wLnzJGpw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "dependencies": { + "@types/node-fetch": "^2.6.12", + "detect-europe-js": "^0.1.2", + "is-standalone-pwa": "^0.1.1", + "node-fetch": "^2.7.0", + "ua-is-frozen": "^0.1.2" + }, + "bin": { + "ua-parser-js": "script/cli.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uWebSockets.js": { + "version": "20.51.0", + "resolved": "git+ssh://git@github.com/uNetworking/uWebSockets.js.git#6609a88ffa9a16ac5158046761356ce03250a0df", + "license": "Apache-2.0" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/winston": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz", + "integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } +} diff --git a/ee/assist-server/package.json b/ee/assist-server/package.json new file mode 100644 index 000000000..50c1fbf95 --- /dev/null +++ b/ee/assist-server/package.json @@ -0,0 +1,24 @@ +{ + "name": "assist-server", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@fastify/deepmerge": "^3.0.0", + "@maxmind/geoip2-node": "^6.0.0", + "express": "^4.21.2", + "jsonwebtoken": "^9.0.2", + "redis": "^4.7.0", + "socket.io": "^4.8.1", + "socket.io-client": "^4.8.1", + "ua-parser-js": "^2.0.3", + "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.51.0", + "winston": "^3.17.0" + } +} diff --git a/ee/assist-server/server.js b/ee/assist-server/server.js new file mode 100644 index 000000000..c143ab0d4 --- /dev/null +++ b/ee/assist-server/server.js @@ -0,0 +1,67 @@ +const { App } = require('uWebSockets.js'); +const { Server } = require('socket.io'); +const { logger } = require("./app/logger"); +const { authorizer } = require("./app/assist"); +const { onConnect, setSocketIOServer } = require("./app/socket"); +const { startCacheRefresher } = require("./app/cache"); + +const app = App(); +const pingInterval = parseInt(process.env.PING_INTERVAL) || 25000; + +const getCompressionConfig = function () { + // WS: The theoretical overhead per socket is 19KB (11KB for compressor and 8KB for decompressor) + let perMessageDeflate = false; + if (process.env.COMPRESSION === "true") { + logger.info(`WS compression: enabled`); + perMessageDeflate = { + zlibDeflateOptions: { + windowBits: 10, + memLevel: 1 + }, + zlibInflateOptions: { + windowBits: 10 + } + } + } else { + logger.info(`WS compression: disabled`); + } + return { + perMessageDeflate: perMessageDeflate, + clientNoContextTakeover: true + }; +} + +const io = new Server({ + maxHttpBufferSize: (parseFloat(process.env.maxHttpBufferSize) || 5) * 1e6, + pingInterval: pingInterval, // Will use it for cache invalidation + cors: { + origin: "*", // Allow connections from any origin (for development) + methods: ["GET", "POST"], + credentials: true + }, + path: '/socket', + ...getCompressionConfig() +}); + +io.use(async (socket, next) => await authorizer.check(socket, next)); +io.on('connection', (socket) => onConnect(socket)); +io.attachApp(app); +io.engine.on("headers", (headers) => { + headers["x-host-id"] = process.env.HOSTNAME || "unknown"; +}); +setSocketIOServer(io); + +const HOST = process.env.LISTEN_HOST || '0.0.0.0'; +const PORT = parseInt(process.env.PORT) || 9001; +app.listen(PORT, (token) => { + if (token) { + console.log(`Server running at http://${HOST}:${PORT}`); + } else { + console.log(`Failed to listen on port ${PORT}`); + } +}); +startCacheRefresher(io); + +process.on('uncaughtException', err => { + logger.error(`Uncaught Exception: ${err}`); +}); \ No newline at end of file diff --git a/ee/backend/pkg/sessions/storage.go b/ee/backend/pkg/sessions/storage.go index 41602c42a..e56dfbad8 100644 --- a/ee/backend/pkg/sessions/storage.go +++ b/ee/backend/pkg/sessions/storage.go @@ -121,7 +121,16 @@ func (s *storageImpl) Get(sessionID uint64) (*Session, error) { // For the ender service only func (s *storageImpl) GetMany(sessionIDs []uint64) ([]*Session, error) { - rows, err := s.db.Query("SELECT session_id, COALESCE( duration, 0 ), start_ts FROM sessions WHERE session_id = ANY($1)", pq.Array(sessionIDs)) + rows, err := s.db.Query(` + SELECT + session_id, + CASE + WHEN duration IS NULL OR duration < 0 THEN 0 + ELSE duration + END, + start_ts + FROM sessions + WHERE session_id = ANY($1)`, pq.Array(sessionIDs)) if err != nil { return nil, err } diff --git a/ee/scripts/schema/db/init_dbs/clickhouse/1.23.0/1.23.0.sql b/ee/scripts/schema/db/init_dbs/clickhouse/1.23.0/1.23.0.sql new file mode 100644 index 000000000..5a7a6afed --- /dev/null +++ b/ee/scripts/schema/db/init_dbs/clickhouse/1.23.0/1.23.0.sql @@ -0,0 +1,168 @@ +CREATE OR REPLACE FUNCTION openreplay_version AS() -> 'v1.23.0-ee'; + + +DROP TABLE IF EXISTS product_analytics.all_events; +CREATE TABLE IF NOT EXISTS product_analytics.all_events +( + project_id UInt16, + auto_captured BOOL DEFAULT FALSE, + event_name String, + display_name String DEFAULT '', + description String DEFAULT '', + event_count_l30days UInt32 DEFAULT 0, + query_count_l30days UInt32 DEFAULT 0, + + created_at DateTime64, + _timestamp DateTime DEFAULT now() +) ENGINE = ReplacingMergeTree(_timestamp) + ORDER BY (project_id, auto_captured, event_name); + +CREATE MATERIALIZED VIEW IF NOT EXISTS product_analytics.all_events_extractor_mv + TO product_analytics.all_events AS +SELECT DISTINCT ON (project_id,auto_captured,event_name) project_id, + `$auto_captured` AS auto_captured, + `$event_name` AS event_name, + display_name, + description +FROM product_analytics.events + LEFT JOIN (SELECT project_id, + auto_captured, + event_name, + display_name, + description + FROM product_analytics.all_events + WHERE all_events.display_name != '' + OR all_events.description != '') AS old_data + ON (events.project_id = old_data.project_id AND events.`$auto_captured` = old_data.auto_captured AND + events.`$event_name` = old_data.event_name); + +CREATE TABLE IF NOT EXISTS product_analytics.event_properties +( + project_id UInt16, + event_name String, + property_name String, + value_type String, + + _timestamp DateTime DEFAULT now() +) ENGINE = ReplacingMergeTree(_timestamp) + ORDER BY (project_id, event_name, property_name, value_type); + +CREATE MATERIALIZED VIEW IF NOT EXISTS product_analytics.event_properties_extractor_mv + TO product_analytics.event_properties AS +SELECT project_id, + `$event_name` AS event_name, + property_name, + JSONType(JSONExtractRaw(toString(`$properties`), property_name)) AS value_type +FROM product_analytics.events + ARRAY JOIN JSONExtractKeys(toString(`$properties`)) as property_name; + +CREATE MATERIALIZED VIEW IF NOT EXISTS product_analytics.event_cproperties_extractor + TO product_analytics.event_properties AS +SELECT project_id, + `$event_name` AS event_name, + property_name, + JSONType(JSONExtractRaw(toString(`properties`), property_name)) AS value_type +FROM product_analytics.events + ARRAY JOIN JSONExtractKeys(toString(`properties`)) as property_name; + +DROP TABLE IF EXISTS product_analytics.all_properties; +CREATE TABLE IF NOT EXISTS product_analytics.all_properties +( + project_id UInt16, + property_name String, + is_event_property BOOL, + display_name String DEFAULT '', + description String DEFAULT '', + status String DEFAULT 'visible' COMMENT 'visible/hidden/dropped', + data_count UInt32 DEFAULT 1, + query_count UInt32 DEFAULT 0, + + created_at DateTime64, + _timestamp DateTime DEFAULT now() +) ENGINE = ReplacingMergeTree(_timestamp) + ORDER BY (project_id, property_name, is_event_property); + + +CREATE MATERIALIZED VIEW IF NOT EXISTS product_analytics.all_properties_extractor_mv + TO product_analytics.all_properties AS +SELECT project_id, + property_name, + TRUE AS is_event_property, + display_name, + description, + status, + data_count, + query_count +FROM product_analytics.events + ARRAY JOIN JSONExtractKeys(toString(`$properties`)) as property_name + LEFT JOIN (SELECT project_id, + property_name, + display_name, + description, + status, + data_count, + query_count + FROM product_analytics.all_properties + WHERE (all_properties.display_name != '' + OR all_properties.description != '') + AND is_event_property) AS old_data + ON (events.project_id = old_data.project_id AND property_name = old_data.property_name); + +CREATE MATERIALIZED VIEW IF NOT EXISTS product_analytics.all_cproperties_extractor_mv + TO product_analytics.all_properties AS +SELECT project_id, + property_name, + TRUE AS is_event_property, + display_name, + description, + status, + data_count, + query_count +FROM product_analytics.events + ARRAY JOIN JSONExtractKeys(toString(`properties`)) as property_name + LEFT JOIN (SELECT project_id, + property_name, + display_name, + description, + status, + data_count, + query_count + FROM product_analytics.all_properties + WHERE (all_properties.display_name != '' + OR all_properties.description != '') + AND is_event_property) AS old_data + ON (events.project_id = old_data.project_id AND property_name = old_data.property_name); + +CREATE TABLE IF NOT EXISTS product_analytics.property_values_samples +( + project_id UInt16, + property_name String, + is_event_property BOOL, + value String, + + _timestamp DateTime DEFAULT now() +) + ENGINE = ReplacingMergeTree(_timestamp) + ORDER BY (project_id, property_name, is_event_property); + +CREATE MATERIALIZED VIEW IF NOT EXISTS product_analytics.property_values_sampler_mv + REFRESH EVERY 30 HOUR TO product_analytics.property_values_samples AS +SELECT project_id, + property_name, + TRUE AS is_event_property, + JSONExtractString(toString(`$properties`), property_name) AS value +FROM product_analytics.events + ARRAY JOIN JSONExtractKeys(toString(`$properties`)) as property_name +WHERE randCanonical() < 0.5 -- This randomly skips inserts + AND value != '' +LIMIT 2 BY project_id,property_name +UNION ALL +SELECT project_id, + property_name, + TRUE AS is_event_property, + JSONExtractString(toString(`properties`), property_name) AS value +FROM product_analytics.events + ARRAY JOIN JSONExtractKeys(toString(`properties`)) as property_name +WHERE randCanonical() < 0.5 -- This randomly skips inserts + AND value != '' +LIMIT 2 BY project_id,property_name; diff --git a/ee/scripts/schema/db/init_dbs/clickhouse/create/init_schema.sql b/ee/scripts/schema/db/init_dbs/clickhouse/create/init_schema.sql index b3d156134..040b4b337 100644 --- a/ee/scripts/schema/db/init_dbs/clickhouse/create/init_schema.sql +++ b/ee/scripts/schema/db/init_dbs/clickhouse/create/init_schema.sql @@ -1,4 +1,4 @@ -CREATE OR REPLACE FUNCTION openreplay_version AS() -> 'v1.22.0-ee'; +CREATE OR REPLACE FUNCTION openreplay_version AS() -> 'v1.23.0-ee'; CREATE DATABASE IF NOT EXISTS experimental; CREATE TABLE IF NOT EXISTS experimental.autocomplete @@ -88,7 +88,7 @@ CREATE TABLE IF NOT EXISTS experimental.events ) ENGINE = ReplacingMergeTree(_timestamp) PARTITION BY toYYYYMM(datetime) ORDER BY (project_id, datetime, event_type, session_id, message_id) - TTL datetime + INTERVAL 3 MONTH; + TTL datetime + INTERVAL 1 MONTH; @@ -140,7 +140,7 @@ CREATE TABLE IF NOT EXISTS experimental.sessions ) ENGINE = ReplacingMergeTree(_timestamp) PARTITION BY toYYYYMMDD(datetime) ORDER BY (project_id, datetime, session_id) - TTL datetime + INTERVAL 3 MONTH + TTL datetime + INTERVAL 1 MONTH SETTINGS index_granularity = 512; CREATE TABLE IF NOT EXISTS experimental.user_favorite_sessions @@ -189,7 +189,7 @@ CREATE TABLE IF NOT EXISTS experimental.issues ) ENGINE = ReplacingMergeTree(_timestamp) PARTITION BY toYYYYMM(_timestamp) ORDER BY (project_id, issue_id, type) - TTL _timestamp + INTERVAL 3 MONTH; + TTL _timestamp + INTERVAL 1 MONTH; @@ -330,7 +330,7 @@ CREATE TABLE IF NOT EXISTS experimental.ios_events ) ENGINE = ReplacingMergeTree(_timestamp) PARTITION BY toYYYYMM(datetime) ORDER BY (project_id, datetime, event_type, session_id, message_id) - TTL datetime + INTERVAL 3 MONTH; + TTL datetime + INTERVAL 1 MONTH; SET allow_experimental_json_type = 1; @@ -639,9 +639,11 @@ CREATE TABLE IF NOT EXISTS product_analytics.group_properties -- The full list of events +-- Experimental: This table is filled by an incremental materialized view CREATE TABLE IF NOT EXISTS product_analytics.all_events ( project_id UInt16, + auto_captured BOOL DEFAULT FALSE, event_name String, display_name String DEFAULT '', description String DEFAULT '', @@ -651,10 +653,68 @@ CREATE TABLE IF NOT EXISTS product_analytics.all_events created_at DateTime64, _timestamp DateTime DEFAULT now() ) ENGINE = ReplacingMergeTree(_timestamp) - ORDER BY (project_id, event_name); + ORDER BY (project_id, auto_captured, event_name); + +-- ----------------- This is experimental, if it doesn't work, we need to do it in db worker ------------- +-- Incremental materialized view to fill all_events using $properties +CREATE MATERIALIZED VIEW IF NOT EXISTS product_analytics.all_events_extractor_mv + TO product_analytics.all_events AS +SELECT DISTINCT ON (project_id,auto_captured,event_name) project_id, + `$auto_captured` AS auto_captured, + `$event_name` AS event_name, + display_name, + description +FROM product_analytics.events + LEFT JOIN (SELECT project_id, + auto_captured, + event_name, + display_name, + description + FROM product_analytics.all_events + WHERE all_events.display_name != '' + OR all_events.description != '') AS old_data + ON (events.project_id = old_data.project_id AND events.`$auto_captured` = old_data.auto_captured AND + events.`$event_name` = old_data.event_name); +-- -------- END --------- + +-- The full list of event-properties (used to tell which property belongs to which event) +-- Experimental: This table is filled by an incremental materialized view +CREATE TABLE IF NOT EXISTS product_analytics.event_properties +( + project_id UInt16, + event_name String, + property_name String, + value_type String, + + _timestamp DateTime DEFAULT now() +) ENGINE = ReplacingMergeTree(_timestamp) + ORDER BY (project_id, event_name, property_name, value_type); + +-- ----------------- This is experimental, if it doesn't work, we need to do it in db worker ------------- +-- Incremental materialized view to fill event_properties using $properties +CREATE MATERIALIZED VIEW IF NOT EXISTS product_analytics.event_properties_extractor_mv + TO product_analytics.event_properties AS +SELECT project_id, + `$event_name` AS event_name, + property_name, + JSONType(JSONExtractRaw(toString(`$properties`), property_name)) AS value_type +FROM product_analytics.events + ARRAY JOIN JSONExtractKeys(toString(`$properties`)) as property_name; + +-- Incremental materialized view to fill event_properties using properties +CREATE MATERIALIZED VIEW IF NOT EXISTS product_analytics.event_cproperties_extractor + TO product_analytics.event_properties AS +SELECT project_id, + `$event_name` AS event_name, + property_name, + JSONType(JSONExtractRaw(toString(`properties`), property_name)) AS value_type +FROM product_analytics.events + ARRAY JOIN JSONExtractKeys(toString(`properties`)) as property_name; +-- -------- END --------- -- The full list of properties (events and users) +-- Experimental: This table is filled by an incremental materialized view CREATE TABLE IF NOT EXISTS product_analytics.all_properties ( project_id UInt16, @@ -670,3 +730,95 @@ CREATE TABLE IF NOT EXISTS product_analytics.all_properties _timestamp DateTime DEFAULT now() ) ENGINE = ReplacingMergeTree(_timestamp) ORDER BY (project_id, property_name, is_event_property); + + +-- ----------------- This is experimental, if it doesn't work, we need to do it in db worker ------------- +-- Incremental materialized view to fill all_properties using $properties +CREATE MATERIALIZED VIEW IF NOT EXISTS product_analytics.all_properties_extractor_mv + TO product_analytics.all_properties AS +SELECT project_id, + property_name, + TRUE AS is_event_property, + display_name, + description, + status, + data_count, + query_count +FROM product_analytics.events + ARRAY JOIN JSONExtractKeys(toString(`$properties`)) as property_name + LEFT JOIN (SELECT project_id, + property_name, + display_name, + description, + status, + data_count, + query_count + FROM product_analytics.all_properties + WHERE (all_properties.display_name != '' + OR all_properties.description != '') + AND is_event_property) AS old_data + ON (events.project_id = old_data.project_id AND property_name = old_data.property_name); + +-- Incremental materialized view to fill all_properties using properties +CREATE MATERIALIZED VIEW IF NOT EXISTS product_analytics.all_cproperties_extractor_mv + TO product_analytics.all_properties AS +SELECT project_id, + property_name, + TRUE AS is_event_property, + display_name, + description, + status, + data_count, + query_count +FROM product_analytics.events + ARRAY JOIN JSONExtractKeys(toString(`properties`)) as property_name + LEFT JOIN (SELECT project_id, + property_name, + display_name, + description, + status, + data_count, + query_count + FROM product_analytics.all_properties + WHERE (all_properties.display_name != '' + OR all_properties.description != '') + AND is_event_property) AS old_data + ON (events.project_id = old_data.project_id AND property_name = old_data.property_name); +-- -------- END --------- + +-- Some random examples of property-values, limited by 2 per property +-- Experimental: This table is filled by a refreshable materialized view +CREATE TABLE IF NOT EXISTS product_analytics.property_values_samples +( + project_id UInt16, + property_name String, + is_event_property BOOL, + value String, + + _timestamp DateTime DEFAULT now() +) + ENGINE = ReplacingMergeTree(_timestamp) + ORDER BY (project_id, property_name, is_event_property); +-- Incremental materialized view to get random examples of property values using $properties & properties +CREATE MATERIALIZED VIEW IF NOT EXISTS product_analytics.property_values_sampler_mv + REFRESH EVERY 30 HOUR TO product_analytics.property_values_samples AS +SELECT project_id, + property_name, + TRUE AS is_event_property, + JSONExtractString(toString(`$properties`), property_name) AS value +FROM product_analytics.events + ARRAY JOIN JSONExtractKeys(toString(`$properties`)) as property_name +WHERE randCanonical() < 0.5 -- This randomly skips inserts + AND value != '' +LIMIT 2 BY project_id,property_name +UNION ALL +-- using union because each table should be the target of 1 single refreshable MV +SELECT project_id, + property_name, + TRUE AS is_event_property, + JSONExtractString(toString(`properties`), property_name) AS value +FROM product_analytics.events + ARRAY JOIN JSONExtractKeys(toString(`properties`)) as property_name +WHERE randCanonical() < 0.5 -- This randomly skips inserts + AND value != '' +LIMIT 2 BY project_id,property_name; diff --git a/ee/scripts/schema/db/init_dbs/postgresql/1.23.0/1.23.0.sql b/ee/scripts/schema/db/init_dbs/postgresql/1.23.0/1.23.0.sql new file mode 100644 index 000000000..957be6dfd --- /dev/null +++ b/ee/scripts/schema/db/init_dbs/postgresql/1.23.0/1.23.0.sql @@ -0,0 +1,30 @@ +\set previous_version 'v1.22.0-ee' +\set next_version 'v1.23.0-ee' +SELECT openreplay_version() AS current_version, + openreplay_version() = :'previous_version' AS valid_previous, + openreplay_version() = :'next_version' AS is_next +\gset + +\if :valid_previous +\echo valid previous DB version :'previous_version', starting DB upgrade to :'next_version' +BEGIN; +SELECT format($fn_def$ +CREATE OR REPLACE FUNCTION openreplay_version() + RETURNS text AS +$$ +SELECT '%1$s' +$$ LANGUAGE sql IMMUTABLE; +$fn_def$, :'next_version') +\gexec + +-- + + + +COMMIT; + +\elif :is_next +\echo new version detected :'next_version', nothing to do +\else +\warn skipping DB upgrade of :'next_version', expected previous version :'previous_version', found :'current_version' +\endif diff --git a/ee/scripts/schema/db/init_dbs/postgresql/init_schema.sql b/ee/scripts/schema/db/init_dbs/postgresql/init_schema.sql index af64c00d4..0fc74fb1c 100644 --- a/ee/scripts/schema/db/init_dbs/postgresql/init_schema.sql +++ b/ee/scripts/schema/db/init_dbs/postgresql/init_schema.sql @@ -1,4 +1,4 @@ -\set or_version 'v1.22.0-ee' +\set or_version 'v1.23.0-ee' SET client_min_messages TO NOTICE; \set ON_ERROR_STOP true SELECT EXISTS (SELECT 1 diff --git a/ee/scripts/schema/db/rollback_dbs/clickhouse/1.23.0/1.23.0.sql b/ee/scripts/schema/db/rollback_dbs/clickhouse/1.23.0/1.23.0.sql new file mode 100644 index 000000000..bb17fac9f --- /dev/null +++ b/ee/scripts/schema/db/rollback_dbs/clickhouse/1.23.0/1.23.0.sql @@ -0,0 +1,3 @@ +CREATE OR REPLACE FUNCTION openreplay_version AS() -> 'v1.22.0-ee'; + +DROP TABLE IF EXISTS product_analytics.event_properties; \ No newline at end of file diff --git a/ee/scripts/schema/db/rollback_dbs/postgresql/1.23.0/1.23.0.sql b/ee/scripts/schema/db/rollback_dbs/postgresql/1.23.0/1.23.0.sql new file mode 100644 index 000000000..ba1cecc07 --- /dev/null +++ b/ee/scripts/schema/db/rollback_dbs/postgresql/1.23.0/1.23.0.sql @@ -0,0 +1,27 @@ +\set previous_version 'v1.23.0-ee' +\set next_version 'v1.22.0-ee' +SELECT openreplay_version() AS current_version, + openreplay_version() = :'previous_version' AS valid_previous, + openreplay_version() = :'next_version' AS is_next +\gset + +\if :valid_previous +\echo valid previous DB version :'previous_version', starting DB downgrade to :'next_version' +BEGIN; +SELECT format($fn_def$ +CREATE OR REPLACE FUNCTION openreplay_version() + RETURNS text AS +$$ +SELECT '%1$s' +$$ LANGUAGE sql IMMUTABLE; +$fn_def$, :'next_version') +\gexec + + +COMMIT; + +\elif :is_next +\echo new version detected :'next_version', nothing to do +\else +\warn skipping DB downgrade of :'next_version', expected previous version :'previous_version', found :'current_version' +\endif \ No newline at end of file diff --git a/frontend/.env.sample b/frontend/.env.sample index 407c27a20..42852e0a5 100644 --- a/frontend/.env.sample +++ b/frontend/.env.sample @@ -22,5 +22,5 @@ MINIO_ACCESS_KEY = '' MINIO_SECRET_KEY = '' # APP and TRACKER VERSIONS -VERSION = 1.22.0 -TRACKER_VERSION = '16.0.1' +VERSION = 1.23.0 +TRACKER_VERSION = '17.0.0' diff --git a/frontend/app/components/Client/Integrations/ProfilerDoc/ProfilerDoc.tsx b/frontend/app/components/Client/Integrations/ProfilerDoc/ProfilerDoc.tsx index eb6013a7c..6b0f5b930 100644 --- a/frontend/app/components/Client/Integrations/ProfilerDoc/ProfilerDoc.tsx +++ b/frontend/app/components/Client/Integrations/ProfilerDoc/ProfilerDoc.tsx @@ -16,10 +16,10 @@ function ProfilerDoc() { ? sites.find((site) => site.id === siteId)?.projectKey : sites[0]?.projectKey; - const usage = `import OpenReplay from '@openreplay/tracker'; + const usage = `import { tracker } from '@openreplay/tracker'; import trackerProfiler from '@openreplay/tracker-profiler'; //... -const tracker = new OpenReplay({ +tracker.configure({ projectKey: '${projectKey}' }); tracker.start() @@ -29,10 +29,12 @@ export const profiler = tracker.use(trackerProfiler()); const fn = profiler('call_name')(() => { //... }, thisArg); // thisArg is optional`; - const usageCjs = `import OpenReplay from '@openreplay/tracker/cjs'; + const usageCjs = `import { tracker } from '@openreplay/tracker/cjs'; +// alternatively you can use dynamic import without /cjs suffix to prevent issues with window scope + import trackerProfiler from '@openreplay/tracker-profiler/cjs'; //... -const tracker = new OpenReplay({ +tracker.configure({ projectKey: '${projectKey}' }); //... diff --git a/frontend/app/components/Client/Integrations/Tracker/AssistDoc/AssistNpm.tsx b/frontend/app/components/Client/Integrations/Tracker/AssistDoc/AssistNpm.tsx index 30893ba95..a333441b9 100644 --- a/frontend/app/components/Client/Integrations/Tracker/AssistDoc/AssistNpm.tsx +++ b/frontend/app/components/Client/Integrations/Tracker/AssistDoc/AssistNpm.tsx @@ -7,17 +7,19 @@ import { useTranslation } from 'react-i18next'; function AssistNpm(props) { const { t } = useTranslation(); - const usage = `import OpenReplay from '@openreplay/tracker'; + const usage = `import { tracker } from '@openreplay/tracker'; import trackerAssist from '@openreplay/tracker-assist'; -const tracker = new OpenReplay({ +tracker.configure({ projectKey: '${props.projectKey}', }); tracker.start() tracker.use(trackerAssist(options)); // check the list of available options below`; - const usageCjs = `import OpenReplay from '@openreplay/tracker/cjs'; + const usageCjs = `import { tracker } from '@openreplay/tracker/cjs'; +// alternatively you can use dynamic import without /cjs suffix to prevent issues with window scope import trackerAssist from '@openreplay/tracker-assist/cjs'; -const tracker = new OpenReplay({ + +tracker.configure({ projectKey: '${props.projectKey}' }); const trackerAssist = tracker.use(trackerAssist(options)); // check the list of available options below diff --git a/frontend/app/components/Client/Integrations/Tracker/GraphQLDoc/GraphQLDoc.tsx b/frontend/app/components/Client/Integrations/Tracker/GraphQLDoc/GraphQLDoc.tsx index dd704b0bf..85e6d68c0 100644 --- a/frontend/app/components/Client/Integrations/Tracker/GraphQLDoc/GraphQLDoc.tsx +++ b/frontend/app/components/Client/Integrations/Tracker/GraphQLDoc/GraphQLDoc.tsx @@ -14,19 +14,20 @@ function GraphQLDoc() { const projectKey = siteId ? sites.find((site) => site.id === siteId)?.projectKey : sites[0]?.projectKey; - const usage = `import OpenReplay from '@openreplay/tracker'; + const usage = `import { tracker } from '@openreplay/tracker'; import trackerGraphQL from '@openreplay/tracker-graphql'; //... -const tracker = new OpenReplay({ +tracker.configure({ projectKey: '${projectKey}' }); tracker.start() //... export const recordGraphQL = tracker.use(trackerGraphQL());`; - const usageCjs = `import OpenReplay from '@openreplay/tracker/cjs'; + const usageCjs = `import { tracker } from '@openreplay/tracker/cjs'; +// alternatively you can use dynamic import without /cjs suffix to prevent issues with window scope import trackerGraphQL from '@openreplay/tracker-graphql/cjs'; //... -const tracker = new OpenReplay({ +tracker.configure({ projectKey: '${projectKey}' }); //... diff --git a/frontend/app/components/Client/Integrations/Tracker/MobxDoc/MobxDoc.tsx b/frontend/app/components/Client/Integrations/Tracker/MobxDoc/MobxDoc.tsx index e31a25b86..688f97824 100644 --- a/frontend/app/components/Client/Integrations/Tracker/MobxDoc/MobxDoc.tsx +++ b/frontend/app/components/Client/Integrations/Tracker/MobxDoc/MobxDoc.tsx @@ -15,20 +15,21 @@ function MobxDoc() { ? sites.find((site) => site.id === siteId)?.projectKey : sites[0]?.projectKey; - const mobxUsage = `import OpenReplay from '@openreplay/tracker'; + const mobxUsage = `import { tracker } from '@openreplay/tracker'; import trackerMobX from '@openreplay/tracker-mobx'; //... -const tracker = new OpenReplay({ +tracker.configure({ projectKey: '${projectKey}' }); tracker.use(trackerMobX()); // check list of available options below tracker.start(); `; - const mobxUsageCjs = `import OpenReplay from '@openreplay/tracker/cjs'; + const mobxUsageCjs = `import { tracker } from '@openreplay/tracker/cjs'; +// alternatively you can use dynamic import without /cjs suffix to prevent issues with window scope import trackerMobX from '@openreplay/tracker-mobx/cjs'; //... -const tracker = new OpenReplay({ +tracker.configure({ projectKey: '${projectKey}' }); tracker.use(trackerMobX()); // check list of available options below diff --git a/frontend/app/components/Client/Integrations/Tracker/NgRxDoc/NgRxDoc.tsx b/frontend/app/components/Client/Integrations/Tracker/NgRxDoc/NgRxDoc.tsx index d1401899f..8e52cdfa2 100644 --- a/frontend/app/components/Client/Integrations/Tracker/NgRxDoc/NgRxDoc.tsx +++ b/frontend/app/components/Client/Integrations/Tracker/NgRxDoc/NgRxDoc.tsx @@ -16,10 +16,10 @@ function NgRxDoc() { : sites[0]?.projectKey; const usage = `import { StoreModule } from '@ngrx/store'; import { reducers } from './reducers'; -import OpenReplay from '@openreplay/tracker'; +import { tracker } from '@openreplay/tracker'; import trackerNgRx from '@openreplay/tracker-ngrx'; //... -const tracker = new OpenReplay({ +tracker.configure({ projectKey: '${projectKey}' }); tracker.start() @@ -32,10 +32,11 @@ const metaReducers = [tracker.use(trackerNgRx())]; // check list of ava export class AppModule {}`; const usageCjs = `import { StoreModule } from '@ngrx/store'; import { reducers } from './reducers'; -import OpenReplay from '@openreplay/tracker/cjs'; +import { tracker } from '@openreplay/tracker/cjs'; +// alternatively you can use dynamic import without /cjs suffix to prevent issues with window scope import trackerNgRx from '@openreplay/tracker-ngrx/cjs'; //... -const tracker = new OpenReplay({ +tracker.configure({ projectKey: '${projectKey}' }); //... diff --git a/frontend/app/components/Client/Integrations/Tracker/PiniaDoc/PiniaDoc.tsx b/frontend/app/components/Client/Integrations/Tracker/PiniaDoc/PiniaDoc.tsx index 8c18077f2..c9ac2e9f9 100644 --- a/frontend/app/components/Client/Integrations/Tracker/PiniaDoc/PiniaDoc.tsx +++ b/frontend/app/components/Client/Integrations/Tracker/PiniaDoc/PiniaDoc.tsx @@ -17,10 +17,10 @@ function PiniaDoc() { ? sites.find((site) => site.id === siteId)?.projectKey : sites[0]?.projectKey; const usage = `import Vuex from 'vuex' -import OpenReplay from '@openreplay/tracker'; +import { tracker } from '@openreplay/tracker'; import trackerVuex from '@openreplay/tracker-vuex'; //... -const tracker = new OpenReplay({ +tracker.configure({ projectKey: '${projectKey}' }); tracker.start() diff --git a/frontend/app/components/Client/Integrations/Tracker/ReduxDoc/ReduxDoc.tsx b/frontend/app/components/Client/Integrations/Tracker/ReduxDoc/ReduxDoc.tsx index 35c2ad37a..216bf6605 100644 --- a/frontend/app/components/Client/Integrations/Tracker/ReduxDoc/ReduxDoc.tsx +++ b/frontend/app/components/Client/Integrations/Tracker/ReduxDoc/ReduxDoc.tsx @@ -16,10 +16,10 @@ function ReduxDoc() { : sites[0]?.projectKey; const usage = `import { applyMiddleware, createStore } from 'redux'; -import OpenReplay from '@openreplay/tracker'; +import { tracker } from '@openreplay/tracker'; import trackerRedux from '@openreplay/tracker-redux'; //... -const tracker = new OpenReplay({ +tracker.configure({ projectKey: '${projectKey}' }); tracker.start() @@ -29,10 +29,11 @@ const store = createStore( applyMiddleware(tracker.use(trackerRedux())) // check list of available options below );`; const usageCjs = `import { applyMiddleware, createStore } from 'redux'; -import OpenReplay from '@openreplay/tracker/cjs'; +import { tracker } from '@openreplay/tracker/cjs'; +// alternatively you can use dynamic import without /cjs suffix to prevent issues with window scope import trackerRedux from '@openreplay/tracker-redux/cjs'; //... -const tracker = new OpenReplay({ +tracker.configure({ projectKey: '${projectKey}' }); //... diff --git a/frontend/app/components/Client/Integrations/Tracker/VueDoc/VueDoc.tsx b/frontend/app/components/Client/Integrations/Tracker/VueDoc/VueDoc.tsx index b32e9f1f4..133f17c89 100644 --- a/frontend/app/components/Client/Integrations/Tracker/VueDoc/VueDoc.tsx +++ b/frontend/app/components/Client/Integrations/Tracker/VueDoc/VueDoc.tsx @@ -16,10 +16,10 @@ function VueDoc() { : sites[0]?.projectKey; const usage = `import Vuex from 'vuex' -import OpenReplay from '@openreplay/tracker'; +import { tracker } from '@openreplay/tracker'; import trackerVuex from '@openreplay/tracker-vuex'; //... -const tracker = new OpenReplay({ +tracker.configure({ projectKey: '${projectKey}' }); tracker.start() @@ -29,10 +29,11 @@ const store = new Vuex.Store({ plugins: [tracker.use(trackerVuex())] // check list of available options below });`; const usageCjs = `import Vuex from 'vuex' -import OpenReplay from '@openreplay/tracker/cjs'; +import { tracker } from '@openreplay/tracker/cjs'; +// alternatively you can use dynamic import without /cjs suffix to prevent issues with window scope import trackerVuex from '@openreplay/tracker-vuex/cjs'; //... -const tracker = new OpenReplay({ +tracker.configure({ projectKey: '${projectKey}' }); //... diff --git a/frontend/app/components/Client/Integrations/Tracker/ZustandDoc/ZustandDoc.tsx b/frontend/app/components/Client/Integrations/Tracker/ZustandDoc/ZustandDoc.tsx index b25f40dd6..75e51afaf 100644 --- a/frontend/app/components/Client/Integrations/Tracker/ZustandDoc/ZustandDoc.tsx +++ b/frontend/app/components/Client/Integrations/Tracker/ZustandDoc/ZustandDoc.tsx @@ -16,11 +16,10 @@ function ZustandDoc(props) { : sites[0]?.projectKey; const usage = `import create from "zustand"; -import Tracker from '@openreplay/tracker'; +import { tracker } from '@openreplay/tracker'; import trackerZustand, { StateLogger } from '@openreplay/tracker-zustand'; - -const tracker = new Tracker({ +tracker.configure({ projectKey: ${projectKey}, }); @@ -43,11 +42,12 @@ const useBearStore = create( ) `; const usageCjs = `import create from "zustand"; -import Tracker from '@openreplay/tracker/cjs'; +import { tracker } from '@openreplay/tracker/cjs'; +// alternatively you can use dynamic import without /cjs suffix to prevent issues with window scope import trackerZustand, { StateLogger } from '@openreplay/tracker-zustand/cjs'; -const tracker = new Tracker({ +tracker.configure({ projectKey: ${projectKey}, }); diff --git a/frontend/app/components/Client/ProfileSettings/ProfileSettings.tsx b/frontend/app/components/Client/ProfileSettings/ProfileSettings.tsx index ab7c31460..96d98d081 100644 --- a/frontend/app/components/Client/ProfileSettings/ProfileSettings.tsx +++ b/frontend/app/components/Client/ProfileSettings/ProfileSettings.tsx @@ -3,6 +3,7 @@ import withPageTitle from 'HOCs/withPageTitle'; import { PageTitle } from 'UI'; import { observer } from 'mobx-react-lite'; import { useStore } from 'App/mstore'; +import LanguageSwitcher from "App/components/LanguageSwitcher"; import Settings from './Settings'; import ChangePassword from './ChangePassword'; import styles from './profileSettings.module.css'; @@ -20,107 +21,90 @@ function ProfileSettings() { return (
{t('Account')}
} /> -
-
-

{t('Profile')}

-
- {t( - 'Your email address is your identity on OpenReplay and is used to login.', - )} -
-
-
- -
-
+
} + />
{account.hasPassword && ( <> -
-
-

{t('Change Password')}

-
- {t('Updating your password from time to time enhances your account’s security.')} -
-
-
- -
-
+
} + />
)} -
-
-

{t('Organization API Key')}

-
- {t('Your API key gives you access to an extra set of services.')} -
-
-
- -
-
+
} + /> + +
} + /> {isEnterprise && (account.admin || account.superAdmin) && ( <>
-
-
-

{t('Tenant Key')}

-
- {t('For SSO (SAML) authentication.')} -
-
-
- -
-
+
} + /> )} {!isEnterprise && ( <>
-
-
-

{t('Data Collection')}

-
- {t('Enables you to control how OpenReplay captures data on your organization’s usage to improve our product.')} -
-
-
- -
-
+
} + /> )} {account.license && ( <>
- -
-
-

{t('License')}

-
- {t('License key and expiration date.')} -
-
-
- -
-
+
} /> )}
); } +function Section({ title, description, children }: { + title: string; + description: string; + children: React.ReactNode; +}) { + return ( +
+
+

{title}

+
+ {description} +
+
+
+ {children} +
+
+ ) +} + export default withPageTitle('Account - OpenReplay Preferences')( observer(ProfileSettings), ); diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/Scale.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/Scale.tsx deleted file mode 100644 index 3d7b588fa..000000000 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/Scale.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react'; -import cn from 'classnames'; -import { Styles } from '../../common'; -import stl from './scale.module.css'; -import { useTranslation } from 'react-i18next'; - -function Scale({ colors }) { - const { t } = useTranslation(); - const lastIndex = Styles.compareColors.length - 1; - - return ( -
- {Styles.compareColors.map((c, i) => ( -
- {i === 0 &&
{t('Slow')}
} - {i === lastIndex &&
{t('Fast')}
} -
- ))} -
- ); -} - -export default Scale; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.module.css b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.module.css deleted file mode 100644 index ec2c46f02..000000000 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.module.css +++ /dev/null @@ -1,55 +0,0 @@ -.maps { - height: auto; - width: 110%; - stroke: $gray-medium; - stroke-width: 1; - stroke-linecap: round; - stroke-linejoin: round; - margin-top: -20px; -} - -.location { - fill: $gray-light !important; - cursor: pointer; - stroke: #fff; - - &:focus, - &:hover { - fill: #2E3ECC !important; - outline: 0; - } -} - -.heat_index0 { - fill:$gray-light !important; -} - -.heat_index5 { - fill: #B0B8FF !important; -} - -.heat_index4 { - fill:#6171FF !important; -} - -.heat_index3 { - fill: #394EFF !important; -} - -.heat_index2 { - fill: #2E3ECC !important; -} - -.heat_index1 { - fill: #222F99 !important; -} - -.tooltip { - position: fixed; - padding: 5px; - border: 1px solid $gray-light; - border-radius: 3px; - background-color: white; - font-size: 12px; - line-height: 1.2; -} diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.tsx deleted file mode 100644 index ae343f7c5..000000000 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import React from 'react'; -import { NoContent } from 'UI'; -import { observer } from 'mobx-react-lite'; -import { numberWithCommas, positionOfTheNumber } from 'App/utils'; -import WorldMap from '@svg-maps/world'; -import { SVGMap } from 'react-svg-map'; -import cn from 'classnames'; -import { NO_METRIC_DATA } from 'App/constants/messages'; -import { InfoCircleOutlined } from '@ant-design/icons'; -import stl from './SpeedIndexByLocation.module.css'; -import Scale from './Scale'; -import { Styles, AvgLabel } from '../../common'; -import { useTranslation } from 'react-i18next'; - -interface Props { - data?: any; -} - -function SpeedIndexByLocation(props: Props) { - const { t } = useTranslation(); - const { data } = props; - const wrapper: any = React.useRef(null); - const [tooltipStyle, setTooltipStyle] = React.useState({ display: 'none' }); - const [pointedLocation, setPointedLocation] = React.useState(null); - - const dataMap: any = React.useMemo(() => { - const _data: any = {}; - const max = data.chart?.reduce( - (acc: any, item: any) => Math.max(acc, item.value), - 0, - ); - const min = data.chart?.reduce( - (acc: any, item: any) => Math.min(acc, item.value), - 0, - ); - data.chart?.forEach((item: any) => { - if (!item || !item.userCountry) { - return; - } - item.perNumber = positionOfTheNumber(min, max, item.value, 5); - _data[item.userCountry.toLowerCase()] = item; - }); - return _data; - }, [data.chart]); - - const getLocationClassName = (location: any) => { - const i = dataMap[location.id] ? dataMap[location.id].perNumber : 0; - const cls = stl[`heat_index${i}`]; - return cn(stl.location, cls); - }; - - const getLocationName = (event: any) => { - if (!event) return null; - const id = event.target.attributes.id.value; - const name = event.target.attributes.name.value; - const percentage = dataMap[id] ? dataMap[id].perNumber : 0; - return { name, id, percentage }; - }; - - const handleLocationMouseOver = (event: any) => { - const pointedLocation = getLocationName(event); - setPointedLocation(pointedLocation); - }; - - const handleLocationMouseOut = () => { - setTooltipStyle({ display: 'none' }); - setPointedLocation(null); - }; - - const handleLocationMouseMove = (event: any) => { - const tooltipStyle = { - display: 'block', - top: event.clientY + 10, - left: event.clientX - 100, - }; - setTooltipStyle(tooltipStyle); - }; - - return ( - - {NO_METRIC_DATA} -
- } - > -
- -
- -
-
- -
-
- {pointedLocation && ( - <> -
{pointedLocation.name}
-
- {t('Avg:')}{' '} - - {dataMap[pointedLocation.id] - ? numberWithCommas( - parseInt(dataMap[pointedLocation.id].value), - ) - : 0} - -
- - )} -
- - ); -} - -export default observer(SpeedIndexByLocation); diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/index.ts b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/index.ts deleted file mode 100644 index 79ab01b34..000000000 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './SpeedIndexByLocation'; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/scale.module.css b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/scale.module.css deleted file mode 100644 index 5aa34f966..000000000 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/scale.module.css +++ /dev/null @@ -1,11 +0,0 @@ -.bars { - & div:first-child { - border-top-left-radius: 3px; - border-top-right-radius: 3px; - } - - & div:last-child { - border-bottom-left-radius: 3px; - border-bottom-right-radius: 3px; - } -} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SpeedIndexByLocationExample.tsx b/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SpeedIndexByLocationExample.tsx deleted file mode 100644 index 69027b892..000000000 --- a/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SpeedIndexByLocationExample.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import React from 'react'; -import ExCard from 'Components/Dashboard/components/DashboardList/NewDashModal/Examples/ExCard'; -import InsightsCard from 'Components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard'; -import { InsightIssue } from 'App/mstore/types/widget'; -import SessionsPerBrowser from 'Components/Dashboard/Widgets/PredefinedWidgets/SessionsPerBrowser'; -import SpeedIndexByLocation from 'Components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation'; - -interface Props { - title: string; - type: string; - onCard: (card: string) => void; -} - -function SpeedIndexByLocationExample(props: Props) { - const data = { - value: 1480, - chart: [ - { - userCountry: 'AT', - value: 415, - }, - { - userCountry: 'PL', - value: 433.1666666666667, - }, - { - userCountry: 'FR', - value: 502, - }, - { - userCountry: 'IT', - value: 540.4117647058823, - }, - { - userCountry: 'TH', - value: 662.0, - }, - { - userCountry: 'ES', - value: 740.5454545454545, - }, - { - userCountry: 'SG', - value: 889.6666666666666, - }, - { - userCountry: 'TW', - value: 1008.0, - }, - { - userCountry: 'HU', - value: 1027.0, - }, - { - userCountry: 'DE', - value: 1054.4583333333333, - }, - { - userCountry: 'BE', - value: 1126.0, - }, - { - userCountry: 'TR', - value: 1174.0, - }, - { - userCountry: 'US', - value: 1273.3015873015872, - }, - { - userCountry: 'GB', - value: 1353.8095238095239, - }, - { - userCountry: 'VN', - value: 1473.8181818181818, - }, - { - userCountry: 'HK', - value: 1654.6666666666667, - }, - ], - unit: 'ms', - }; - return ( - - - - ); -} - -export default SpeedIndexByLocationExample; diff --git a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx index 577670e44..4b0313ef2 100644 --- a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx +++ b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx @@ -200,7 +200,6 @@ function WidgetChart(props: Props) { const payload = { ...params, ..._metric.toJson(), - viewType: 'lineChart', }; fetchMetricChartData( _metric, diff --git a/frontend/app/components/Dashboard/components/WidgetForm/WidgetFormNew.tsx b/frontend/app/components/Dashboard/components/WidgetForm/WidgetFormNew.tsx index 2a3594a05..a726fe0b2 100644 --- a/frontend/app/components/Dashboard/components/WidgetForm/WidgetFormNew.tsx +++ b/frontend/app/components/Dashboard/components/WidgetForm/WidgetFormNew.tsx @@ -66,8 +66,23 @@ export default observer(WidgetFormNew); const FilterSection = observer( ({ layout, metric, excludeFilterKeys, excludeCategory }: any) => { + const isTable = metric.metricType === TABLE; + const isHeatMap = metric.metricType === HEATMAP; + const isFunnel = metric.metricType === FUNNEL; + const isInsights = metric.metricType === INSIGHTS; + const isPathAnalysis = metric.metricType === USER_PATH; + const isRetention = metric.metricType === RETENTION; + const canAddSeries = metric.series.length < 3; + + const isSingleSeries = + isTable || + isFunnel || + isHeatMap || + isInsights || + isRetention || + isPathAnalysis; const { t } = useTranslation(); - const allOpen = layout.startsWith('flex-row'); + const allOpen = isSingleSeries || layout.startsWith('flex-row'); const defaultClosed = React.useRef(!allOpen && metric.exists()); const [seriesCollapseState, setSeriesCollapseState] = React.useState< Record @@ -84,21 +99,6 @@ const FilterSection = observer( }); setSeriesCollapseState(defaultSeriesCollapseState); }, [metric.series]); - const isTable = metric.metricType === TABLE; - const isHeatMap = metric.metricType === HEATMAP; - const isFunnel = metric.metricType === FUNNEL; - const isInsights = metric.metricType === INSIGHTS; - const isPathAnalysis = metric.metricType === USER_PATH; - const isRetention = metric.metricType === RETENTION; - const canAddSeries = metric.series.length < 3; - - const isSingleSeries = - isTable || - isFunnel || - isHeatMap || - isInsights || - isRetention || - isPathAnalysis; const collapseAll = () => { setSeriesCollapseState((seriesCollapseState) => { diff --git a/frontend/app/components/Dashboard/components/WidgetPredefinedChart/WidgetPredefinedChart.tsx b/frontend/app/components/Dashboard/components/WidgetPredefinedChart/WidgetPredefinedChart.tsx index 8800ce5ee..29d1a4a26 100644 --- a/frontend/app/components/Dashboard/components/WidgetPredefinedChart/WidgetPredefinedChart.tsx +++ b/frontend/app/components/Dashboard/components/WidgetPredefinedChart/WidgetPredefinedChart.tsx @@ -18,7 +18,6 @@ import SessionsImpactedBySlowRequests from 'App/components/Dashboard/Widgets/Pre import SessionsPerBrowser from 'App/components/Dashboard/Widgets/PredefinedWidgets/SessionsPerBrowser'; import { FilterKey } from 'Types/filter/filterType'; import CallWithErrors from '../../Widgets/PredefinedWidgets/CallWithErrors'; -import SpeedIndexByLocation from '../../Widgets/PredefinedWidgets/SpeedIndexByLocation'; import ResponseTimeDistribution from '../../Widgets/PredefinedWidgets/ResponseTimeDistribution'; import { useTranslation } from 'react-i18next'; @@ -49,8 +48,6 @@ function WidgetPredefinedChart(props: Props) { return ; case FilterKey.CALLS_ERRORS: return ; - case FilterKey.SPEED_LOCATION: - return ; default: return (
{t('Widget not supported')}
diff --git a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapperNew.tsx b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapperNew.tsx index 3b49431e5..1b8bf5cb9 100644 --- a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapperNew.tsx +++ b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapperNew.tsx @@ -83,6 +83,7 @@ function WidgetWrapperNew(props: Props & RouteComponentProps) { }); const onChartClick = () => { + dashboardStore.setDrillDownPeriod(dashboardStore.period); // if (!isWidget || isPredefined) return; props.history.push( withSiteId( diff --git a/frontend/app/components/LanguageSwitcher/LanguageSwitcher.tsx b/frontend/app/components/LanguageSwitcher/LanguageSwitcher.tsx index 225d2ba3b..e408d6980 100644 --- a/frontend/app/components/LanguageSwitcher/LanguageSwitcher.tsx +++ b/frontend/app/components/LanguageSwitcher/LanguageSwitcher.tsx @@ -1,9 +1,7 @@ -import { Button, Dropdown, MenuProps, Space, Typography } from 'antd'; -import React, { useCallback, useState } from 'react'; +import { Button, Dropdown, MenuProps, Typography } from 'antd'; +import React from 'react'; import { useTranslation } from 'react-i18next'; -import { CaretDownOutlined } from '@ant-design/icons'; -import { Languages } from 'lucide-react'; -import { Icon } from '../ui'; +import { ChevronDown } from 'lucide-react'; const langs = [ { code: 'en', label: 'English' }, @@ -12,14 +10,25 @@ const langs = [ { code: 'ru', label: 'Русский' }, { code: 'zh', label: '中國人' }, ]; +const langLabels = { + en: 'English', + fr: 'Français', + es: 'Español', + ru: 'Русский', + zh: '中國人', +} function LanguageSwitcher() { const { i18n } = useTranslation(); + const [selected, setSelected] = React.useState(i18n.language); - const handleChangeLanguage = useCallback((lang: string) => { - i18n.changeLanguage(lang); - localStorage.setItem('i18nextLng', lang); - }, []); + const onChange = (val: string) => { + setSelected(val) + } + const handleChangeLanguage = () => { + void i18n.changeLanguage(selected) + localStorage.setItem('i18nextLng', selected) + } const menuItems: MenuProps['items'] = langs.map((lang) => ({ key: lang.code, @@ -31,21 +40,31 @@ function LanguageSwitcher() { })); return ( - handleChangeLanguage(e.key), - }} - placement="bottomLeft" - > - + + +
); } diff --git a/frontend/app/components/Login/Login.tsx b/frontend/app/components/Login/Login.tsx index ce7d15522..45bb73ee2 100644 --- a/frontend/app/components/Login/Login.tsx +++ b/frontend/app/components/Login/Login.tsx @@ -66,7 +66,8 @@ function Login({ if (event.data.type === 'orspot:logged') { clearInterval(int); window.removeEventListener('message', onSpotMsg); - toast.success(t('You have been logged into Spot successfully')); + const msg = t('You have been logged into Spot successfully') + toast.success(msg); } }; window.addEventListener('message', onSpotMsg); diff --git a/frontend/app/components/Onboarding/components/OnboardingTabs/InstallDocs/InstallDocs.js b/frontend/app/components/Onboarding/components/OnboardingTabs/InstallDocs/InstallDocs.js index c9dc3c245..724d95eea 100644 --- a/frontend/app/components/Onboarding/components/OnboardingTabs/InstallDocs/InstallDocs.js +++ b/frontend/app/components/Onboarding/components/OnboardingTabs/InstallDocs/InstallDocs.js @@ -7,16 +7,17 @@ import stl from './installDocs.module.css'; import { useTranslation } from 'react-i18next'; const installationCommand = 'npm i @openreplay/tracker'; -const usageCode = `import Tracker from '@openreplay/tracker'; +const usageCode = `import { tracker } from '@openreplay/tracker'; -const tracker = new Tracker({ +tracker.configure({ projectKey: "PROJECT_KEY", ingestPoint: "https://${window.location.hostname}/ingest", }); tracker.start()`; -const usageCodeSST = `import Tracker from '@openreplay/tracker/cjs'; +const usageCodeSST = `import { tracker } from '@openreplay/tracker/cjs'; +// alternatively you can use dynamic import without /cjs suffix to prevent issues with window scope -const tracker = new Tracker({ +tracker.configure({ projectKey: "PROJECT_KEY", ingestPoint: "https://${window.location.hostname}/ingest", }); diff --git a/frontend/app/components/Session/Player/ClickMapRenderer/Renderer.tsx b/frontend/app/components/Session/Player/ClickMapRenderer/Renderer.tsx index 551da2fa1..018b7660f 100644 --- a/frontend/app/components/Session/Player/ClickMapRenderer/Renderer.tsx +++ b/frontend/app/components/Session/Player/ClickMapRenderer/Renderer.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import { findDOMNode } from 'react-dom'; import cn from 'classnames'; import Overlay from 'Components/Session_/Player/Overlay'; import stl from 'Components/Session_/Player/player.module.css'; @@ -10,9 +9,7 @@ function Player() { const playerContext = React.useContext(PlayerContext); const screenWrapper = React.useRef(null); React.useEffect(() => { - const parentElement = findDOMNode( - screenWrapper.current, - ) as HTMLDivElement | null; + const parentElement = screenWrapper.current if (parentElement) { playerContext.player.attach(parentElement); } diff --git a/frontend/app/components/Session/Player/ClipPlayer/ClipPlayerControls.tsx b/frontend/app/components/Session/Player/ClipPlayer/ClipPlayerControls.tsx index 8f0b0eef5..6d2dc0f82 100644 --- a/frontend/app/components/Session/Player/ClipPlayer/ClipPlayerControls.tsx +++ b/frontend/app/components/Session/Player/ClipPlayer/ClipPlayerControls.tsx @@ -48,7 +48,7 @@ function ClipPlayerControls({
); diff --git a/frontend/app/components/Session/Player/LivePlayer/LivePlayerInst.tsx b/frontend/app/components/Session/Player/LivePlayer/LivePlayerInst.tsx index 18a8ecc3b..d3777c84d 100644 --- a/frontend/app/components/Session/Player/LivePlayer/LivePlayerInst.tsx +++ b/frontend/app/components/Session/Player/LivePlayer/LivePlayerInst.tsx @@ -1,7 +1,6 @@ import cn from 'classnames'; import { observer } from 'mobx-react-lite'; import React from 'react'; -import { findDOMNode } from 'react-dom'; import { ILivePlayerContext, @@ -40,9 +39,7 @@ function Player({ fullView, isMultiview }: IProps) { React.useEffect(() => { if (!closedLive || isMultiview) { - const parentElement = findDOMNode( - screenWrapper.current, - ) as HTMLDivElement | null; // TODO: good architecture + const parentElement = screenWrapper.current // TODO: good architecture if (parentElement) { playerContext.player.attach(parentElement); playerContext.player.play(); diff --git a/frontend/app/components/Session/Player/MobilePlayer/PlayerInst.tsx b/frontend/app/components/Session/Player/MobilePlayer/PlayerInst.tsx index aa4cefeba..cc0ff2870 100644 --- a/frontend/app/components/Session/Player/MobilePlayer/PlayerInst.tsx +++ b/frontend/app/components/Session/Player/MobilePlayer/PlayerInst.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import { findDOMNode } from 'react-dom'; import cn from 'classnames'; import { EscapeButton } from 'UI'; import { diff --git a/frontend/app/components/Session/Player/ReplayPlayer/PlayerInst.tsx b/frontend/app/components/Session/Player/ReplayPlayer/PlayerInst.tsx index acf589147..4c0f3ab59 100644 --- a/frontend/app/components/Session/Player/ReplayPlayer/PlayerInst.tsx +++ b/frontend/app/components/Session/Player/ReplayPlayer/PlayerInst.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import { findDOMNode } from 'react-dom'; import cn from 'classnames'; import { WebStackEventPanel } from 'Shared/DevTools/StackEventPanel/StackEventPanel'; import { EscapeButton } from 'UI'; @@ -74,9 +73,7 @@ function Player(props: IProps) { React.useEffect(() => { updateLastPlayedSession(sessionId); if (isReady && !isAttached.current) { - const parentElement = findDOMNode( - screenWrapper.current, - ) as HTMLDivElement | null; // TODO: good architecture + const parentElement = screenWrapper.current // TODO: good architecture if (parentElement) { playerContext.player.attach(parentElement); isAttached.current = true; diff --git a/frontend/app/components/Session/Player/SharedComponents/Tab.tsx b/frontend/app/components/Session/Player/SharedComponents/Tab.tsx index fcba34c42..e08fa94b7 100644 --- a/frontend/app/components/Session/Player/SharedComponents/Tab.tsx +++ b/frontend/app/components/Session/Player/SharedComponents/Tab.tsx @@ -28,7 +28,7 @@ function Tab({ i, tab, currentTab, changeTab, isLive, isClosed, name }: Props) { : 'cursor-pointer border-gray-lighter !border-b !border-t-transparent !border-l-transparent !border-r-transparent', )} > - 20 ? name : ''}> + 20 ? name : ''} mouseEnterDelay={0.5}>
{i + 1}
diff --git a/frontend/app/components/Session/WebPlayer.tsx b/frontend/app/components/Session/WebPlayer.tsx index d0fff842d..874f15bd5 100644 --- a/frontend/app/components/Session/WebPlayer.tsx +++ b/frontend/app/components/Session/WebPlayer.tsx @@ -38,8 +38,8 @@ function WebPlayer(props: any) { uxtestingStore, uiPlayerStore, integrationsStore, - userStore, } = useStore(); + const devTools = sessionStore.devTools const session = sessionStore.current; const { prefetched } = sessionStore; const startedAt = sessionStore.current.startedAt || 0; @@ -66,6 +66,10 @@ function WebPlayer(props: any) { }; document.addEventListener('visibilitychange', handleActivation); } + + return () => { + devTools.update('network', { activeTab: 'ALL' }); + } }, []); useEffect(() => { diff --git a/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx b/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx index 67767572a..8b37a2819 100644 --- a/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx +++ b/frontend/app/components/Session_/EventsBlock/EventsBlock.tsx @@ -85,19 +85,30 @@ function EventsBlock(props: IProps) { } }); } - const eventsWithMobxNotes = [...notesWithEvents, ...notes].sort(sortEvents); - return mergeEventLists( - filteredLength > 0 ? filteredEvents : eventsWithMobxNotes, - tabChangeEvents, - ).filter((e) => - zoomEnabled + const eventsWithMobxNotes = [...notesWithEvents, ...notes] + .sort(sortEvents); + const filteredTabEvents = query.length + ? tabChangeEvents + .filter((e => (e.activeUrl as string).includes(query))) + : tabChangeEvents; + const list = mergeEventLists( + query.length > 0 ? filteredEvents : eventsWithMobxNotes, + filteredTabEvents + ) + if (zoomEnabled) { + return list.filter((e) => + zoomEnabled ? 'time' in e ? e.time >= zoomStartTs && e.time <= zoomEndTs : false - : true, - ); + : true + ); + } else { + return list + } }, [ filteredLength, + query, notesWithEvtsLength, notesLength, zoomEnabled, diff --git a/frontend/app/components/Spots/SpotPlayer/components/SpotPlayerHeader.tsx b/frontend/app/components/Spots/SpotPlayer/components/SpotPlayerHeader.tsx index 7b5b39a16..6bf8671ce 100644 --- a/frontend/app/components/Spots/SpotPlayer/components/SpotPlayerHeader.tsx +++ b/frontend/app/components/Spots/SpotPlayer/components/SpotPlayerHeader.tsx @@ -90,7 +90,7 @@ function SpotPlayerHeader({ const onMenuClick = async ({ key }: { key: string }) => { if (key === '1') { const { url } = await spotStore.getVideo(spotStore.currentSpot!.spotId); - await downloadFile(url, `${spotStore.currentSpot!.title}.webm`); + await downloadFile(url, `${spotStore.currentSpot!.title}.mp4`); } else if (key === '2') { spotStore.deleteSpot([spotStore.currentSpot!.spotId]).then(() => { history.push(spotsList()); diff --git a/frontend/app/components/Spots/SpotsList/SpotListItem.tsx b/frontend/app/components/Spots/SpotsList/SpotListItem.tsx index 80aff7707..573a2dd81 100644 --- a/frontend/app/components/Spots/SpotsList/SpotListItem.tsx +++ b/frontend/app/components/Spots/SpotsList/SpotListItem.tsx @@ -81,7 +81,7 @@ function SpotListItem({ return setIsEdit(true); case 'download': const { url } = await onVideo(spot.spotId); - await downloadFile(url, `${spot.title}.webm`); + await downloadFile(url, `${spot.title}.mp4`); return; case 'copy': copy( diff --git a/frontend/app/components/hocs/withOverlay.js b/frontend/app/components/hocs/withOverlay.js index ca7c6f228..d6c8ef77c 100644 --- a/frontend/app/components/hocs/withOverlay.js +++ b/frontend/app/components/hocs/withOverlay.js @@ -1,5 +1,4 @@ import React from 'react'; -import { findDOMNode } from 'react-dom'; let overlayedCount = 0; @@ -71,7 +70,7 @@ const withOverlay = } setOverlayedStyles() { - const baseRoot = findDOMNode(this.baseRef.current); + const baseRoot = this.baseRef.current; const overlayed = this.props[overlayedName]; const actualZIndex = Z_BASE + 1 + zIndex; if (baseRoot) { diff --git a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx index 13e9c2655..32f8f2bcd 100644 --- a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx +++ b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx @@ -1,9 +1,17 @@ /* eslint-disable i18next/no-literal-string */ import { ResourceType, Timed } from 'Player'; +import { WsChannel } from 'Player/web/messages'; import MobilePlayer from 'Player/mobile/IOSPlayer'; import WebPlayer from 'Player/web/WebPlayer'; import { observer } from 'mobx-react-lite'; -import React, { useMemo, useState } from 'react'; +import React, { + useMemo, + useState, + useEffect, + useCallback, + useRef, +} from 'react'; +import i18n from 'App/i18n' import { useModal } from 'App/components/Modal'; import { @@ -12,25 +20,27 @@ import { } from 'App/components/Session/playerContext'; import { formatMs } from 'App/date'; import { useStore } from 'App/mstore'; -import { formatBytes } from 'App/utils'; +import { formatBytes, debounceCall } from 'App/utils'; import { Icon, NoContent, Tabs } from 'UI'; import { Tooltip, Input, Switch, Form } from 'antd'; -import { SearchOutlined, InfoCircleOutlined } from '@ant-design/icons'; +import { + SearchOutlined, + InfoCircleOutlined, +} from '@ant-design/icons'; import FetchDetailsModal from 'Shared/FetchDetailsModal'; -import { WsChannel } from 'App/player/web/messages'; import BottomBlock from '../BottomBlock'; import InfoLine from '../BottomBlock/InfoLine'; import TabSelector from '../TabSelector'; import TimeTable from '../TimeTable'; import useAutoscroll, { getLastItemTime } from '../useAutoscroll'; -import { useRegExListFilterMemo, useTabListFilterMemo } from '../useListFilter'; import WSPanel from './WSPanel'; import { useTranslation } from 'react-i18next'; +import { mergeListsWithZoom, processInChunks } from './utils' +// Constants remain the same const INDEX_KEY = 'network'; - const ALL = 'ALL'; const XHR = 'xhr'; const JS = 'js'; @@ -62,6 +72,9 @@ export const NETWORK_TABS = TAP_KEYS.map((tab) => ({ const DOM_LOADED_TIME_COLOR = 'teal'; const LOAD_TIME_COLOR = 'red'; +const BATCH_SIZE = 2500; +const INITIAL_LOAD_SIZE = 5000; + export function renderType(r: any) { return ( {r.type}
}> @@ -79,13 +92,17 @@ export function renderName(r: any) { } function renderSize(r: any) { - const { t } = useTranslation(); - if (r.responseBodySize) return formatBytes(r.responseBodySize); + const t = i18n.t; + const notCaptured = t('Not captured'); + const resSizeStr = t('Resource size') let triggerText; let content; - if (r.decodedBodySize == null || r.decodedBodySize === 0) { + if (r.responseBodySize) { + triggerText = formatBytes(r.responseBodySize); + content = undefined; + } else if (r.decodedBodySize == null || r.decodedBodySize === 0) { triggerText = 'x'; - content = t('Not captured'); + content = notCaptured; } else { const headerSize = r.headerSize || 0; const showTransferred = r.headerSize != null; @@ -100,7 +117,7 @@ function renderSize(r: any) { )} transferred over network`} )} -
  • {`${t('Resource size')}: ${formatBytes(r.decodedBodySize)} `}
  • +
  • {`${resSizeStr}: ${formatBytes(r.decodedBodySize)} `}
  • ); } @@ -168,6 +185,8 @@ function renderStatus({ ); } + +// Main component for Network Panel function NetworkPanelCont({ panelHeight }: { panelHeight: number }) { const { player, store } = React.useContext(PlayerContext); const { sessionStore, uiPlayerStore } = useStore(); @@ -216,6 +235,7 @@ function NetworkPanelCont({ panelHeight }: { panelHeight: number }) { const getTabNum = (tab: string) => tabsArr.findIndex((t) => t === tab) + 1; const getTabName = (tabId: string) => tabNames[tabId]; + return ( void, hasMore: boolean) => { + const observerRef = useRef(null); + const loadingRef = useRef(null); + + useEffect(() => { + const observer = new IntersectionObserver( + (entries) => { + if (entries[0]?.isIntersecting && hasMore) { + loadMoreCallback(); + } + }, + { threshold: 0.1 }, + ); + + if (loadingRef.current) { + observer.observe(loadingRef.current); + } + + // @ts-ignore + observerRef.current = observer; + + return () => { + if (observerRef.current) { + observerRef.current.disconnect(); + } + }; + }, [loadMoreCallback, hasMore, loadingRef]); + + return loadingRef; }; interface Props { @@ -302,8 +343,8 @@ interface Props { resourceList: Timed[]; fetchListNow: Timed[]; resourceListNow: Timed[]; - websocketList: Array; - websocketListNow: Array; + websocketList: Array; + websocketListNow: Array; player: WebPlayer | MobilePlayer; startedAt: number; isMobile?: boolean; @@ -349,107 +390,189 @@ export const NetworkPanelComp = observer( >(null); const { showModal } = useModal(); const [showOnlyErrors, setShowOnlyErrors] = useState(false); - const [isDetailsModalActive, setIsDetailsModalActive] = useState(false); + const [isLoading, setIsLoading] = useState(true); + const [isProcessing, setIsProcessing] = useState(false); + const [displayedItems, setDisplayedItems] = useState([]); + const [totalItems, setTotalItems] = useState(0); + const [summaryStats, setSummaryStats] = useState({ + resourcesSize: 0, + transferredSize: 0, + }); + + const originalListRef = useRef([]); + const socketListRef = useRef([]); + const { sessionStore: { devTools }, } = useStore(); const { filter } = devTools[INDEX_KEY]; const { activeTab } = devTools[INDEX_KEY]; const activeIndex = activeOutsideIndex ?? devTools[INDEX_KEY].index; + const [inputFilterValue, setInputFilterValue] = useState(filter); - const socketList = useMemo( - () => - websocketList.filter( - (ws, i, arr) => - arr.findIndex((it) => it.channelName === ws.channelName) === i, - ), - [websocketList], + const debouncedFilter = useCallback( + debounceCall((filterValue) => { + devTools.update(INDEX_KEY, { filter: filterValue }); + }, 300), + [], ); - const list = useMemo( - () => - // TODO: better merge (with body size info) - do it in player - resourceList - .filter( - (res) => - !fetchList.some((ft) => { - // res.url !== ft.url doesn't work on relative URLs appearing within fetchList (to-fix in player) - if (res.name === ft.name) { - if (res.time === ft.time) return true; - if (res.url.includes(ft.url)) { - return ( - Math.abs(res.time - ft.time) < 350 || - Math.abs(res.timestamp - ft.timestamp) < 350 - ); - } - } - - if (res.name !== ft.name) { - return false; - } - if (Math.abs(res.time - ft.time) > 250) { - return false; - } // TODO: find good epsilons - if (Math.abs(res.duration - ft.duration) > 200) { - return false; - } - - return true; - }), - ) - .concat(fetchList) - .concat( - socketList.map((ws) => ({ - ...ws, - type: 'websocket', - method: 'ws', - url: ws.channelName, - name: ws.channelName, - status: '101', - duration: 0, - transferredBodySize: 0, - })), - ) - .filter((req) => - zoomEnabled - ? req.time >= zoomStartTs! && req.time <= zoomEndTs! - : true, - ) - .sort((a, b) => a.time - b.time), - [resourceList.length, fetchList.length, socketList.length], - ); - - let filteredList = useMemo(() => { - if (!showOnlyErrors) { - return list; - } - return list.filter( - (it) => parseInt(it.status) >= 400 || !it.success || it.error, + // Process socket lists once + useEffect(() => { + const uniqueSocketList = websocketList.filter( + (ws, i, arr) => + arr.findIndex((it) => it.channelName === ws.channelName) === i, ); - }, [showOnlyErrors, list]); - filteredList = useRegExListFilterMemo( - filteredList, - (it) => [it.status, it.name, it.type, it.method], - filter, - ); - filteredList = useTabListFilterMemo( - filteredList, - (it) => TYPE_TO_TAB[it.type], - ALL, - activeTab, - ); + socketListRef.current = uniqueSocketList; + }, [websocketList.length]); - const onTabClick = (activeTab: (typeof TAP_KEYS)[number]) => + // Initial data processing - do this only once when data changes + useEffect(() => { + setIsLoading(true); + + // Heaviest operation here, will create a final merged network list + const processData = async () => { + const fetchUrls = new Set( + fetchList.map((ft) => { + return `${ft.name}-${Math.floor(ft.time / 100)}-${Math.floor(ft.duration / 100)}`; + }), + ); + + // We want to get resources that aren't in fetch list + const filteredResources = await processInChunks(resourceList, (chunk) => + chunk.filter((res: any) => { + const key = `${res.name}-${Math.floor(res.time / 100)}-${Math.floor(res.duration / 100)}`; + return !fetchUrls.has(key); + }), + BATCH_SIZE, + 25, + ); + + const processedSockets = socketListRef.current.map((ws: any) => ({ + ...ws, + type: 'websocket', + method: 'ws', + url: ws.channelName, + name: ws.channelName, + status: '101', + duration: 0, + transferredBodySize: 0, + })); + + const mergedList: Timed[] = mergeListsWithZoom( + filteredResources as Timed[], + fetchList, + processedSockets as Timed[], + { enabled: Boolean(zoomEnabled), start: zoomStartTs ?? 0, end: zoomEndTs ?? 0 } + ) + + originalListRef.current = mergedList; + setTotalItems(mergedList.length); + + calculateResourceStats(resourceList); + + // Only display initial chunk + setDisplayedItems(mergedList.slice(0, INITIAL_LOAD_SIZE)); + setIsLoading(false); + }; + + void processData(); + }, [ + resourceList.length, + fetchList.length, + socketListRef.current.length, + zoomEnabled, + zoomStartTs, + zoomEndTs, + ]); + + const calculateResourceStats = (resourceList: Record) => { + setTimeout(() => { + let resourcesSize = 0 + let transferredSize = 0 + resourceList.forEach(({ decodedBodySize, headerSize, encodedBodySize }: any) => { + resourcesSize += decodedBodySize || 0 + transferredSize += (headerSize || 0) + (encodedBodySize || 0) + }) + + setSummaryStats({ + resourcesSize, + transferredSize, + }); + }, 0); + } + + useEffect(() => { + if (originalListRef.current.length === 0) return; + setIsProcessing(true); + const applyFilters = async () => { + let filteredItems: any[] = originalListRef.current; + + filteredItems = await processInChunks(filteredItems, (chunk) => + chunk.filter( + (it) => { + let valid = true; + if (showOnlyErrors) { + valid = parseInt(it.status) >= 400 || !it.success || it.error + } + if (filter) { + try { + const regex = new RegExp(filter, 'i'); + valid = valid && regex.test(it.status) || regex.test(it.name) || regex.test(it.type) || regex.test(it.method); + } catch (e) { + valid = valid && String(it.status).includes(filter) || it.name.includes(filter) || it.type.includes(filter) || (it.method && it.method.includes(filter)); + } + } + if (activeTab !== ALL) { + valid = valid && TYPE_TO_TAB[it.type] === activeTab; + } + + return valid; + }, + ), + ); + + // Update displayed items + setDisplayedItems(filteredItems.slice(0, INITIAL_LOAD_SIZE)); + setTotalItems(filteredItems.length); + setIsProcessing(false); + }; + + void applyFilters(); + }, [filter, activeTab, showOnlyErrors]); + + const loadMoreItems = useCallback(() => { + if (isProcessing) return; + + setIsProcessing(true); + setTimeout(() => { + setDisplayedItems((prevItems) => { + const currentLength = prevItems.length; + const newItems = originalListRef.current.slice( + currentLength, + currentLength + BATCH_SIZE, + ); + return [...prevItems, ...newItems]; + }); + setIsProcessing(false); + }, 10); + }, [isProcessing]); + + const hasMoreItems = displayedItems.length < totalItems; + const loadingRef = useInfiniteScroll(loadMoreItems, hasMoreItems); + + const onTabClick = (activeTab) => { devTools.update(INDEX_KEY, { activeTab }); - const onFilterChange = ({ - target: { value }, - }: React.ChangeEvent) => - devTools.update(INDEX_KEY, { filter: value }); + }; + + const onFilterChange = ({ target: { value } }) => { + setInputFilterValue(value) + debouncedFilter(value); + }; - // AutoScroll const [timeoutStartAutoscroll, stopAutoscroll] = useAutoscroll( - filteredList, + displayedItems, getLastItemTime(fetchListNow, resourceListNow), activeIndex, (index) => devTools.update(INDEX_KEY, { index }), @@ -462,24 +585,6 @@ export const NetworkPanelComp = observer( timeoutStartAutoscroll(); }; - const resourcesSize = useMemo( - () => - resourceList.reduce( - (sum, { decodedBodySize }) => sum + (decodedBodySize || 0), - 0, - ), - [resourceList.length], - ); - const transferredSize = useMemo( - () => - resourceList.reduce( - (sum, { headerSize, encodedBodySize }) => - sum + (headerSize || 0) + (encodedBodySize || 0), - 0, - ), - [resourceList.length], - ); - const referenceLines = useMemo(() => { const arr = []; @@ -513,7 +618,7 @@ export const NetworkPanelComp = observer( isSpot={isSpot} time={item.time + startedAt} resource={item} - rows={filteredList} + rows={displayedItems} fetchPresented={fetchList.length > 0} />, { @@ -525,12 +630,12 @@ export const NetworkPanelComp = observer( }, }, ); - devTools.update(INDEX_KEY, { index: filteredList.indexOf(item) }); + devTools.update(INDEX_KEY, { index: displayedItems.indexOf(item) }); stopAutoscroll(); }; - const tableCols = React.useMemo(() => { - const cols: any[] = [ + const tableCols = useMemo(() => { + const cols = [ { label: t('Status'), dataKey: 'status', @@ -585,7 +690,7 @@ export const NetworkPanelComp = observer( }); } return cols; - }, [showSingleTab]); + }, [showSingleTab, activeTab, t, getTabName, getTabNum, isSpot]); return ( } /> @@ -625,7 +730,7 @@ export const NetworkPanelComp = observer(
    -
    +
    + + {isProcessing && ( + + Processing data... + + )}
    + 0} + display={summaryStats.transferredSize > 0} /> 0} + display={summaryStats.resourcesSize > 0} />
    - - - {t('No Data')} + + {isLoading ? ( +
    +
    +
    +

    Processing initial network data...

    - } - size="small" - show={filteredList.length === 0} - > - {/* @ts-ignore */} - { - devTools.update(INDEX_KEY, { - index: filteredList.indexOf(row), - }); - player.jump(row.time); - }} - activeIndex={activeIndex} +
    + ) : ( + + + {t('No Data')} +
    + } + size="small" + show={displayedItems.length === 0} > - {tableCols} - - {selectedWsChannel ? ( - setSelectedWsChannel(null)} - /> - ) : null} - +
    + { + devTools.update(INDEX_KEY, { + index: displayedItems.indexOf(row), + }); + player.jump(row.time); + }} + activeIndex={activeIndex} + > + {tableCols} + + + {hasMoreItems && ( +
    +
    +
    + Loading more data ({totalItems - displayedItems.length}{' '} + remaining) +
    +
    + )} +
    + + {selectedWsChannel ? ( + setSelectedWsChannel(null)} + /> + ) : null} + + )}
    ); @@ -722,7 +860,6 @@ export const NetworkPanelComp = observer( ); const WebNetworkPanel = observer(NetworkPanelCont); - const MobileNetworkPanel = observer(MobileNetworkPanelCont); export { WebNetworkPanel, MobileNetworkPanel }; diff --git a/frontend/app/components/shared/DevTools/NetworkPanel/utils.ts b/frontend/app/components/shared/DevTools/NetworkPanel/utils.ts new file mode 100644 index 000000000..c54130975 --- /dev/null +++ b/frontend/app/components/shared/DevTools/NetworkPanel/utils.ts @@ -0,0 +1,178 @@ +export function mergeListsWithZoom< + T extends Record, + Y extends Record, + Z extends Record, +>( + arr1: T[], + arr2: Y[], + arr3: Z[], + zoom?: { enabled: boolean; start: number; end: number }, +): Array { + // Early return for empty arrays + if (arr1.length === 0 && arr2.length === 0 && arr3.length === 0) { + return []; + } + + // Optimized for common case - no zoom + if (!zoom?.enabled) { + return mergeThreeSortedArrays(arr1, arr2, arr3); + } + + // Binary search for start indexes (faster than linear search for large arrays) + const index1 = binarySearchStartIndex(arr1, zoom.start); + const index2 = binarySearchStartIndex(arr2, zoom.start); + const index3 = binarySearchStartIndex(arr3, zoom.start); + + // Merge arrays within zoom range + return mergeThreeSortedArraysWithinRange( + arr1, + arr2, + arr3, + index1, + index2, + index3, + zoom.start, + zoom.end, + ); +} + +function binarySearchStartIndex>( + arr: T[], + threshold: number, +): number { + if (arr.length === 0) return 0; + + let low = 0; + let high = arr.length - 1; + + // Handle edge cases first for better performance + if (arr[high].time < threshold) return arr.length; + if (arr[low].time >= threshold) return 0; + + while (low <= high) { + const mid = Math.floor((low + high) / 2); + + if (arr[mid].time < threshold) { + low = mid + 1; + } else { + high = mid - 1; + } + } + + return low; +} + +function mergeThreeSortedArrays< + T extends Record, + Y extends Record, + Z extends Record, +>(arr1: T[], arr2: Y[], arr3: Z[]): Array { + const totalLength = arr1.length + arr2.length + arr3.length; + // prealloc array size + const result = new Array(totalLength); + + let i = 0, + j = 0, + k = 0, + index = 0; + + while (i < arr1.length || j < arr2.length || k < arr3.length) { + const val1 = i < arr1.length ? arr1[i].time : Infinity; + const val2 = j < arr2.length ? arr2[j].time : Infinity; + const val3 = k < arr3.length ? arr3[k].time : Infinity; + + if (val1 <= val2 && val1 <= val3) { + result[index++] = arr1[i++]; + } else if (val2 <= val1 && val2 <= val3) { + result[index++] = arr2[j++]; + } else { + result[index++] = arr3[k++]; + } + } + + return result; +} + +// same as above, just with zoom stuff +function mergeThreeSortedArraysWithinRange< + T extends Record, + Y extends Record, + Z extends Record, +>( + arr1: T[], + arr2: Y[], + arr3: Z[], + startIdx1: number, + startIdx2: number, + startIdx3: number, + start: number, + end: number, +): Array { + // we don't know beforehand how many items will be there + const result = []; + + let i = startIdx1; + let j = startIdx2; + let k = startIdx3; + + while (i < arr1.length || j < arr2.length || k < arr3.length) { + const val1 = i < arr1.length ? arr1[i].time : Infinity; + const val2 = j < arr2.length ? arr2[j].time : Infinity; + const val3 = k < arr3.length ? arr3[k].time : Infinity; + + // Early termination: if all remaining values exceed end time + if (Math.min(val1, val2, val3) > end) { + break; + } + + if (val1 <= val2 && val1 <= val3) { + if (val1 <= end) { + result.push(arr1[i]); + } + i++; + } else if (val2 <= val1 && val2 <= val3) { + if (val2 <= end) { + result.push(arr2[j]); + } + j++; + } else { + if (val3 <= end) { + result.push(arr3[k]); + } + k++; + } + } + + return result; +} + +export function processInChunks( + items: any[], + processFn: (item: any) => any, + chunkSize = 1000, + overscan = 0, +) { + return new Promise((resolve) => { + if (items.length === 0) { + resolve([]); + return; + } + + let result: any[] = []; + let index = 0; + + const processNextChunk = () => { + const chunk = items.slice(index, index + chunkSize + overscan); + result = result.concat(processFn(chunk)); + index += chunkSize; + + if (index < items.length) { + setTimeout(processNextChunk, 0); + } else { + resolve(result); + } + }; + + processNextChunk(); + }); +} diff --git a/frontend/app/components/shared/DevTools/TimeTable/TimeTable.tsx b/frontend/app/components/shared/DevTools/TimeTable/TimeTable.tsx index 8075b918b..50c132f6f 100644 --- a/frontend/app/components/shared/DevTools/TimeTable/TimeTable.tsx +++ b/frontend/app/components/shared/DevTools/TimeTable/TimeTable.tsx @@ -145,7 +145,7 @@ function TimeTable(props: Props) { ]); React.useEffect(() => { if (props.activeIndex && props.activeIndex >= 0 && scroller.current) { - scroller.current.scrollToIndex(props.activeIndex); + scroller.current.scrollToIndex(props.activeIndex, { align: 'center', smooth: false }); setFirstVisibleRowIndex(props.activeIndex ?? 0); } }, [props.activeIndex]); diff --git a/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx b/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx index 5a7c54914..35350d69a 100644 --- a/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx +++ b/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx @@ -19,7 +19,7 @@ const AUTOREFRESH_INTERVAL = 2 * 60 * 1000; const PER_PAGE = 10; function LiveSessionList() { - const { searchStoreLive, sessionStore, customFieldStore } = useStore(); + const { searchStoreLive, sessionStore, customFieldStore, projectsStore } = useStore(); const filter = searchStoreLive.instance; const list = sessionStore.liveSessions; const { totalLiveSessions } = sessionStore; @@ -47,26 +47,18 @@ function LiveSessionList() { const _filter = { ...filter }; let shouldUpdate = false; - - // Set default sort if not already set if (sortOptions[1] && !filter.sort) { _filter.sort = sortOptions[1].value; shouldUpdate = true; } - - // Only update filters if there's a change if (shouldUpdate) { searchStoreLive.edit(_filter); } - - // Start auto-refresh timeout timeout(); - - // Cleanup on component unmount or re-run return () => { clearTimeout(timeoutId); }; - }, [metaListLoading, filter.sort]); // Add necessary dependencies + }, [metaListLoading, filter.sort]); const refetch = () => { void searchStoreLive.fetchSessions(); @@ -98,7 +90,7 @@ function LiveSessionList() {
    - +
    {t('Sort By')} @@ -166,7 +158,6 @@ function LiveSessionList() {
    } - // image={} show={!loading && list.length === 0} >
    diff --git a/frontend/app/components/shared/LiveSessionReloadButton/LiveSessionReloadButton.tsx b/frontend/app/components/shared/LiveSessionReloadButton/LiveSessionReloadButton.tsx index c780d6f2b..aeac1ba10 100644 --- a/frontend/app/components/shared/LiveSessionReloadButton/LiveSessionReloadButton.tsx +++ b/frontend/app/components/shared/LiveSessionReloadButton/LiveSessionReloadButton.tsx @@ -4,15 +4,11 @@ import { observer } from 'mobx-react-lite'; import ReloadButton from '../ReloadButton'; import { useTranslation } from 'react-i18next'; -interface Props { - onClick: () => void; -} - -function LiveSessionReloadButton(props: Props) { +function LiveSessionReloadButton() { const { t } = useTranslation(); - const { sessionStore } = useStore(); - const { onClick } = props; - const loading = sessionStore.loadingLiveSessions; + const { searchStoreLive } = useStore(); + const onClick = searchStoreLive.fetchSessions + const loading = searchStoreLive.loading; return ( ); diff --git a/frontend/app/components/shared/OutsideClickDetectingDiv/OutsideClickDetectingDiv.js b/frontend/app/components/shared/OutsideClickDetectingDiv/OutsideClickDetectingDiv.js index 1074a2553..6dc49bb4a 100644 --- a/frontend/app/components/shared/OutsideClickDetectingDiv/OutsideClickDetectingDiv.js +++ b/frontend/app/components/shared/OutsideClickDetectingDiv/OutsideClickDetectingDiv.js @@ -1,4 +1,3 @@ -import { findDOMNode } from 'react-dom'; import React, { useRef, useLayoutEffect } from 'react'; const refs = []; diff --git a/frontend/app/components/shared/ReloadButton/ReloadButton.tsx b/frontend/app/components/shared/ReloadButton/ReloadButton.tsx index a165a1cb2..66c517a87 100644 --- a/frontend/app/components/shared/ReloadButton/ReloadButton.tsx +++ b/frontend/app/components/shared/ReloadButton/ReloadButton.tsx @@ -18,6 +18,7 @@ export default function ReloadButton(props: Props) {
    )} diff --git a/frontend/app/components/shared/SessionItem/SessionMetaList/SessionMetaList.tsx b/frontend/app/components/shared/SessionItem/SessionMetaList/SessionMetaList.tsx index 4ceb4f3fe..3c7608138 100644 --- a/frontend/app/components/shared/SessionItem/SessionMetaList/SessionMetaList.tsx +++ b/frontend/app/components/shared/SessionItem/SessionMetaList/SessionMetaList.tsx @@ -7,6 +7,7 @@ interface Props { className?: string; metaList: any[]; maxLength?: number; + onMetaClick?: (meta: { name: string, value: string }) => void; } export default function SessionMetaList(props: Props) { @@ -15,9 +16,9 @@ export default function SessionMetaList(props: Props) { return (
    {metaList.slice(0, maxLength).map(({ label, value }, index) => ( - - - +
    props.onMetaClick?.({ name: `_${label}`, value })}> + +
    ))} {metaList.length > maxLength && ( diff --git a/frontend/app/components/shared/SessionsTabOverview/SessionsTabOverview.tsx b/frontend/app/components/shared/SessionsTabOverview/SessionsTabOverview.tsx index 39f544fc1..79fae1c9d 100644 --- a/frontend/app/components/shared/SessionsTabOverview/SessionsTabOverview.tsx +++ b/frontend/app/components/shared/SessionsTabOverview/SessionsTabOverview.tsx @@ -1,8 +1,4 @@ -import { Input } from 'antd'; import React from 'react'; -import { useStore } from 'App/mstore'; - -import { observer } from 'mobx-react-lite'; import NoSessionsMessage from 'Shared/NoSessionsMessage/NoSessionsMessage'; import MainSearchBar from 'Shared/MainSearchBar/MainSearchBar'; import usePageTitle from '@/hooks/usePageTitle'; @@ -13,22 +9,8 @@ import SessionHeader from './components/SessionHeader'; import LatestSessionsMessage from './components/LatestSessionsMessage'; function SessionsTabOverview() { - const [query, setQuery] = React.useState(''); - const { aiFiltersStore, searchStore } = useStore(); - const appliedFilter = searchStore.instance; usePageTitle('Sessions - OpenReplay'); - const handleKeyDown = (event: any) => { - if (event.key === 'Enter') { - fetchResults(); - } - }; - const fetchResults = () => { - void aiFiltersStore.omniSearch(query, appliedFilter.toData()); - }; - - const testingKey = - localStorage.getItem('__mauricio_testing_access') === 'true'; return ( <> @@ -36,15 +18,6 @@ function SessionsTabOverview() {
    - {testingKey ? ( - setQuery(e.target.value)} - className="mb-2" - placeholder="ask session ai" - /> - ) : null}
    @@ -59,4 +32,4 @@ export default withPermissions( '', false, false, -)(observer(SessionsTabOverview)); +)(SessionsTabOverview); diff --git a/frontend/app/components/shared/SessionsTabOverview/components/LatestSessionsMessage/LatestSessionsMessage.tsx b/frontend/app/components/shared/SessionsTabOverview/components/LatestSessionsMessage/LatestSessionsMessage.tsx index 791ef3d75..66cf7be5a 100644 --- a/frontend/app/components/shared/SessionsTabOverview/components/LatestSessionsMessage/LatestSessionsMessage.tsx +++ b/frontend/app/components/shared/SessionsTabOverview/components/LatestSessionsMessage/LatestSessionsMessage.tsx @@ -23,9 +23,7 @@ function LatestSessionsMessage() { {t('Show')} {numberWithCommas(count)} {t('New')}{' '} {count > 1 ? t('Sessions') : t('Session')}
    - ) : ( - <> - ); + ) : null; } export default observer(LatestSessionsMessage); diff --git a/frontend/app/components/shared/SessionsTabOverview/components/SessionList/SessionList.tsx b/frontend/app/components/shared/SessionsTabOverview/components/SessionList/SessionList.tsx index 8a789b97f..d60d1a3a6 100644 --- a/frontend/app/components/shared/SessionsTabOverview/components/SessionList/SessionList.tsx +++ b/frontend/app/components/shared/SessionsTabOverview/components/SessionList/SessionList.tsx @@ -38,13 +38,10 @@ function SessionList() { } = useStore(); const { isEnterprise } = userStore; const { isLoggedIn } = userStore; - const { list } = sessionStore; - const { lastPlayedSessionId } = sessionStore; + const { lastPlayedSessionId, list, total } = sessionStore; const loading = sessionStore.loadingSessions; - const { total } = sessionStore; const onToggleFavorite = sessionStore.toggleFavorite; - const { siteId } = projectsStore; - const { updateProjectRecordingStatus } = projectsStore; + const { updateProjectRecordingStatus, siteId, previousSiteid } = projectsStore; const { currentPage, activeTab, pageSize } = searchStore; const { filters } = searchStore.instance; const _filterKeys = filters.map((i: any) => i.key); @@ -56,7 +53,7 @@ function SessionList() { const metaList = customFieldStore.list; useEffect(() => { - if (!searchStore.urlParsed) return; + if (!searchStore.urlParsed || siteId !== previousSiteid) return; void searchStore.checkForLatestSessionCount(); }, [location.pathname]); diff --git a/frontend/app/components/shared/SessionsTabOverview/components/SessionTags/SessionTags.tsx b/frontend/app/components/shared/SessionsTabOverview/components/SessionTags/SessionTags.tsx index d22000d64..018e0b389 100644 --- a/frontend/app/components/shared/SessionsTabOverview/components/SessionTags/SessionTags.tsx +++ b/frontend/app/components/shared/SessionsTabOverview/components/SessionTags/SessionTags.tsx @@ -20,73 +20,13 @@ const tagIcons = { function SessionTags() { const { t } = useTranslation(); const screens = useBreakpoint(); - const { projectsStore, sessionStore, searchStore } = useStore(); - const total = sessionStore.total; + const { projectsStore, searchStore } = useStore(); const platform = projectsStore.active?.platform || ''; const activeTab = searchStore.activeTags; - const [isMobile, setIsMobile] = useState(false); - const [isDropdownOpen, setIsDropdownOpen] = useState(false); - const dropdownRef = useRef(null); - const filteredOptions = issues_types - .filter( - (tag) => - tag.type !== 'mouse_thrashing' && - (platform === 'web' - ? tag.type !== types.TAP_RAGE - : tag.type !== types.CLICK_RAGE), - ) - .map((tag) => ({ - value: tag.type, - icon: tagIcons[tag.type], - label: t(tag.name), - })); - - // Find the currently active option - const activeOption = - filteredOptions.find((option) => option.value === activeTab[0]) || - filteredOptions[0]; - - // Check if on mobile - useEffect(() => { - const checkIfMobile = () => { - setIsMobile(window.innerWidth < 768); - }; - - checkIfMobile(); - window.addEventListener('resize', checkIfMobile); - - return () => { - window.removeEventListener('resize', checkIfMobile); - }; - }, []); - - // Close dropdown when clicking outside - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if ( - dropdownRef.current && - !(dropdownRef.current as HTMLElement).contains(event.target as Node) - ) { - setIsDropdownOpen(false); - } - }; - - document.addEventListener('mousedown', handleClickOutside); - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; - }, []); - - // Handler for dropdown item selection - const handleSelectOption = (value: string) => { - searchStore.toggleTag(value as any); - setIsDropdownOpen(false); - }; - - if (total === 0 && (activeTab.length === 0 || activeTab[0] === 'all')) { - return null; - } + React.useEffect(() => { + searchStore.resetTags(); + }, [projectsStore.activeSiteId]) return (
    diff --git a/frontend/app/components/shared/TrackingCodeModal/InstallDocs/InstallDocs.js b/frontend/app/components/shared/TrackingCodeModal/InstallDocs/InstallDocs.js index b9f400d4e..526382406 100644 --- a/frontend/app/components/shared/TrackingCodeModal/InstallDocs/InstallDocs.js +++ b/frontend/app/components/shared/TrackingCodeModal/InstallDocs/InstallDocs.js @@ -5,17 +5,18 @@ import stl from './installDocs.module.css'; import { useTranslation } from 'react-i18next'; const installationCommand = 'npm i @openreplay/tracker'; -const usageCode = `import Tracker from '@openreplay/tracker'; +const usageCode = `import { tracker } from '@openreplay/tracker'; -const tracker = new Tracker({ +tracker.configure({ projectKey: "PROJECT_KEY", ingestPoint: "https://${window.location.hostname}/ingest", }); tracker.start()`; -const usageCodeSST = `import Tracker from '@openreplay/tracker/cjs'; +const usageCodeSST = `import { tracker } from '@openreplay/tracker/cjs'; +// alternatively you can use dynamic import without /cjs suffix to prevent issues with window scope -const tracker = new Tracker({ +tracker.configure({ projectKey: "PROJECT_KEY", ingestPoint: "https://${window.location.hostname}/ingest", }); diff --git a/frontend/app/components/ui/Notification/Notification.js b/frontend/app/components/ui/Notification/Notification.js index 2ad5e5aa9..034fd6125 100644 --- a/frontend/app/components/ui/Notification/Notification.js +++ b/frontend/app/components/ui/Notification/Notification.js @@ -5,17 +5,14 @@ import styles from './notification.module.css'; export default function ({ transition = Flip, position = 'bottom-right', - autoClose = 3000, ...props }) { return ( void }) { + const history = useHistory() + + const onClick = () => { + history.push(client('account')) + } + return ( +
    + +
    + OpenReplay now supports French, Russian, Chinese, and Spanish 🎉. Update your language in settings. +
    +
    + +
    + ) +} + +export default LangBanner \ No newline at end of file diff --git a/frontend/app/layout/TopHeader.tsx b/frontend/app/layout/TopHeader.tsx index 07a41bd28..045098637 100644 --- a/frontend/app/layout/TopHeader.tsx +++ b/frontend/app/layout/TopHeader.tsx @@ -1,6 +1,7 @@ import { Layout, Space, Tooltip } from 'antd'; import { observer } from 'mobx-react-lite'; import React, { useEffect } from 'react'; +import LangBanner from './LangBanner'; import { INDEXES } from 'App/constants/zindex'; import Logo from 'App/layout/Logo'; @@ -11,14 +12,27 @@ import { useTranslation } from 'react-i18next'; const { Header } = Layout; +const langBannerClosedKey = '__or__langBannerClosed'; +const getLangBannerClosed = () => localStorage.getItem(langBannerClosedKey) === '1' function TopHeader() { const { userStore, notificationStore, projectsStore, settingsStore } = useStore(); const { account } = userStore; const { siteId } = projectsStore; const { initialDataFetched } = userStore; + const [langBannerClosed, setLangBannerClosed] = React.useState(getLangBannerClosed); const { t } = useTranslation(); + React.useEffect(() => { + const langBannerVal = localStorage.getItem(langBannerClosedKey); + if (langBannerVal === null) { + localStorage.setItem(langBannerClosedKey, '0') + } + if (langBannerVal === '0') { + localStorage.setItem(langBannerClosedKey, '1') + } + }, []) + useEffect(() => { if (!account.id || initialDataFetched) return; Promise.all([ @@ -29,51 +43,58 @@ function TopHeader() { }); }, [account]); + const closeLangBanner = () => { + setLangBannerClosed(true); + localStorage.setItem(langBannerClosedKey, '1'); + } return ( -
    - -
    { - settingsStore.updateMenuCollapsed(!settingsStore.menuCollapsed); - }} - style={{ paddingTop: '4px' }} - className="cursor-pointer xl:block hidden" - > - + {langBannerClosed ? null : } +
    + +
    { + settingsStore.updateMenuCollapsed(!settingsStore.menuCollapsed); + }} + style={{ paddingTop: '4px' }} + className="cursor-pointer xl:block hidden" > - - -
    + mouseEnterDelay={1} + > + + +
    -
    - -
    -
    +
    + +
    + - -
    + + + ); } diff --git a/frontend/app/layout/TopRight.tsx b/frontend/app/layout/TopRight.tsx index 313a960db..8c0c3da29 100644 --- a/frontend/app/layout/TopRight.tsx +++ b/frontend/app/layout/TopRight.tsx @@ -11,18 +11,13 @@ import ProjectDropdown from 'Shared/ProjectDropdown'; import { useStore } from 'App/mstore'; import { observer } from 'mobx-react-lite'; -interface Props { - account: any; - spotOnly?: boolean; -} - -function TopRight(props: Props) { +function TopRight() { const { userStore } = useStore(); const spotOnly = userStore.scopeState === 1; const { account } = userStore; return ( - {props.spotOnly ? null : ( + {spotOnly ? null : ( <> @@ -30,7 +25,6 @@ function TopRight(props: Props) { {account.name ? : null} - )} diff --git a/frontend/app/locales/en.json b/frontend/app/locales/en.json index 57035348f..80292d6be 100644 --- a/frontend/app/locales/en.json +++ b/frontend/app/locales/en.json @@ -1498,5 +1498,8 @@ "More attribute": "More attribute", "More attributes": "More attributes", "Account settings updated successfully": "Account settings updated successfully", - "Include rage clicks": "Include rage clicks" + "Include rage clicks": "Include rage clicks", + "Interface Language": "Interface Language", + "Select the language in which OpenReplay will appear.": "Select the language in which OpenReplay will appear.", + "Language": "Language" } diff --git a/frontend/app/locales/es.json b/frontend/app/locales/es.json index 65670ecd8..0d2c3c73b 100644 --- a/frontend/app/locales/es.json +++ b/frontend/app/locales/es.json @@ -1498,5 +1498,8 @@ "More attribute": "Más atributos", "More attributes": "Más atributos", "Account settings updated successfully": "Configuración de la cuenta actualizada correctamente", - "Include rage clicks": "Incluir clics de ira" + "Include rage clicks": "Incluir clics de ira", + "Interface Language": "Idioma de la interfaz", + "Select the language in which OpenReplay will appear.": "Selecciona el idioma en el que aparecerá OpenReplay.", + "Language": "Idioma" } diff --git a/frontend/app/locales/fr.json b/frontend/app/locales/fr.json index 3292c2423..91c535204 100644 --- a/frontend/app/locales/fr.json +++ b/frontend/app/locales/fr.json @@ -1498,5 +1498,8 @@ "More attribute": "Plus d'attributs", "More attributes": "Plus d'attributs", "Account settings updated successfully": "Paramètres du compte mis à jour avec succès", - "Include rage clicks": "Inclure les clics de rage" + "Include rage clicks": "Inclure les clics de rage", + "Interface Language": "Langue de l'interface", + "Select the language in which OpenReplay will appear.": "Sélectionnez la langue dans laquelle OpenReplay apparaîtra.", + "Language": "Langue" } diff --git a/frontend/app/locales/ru.json b/frontend/app/locales/ru.json index 80c720f3b..d713686d1 100644 --- a/frontend/app/locales/ru.json +++ b/frontend/app/locales/ru.json @@ -1498,5 +1498,8 @@ "More attribute": "Еще атрибут", "More attributes": "Еще атрибуты", "Account settings updated successfully": "Настройки аккаунта успешно обновлены", - "Include rage clicks": "Включить невыносимые клики" + "Include rage clicks": "Включить невыносимые клики", + "Interface Language": "Язык интерфейса", + "Select the language in which OpenReplay will appear.": "Выберите язык, на котором будет отображаться OpenReplay.", + "Language": "Язык" } diff --git a/frontend/app/locales/zh.json b/frontend/app/locales/zh.json index f60b057f9..236164820 100644 --- a/frontend/app/locales/zh.json +++ b/frontend/app/locales/zh.json @@ -1498,5 +1498,8 @@ "More attributes": "更多属性", "More attribute": "更多属性", "Account settings updated successfully": "帐户设置已成功更新", - "Include rage clicks": "包括点击狂怒" + "Include rage clicks": "包括点击狂怒", + "Interface Language": "界面语言", + "Select the language in which OpenReplay will appear.": "选择 OpenReplay 将显示的语言。", + "Language": "语言" } diff --git a/frontend/app/mstore/dashboardStore.ts b/frontend/app/mstore/dashboardStore.ts index b684bbccd..723b97c7d 100644 --- a/frontend/app/mstore/dashboardStore.ts +++ b/frontend/app/mstore/dashboardStore.ts @@ -34,7 +34,7 @@ export default class DashboardStore { comparisonFilter: Filter = new Filter(); - drillDownPeriod: Record = Period({ rangeName: LAST_7_DAYS }); + drillDownPeriod: Record = Period({ rangeName: LAST_24_HOURS }); selectedDensity: number = 7; // depends on default drilldown, 7 points here!!!; diff --git a/frontend/app/mstore/projectsStore.ts b/frontend/app/mstore/projectsStore.ts index ce53148d7..0b1caf5c1 100644 --- a/frontend/app/mstore/projectsStore.ts +++ b/frontend/app/mstore/projectsStore.ts @@ -1,4 +1,4 @@ -import { makeAutoObservable, runInAction } from 'mobx'; +import { makeAutoObservable, runInAction, reaction } from 'mobx'; import { GLOBAL_HAS_NO_RECORDINGS, SITE_ID_STORAGE_KEY, @@ -20,6 +20,7 @@ export default class ProjectsStore { instance: Project | null = null; siteId: string | null = null; + previousSiteid: string | null = null; active: Project | null = null; @@ -37,6 +38,15 @@ export default class ProjectsStore { const storedSiteId = localStorage.getItem(SITE_ID_STORAGE_KEY); this.siteId = storedSiteId ?? null; makeAutoObservable(this); + + reaction( + () => this.activeSiteId, + (_, prevId) => { + if (prevId) { + this.previousSiteid = prevId; + } + } + ) } get isMobile() { diff --git a/frontend/app/mstore/searchStore.ts b/frontend/app/mstore/searchStore.ts index da92fedd6..1f2e74553 100644 --- a/frontend/app/mstore/searchStore.ts +++ b/frontend/app/mstore/searchStore.ts @@ -205,6 +205,10 @@ class SearchStore { }); } + resetTags = () => { + this.activeTags = ['all']; + } + toggleTag(tag?: iTag) { if (!tag) { this.activeTags = []; diff --git a/frontend/app/mstore/searchStoreLive.ts b/frontend/app/mstore/searchStoreLive.ts index 3d53db791..79cb1b13a 100644 --- a/frontend/app/mstore/searchStoreLive.ts +++ b/frontend/app/mstore/searchStoreLive.ts @@ -75,6 +75,8 @@ class SearchStoreLive { loadingFilterSearch = false; + loading = false; + constructor() { makeAutoObservable(this); @@ -220,6 +222,7 @@ class SearchStoreLive { updateFilter = (index: number, search: Partial) => { const newFilters = this.instance.filters.map((_filter: any, i: any) => { if (i === index) { + search.value = checkFilterValue(search.value); return search; } return _filter; @@ -242,11 +245,25 @@ class SearchStoreLive { }); }; - async fetchSessions() { - await sessionStore.fetchLiveSessions({ - ...this.instance.toSearch(), - page: this.currentPage, - }); + setLoading = (val: boolean) => { + this.loading = val; + } + + fetchSessions = async (force?: boolean) => { + if (!force && this.loading) { + return; + } + this.setLoading(true) + try { + await sessionStore.fetchLiveSessions({ + ...this.instance.toSearch(), + page: this.currentPage, + }); + } catch (e) { + console.error('Error fetching sessions:', e); + } finally { + this.setLoading(false) + } } } diff --git a/frontend/app/mstore/sessionStore.ts b/frontend/app/mstore/sessionStore.ts index e6c51c8e2..a0301adee 100644 --- a/frontend/app/mstore/sessionStore.ts +++ b/frontend/app/mstore/sessionStore.ts @@ -288,10 +288,7 @@ export default class SessionStore { params.order = params.order === 'asc' ? 'desc' : 'asc'; } const data: any = await sessionService.getLiveSessions(params); - this.liveSessions = data.sessions.map( - (session: any) => new Session({ ...session, live: true }), - ); - this.totalLiveSessions = data.total; + this.customSetSessions(data); } catch (e) { console.error(e); } finally { @@ -603,7 +600,9 @@ export default class SessionStore { }; customSetSessions = (data: any) => { - this.liveSessions = data.sessions.map((s: any) => new Session(s)); + this.liveSessions = data.sessions.map( + (session: any) => new Session({ ...session, live: true }), + ); this.totalLiveSessions = data.total; }; diff --git a/frontend/app/mstore/types/filterItem.ts b/frontend/app/mstore/types/filterItem.ts index d53122d99..f8785035d 100644 --- a/frontend/app/mstore/types/filterItem.ts +++ b/frontend/app/mstore/types/filterItem.ts @@ -157,7 +157,7 @@ export default class FilterItem { const json = { type: isMetadata ? FilterKey.METADATA : this.key, isEvent: Boolean(this.isEvent), - value: this.value.map((i: any) => (i ? i.toString() : '')), + value: this.value?.map((i: any) => (i ? i.toString() : '')) || [], operator: this.operator, source: isMetadata ? this.key.replace(/^_/, '') : this.source, sourceOperator: this.sourceOperator, diff --git a/frontend/app/mstore/types/search.ts b/frontend/app/mstore/types/search.ts index 1c6c42e54..03c20904c 100644 --- a/frontend/app/mstore/types/search.ts +++ b/frontend/app/mstore/types/search.ts @@ -7,6 +7,7 @@ import Filter, { IFilter } from 'App/mstore/types/filter'; import FilterItem from 'App/mstore/types/filterItem'; import { makeAutoObservable, observable } from 'mobx'; import { LAST_24_HOURS, LAST_30_DAYS, LAST_7_DAYS } from 'Types/app/period'; +import { roundToNextMinutes } from '@/utils'; // @ts-ignore const rangeValue = DATE_RANGE_VALUES.LAST_24_HOURS; @@ -177,6 +178,7 @@ export default class Search { js.rangeValue, js.startDate, js.endDate, + 15, ); js.startDate = startDate; js.endDate = endDate; @@ -190,12 +192,11 @@ export default class Search { rangeName: string, customStartDate: number, customEndDate: number, - ): { - startDate: number; - endDate: number; - } { + roundMinutes?: number, + ): { startDate: number; endDate: number } { let endDate = new Date().getTime(); let startDate: number; + const minutes = roundMinutes || 15; switch (rangeName) { case LAST_7_DAYS: @@ -206,9 +207,7 @@ export default class Search { break; case CUSTOM_RANGE: if (!customStartDate || !customEndDate) { - throw new Error( - 'Start date and end date must be provided for CUSTOM_RANGE.', - ); + throw new Error('Start date and end date must be provided for CUSTOM_RANGE.'); } startDate = customStartDate; endDate = customEndDate; @@ -218,10 +217,12 @@ export default class Search { startDate = endDate - 24 * 60 * 60 * 1000; } - return { - startDate, - endDate, - }; + if (rangeName !== CUSTOM_RANGE) { + startDate = roundToNextMinutes(startDate, minutes); + endDate = roundToNextMinutes(endDate, minutes); + } + + return { startDate, endDate }; } fromJS({ eventsOrder, filters, events, custom, ...filterData }: any) { diff --git a/frontend/app/mstore/userStore.ts b/frontend/app/mstore/userStore.ts index 3855ffd50..7412b0968 100644 --- a/frontend/app/mstore/userStore.ts +++ b/frontend/app/mstore/userStore.ts @@ -242,18 +242,7 @@ class UserStore { resolve(response); }) .catch(async (e) => { - const err = await e.response?.json(); - runInAction(() => { - this.saving = false; - }); - const errStr = err.errors[0] - ? err.errors[0].includes('already exists') - ? this.t( - "This email is already linked to an account or team on OpenReplay and can't be used again.", - ) - : err.errors[0] - : this.t('Error saving user'); - toast.error(errStr); + toast.error(e.message || this.t("Failed to save user's data.")); reject(e); }) .finally(() => { @@ -393,15 +382,16 @@ class UserStore { this.signUpRequest = { loading: false, errors: [] }; }); } catch (error) { + const inUse = error.message.includes('already in use'); + const inUseMsg = this.t('An account with this email already exists. Please log in or use a different email address.') + const genericMsg = this.t('Error signing up; please check your data and try again') runInAction(() => { this.signUpRequest = { loading: false, errors: error.response?.errors || [], }; }); - toast.error( - this.t('Error signing up; please check your data and try again'), - ); + toast.error(inUse ? inUseMsg : genericMsg); } finally { runInAction(() => { this.signUpRequest.loading = false; diff --git a/frontend/app/player/web/MessageLoader.ts b/frontend/app/player/web/MessageLoader.ts index 160312022..5946e9a06 100644 --- a/frontend/app/player/web/MessageLoader.ts +++ b/frontend/app/player/web/MessageLoader.ts @@ -114,7 +114,7 @@ export default class MessageLoader { }); const sortedMsgs = msgs - // .sort((m1, m2) => m1.time - m2.time); + // .sort((m1, m2) => m1.time - m2.time) .sort(brokenDomSorter) .sort(sortIframes); @@ -343,7 +343,6 @@ const DOMMessages = [ MType.CreateElementNode, MType.CreateTextNode, MType.MoveNode, - MType.RemoveNode, MType.CreateIFrameDocument, ]; @@ -355,6 +354,11 @@ function brokenDomSorter(m1: PlayerMsg, m2: PlayerMsg) { if (m1.tp !== MType.CreateDocument && m2.tp === MType.CreateDocument) return 1; + if (m1.tp === MType.RemoveNode) + return 1; + if (m2.tp === MType.RemoveNode) + return -1; + const m1IsDOM = DOMMessages.includes(m1.tp); const m2IsDOM = DOMMessages.includes(m2.tp); if (m1IsDOM && m2IsDOM) { diff --git a/frontend/app/player/web/assist/AssistManager.ts b/frontend/app/player/web/assist/AssistManager.ts index 30c61b346..c78102bba 100644 --- a/frontend/app/player/web/assist/AssistManager.ts +++ b/frontend/app/player/web/assist/AssistManager.ts @@ -191,6 +191,9 @@ export default class AssistManager { auth: { token: agentToken, }, + extraHeaders: { + sessionId: this.session.sessionId, + }, query: { peerId: this.peerID, projectId, diff --git a/frontend/app/types/app/period.js b/frontend/app/types/app/period.js index 75fcf9fd3..672d47251 100644 --- a/frontend/app/types/app/period.js +++ b/frontend/app/types/app/period.js @@ -1,5 +1,6 @@ import { DateTime, Interval, Settings } from 'luxon'; import Record from 'Types/Record'; +import { roundToNextMinutes } from '@/utils'; export const LAST_30_MINUTES = 'LAST_30_MINUTES'; export const TODAY = 'TODAY'; @@ -30,7 +31,9 @@ function getRange(rangeName, offset) { now.startOf('day'), ); case LAST_24_HOURS: - return Interval.fromDateTimes(now.minus({ hours: 24 }), now); + const mod = now.minute % 15; + const next = now.plus({ minutes: mod === 0 ? 15 : 15 - mod }).startOf('minute'); + return Interval.fromDateTimes(next.minus({ hours: 24 }), next); case LAST_30_MINUTES: return Interval.fromDateTimes( now.minus({ minutes: 30 }).startOf('minute'), diff --git a/frontend/app/utils/index.ts b/frontend/app/utils/index.ts index bf2eb3ff9..e74a56f87 100644 --- a/frontend/app/utils/index.ts +++ b/frontend/app/utils/index.ts @@ -29,6 +29,15 @@ export function debounce(callback, wait, context = this) { }; } +export function debounceCall(func, wait) { + let timeout; + return function (...args) { + const context = this; + clearTimeout(timeout); + timeout = setTimeout(() => func.apply(context, args), wait); + }; +} + export function randomInt(a, b) { const min = (b ? a : 0) - 0.5; const max = b || a || Number.MAX_SAFE_INTEGER; @@ -613,3 +622,14 @@ export function exportAntCsv(tableColumns, tableData, filename = 'table.csv') { const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); saveAsFile(blob, filename); } + +export function roundToNextMinutes(timestamp: number, minutes: number): number { + const date = new Date(timestamp); + date.setSeconds(0, 0); + const currentMinutes = date.getMinutes(); + const remainder = currentMinutes % minutes; + if (remainder !== 0) { + date.setMinutes(currentMinutes + (minutes - remainder)); + } + return date.getTime(); +} diff --git a/frontend/babel.config.js b/frontend/babel.config.js index aaf309964..c611d9ca0 100644 --- a/frontend/babel.config.js +++ b/frontend/babel.config.js @@ -1,7 +1,9 @@ module.exports = { presets: [ '@babel/preset-env', - '@babel/preset-react', + ["@babel/preset-react", { + "runtime": "automatic" + }], '@babel/preset-typescript', ], plugins: [ diff --git a/frontend/package.json b/frontend/package.json index be06f0825..f286e32a9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -34,7 +34,7 @@ "@wojtekmaj/react-daterange-picker": "^6.0.0", "antd": "^5.21.2", "chroma-js": "^2.4.2", - "classnames": "^2.3.1", + "classnames": "^2.5.1", "copy-to-clipboard": "^3.3.1", "country-flag-icons": "^1.5.7", "echarts": "^5.6.0", @@ -54,25 +54,24 @@ "i18next": "^24.2.2", "i18next-browser-languagedetector": "^8.0.4", "immutable": "^4.3.7", - "import": "^0.0.6", "jest-environment-jsdom": "^29.5.0", "js-untar": "^2.0.0", "jspdf": "^2.5.1", "lottie-react": "^2.4.0", "lucide-react": "0.454.0", "luxon": "^3.5.0", - "microdiff": "^1.4.0", + "microdiff": "^1.5.0", "mobx": "^6.13.3", "mobx-persist-store": "^1.1.5", - "mobx-react-lite": "^4.0.7", + "mobx-react-lite": "^4.1.0", "prismjs": "^1.29.0", "rc-time-picker": "^3.7.3", - "react": "^18.2.0", - "react-confirm": "^0.3.0-7", + "react": "^19.0.0", + "react-confirm": "^0.3.0", "react-daterange-picker": "^2.0.1", "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^15.1.2", - "react-dom": "^18.2.0", + "react-dom": "^19.0.0", "react-google-recaptcha": "^2.1.0", "react-i18next": "^15.4.1", "react-intersection-observer": "^9.13.1", @@ -80,14 +79,14 @@ "react-router-dom": "^5.3.3", "react-select": "^5.3.2", "react-svg-map": "^2.2.0", - "react-toastify": "^9.1.1", + "react-toastify": "^11.0.5", "react18-json-view": "^0.2.8", "recharts": "^2.12.7", "socket.io-client": "^4.4.1", "syncod": "^0.0.1", "ts-api-utils": "^2.0.1", "typescript-eslint": "^8.25.0", - "virtua": "^0.39.2" + "virtua": "^0.40.3" }, "devDependencies": { "@babel/cli": "^7.23.0", @@ -107,18 +106,16 @@ "@jest/globals": "^29.7.0", "@openreplay/sourcemap-uploader": "^3.0.10", "@trivago/prettier-plugin-sort-imports": "^4.3.0", - "@types/add": "^2", "@types/eslint-plugin-jsx-a11y": "^6", "@types/luxon": "^3.4.2", "@types/node": "^22.7.8", "@types/prismjs": "^1", - "@types/react": "^18.0.9", + "@types/react": "^19.0.10", "@types/react-confirm": "^0.2.3", - "@types/react-dom": "^18.0.4", + "@types/react-dom": "^19.0.4", "@types/react-router-dom": "^5.3.3", "@typescript-eslint/eslint-plugin": "^8.25.0", "@typescript-eslint/parser": "^8.25.0", - "add": "^2.0.6", "autoprefixer": "^10.4.7", "babel-loader": "^9.1.3", "babel-plugin-prismjs": "^2.1.0", @@ -155,15 +152,14 @@ "style-loader": "^3.3.1", "svg-inline-loader": "^0.8.2", "svgo": "^2.8.0", - "tailwindcss": "^3.4.3", + "tailwindcss": "^3.4.17", "thread-loader": "^4.0.2", "ts-jest": "^29.0.5", "ts-node": "^10.7.0", - "typescript": "^4.6.4", + "typescript": "^4.9.5", "webpack": "^5.96.0", "webpack-cli": "^5.1.4", - "webpack-dev-server": "^5.1.0", - "yarn": "^1.22.22" + "webpack-dev-server": "^5.1.0" }, "engines": { "node": ">=20.18.0" diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 0c4c3c15e..f85667a47 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -3198,13 +3198,6 @@ __metadata: languageName: node linkType: hard -"@types/add@npm:^2": - version: 2.0.3 - resolution: "@types/add@npm:2.0.3" - checksum: 10c1/e1b46d681fe217a75b8210823d297d31206200f1a631393b41701fccb98ec51588cc6b10c0b02a70059512c999f7277049d89343cc1fa49bc61ceb57a78af07c - languageName: node - linkType: hard - "@types/babel__core@npm:^7.1.14": version: 7.20.5 resolution: "@types/babel__core@npm:7.20.5" @@ -3572,13 +3565,6 @@ __metadata: languageName: node linkType: hard -"@types/prop-types@npm:*": - version: 15.7.14 - resolution: "@types/prop-types@npm:15.7.14" - checksum: 10c1/7b1baa6b13a9df252daa1e88001e75e3595875f349b791aa423ceedebf4a8e05b4ebae07bcb182350add3a9180de6adb8103d9cf4a893056ccd39cde72886073 - languageName: node - linkType: hard - "@types/qs@npm:*": version: 6.9.18 resolution: "@types/qs@npm:6.9.18" @@ -3609,12 +3595,12 @@ __metadata: languageName: node linkType: hard -"@types/react-dom@npm:^18.0.4": - version: 18.3.5 - resolution: "@types/react-dom@npm:18.3.5" +"@types/react-dom@npm:^19.0.4": + version: 19.0.4 + resolution: "@types/react-dom@npm:19.0.4" peerDependencies: - "@types/react": ^18.0.0 - checksum: 10c1/2d88836ac7af7f8a0b5c1c18086177ecf384875c15dec9c1684a9336eaf9bd222c2b6893902b4a06d178ea660871c2ad1f4307643dc014921c3cea5c4122e57d + "@types/react": ^19.0.0 + checksum: 10c1/47a37eba2bcde53f7002dca4e2af81066aba8b8dec84b515043af6ca24d6a2904f1509f8856eb3a004e80fe9d25146e14fa87e1ed8dc6b4ac2f83899d3c0044d languageName: node linkType: hard @@ -3648,7 +3634,7 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:*": +"@types/react@npm:*, @types/react@npm:^19.0.10": version: 19.0.10 resolution: "@types/react@npm:19.0.10" dependencies: @@ -3657,16 +3643,6 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:^18.0.9": - version: 18.3.18 - resolution: "@types/react@npm:18.3.18" - dependencies: - "@types/prop-types": "npm:*" - csstype: "npm:^3.0.2" - checksum: 10c1/ade6b8945a8a5d8df380945fdd9dee27f42b1be83b3c932d4e0584a557f2194d59ba9ddc7495de74ee5c7e6a59e88c47a5561706d43c3424a617dac0769feb1c - languageName: node - linkType: hard - "@types/retry@npm:0.12.2": version: 0.12.2 resolution: "@types/retry@npm:0.12.2" @@ -4203,13 +4179,6 @@ __metadata: languageName: node linkType: hard -"add@npm:^2.0.6": - version: 2.0.6 - resolution: "add@npm:2.0.6" - checksum: 10c1/95e1f84d20e4c8330a05fafcce806a14837db9cbc24fd9be3f4667910ed95b9f4fa18b02c931e41269e0691ad7f461f7805796dd9b03811942a859aa06664e0d - languageName: node - linkType: hard - "agent-base@npm:6, agent-base@npm:^6.0.2": version: 6.0.2 resolution: "agent-base@npm:6.0.2" @@ -5579,14 +5548,7 @@ __metadata: languageName: node linkType: hard -"clsx@npm:^1.1.1": - version: 1.2.1 - resolution: "clsx@npm:1.2.1" - checksum: 10c1/45d7148b63e9ec9c32bd31f8289f1c52f1ca6c3a392a0d60978c8590faa71f4a9a3acef0a71eb3492a672a3cebc6019230799bfc59ec2e96b5f32eb58fe0976f - languageName: node - linkType: hard - -"clsx@npm:^2.0.0": +"clsx@npm:^2.0.0, clsx@npm:^2.1.1": version: 2.1.1 resolution: "clsx@npm:2.1.1" checksum: 10c1/441be4753d5e5797ebf734b2945129abe0cee8c578733948b71a0ca1b64808378c9ccf7a7a7794c840d21346cbb8314c5769cf68e3bba45a615b5c05f0448f35 @@ -9277,17 +9239,6 @@ __metadata: languageName: node linkType: hard -"import@npm:^0.0.6": - version: 0.0.6 - resolution: "import@npm:0.0.6" - dependencies: - optimist: "npm:0.3.x" - bin: - import: import - checksum: 10c1/8398837b4b8c62dff0c8398927ea5a2328fcbf378bf8235d8bcf689d94a5d482091a7232b52297a9b50b18ac956186dfc5682716a60bef1bb708023cb498fb05 - languageName: node - linkType: hard - "imurmurhash@npm:^0.1.4": version: 0.1.4 resolution: "imurmurhash@npm:0.1.4" @@ -10966,7 +10917,7 @@ __metadata: languageName: node linkType: hard -"loose-envify@npm:^1.0.0, loose-envify@npm:^1.1.0, loose-envify@npm:^1.2.0, loose-envify@npm:^1.3.1, loose-envify@npm:^1.4.0": +"loose-envify@npm:^1.0.0, loose-envify@npm:^1.2.0, loose-envify@npm:^1.3.1, loose-envify@npm:^1.4.0": version: 1.4.0 resolution: "loose-envify@npm:1.4.0" dependencies: @@ -11230,7 +11181,7 @@ __metadata: languageName: node linkType: hard -"microdiff@npm:^1.4.0": +"microdiff@npm:^1.5.0": version: 1.5.0 resolution: "microdiff@npm:1.5.0" checksum: 10c1/18c4223b25cbd53807858e596db474b20b61781bfccd996615b10ddda10f1c3fe7edd81f63d8fa413fee979fd3bafb3ec025e6cd72efe9f8e35f4eba9ceb021e @@ -11524,7 +11475,7 @@ __metadata: languageName: node linkType: hard -"mobx-react-lite@npm:^4.0.7": +"mobx-react-lite@npm:^4.1.0": version: 4.1.0 resolution: "mobx-react-lite@npm:4.1.0" dependencies: @@ -12002,19 +11953,17 @@ __metadata: "@svg-maps/world": "npm:^1.0.1" "@tanstack/react-query": "npm:^5.56.2" "@trivago/prettier-plugin-sort-imports": "npm:^4.3.0" - "@types/add": "npm:^2" "@types/eslint-plugin-jsx-a11y": "npm:^6" "@types/luxon": "npm:^3.4.2" "@types/node": "npm:^22.7.8" "@types/prismjs": "npm:^1" - "@types/react": "npm:^18.0.9" + "@types/react": "npm:^19.0.10" "@types/react-confirm": "npm:^0.2.3" - "@types/react-dom": "npm:^18.0.4" + "@types/react-dom": "npm:^19.0.4" "@types/react-router-dom": "npm:^5.3.3" "@typescript-eslint/eslint-plugin": "npm:^8.25.0" "@typescript-eslint/parser": "npm:^8.25.0" "@wojtekmaj/react-daterange-picker": "npm:^6.0.0" - add: "npm:^2.0.6" antd: "npm:^5.21.2" autoprefixer: "npm:^10.4.7" babel-loader: "npm:^9.1.3" @@ -12022,7 +11971,7 @@ __metadata: babel-plugin-react-require: "npm:^4.0.2" babel-plugin-recharts: "npm:^2.0.0" chroma-js: "npm:^2.4.2" - classnames: "npm:^2.3.1" + classnames: "npm:^2.5.1" compression-webpack-plugin: "npm:^10.0.0" copy-to-clipboard: "npm:^3.3.1" copy-webpack-plugin: "npm:^12.0.2" @@ -12057,7 +12006,6 @@ __metadata: i18next: "npm:^24.2.2" i18next-browser-languagedetector: "npm:^8.0.4" immutable: "npm:^4.3.7" - import: "npm:^0.0.6" jest: "npm:^29.5.0" jest-environment-jsdom: "npm:^29.5.0" js-untar: "npm:^2.0.0" @@ -12065,12 +12013,12 @@ __metadata: lottie-react: "npm:^2.4.0" lucide-react: "npm:0.454.0" luxon: "npm:^3.5.0" - microdiff: "npm:^1.4.0" + microdiff: "npm:^1.5.0" mini-css-extract-plugin: "npm:^2.6.0" minio: "npm:^7.1.3" mobx: "npm:^6.13.3" mobx-persist-store: "npm:^1.1.5" - mobx-react-lite: "npm:^4.0.7" + mobx-react-lite: "npm:^4.1.0" node-gyp: "npm:^9.0.0" postcss: "npm:^8.4.48" postcss-import: "npm:^16.1.0" @@ -12081,12 +12029,12 @@ __metadata: prettier: "npm:^3.5.2" prismjs: "npm:^1.29.0" rc-time-picker: "npm:^3.7.3" - react: "npm:^18.2.0" - react-confirm: "npm:^0.3.0-7" + react: "npm:^19.0.0" + react-confirm: "npm:^0.3.0" react-daterange-picker: "npm:^2.0.1" react-dnd: "npm:^16.0.1" react-dnd-html5-backend: "npm:^15.1.2" - react-dom: "npm:^18.2.0" + react-dom: "npm:^19.0.0" react-google-recaptcha: "npm:^2.1.0" react-i18next: "npm:^15.4.1" react-intersection-observer: "npm:^9.13.1" @@ -12094,7 +12042,7 @@ __metadata: react-router-dom: "npm:^5.3.3" react-select: "npm:^5.3.2" react-svg-map: "npm:^2.2.0" - react-toastify: "npm:^9.1.1" + react-toastify: "npm:^11.0.5" react18-json-view: "npm:^0.2.8" recharts: "npm:^2.12.7" sass: "npm:^1.51.0" @@ -12104,30 +12052,20 @@ __metadata: svg-inline-loader: "npm:^0.8.2" svgo: "npm:^2.8.0" syncod: "npm:^0.0.1" - tailwindcss: "npm:^3.4.3" + tailwindcss: "npm:^3.4.17" thread-loader: "npm:^4.0.2" ts-api-utils: "npm:^2.0.1" ts-jest: "npm:^29.0.5" ts-node: "npm:^10.7.0" - typescript: "npm:^4.6.4" + typescript: "npm:^4.9.5" typescript-eslint: "npm:^8.25.0" - virtua: "npm:^0.39.2" + virtua: "npm:^0.40.3" webpack: "npm:^5.96.0" webpack-cli: "npm:^5.1.4" webpack-dev-server: "npm:^5.1.0" - yarn: "npm:^1.22.22" languageName: unknown linkType: soft -"optimist@npm:0.3.x": - version: 0.3.7 - resolution: "optimist@npm:0.3.7" - dependencies: - wordwrap: "npm:~0.0.2" - checksum: 10c1/1fe2982217f5ba3936a354c6fdcfe265bcb696b31442e61861b15538f414a36506cdcd1dcf1adb636d65fcfc7621e418edf3f6ecb6726ee29b3641e3b8ae1318 - languageName: node - linkType: hard - "optionator@npm:^0.9.3": version: 0.9.4 resolution: "optionator@npm:0.9.4" @@ -13970,7 +13908,7 @@ __metadata: languageName: node linkType: hard -"react-confirm@npm:^0.3.0-7": +"react-confirm@npm:^0.3.0": version: 0.3.0 resolution: "react-confirm@npm:0.3.0" peerDependencies: @@ -14055,15 +13993,14 @@ __metadata: languageName: node linkType: hard -"react-dom@npm:^18.2.0": - version: 18.3.1 - resolution: "react-dom@npm:18.3.1" +"react-dom@npm:^19.0.0": + version: 19.0.0 + resolution: "react-dom@npm:19.0.0" dependencies: - loose-envify: "npm:^1.1.0" - scheduler: "npm:^0.23.2" + scheduler: "npm:^0.25.0" peerDependencies: - react: ^18.3.1 - checksum: 10c1/c016cff925b919fb1595c821326a03b47541978e3d31d166ba7dbda4b8a302b1bf7e6718e09b6052fa0e60c01c263ec12c3a6c291913fefc9325f06479c0ce10 + react: ^19.0.0 + checksum: 10c1/a8df7203aeaf200ab998b9d8617fd266392e14ca5d2c88a7cc5cb0977ea26262c27ca32208e71eb9a3335b9724ea7549defe25ae5c618c229273a8b0824d27ee languageName: node linkType: hard @@ -14232,15 +14169,15 @@ __metadata: languageName: node linkType: hard -"react-toastify@npm:^9.1.1": - version: 9.1.3 - resolution: "react-toastify@npm:9.1.3" +"react-toastify@npm:^11.0.5": + version: 11.0.5 + resolution: "react-toastify@npm:11.0.5" dependencies: - clsx: "npm:^1.1.1" + clsx: "npm:^2.1.1" peerDependencies: - react: ">=16" - react-dom: ">=16" - checksum: 10c1/6767169d21a3105ba0a0a41034da83e711fb4c213386358f724fae861630f6ddb205a930a1241a1c5fe61229ad52634b85c448329b01356962411c0e38af25ca + react: ^18 || ^19 + react-dom: ^18 || ^19 + checksum: 10c1/608c0a24fdbfcabe19f33570b9469921857ba24efb929083ab46c321b99cefeb4c76f53a788bb8796ff49bb729d7ab8d22a0ef17c1ea6f70e47ee74ec89aadf1 languageName: node linkType: hard @@ -14270,12 +14207,10 @@ __metadata: languageName: node linkType: hard -"react@npm:^18.2.0": - version: 18.3.1 - resolution: "react@npm:18.3.1" - dependencies: - loose-envify: "npm:^1.1.0" - checksum: 10c1/83b2f9273841f67512e317005a5cd267d39aa96ad2c59ed657db45b3c767b750106b7aa6e474189fc9e8bef81af73f7c64091fffe3b56bf7f88ee16afa5c90a3 +"react@npm:^19.0.0": + version: 19.0.0 + resolution: "react@npm:19.0.0" + checksum: 10c1/faddc178d86c177613600ae875a2ee00b72dd087bfd4c04fbe60ca75ebb54feb365d7ff0292fe42eef12eb2725c1a57fb04125003a99908c52f6c17739e3a4ea languageName: node linkType: hard @@ -14859,12 +14794,10 @@ __metadata: languageName: node linkType: hard -"scheduler@npm:^0.23.2": - version: 0.23.2 - resolution: "scheduler@npm:0.23.2" - dependencies: - loose-envify: "npm:^1.1.0" - checksum: 10c1/afb2a2463800e4a66a6eef70c9a683a0827c61dc0c4185748b9ab1db195e5ea0b798be0c91abda39e200ee231194ba99e83e93f35a1734be8d9de1f05e2a3b69 +"scheduler@npm:^0.25.0": + version: 0.25.0 + resolution: "scheduler@npm:0.25.0" + checksum: 10c1/78e375e1b6cae8d2680a856fa5d4fad57f002b6f3cbdc8d519cc27aac16d6f4df03d4276aabea783deb15978ce172a1650c2fbc91bf573371fd65841429a2894 languageName: node linkType: hard @@ -15883,7 +15816,7 @@ __metadata: languageName: node linkType: hard -"tailwindcss@npm:^3.4.3": +"tailwindcss@npm:^3.4.17": version: 3.4.17 resolution: "tailwindcss@npm:3.4.17" dependencies: @@ -16469,7 +16402,7 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^4.6.4": +"typescript@npm:^4.9.5": version: 4.9.5 resolution: "typescript@npm:4.9.5" bin: @@ -16479,7 +16412,7 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A^4.6.4#optional!builtin": +"typescript@patch:typescript@npm%3A^4.9.5#optional!builtin": version: 4.9.5 resolution: "typescript@patch:typescript@npm%3A4.9.5#optional!builtin::version=4.9.5&hash=289587" bin: @@ -16811,9 +16744,9 @@ __metadata: languageName: node linkType: hard -"virtua@npm:^0.39.2": - version: 0.39.3 - resolution: "virtua@npm:0.39.3" +"virtua@npm:^0.40.3": + version: 0.40.3 + resolution: "virtua@npm:0.40.3" peerDependencies: react: ">=16.14.0" react-dom: ">=16.14.0" @@ -16831,7 +16764,7 @@ __metadata: optional: true vue: optional: true - checksum: 10c1/23e07ee76e3b3b5cdee98cb9979217993281ae40fcc562e4254287ba63539df66921c8072e495a9da6f387758949abb20c49415975043931141d19245c174b44 + checksum: 10c1/b7ac9d763130fb6d6cfcada16d8a81299a4110ebc2475d0d1b094b5f67a3d04df88b3c54bdce6b730d3a3390c9809af73ca9429273e9cdea24700f8167e1a921 languageName: node linkType: hard @@ -17227,13 +17160,6 @@ __metadata: languageName: node linkType: hard -"wordwrap@npm:~0.0.2": - version: 0.0.3 - resolution: "wordwrap@npm:0.0.3" - checksum: 10c1/50d0a1578e1a638f3faf4491bf629b29707b82ae69311e4da8bee69e9b4489a386bb5bbde8e7e56dd6ccd9a98662d2b84921433f307dea8a1f374ff81cb12954 - languageName: node - linkType: hard - "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": version: 7.0.0 resolution: "wrap-ansi@npm:7.0.0" @@ -17432,16 +17358,6 @@ __metadata: languageName: node linkType: hard -"yarn@npm:^1.22.22": - version: 1.22.22 - resolution: "yarn@npm:1.22.22" - bin: - yarn: bin/yarn.js - yarnpkg: bin/yarn.js - checksum: 10c1/dc6d60e86d3bcd9e678d7c9e61888eb7ca642efe33e7ab932f6283ad0998da18802a877710e20bac5a98ec30e6c2b5bc098a1f66f7d29a1fbcd28392e8a0c1a9 - languageName: node - linkType: hard - "yauzl@npm:^2.10.0": version: 2.10.0 resolution: "yauzl@npm:2.10.0" diff --git a/tracker/tracker-assist/.yarnrc.yml b/networkProxy/.yarnrc.yml similarity index 100% rename from tracker/tracker-assist/.yarnrc.yml rename to networkProxy/.yarnrc.yml diff --git a/networkProxy/package-lock.json b/networkProxy/package-lock.json deleted file mode 100644 index 5e3c8ae4e..000000000 --- a/networkProxy/package-lock.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "network-proxy", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "network-proxy", - "version": "1.0.0", - "license": "ISC", - "devDependencies": { - "typescript": "^5.6.2" - } - }, - "node_modules/typescript": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", - "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - } - } -} diff --git a/networkProxy/package.json b/networkProxy/package.json index 4c08a2ffc..3acce8bae 100644 --- a/networkProxy/package.json +++ b/networkProxy/package.json @@ -1,6 +1,6 @@ { "name": "@openreplay/network-proxy", - "version": "1.0.5", + "version": "1.1.0", "description": "this library helps us to create proxy objects for fetch, XHR and beacons for proper request tracking.", "main": "dist/index.js", "module": "dist/index.js", @@ -17,9 +17,10 @@ "author": "Nikita ", "license": "MIT", "devDependencies": { - "@vitest/coverage-istanbul": "^2.1.1", - "jsdom": "^25.0.1", - "typescript": "^5.6.2", - "vitest": "^2.1.1" - } + "@vitest/coverage-istanbul": "^3.0.9", + "jsdom": "^26.0.0", + "typescript": "^5.8.2", + "vitest": "^3.0.9" + }, + "packageManager": "yarn@4.7.0" } diff --git a/networkProxy/src/networkMessage.ts b/networkProxy/src/networkMessage.ts index b0dade2cc..c33be7036 100644 --- a/networkProxy/src/networkMessage.ts +++ b/networkProxy/src/networkMessage.ts @@ -4,6 +4,13 @@ import { httpMethod, RequestState, } from './types' +import { + tryFilterUrl, + filterHeaders, + filterBody, + sanitizeObject, +} from "./sanitizers"; + /** * I know we're not using most of the information from this class * but it can be useful in the future if we will decide to display more stuff in our ui @@ -39,14 +46,19 @@ export default class NetworkMessage { getMessage(): INetworkMessage | null { const { reqHs, resHs } = this.writeHeaders() + const reqBody = this.method === 'GET' + ? JSON.stringify(sanitizeObject(this.getData)) : filterBody(this.requestData) const request = { - headers: reqHs, - body: this.method === 'GET' ? JSON.stringify(this.getData) : this.requestData, + headers: filterHeaders(reqHs), + body: reqBody, + } + const response = { + headers: filterHeaders(resHs), + body: filterBody(this.response) } - const response = { headers: resHs, body: this.response } const messageInfo = this.sanitize({ - url: this.url, + url: tryFilterUrl(this.url), method: this.method, status: this.status, request, diff --git a/networkProxy/src/sanitizers.ts b/networkProxy/src/sanitizers.ts new file mode 100644 index 000000000..97618123a --- /dev/null +++ b/networkProxy/src/sanitizers.ts @@ -0,0 +1,130 @@ +export const sensitiveParams = new Set([ + "password", + "pass", + "pwd", + "mdp", + "token", + "bearer", + "jwt", + "api_key", + "api-key", + "apiKey", + "secret", + "ssn", + "zip", + "zipcode", + "x-api-key", + "www-authenticate", + "x-csrf-token", + "x-requested-with", + "x-forwarded-for", + "x-real-ip", + "cookie", + "authorization", + "auth", + "proxy-authorization", + "set-cookie", + "account_key", +]); + +function numDigits(x) { + return (Math.log10((x ^ (x >> 31)) - (x >> 31)) | 0) + 1; +} + +function obscure(value: string | number) { + if (typeof value === "number") { + const digits = numDigits(value) + return "9".repeat(digits) + } + return value.replace(/[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff\s]/g, '*') +} + +export function filterHeaders(headers: Record | { name: string; value: string }[]) { + const filteredHeaders: Record = {}; + if (Array.isArray(headers)) { + headers.forEach(({ name, value }) => { + if (sensitiveParams.has(name.toLowerCase())) { + filteredHeaders[name] = obscure(value); + } else { + filteredHeaders[name] = value; + } + }); + } else { + for (const [key, value] of Object.entries(headers)) { + if (sensitiveParams.has(key.toLowerCase())) { + filteredHeaders[key] = obscure(value); + } else { + filteredHeaders[key] = value; + } + } + } + return filteredHeaders; +} + +export function filterBody(body: any): string { + if (!body) { + return body; + } + + let parsedBody; + let isJSON = false; + + try { + parsedBody = JSON.parse(body); + isJSON = true; + } catch (e) { + // not json + } + + if (isJSON) { + obscureSensitiveData(parsedBody); + return JSON.stringify(parsedBody); + } else { + const params = new URLSearchParams(body); + for (const key of params.keys()) { + if (sensitiveParams.has(key.toLowerCase())) { + const value = obscure(params.get(key)) + params.set(key, value); + } + } + + return params.toString(); + } +} + +export function sanitizeObject(obj: Record) { + obscureSensitiveData(obj) + return obj +} +function obscureSensitiveData(obj: Record | any[]) { + if (Array.isArray(obj)) { + obj.forEach(obscureSensitiveData); + } else if (obj && typeof obj === "object") { + for (const key in obj) { + if (Object.hasOwn(obj, key)) { + if (sensitiveParams.has(key.toLowerCase())) { + obj[key] = obscure(obj[key]); + } else if (obj[key] !== null && typeof obj[key] === "object") { + obscureSensitiveData(obj[key]); + } + } + } + } +} + +export function tryFilterUrl(url: string) { + if (!url) return ""; + try { + const urlObj = new URL(url); + if (urlObj.searchParams) { + for (const key of urlObj.searchParams.keys()) { + if (sensitiveParams.has(key.toLowerCase())) { + urlObj.searchParams.set(key, "******"); + } + } + } + return urlObj.toString(); + } catch (e) { + return url; + } +} diff --git a/networkProxy/tests/networkMessage.test.ts b/networkProxy/tests/networkMessage.test.ts index 2cba10196..bb1e21098 100644 --- a/networkProxy/tests/networkMessage.test.ts +++ b/networkProxy/tests/networkMessage.test.ts @@ -13,7 +13,7 @@ describe('NetworkMessage', () => { describe('getMessage', () => { it('should properly construct and return a NetworkRequest', () => { // @ts-ignore - const networkMessage = new NetworkMessage(ignoredHeaders, setSessionTokenHeader, sanitize); + const networkMessage = new NetworkMessage(ignoredHeaders, setSessionTokenHeader, (data) => data); networkMessage.method = 'GET'; networkMessage.url = 'https://example.com'; @@ -21,25 +21,35 @@ describe('NetworkMessage', () => { networkMessage.requestType = 'xhr'; networkMessage.startTime = 0; networkMessage.duration = 500; - networkMessage.getData = { key: 'value' }; - - // Expect sanitized message - sanitize.mockReturnValueOnce({ - url: 'https://example.com', - method: 'GET', - status: 200, - request: {}, - response: {}, - }); + networkMessage.getData = { + test: 'value', + test2: 123 + }; + networkMessage.response = JSON.stringify({ + token: '123123', + password: 'qwerty123' + }) const result = networkMessage.getMessage(); const expected = { requestType: 'xhr', method: 'GET', - url: 'https://example.com', - request: JSON.stringify({}), - response: JSON.stringify({}), + url: 'https://example.com/', + request: JSON.stringify({ + headers: {}, + body: JSON.stringify({ + test: 'value', + test2: 123 + }), + }), + response: JSON.stringify({ + headers: {}, + body: JSON.stringify({ + token: '******', + password: '*********', + }), + }), status: 200, startTime: result!.startTime, duration: 500, @@ -48,7 +58,6 @@ describe('NetworkMessage', () => { expect(result).toBeDefined(); expect(result).toEqual(expected); - expect(sanitize).toHaveBeenCalledTimes(1); }); }); diff --git a/networkProxy/tsconfig.json b/networkProxy/tsconfig.json index 6378fdbb1..6361eb5dd 100644 --- a/networkProxy/tsconfig.json +++ b/networkProxy/tsconfig.json @@ -2,6 +2,7 @@ "compilerOptions": { "target": "ES2017", "module": "ES2022", + "lib": ["ES2022", "DOM", "DOM.Iterable"], "declaration": true, "outDir": "./dist", "strict": false, diff --git a/networkProxy/yarn.lock b/networkProxy/yarn.lock index c8ceba9fe..aff35a981 100644 --- a/networkProxy/yarn.lock +++ b/networkProxy/yarn.lock @@ -1,1439 +1,2863 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@ampproject/remapping@^2.2.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" - integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== - dependencies: - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.24" - -"@babel/code-frame@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.7.tgz#882fd9e09e8ee324e496bd040401c6f046ef4465" - integrity sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA== - dependencies: - "@babel/highlight" "^7.24.7" - picocolors "^1.0.0" - -"@babel/compat-data@^7.25.2": - version "7.25.4" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.4.tgz#7d2a80ce229890edcf4cc259d4d696cb4dae2fcb" - integrity sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ== - -"@babel/core@^7.23.9": - version "7.25.2" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.25.2.tgz#ed8eec275118d7613e77a352894cd12ded8eba77" - integrity sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.24.7" - "@babel/generator" "^7.25.0" - "@babel/helper-compilation-targets" "^7.25.2" - "@babel/helper-module-transforms" "^7.25.2" - "@babel/helpers" "^7.25.0" - "@babel/parser" "^7.25.0" - "@babel/template" "^7.25.0" - "@babel/traverse" "^7.25.2" - "@babel/types" "^7.25.2" - convert-source-map "^2.0.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.3" - semver "^6.3.1" - -"@babel/generator@^7.25.0", "@babel/generator@^7.25.6": - version "7.25.6" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.6.tgz#0df1ad8cb32fe4d2b01d8bf437f153d19342a87c" - integrity sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw== - dependencies: - "@babel/types" "^7.25.6" - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.25" - jsesc "^2.5.1" - -"@babel/helper-compilation-targets@^7.25.2": - version "7.25.2" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz#e1d9410a90974a3a5a66e84ff55ef62e3c02d06c" - integrity sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw== - dependencies: - "@babel/compat-data" "^7.25.2" - "@babel/helper-validator-option" "^7.24.8" - browserslist "^4.23.1" - lru-cache "^5.1.1" - semver "^6.3.1" - -"@babel/helper-module-imports@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz#f2f980392de5b84c3328fc71d38bd81bbb83042b" - integrity sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA== - dependencies: - "@babel/traverse" "^7.24.7" - "@babel/types" "^7.24.7" - -"@babel/helper-module-transforms@^7.25.2": - version "7.25.2" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz#ee713c29768100f2776edf04d4eb23b8d27a66e6" - integrity sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ== - dependencies: - "@babel/helper-module-imports" "^7.24.7" - "@babel/helper-simple-access" "^7.24.7" - "@babel/helper-validator-identifier" "^7.24.7" - "@babel/traverse" "^7.25.2" - -"@babel/helper-simple-access@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz#bcade8da3aec8ed16b9c4953b74e506b51b5edb3" - integrity sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg== - dependencies: - "@babel/traverse" "^7.24.7" - "@babel/types" "^7.24.7" - -"@babel/helper-string-parser@^7.24.8": - version "7.24.8" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz#5b3329c9a58803d5df425e5785865881a81ca48d" - integrity sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ== - -"@babel/helper-validator-identifier@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db" - integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== - -"@babel/helper-validator-option@^7.24.8": - version "7.24.8" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz#3725cdeea8b480e86d34df15304806a06975e33d" - integrity sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q== - -"@babel/helpers@^7.25.0": - version "7.25.6" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.25.6.tgz#57ee60141829ba2e102f30711ffe3afab357cc60" - integrity sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q== - dependencies: - "@babel/template" "^7.25.0" - "@babel/types" "^7.25.6" - -"@babel/highlight@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.7.tgz#a05ab1df134b286558aae0ed41e6c5f731bf409d" - integrity sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw== - dependencies: - "@babel/helper-validator-identifier" "^7.24.7" - chalk "^2.4.2" - js-tokens "^4.0.0" - picocolors "^1.0.0" - -"@babel/parser@^7.23.9", "@babel/parser@^7.25.0", "@babel/parser@^7.25.4", "@babel/parser@^7.25.6": - version "7.25.6" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.6.tgz#85660c5ef388cbbf6e3d2a694ee97a38f18afe2f" - integrity sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q== - dependencies: - "@babel/types" "^7.25.6" - -"@babel/template@^7.25.0": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.0.tgz#e733dc3134b4fede528c15bc95e89cb98c52592a" - integrity sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q== - dependencies: - "@babel/code-frame" "^7.24.7" - "@babel/parser" "^7.25.0" - "@babel/types" "^7.25.0" - -"@babel/traverse@^7.24.7", "@babel/traverse@^7.25.2": - version "7.25.6" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.6.tgz#04fad980e444f182ecf1520504941940a90fea41" - integrity sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ== - dependencies: - "@babel/code-frame" "^7.24.7" - "@babel/generator" "^7.25.6" - "@babel/parser" "^7.25.6" - "@babel/template" "^7.25.0" - "@babel/types" "^7.25.6" - debug "^4.3.1" - globals "^11.1.0" - -"@babel/types@^7.24.7", "@babel/types@^7.25.0", "@babel/types@^7.25.2", "@babel/types@^7.25.4", "@babel/types@^7.25.6": - version "7.25.6" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.6.tgz#893942ddb858f32ae7a004ec9d3a76b3463ef8e6" - integrity sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw== - dependencies: - "@babel/helper-string-parser" "^7.24.8" - "@babel/helper-validator-identifier" "^7.24.7" - to-fast-properties "^2.0.0" - -"@esbuild/aix-ppc64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f" - integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ== - -"@esbuild/android-arm64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052" - integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A== - -"@esbuild/android-arm@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28" - integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg== - -"@esbuild/android-x64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e" - integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA== - -"@esbuild/darwin-arm64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a" - integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ== - -"@esbuild/darwin-x64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22" - integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw== - -"@esbuild/freebsd-arm64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e" - integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g== - -"@esbuild/freebsd-x64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261" - integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ== - -"@esbuild/linux-arm64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b" - integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q== - -"@esbuild/linux-arm@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9" - integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA== - -"@esbuild/linux-ia32@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2" - integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg== - -"@esbuild/linux-loong64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df" - integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg== - -"@esbuild/linux-mips64el@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe" - integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg== - -"@esbuild/linux-ppc64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4" - integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w== - -"@esbuild/linux-riscv64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc" - integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA== - -"@esbuild/linux-s390x@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de" - integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A== - -"@esbuild/linux-x64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0" - integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ== - -"@esbuild/netbsd-x64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047" - integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg== - -"@esbuild/openbsd-x64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70" - integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow== - -"@esbuild/sunos-x64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b" - integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg== - -"@esbuild/win32-arm64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d" - integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A== - -"@esbuild/win32-ia32@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b" - integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA== - -"@esbuild/win32-x64@0.21.5": - version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c" - integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw== - -"@isaacs/cliui@^8.0.2": - version "8.0.2" - resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" - integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== - dependencies: - string-width "^5.1.2" - string-width-cjs "npm:string-width@^4.2.0" - strip-ansi "^7.0.1" - strip-ansi-cjs "npm:strip-ansi@^6.0.1" - wrap-ansi "^8.1.0" - wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" - -"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" - integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== - -"@jridgewell/gen-mapping@^0.3.5": - version "0.3.5" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" - integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== - dependencies: - "@jridgewell/set-array" "^1.2.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.24" - -"@jridgewell/resolve-uri@^3.1.0": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" - integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== - -"@jridgewell/set-array@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" - integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== - -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" - integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== - -"@jridgewell/trace-mapping@^0.3.23", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": - version "0.3.25" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" - integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - -"@pkgjs/parseargs@^0.11.0": - version "0.11.0" - resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" - integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== - -"@rollup/rollup-android-arm-eabi@4.22.5": - version "4.22.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.5.tgz#e0f5350845090ca09690fe4a472717f3b8aae225" - integrity sha512-SU5cvamg0Eyu/F+kLeMXS7GoahL+OoizlclVFX3l5Ql6yNlywJJ0OuqTzUx0v+aHhPHEB/56CT06GQrRrGNYww== - -"@rollup/rollup-android-arm64@4.22.5": - version "4.22.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.5.tgz#08270faef6747e2716d3e978a8bbf479f75fb19a" - integrity sha512-S4pit5BP6E5R5C8S6tgU/drvgjtYW76FBuG6+ibG3tMvlD1h9LHVF9KmlmaUBQ8Obou7hEyS+0w+IR/VtxwNMQ== - -"@rollup/rollup-darwin-arm64@4.22.5": - version "4.22.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.5.tgz#691671133b350661328d42c8dbdedd56dfb97dfd" - integrity sha512-250ZGg4ipTL0TGvLlfACkIxS9+KLtIbn7BCZjsZj88zSg2Lvu3Xdw6dhAhfe/FjjXPVNCtcSp+WZjVsD3a/Zlw== - -"@rollup/rollup-darwin-x64@4.22.5": - version "4.22.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.5.tgz#b2ec52a1615f24b1cd40bc8906ae31af81e8a342" - integrity sha512-D8brJEFg5D+QxFcW6jYANu+Rr9SlKtTenmsX5hOSzNYVrK5oLAEMTUgKWYJP+wdKyCdeSwnapLsn+OVRFycuQg== - -"@rollup/rollup-linux-arm-gnueabihf@4.22.5": - version "4.22.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.5.tgz#217f01f304808920680bd269002df38e25d9205f" - integrity sha512-PNqXYmdNFyWNg0ma5LdY8wP+eQfdvyaBAojAXgO7/gs0Q/6TQJVXAXe8gwW9URjbS0YAammur0fynYGiWsKlXw== - -"@rollup/rollup-linux-arm-musleabihf@4.22.5": - version "4.22.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.5.tgz#93ac1c5a1e389f4482a2edaeec41fcffee54a930" - integrity sha512-kSSCZOKz3HqlrEuwKd9TYv7vxPYD77vHSUvM2y0YaTGnFc8AdI5TTQRrM1yIp3tXCKrSL9A7JLoILjtad5t8pQ== - -"@rollup/rollup-linux-arm64-gnu@4.22.5": - version "4.22.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.5.tgz#a7f146787d6041fecc4ecdf1aa72234661ca94a4" - integrity sha512-oTXQeJHRbOnwRnRffb6bmqmUugz0glXaPyspp4gbQOPVApdpRrY/j7KP3lr7M8kTfQTyrBUzFjj5EuHAhqH4/w== - -"@rollup/rollup-linux-arm64-musl@4.22.5": - version "4.22.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.5.tgz#6a37236189648e678bd564d6e8ca798f42cf42c5" - integrity sha512-qnOTIIs6tIGFKCHdhYitgC2XQ2X25InIbZFor5wh+mALH84qnFHvc+vmWUpyX97B0hNvwNUL4B+MB8vJvH65Fw== - -"@rollup/rollup-linux-powerpc64le-gnu@4.22.5": - version "4.22.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.5.tgz#5661420dc463bec31ecb2d17d113de858cfcfe2d" - integrity sha512-TMYu+DUdNlgBXING13rHSfUc3Ky5nLPbWs4bFnT+R6Vu3OvXkTkixvvBKk8uO4MT5Ab6lC3U7x8S8El2q5o56w== - -"@rollup/rollup-linux-riscv64-gnu@4.22.5": - version "4.22.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.5.tgz#cb00342b7432bdef723aa606281de2f522d6dcf7" - integrity sha512-PTQq1Kz22ZRvuhr3uURH+U/Q/a0pbxJoICGSprNLAoBEkyD3Sh9qP5I0Asn0y0wejXQBbsVMRZRxlbGFD9OK4A== - -"@rollup/rollup-linux-s390x-gnu@4.22.5": - version "4.22.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.5.tgz#0708889674dccecccd28e2befccf791e0767fcb7" - integrity sha512-bR5nCojtpuMss6TDEmf/jnBnzlo+6n1UhgwqUvRoe4VIotC7FG1IKkyJbwsT7JDsF2jxR+NTnuOwiGv0hLyDoQ== - -"@rollup/rollup-linux-x64-gnu@4.22.5": - version "4.22.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.5.tgz#a135b040b21582e91cfed2267ccfc7d589e1dbc6" - integrity sha512-N0jPPhHjGShcB9/XXZQWuWBKZQnC1F36Ce3sDqWpujsGjDz/CQtOL9LgTrJ+rJC8MJeesMWrMWVLKKNR/tMOCA== - -"@rollup/rollup-linux-x64-musl@4.22.5": - version "4.22.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.5.tgz#88395a81a3ab7ee3dc8dc31a73ff62ed3185f34d" - integrity sha512-uBa2e28ohzNNwjr6Uxm4XyaA1M/8aTgfF2T7UIlElLaeXkgpmIJ2EitVNQxjO9xLLLy60YqAgKn/AqSpCUkE9g== - -"@rollup/rollup-win32-arm64-msvc@4.22.5": - version "4.22.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.5.tgz#12ee49233b1125f2c1da38392f63b1dbb0c31bba" - integrity sha512-RXT8S1HP8AFN/Kr3tg4fuYrNxZ/pZf1HemC5Tsddc6HzgGnJm0+Lh5rAHJkDuW3StI0ynNXukidROMXYl6ew8w== - -"@rollup/rollup-win32-ia32-msvc@4.22.5": - version "4.22.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.5.tgz#0f987b134c6b3123c22842b33ba0c2b6fb78cc3b" - integrity sha512-ElTYOh50InL8kzyUD6XsnPit7jYCKrphmddKAe1/Ytt74apOxDq5YEcbsiKs0fR3vff3jEneMM+3I7jbqaMyBg== - -"@rollup/rollup-win32-x64-msvc@4.22.5": - version "4.22.5" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.5.tgz#f2feb149235a5dc1deb5439758f8871255e5a161" - integrity sha512-+lvL/4mQxSV8MukpkKyyvfwhH266COcWlXE/1qxwN08ajovta3459zrjLghYMgDerlzNwLAcFpvU+WWE5y6nAQ== - -"@types/estree@1.0.6", "@types/estree@^1.0.0": - version "1.0.6" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" - integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== - -"@vitest/coverage-istanbul@^2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@vitest/coverage-istanbul/-/coverage-istanbul-2.1.1.tgz#11950fea0bff2628f3c278e2dac338e4af69c521" - integrity sha512-ZQM8uLinwmhmLp49fxLxIM46nC7NisCbaiydcQoV1hLvQfFL92Gg3tInRvowZyV78G0IknjN10JzH7oqPlPjZw== - dependencies: - "@istanbuljs/schema" "^0.1.3" - debug "^4.3.6" - istanbul-lib-coverage "^3.2.2" - istanbul-lib-instrument "^6.0.3" - istanbul-lib-report "^3.0.1" - istanbul-lib-source-maps "^5.0.6" - istanbul-reports "^3.1.7" - magicast "^0.3.4" - test-exclude "^7.0.1" - tinyrainbow "^1.2.0" - -"@vitest/expect@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-2.1.1.tgz#907137a86246c5328929d796d741c4e95d1ee19d" - integrity sha512-YeueunS0HiHiQxk+KEOnq/QMzlUuOzbU1Go+PgAsHvvv3tUkJPm9xWt+6ITNTlzsMXUjmgm5T+U7KBPK2qQV6w== - dependencies: - "@vitest/spy" "2.1.1" - "@vitest/utils" "2.1.1" - chai "^5.1.1" - tinyrainbow "^1.2.0" - -"@vitest/mocker@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-2.1.1.tgz#3e37c80ac267318d4aa03c5073a017d148dc8e67" - integrity sha512-LNN5VwOEdJqCmJ/2XJBywB11DLlkbY0ooDJW3uRX5cZyYCrc4PI/ePX0iQhE3BiEGiQmK4GE7Q/PqCkkaiPnrA== - dependencies: - "@vitest/spy" "^2.1.0-beta.1" - estree-walker "^3.0.3" - magic-string "^0.30.11" - -"@vitest/pretty-format@2.1.1", "@vitest/pretty-format@^2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-2.1.1.tgz#fea25dd4e88c3c1329fbccd1d16b1d607eb40067" - integrity sha512-SjxPFOtuINDUW8/UkElJYQSFtnWX7tMksSGW0vfjxMneFqxVr8YJ979QpMbDW7g+BIiq88RAGDjf7en6rvLPPQ== - dependencies: - tinyrainbow "^1.2.0" - -"@vitest/runner@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-2.1.1.tgz#f3b1fbc3c109fc44e2cceecc881344453f275559" - integrity sha512-uTPuY6PWOYitIkLPidaY5L3t0JJITdGTSwBtwMjKzo5O6RCOEncz9PUN+0pDidX8kTHYjO0EwUIvhlGpnGpxmA== - dependencies: - "@vitest/utils" "2.1.1" - pathe "^1.1.2" - -"@vitest/snapshot@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-2.1.1.tgz#38ef23104e90231fea5540754a19d8468afbba66" - integrity sha512-BnSku1WFy7r4mm96ha2FzN99AZJgpZOWrAhtQfoxjUU5YMRpq1zmHRq7a5K9/NjqonebO7iVDla+VvZS8BOWMw== - dependencies: - "@vitest/pretty-format" "2.1.1" - magic-string "^0.30.11" - pathe "^1.1.2" - -"@vitest/spy@2.1.1", "@vitest/spy@^2.1.0-beta.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-2.1.1.tgz#20891f7421a994256ea0d739ed72f05532c78488" - integrity sha512-ZM39BnZ9t/xZ/nF4UwRH5il0Sw93QnZXd9NAZGRpIgj0yvVwPpLd702s/Cx955rGaMlyBQkZJ2Ir7qyY48VZ+g== - dependencies: - tinyspy "^3.0.0" - -"@vitest/utils@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-2.1.1.tgz#284d016449ecb4f8704d198d049fde8360cc136e" - integrity sha512-Y6Q9TsI+qJ2CC0ZKj6VBb+T8UPz593N113nnUykqwANqhgf3QkZeHFlusgKLTqrnVHbj/XDKZcDHol+dxVT+rQ== - dependencies: - "@vitest/pretty-format" "2.1.1" - loupe "^3.1.1" - tinyrainbow "^1.2.0" - -agent-base@^7.0.2, agent-base@^7.1.0: - version "7.1.1" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.1.tgz#bdbded7dfb096b751a2a087eeeb9664725b2e317" - integrity sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA== - dependencies: - debug "^4.3.4" - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-regex@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.1.0.tgz#95ec409c69619d6cb1b8b34f14b660ef28ebd654" - integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== - -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -ansi-styles@^4.0.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -ansi-styles@^6.1.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" - integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== - -assertion-error@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-2.0.1.tgz#f641a196b335690b1070bf00b6e7593fec190bf7" - integrity sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA== - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -brace-expansion@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" - integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== - dependencies: - balanced-match "^1.0.0" - -browserslist@^4.23.1: - version "4.24.0" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.0.tgz#a1325fe4bc80b64fda169629fc01b3d6cecd38d4" - integrity sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A== - dependencies: - caniuse-lite "^1.0.30001663" - electron-to-chromium "^1.5.28" - node-releases "^2.0.18" - update-browserslist-db "^1.1.0" - -cac@^6.7.14: - version "6.7.14" - resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" - integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== - -caniuse-lite@^1.0.30001663: - version "1.0.30001664" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001664.tgz#d588d75c9682d3301956b05a3749652a80677df4" - integrity sha512-AmE7k4dXiNKQipgn7a2xg558IRqPN3jMQY/rOsbxDhrd0tyChwbITBfiwtnqz8bi2M5mIWbxAYBvk7W7QBUS2g== - -chai@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/chai/-/chai-5.1.1.tgz#f035d9792a22b481ead1c65908d14bb62ec1c82c" - integrity sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA== - dependencies: - assertion-error "^2.0.1" - check-error "^2.1.1" - deep-eql "^5.0.1" - loupe "^3.1.0" - pathval "^2.0.0" - -chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -check-error@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/check-error/-/check-error-2.1.1.tgz#87eb876ae71ee388fa0471fe423f494be1d96ccc" - integrity sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw== - -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -combined-stream@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - -convert-source-map@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" - integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== - -cross-spawn@^7.0.0: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -cssstyle@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-4.1.0.tgz#161faee382af1bafadb6d3867a92a19bcb4aea70" - integrity sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA== - dependencies: - rrweb-cssom "^0.7.1" - -data-urls@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-5.0.0.tgz#2f76906bce1824429ffecb6920f45a0b30f00dde" - integrity sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg== - dependencies: - whatwg-mimetype "^4.0.0" - whatwg-url "^14.0.0" - -debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4, debug@^4.3.6: - version "4.3.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" - integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== - dependencies: - ms "^2.1.3" - -decimal.js@^10.4.3: - version "10.4.3" - resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" - integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== - -deep-eql@^5.0.1: - version "5.0.2" - resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-5.0.2.tgz#4b756d8d770a9257300825d52a2c2cff99c3a341" - integrity sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q== - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== - -eastasianwidth@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" - integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== - -electron-to-chromium@^1.5.28: - version "1.5.29" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.29.tgz#aa592a3caa95d07cc26a66563accf99fa573a1ee" - integrity sha512-PF8n2AlIhCKXQ+gTpiJi0VhcHDb69kYX4MtCiivctc2QD3XuNZ/XIOlbGzt7WAjjEev0TtaH6Cu3arZExm5DOw== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -emoji-regex@^9.2.2: - version "9.2.2" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" - integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== - -entities@^4.4.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" - integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== - -esbuild@^0.21.3: - version "0.21.5" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d" - integrity sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw== - optionalDependencies: - "@esbuild/aix-ppc64" "0.21.5" - "@esbuild/android-arm" "0.21.5" - "@esbuild/android-arm64" "0.21.5" - "@esbuild/android-x64" "0.21.5" - "@esbuild/darwin-arm64" "0.21.5" - "@esbuild/darwin-x64" "0.21.5" - "@esbuild/freebsd-arm64" "0.21.5" - "@esbuild/freebsd-x64" "0.21.5" - "@esbuild/linux-arm" "0.21.5" - "@esbuild/linux-arm64" "0.21.5" - "@esbuild/linux-ia32" "0.21.5" - "@esbuild/linux-loong64" "0.21.5" - "@esbuild/linux-mips64el" "0.21.5" - "@esbuild/linux-ppc64" "0.21.5" - "@esbuild/linux-riscv64" "0.21.5" - "@esbuild/linux-s390x" "0.21.5" - "@esbuild/linux-x64" "0.21.5" - "@esbuild/netbsd-x64" "0.21.5" - "@esbuild/openbsd-x64" "0.21.5" - "@esbuild/sunos-x64" "0.21.5" - "@esbuild/win32-arm64" "0.21.5" - "@esbuild/win32-ia32" "0.21.5" - "@esbuild/win32-x64" "0.21.5" - -escalade@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" - integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== - -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== - -estree-walker@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d" - integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g== - dependencies: - "@types/estree" "^1.0.0" - -foreground-child@^3.1.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.0.tgz#0ac8644c06e431439f8561db8ecf29a7b5519c77" - integrity sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg== - dependencies: - cross-spawn "^7.0.0" - signal-exit "^4.0.1" - -form-data@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" - integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - -fsevents@~2.3.2, fsevents@~2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - -gensync@^1.0.0-beta.2: - version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" - integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== - -get-func-name@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" - integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== - -glob@^10.4.1: - version "10.4.5" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" - integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== - dependencies: - foreground-child "^3.1.0" - jackspeak "^3.1.2" - minimatch "^9.0.4" - minipass "^7.1.2" - package-json-from-dist "^1.0.0" - path-scurry "^1.11.1" - -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -html-encoding-sniffer@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz#696df529a7cfd82446369dc5193e590a3735b448" - integrity sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ== - dependencies: - whatwg-encoding "^3.1.1" - -html-escaper@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" - integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== - -http-proxy-agent@^7.0.2: - version "7.0.2" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz#9a8b1f246866c028509486585f62b8f2c18c270e" - integrity sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig== - dependencies: - agent-base "^7.1.0" - debug "^4.3.4" - -https-proxy-agent@^7.0.5: - version "7.0.5" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz#9e8b5013873299e11fab6fd548405da2d6c602b2" - integrity sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw== - dependencies: - agent-base "^7.0.2" - debug "4" - -iconv-lite@0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" - integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== - dependencies: - safer-buffer ">= 2.1.2 < 3.0.0" - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-potential-custom-element-name@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" - integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0, istanbul-lib-coverage@^3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" - integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== - -istanbul-lib-instrument@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" - integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== - dependencies: - "@babel/core" "^7.23.9" - "@babel/parser" "^7.23.9" - "@istanbuljs/schema" "^0.1.3" - istanbul-lib-coverage "^3.2.0" - semver "^7.5.4" - -istanbul-lib-report@^3.0.0, istanbul-lib-report@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" - integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== - dependencies: - istanbul-lib-coverage "^3.0.0" - make-dir "^4.0.0" - supports-color "^7.1.0" - -istanbul-lib-source-maps@^5.0.6: - version "5.0.6" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz#acaef948df7747c8eb5fbf1265cb980f6353a441" - integrity sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A== - dependencies: - "@jridgewell/trace-mapping" "^0.3.23" - debug "^4.1.1" - istanbul-lib-coverage "^3.0.0" - -istanbul-reports@^3.1.7: - version "3.1.7" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b" - integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g== - dependencies: - html-escaper "^2.0.0" - istanbul-lib-report "^3.0.0" - -jackspeak@^3.1.2: - version "3.4.3" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" - integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== - dependencies: - "@isaacs/cliui" "^8.0.2" - optionalDependencies: - "@pkgjs/parseargs" "^0.11.0" - -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -jsdom@^25.0.1: - version "25.0.1" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-25.0.1.tgz#536ec685c288fc8a5773a65f82d8b44badcc73ef" - integrity sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw== - dependencies: - cssstyle "^4.1.0" - data-urls "^5.0.0" - decimal.js "^10.4.3" - form-data "^4.0.0" - html-encoding-sniffer "^4.0.0" - http-proxy-agent "^7.0.2" - https-proxy-agent "^7.0.5" - is-potential-custom-element-name "^1.0.1" - nwsapi "^2.2.12" - parse5 "^7.1.2" - rrweb-cssom "^0.7.1" - saxes "^6.0.0" - symbol-tree "^3.2.4" - tough-cookie "^5.0.0" - w3c-xmlserializer "^5.0.0" - webidl-conversions "^7.0.0" - whatwg-encoding "^3.1.1" - whatwg-mimetype "^4.0.0" - whatwg-url "^14.0.0" - ws "^8.18.0" - xml-name-validator "^5.0.0" - -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" - integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== - -json5@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== - -loupe@^3.1.0, loupe@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/loupe/-/loupe-3.1.1.tgz#71d038d59007d890e3247c5db97c1ec5a92edc54" - integrity sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw== - dependencies: - get-func-name "^2.0.1" - -lru-cache@^10.2.0: - version "10.4.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" - integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== - -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - -magic-string@^0.30.11: - version "0.30.11" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.11.tgz#301a6f93b3e8c2cb13ac1a7a673492c0dfd12954" - integrity sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A== - dependencies: - "@jridgewell/sourcemap-codec" "^1.5.0" - -magicast@^0.3.4: - version "0.3.5" - resolved "https://registry.yarnpkg.com/magicast/-/magicast-0.3.5.tgz#8301c3c7d66704a0771eb1bad74274f0ec036739" - integrity sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ== - dependencies: - "@babel/parser" "^7.25.4" - "@babel/types" "^7.25.4" - source-map-js "^1.2.0" - -make-dir@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" - integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== - dependencies: - semver "^7.5.3" - -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@^2.1.12: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -minimatch@^9.0.4: - version "9.0.5" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" - integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== - dependencies: - brace-expansion "^2.0.1" - -"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" - integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== - -ms@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -nanoid@^3.3.7: - version "3.3.7" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" - integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== - -node-releases@^2.0.18: - version "2.0.18" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" - integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== - -nwsapi@^2.2.12: - version "2.2.13" - resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.13.tgz#e56b4e98960e7a040e5474536587e599c4ff4655" - integrity sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ== - -package-json-from-dist@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" - integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== - -parse5@^7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" - integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== - dependencies: - entities "^4.4.0" - -path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-scurry@^1.11.1: - version "1.11.1" - resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" - integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== - dependencies: - lru-cache "^10.2.0" - minipass "^5.0.0 || ^6.0.2 || ^7.0.0" - -pathe@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec" - integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ== - -pathval@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/pathval/-/pathval-2.0.0.tgz#7e2550b422601d4f6b8e26f1301bc8f15a741a25" - integrity sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA== - -picocolors@^1.0.0, picocolors@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.0.tgz#5358b76a78cde483ba5cef6a9dc9671440b27d59" - integrity sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw== - -postcss@^8.4.43: - version "8.4.47" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.47.tgz#5bf6c9a010f3e724c503bf03ef7947dcb0fea365" - integrity sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ== - dependencies: - nanoid "^3.3.7" - picocolors "^1.1.0" - source-map-js "^1.2.1" - -punycode@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" - integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== - -rollup@^4.20.0: - version "4.22.5" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.22.5.tgz#d5108cc470249417e50492456253884d19f5d40f" - integrity sha512-WoinX7GeQOFMGznEcWA1WrTQCd/tpEbMkc3nuMs9BT0CPjMdSjPMTVClwWd4pgSQwJdP65SK9mTCNvItlr5o7w== - dependencies: - "@types/estree" "1.0.6" - optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.22.5" - "@rollup/rollup-android-arm64" "4.22.5" - "@rollup/rollup-darwin-arm64" "4.22.5" - "@rollup/rollup-darwin-x64" "4.22.5" - "@rollup/rollup-linux-arm-gnueabihf" "4.22.5" - "@rollup/rollup-linux-arm-musleabihf" "4.22.5" - "@rollup/rollup-linux-arm64-gnu" "4.22.5" - "@rollup/rollup-linux-arm64-musl" "4.22.5" - "@rollup/rollup-linux-powerpc64le-gnu" "4.22.5" - "@rollup/rollup-linux-riscv64-gnu" "4.22.5" - "@rollup/rollup-linux-s390x-gnu" "4.22.5" - "@rollup/rollup-linux-x64-gnu" "4.22.5" - "@rollup/rollup-linux-x64-musl" "4.22.5" - "@rollup/rollup-win32-arm64-msvc" "4.22.5" - "@rollup/rollup-win32-ia32-msvc" "4.22.5" - "@rollup/rollup-win32-x64-msvc" "4.22.5" - fsevents "~2.3.2" - -rrweb-cssom@^0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz#c73451a484b86dd7cfb1e0b2898df4b703183e4b" - integrity sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg== - -"safer-buffer@>= 2.1.2 < 3.0.0": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -saxes@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/saxes/-/saxes-6.0.0.tgz#fe5b4a4768df4f14a201b1ba6a65c1f3d9988cc5" - integrity sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA== - dependencies: - xmlchars "^2.2.0" - -semver@^6.3.1: - version "6.3.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - -semver@^7.5.3, semver@^7.5.4: - version "7.6.3" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" - integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -siginfo@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30" - integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g== - -signal-exit@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" - integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== - -source-map-js@^1.2.0, source-map-js@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" - integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== - -stackback@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b" - integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw== - -std-env@^3.7.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.7.0.tgz#c9f7386ced6ecf13360b6c6c55b8aaa4ef7481d2" - integrity sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg== - -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^5.0.1, string-width@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" - integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== - dependencies: - eastasianwidth "^0.2.0" - emoji-regex "^9.2.2" - strip-ansi "^7.0.1" - -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^7.0.1: - version "7.1.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" - integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== - dependencies: - ansi-regex "^6.0.1" - -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -symbol-tree@^3.2.4: - version "3.2.4" - resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" - integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== - -test-exclude@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-7.0.1.tgz#20b3ba4906ac20994e275bbcafd68d510264c2a2" - integrity sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg== - dependencies: - "@istanbuljs/schema" "^0.1.2" - glob "^10.4.1" - minimatch "^9.0.4" - -tinybench@^2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b" - integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg== - -tinyexec@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-0.3.0.tgz#ed60cfce19c17799d4a241e06b31b0ec2bee69e6" - integrity sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg== - -tinypool@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.0.1.tgz#c64233c4fac4304e109a64340178760116dbe1fe" - integrity sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA== - -tinyrainbow@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-1.2.0.tgz#5c57d2fc0fb3d1afd78465c33ca885d04f02abb5" - integrity sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ== - -tinyspy@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-3.0.2.tgz#86dd3cf3d737b15adcf17d7887c84a75201df20a" - integrity sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q== - -tldts-core@^6.1.48: - version "6.1.48" - resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-6.1.48.tgz#efa7dc689b9757d1d4326b787cd992f10a16b2fb" - integrity sha512-3gD9iKn/n2UuFH1uilBviK9gvTNT6iYwdqrj1Vr5mh8FuelvpRNaYVH4pNYqUgOGU4aAdL9X35eLuuj0gRsx+A== - -tldts@^6.1.32: - version "6.1.48" - resolved "https://registry.yarnpkg.com/tldts/-/tldts-6.1.48.tgz#bfef97f407fe73f1a88db8e0f6905378e9a348c0" - integrity sha512-SPbnh1zaSzi/OsmHb1vrPNnYuwJbdWjwo5TbBYYMlTtH3/1DSb41t8bcSxkwDmmbG2q6VLPVvQc7Yf23T+1EEw== - dependencies: - tldts-core "^6.1.48" - -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== - -tough-cookie@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-5.0.0.tgz#6b6518e2b5c070cf742d872ee0f4f92d69eac1af" - integrity sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q== - dependencies: - tldts "^6.1.32" - -tr46@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-5.0.0.tgz#3b46d583613ec7283020d79019f1335723801cec" - integrity sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g== - dependencies: - punycode "^2.3.1" - -typescript@^5.6.2: - version "5.6.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.2.tgz#d1de67b6bef77c41823f822df8f0b3bcff60a5a0" - integrity sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw== - -update-browserslist-db@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz#80846fba1d79e82547fb661f8d141e0945755fe5" - integrity sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A== - dependencies: - escalade "^3.2.0" - picocolors "^1.1.0" - -vite-node@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-2.1.1.tgz#7d46f623c04dfed6df34e7127711508a3386fa1c" - integrity sha512-N/mGckI1suG/5wQI35XeR9rsMsPqKXzq1CdUndzVstBj/HvyxxGctwnK6WX43NGt5L3Z5tcRf83g4TITKJhPrA== - dependencies: - cac "^6.7.14" - debug "^4.3.6" - pathe "^1.1.2" - vite "^5.0.0" - -vite@^5.0.0: - version "5.4.8" - resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.8.tgz#af548ce1c211b2785478d3ba3e8da51e39a287e8" - integrity sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ== - dependencies: - esbuild "^0.21.3" - postcss "^8.4.43" - rollup "^4.20.0" - optionalDependencies: - fsevents "~2.3.3" - -vitest@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/vitest/-/vitest-2.1.1.tgz#24a6f6f5d894509f10685b82de008c507faacbb1" - integrity sha512-97We7/VC0e9X5zBVkvt7SGQMGrRtn3KtySFQG5fpaMlS+l62eeXRQO633AYhSTC3z7IMebnPPNjGXVGNRFlxBA== - dependencies: - "@vitest/expect" "2.1.1" - "@vitest/mocker" "2.1.1" - "@vitest/pretty-format" "^2.1.1" - "@vitest/runner" "2.1.1" - "@vitest/snapshot" "2.1.1" - "@vitest/spy" "2.1.1" - "@vitest/utils" "2.1.1" - chai "^5.1.1" - debug "^4.3.6" - magic-string "^0.30.11" - pathe "^1.1.2" - std-env "^3.7.0" - tinybench "^2.9.0" - tinyexec "^0.3.0" - tinypool "^1.0.0" - tinyrainbow "^1.2.0" - vite "^5.0.0" - vite-node "2.1.1" - why-is-node-running "^2.3.0" - -w3c-xmlserializer@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz#f925ba26855158594d907313cedd1476c5967f6c" - integrity sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA== - dependencies: - xml-name-validator "^5.0.0" - -webidl-conversions@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" - integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== - -whatwg-encoding@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz#d0f4ef769905d426e1688f3e34381a99b60b76e5" - integrity sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ== - dependencies: - iconv-lite "0.6.3" - -whatwg-mimetype@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz#bc1bf94a985dc50388d54a9258ac405c3ca2fc0a" - integrity sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg== - -whatwg-url@^14.0.0: - version "14.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-14.0.0.tgz#00baaa7fd198744910c4b1ef68378f2200e4ceb6" - integrity sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw== - dependencies: - tr46 "^5.0.0" - webidl-conversions "^7.0.0" - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -why-is-node-running@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz#a3f69a97107f494b3cdc3bdddd883a7d65cebf04" - integrity sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w== - dependencies: - siginfo "^2.0.0" - stackback "0.0.2" +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10c0 + +"@ampproject/remapping@npm:^2.2.0": + version: 2.3.0 + resolution: "@ampproject/remapping@npm:2.3.0" + dependencies: + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/81d63cca5443e0f0c72ae18b544cc28c7c0ec2cea46e7cb888bb0e0f411a1191d0d6b7af798d54e30777d8d1488b2ec0732aac2be342d3d7d3ffd271c6f489ed + languageName: node + linkType: hard + +"@asamuzakjp/css-color@npm:^3.1.1": + version: 3.1.1 + resolution: "@asamuzakjp/css-color@npm:3.1.1" + dependencies: + "@csstools/css-calc": "npm:^2.1.2" + "@csstools/css-color-parser": "npm:^3.0.8" + "@csstools/css-parser-algorithms": "npm:^3.0.4" + "@csstools/css-tokenizer": "npm:^3.0.3" + lru-cache: "npm:^10.4.3" + checksum: 10c0/4abb010fd29de8acae8571eba738468c22cb45a1f77647df3c59a80f1c83d83d728cae3ebbf99e5c73f2517761abaaffbe5e4176fc46b5f9bf60f1478463b51e + languageName: node + linkType: hard + +"@babel/code-frame@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/code-frame@npm:7.24.7" + dependencies: + "@babel/highlight": "npm:^7.24.7" + picocolors: "npm:^1.0.0" + checksum: 10c0/ab0af539473a9f5aeaac7047e377cb4f4edd255a81d84a76058595f8540784cc3fbe8acf73f1e073981104562490aabfb23008cd66dc677a456a4ed5390fdde6 + languageName: node + linkType: hard + +"@babel/compat-data@npm:^7.25.2": + version: 7.25.4 + resolution: "@babel/compat-data@npm:7.25.4" + checksum: 10c0/50d79734d584a28c69d6f5b99adfaa064d0f41609a378aef04eb06accc5b44f8520e68549eba3a082478180957b7d5783f1bfb1672e4ae8574e797ce8bae79fa + languageName: node + linkType: hard + +"@babel/core@npm:^7.23.9": + version: 7.25.2 + resolution: "@babel/core@npm:7.25.2" + dependencies: + "@ampproject/remapping": "npm:^2.2.0" + "@babel/code-frame": "npm:^7.24.7" + "@babel/generator": "npm:^7.25.0" + "@babel/helper-compilation-targets": "npm:^7.25.2" + "@babel/helper-module-transforms": "npm:^7.25.2" + "@babel/helpers": "npm:^7.25.0" + "@babel/parser": "npm:^7.25.0" + "@babel/template": "npm:^7.25.0" + "@babel/traverse": "npm:^7.25.2" + "@babel/types": "npm:^7.25.2" + convert-source-map: "npm:^2.0.0" + debug: "npm:^4.1.0" + gensync: "npm:^1.0.0-beta.2" + json5: "npm:^2.2.3" + semver: "npm:^6.3.1" + checksum: 10c0/a425fa40e73cb72b6464063a57c478bc2de9dbcc19c280f1b55a3d88b35d572e87e8594e7d7b4880331addb6faef641bbeb701b91b41b8806cd4deae5d74f401 + languageName: node + linkType: hard + +"@babel/generator@npm:^7.25.0, @babel/generator@npm:^7.25.6": + version: 7.25.6 + resolution: "@babel/generator@npm:7.25.6" + dependencies: + "@babel/types": "npm:^7.25.6" + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.25" + jsesc: "npm:^2.5.1" + checksum: 10c0/f89282cce4ddc63654470b98086994d219407d025497f483eb03ba102086e11e2b685b27122f6ff2e1d93b5b5fa0c3a6b7e974fbf2e4a75b685041a746a4291e + languageName: node + linkType: hard + +"@babel/helper-compilation-targets@npm:^7.25.2": + version: 7.25.2 + resolution: "@babel/helper-compilation-targets@npm:7.25.2" + dependencies: + "@babel/compat-data": "npm:^7.25.2" + "@babel/helper-validator-option": "npm:^7.24.8" + browserslist: "npm:^4.23.1" + lru-cache: "npm:^5.1.1" + semver: "npm:^6.3.1" + checksum: 10c0/de10e986b5322c9f807350467dc845ec59df9e596a5926a3b5edbb4710d8e3b8009d4396690e70b88c3844fe8ec4042d61436dd4b92d1f5f75655cf43ab07e99 + languageName: node + linkType: hard + +"@babel/helper-module-imports@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-module-imports@npm:7.24.7" + dependencies: + "@babel/traverse": "npm:^7.24.7" + "@babel/types": "npm:^7.24.7" + checksum: 10c0/97c57db6c3eeaea31564286e328a9fb52b0313c5cfcc7eee4bc226aebcf0418ea5b6fe78673c0e4a774512ec6c86e309d0f326e99d2b37bfc16a25a032498af0 + languageName: node + linkType: hard + +"@babel/helper-module-transforms@npm:^7.25.2": + version: 7.25.2 + resolution: "@babel/helper-module-transforms@npm:7.25.2" + dependencies: + "@babel/helper-module-imports": "npm:^7.24.7" + "@babel/helper-simple-access": "npm:^7.24.7" + "@babel/helper-validator-identifier": "npm:^7.24.7" + "@babel/traverse": "npm:^7.25.2" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/adaa15970ace0aee5934b5a633789b5795b6229c6a9cf3e09a7e80aa33e478675eee807006a862aa9aa517935d81f88a6db8a9f5936e3a2a40ec75f8062bc329 + languageName: node + linkType: hard + +"@babel/helper-simple-access@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-simple-access@npm:7.24.7" + dependencies: + "@babel/traverse": "npm:^7.24.7" + "@babel/types": "npm:^7.24.7" + checksum: 10c0/7230e419d59a85f93153415100a5faff23c133d7442c19e0cd070da1784d13cd29096ee6c5a5761065c44e8164f9f80e3a518c41a0256df39e38f7ad6744fed7 + languageName: node + linkType: hard + +"@babel/helper-string-parser@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/helper-string-parser@npm:7.24.8" + checksum: 10c0/6361f72076c17fabf305e252bf6d580106429014b3ab3c1f5c4eb3e6d465536ea6b670cc0e9a637a77a9ad40454d3e41361a2909e70e305116a23d68ce094c08 + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-validator-identifier@npm:7.24.7" + checksum: 10c0/87ad608694c9477814093ed5b5c080c2e06d44cb1924ae8320474a74415241223cc2a725eea2640dd783ff1e3390e5f95eede978bc540e870053152e58f1d651 + languageName: node + linkType: hard + +"@babel/helper-validator-option@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/helper-validator-option@npm:7.24.8" + checksum: 10c0/73db93a34ae89201351288bee7623eed81a54000779462a986105b54ffe82069e764afd15171a428b82e7c7a9b5fec10b5d5603b216317a414062edf5c67a21f + languageName: node + linkType: hard + +"@babel/helpers@npm:^7.25.0": + version: 7.25.6 + resolution: "@babel/helpers@npm:7.25.6" + dependencies: + "@babel/template": "npm:^7.25.0" + "@babel/types": "npm:^7.25.6" + checksum: 10c0/448c1cdabccca42fd97a252f73f1e4bcd93776dbf24044f3b4f49b756bf2ece73ee6df05177473bb74ea7456dddd18d6f481e4d96d2cc7839d078900d48c696c + languageName: node + linkType: hard + +"@babel/highlight@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/highlight@npm:7.24.7" + dependencies: + "@babel/helper-validator-identifier": "npm:^7.24.7" + chalk: "npm:^2.4.2" + js-tokens: "npm:^4.0.0" + picocolors: "npm:^1.0.0" + checksum: 10c0/674334c571d2bb9d1c89bdd87566383f59231e16bcdcf5bb7835babdf03c9ae585ca0887a7b25bdf78f303984af028df52831c7989fecebb5101cc132da9393a + languageName: node + linkType: hard + +"@babel/parser@npm:^7.23.9, @babel/parser@npm:^7.25.0, @babel/parser@npm:^7.25.4, @babel/parser@npm:^7.25.6": + version: 7.25.6 + resolution: "@babel/parser@npm:7.25.6" + dependencies: + "@babel/types": "npm:^7.25.6" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/f88a0e895dbb096fd37c4527ea97d12b5fc013720602580a941ac3a339698872f0c911e318c292b184c36b5fbe23b612f05aff9d24071bc847c7b1c21552c41d + languageName: node + linkType: hard + +"@babel/template@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/template@npm:7.25.0" + dependencies: + "@babel/code-frame": "npm:^7.24.7" + "@babel/parser": "npm:^7.25.0" + "@babel/types": "npm:^7.25.0" + checksum: 10c0/4e31afd873215744c016e02b04f43b9fa23205d6d0766fb2e93eb4091c60c1b88897936adb895fb04e3c23de98dfdcbe31bc98daaa1a4e0133f78bb948e1209b + languageName: node + linkType: hard + +"@babel/traverse@npm:^7.24.7, @babel/traverse@npm:^7.25.2": + version: 7.25.6 + resolution: "@babel/traverse@npm:7.25.6" + dependencies: + "@babel/code-frame": "npm:^7.24.7" + "@babel/generator": "npm:^7.25.6" + "@babel/parser": "npm:^7.25.6" + "@babel/template": "npm:^7.25.0" + "@babel/types": "npm:^7.25.6" + debug: "npm:^4.3.1" + globals: "npm:^11.1.0" + checksum: 10c0/964304c6fa46bd705428ba380bf73177eeb481c3f26d82ea3d0661242b59e0dd4329d23886035e9ca9a4ceb565c03a76fd615109830687a27bcd350059d6377e + languageName: node + linkType: hard + +"@babel/types@npm:^7.24.7, @babel/types@npm:^7.25.0, @babel/types@npm:^7.25.2, @babel/types@npm:^7.25.4, @babel/types@npm:^7.25.6": + version: 7.25.6 + resolution: "@babel/types@npm:7.25.6" + dependencies: + "@babel/helper-string-parser": "npm:^7.24.8" + "@babel/helper-validator-identifier": "npm:^7.24.7" + to-fast-properties: "npm:^2.0.0" + checksum: 10c0/89d45fbee24e27a05dca2d08300a26b905bd384a480448823f6723c72d3a30327c517476389b7280ce8cb9a2c48ef8f47da7f9f6d326faf6f53fd6b68237bdc4 + languageName: node + linkType: hard + +"@csstools/color-helpers@npm:^5.0.2": + version: 5.0.2 + resolution: "@csstools/color-helpers@npm:5.0.2" + checksum: 10c0/bebaddb28b9eb58b0449edd5d0c0318fa88f3cb079602ee27e88c9118070d666dcc4e09a5aa936aba2fde6ba419922ade07b7b506af97dd7051abd08dfb2959b + languageName: node + linkType: hard + +"@csstools/css-calc@npm:^2.1.2": + version: 2.1.2 + resolution: "@csstools/css-calc@npm:2.1.2" + peerDependencies: + "@csstools/css-parser-algorithms": ^3.0.4 + "@csstools/css-tokenizer": ^3.0.3 + checksum: 10c0/34ced30553968ef5d5f9e00e3b90b48c47480cf130e282e99d57ec9b09f803aab8bc06325683e72a1518b5e7180a3da8b533f1b462062757c21989a53b482e1a + languageName: node + linkType: hard + +"@csstools/css-color-parser@npm:^3.0.8": + version: 3.0.8 + resolution: "@csstools/css-color-parser@npm:3.0.8" + dependencies: + "@csstools/color-helpers": "npm:^5.0.2" + "@csstools/css-calc": "npm:^2.1.2" + peerDependencies: + "@csstools/css-parser-algorithms": ^3.0.4 + "@csstools/css-tokenizer": ^3.0.3 + checksum: 10c0/90722c5a62ca94e9d578ddf59be604a76400b932bd3d4bd23cb1ae9b7ace8fcf83c06995d2b31f96f4afef24a7cefba79beb11ed7ee4999d7ecfec3869368359 + languageName: node + linkType: hard + +"@csstools/css-parser-algorithms@npm:^3.0.4": + version: 3.0.4 + resolution: "@csstools/css-parser-algorithms@npm:3.0.4" + peerDependencies: + "@csstools/css-tokenizer": ^3.0.3 + checksum: 10c0/d411f07765e14eede17bccc6bd4f90ff303694df09aabfede3fd104b2dfacfd4fe3697cd25ddad14684c850328f3f9420ebfa9f78380892492974db24ae47dbd + languageName: node + linkType: hard + +"@csstools/css-tokenizer@npm:^3.0.3": + version: 3.0.3 + resolution: "@csstools/css-tokenizer@npm:3.0.3" + checksum: 10c0/c31bf410e1244b942e71798e37c54639d040cb59e0121b21712b40015fced2b0fb1ffe588434c5f8923c9cd0017cfc1c1c8f3921abc94c96edf471aac2eba5e5 + languageName: node + linkType: hard + +"@esbuild/aix-ppc64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/aix-ppc64@npm:0.25.1" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/android-arm64@npm:0.25.1" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/android-arm@npm:0.25.1" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/android-x64@npm:0.25.1" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/darwin-arm64@npm:0.25.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/darwin-x64@npm:0.25.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/freebsd-arm64@npm:0.25.1" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/freebsd-x64@npm:0.25.1" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/linux-arm64@npm:0.25.1" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/linux-arm@npm:0.25.1" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/linux-ia32@npm:0.25.1" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/linux-loong64@npm:0.25.1" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/linux-mips64el@npm:0.25.1" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/linux-ppc64@npm:0.25.1" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/linux-riscv64@npm:0.25.1" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/linux-s390x@npm:0.25.1" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/linux-x64@npm:0.25.1" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-arm64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/netbsd-arm64@npm:0.25.1" + conditions: os=netbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/netbsd-x64@npm:0.25.1" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-arm64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/openbsd-arm64@npm:0.25.1" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/openbsd-x64@npm:0.25.1" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/sunos-x64@npm:0.25.1" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/win32-arm64@npm:0.25.1" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/win32-ia32@npm:0.25.1" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/win32-x64@npm:0.25.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@isaacs/cliui@npm:^8.0.2": + version: 8.0.2 + resolution: "@isaacs/cliui@npm:8.0.2" + dependencies: + string-width: "npm:^5.1.2" + string-width-cjs: "npm:string-width@^4.2.0" + strip-ansi: "npm:^7.0.1" + strip-ansi-cjs: "npm:strip-ansi@^6.0.1" + wrap-ansi: "npm:^8.1.0" + wrap-ansi-cjs: "npm:wrap-ansi@^7.0.0" + checksum: 10c0/b1bf42535d49f11dc137f18d5e4e63a28c5569de438a221c369483731e9dac9fb797af554e8bf02b6192d1e5eba6e6402cf93900c3d0ac86391d00d04876789e + languageName: node + linkType: hard + +"@isaacs/fs-minipass@npm:^4.0.0": + version: 4.0.1 + resolution: "@isaacs/fs-minipass@npm:4.0.1" + dependencies: + minipass: "npm:^7.0.4" + checksum: 10c0/c25b6dc1598790d5b55c0947a9b7d111cfa92594db5296c3b907e2f533c033666f692a3939eadac17b1c7c40d362d0b0635dc874cbfe3e70db7c2b07cc97a5d2 + languageName: node + linkType: hard + +"@istanbuljs/schema@npm:^0.1.2, @istanbuljs/schema@npm:^0.1.3": + version: 0.1.3 + resolution: "@istanbuljs/schema@npm:0.1.3" + checksum: 10c0/61c5286771676c9ca3eb2bd8a7310a9c063fb6e0e9712225c8471c582d157392c88f5353581c8c9adbe0dff98892317d2fdfc56c3499aa42e0194405206a963a + languageName: node + linkType: hard + +"@jridgewell/gen-mapping@npm:^0.3.5": + version: 0.3.5 + resolution: "@jridgewell/gen-mapping@npm:0.3.5" + dependencies: + "@jridgewell/set-array": "npm:^1.2.1" + "@jridgewell/sourcemap-codec": "npm:^1.4.10" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/1be4fd4a6b0f41337c4f5fdf4afc3bd19e39c3691924817108b82ffcb9c9e609c273f936932b9fba4b3a298ce2eb06d9bff4eb1cc3bd81c4f4ee1b4917e25feb + languageName: node + linkType: hard + +"@jridgewell/resolve-uri@npm:^3.1.0": + version: 3.1.2 + resolution: "@jridgewell/resolve-uri@npm:3.1.2" + checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e + languageName: node + linkType: hard + +"@jridgewell/set-array@npm:^1.2.1": + version: 1.2.1 + resolution: "@jridgewell/set-array@npm:1.2.1" + checksum: 10c0/2a5aa7b4b5c3464c895c802d8ae3f3d2b92fcbe84ad12f8d0bfbb1f5ad006717e7577ee1fd2eac00c088abe486c7adb27976f45d2941ff6b0b92b2c3302c60f4 + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.5.0": + version: 1.5.0 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.0" + checksum: 10c0/2eb864f276eb1096c3c11da3e9bb518f6d9fc0023c78344cdc037abadc725172c70314bdb360f2d4b7bffec7f5d657ce006816bc5d4ecb35e61b66132db00c18 + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:^0.3.23, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": + version: 0.3.25 + resolution: "@jridgewell/trace-mapping@npm:0.3.25" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.1.0" + "@jridgewell/sourcemap-codec": "npm:^1.4.14" + checksum: 10c0/3d1ce6ebc69df9682a5a8896b414c6537e428a1d68b02fcc8363b04284a8ca0df04d0ee3013132252ab14f2527bc13bea6526a912ecb5658f0e39fd2860b4df4 + languageName: node + linkType: hard + +"@npmcli/agent@npm:^3.0.0": + version: 3.0.0 + resolution: "@npmcli/agent@npm:3.0.0" + dependencies: + agent-base: "npm:^7.1.0" + http-proxy-agent: "npm:^7.0.0" + https-proxy-agent: "npm:^7.0.1" + lru-cache: "npm:^10.0.1" + socks-proxy-agent: "npm:^8.0.3" + checksum: 10c0/efe37b982f30740ee77696a80c196912c274ecd2cb243bc6ae7053a50c733ce0f6c09fda085145f33ecf453be19654acca74b69e81eaad4c90f00ccffe2f9271 + languageName: node + linkType: hard + +"@npmcli/fs@npm:^4.0.0": + version: 4.0.0 + resolution: "@npmcli/fs@npm:4.0.0" + dependencies: + semver: "npm:^7.3.5" + checksum: 10c0/c90935d5ce670c87b6b14fab04a965a3b8137e585f8b2a6257263bd7f97756dd736cb165bb470e5156a9e718ecd99413dccc54b1138c1a46d6ec7cf325982fe5 + languageName: node + linkType: hard + +"@openreplay/network-proxy@workspace:.": + version: 0.0.0-use.local + resolution: "@openreplay/network-proxy@workspace:." + dependencies: + "@vitest/coverage-istanbul": "npm:^3.0.9" + jsdom: "npm:^26.0.0" + typescript: "npm:^5.8.2" + vitest: "npm:^3.0.9" + languageName: unknown + linkType: soft + +"@pkgjs/parseargs@npm:^0.11.0": + version: 0.11.0 + resolution: "@pkgjs/parseargs@npm:0.11.0" + checksum: 10c0/5bd7576bb1b38a47a7fc7b51ac9f38748e772beebc56200450c4a817d712232b8f1d3ef70532c80840243c657d491cf6a6be1e3a214cff907645819fdc34aadd + languageName: node + linkType: hard + +"@rollup/rollup-android-arm-eabi@npm:4.37.0": + version: 4.37.0 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.37.0" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@rollup/rollup-android-arm64@npm:4.37.0": + version: 4.37.0 + resolution: "@rollup/rollup-android-arm64@npm:4.37.0" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-arm64@npm:4.37.0": + version: 4.37.0 + resolution: "@rollup/rollup-darwin-arm64@npm:4.37.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-x64@npm:4.37.0": + version: 4.37.0 + resolution: "@rollup/rollup-darwin-x64@npm:4.37.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-freebsd-arm64@npm:4.37.0": + version: 4.37.0 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.37.0" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-freebsd-x64@npm:4.37.0": + version: 4.37.0 + resolution: "@rollup/rollup-freebsd-x64@npm:4.37.0" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-gnueabihf@npm:4.37.0": + version: 4.37.0 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.37.0" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-musleabihf@npm:4.37.0": + version: 4.37.0 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.37.0" + conditions: os=linux & cpu=arm & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-gnu@npm:4.37.0": + version: 4.37.0 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.37.0" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-musl@npm:4.37.0": + version: 4.37.0 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.37.0" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-loongarch64-gnu@npm:4.37.0": + version: 4.37.0 + resolution: "@rollup/rollup-linux-loongarch64-gnu@npm:4.37.0" + conditions: os=linux & cpu=loong64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-powerpc64le-gnu@npm:4.37.0": + version: 4.37.0 + resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.37.0" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-gnu@npm:4.37.0": + version: 4.37.0 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.37.0" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-musl@npm:4.37.0": + version: 4.37.0 + resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.37.0" + conditions: os=linux & cpu=riscv64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-s390x-gnu@npm:4.37.0": + version: 4.37.0 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.37.0" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-gnu@npm:4.37.0": + version: 4.37.0 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.37.0" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-musl@npm:4.37.0": + version: 4.37.0 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.37.0" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-win32-arm64-msvc@npm:4.37.0": + version: 4.37.0 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.37.0" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-ia32-msvc@npm:4.37.0": + version: 4.37.0 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.37.0" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@rollup/rollup-win32-x64-msvc@npm:4.37.0": + version: 4.37.0 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.37.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@types/estree@npm:1.0.6, @types/estree@npm:^1.0.0": + version: 1.0.6 + resolution: "@types/estree@npm:1.0.6" + checksum: 10c0/cdfd751f6f9065442cd40957c07fd80361c962869aa853c1c2fd03e101af8b9389d8ff4955a43a6fcfa223dd387a089937f95be0f3eec21ca527039fd2d9859a + languageName: node + linkType: hard + +"@vitest/coverage-istanbul@npm:^3.0.9": + version: 3.0.9 + resolution: "@vitest/coverage-istanbul@npm:3.0.9" + dependencies: + "@istanbuljs/schema": "npm:^0.1.3" + debug: "npm:^4.4.0" + istanbul-lib-coverage: "npm:^3.2.2" + istanbul-lib-instrument: "npm:^6.0.3" + istanbul-lib-report: "npm:^3.0.1" + istanbul-lib-source-maps: "npm:^5.0.6" + istanbul-reports: "npm:^3.1.7" + magicast: "npm:^0.3.5" + test-exclude: "npm:^7.0.1" + tinyrainbow: "npm:^2.0.0" + peerDependencies: + vitest: 3.0.9 + checksum: 10c0/af3e224962a63da52eafe34b8cb4f4355fd9dbd1634f1b28482d809b31a22ca1a250a69683376fd4404bb8f3a5156c2250057f5b475f241c4f7cac49935882e7 + languageName: node + linkType: hard + +"@vitest/expect@npm:3.0.9": + version: 3.0.9 + resolution: "@vitest/expect@npm:3.0.9" + dependencies: + "@vitest/spy": "npm:3.0.9" + "@vitest/utils": "npm:3.0.9" + chai: "npm:^5.2.0" + tinyrainbow: "npm:^2.0.0" + checksum: 10c0/4e5eef8fbc9c3e47f3fb69dbbd5b51aabdf1b6de2f781556d37d79731678fc83cf4a01d146226b12a27df051a4110153a6172506c9c74ae08e5b924a9c947f08 + languageName: node + linkType: hard + +"@vitest/mocker@npm:3.0.9": + version: 3.0.9 + resolution: "@vitest/mocker@npm:3.0.9" + dependencies: + "@vitest/spy": "npm:3.0.9" + estree-walker: "npm:^3.0.3" + magic-string: "npm:^0.30.17" + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + checksum: 10c0/9083a83902ca550cf004413b9fc87c8367a789e18a3c5a61e63c72810f9153e7d1c100c66f0b0656ea1035a700a373d5b78b49de0963ab62333c720aeec9f1b3 + languageName: node + linkType: hard + +"@vitest/pretty-format@npm:3.0.9, @vitest/pretty-format@npm:^3.0.9": + version: 3.0.9 + resolution: "@vitest/pretty-format@npm:3.0.9" + dependencies: + tinyrainbow: "npm:^2.0.0" + checksum: 10c0/56ae7b1f14df2905b3205d4e121727631c4938ec44f76c1e9fa49923919010378f0dad70b1d277672f3ef45ddf6372140c8d1da95e45df8282f70b74328fce47 + languageName: node + linkType: hard + +"@vitest/runner@npm:3.0.9": + version: 3.0.9 + resolution: "@vitest/runner@npm:3.0.9" + dependencies: + "@vitest/utils": "npm:3.0.9" + pathe: "npm:^2.0.3" + checksum: 10c0/b276f238a16a6d02bb244f655d9cd8db8cce4708a6267cc48476a785ca8887741c440ae27b379a5bbbb6fe4f9f12675f13da0270253043195defd7a36bf15114 + languageName: node + linkType: hard + +"@vitest/snapshot@npm:3.0.9": + version: 3.0.9 + resolution: "@vitest/snapshot@npm:3.0.9" + dependencies: + "@vitest/pretty-format": "npm:3.0.9" + magic-string: "npm:^0.30.17" + pathe: "npm:^2.0.3" + checksum: 10c0/8298caa334d357cb22b1946cbebedb22f04d38fe080d6da7445873221fe6f89c2b82fe4f368d9eb8a62a77bd76d1b4234595bb085279d48130f09ba6b2e18637 + languageName: node + linkType: hard + +"@vitest/spy@npm:3.0.9": + version: 3.0.9 + resolution: "@vitest/spy@npm:3.0.9" + dependencies: + tinyspy: "npm:^3.0.2" + checksum: 10c0/993085dbaf9e651ca9516f88e440424d29279def998186628a1ebcab5558a3045fee8562630608f58303507135f6f3bf9970f65639f3b9baa8bf86cab3eb4742 + languageName: node + linkType: hard + +"@vitest/utils@npm:3.0.9": + version: 3.0.9 + resolution: "@vitest/utils@npm:3.0.9" + dependencies: + "@vitest/pretty-format": "npm:3.0.9" + loupe: "npm:^3.1.3" + tinyrainbow: "npm:^2.0.0" + checksum: 10c0/b966dfb3b926ee9bea59c1fb297abc67adaa23a8a582453ee81167b238446394693617a5e0523eb2791d6983173ef1c07bf28a76bd5a63b49a100610ed6b6a6c + languageName: node + linkType: hard + +"abbrev@npm:^3.0.0": + version: 3.0.0 + resolution: "abbrev@npm:3.0.0" + checksum: 10c0/049704186396f571650eb7b22ed3627b77a5aedf98bb83caf2eac81ca2a3e25e795394b0464cfb2d6076df3db6a5312139eac5b6a126ca296ac53c5008069c28 + languageName: node + linkType: hard + +"agent-base@npm:^7.1.0": + version: 7.1.1 + resolution: "agent-base@npm:7.1.1" + dependencies: + debug: "npm:^4.3.4" + checksum: 10c0/e59ce7bed9c63bf071a30cc471f2933862044c97fd9958967bfe22521d7a0f601ce4ed5a8c011799d0c726ca70312142ae193bbebb60f576b52be19d4a363b50 + languageName: node + linkType: hard + +"agent-base@npm:^7.1.2": + version: 7.1.3 + resolution: "agent-base@npm:7.1.3" + checksum: 10c0/6192b580c5b1d8fb399b9c62bf8343d76654c2dd62afcb9a52b2cf44a8b6ace1e3b704d3fe3547d91555c857d3df02603341ff2cb961b9cfe2b12f9f3c38ee11 + languageName: node + linkType: hard + +"ansi-regex@npm:^5.0.1": + version: 5.0.1 + resolution: "ansi-regex@npm:5.0.1" + checksum: 10c0/9a64bb8627b434ba9327b60c027742e5d17ac69277960d041898596271d992d4d52ba7267a63ca10232e29f6107fc8a835f6ce8d719b88c5f8493f8254813737 + languageName: node + linkType: hard + +"ansi-regex@npm:^6.0.1": + version: 6.1.0 + resolution: "ansi-regex@npm:6.1.0" + checksum: 10c0/a91daeddd54746338478eef88af3439a7edf30f8e23196e2d6ed182da9add559c601266dbef01c2efa46a958ad6f1f8b176799657616c702b5b02e799e7fd8dc + languageName: node + linkType: hard + +"ansi-styles@npm:^3.2.1": + version: 3.2.1 + resolution: "ansi-styles@npm:3.2.1" + dependencies: + color-convert: "npm:^1.9.0" + checksum: 10c0/ece5a8ef069fcc5298f67e3f4771a663129abd174ea2dfa87923a2be2abf6cd367ef72ac87942da00ce85bd1d651d4cd8595aebdb1b385889b89b205860e977b + languageName: node + linkType: hard + +"ansi-styles@npm:^4.0.0": + version: 4.3.0 + resolution: "ansi-styles@npm:4.3.0" + dependencies: + color-convert: "npm:^2.0.1" + checksum: 10c0/895a23929da416f2bd3de7e9cb4eabd340949328ab85ddd6e484a637d8f6820d485f53933446f5291c3b760cbc488beb8e88573dd0f9c7daf83dccc8fe81b041 + languageName: node + linkType: hard + +"ansi-styles@npm:^6.1.0": + version: 6.2.1 + resolution: "ansi-styles@npm:6.2.1" + checksum: 10c0/5d1ec38c123984bcedd996eac680d548f31828bd679a66db2bdf11844634dde55fec3efa9c6bb1d89056a5e79c1ac540c4c784d592ea1d25028a92227d2f2d5c + languageName: node + linkType: hard + +"assertion-error@npm:^2.0.1": + version: 2.0.1 + resolution: "assertion-error@npm:2.0.1" + checksum: 10c0/bbbcb117ac6480138f8c93cf7f535614282dea9dc828f540cdece85e3c665e8f78958b96afac52f29ff883c72638e6a87d469ecc9fe5bc902df03ed24a55dba8 + languageName: node + linkType: hard + +"asynckit@npm:^0.4.0": + version: 0.4.0 + resolution: "asynckit@npm:0.4.0" + checksum: 10c0/d73e2ddf20c4eb9337e1b3df1a0f6159481050a5de457c55b14ea2e5cb6d90bb69e004c9af54737a5ee0917fcf2c9e25de67777bbe58261847846066ba75bc9d + languageName: node + linkType: hard + +"balanced-match@npm:^1.0.0": + version: 1.0.2 + resolution: "balanced-match@npm:1.0.2" + checksum: 10c0/9308baf0a7e4838a82bbfd11e01b1cb0f0cf2893bc1676c27c2a8c0e70cbae1c59120c3268517a8ae7fb6376b4639ef81ca22582611dbee4ed28df945134aaee + languageName: node + linkType: hard + +"brace-expansion@npm:^2.0.1": + version: 2.0.1 + resolution: "brace-expansion@npm:2.0.1" + dependencies: + balanced-match: "npm:^1.0.0" + checksum: 10c0/b358f2fe060e2d7a87aa015979ecea07f3c37d4018f8d6deb5bd4c229ad3a0384fe6029bb76cd8be63c81e516ee52d1a0673edbe2023d53a5191732ae3c3e49f + languageName: node + linkType: hard + +"browserslist@npm:^4.23.1": + version: 4.24.0 + resolution: "browserslist@npm:4.24.0" + dependencies: + caniuse-lite: "npm:^1.0.30001663" + electron-to-chromium: "npm:^1.5.28" + node-releases: "npm:^2.0.18" + update-browserslist-db: "npm:^1.1.0" + bin: + browserslist: cli.js + checksum: 10c0/95e76ad522753c4c470427f6e3c8a4bb5478ff448841e22b3d3e53f89ecaf17b6984666d6c7e715c370f1e7fa0cf684f42e34e554236a8b2fab38ea76b9e4c52 + languageName: node + linkType: hard + +"cac@npm:^6.7.14": + version: 6.7.14 + resolution: "cac@npm:6.7.14" + checksum: 10c0/4ee06aaa7bab8981f0d54e5f5f9d4adcd64058e9697563ce336d8a3878ed018ee18ebe5359b2430eceae87e0758e62ea2019c3f52ae6e211b1bd2e133856cd10 + languageName: node + linkType: hard + +"cacache@npm:^19.0.1": + version: 19.0.1 + resolution: "cacache@npm:19.0.1" + dependencies: + "@npmcli/fs": "npm:^4.0.0" + fs-minipass: "npm:^3.0.0" + glob: "npm:^10.2.2" + lru-cache: "npm:^10.0.1" + minipass: "npm:^7.0.3" + minipass-collect: "npm:^2.0.1" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + p-map: "npm:^7.0.2" + ssri: "npm:^12.0.0" + tar: "npm:^7.4.3" + unique-filename: "npm:^4.0.0" + checksum: 10c0/01f2134e1bd7d3ab68be851df96c8d63b492b1853b67f2eecb2c37bb682d37cb70bb858a16f2f0554d3c0071be6dfe21456a1ff6fa4b7eed996570d6a25ffe9c + languageName: node + linkType: hard + +"call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2": + version: 1.0.2 + resolution: "call-bind-apply-helpers@npm:1.0.2" + dependencies: + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + checksum: 10c0/47bd9901d57b857590431243fea704ff18078b16890a6b3e021e12d279bbf211d039155e27d7566b374d49ee1f8189344bac9833dec7a20cdec370506361c938 + languageName: node + linkType: hard + +"caniuse-lite@npm:^1.0.30001663": + version: 1.0.30001664 + resolution: "caniuse-lite@npm:1.0.30001664" + checksum: 10c0/db2b431aba41a585191ab1e4d40da0ad349ff32400edac2a167bf6bf92dbf9c704eab03dc60fb89e882ce02478d61c3036b2b1bdce8edf9b2aabda5608bae05e + languageName: node + linkType: hard + +"chai@npm:^5.2.0": + version: 5.2.0 + resolution: "chai@npm:5.2.0" + dependencies: + assertion-error: "npm:^2.0.1" + check-error: "npm:^2.1.1" + deep-eql: "npm:^5.0.1" + loupe: "npm:^3.1.0" + pathval: "npm:^2.0.0" + checksum: 10c0/dfd1cb719c7cebb051b727672d382a35338af1470065cb12adb01f4ee451bbf528e0e0f9ab2016af5fc1eea4df6e7f4504dc8443f8f00bd8fb87ad32dc516f7d + languageName: node + linkType: hard + +"chalk@npm:^2.4.2": + version: 2.4.2 + resolution: "chalk@npm:2.4.2" + dependencies: + ansi-styles: "npm:^3.2.1" + escape-string-regexp: "npm:^1.0.5" + supports-color: "npm:^5.3.0" + checksum: 10c0/e6543f02ec877732e3a2d1c3c3323ddb4d39fbab687c23f526e25bd4c6a9bf3b83a696e8c769d078e04e5754921648f7821b2a2acfd16c550435fd630026e073 + languageName: node + linkType: hard + +"check-error@npm:^2.1.1": + version: 2.1.1 + resolution: "check-error@npm:2.1.1" + checksum: 10c0/979f13eccab306cf1785fa10941a590b4e7ea9916ea2a4f8c87f0316fc3eab07eabefb6e587424ef0f88cbcd3805791f172ea739863ca3d7ce2afc54641c7f0e + languageName: node + linkType: hard + +"chownr@npm:^3.0.0": + version: 3.0.0 + resolution: "chownr@npm:3.0.0" + checksum: 10c0/43925b87700f7e3893296c8e9c56cc58f926411cce3a6e5898136daaf08f08b9a8eb76d37d3267e707d0dcc17aed2e2ebdf5848c0c3ce95cf910a919935c1b10 + languageName: node + linkType: hard + +"color-convert@npm:^1.9.0": + version: 1.9.3 + resolution: "color-convert@npm:1.9.3" + dependencies: + color-name: "npm:1.1.3" + checksum: 10c0/5ad3c534949a8c68fca8fbc6f09068f435f0ad290ab8b2f76841b9e6af7e0bb57b98cb05b0e19fe33f5d91e5a8611ad457e5f69e0a484caad1f7487fd0e8253c + languageName: node + linkType: hard + +"color-convert@npm:^2.0.1": + version: 2.0.1 + resolution: "color-convert@npm:2.0.1" + dependencies: + color-name: "npm:~1.1.4" + checksum: 10c0/37e1150172f2e311fe1b2df62c6293a342ee7380da7b9cfdba67ea539909afbd74da27033208d01d6d5cfc65ee7868a22e18d7e7648e004425441c0f8a15a7d7 + languageName: node + linkType: hard + +"color-name@npm:1.1.3": + version: 1.1.3 + resolution: "color-name@npm:1.1.3" + checksum: 10c0/566a3d42cca25b9b3cd5528cd7754b8e89c0eb646b7f214e8e2eaddb69994ac5f0557d9c175eb5d8f0ad73531140d9c47525085ee752a91a2ab15ab459caf6d6 + languageName: node + linkType: hard + +"color-name@npm:~1.1.4": + version: 1.1.4 + resolution: "color-name@npm:1.1.4" + checksum: 10c0/a1a3f914156960902f46f7f56bc62effc6c94e84b2cae157a526b1c1f74b677a47ec602bf68a61abfa2b42d15b7c5651c6dbe72a43af720bc588dff885b10f95 + languageName: node + linkType: hard + +"combined-stream@npm:^1.0.8": + version: 1.0.8 + resolution: "combined-stream@npm:1.0.8" + dependencies: + delayed-stream: "npm:~1.0.0" + checksum: 10c0/0dbb829577e1b1e839fa82b40c07ffaf7de8a09b935cadd355a73652ae70a88b4320db322f6634a4ad93424292fa80973ac6480986247f1734a1137debf271d5 + languageName: node + linkType: hard + +"convert-source-map@npm:^2.0.0": + version: 2.0.0 + resolution: "convert-source-map@npm:2.0.0" + checksum: 10c0/8f2f7a27a1a011cc6cc88cc4da2d7d0cfa5ee0369508baae3d98c260bb3ac520691464e5bbe4ae7cdf09860c1d69ecc6f70c63c6e7c7f7e3f18ec08484dc7d9b + languageName: node + linkType: hard + +"cross-spawn@npm:^7.0.0": + version: 7.0.3 + resolution: "cross-spawn@npm:7.0.3" + dependencies: + path-key: "npm:^3.1.0" + shebang-command: "npm:^2.0.0" + which: "npm:^2.0.1" + checksum: 10c0/5738c312387081c98d69c98e105b6327b069197f864a60593245d64c8089c8a0a744e16349281210d56835bb9274130d825a78b2ad6853ca13cfbeffc0c31750 + languageName: node + linkType: hard + +"cssstyle@npm:^4.2.1": + version: 4.3.0 + resolution: "cssstyle@npm:4.3.0" + dependencies: + "@asamuzakjp/css-color": "npm:^3.1.1" + rrweb-cssom: "npm:^0.8.0" + checksum: 10c0/770ccb288a99257fd0d5b129e03878f848e922d3b017358acb02e8dd530e8f0c7c6f74e6ae5367d715e2da36a490a734b4177fc1b78f3f08eca25f204a56a692 + languageName: node + linkType: hard + +"data-urls@npm:^5.0.0": + version: 5.0.0 + resolution: "data-urls@npm:5.0.0" + dependencies: + whatwg-mimetype: "npm:^4.0.0" + whatwg-url: "npm:^14.0.0" + checksum: 10c0/1b894d7d41c861f3a4ed2ae9b1c3f0909d4575ada02e36d3d3bc584bdd84278e20709070c79c3b3bff7ac98598cb191eb3e86a89a79ea4ee1ef360e1694f92ad + languageName: node + linkType: hard + +"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.4": + version: 4.3.7 + resolution: "debug@npm:4.3.7" + dependencies: + ms: "npm:^2.1.3" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10c0/1471db19c3b06d485a622d62f65947a19a23fbd0dd73f7fd3eafb697eec5360cde447fb075919987899b1a2096e85d35d4eb5a4de09a57600ac9cf7e6c8e768b + languageName: node + linkType: hard + +"debug@npm:^4.4.0": + version: 4.4.0 + resolution: "debug@npm:4.4.0" + dependencies: + ms: "npm:^2.1.3" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10c0/db94f1a182bf886f57b4755f85b3a74c39b5114b9377b7ab375dc2cfa3454f09490cc6c30f829df3fc8042bc8b8995f6567ce5cd96f3bc3688bd24027197d9de + languageName: node + linkType: hard + +"decimal.js@npm:^10.4.3": + version: 10.4.3 + resolution: "decimal.js@npm:10.4.3" + checksum: 10c0/6d60206689ff0911f0ce968d40f163304a6c1bc739927758e6efc7921cfa630130388966f16bf6ef6b838cb33679fbe8e7a78a2f3c478afce841fd55ac8fb8ee + languageName: node + linkType: hard + +"deep-eql@npm:^5.0.1": + version: 5.0.2 + resolution: "deep-eql@npm:5.0.2" + checksum: 10c0/7102cf3b7bb719c6b9c0db2e19bf0aa9318d141581befe8c7ce8ccd39af9eaa4346e5e05adef7f9bd7015da0f13a3a25dcfe306ef79dc8668aedbecb658dd247 + languageName: node + linkType: hard + +"delayed-stream@npm:~1.0.0": + version: 1.0.0 + resolution: "delayed-stream@npm:1.0.0" + checksum: 10c0/d758899da03392e6712f042bec80aa293bbe9e9ff1b2634baae6a360113e708b91326594c8a486d475c69d6259afb7efacdc3537bfcda1c6c648e390ce601b19 + languageName: node + linkType: hard + +"dunder-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "dunder-proto@npm:1.0.1" + dependencies: + call-bind-apply-helpers: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + gopd: "npm:^1.2.0" + checksum: 10c0/199f2a0c1c16593ca0a145dbf76a962f8033ce3129f01284d48c45ed4e14fea9bbacd7b3610b6cdc33486cef20385ac054948fefc6272fcce645c09468f93031 + languageName: node + linkType: hard + +"eastasianwidth@npm:^0.2.0": + version: 0.2.0 + resolution: "eastasianwidth@npm:0.2.0" + checksum: 10c0/26f364ebcdb6395f95124fda411f63137a4bfb5d3a06453f7f23dfe52502905bd84e0488172e0f9ec295fdc45f05c23d5d91baf16bd26f0fe9acd777a188dc39 + languageName: node + linkType: hard + +"electron-to-chromium@npm:^1.5.28": + version: 1.5.29 + resolution: "electron-to-chromium@npm:1.5.29" + checksum: 10c0/ae4849f1fe8d756d30c6f5f992803d8550a98b38a30aecc7d9776858cf229ad05b12cb9f7675f0a89330a077d16e28388cfe394fdd9d0828ffe860c8568c95c2 + languageName: node + linkType: hard + +"emoji-regex@npm:^8.0.0": + version: 8.0.0 + resolution: "emoji-regex@npm:8.0.0" + checksum: 10c0/b6053ad39951c4cf338f9092d7bfba448cdfd46fe6a2a034700b149ac9ffbc137e361cbd3c442297f86bed2e5f7576c1b54cc0a6bf8ef5106cc62f496af35010 + languageName: node + linkType: hard + +"emoji-regex@npm:^9.2.2": + version: 9.2.2 + resolution: "emoji-regex@npm:9.2.2" + checksum: 10c0/af014e759a72064cf66e6e694a7fc6b0ed3d8db680427b021a89727689671cefe9d04151b2cad51dbaf85d5ba790d061cd167f1cf32eb7b281f6368b3c181639 + languageName: node + linkType: hard + +"encoding@npm:^0.1.13": + version: 0.1.13 + resolution: "encoding@npm:0.1.13" + dependencies: + iconv-lite: "npm:^0.6.2" + checksum: 10c0/36d938712ff00fe1f4bac88b43bcffb5930c1efa57bbcdca9d67e1d9d6c57cfb1200fb01efe0f3109b2ce99b231f90779532814a81370a1bd3274a0f58585039 + languageName: node + linkType: hard + +"entities@npm:^4.5.0": + version: 4.5.0 + resolution: "entities@npm:4.5.0" + checksum: 10c0/5b039739f7621f5d1ad996715e53d964035f75ad3b9a4d38c6b3804bb226e282ffeae2443624d8fdd9c47d8e926ae9ac009c54671243f0c3294c26af7cc85250 + languageName: node + linkType: hard + +"env-paths@npm:^2.2.0": + version: 2.2.1 + resolution: "env-paths@npm:2.2.1" + checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 + languageName: node + linkType: hard + +"err-code@npm:^2.0.2": + version: 2.0.3 + resolution: "err-code@npm:2.0.3" + checksum: 10c0/b642f7b4dd4a376e954947550a3065a9ece6733ab8e51ad80db727aaae0817c2e99b02a97a3d6cecc648a97848305e728289cf312d09af395403a90c9d4d8a66 + languageName: node + linkType: hard + +"es-define-property@npm:^1.0.1": + version: 1.0.1 + resolution: "es-define-property@npm:1.0.1" + checksum: 10c0/3f54eb49c16c18707949ff25a1456728c883e81259f045003499efba399c08bad00deebf65cccde8c0e07908c1a225c9d472b7107e558f2a48e28d530e34527c + languageName: node + linkType: hard + +"es-errors@npm:^1.3.0": + version: 1.3.0 + resolution: "es-errors@npm:1.3.0" + checksum: 10c0/0a61325670072f98d8ae3b914edab3559b6caa980f08054a3b872052640d91da01d38df55df797fcc916389d77fc92b8d5906cf028f4db46d7e3003abecbca85 + languageName: node + linkType: hard + +"es-module-lexer@npm:^1.6.0": + version: 1.6.0 + resolution: "es-module-lexer@npm:1.6.0" + checksum: 10c0/667309454411c0b95c476025929881e71400d74a746ffa1ff4cb450bd87f8e33e8eef7854d68e401895039ac0bac64e7809acbebb6253e055dd49ea9e3ea9212 + languageName: node + linkType: hard + +"es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1": + version: 1.1.1 + resolution: "es-object-atoms@npm:1.1.1" + dependencies: + es-errors: "npm:^1.3.0" + checksum: 10c0/65364812ca4daf48eb76e2a3b7a89b3f6a2e62a1c420766ce9f692665a29d94fe41fe88b65f24106f449859549711e4b40d9fb8002d862dfd7eb1c512d10be0c + languageName: node + linkType: hard + +"es-set-tostringtag@npm:^2.1.0": + version: 2.1.0 + resolution: "es-set-tostringtag@npm:2.1.0" + dependencies: + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.6" + has-tostringtag: "npm:^1.0.2" + hasown: "npm:^2.0.2" + checksum: 10c0/ef2ca9ce49afe3931cb32e35da4dcb6d86ab02592cfc2ce3e49ced199d9d0bb5085fc7e73e06312213765f5efa47cc1df553a6a5154584b21448e9fb8355b1af + languageName: node + linkType: hard + +"esbuild@npm:^0.25.0": + version: 0.25.1 + resolution: "esbuild@npm:0.25.1" + dependencies: + "@esbuild/aix-ppc64": "npm:0.25.1" + "@esbuild/android-arm": "npm:0.25.1" + "@esbuild/android-arm64": "npm:0.25.1" + "@esbuild/android-x64": "npm:0.25.1" + "@esbuild/darwin-arm64": "npm:0.25.1" + "@esbuild/darwin-x64": "npm:0.25.1" + "@esbuild/freebsd-arm64": "npm:0.25.1" + "@esbuild/freebsd-x64": "npm:0.25.1" + "@esbuild/linux-arm": "npm:0.25.1" + "@esbuild/linux-arm64": "npm:0.25.1" + "@esbuild/linux-ia32": "npm:0.25.1" + "@esbuild/linux-loong64": "npm:0.25.1" + "@esbuild/linux-mips64el": "npm:0.25.1" + "@esbuild/linux-ppc64": "npm:0.25.1" + "@esbuild/linux-riscv64": "npm:0.25.1" + "@esbuild/linux-s390x": "npm:0.25.1" + "@esbuild/linux-x64": "npm:0.25.1" + "@esbuild/netbsd-arm64": "npm:0.25.1" + "@esbuild/netbsd-x64": "npm:0.25.1" + "@esbuild/openbsd-arm64": "npm:0.25.1" + "@esbuild/openbsd-x64": "npm:0.25.1" + "@esbuild/sunos-x64": "npm:0.25.1" + "@esbuild/win32-arm64": "npm:0.25.1" + "@esbuild/win32-ia32": "npm:0.25.1" + "@esbuild/win32-x64": "npm:0.25.1" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/80fca30dd0f21aec23fdfab34f0a8d5f55df5097dd7f475f2ab561d45662c32ee306f5649071cd1a0ba0614b164c48ca3dc3ee1551a4daf204b8af90e4d893f5 + languageName: node + linkType: hard + +"escalade@npm:^3.2.0": + version: 3.2.0 + resolution: "escalade@npm:3.2.0" + checksum: 10c0/ced4dd3a78e15897ed3be74e635110bbf3b08877b0a41be50dcb325ee0e0b5f65fc2d50e9845194d7c4633f327e2e1c6cce00a71b617c5673df0374201d67f65 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^1.0.5": + version: 1.0.5 + resolution: "escape-string-regexp@npm:1.0.5" + checksum: 10c0/a968ad453dd0c2724e14a4f20e177aaf32bb384ab41b674a8454afe9a41c5e6fe8903323e0a1052f56289d04bd600f81278edf140b0fcc02f5cac98d0f5b5371 + languageName: node + linkType: hard + +"estree-walker@npm:^3.0.3": + version: 3.0.3 + resolution: "estree-walker@npm:3.0.3" + dependencies: + "@types/estree": "npm:^1.0.0" + checksum: 10c0/c12e3c2b2642d2bcae7d5aa495c60fa2f299160946535763969a1c83fc74518ffa9c2cd3a8b69ac56aea547df6a8aac25f729a342992ef0bbac5f1c73e78995d + languageName: node + linkType: hard + +"expect-type@npm:^1.1.0": + version: 1.2.0 + resolution: "expect-type@npm:1.2.0" + checksum: 10c0/6069e1980bf16b9385646800e23499c1447df636c433014f6bbabe4bb0e20bd0033f30d38a6f9ae0938b0203a9e870cc82cdfd74b7c837b480cefb8e8240d8e8 + languageName: node + linkType: hard + +"exponential-backoff@npm:^3.1.1": + version: 3.1.2 + resolution: "exponential-backoff@npm:3.1.2" + checksum: 10c0/d9d3e1eafa21b78464297df91f1776f7fbaa3d5e3f7f0995648ca5b89c069d17055033817348d9f4a43d1c20b0eab84f75af6991751e839df53e4dfd6f22e844 + languageName: node + linkType: hard + +"foreground-child@npm:^3.1.0": + version: 3.3.0 + resolution: "foreground-child@npm:3.3.0" + dependencies: + cross-spawn: "npm:^7.0.0" + signal-exit: "npm:^4.0.1" + checksum: 10c0/028f1d41000553fcfa6c4bb5c372963bf3d9bf0b1f25a87d1a6253014343fb69dfb1b42d9625d7cf44c8ba429940f3d0ff718b62105d4d4a4f6ef8ca0a53faa2 + languageName: node + linkType: hard + +"form-data@npm:^4.0.1": + version: 4.0.2 + resolution: "form-data@npm:4.0.2" + dependencies: + asynckit: "npm:^0.4.0" + combined-stream: "npm:^1.0.8" + es-set-tostringtag: "npm:^2.1.0" + mime-types: "npm:^2.1.12" + checksum: 10c0/e534b0cf025c831a0929bf4b9bbe1a9a6b03e273a8161f9947286b9b13bf8fb279c6944aae0070c4c311100c6d6dbb815cd955dc217728caf73fad8dc5b8ee9c + languageName: node + linkType: hard + +"fs-minipass@npm:^3.0.0": + version: 3.0.3 + resolution: "fs-minipass@npm:3.0.3" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/63e80da2ff9b621e2cb1596abcb9207f1cf82b968b116ccd7b959e3323144cce7fb141462200971c38bbf2ecca51695069db45265705bed09a7cd93ae5b89f94 + languageName: node + linkType: hard + +"fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": + version: 2.3.3 + resolution: "fsevents@npm:2.3.3" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/a1f0c44595123ed717febbc478aa952e47adfc28e2092be66b8ab1635147254ca6cfe1df792a8997f22716d4cbafc73309899ff7bfac2ac3ad8cf2e4ecc3ec60 + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": + version: 2.3.3 + resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin + languageName: node + linkType: hard + +"function-bind@npm:^1.1.2": + version: 1.1.2 + resolution: "function-bind@npm:1.1.2" + checksum: 10c0/d8680ee1e5fcd4c197e4ac33b2b4dce03c71f4d91717292785703db200f5c21f977c568d28061226f9b5900cbcd2c84463646134fd5337e7925e0942bc3f46d5 + languageName: node + linkType: hard + +"gensync@npm:^1.0.0-beta.2": + version: 1.0.0-beta.2 + resolution: "gensync@npm:1.0.0-beta.2" + checksum: 10c0/782aba6cba65b1bb5af3b095d96249d20edbe8df32dbf4696fd49be2583faf676173bf4809386588828e4dd76a3354fcbeb577bab1c833ccd9fc4577f26103f8 + languageName: node + linkType: hard + +"get-func-name@npm:^2.0.1": + version: 2.0.2 + resolution: "get-func-name@npm:2.0.2" + checksum: 10c0/89830fd07623fa73429a711b9daecdb304386d237c71268007f788f113505ef1d4cc2d0b9680e072c5082490aec9df5d7758bf5ac6f1c37062855e8e3dc0b9df + languageName: node + linkType: hard + +"get-intrinsic@npm:^1.2.6": + version: 1.3.0 + resolution: "get-intrinsic@npm:1.3.0" + dependencies: + call-bind-apply-helpers: "npm:^1.0.2" + es-define-property: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.1.1" + function-bind: "npm:^1.1.2" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + hasown: "npm:^2.0.2" + math-intrinsics: "npm:^1.1.0" + checksum: 10c0/52c81808af9a8130f581e6a6a83e1ba4a9f703359e7a438d1369a5267a25412322f03dcbd7c549edaef0b6214a0630a28511d7df0130c93cfd380f4fa0b5b66a + languageName: node + linkType: hard + +"get-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "get-proto@npm:1.0.1" + dependencies: + dunder-proto: "npm:^1.0.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/9224acb44603c5526955e83510b9da41baf6ae73f7398875fba50edc5e944223a89c4a72b070fcd78beb5f7bdda58ecb6294adc28f7acfc0da05f76a2399643c + languageName: node + linkType: hard + +"glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.3.7, glob@npm:^10.4.1": + version: 10.4.5 + resolution: "glob@npm:10.4.5" + dependencies: + foreground-child: "npm:^3.1.0" + jackspeak: "npm:^3.1.2" + minimatch: "npm:^9.0.4" + minipass: "npm:^7.1.2" + package-json-from-dist: "npm:^1.0.0" + path-scurry: "npm:^1.11.1" + bin: + glob: dist/esm/bin.mjs + checksum: 10c0/19a9759ea77b8e3ca0a43c2f07ecddc2ad46216b786bb8f993c445aee80d345925a21e5280c7b7c6c59e860a0154b84e4b2b60321fea92cd3c56b4a7489f160e + languageName: node + linkType: hard + +"globals@npm:^11.1.0": + version: 11.12.0 + resolution: "globals@npm:11.12.0" + checksum: 10c0/758f9f258e7b19226bd8d4af5d3b0dcf7038780fb23d82e6f98932c44e239f884847f1766e8fa9cc5635ccb3204f7fa7314d4408dd4002a5e8ea827b4018f0a1 + languageName: node + linkType: hard + +"gopd@npm:^1.2.0": + version: 1.2.0 + resolution: "gopd@npm:1.2.0" + checksum: 10c0/50fff1e04ba2b7737c097358534eacadad1e68d24cccee3272e04e007bed008e68d2614f3987788428fd192a5ae3889d08fb2331417e4fc4a9ab366b2043cead + languageName: node + linkType: hard + +"graceful-fs@npm:^4.2.6": + version: 4.2.11 + resolution: "graceful-fs@npm:4.2.11" + checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 + languageName: node + linkType: hard + +"has-flag@npm:^3.0.0": + version: 3.0.0 + resolution: "has-flag@npm:3.0.0" + checksum: 10c0/1c6c83b14b8b1b3c25b0727b8ba3e3b647f99e9e6e13eb7322107261de07a4c1be56fc0d45678fc376e09772a3a1642ccdaf8fc69bdf123b6c086598397ce473 + languageName: node + linkType: hard + +"has-flag@npm:^4.0.0": + version: 4.0.0 + resolution: "has-flag@npm:4.0.0" + checksum: 10c0/2e789c61b7888d66993e14e8331449e525ef42aac53c627cc53d1c3334e768bcb6abdc4f5f0de1478a25beec6f0bd62c7549058b7ac53e924040d4f301f02fd1 + languageName: node + linkType: hard + +"has-symbols@npm:^1.0.3, has-symbols@npm:^1.1.0": + version: 1.1.0 + resolution: "has-symbols@npm:1.1.0" + checksum: 10c0/dde0a734b17ae51e84b10986e651c664379018d10b91b6b0e9b293eddb32f0f069688c841fb40f19e9611546130153e0a2a48fd7f512891fb000ddfa36f5a20e + languageName: node + linkType: hard + +"has-tostringtag@npm:^1.0.2": + version: 1.0.2 + resolution: "has-tostringtag@npm:1.0.2" + dependencies: + has-symbols: "npm:^1.0.3" + checksum: 10c0/a8b166462192bafe3d9b6e420a1d581d93dd867adb61be223a17a8d6dad147aa77a8be32c961bb2f27b3ef893cae8d36f564ab651f5e9b7938ae86f74027c48c + languageName: node + linkType: hard + +"hasown@npm:^2.0.2": + version: 2.0.2 + resolution: "hasown@npm:2.0.2" + dependencies: + function-bind: "npm:^1.1.2" + checksum: 10c0/3769d434703b8ac66b209a4cca0737519925bbdb61dd887f93a16372b14694c63ff4e797686d87c90f08168e81082248b9b028bad60d4da9e0d1148766f56eb9 + languageName: node + linkType: hard + +"html-encoding-sniffer@npm:^4.0.0": + version: 4.0.0 + resolution: "html-encoding-sniffer@npm:4.0.0" + dependencies: + whatwg-encoding: "npm:^3.1.1" + checksum: 10c0/523398055dc61ac9b34718a719cb4aa691e4166f29187e211e1607de63dc25ac7af52ca7c9aead0c4b3c0415ffecb17326396e1202e2e86ff4bca4c0ee4c6140 + languageName: node + linkType: hard + +"html-escaper@npm:^2.0.0": + version: 2.0.2 + resolution: "html-escaper@npm:2.0.2" + checksum: 10c0/208e8a12de1a6569edbb14544f4567e6ce8ecc30b9394fcaa4e7bb1e60c12a7c9a1ed27e31290817157e8626f3a4f29e76c8747030822eb84a6abb15c255f0a0 + languageName: node + linkType: hard + +"http-cache-semantics@npm:^4.1.1": + version: 4.1.1 + resolution: "http-cache-semantics@npm:4.1.1" + checksum: 10c0/ce1319b8a382eb3cbb4a37c19f6bfe14e5bb5be3d09079e885e8c513ab2d3cd9214902f8a31c9dc4e37022633ceabfc2d697405deeaf1b8f3552bb4ed996fdfc + languageName: node + linkType: hard + +"http-proxy-agent@npm:^7.0.0, http-proxy-agent@npm:^7.0.2": + version: 7.0.2 + resolution: "http-proxy-agent@npm:7.0.2" + dependencies: + agent-base: "npm:^7.1.0" + debug: "npm:^4.3.4" + checksum: 10c0/4207b06a4580fb85dd6dff521f0abf6db517489e70863dca1a0291daa7f2d3d2d6015a57bd702af068ea5cf9f1f6ff72314f5f5b4228d299c0904135d2aef921 + languageName: node + linkType: hard + +"https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.6": + version: 7.0.6 + resolution: "https-proxy-agent@npm:7.0.6" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:4" + checksum: 10c0/f729219bc735edb621fa30e6e84e60ee5d00802b8247aac0d7b79b0bd6d4b3294737a337b93b86a0bd9e68099d031858a39260c976dc14cdbba238ba1f8779ac + languageName: node + linkType: hard + +"iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2": + version: 0.6.3 + resolution: "iconv-lite@npm:0.6.3" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3.0.0" + checksum: 10c0/98102bc66b33fcf5ac044099d1257ba0b7ad5e3ccd3221f34dd508ab4070edff183276221684e1e0555b145fce0850c9f7d2b60a9fcac50fbb4ea0d6e845a3b1 + languageName: node + linkType: hard + +"imurmurhash@npm:^0.1.4": + version: 0.1.4 + resolution: "imurmurhash@npm:0.1.4" + checksum: 10c0/8b51313850dd33605c6c9d3fd9638b714f4c4c40250cff658209f30d40da60f78992fb2df5dabee4acf589a6a82bbc79ad5486550754bd9ec4e3fc0d4a57d6a6 + languageName: node + linkType: hard + +"ip-address@npm:^9.0.5": + version: 9.0.5 + resolution: "ip-address@npm:9.0.5" + dependencies: + jsbn: "npm:1.1.0" + sprintf-js: "npm:^1.1.3" + checksum: 10c0/331cd07fafcb3b24100613e4b53e1a2b4feab11e671e655d46dc09ee233da5011284d09ca40c4ecbdfe1d0004f462958675c224a804259f2f78d2465a87824bc + languageName: node + linkType: hard + +"is-fullwidth-code-point@npm:^3.0.0": + version: 3.0.0 + resolution: "is-fullwidth-code-point@npm:3.0.0" + checksum: 10c0/bb11d825e049f38e04c06373a8d72782eee0205bda9d908cc550ccb3c59b99d750ff9537982e01733c1c94a58e35400661f57042158ff5e8f3e90cf936daf0fc + languageName: node + linkType: hard + +"is-potential-custom-element-name@npm:^1.0.1": + version: 1.0.1 + resolution: "is-potential-custom-element-name@npm:1.0.1" + checksum: 10c0/b73e2f22bc863b0939941d369486d308b43d7aef1f9439705e3582bfccaa4516406865e32c968a35f97a99396dac84e2624e67b0a16b0a15086a785e16ce7db9 + languageName: node + linkType: hard + +"isexe@npm:^2.0.0": + version: 2.0.0 + resolution: "isexe@npm:2.0.0" + checksum: 10c0/228cfa503fadc2c31596ab06ed6aa82c9976eec2bfd83397e7eaf06d0ccf42cd1dfd6743bf9aeb01aebd4156d009994c5f76ea898d2832c1fe342da923ca457d + languageName: node + linkType: hard + +"isexe@npm:^3.1.1": + version: 3.1.1 + resolution: "isexe@npm:3.1.1" + checksum: 10c0/9ec257654093443eb0a528a9c8cbba9c0ca7616ccb40abd6dde7202734d96bb86e4ac0d764f0f8cd965856aacbff2f4ce23e730dc19dfb41e3b0d865ca6fdcc7 + languageName: node + linkType: hard + +"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0, istanbul-lib-coverage@npm:^3.2.2": + version: 3.2.2 + resolution: "istanbul-lib-coverage@npm:3.2.2" + checksum: 10c0/6c7ff2106769e5f592ded1fb418f9f73b4411fd5a084387a5410538332b6567cd1763ff6b6cadca9b9eb2c443cce2f7ea7d7f1b8d315f9ce58539793b1e0922b + languageName: node + linkType: hard + +"istanbul-lib-instrument@npm:^6.0.3": + version: 6.0.3 + resolution: "istanbul-lib-instrument@npm:6.0.3" + dependencies: + "@babel/core": "npm:^7.23.9" + "@babel/parser": "npm:^7.23.9" + "@istanbuljs/schema": "npm:^0.1.3" + istanbul-lib-coverage: "npm:^3.2.0" + semver: "npm:^7.5.4" + checksum: 10c0/a1894e060dd2a3b9f046ffdc87b44c00a35516f5e6b7baf4910369acca79e506fc5323a816f811ae23d82334b38e3ddeb8b3b331bd2c860540793b59a8689128 + languageName: node + linkType: hard + +"istanbul-lib-report@npm:^3.0.0, istanbul-lib-report@npm:^3.0.1": + version: 3.0.1 + resolution: "istanbul-lib-report@npm:3.0.1" + dependencies: + istanbul-lib-coverage: "npm:^3.0.0" + make-dir: "npm:^4.0.0" + supports-color: "npm:^7.1.0" + checksum: 10c0/84323afb14392de8b6a5714bd7e9af845cfbd56cfe71ed276cda2f5f1201aea673c7111901227ee33e68e4364e288d73861eb2ed48f6679d1e69a43b6d9b3ba7 + languageName: node + linkType: hard + +"istanbul-lib-source-maps@npm:^5.0.6": + version: 5.0.6 + resolution: "istanbul-lib-source-maps@npm:5.0.6" + dependencies: + "@jridgewell/trace-mapping": "npm:^0.3.23" + debug: "npm:^4.1.1" + istanbul-lib-coverage: "npm:^3.0.0" + checksum: 10c0/ffe75d70b303a3621ee4671554f306e0831b16f39ab7f4ab52e54d356a5d33e534d97563e318f1333a6aae1d42f91ec49c76b6cd3f3fb378addcb5c81da0255f + languageName: node + linkType: hard + +"istanbul-reports@npm:^3.1.7": + version: 3.1.7 + resolution: "istanbul-reports@npm:3.1.7" + dependencies: + html-escaper: "npm:^2.0.0" + istanbul-lib-report: "npm:^3.0.0" + checksum: 10c0/a379fadf9cf8dc5dfe25568115721d4a7eb82fbd50b005a6672aff9c6989b20cc9312d7865814e0859cd8df58cbf664482e1d3604be0afde1f7fc3ccc1394a51 + languageName: node + linkType: hard + +"jackspeak@npm:^3.1.2": + version: 3.4.3 + resolution: "jackspeak@npm:3.4.3" + dependencies: + "@isaacs/cliui": "npm:^8.0.2" + "@pkgjs/parseargs": "npm:^0.11.0" + dependenciesMeta: + "@pkgjs/parseargs": + optional: true + checksum: 10c0/6acc10d139eaefdbe04d2f679e6191b3abf073f111edf10b1de5302c97ec93fffeb2fdd8681ed17f16268aa9dd4f8c588ed9d1d3bffbbfa6e8bf897cbb3149b9 + languageName: node + linkType: hard + +"js-tokens@npm:^4.0.0": + version: 4.0.0 + resolution: "js-tokens@npm:4.0.0" + checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed + languageName: node + linkType: hard + +"jsbn@npm:1.1.0": + version: 1.1.0 + resolution: "jsbn@npm:1.1.0" + checksum: 10c0/4f907fb78d7b712e11dea8c165fe0921f81a657d3443dde75359ed52eb2b5d33ce6773d97985a089f09a65edd80b11cb75c767b57ba47391fee4c969f7215c96 + languageName: node + linkType: hard + +"jsdom@npm:^26.0.0": + version: 26.0.0 + resolution: "jsdom@npm:26.0.0" + dependencies: + cssstyle: "npm:^4.2.1" + data-urls: "npm:^5.0.0" + decimal.js: "npm:^10.4.3" + form-data: "npm:^4.0.1" + html-encoding-sniffer: "npm:^4.0.0" + http-proxy-agent: "npm:^7.0.2" + https-proxy-agent: "npm:^7.0.6" + is-potential-custom-element-name: "npm:^1.0.1" + nwsapi: "npm:^2.2.16" + parse5: "npm:^7.2.1" + rrweb-cssom: "npm:^0.8.0" + saxes: "npm:^6.0.0" + symbol-tree: "npm:^3.2.4" + tough-cookie: "npm:^5.0.0" + w3c-xmlserializer: "npm:^5.0.0" + webidl-conversions: "npm:^7.0.0" + whatwg-encoding: "npm:^3.1.1" + whatwg-mimetype: "npm:^4.0.0" + whatwg-url: "npm:^14.1.0" + ws: "npm:^8.18.0" + xml-name-validator: "npm:^5.0.0" + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + checksum: 10c0/e48725ba4027edcfc9bca5799eaec72c6561ecffe3675a8ff87fe9c3541ca4ff9f82b4eff5b3d9c527302da0d859b2f60e9364347a5d42b77f5c76c436c569dc + languageName: node + linkType: hard + +"jsesc@npm:^2.5.1": + version: 2.5.2 + resolution: "jsesc@npm:2.5.2" + bin: + jsesc: bin/jsesc + checksum: 10c0/dbf59312e0ebf2b4405ef413ec2b25abb5f8f4d9bc5fb8d9f90381622ebca5f2af6a6aa9a8578f65903f9e33990a6dc798edd0ce5586894bf0e9e31803a1de88 + languageName: node + linkType: hard + +"json5@npm:^2.2.3": + version: 2.2.3 + resolution: "json5@npm:2.2.3" + bin: + json5: lib/cli.js + checksum: 10c0/5a04eed94810fa55c5ea138b2f7a5c12b97c3750bc63d11e511dcecbfef758003861522a070c2272764ee0f4e3e323862f386945aeb5b85b87ee43f084ba586c + languageName: node + linkType: hard + +"loupe@npm:^3.1.0": + version: 3.1.1 + resolution: "loupe@npm:3.1.1" + dependencies: + get-func-name: "npm:^2.0.1" + checksum: 10c0/99f88badc47e894016df0c403de846fedfea61154aadabbf776c8428dd59e8d8378007135d385d737de32ae47980af07d22ba7bec5ef7beebd721de9baa0a0af + languageName: node + linkType: hard + +"loupe@npm:^3.1.3": + version: 3.1.3 + resolution: "loupe@npm:3.1.3" + checksum: 10c0/f5dab4144254677de83a35285be1b8aba58b3861439ce4ba65875d0d5f3445a4a496daef63100ccf02b2dbc25bf58c6db84c9cb0b96d6435331e9d0a33b48541 + languageName: node + linkType: hard + +"lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0, lru-cache@npm:^10.4.3": + version: 10.4.3 + resolution: "lru-cache@npm:10.4.3" + checksum: 10c0/ebd04fbca961e6c1d6c0af3799adcc966a1babe798f685bb84e6599266599cd95d94630b10262f5424539bc4640107e8a33aa28585374abf561d30d16f4b39fb + languageName: node + linkType: hard + +"lru-cache@npm:^5.1.1": + version: 5.1.1 + resolution: "lru-cache@npm:5.1.1" + dependencies: + yallist: "npm:^3.0.2" + checksum: 10c0/89b2ef2ef45f543011e38737b8a8622a2f8998cddf0e5437174ef8f1f70a8b9d14a918ab3e232cb3ba343b7abddffa667f0b59075b2b80e6b4d63c3de6127482 + languageName: node + linkType: hard + +"magic-string@npm:^0.30.17": + version: 0.30.17 + resolution: "magic-string@npm:0.30.17" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.0" + checksum: 10c0/16826e415d04b88378f200fe022b53e638e3838b9e496edda6c0e086d7753a44a6ed187adc72d19f3623810589bf139af1a315541cd6a26ae0771a0193eaf7b8 + languageName: node + linkType: hard + +"magicast@npm:^0.3.5": + version: 0.3.5 + resolution: "magicast@npm:0.3.5" + dependencies: + "@babel/parser": "npm:^7.25.4" + "@babel/types": "npm:^7.25.4" + source-map-js: "npm:^1.2.0" + checksum: 10c0/a6cacc0a848af84f03e3f5bda7b0de75e4d0aa9ddce5517fd23ed0f31b5ddd51b2d0ff0b7e09b51f7de0f4053c7a1107117edda6b0732dca3e9e39e6c5a68c64 + languageName: node + linkType: hard + +"make-dir@npm:^4.0.0": + version: 4.0.0 + resolution: "make-dir@npm:4.0.0" + dependencies: + semver: "npm:^7.5.3" + checksum: 10c0/69b98a6c0b8e5c4fe9acb61608a9fbcfca1756d910f51e5dbe7a9e5cfb74fca9b8a0c8a0ffdf1294a740826c1ab4871d5bf3f62f72a3049e5eac6541ddffed68 + languageName: node + linkType: hard + +"make-fetch-happen@npm:^14.0.3": + version: 14.0.3 + resolution: "make-fetch-happen@npm:14.0.3" + dependencies: + "@npmcli/agent": "npm:^3.0.0" + cacache: "npm:^19.0.1" + http-cache-semantics: "npm:^4.1.1" + minipass: "npm:^7.0.2" + minipass-fetch: "npm:^4.0.0" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + negotiator: "npm:^1.0.0" + proc-log: "npm:^5.0.0" + promise-retry: "npm:^2.0.1" + ssri: "npm:^12.0.0" + checksum: 10c0/c40efb5e5296e7feb8e37155bde8eb70bc57d731b1f7d90e35a092fde403d7697c56fb49334d92d330d6f1ca29a98142036d6480a12681133a0a1453164cb2f0 + languageName: node + linkType: hard + +"math-intrinsics@npm:^1.1.0": + version: 1.1.0 + resolution: "math-intrinsics@npm:1.1.0" + checksum: 10c0/7579ff94e899e2f76ab64491d76cf606274c874d8f2af4a442c016bd85688927fcfca157ba6bf74b08e9439dc010b248ce05b96cc7c126a354c3bae7fcb48b7f + languageName: node + linkType: hard + +"mime-db@npm:1.52.0": + version: 1.52.0 + resolution: "mime-db@npm:1.52.0" + checksum: 10c0/0557a01deebf45ac5f5777fe7740b2a5c309c6d62d40ceab4e23da9f821899ce7a900b7ac8157d4548ddbb7beffe9abc621250e6d182b0397ec7f10c7b91a5aa + languageName: node + linkType: hard + +"mime-types@npm:^2.1.12": + version: 2.1.35 + resolution: "mime-types@npm:2.1.35" + dependencies: + mime-db: "npm:1.52.0" + checksum: 10c0/82fb07ec56d8ff1fc999a84f2f217aa46cb6ed1033fefaabd5785b9a974ed225c90dc72fff460259e66b95b73648596dbcc50d51ed69cdf464af2d237d3149b2 + languageName: node + linkType: hard + +"minimatch@npm:^9.0.4": + version: 9.0.5 + resolution: "minimatch@npm:9.0.5" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10c0/de96cf5e35bdf0eab3e2c853522f98ffbe9a36c37797778d2665231ec1f20a9447a7e567cb640901f89e4daaa95ae5d70c65a9e8aa2bb0019b6facbc3c0575ed + languageName: node + linkType: hard + +"minipass-collect@npm:^2.0.1": + version: 2.0.1 + resolution: "minipass-collect@npm:2.0.1" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/5167e73f62bb74cc5019594709c77e6a742051a647fe9499abf03c71dca75515b7959d67a764bdc4f8b361cf897fbf25e2d9869ee039203ed45240f48b9aa06e + languageName: node + linkType: hard + +"minipass-fetch@npm:^4.0.0": + version: 4.0.1 + resolution: "minipass-fetch@npm:4.0.1" + dependencies: + encoding: "npm:^0.1.13" + minipass: "npm:^7.0.3" + minipass-sized: "npm:^1.0.3" + minizlib: "npm:^3.0.1" + dependenciesMeta: + encoding: + optional: true + checksum: 10c0/a3147b2efe8e078c9bf9d024a0059339c5a09c5b1dded6900a219c218cc8b1b78510b62dae556b507304af226b18c3f1aeb1d48660283602d5b6586c399eed5c + languageName: node + linkType: hard + +"minipass-flush@npm:^1.0.5": + version: 1.0.5 + resolution: "minipass-flush@npm:1.0.5" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/2a51b63feb799d2bb34669205eee7c0eaf9dce01883261a5b77410c9408aa447e478efd191b4de6fc1101e796ff5892f8443ef20d9544385819093dbb32d36bd + languageName: node + linkType: hard + +"minipass-pipeline@npm:^1.2.4": + version: 1.2.4 + resolution: "minipass-pipeline@npm:1.2.4" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/cbda57cea20b140b797505dc2cac71581a70b3247b84480c1fed5ca5ba46c25ecc25f68bfc9e6dcb1a6e9017dab5c7ada5eab73ad4f0a49d84e35093e0c643f2 + languageName: node + linkType: hard + +"minipass-sized@npm:^1.0.3": + version: 1.0.3 + resolution: "minipass-sized@npm:1.0.3" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/298f124753efdc745cfe0f2bdfdd81ba25b9f4e753ca4a2066eb17c821f25d48acea607dfc997633ee5bf7b6dfffb4eee4f2051eb168663f0b99fad2fa4829cb + languageName: node + linkType: hard + +"minipass@npm:^3.0.0": + version: 3.3.6 + resolution: "minipass@npm:3.3.6" + dependencies: + yallist: "npm:^4.0.0" + checksum: 10c0/a114746943afa1dbbca8249e706d1d38b85ed1298b530f5808ce51f8e9e941962e2a5ad2e00eae7dd21d8a4aae6586a66d4216d1a259385e9d0358f0c1eba16c + languageName: node + linkType: hard + +"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2": + version: 7.1.2 + resolution: "minipass@npm:7.1.2" + checksum: 10c0/b0fd20bb9fb56e5fa9a8bfac539e8915ae07430a619e4b86ff71f5fc757ef3924b23b2c4230393af1eda647ed3d75739e4e0acb250a6b1eb277cf7f8fe449557 + languageName: node + linkType: hard + +"minizlib@npm:^3.0.1": + version: 3.0.1 + resolution: "minizlib@npm:3.0.1" + dependencies: + minipass: "npm:^7.0.4" + rimraf: "npm:^5.0.5" + checksum: 10c0/82f8bf70da8af656909a8ee299d7ed3b3372636749d29e105f97f20e88971be31f5ed7642f2e898f00283b68b701cc01307401cdc209b0efc5dd3818220e5093 + languageName: node + linkType: hard + +"mkdirp@npm:^3.0.1": + version: 3.0.1 + resolution: "mkdirp@npm:3.0.1" + bin: + mkdirp: dist/cjs/src/bin.js + checksum: 10c0/9f2b975e9246351f5e3a40dcfac99fcd0baa31fbfab615fe059fb11e51f10e4803c63de1f384c54d656e4db31d000e4767e9ef076a22e12a641357602e31d57d + languageName: node + linkType: hard + +"ms@npm:^2.1.3": + version: 2.1.3 + resolution: "ms@npm:2.1.3" + checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 + languageName: node + linkType: hard + +"nanoid@npm:^3.3.8": + version: 3.3.11 + resolution: "nanoid@npm:3.3.11" + bin: + nanoid: bin/nanoid.cjs + checksum: 10c0/40e7f70b3d15f725ca072dfc4f74e81fcf1fbb02e491cf58ac0c79093adc9b0a73b152bcde57df4b79cd097e13023d7504acb38404a4da7bc1cd8e887b82fe0b + languageName: node + linkType: hard + +"negotiator@npm:^1.0.0": + version: 1.0.0 + resolution: "negotiator@npm:1.0.0" + checksum: 10c0/4c559dd52669ea48e1914f9d634227c561221dd54734070791f999c52ed0ff36e437b2e07d5c1f6e32909fc625fe46491c16e4a8f0572567d4dd15c3a4fda04b + languageName: node + linkType: hard + +"node-gyp@npm:latest": + version: 11.1.0 + resolution: "node-gyp@npm:11.1.0" + dependencies: + env-paths: "npm:^2.2.0" + exponential-backoff: "npm:^3.1.1" + glob: "npm:^10.3.10" + graceful-fs: "npm:^4.2.6" + make-fetch-happen: "npm:^14.0.3" + nopt: "npm:^8.0.0" + proc-log: "npm:^5.0.0" + semver: "npm:^7.3.5" + tar: "npm:^7.4.3" + which: "npm:^5.0.0" + bin: + node-gyp: bin/node-gyp.js + checksum: 10c0/c38977ce502f1ea41ba2b8721bd5b49bc3d5b3f813eabfac8414082faf0620ccb5211e15c4daecc23ed9f5e3e9cc4da00e575a0bcfc2a95a069294f2afa1e0cd + languageName: node + linkType: hard + +"node-releases@npm:^2.0.18": + version: 2.0.18 + resolution: "node-releases@npm:2.0.18" + checksum: 10c0/786ac9db9d7226339e1dc84bbb42007cb054a346bd9257e6aa154d294f01bc6a6cddb1348fa099f079be6580acbb470e3c048effd5f719325abd0179e566fd27 + languageName: node + linkType: hard + +"nopt@npm:^8.0.0": + version: 8.1.0 + resolution: "nopt@npm:8.1.0" + dependencies: + abbrev: "npm:^3.0.0" + bin: + nopt: bin/nopt.js + checksum: 10c0/62e9ea70c7a3eb91d162d2c706b6606c041e4e7b547cbbb48f8b3695af457dd6479904d7ace600856bf923dd8d1ed0696f06195c8c20f02ac87c1da0e1d315ef + languageName: node + linkType: hard + +"nwsapi@npm:^2.2.16": + version: 2.2.19 + resolution: "nwsapi@npm:2.2.19" + checksum: 10c0/5bd9da260b2b24a775103c835a93c79584a870307eb59270b43c6970b7ae9c0af3cfffd9df5292c24d2ca2c67f49672b4c9be9824c347d6083b90e002a12779a + languageName: node + linkType: hard + +"p-map@npm:^7.0.2": + version: 7.0.3 + resolution: "p-map@npm:7.0.3" + checksum: 10c0/46091610da2b38ce47bcd1d8b4835a6fa4e832848a6682cf1652bc93915770f4617afc844c10a77d1b3e56d2472bb2d5622353fa3ead01a7f42b04fc8e744a5c + languageName: node + linkType: hard + +"package-json-from-dist@npm:^1.0.0": + version: 1.0.1 + resolution: "package-json-from-dist@npm:1.0.1" + checksum: 10c0/62ba2785eb655fec084a257af34dbe24292ab74516d6aecef97ef72d4897310bc6898f6c85b5cd22770eaa1ce60d55a0230e150fb6a966e3ecd6c511e23d164b + languageName: node + linkType: hard + +"parse5@npm:^7.2.1": + version: 7.2.1 + resolution: "parse5@npm:7.2.1" + dependencies: + entities: "npm:^4.5.0" + checksum: 10c0/829d37a0c709215a887e410a7118d754f8e1afd7edb529db95bc7bbf8045fb0266a7b67801331d8e8d9d073ea75793624ec27ce9ff3b96862c3b9008f4d68e80 + languageName: node + linkType: hard + +"path-key@npm:^3.1.0": + version: 3.1.1 + resolution: "path-key@npm:3.1.1" + checksum: 10c0/748c43efd5a569c039d7a00a03b58eecd1d75f3999f5a28303d75f521288df4823bc057d8784eb72358b2895a05f29a070bc9f1f17d28226cc4e62494cc58c4c + languageName: node + linkType: hard + +"path-scurry@npm:^1.11.1": + version: 1.11.1 + resolution: "path-scurry@npm:1.11.1" + dependencies: + lru-cache: "npm:^10.2.0" + minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" + checksum: 10c0/32a13711a2a505616ae1cc1b5076801e453e7aae6ac40ab55b388bb91b9d0547a52f5aaceff710ea400205f18691120d4431e520afbe4266b836fadede15872d + languageName: node + linkType: hard + +"pathe@npm:^2.0.3": + version: 2.0.3 + resolution: "pathe@npm:2.0.3" + checksum: 10c0/c118dc5a8b5c4166011b2b70608762e260085180bb9e33e80a50dcdb1e78c010b1624f4280c492c92b05fc276715a4c357d1f9edc570f8f1b3d90b6839ebaca1 + languageName: node + linkType: hard + +"pathval@npm:^2.0.0": + version: 2.0.0 + resolution: "pathval@npm:2.0.0" + checksum: 10c0/602e4ee347fba8a599115af2ccd8179836a63c925c23e04bd056d0674a64b39e3a081b643cc7bc0b84390517df2d800a46fcc5598d42c155fe4977095c2f77c5 + languageName: node + linkType: hard + +"picocolors@npm:^1.0.0, picocolors@npm:^1.1.0": + version: 1.1.0 + resolution: "picocolors@npm:1.1.0" + checksum: 10c0/86946f6032148801ef09c051c6fb13b5cf942eaf147e30ea79edb91dd32d700934edebe782a1078ff859fb2b816792e97ef4dab03d7f0b804f6b01a0df35e023 + languageName: node + linkType: hard + +"picocolors@npm:^1.1.1": + version: 1.1.1 + resolution: "picocolors@npm:1.1.1" + checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58 + languageName: node + linkType: hard + +"postcss@npm:^8.5.3": + version: 8.5.3 + resolution: "postcss@npm:8.5.3" + dependencies: + nanoid: "npm:^3.3.8" + picocolors: "npm:^1.1.1" + source-map-js: "npm:^1.2.1" + checksum: 10c0/b75510d7b28c3ab728c8733dd01538314a18c52af426f199a3c9177e63eb08602a3938bfb66b62dc01350b9aed62087eabbf229af97a1659eb8d3513cec823b3 + languageName: node + linkType: hard + +"proc-log@npm:^5.0.0": + version: 5.0.0 + resolution: "proc-log@npm:5.0.0" + checksum: 10c0/bbe5edb944b0ad63387a1d5b1911ae93e05ce8d0f60de1035b218cdcceedfe39dbd2c697853355b70f1a090f8f58fe90da487c85216bf9671f9499d1a897e9e3 + languageName: node + linkType: hard + +"promise-retry@npm:^2.0.1": + version: 2.0.1 + resolution: "promise-retry@npm:2.0.1" + dependencies: + err-code: "npm:^2.0.2" + retry: "npm:^0.12.0" + checksum: 10c0/9c7045a1a2928094b5b9b15336dcd2a7b1c052f674550df63cc3f36cd44028e5080448175b6f6ca32b642de81150f5e7b1a98b728f15cb069f2dd60ac2616b96 + languageName: node + linkType: hard + +"punycode@npm:^2.3.1": + version: 2.3.1 + resolution: "punycode@npm:2.3.1" + checksum: 10c0/14f76a8206bc3464f794fb2e3d3cc665ae416c01893ad7a02b23766eb07159144ee612ad67af5e84fa4479ccfe67678c4feb126b0485651b302babf66f04f9e9 + languageName: node + linkType: hard + +"retry@npm:^0.12.0": + version: 0.12.0 + resolution: "retry@npm:0.12.0" + checksum: 10c0/59933e8501727ba13ad73ef4a04d5280b3717fd650408460c987392efe9d7be2040778ed8ebe933c5cbd63da3dcc37919c141ef8af0a54a6e4fca5a2af177bfe + languageName: node + linkType: hard + +"rimraf@npm:^5.0.5": + version: 5.0.10 + resolution: "rimraf@npm:5.0.10" + dependencies: + glob: "npm:^10.3.7" + bin: + rimraf: dist/esm/bin.mjs + checksum: 10c0/7da4fd0e15118ee05b918359462cfa1e7fe4b1228c7765195a45b55576e8c15b95db513b8466ec89129666f4af45ad978a3057a02139afba1a63512a2d9644cc + languageName: node + linkType: hard + +"rollup@npm:^4.30.1": + version: 4.37.0 + resolution: "rollup@npm:4.37.0" + dependencies: + "@rollup/rollup-android-arm-eabi": "npm:4.37.0" + "@rollup/rollup-android-arm64": "npm:4.37.0" + "@rollup/rollup-darwin-arm64": "npm:4.37.0" + "@rollup/rollup-darwin-x64": "npm:4.37.0" + "@rollup/rollup-freebsd-arm64": "npm:4.37.0" + "@rollup/rollup-freebsd-x64": "npm:4.37.0" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.37.0" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.37.0" + "@rollup/rollup-linux-arm64-gnu": "npm:4.37.0" + "@rollup/rollup-linux-arm64-musl": "npm:4.37.0" + "@rollup/rollup-linux-loongarch64-gnu": "npm:4.37.0" + "@rollup/rollup-linux-powerpc64le-gnu": "npm:4.37.0" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.37.0" + "@rollup/rollup-linux-riscv64-musl": "npm:4.37.0" + "@rollup/rollup-linux-s390x-gnu": "npm:4.37.0" + "@rollup/rollup-linux-x64-gnu": "npm:4.37.0" + "@rollup/rollup-linux-x64-musl": "npm:4.37.0" + "@rollup/rollup-win32-arm64-msvc": "npm:4.37.0" + "@rollup/rollup-win32-ia32-msvc": "npm:4.37.0" + "@rollup/rollup-win32-x64-msvc": "npm:4.37.0" + "@types/estree": "npm:1.0.6" + fsevents: "npm:~2.3.2" + dependenciesMeta: + "@rollup/rollup-android-arm-eabi": + optional: true + "@rollup/rollup-android-arm64": + optional: true + "@rollup/rollup-darwin-arm64": + optional: true + "@rollup/rollup-darwin-x64": + optional: true + "@rollup/rollup-freebsd-arm64": + optional: true + "@rollup/rollup-freebsd-x64": + optional: true + "@rollup/rollup-linux-arm-gnueabihf": + optional: true + "@rollup/rollup-linux-arm-musleabihf": + optional: true + "@rollup/rollup-linux-arm64-gnu": + optional: true + "@rollup/rollup-linux-arm64-musl": + optional: true + "@rollup/rollup-linux-loongarch64-gnu": + optional: true + "@rollup/rollup-linux-powerpc64le-gnu": + optional: true + "@rollup/rollup-linux-riscv64-gnu": + optional: true + "@rollup/rollup-linux-riscv64-musl": + optional: true + "@rollup/rollup-linux-s390x-gnu": + optional: true + "@rollup/rollup-linux-x64-gnu": + optional: true + "@rollup/rollup-linux-x64-musl": + optional: true + "@rollup/rollup-win32-arm64-msvc": + optional: true + "@rollup/rollup-win32-ia32-msvc": + optional: true + "@rollup/rollup-win32-x64-msvc": + optional: true + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: 10c0/2e00382e08938636edfe0a7547ea2eaa027205dc0b6ff85d8b82be0fbe55a4ef88a1995fee2a5059e33dbccf12d1376c236825353afb89c96298cc95c5160a46 + languageName: node + linkType: hard + +"rrweb-cssom@npm:^0.8.0": + version: 0.8.0 + resolution: "rrweb-cssom@npm:0.8.0" + checksum: 10c0/56f2bfd56733adb92c0b56e274c43f864b8dd48784d6fe946ef5ff8d438234015e59ad837fc2ad54714b6421384141c1add4eb569e72054e350d1f8a50b8ac7b + languageName: node + linkType: hard + +"safer-buffer@npm:>= 2.1.2 < 3.0.0": + version: 2.1.2 + resolution: "safer-buffer@npm:2.1.2" + checksum: 10c0/7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4 + languageName: node + linkType: hard + +"saxes@npm:^6.0.0": + version: 6.0.0 + resolution: "saxes@npm:6.0.0" + dependencies: + xmlchars: "npm:^2.2.0" + checksum: 10c0/3847b839f060ef3476eb8623d099aa502ad658f5c40fd60c105ebce86d244389b0d76fcae30f4d0c728d7705ceb2f7e9b34bb54717b6a7dbedaf5dad2d9a4b74 + languageName: node + linkType: hard + +"semver@npm:^6.3.1": + version: 6.3.1 + resolution: "semver@npm:6.3.1" + bin: + semver: bin/semver.js + checksum: 10c0/e3d79b609071caa78bcb6ce2ad81c7966a46a7431d9d58b8800cfa9cb6a63699b3899a0e4bcce36167a284578212d9ae6942b6929ba4aa5015c079a67751d42d + languageName: node + linkType: hard + +"semver@npm:^7.3.5": + version: 7.7.1 + resolution: "semver@npm:7.7.1" + bin: + semver: bin/semver.js + checksum: 10c0/fd603a6fb9c399c6054015433051bdbe7b99a940a8fb44b85c2b524c4004b023d7928d47cb22154f8d054ea7ee8597f586605e05b52047f048278e4ac56ae958 + languageName: node + linkType: hard + +"semver@npm:^7.5.3, semver@npm:^7.5.4": + version: 7.6.3 + resolution: "semver@npm:7.6.3" + bin: + semver: bin/semver.js + checksum: 10c0/88f33e148b210c153873cb08cfe1e281d518aaa9a666d4d148add6560db5cd3c582f3a08ccb91f38d5f379ead256da9931234ed122057f40bb5766e65e58adaf + languageName: node + linkType: hard + +"shebang-command@npm:^2.0.0": + version: 2.0.0 + resolution: "shebang-command@npm:2.0.0" + dependencies: + shebang-regex: "npm:^3.0.0" + checksum: 10c0/a41692e7d89a553ef21d324a5cceb5f686d1f3c040759c50aab69688634688c5c327f26f3ecf7001ebfd78c01f3c7c0a11a7c8bfd0a8bc9f6240d4f40b224e4e + languageName: node + linkType: hard + +"shebang-regex@npm:^3.0.0": + version: 3.0.0 + resolution: "shebang-regex@npm:3.0.0" + checksum: 10c0/1dbed0726dd0e1152a92696c76c7f06084eb32a90f0528d11acd764043aacf76994b2fb30aa1291a21bd019d6699164d048286309a278855ee7bec06cf6fb690 + languageName: node + linkType: hard + +"siginfo@npm:^2.0.0": + version: 2.0.0 + resolution: "siginfo@npm:2.0.0" + checksum: 10c0/3def8f8e516fbb34cb6ae415b07ccc5d9c018d85b4b8611e3dc6f8be6d1899f693a4382913c9ed51a06babb5201639d76453ab297d1c54a456544acf5c892e34 + languageName: node + linkType: hard + +"signal-exit@npm:^4.0.1": + version: 4.1.0 + resolution: "signal-exit@npm:4.1.0" + checksum: 10c0/41602dce540e46d599edba9d9860193398d135f7ff72cab629db5171516cfae628d21e7bfccde1bbfdf11c48726bc2a6d1a8fb8701125852fbfda7cf19c6aa83 + languageName: node + linkType: hard + +"smart-buffer@npm:^4.2.0": + version: 4.2.0 + resolution: "smart-buffer@npm:4.2.0" + checksum: 10c0/a16775323e1404dd43fabafe7460be13a471e021637bc7889468eb45ce6a6b207261f454e4e530a19500cc962c4cc5348583520843b363f4193cee5c00e1e539 + languageName: node + linkType: hard + +"socks-proxy-agent@npm:^8.0.3": + version: 8.0.5 + resolution: "socks-proxy-agent@npm:8.0.5" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:^4.3.4" + socks: "npm:^2.8.3" + checksum: 10c0/5d2c6cecba6821389aabf18728325730504bf9bb1d9e342e7987a5d13badd7a98838cc9a55b8ed3cb866ad37cc23e1086f09c4d72d93105ce9dfe76330e9d2a6 + languageName: node + linkType: hard + +"socks@npm:^2.8.3": + version: 2.8.4 + resolution: "socks@npm:2.8.4" + dependencies: + ip-address: "npm:^9.0.5" + smart-buffer: "npm:^4.2.0" + checksum: 10c0/00c3271e233ccf1fb83a3dd2060b94cc37817e0f797a93c560b9a7a86c4a0ec2961fb31263bdd24a3c28945e24868b5f063cd98744171d9e942c513454b50ae5 + languageName: node + linkType: hard + +"source-map-js@npm:^1.2.0, source-map-js@npm:^1.2.1": + version: 1.2.1 + resolution: "source-map-js@npm:1.2.1" + checksum: 10c0/7bda1fc4c197e3c6ff17de1b8b2c20e60af81b63a52cb32ec5a5d67a20a7d42651e2cb34ebe93833c5a2a084377e17455854fee3e21e7925c64a51b6a52b0faf + languageName: node + linkType: hard + +"sprintf-js@npm:^1.1.3": + version: 1.1.3 + resolution: "sprintf-js@npm:1.1.3" + checksum: 10c0/09270dc4f30d479e666aee820eacd9e464215cdff53848b443964202bf4051490538e5dd1b42e1a65cf7296916ca17640aebf63dae9812749c7542ee5f288dec + languageName: node + linkType: hard + +"ssri@npm:^12.0.0": + version: 12.0.0 + resolution: "ssri@npm:12.0.0" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/caddd5f544b2006e88fa6b0124d8d7b28208b83c72d7672d5ade44d794525d23b540f3396108c4eb9280dcb7c01f0bef50682f5b4b2c34291f7c5e211fd1417d + languageName: node + linkType: hard + +"stackback@npm:0.0.2": + version: 0.0.2 + resolution: "stackback@npm:0.0.2" + checksum: 10c0/89a1416668f950236dd5ac9f9a6b2588e1b9b62b1b6ad8dff1bfc5d1a15dbf0aafc9b52d2226d00c28dffff212da464eaeebfc6b7578b9d180cef3e3782c5983 + languageName: node + linkType: hard + +"std-env@npm:^3.8.0": + version: 3.8.1 + resolution: "std-env@npm:3.8.1" + checksum: 10c0/e9b19cca6bc6f06f91607db5b636662914ca8ec9efc525a99da6ec7e493afec109d3b017d21d9782b4369fcfb2891c7c4b4e3c60d495fdadf6861ce434e07bf8 + languageName: node + linkType: hard + +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0": + version: 4.2.3 + resolution: "string-width@npm:4.2.3" + dependencies: + emoji-regex: "npm:^8.0.0" + is-fullwidth-code-point: "npm:^3.0.0" + strip-ansi: "npm:^6.0.1" + checksum: 10c0/1e525e92e5eae0afd7454086eed9c818ee84374bb80328fc41217ae72ff5f065ef1c9d7f72da41de40c75fa8bb3dee63d92373fd492c84260a552c636392a47b + languageName: node + linkType: hard + +"string-width@npm:^5.0.1, string-width@npm:^5.1.2": + version: 5.1.2 + resolution: "string-width@npm:5.1.2" + dependencies: + eastasianwidth: "npm:^0.2.0" + emoji-regex: "npm:^9.2.2" + strip-ansi: "npm:^7.0.1" + checksum: 10c0/ab9c4264443d35b8b923cbdd513a089a60de339216d3b0ed3be3ba57d6880e1a192b70ae17225f764d7adbf5994e9bb8df253a944736c15a0240eff553c678ca + languageName: node + linkType: hard + +"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": + version: 6.0.1 + resolution: "strip-ansi@npm:6.0.1" + dependencies: + ansi-regex: "npm:^5.0.1" + checksum: 10c0/1ae5f212a126fe5b167707f716942490e3933085a5ff6c008ab97ab2f272c8025d3aa218b7bd6ab25729ca20cc81cddb252102f8751e13482a5199e873680952 + languageName: node + linkType: hard + +"strip-ansi@npm:^7.0.1": + version: 7.1.0 + resolution: "strip-ansi@npm:7.1.0" + dependencies: + ansi-regex: "npm:^6.0.1" + checksum: 10c0/a198c3762e8832505328cbf9e8c8381de14a4fa50a4f9b2160138158ea88c0f5549fb50cb13c651c3088f47e63a108b34622ec18c0499b6c8c3a5ddf6b305ac4 + languageName: node + linkType: hard + +"supports-color@npm:^5.3.0": + version: 5.5.0 + resolution: "supports-color@npm:5.5.0" + dependencies: + has-flag: "npm:^3.0.0" + checksum: 10c0/6ae5ff319bfbb021f8a86da8ea1f8db52fac8bd4d499492e30ec17095b58af11f0c55f8577390a749b1c4dde691b6a0315dab78f5f54c9b3d83f8fb5905c1c05 + languageName: node + linkType: hard + +"supports-color@npm:^7.1.0": + version: 7.2.0 + resolution: "supports-color@npm:7.2.0" + dependencies: + has-flag: "npm:^4.0.0" + checksum: 10c0/afb4c88521b8b136b5f5f95160c98dee7243dc79d5432db7efc27efb219385bbc7d9427398e43dd6cc730a0f87d5085ce1652af7efbe391327bc0a7d0f7fc124 + languageName: node + linkType: hard + +"symbol-tree@npm:^3.2.4": + version: 3.2.4 + resolution: "symbol-tree@npm:3.2.4" + checksum: 10c0/dfbe201ae09ac6053d163578778c53aa860a784147ecf95705de0cd23f42c851e1be7889241495e95c37cabb058edb1052f141387bef68f705afc8f9dd358509 + languageName: node + linkType: hard + +"tar@npm:^7.4.3": + version: 7.4.3 + resolution: "tar@npm:7.4.3" + dependencies: + "@isaacs/fs-minipass": "npm:^4.0.0" + chownr: "npm:^3.0.0" + minipass: "npm:^7.1.2" + minizlib: "npm:^3.0.1" + mkdirp: "npm:^3.0.1" + yallist: "npm:^5.0.0" + checksum: 10c0/d4679609bb2a9b48eeaf84632b6d844128d2412b95b6de07d53d8ee8baf4ca0857c9331dfa510390a0727b550fd543d4d1a10995ad86cdf078423fbb8d99831d + languageName: node + linkType: hard + +"test-exclude@npm:^7.0.1": + version: 7.0.1 + resolution: "test-exclude@npm:7.0.1" + dependencies: + "@istanbuljs/schema": "npm:^0.1.2" + glob: "npm:^10.4.1" + minimatch: "npm:^9.0.4" + checksum: 10c0/6d67b9af4336a2e12b26a68c83308c7863534c65f27ed4ff7068a56f5a58f7ac703e8fc80f698a19bb154fd8f705cdf7ec347d9512b2c522c737269507e7b263 + languageName: node + linkType: hard + +"tinybench@npm:^2.9.0": + version: 2.9.0 + resolution: "tinybench@npm:2.9.0" + checksum: 10c0/c3500b0f60d2eb8db65250afe750b66d51623057ee88720b7f064894a6cb7eb93360ca824a60a31ab16dab30c7b1f06efe0795b352e37914a9d4bad86386a20c + languageName: node + linkType: hard + +"tinyexec@npm:^0.3.2": + version: 0.3.2 + resolution: "tinyexec@npm:0.3.2" + checksum: 10c0/3efbf791a911be0bf0821eab37a3445c2ba07acc1522b1fa84ae1e55f10425076f1290f680286345ed919549ad67527d07281f1c19d584df3b74326909eb1f90 + languageName: node + linkType: hard + +"tinypool@npm:^1.0.2": + version: 1.0.2 + resolution: "tinypool@npm:1.0.2" + checksum: 10c0/31ac184c0ff1cf9a074741254fe9ea6de95026749eb2b8ec6fd2b9d8ca94abdccda731f8e102e7f32e72ed3b36d32c6975fd5f5523df3f1b6de6c3d8dfd95e63 + languageName: node + linkType: hard + +"tinyrainbow@npm:^2.0.0": + version: 2.0.0 + resolution: "tinyrainbow@npm:2.0.0" + checksum: 10c0/c83c52bef4e0ae7fb8ec6a722f70b5b6fa8d8be1c85792e829f56c0e1be94ab70b293c032dc5048d4d37cfe678f1f5babb04bdc65fd123098800148ca989184f + languageName: node + linkType: hard + +"tinyspy@npm:^3.0.2": + version: 3.0.2 + resolution: "tinyspy@npm:3.0.2" + checksum: 10c0/55ffad24e346622b59292e097c2ee30a63919d5acb7ceca87fc0d1c223090089890587b426e20054733f97a58f20af2c349fb7cc193697203868ab7ba00bcea0 + languageName: node + linkType: hard + +"tldts-core@npm:^6.1.48": + version: 6.1.48 + resolution: "tldts-core@npm:6.1.48" + checksum: 10c0/3e635ff51848e2f1bf4f325f1e8c627943c8615cf47e5d5301744798ded49df51d72288f27964ea06e9e0c02f05d75c98d5e89fa468663d315cd80b1d66687b1 + languageName: node + linkType: hard + +"tldts@npm:^6.1.32": + version: 6.1.48 + resolution: "tldts@npm:6.1.48" + dependencies: + tldts-core: "npm:^6.1.48" + bin: + tldts: bin/cli.js + checksum: 10c0/eefa0f871df25159faebcb79e0ae2de83f3fd6bd1f0d19ec87a15d69017a8c887a68eacfdf85d84f36b7a3aaf6583bf2337d22edb1406df7d5dc0aaffb2444f7 + languageName: node + linkType: hard + +"to-fast-properties@npm:^2.0.0": + version: 2.0.0 + resolution: "to-fast-properties@npm:2.0.0" + checksum: 10c0/b214d21dbfb4bce3452b6244b336806ffea9c05297148d32ebb428d5c43ce7545bdfc65a1ceb58c9ef4376a65c0cb2854d645f33961658b3e3b4f84910ddcdd7 + languageName: node + linkType: hard + +"tough-cookie@npm:^5.0.0": + version: 5.0.0 + resolution: "tough-cookie@npm:5.0.0" + dependencies: + tldts: "npm:^6.1.32" + checksum: 10c0/4a69c885bf6f45c5a64e60262af99e8c0d58a33bd3d0ce5da62121eeb9c00996d0128a72df8fc4614cbde59cc8b70aa3e21e4c3c98c2bbde137d7aba7fa00124 + languageName: node + linkType: hard + +"tr46@npm:^5.0.0": + version: 5.0.0 + resolution: "tr46@npm:5.0.0" + dependencies: + punycode: "npm:^2.3.1" + checksum: 10c0/1521b6e7bbc8adc825c4561480f9fe48eb2276c81335eed9fa610aa4c44a48a3221f78b10e5f18b875769eb3413e30efbf209ed556a17a42aa8d690df44b7bee + languageName: node + linkType: hard + +"tr46@npm:^5.1.0": + version: 5.1.0 + resolution: "tr46@npm:5.1.0" + dependencies: + punycode: "npm:^2.3.1" + checksum: 10c0/d761f7144e0cb296187674ef245c74f761e334d7cf25ca73ef60e4c72c097c75051031c093fa1a2fee04b904977b316716a7915854bcae8fb1a371746513cbe8 + languageName: node + linkType: hard + +"typescript@npm:^5.8.2": + version: 5.8.2 + resolution: "typescript@npm:5.8.2" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/5c4f6fbf1c6389b6928fe7b8fcd5dc73bb2d58cd4e3883f1d774ed5bd83b151cbac6b7ecf11723de56d4676daeba8713894b1e9af56174f2f9780ae7848ec3c6 + languageName: node + linkType: hard + +"typescript@patch:typescript@npm%3A^5.8.2#optional!builtin": + version: 5.8.2 + resolution: "typescript@patch:typescript@npm%3A5.8.2#optional!builtin::version=5.8.2&hash=5786d5" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/5448a08e595cc558ab321e49d4cac64fb43d1fa106584f6ff9a8d8e592111b373a995a1d5c7f3046211c8a37201eb6d0f1566f15cdb7a62a5e3be01d087848e2 + languageName: node + linkType: hard + +"unique-filename@npm:^4.0.0": + version: 4.0.0 + resolution: "unique-filename@npm:4.0.0" + dependencies: + unique-slug: "npm:^5.0.0" + checksum: 10c0/38ae681cceb1408ea0587b6b01e29b00eee3c84baee1e41fd5c16b9ed443b80fba90c40e0ba69627e30855570a34ba8b06702d4a35035d4b5e198bf5a64c9ddc + languageName: node + linkType: hard + +"unique-slug@npm:^5.0.0": + version: 5.0.0 + resolution: "unique-slug@npm:5.0.0" + dependencies: + imurmurhash: "npm:^0.1.4" + checksum: 10c0/d324c5a44887bd7e105ce800fcf7533d43f29c48757ac410afd42975de82cc38ea2035c0483f4de82d186691bf3208ef35c644f73aa2b1b20b8e651be5afd293 + languageName: node + linkType: hard + +"update-browserslist-db@npm:^1.1.0": + version: 1.1.1 + resolution: "update-browserslist-db@npm:1.1.1" + dependencies: + escalade: "npm:^3.2.0" + picocolors: "npm:^1.1.0" + peerDependencies: + browserslist: ">= 4.21.0" + bin: + update-browserslist-db: cli.js + checksum: 10c0/536a2979adda2b4be81b07e311bd2f3ad5e978690987956bc5f514130ad50cac87cd22c710b686d79731e00fbee8ef43efe5fcd72baa241045209195d43dcc80 + languageName: node + linkType: hard + +"vite-node@npm:3.0.9": + version: 3.0.9 + resolution: "vite-node@npm:3.0.9" + dependencies: + cac: "npm:^6.7.14" + debug: "npm:^4.4.0" + es-module-lexer: "npm:^1.6.0" + pathe: "npm:^2.0.3" + vite: "npm:^5.0.0 || ^6.0.0" + bin: + vite-node: vite-node.mjs + checksum: 10c0/97768a64182832c1ae1797667920fec002d283506b628b684df707fc453c6bf58719029c52c7a4cdf98f5a5a44769036126efdb8192d4040ba3d39f271aa338b + languageName: node + linkType: hard + +"vite@npm:^5.0.0 || ^6.0.0": + version: 6.2.3 + resolution: "vite@npm:6.2.3" + dependencies: + esbuild: "npm:^0.25.0" + fsevents: "npm:~2.3.3" + postcss: "npm:^8.5.3" + rollup: "npm:^4.30.1" + peerDependencies: + "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: ">=1.21.0" + less: "*" + lightningcss: ^1.21.0 + sass: "*" + sass-embedded: "*" + stylus: "*" + sugarss: "*" + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + bin: + vite: bin/vite.js + checksum: 10c0/ba6ad7e83e5a63fb0b6f62d3a3963624b8784bdc1bfa2a83e16cf268fb58c76bd9f8e69f39ed34bf8711cdb8fd7702916f878781da53c232c34ef7a85e0600cf + languageName: node + linkType: hard + +"vitest@npm:^3.0.9": + version: 3.0.9 + resolution: "vitest@npm:3.0.9" + dependencies: + "@vitest/expect": "npm:3.0.9" + "@vitest/mocker": "npm:3.0.9" + "@vitest/pretty-format": "npm:^3.0.9" + "@vitest/runner": "npm:3.0.9" + "@vitest/snapshot": "npm:3.0.9" + "@vitest/spy": "npm:3.0.9" + "@vitest/utils": "npm:3.0.9" + chai: "npm:^5.2.0" + debug: "npm:^4.4.0" + expect-type: "npm:^1.1.0" + magic-string: "npm:^0.30.17" + pathe: "npm:^2.0.3" + std-env: "npm:^3.8.0" + tinybench: "npm:^2.9.0" + tinyexec: "npm:^0.3.2" + tinypool: "npm:^1.0.2" + tinyrainbow: "npm:^2.0.0" + vite: "npm:^5.0.0 || ^6.0.0" + vite-node: "npm:3.0.9" + why-is-node-running: "npm:^2.3.0" + peerDependencies: + "@edge-runtime/vm": "*" + "@types/debug": ^4.1.12 + "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 + "@vitest/browser": 3.0.9 + "@vitest/ui": 3.0.9 + happy-dom: "*" + jsdom: "*" + peerDependenciesMeta: + "@edge-runtime/vm": + optional: true + "@types/debug": + optional: true + "@types/node": + optional: true + "@vitest/browser": + optional: true + "@vitest/ui": + optional: true + happy-dom: + optional: true + jsdom: + optional: true + bin: + vitest: vitest.mjs + checksum: 10c0/5bcd25cab1681f3a968a6483cd5fe115791bc02769bd73bc680bf40153474391a03a6329781b0fb0b8c2f95c82eb342a972bd5132d9bd0d4be92977af19574d0 + languageName: node + linkType: hard + +"w3c-xmlserializer@npm:^5.0.0": + version: 5.0.0 + resolution: "w3c-xmlserializer@npm:5.0.0" + dependencies: + xml-name-validator: "npm:^5.0.0" + checksum: 10c0/8712774c1aeb62dec22928bf1cdfd11426c2c9383a1a63f2bcae18db87ca574165a0fbe96b312b73652149167ac6c7f4cf5409f2eb101d9c805efe0e4bae798b + languageName: node + linkType: hard + +"webidl-conversions@npm:^7.0.0": + version: 7.0.0 + resolution: "webidl-conversions@npm:7.0.0" + checksum: 10c0/228d8cb6d270c23b0720cb2d95c579202db3aaf8f633b4e9dd94ec2000a04e7e6e43b76a94509cdb30479bd00ae253ab2371a2da9f81446cc313f89a4213a2c4 + languageName: node + linkType: hard + +"whatwg-encoding@npm:^3.1.1": + version: 3.1.1 + resolution: "whatwg-encoding@npm:3.1.1" + dependencies: + iconv-lite: "npm:0.6.3" + checksum: 10c0/273b5f441c2f7fda3368a496c3009edbaa5e43b71b09728f90425e7f487e5cef9eb2b846a31bd760dd8077739c26faf6b5ca43a5f24033172b003b72cf61a93e + languageName: node + linkType: hard + +"whatwg-mimetype@npm:^4.0.0": + version: 4.0.0 + resolution: "whatwg-mimetype@npm:4.0.0" + checksum: 10c0/a773cdc8126b514d790bdae7052e8bf242970cebd84af62fb2f35a33411e78e981f6c0ab9ed1fe6ec5071b09d5340ac9178e05b52d35a9c4bcf558ba1b1551df + languageName: node + linkType: hard + +"whatwg-url@npm:^14.0.0": + version: 14.0.0 + resolution: "whatwg-url@npm:14.0.0" + dependencies: + tr46: "npm:^5.0.0" + webidl-conversions: "npm:^7.0.0" + checksum: 10c0/ac32e9ba9d08744605519bbe9e1371174d36229689ecc099157b6ba102d4251a95e81d81f3d80271eb8da182eccfa65653f07f0ab43ea66a6934e643fd091ba9 + languageName: node + linkType: hard + +"whatwg-url@npm:^14.1.0": + version: 14.2.0 + resolution: "whatwg-url@npm:14.2.0" + dependencies: + tr46: "npm:^5.1.0" + webidl-conversions: "npm:^7.0.0" + checksum: 10c0/f746fc2f4c906607d09537de1227b13f9494c171141e5427ed7d2c0dd0b6a48b43d8e71abaae57d368d0c06b673fd8ec63550b32ad5ed64990c7b0266c2b4272 + languageName: node + linkType: hard + +"which@npm:^2.0.1": + version: 2.0.2 + resolution: "which@npm:2.0.2" + dependencies: + isexe: "npm:^2.0.0" + bin: + node-which: ./bin/node-which + checksum: 10c0/66522872a768b60c2a65a57e8ad184e5372f5b6a9ca6d5f033d4b0dc98aff63995655a7503b9c0a2598936f532120e81dd8cc155e2e92ed662a2b9377cc4374f + languageName: node + linkType: hard + +"which@npm:^5.0.0": + version: 5.0.0 + resolution: "which@npm:5.0.0" + dependencies: + isexe: "npm:^3.1.1" + bin: + node-which: bin/which.js + checksum: 10c0/e556e4cd8b7dbf5df52408c9a9dd5ac6518c8c5267c8953f5b0564073c66ed5bf9503b14d876d0e9c7844d4db9725fb0dcf45d6e911e17e26ab363dc3965ae7b + languageName: node + linkType: hard + +"why-is-node-running@npm:^2.3.0": + version: 2.3.0 + resolution: "why-is-node-running@npm:2.3.0" + dependencies: + siginfo: "npm:^2.0.0" + stackback: "npm:0.0.2" + bin: + why-is-node-running: cli.js + checksum: 10c0/1cde0b01b827d2cf4cb11db962f3958b9175d5d9e7ac7361d1a7b0e2dc6069a263e69118bd974c4f6d0a890ef4eedfe34cf3d5167ec14203dbc9a18620537054 + languageName: node + linkType: hard "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + version: 7.0.0 + resolution: "wrap-ansi@npm:7.0.0" dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" + ansi-styles: "npm:^4.0.0" + string-width: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + checksum: 10c0/d15fc12c11e4cbc4044a552129ebc75ee3f57aa9c1958373a4db0292d72282f54373b536103987a4a7594db1ef6a4f10acf92978f79b98c49306a4b58c77d4da + languageName: node + linkType: hard -wrap-ansi@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" - integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== +"wrap-ansi@npm:^8.1.0": + version: 8.1.0 + resolution: "wrap-ansi@npm:8.1.0" dependencies: - ansi-styles "^6.1.0" - string-width "^5.0.1" - strip-ansi "^7.0.1" + ansi-styles: "npm:^6.1.0" + string-width: "npm:^5.0.1" + strip-ansi: "npm:^7.0.1" + checksum: 10c0/138ff58a41d2f877eae87e3282c0630fc2789012fc1af4d6bd626eeb9a2f9a65ca92005e6e69a75c7b85a68479fe7443c7dbe1eb8fbaa681a4491364b7c55c60 + languageName: node + linkType: hard -ws@^8.18.0: - version "8.18.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" - integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== +"ws@npm:^8.18.0": + version: 8.18.0 + resolution: "ws@npm:8.18.0" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 10c0/25eb33aff17edcb90721ed6b0eb250976328533ad3cd1a28a274bd263682e7296a6591ff1436d6cbc50fa67463158b062f9d1122013b361cec99a05f84680e06 + languageName: node + linkType: hard -xml-name-validator@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-5.0.0.tgz#82be9b957f7afdacf961e5980f1bf227c0bf7673" - integrity sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg== +"xml-name-validator@npm:^5.0.0": + version: 5.0.0 + resolution: "xml-name-validator@npm:5.0.0" + checksum: 10c0/3fcf44e7b73fb18be917fdd4ccffff3639373c7cb83f8fc35df6001fecba7942f1dbead29d91ebb8315e2f2ff786b508f0c9dc0215b6353f9983c6b7d62cb1f5 + languageName: node + linkType: hard -xmlchars@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" - integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== +"xmlchars@npm:^2.2.0": + version: 2.2.0 + resolution: "xmlchars@npm:2.2.0" + checksum: 10c0/b64b535861a6f310c5d9bfa10834cf49127c71922c297da9d4d1b45eeaae40bf9b4363275876088fbe2667e5db028d2cd4f8ee72eed9bede840a67d57dab7593 + languageName: node + linkType: hard -yallist@^3.0.2: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== +"yallist@npm:^3.0.2": + version: 3.1.1 + resolution: "yallist@npm:3.1.1" + checksum: 10c0/c66a5c46bc89af1625476f7f0f2ec3653c1a1791d2f9407cfb4c2ba812a1e1c9941416d71ba9719876530e3340a99925f697142989371b72d93b9ee628afd8c1 + languageName: node + linkType: hard + +"yallist@npm:^4.0.0": + version: 4.0.0 + resolution: "yallist@npm:4.0.0" + checksum: 10c0/2286b5e8dbfe22204ab66e2ef5cc9bbb1e55dfc873bbe0d568aa943eb255d131890dfd5bf243637273d31119b870f49c18fcde2c6ffbb7a7a092b870dc90625a + languageName: node + linkType: hard + +"yallist@npm:^5.0.0": + version: 5.0.0 + resolution: "yallist@npm:5.0.0" + checksum: 10c0/a499c81ce6d4a1d260d4ea0f6d49ab4da09681e32c3f0472dee16667ed69d01dae63a3b81745a24bd78476ec4fcf856114cb4896ace738e01da34b2c42235416 + languageName: node + linkType: hard diff --git a/scripts/helmcharts/databases/charts/clickhouse/templates/configmap.yaml b/scripts/helmcharts/databases/charts/clickhouse/templates/configmap.yaml new file mode 100644 index 000000000..41a94dd90 --- /dev/null +++ b/scripts/helmcharts/databases/charts/clickhouse/templates/configmap.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: clickhouse-override +data: + {{- range $filename, $content := .Values.configOverride }} + {{ $filename }}: |- +{{ $content | indent 4 }} + {{- end }} + diff --git a/scripts/helmcharts/databases/charts/clickhouse/templates/statefulset.yaml b/scripts/helmcharts/databases/charts/clickhouse/templates/statefulset.yaml index ebf79cfe2..dedcbb428 100644 --- a/scripts/helmcharts/databases/charts/clickhouse/templates/statefulset.yaml +++ b/scripts/helmcharts/databases/charts/clickhouse/templates/statefulset.yaml @@ -73,6 +73,8 @@ spec: volumeMounts: - name: default-chi-openreplay-clickhouse-replicated-0-0-0 mountPath: /var/lib/clickhouse + - name: clickhouse-override-config + mountPath: /etc/clickhouse-server/config.d resources: {{- toYaml .Values.resources | nindent 12 }} {{- with .Values.nodeSelector }} @@ -87,6 +89,11 @@ spec: tolerations: {{- toYaml . | nindent 8 }} {{- end }} + volumes: + - name: clickhouse-override-config + configMap: + name: clickhouse-override + optional: true volumeClaimTemplates: - metadata: name: default-chi-openreplay-clickhouse-replicated-0-0-0 diff --git a/scripts/helmcharts/databases/charts/clickhouse/values.yaml b/scripts/helmcharts/databases/charts/clickhouse/values.yaml index 0d5c3daad..b5cf37f44 100644 --- a/scripts/helmcharts/databases/charts/clickhouse/values.yaml +++ b/scripts/helmcharts/databases/charts/clickhouse/values.yaml @@ -83,3 +83,28 @@ tolerations: [] affinity: {} storageSize: 100Gi + +configOverride: + zzoverride.xml: |- + + + information + true + + + + 0.0.0.0 + 100 + 64 + 2 + fair_round_robin + 102400000000 + 10000 + 0.8 + + 26214 + + # another-config.xml: |- + # + # value + # diff --git a/scripts/helmcharts/openreplay/charts/assets/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/assets/templates/deployment.yaml index ad45f0e70..825228e88 100644 --- a/scripts/helmcharts/openreplay/charts/assets/templates/deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/assets/templates/deployment.yaml @@ -74,7 +74,7 @@ spec: - name: LICENSE_KEY value: '{{ .Values.global.enterpriseEditionLicense }}' - name: AWS_ENDPOINT - value: '{{ .Values.global.s3.endpoint }}' + value: '{{- include "openreplay.s3Endpoint" . }}' - name: AWS_REGION value: '{{ .Values.global.s3.region }}' - name: KAFKA_SERVERS diff --git a/scripts/helmcharts/openreplay/charts/assist-server/.helmignore b/scripts/helmcharts/openreplay/charts/assist-server/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/scripts/helmcharts/openreplay/charts/assist-server/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/scripts/helmcharts/openreplay/charts/assist-server/Chart.yaml b/scripts/helmcharts/openreplay/charts/assist-server/Chart.yaml new file mode 100644 index 000000000..57014cf36 --- /dev/null +++ b/scripts/helmcharts/openreplay/charts/assist-server/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: assist-server +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful assist-server or functions for the chart developer. They're included as +# a dependency of application charts to inject those assist-server and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.1 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +AppVersion: "v1.22.0" diff --git a/scripts/helmcharts/openreplay/charts/assist-server/templates/NOTES.txt b/scripts/helmcharts/openreplay/charts/assist-server/templates/NOTES.txt new file mode 100644 index 000000000..e23331c6d --- /dev/null +++ b/scripts/helmcharts/openreplay/charts/assist-server/templates/NOTES.txt @@ -0,0 +1,22 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "assist-server.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "assist-server.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "assist-server.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "assist-server.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/scripts/helmcharts/openreplay/charts/assist-server/templates/_helpers.tpl b/scripts/helmcharts/openreplay/charts/assist-server/templates/_helpers.tpl new file mode 100644 index 000000000..1b621eb3d --- /dev/null +++ b/scripts/helmcharts/openreplay/charts/assist-server/templates/_helpers.tpl @@ -0,0 +1,65 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "assist-server.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "assist-server.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "assist-server.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "assist-server.labels" -}} +helm.sh/chart: {{ include "assist-server.chart" . }} +{{ include "assist-server.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- if .Values.global.appLabels }} +{{- .Values.global.appLabels | toYaml | nindent 0}} +{{- end}} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "assist-server.selectorLabels" -}} +app.kubernetes.io/name: {{ include "assist-server.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "assist-server.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "assist-server.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/scripts/helmcharts/openreplay/charts/assist-server/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/assist-server/templates/deployment.yaml new file mode 100644 index 000000000..102a6a8d8 --- /dev/null +++ b/scripts/helmcharts/openreplay/charts/assist-server/templates/deployment.yaml @@ -0,0 +1,113 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "assist-server.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "assist-server.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "assist-server.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "assist-server.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "assist-server.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + shareProcessNamespace: true + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + {{- if .Values.global.enterpriseEditionLicense }} + image: "{{ tpl .Values.image.repository . }}:{{ .Values.image.tag | default .Chart.AppVersion }}-ee" + {{- else }} + image: "{{ tpl .Values.image.repository . }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + {{- end }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- if .Values.healthCheck}} + {{- .Values.healthCheck | toYaml | nindent 10}} + {{- end}} + env: + - name: ASSIST_JWT_SECRET + value: {{ .Values.global.assistJWTSecret }} + - name: ASSIST_KEY + value: {{ .Values.global.assistKey }} + - name: AWS_DEFAULT_REGION + value: "{{ .Values.global.s3.region }}" + - name: S3_HOST + {{- if contains "minio" .Values.global.s3.endpoint }} + value: '{{ ternary "https" "http" .Values.global.ORSecureAccess}}://{{ .Values.global.domainName }}:{{ ternary .Values.global.ingress.controller.service.ports.https .Values.global.ingress.controller.service.ports.http .Values.global.ORSecureAccess }}' + {{- else}} + value: '{{ .Values.global.s3.endpoint }}' + {{- end}} + - name: S3_KEY + {{- if .Values.global.s3.existingSecret }} + valueFrom: + secretKeyRef: + name: {{ .Values.global.s3.existingSecret }} + key: access-key + {{- else }} + value: {{ .Values.global.s3.accessKey }} + {{- end }} + - name: S3_SECRET + {{- if .Values.global.s3.existingSecret }} + valueFrom: + secretKeyRef: + name: {{ .Values.global.s3.existingSecret }} + key: secret-key + {{- else }} + value: {{ .Values.global.s3.secretKey }} + {{- end }} + - name: REDIS_URL + value: {{ .Values.global.redis.redisHost }} + {{- range $key, $val := .Values.global.env }} + - name: {{ $key }} + value: '{{ $val }}' + {{- end }} + {{- range $key, $val := .Values.env }} + - name: {{ $key }} + value: '{{ $val }}' + {{- end}} + ports: + {{- range $key, $val := .Values.service.ports }} + - name: {{ $key }} + containerPort: {{ $val }} + {{- end }} + protocol: TCP + {{- with .Values.persistence.mounts }} + volumeMounts: + {{- toYaml . | nindent 12 }} + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.persistence.volumes }} + volumes: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/scripts/helmcharts/openreplay/charts/assist-server/templates/hpa.yaml b/scripts/helmcharts/openreplay/charts/assist-server/templates/hpa.yaml new file mode 100644 index 000000000..80f270bc4 --- /dev/null +++ b/scripts/helmcharts/openreplay/charts/assist-server/templates/hpa.yaml @@ -0,0 +1,33 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "assist-server.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "assist-server.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "assist-server.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/scripts/helmcharts/openreplay/charts/assist-server/templates/ingress.yaml b/scripts/helmcharts/openreplay/charts/assist-server/templates/ingress.yaml new file mode 100644 index 000000000..7811676c1 --- /dev/null +++ b/scripts/helmcharts/openreplay/charts/assist-server/templates/ingress.yaml @@ -0,0 +1,56 @@ +{{- if .Values.ingress.enabled }} +{{- $fullName := include "assist-server.fullname" . -}} +{{- $socketioSvcPort := .Values.service.ports.socketio -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $fullName }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "assist-server.labels" . | nindent 4 }} + annotations: + nginx.ingress.kubernetes.io/rewrite-target: /$1 + nginx.ingress.kubernetes.io/configuration-snippet: | + #set $sticky_used "no"; + #if ($sessionid != "") { + # set $sticky_used "yes"; + #} + + #add_header X-Debug-Session-ID $sessionid; + #add_header X-Debug-Session-Type "wss"; + #add_header X-Sticky-Session-Used $sticky_used; + #add_header X-Upstream-Server $upstream_addr; + + proxy_hide_header access-control-allow-headers; + proxy_hide_header Access-Control-Allow-Origin; + add_header 'Access-Control-Allow-Origin' $http_origin always; + add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS' always; + add_header 'Access-Control-Allow-Headers' 'sessionid, Content-Type, Authorization' always; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Type' 'text/plain charset=UTF-8'; + + nginx.ingress.kubernetes.io/upstream-hash-by: $sessionid + + {{- with .Values.ingress.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + ingressClassName: "{{ tpl .Values.ingress.className . }}" + tls: + - hosts: + - {{ .Values.global.domainName }} + {{- if .Values.ingress.tls.secretName}} + secretName: {{ .Values.ingress.tls.secretName }} + {{- end}} + rules: + - host: {{ .Values.global.domainName }} + http: + paths: + - pathType: Prefix + backend: + service: + name: {{ $fullName }} + port: + number: {{ $socketioSvcPort }} + path: /ws-assist-server/(.*) +{{- end }} diff --git a/scripts/helmcharts/openreplay/charts/assist-server/templates/service.yaml b/scripts/helmcharts/openreplay/charts/assist-server/templates/service.yaml new file mode 100644 index 000000000..cf23f4676 --- /dev/null +++ b/scripts/helmcharts/openreplay/charts/assist-server/templates/service.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "assist-server.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "assist-server.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + {{- range $key, $val := .Values.service.ports }} + - port: {{ $val }} + targetPort: {{ $key }} + protocol: TCP + name: {{ $key }} + {{- end}} + selector: + {{- include "assist-server.selectorLabels" . | nindent 4 }} diff --git a/scripts/helmcharts/openreplay/charts/assist-server/templates/serviceMonitor.yaml b/scripts/helmcharts/openreplay/charts/assist-server/templates/serviceMonitor.yaml new file mode 100644 index 000000000..6fa157aff --- /dev/null +++ b/scripts/helmcharts/openreplay/charts/assist-server/templates/serviceMonitor.yaml @@ -0,0 +1,18 @@ +{{- if and ( .Capabilities.APIVersions.Has "monitoring.coreos.com/v1" ) ( .Values.serviceMonitor.enabled ) }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "assist-server.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "assist-server.labels" . | nindent 4 }} + {{- if .Values.serviceMonitor.additionalLabels }} + {{- toYaml .Values.serviceMonitor.additionalLabels | nindent 4 }} + {{- end }} +spec: + endpoints: + {{- .Values.serviceMonitor.scrapeConfigs | toYaml | nindent 4 }} + selector: + matchLabels: + {{- include "assist-server.selectorLabels" . | nindent 6 }} +{{- end }} diff --git a/scripts/helmcharts/openreplay/charts/assist-server/templates/serviceaccount.yaml b/scripts/helmcharts/openreplay/charts/assist-server/templates/serviceaccount.yaml new file mode 100644 index 000000000..eb5d75858 --- /dev/null +++ b/scripts/helmcharts/openreplay/charts/assist-server/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "assist-server.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "assist-server.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/scripts/helmcharts/openreplay/charts/assist-server/values.yaml b/scripts/helmcharts/openreplay/charts/assist-server/values.yaml new file mode 100644 index 000000000..a2db60b69 --- /dev/null +++ b/scripts/helmcharts/openreplay/charts/assist-server/values.yaml @@ -0,0 +1,134 @@ +# Default values for openreplay. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + repository: "{{ .Values.global.openReplayContainerRegistry }}/assist-server" + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "" + +imagePullSecrets: [] +nameOverride: "assist-server" +fullnameOverride: "assist-server-openreplay" + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +podAnnotations: {} + +securityContext: + runAsUser: 1001 + runAsGroup: 1001 +podSecurityContext: + runAsUser: 1001 + runAsGroup: 1001 + fsGroup: 1001 + fsGroupChangePolicy: "OnRootMismatch" +# podSecurityContext: {} + # fsGroup: 2000 + +# securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +#service: +# type: ClusterIP +# port: 9000 + +serviceMonitor: + enabled: false + additionalLabels: + release: observability + scrapeConfigs: + - port: metrics + honorLabels: true + interval: 15s + path: /metrics + scheme: http + scrapeTimeout: 10s + +service: + type: ClusterIP + ports: + socketio: 9001 + metrics: 8888 + +ingress: + enabled: true + className: "{{ .Values.global.ingress.controller.ingressClassResource.name }}" + annotations: + nginx.ingress.kubernetes.io/configuration-snippet: | + add_header X-Debug-Session-ID $http_sessionid; + add_header X-Debug-Session-Type "wss"; + + # CORS configuration + # We don't need the upstream header + proxy_hide_header Access-Control-Allow-Origin; + add_header 'Access-Control-Allow-Origin' $http_origin always; + add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS' always; + add_header 'Access-Control-Allow-Headers' 'sessionid, Content-Type, Authorization' always; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Type' 'text/plain charset=UTF-8'; + nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" + nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + tls: + secretName: openreplay-ssl + +resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 5 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +env: + debug: 0 + uws: false + redis: false + CLEAR_SOCKET_TIME: 720 + + +nodeSelector: {} + +tolerations: [] + +affinity: {} + + +persistence: {} + # # Spec of spec.template.spec.containers[*].volumeMounts + # mounts: + # - name: kafka-ssl + # mountPath: /opt/kafka/ssl + # # Spec of spec.template.spec.volumes + # volumes: + # - name: kafka-ssl + # secret: + # secretName: kafka-ssl diff --git a/scripts/helmcharts/openreplay/charts/assist/templates/ingress.yaml b/scripts/helmcharts/openreplay/charts/assist/templates/ingress.yaml index 60bdcbe38..41436c679 100644 --- a/scripts/helmcharts/openreplay/charts/assist/templates/ingress.yaml +++ b/scripts/helmcharts/openreplay/charts/assist/templates/ingress.yaml @@ -10,7 +10,27 @@ metadata: {{- include "assist.labels" . | nindent 4 }} annotations: nginx.ingress.kubernetes.io/rewrite-target: /$1 - nginx.ingress.kubernetes.io/upstream-hash-by: $http_x_forwarded_for + nginx.ingress.kubernetes.io/configuration-snippet: | + #set $sticky_used "no"; + #if ($sessionid != "") { + # set $sticky_used "yes"; + #} + + #add_header X-Debug-Session-ID $sessionid; + #add_header X-Debug-Session-Type "wss"; + #add_header X-Sticky-Session-Used $sticky_used; + #add_header X-Upstream-Server $upstream_addr; + + proxy_hide_header access-control-allow-headers; + proxy_hide_header Access-Control-Allow-Origin; + add_header 'Access-Control-Allow-Origin' $http_origin always; + add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS' always; + add_header 'Access-Control-Allow-Headers' 'sessionid, Content-Type, Authorization' always; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Type' 'text/plain charset=UTF-8'; + + nginx.ingress.kubernetes.io/upstream-hash-by: $sessionid + {{- with .Values.ingress.annotations }} {{- toYaml . | nindent 4 }} {{- end }} diff --git a/scripts/helmcharts/openreplay/charts/assist/values.yaml b/scripts/helmcharts/openreplay/charts/assist/values.yaml index bf2149df5..a3a14eff5 100644 --- a/scripts/helmcharts/openreplay/charts/assist/values.yaml +++ b/scripts/helmcharts/openreplay/charts/assist/values.yaml @@ -70,6 +70,18 @@ ingress: enabled: true className: "{{ .Values.global.ingress.controller.ingressClassResource.name }}" annotations: + nginx.ingress.kubernetes.io/configuration-snippet: | + add_header X-Debug-Session-ID $http_sessionid; + add_header X-Debug-Session-Type "wss"; + + # CORS configuration + # We don't need the upstream header + proxy_hide_header Access-Control-Allow-Origin; + add_header 'Access-Control-Allow-Origin' $http_origin always; + add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS' always; + add_header 'Access-Control-Allow-Headers' 'sessionid, Content-Type, Authorization' always; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Type' 'text/plain charset=UTF-8'; nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" # kubernetes.io/ingress.class: nginx diff --git a/scripts/helmcharts/openreplay/charts/canvases/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/canvases/templates/deployment.yaml index 428d58897..f160e50a9 100644 --- a/scripts/helmcharts/openreplay/charts/canvases/templates/deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/canvases/templates/deployment.yaml @@ -62,7 +62,7 @@ spec: value: {{ .Values.global.s3.secretKey }} {{- end }} - name: AWS_ENDPOINT - value: '{{ .Values.global.s3.endpoint }}' + value: '{{- include "openreplay.s3Endpoint" . }}' - name: AWS_REGION value: '{{ .Values.global.s3.region }}' - name: BUCKET_NAME diff --git a/scripts/helmcharts/openreplay/charts/connector/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/connector/templates/deployment.yaml index d46405502..065ce1f09 100644 --- a/scripts/helmcharts/openreplay/charts/connector/templates/deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/connector/templates/deployment.yaml @@ -83,7 +83,7 @@ spec: value: {{ .Values.global.s3.secretKey }} {{- end }} - name: AWS_ENDPOINT - value: '{{ .Values.global.s3.endpoint }}' + value: '{{- include "openreplay.s3Endpoint" . }}' - name: AWS_REGION value: '{{ .Values.global.s3.region }}' {{- include "openreplay.env.redis_string" .Values.global.redis | nindent 12 }} diff --git a/scripts/helmcharts/openreplay/charts/frontend/Chart.yaml b/scripts/helmcharts/openreplay/charts/frontend/Chart.yaml index 0e43c4a38..d009221ed 100644 --- a/scripts/helmcharts/openreplay/charts/frontend/Chart.yaml +++ b/scripts/helmcharts/openreplay/charts/frontend/Chart.yaml @@ -18,4 +18,4 @@ version: 0.1.10 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -AppVersion: "v1.22.15" +AppVersion: "v1.22.1" diff --git a/scripts/helmcharts/openreplay/charts/images/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/images/templates/deployment.yaml index 2a93d495d..38f4f24c4 100644 --- a/scripts/helmcharts/openreplay/charts/images/templates/deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/images/templates/deployment.yaml @@ -62,7 +62,7 @@ spec: value: {{ .Values.global.s3.secretKey }} {{- end }} - name: AWS_ENDPOINT - value: '{{ .Values.global.s3.endpoint }}' + value: '{{- include "openreplay.s3Endpoint" . }}' - name: AWS_REGION value: '{{ .Values.global.s3.region }}' - name: BUCKET_NAME diff --git a/scripts/helmcharts/openreplay/charts/storage/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/storage/templates/deployment.yaml index bc5ea5a9c..8868c77fb 100644 --- a/scripts/helmcharts/openreplay/charts/storage/templates/deployment.yaml +++ b/scripts/helmcharts/openreplay/charts/storage/templates/deployment.yaml @@ -62,7 +62,7 @@ spec: value: {{ .Values.global.s3.secretKey }} {{- end }} - name: AWS_ENDPOINT - value: '{{ .Values.global.s3.endpoint }}' + value: '{{- include "openreplay.s3Endpoint" . }}' - name: AWS_REGION value: '{{ .Values.global.s3.region }}' - name: BUCKET_NAME diff --git a/scripts/helmcharts/openreplay/templates/_helpers.tpl b/scripts/helmcharts/openreplay/templates/_helpers.tpl index be8ef934e..de12340a9 100644 --- a/scripts/helmcharts/openreplay/templates/_helpers.tpl +++ b/scripts/helmcharts/openreplay/templates/_helpers.tpl @@ -33,10 +33,13 @@ ingress-nginx: &ingress-nginx {{- if contains "minio" .Values.global.s3.endpoint -}} {{- include "openreplay.domainURL" . -}} {{- else -}} - {{- .Values.global.s3.endpoint -}} + {{- .Values.global.s3.endpoint -}} {{- end -}} -{{- else -}} +{{/* Endpoint wil be empty if used with aws iam roles*/}} +{{- else if and .Values.global.s3.accessKey .Values.global.s3.secretKey -}} {{- printf "https://s3.%s.amazonaws.com" .Values.global.s3.region -}} +{{- else -}} + {{- .Values.global.s3.endpoint -}} {{- end -}} {{- end -}} diff --git a/scripts/helmcharts/openreplay/templates/job.yaml b/scripts/helmcharts/openreplay/templates/job.yaml index 1cf314514..e4d5463a8 100644 --- a/scripts/helmcharts/openreplay/templates/job.yaml +++ b/scripts/helmcharts/openreplay/templates/job.yaml @@ -404,7 +404,7 @@ spec: - name: AWS_DEFAULT_REGION value: "{{ .Values.global.s3.region }}" - name: AWS_ENDPOINT - value: "{{ .Values.global.s3.endpoint }}" + value: "{{- include "openreplay.s3Endpoint" . }}" - name: VAULT_BUCKET value: "{{ .Values.global.s3.vaultBucket }}" image: amazon/aws-cli diff --git a/scripts/helmcharts/openreplay/values.yaml b/scripts/helmcharts/openreplay/values.yaml index 9d90dfef2..8be97a2dc 100644 --- a/scripts/helmcharts/openreplay/values.yaml +++ b/scripts/helmcharts/openreplay/values.yaml @@ -17,6 +17,13 @@ redis: &redis ingress-nginx: enabled: true controller: + config: + http-snippet: |- + # Extract sessionid from peerId, it'll be used for sticky session. + map $arg_peerId $sessionid { + default ""; + "~.*-(\d+)(?:-.*|$)" $1; + } admissionWebhooks: patch: podAnnotations: diff --git a/scripts/schema/db/init_dbs/clickhouse/1.23.0/1.23.0.sql b/scripts/schema/db/init_dbs/clickhouse/1.23.0/1.23.0.sql new file mode 100644 index 000000000..761e2ca76 --- /dev/null +++ b/scripts/schema/db/init_dbs/clickhouse/1.23.0/1.23.0.sql @@ -0,0 +1,179 @@ +CREATE OR REPLACE FUNCTION openreplay_version AS() -> 'v1.23.0'; + + +CREATE TABLE IF NOT EXISTS experimental.user_viewed_sessions +( + project_id UInt16, + user_id UInt32, + session_id UInt64, + _timestamp DateTime DEFAULT now() +) ENGINE = ReplacingMergeTree(_timestamp) + PARTITION BY toYYYYMM(_timestamp) + ORDER BY (project_id, user_id, session_id) + TTL _timestamp + INTERVAL 3 MONTH; + +DROP TABLE IF EXISTS product_analytics.all_events; +CREATE TABLE IF NOT EXISTS product_analytics.all_events +( + project_id UInt16, + auto_captured BOOL DEFAULT FALSE, + event_name String, + display_name String DEFAULT '', + description String DEFAULT '', + event_count_l30days UInt32 DEFAULT 0, + query_count_l30days UInt32 DEFAULT 0, + + created_at DateTime64, + _timestamp DateTime DEFAULT now() +) ENGINE = ReplacingMergeTree(_timestamp) + ORDER BY (project_id, auto_captured, event_name); + +CREATE MATERIALIZED VIEW IF NOT EXISTS product_analytics.all_events_extractor_mv + TO product_analytics.all_events AS +SELECT DISTINCT ON (project_id,auto_captured,event_name) project_id, + `$auto_captured` AS auto_captured, + `$event_name` AS event_name, + display_name, + description +FROM product_analytics.events + LEFT JOIN (SELECT project_id, + auto_captured, + event_name, + display_name, + description + FROM product_analytics.all_events + WHERE all_events.display_name != '' + OR all_events.description != '') AS old_data + ON (events.project_id = old_data.project_id AND events.`$auto_captured` = old_data.auto_captured AND + events.`$event_name` = old_data.event_name); + +CREATE TABLE IF NOT EXISTS product_analytics.event_properties +( + project_id UInt16, + event_name String, + property_name String, + value_type String, + + _timestamp DateTime DEFAULT now() +) ENGINE = ReplacingMergeTree(_timestamp) + ORDER BY (project_id, event_name, property_name, value_type); + +CREATE MATERIALIZED VIEW IF NOT EXISTS product_analytics.event_properties_extractor_mv + TO product_analytics.event_properties AS +SELECT project_id, + `$event_name` AS event_name, + property_name, + JSONType(JSONExtractRaw(toString(`$properties`), property_name)) AS value_type +FROM product_analytics.events + ARRAY JOIN JSONExtractKeys(toString(`$properties`)) as property_name; + +CREATE MATERIALIZED VIEW IF NOT EXISTS product_analytics.event_cproperties_extractor + TO product_analytics.event_properties AS +SELECT project_id, + `$event_name` AS event_name, + property_name, + JSONType(JSONExtractRaw(toString(`properties`), property_name)) AS value_type +FROM product_analytics.events + ARRAY JOIN JSONExtractKeys(toString(`properties`)) as property_name; + +DROP TABLE IF EXISTS product_analytics.all_properties; +CREATE TABLE IF NOT EXISTS product_analytics.all_properties +( + project_id UInt16, + property_name String, + is_event_property BOOL, + display_name String DEFAULT '', + description String DEFAULT '', + status String DEFAULT 'visible' COMMENT 'visible/hidden/dropped', + data_count UInt32 DEFAULT 1, + query_count UInt32 DEFAULT 0, + + created_at DateTime64, + _timestamp DateTime DEFAULT now() +) ENGINE = ReplacingMergeTree(_timestamp) + ORDER BY (project_id, property_name, is_event_property); + + +CREATE MATERIALIZED VIEW IF NOT EXISTS product_analytics.all_properties_extractor_mv + TO product_analytics.all_properties AS +SELECT project_id, + property_name, + TRUE AS is_event_property, + display_name, + description, + status, + data_count, + query_count +FROM product_analytics.events + ARRAY JOIN JSONExtractKeys(toString(`$properties`)) as property_name + LEFT JOIN (SELECT project_id, + property_name, + display_name, + description, + status, + data_count, + query_count + FROM product_analytics.all_properties + WHERE (all_properties.display_name != '' + OR all_properties.description != '') + AND is_event_property) AS old_data + ON (events.project_id = old_data.project_id AND property_name = old_data.property_name); + +CREATE MATERIALIZED VIEW IF NOT EXISTS product_analytics.all_cproperties_extractor_mv + TO product_analytics.all_properties AS +SELECT project_id, + property_name, + TRUE AS is_event_property, + display_name, + description, + status, + data_count, + query_count +FROM product_analytics.events + ARRAY JOIN JSONExtractKeys(toString(`properties`)) as property_name + LEFT JOIN (SELECT project_id, + property_name, + display_name, + description, + status, + data_count, + query_count + FROM product_analytics.all_properties + WHERE (all_properties.display_name != '' + OR all_properties.description != '') + AND is_event_property) AS old_data + ON (events.project_id = old_data.project_id AND property_name = old_data.property_name); + +CREATE TABLE IF NOT EXISTS product_analytics.property_values_samples +( + project_id UInt16, + property_name String, + is_event_property BOOL, + value String, + + _timestamp DateTime DEFAULT now() +) + ENGINE = ReplacingMergeTree(_timestamp) + ORDER BY (project_id, property_name, is_event_property); + +CREATE MATERIALIZED VIEW IF NOT EXISTS product_analytics.property_values_sampler_mv + REFRESH EVERY 30 HOUR TO product_analytics.property_values_samples AS +SELECT project_id, + property_name, + TRUE AS is_event_property, + JSONExtractString(toString(`$properties`), property_name) AS value +FROM product_analytics.events + ARRAY JOIN JSONExtractKeys(toString(`$properties`)) as property_name +WHERE randCanonical() < 0.5 -- This randomly skips inserts + AND value != '' +LIMIT 2 BY project_id,property_name +UNION ALL +SELECT project_id, + property_name, + TRUE AS is_event_property, + JSONExtractString(toString(`properties`), property_name) AS value +FROM product_analytics.events + ARRAY JOIN JSONExtractKeys(toString(`properties`)) as property_name +WHERE randCanonical() < 0.5 -- This randomly skips inserts + AND value != '' +LIMIT 2 BY project_id,property_name; diff --git a/scripts/schema/db/init_dbs/clickhouse/create/init_schema.sql b/scripts/schema/db/init_dbs/clickhouse/create/init_schema.sql index 150b7661e..8a0db7215 100644 --- a/scripts/schema/db/init_dbs/clickhouse/create/init_schema.sql +++ b/scripts/schema/db/init_dbs/clickhouse/create/init_schema.sql @@ -1,4 +1,4 @@ -CREATE OR REPLACE FUNCTION openreplay_version AS() -> 'v1.22.0'; +CREATE OR REPLACE FUNCTION openreplay_version AS() -> 'v1.23.0'; CREATE DATABASE IF NOT EXISTS experimental; CREATE TABLE IF NOT EXISTS experimental.autocomplete @@ -143,6 +143,40 @@ CREATE TABLE IF NOT EXISTS experimental.sessions TTL datetime + INTERVAL 1 MONTH SETTINGS index_granularity = 512; +CREATE TABLE IF NOT EXISTS experimental.user_favorite_sessions +( + project_id UInt16, + user_id UInt32, + session_id UInt64, + _timestamp DateTime DEFAULT now(), + sign Int8 +) ENGINE = CollapsingMergeTree(sign) + PARTITION BY toYYYYMM(_timestamp) + ORDER BY (project_id, user_id, session_id) + TTL _timestamp + INTERVAL 3 MONTH; + +CREATE TABLE IF NOT EXISTS experimental.user_viewed_sessions +( + project_id UInt16, + user_id UInt32, + session_id UInt64, + _timestamp DateTime DEFAULT now() +) ENGINE = ReplacingMergeTree(_timestamp) + PARTITION BY toYYYYMM(_timestamp) + ORDER BY (project_id, user_id, session_id) + TTL _timestamp + INTERVAL 3 MONTH; + +CREATE TABLE IF NOT EXISTS experimental.user_viewed_errors +( + project_id UInt16, + user_id UInt32, + error_id String, + _timestamp DateTime DEFAULT now() +) ENGINE = ReplacingMergeTree(_timestamp) + PARTITION BY toYYYYMM(_timestamp) + ORDER BY (project_id, user_id, error_id) + TTL _timestamp + INTERVAL 3 MONTH; + CREATE TABLE IF NOT EXISTS experimental.issues ( project_id UInt16, @@ -501,9 +535,11 @@ CREATE TABLE IF NOT EXISTS product_analytics.group_properties -- The full list of events +-- Experimental: This table is filled by an incremental materialized view CREATE TABLE IF NOT EXISTS product_analytics.all_events ( project_id UInt16, + auto_captured BOOL DEFAULT FALSE, event_name String, display_name String DEFAULT '', description String DEFAULT '', @@ -513,10 +549,68 @@ CREATE TABLE IF NOT EXISTS product_analytics.all_events created_at DateTime64, _timestamp DateTime DEFAULT now() ) ENGINE = ReplacingMergeTree(_timestamp) - ORDER BY (project_id, event_name); + ORDER BY (project_id, auto_captured, event_name); + +-- ----------------- This is experimental, if it doesn't work, we need to do it in db worker ------------- +-- Incremental materialized view to fill all_events using $properties +CREATE MATERIALIZED VIEW IF NOT EXISTS product_analytics.all_events_extractor_mv + TO product_analytics.all_events AS +SELECT DISTINCT ON (project_id,auto_captured,event_name) project_id, + `$auto_captured` AS auto_captured, + `$event_name` AS event_name, + display_name, + description +FROM product_analytics.events + LEFT JOIN (SELECT project_id, + auto_captured, + event_name, + display_name, + description + FROM product_analytics.all_events + WHERE all_events.display_name != '' + OR all_events.description != '') AS old_data + ON (events.project_id = old_data.project_id AND events.`$auto_captured` = old_data.auto_captured AND + events.`$event_name` = old_data.event_name); +-- -------- END --------- + +-- The full list of event-properties (used to tell which property belongs to which event) +-- Experimental: This table is filled by an incremental materialized view +CREATE TABLE IF NOT EXISTS product_analytics.event_properties +( + project_id UInt16, + event_name String, + property_name String, + value_type String, + + _timestamp DateTime DEFAULT now() +) ENGINE = ReplacingMergeTree(_timestamp) + ORDER BY (project_id, event_name, property_name, value_type); + +-- ----------------- This is experimental, if it doesn't work, we need to do it in db worker ------------- +-- Incremental materialized view to fill event_properties using $properties +CREATE MATERIALIZED VIEW IF NOT EXISTS product_analytics.event_properties_extractor_mv + TO product_analytics.event_properties AS +SELECT project_id, + `$event_name` AS event_name, + property_name, + JSONType(JSONExtractRaw(toString(`$properties`), property_name)) AS value_type +FROM product_analytics.events + ARRAY JOIN JSONExtractKeys(toString(`$properties`)) as property_name; + +-- Incremental materialized view to fill event_properties using properties +CREATE MATERIALIZED VIEW IF NOT EXISTS product_analytics.event_cproperties_extractor + TO product_analytics.event_properties AS +SELECT project_id, + `$event_name` AS event_name, + property_name, + JSONType(JSONExtractRaw(toString(`properties`), property_name)) AS value_type +FROM product_analytics.events + ARRAY JOIN JSONExtractKeys(toString(`properties`)) as property_name; +-- -------- END --------- -- The full list of properties (events and users) +-- Experimental: This table is filled by an incremental materialized view CREATE TABLE IF NOT EXISTS product_analytics.all_properties ( project_id UInt16, @@ -532,3 +626,95 @@ CREATE TABLE IF NOT EXISTS product_analytics.all_properties _timestamp DateTime DEFAULT now() ) ENGINE = ReplacingMergeTree(_timestamp) ORDER BY (project_id, property_name, is_event_property); + + +-- ----------------- This is experimental, if it doesn't work, we need to do it in db worker ------------- +-- Incremental materialized view to fill all_properties using $properties +CREATE MATERIALIZED VIEW IF NOT EXISTS product_analytics.all_properties_extractor_mv + TO product_analytics.all_properties AS +SELECT project_id, + property_name, + TRUE AS is_event_property, + display_name, + description, + status, + data_count, + query_count +FROM product_analytics.events + ARRAY JOIN JSONExtractKeys(toString(`$properties`)) as property_name + LEFT JOIN (SELECT project_id, + property_name, + display_name, + description, + status, + data_count, + query_count + FROM product_analytics.all_properties + WHERE (all_properties.display_name != '' + OR all_properties.description != '') + AND is_event_property) AS old_data + ON (events.project_id = old_data.project_id AND property_name = old_data.property_name); + +-- Incremental materialized view to fill all_properties using properties +CREATE MATERIALIZED VIEW IF NOT EXISTS product_analytics.all_cproperties_extractor_mv + TO product_analytics.all_properties AS +SELECT project_id, + property_name, + TRUE AS is_event_property, + display_name, + description, + status, + data_count, + query_count +FROM product_analytics.events + ARRAY JOIN JSONExtractKeys(toString(`properties`)) as property_name + LEFT JOIN (SELECT project_id, + property_name, + display_name, + description, + status, + data_count, + query_count + FROM product_analytics.all_properties + WHERE (all_properties.display_name != '' + OR all_properties.description != '') + AND is_event_property) AS old_data + ON (events.project_id = old_data.project_id AND property_name = old_data.property_name); +-- -------- END --------- + +-- Some random examples of property-values, limited by 2 per property +-- Experimental: This table is filled by a refreshable materialized view +CREATE TABLE IF NOT EXISTS product_analytics.property_values_samples +( + project_id UInt16, + property_name String, + is_event_property BOOL, + value String, + + _timestamp DateTime DEFAULT now() +) + ENGINE = ReplacingMergeTree(_timestamp) + ORDER BY (project_id, property_name, is_event_property); +-- Incremental materialized view to get random examples of property values using $properties & properties +CREATE MATERIALIZED VIEW IF NOT EXISTS product_analytics.property_values_sampler_mv + REFRESH EVERY 30 HOUR TO product_analytics.property_values_samples AS +SELECT project_id, + property_name, + TRUE AS is_event_property, + JSONExtractString(toString(`$properties`), property_name) AS value +FROM product_analytics.events + ARRAY JOIN JSONExtractKeys(toString(`$properties`)) as property_name +WHERE randCanonical() < 0.5 -- This randomly skips inserts + AND value != '' +LIMIT 2 BY project_id,property_name +UNION ALL +-- using union because each table should be the target of 1 single refreshable MV +SELECT project_id, + property_name, + TRUE AS is_event_property, + JSONExtractString(toString(`properties`), property_name) AS value +FROM product_analytics.events + ARRAY JOIN JSONExtractKeys(toString(`properties`)) as property_name +WHERE randCanonical() < 0.5 -- This randomly skips inserts + AND value != '' +LIMIT 2 BY project_id,property_name; diff --git a/scripts/schema/db/init_dbs/postgresql/1.23.0/1.23.0.sql b/scripts/schema/db/init_dbs/postgresql/1.23.0/1.23.0.sql new file mode 100644 index 000000000..541f5f808 --- /dev/null +++ b/scripts/schema/db/init_dbs/postgresql/1.23.0/1.23.0.sql @@ -0,0 +1,30 @@ +\set previous_version 'v1.22.0' +\set next_version 'v1.23.0' +SELECT openreplay_version() AS current_version, + openreplay_version() = :'previous_version' AS valid_previous, + openreplay_version() = :'next_version' AS is_next +\gset + +\if :valid_previous +\echo valid previous DB version :'previous_version', starting DB upgrade to :'next_version' +BEGIN; +SELECT format($fn_def$ +CREATE OR REPLACE FUNCTION openreplay_version() + RETURNS text AS +$$ +SELECT '%1$s' +$$ LANGUAGE sql IMMUTABLE; +$fn_def$, :'next_version') +\gexec + +-- + + + +COMMIT; + +\elif :is_next +\echo new version detected :'next_version', nothing to do +\else +\warn skipping DB upgrade of :'next_version', expected previous version :'previous_version', found :'current_version' +\endif diff --git a/scripts/schema/db/init_dbs/postgresql/init_schema.sql b/scripts/schema/db/init_dbs/postgresql/init_schema.sql index b10d2cc3d..2a5a79473 100644 --- a/scripts/schema/db/init_dbs/postgresql/init_schema.sql +++ b/scripts/schema/db/init_dbs/postgresql/init_schema.sql @@ -1,4 +1,4 @@ -\set or_version 'v1.22.0' +\set or_version 'v1.23.0' SET client_min_messages TO NOTICE; \set ON_ERROR_STOP true SELECT EXISTS (SELECT 1 diff --git a/scripts/schema/db/rollback_dbs/clickhouse/1.23.0/1.23.0.sql b/scripts/schema/db/rollback_dbs/clickhouse/1.23.0/1.23.0.sql new file mode 100644 index 000000000..36def25d2 --- /dev/null +++ b/scripts/schema/db/rollback_dbs/clickhouse/1.23.0/1.23.0.sql @@ -0,0 +1,6 @@ +CREATE OR REPLACE FUNCTION openreplay_version AS() -> 'v1.22.0'; + + +DROP TABLE IF EXISTS experimental.user_viewed_sessions; + +DROP TABLE IF EXISTS product_analytics.event_properties; \ No newline at end of file diff --git a/scripts/schema/db/rollback_dbs/postgresql/1.23.0/1.23.0.sql b/scripts/schema/db/rollback_dbs/postgresql/1.23.0/1.23.0.sql new file mode 100644 index 000000000..481eb3200 --- /dev/null +++ b/scripts/schema/db/rollback_dbs/postgresql/1.23.0/1.23.0.sql @@ -0,0 +1,27 @@ +\set previous_version 'v1.23.0' +\set next_version 'v1.22.0' +SELECT openreplay_version() AS current_version, + openreplay_version() = :'previous_version' AS valid_previous, + openreplay_version() = :'next_version' AS is_next +\gset + +\if :valid_previous +\echo valid previous DB version :'previous_version', starting DB downgrade to :'next_version' +BEGIN; +SELECT format($fn_def$ +CREATE OR REPLACE FUNCTION openreplay_version() + RETURNS text AS +$$ +SELECT '%1$s' +$$ LANGUAGE sql IMMUTABLE; +$fn_def$, :'next_version') +\gexec + + +COMMIT; + +\elif :is_next +\echo new version detected :'next_version', nothing to do +\else +\warn skipping DB downgrade of :'next_version', expected previous version :'previous_version', found :'current_version' +\endif \ No newline at end of file diff --git a/spot/bun.lockb b/spot/bun.lockb index f8ea1b368..58ef0acb1 100755 Binary files a/spot/bun.lockb and b/spot/bun.lockb differ diff --git a/spot/entrypoints/background.ts b/spot/entrypoints/background.ts index 197d932be..3b64d3cc7 100644 --- a/spot/entrypoints/background.ts +++ b/spot/entrypoints/background.ts @@ -1,73 +1,25 @@ import { isTokenExpired } from "~/utils/jwt"; +import { + startTrackingNetwork, + getFinalRequests, + stopTrackingNetwork, +} from "~/utils/networkTracking"; +import { mergeRequests, SpotNetworkRequest } from "~/utils/networkTrackingUtils"; +import { safeApiUrl } from '~/utils/smallUtils' +import { + attachDebuggerToTab, + stopDebugger, + getRequests as getDebuggerRequests, + resetMap, +} from "~/utils/networkDebuggerTracking"; +import { messages } from '~/utils/messages' let checkBusy = false; export default defineBackground(() => { const CHECK_INT = 60 * 1000; const PING_INT = 30 * 1000; - const VER = "1.0.10"; - - const messages = { - popup: { - from: { - updateSettings: "ort:settings", - start: "popup:start", - }, - to: { - micStatus: "popup:mic-status", - stopped: "popup:stopped", - started: "popup:started", - noLogin: "popup:no-login", - }, - stop: "popup:stop", - checkStatus: "popup:check-status", - loginExist: "popup:login", - getAudioPerms: "popup:get-audio-perm", - }, - content: { - from: { - bumpVitals: "ort:bump-vitals", - bumpClicks: "ort:bump-clicks", - bumpLocation: "ort:bump-location", - discard: "ort:discard", - checkLogin: "ort:get-login", - checkRecStatus: "ort:check-status", - checkMicStatus: "ort:getMicStatus", - setLoginToken: "ort:login-token", - invalidateToken: "ort:invalidate-token", - saveSpotData: "ort:save-spot", - saveSpotVidChunk: "ort:save-spot-part", - countEnd: "ort:countend", - contentReady: "ort:content-ready", - checkNewTab: "ort:check-new-tab", - started: "ort:started", - stopped: "ort:stopped", - toStop: "ort:stop", - restart: "ort:restart", - getErrorEvents: "ort:get-error-events", - }, - to: { - setJWT: "content:set-jwt", - micStatus: "content:mic-status", - unmount: "content:unmount", - notification: "notif:display", - updateErrorEvents: "content:error-events", - }, - }, - injected: { - from: { - bumpLogs: "ort:bump-logs", - bumpNetwork: "ort:bump-network", - }, - }, - offscreen: { - to: { - checkRecStatus: "offscr:check-status", - startRecording: "offscr:start-recording", - stopRecording: "offscr:stop-recording", - }, - }, - }; + const VER = "1.0.14"; interface SpotObj { name: string; @@ -110,6 +62,7 @@ export default defineBackground(() => { openInNewTab: true, consoleLogs: true, networkLogs: true, + useDebugger: false, ingestPoint: "https://app.openreplay.com", }; const defaultSpotObj = { @@ -136,14 +89,21 @@ export default defineBackground(() => { let finalVideoBase64 = ""; let finalReady = false; let finalSpotObj: SpotObj = defaultSpotObj; + let injectNetworkRequests = []; let onStop: (() => void) | null = null; let settings = defaultSettings; - let recordingState = { + type recState = { + activeTabId: number | null; + area: string | null; + recording: string; + audioPerm: number; + } + let recordingState: recState = { activeTabId: null, area: null, recording: REC_STATE.stopped, audioPerm: 0, - } as Record; + } let jwtToken = ""; let refreshInt: any; let pingInt: any; @@ -180,17 +140,6 @@ export default defineBackground(() => { } } - function safeApiUrl(url: string) { - let str = url; - if (str.endsWith("/")) { - str = str.slice(0, -1); - } - if (str.includes("app.openreplay.com")) { - str = str.replace("app.openreplay.com", "api.openreplay.com"); - } - return str; - } - let slackChannels: { name: string; webhookId: number }[] = []; void checkTokenValidity(); @@ -359,6 +308,9 @@ export default defineBackground(() => { }); }); } else { + if (!settings.useDebugger) { + startTrackingNetwork(); + } void sendToActiveTab({ type: "content:mount", area: request.area, @@ -384,12 +336,30 @@ export default defineBackground(() => { finalVideoBase64 = ""; const recArea = request.area; finalSpotObj.startTs = Date.now(); + if (settings.networkLogs) { + if (settings.useDebugger) { + resetMap(); + browser.tabs.query({ + active: true, + currentWindow: true, + }).then((tabs) => { + if (tabs.length === 0) { + return console.error("No active tab found"); + } + recordingState.activeTabId = tabs[0].id; + void attachDebuggerToTab(recordingState.activeTabId) + }) + } else { + startTrackingNetwork(); + } + } if (recArea === "tab") { function signalTabRecording() { recordingState = { activeTabId: recordingState.activeTabId, area: "tab", recording: REC_STATE.recording, + audioPerm: recordingState.audioPerm, }; const microphone = request.mic; micStatus = microphone ? "on" : "off"; @@ -431,6 +401,7 @@ export default defineBackground(() => { activeTabId: null, area: "desktop", recording: REC_STATE.recording, + audioPerm: recordingState.audioPerm }; startRecording( recArea, @@ -518,6 +489,11 @@ export default defineBackground(() => { }, PING_INT); } }); + if (request.ingest) { + const updatedSettings = Object.assign(settings, { ingestPoint: request.ingest }); + settings = updatedSettings; + void browser.storage.local.set({ settings: updatedSettings }); + } } if (request.type === messages.content.from.invalidateToken) { if (refreshInt) { @@ -577,7 +553,7 @@ export default defineBackground(() => { return "pong"; } if (request.type === messages.injected.from.bumpNetwork) { - finalSpotObj.network.push(request.event); + injectNetworkRequests.push(request.event); return "pong"; } if (request.type === messages.content.from.bumpClicks) { @@ -618,6 +594,7 @@ export default defineBackground(() => { activeTabId: null, area: null, recording: REC_STATE.stopped, + audioPerm: recordingState.audioPerm }; } if (request.type === messages.content.from.restart) { @@ -649,7 +626,7 @@ export default defineBackground(() => { title: "JS Error", time: (l.time - finalSpotObj.startTs) / 1000, })); - const network = finalSpotObj.network + const network = [...injectNetworkRequests, ...finalSpotObj.network] .filter((net) => net.statusCode >= 400 || net.error) .map((n) => ({ title: "Network Error", @@ -665,8 +642,27 @@ export default defineBackground(() => { } if (request.type === messages.content.from.toStop) { if (recordingState.recording === REC_STATE.stopped) { - return console.error('Calling stopped recording?') + return console.error("Calling stopped recording?"); } + let networkRequests: any = []; + let mappedNetwork: any = []; + if (settings.networkLogs) { + if (settings.useDebugger) { + stopDebugger(); + mappedNetwork = getDebuggerRequests(); + } else { + networkRequests = getFinalRequests( + recordingState.area === 'tab' ? recordingState.activeTabId! : undefined, + ); + stopTrackingNetwork(); + mappedNetwork = mergeRequests( + networkRequests, + injectNetworkRequests, + ); + } + } + injectNetworkRequests = []; + finalSpotObj.network = mappedNetwork; browser.runtime .sendMessage({ type: messages.offscreen.to.stopRecording, @@ -749,12 +745,12 @@ export default defineBackground(() => { if (request.type === messages.content.from.saveSpotVidChunk) { finalVideoBase64 += request.part; finalReady = request.index === request.total - 1; - const getPlatformData = async () => { - const vendor = await browser.runtime.getPlatformInfo(); - const platform = `${vendor.os} ${vendor.arch}`; - return { platform }; - }; if (finalReady) { + const getPlatformData = async () => { + const vendor = await browser.runtime.getPlatformInfo(); + const platform = `${vendor.os} ${vendor.arch}`; + return { platform }; + }; const duration = finalSpotObj.crop ? finalSpotObj.crop[1] - finalSpotObj.crop[0] : finalSpotObj.duration; @@ -860,6 +856,7 @@ export default defineBackground(() => { activeTabId: null, area: null, recording: REC_STATE.stopped, + audioPerm: recordingState.audioPerm }; // id of spot, mobURL - for events, videoURL - for video if (!resp || !resp.id) { @@ -896,7 +893,7 @@ export default defineBackground(() => { const vPromise = fetch(videoURL, { method: "PUT", headers: { - "Content-Type": "video/webm", + "Content-Type": "video/mp4", }, body: blob, }); @@ -945,16 +942,17 @@ export default defineBackground(() => { active: true, }); } - // in future: - // const tabs = await browser.tabs.query({}) as chrome.tabs.Tab[] - // for (const tab of tabs) { - // if (tab.id) { - // this will require more permissions, do we even want this? - // void chrome.tabs.executeScript(tab.id, {file: "content"}); - // } - // } + for (const tab of await chrome.tabs.query({})) { + if (tab.url?.match(/(chrome|chrome-extension):\/\//gi)) { + continue; + } + const res = await browser.scripting.executeScript({ + target: { tabId: tab.id }, + files: ["/content-scripts/content.js"], + }); + console.log('restoring content at', res) + } await checkTokenValidity(); - await initializeOffscreenDocument(); }); void initializeOffscreenDocument(); @@ -993,7 +991,7 @@ export default defineBackground(() => { data?: any; activeTabId?: number; [key: string]: any; - }) { + }, onSent?: (tabId: number) => void) { let activeTabs = await browser.tabs.query({ active: true, currentWindow: true, @@ -1019,6 +1017,7 @@ export default defineBackground(() => { message, ); await browser.tabs.sendMessage(sendTo, message); + onSent?.(sendTo); } } @@ -1035,7 +1034,7 @@ export default defineBackground(() => { } } - function base64ToBlob(base64: string, mimeType = "video/webm") { + function base64ToBlob(base64: string, mimeType = "video/mp4") { const binaryString = atob(base64.split(",")[1]); const byteNumbers = new Array(binaryString.length); for (let i = 0; i < binaryString.length; i++) { @@ -1083,6 +1082,7 @@ export default defineBackground(() => { activeTabId: null, area: null, recording: REC_STATE.stopped, + audioPerm: recordingState.audioPerm }; void sendToActiveTab({ type: messages.content.to.unmount, @@ -1094,7 +1094,7 @@ export default defineBackground(() => { return; } const mountMsg = { - type: "content:start", + type: messages.content.to.start, microphone, time: 0, slackChannels, @@ -1129,6 +1129,8 @@ export default defineBackground(() => { state: getRecState(), activeTabId: null, }; + console.log(tabId, 'activation for new') + attachDebuggerToTab(tabId) void sendToActiveTab(msg); }); if (previousTab) { @@ -1155,7 +1157,7 @@ export default defineBackground(() => { if (state === REC_STATE.stopped) { return stopNavListening(); } - contentArmy[details.tabId] = false + contentArmy[details.tabId] = false; if (area === "tab" && (!trackedTab || details.tabId !== trackedTab)) { return; @@ -1179,10 +1181,10 @@ export default defineBackground(() => { } function startNavListening() { - browser.webNavigation.onCompleted.addListener(tabNavigatedListener) + browser.webNavigation.onCompleted.addListener(tabNavigatedListener); } function stopNavListening() { - browser.webNavigation.onCompleted.removeListener(tabNavigatedListener) + browser.webNavigation.onCompleted.removeListener(tabNavigatedListener); } /** discards recording if was recording single tab and its now closed */ @@ -1199,6 +1201,7 @@ export default defineBackground(() => { activeTabId: null, area: null, recording: REC_STATE.stopped, + audioPerm: recordingState.audioPerm }; } } diff --git a/spot/entrypoints/content/SavingControls.tsx b/spot/entrypoints/content/SavingControls.tsx index 1576eaeb6..f32ee556b 100644 --- a/spot/entrypoints/content/SavingControls.tsx +++ b/spot/entrypoints/content/SavingControls.tsx @@ -30,7 +30,7 @@ const base64ToBlob = (base64: string) => { for (let i = 0; i < byteString.length; i++) { ia[i] = byteString.charCodeAt(i); } - return new Blob([ab], { type: "video/webm" }); + return new Blob([ab], { type: "video/mp4" }); }; function SavingControls({ @@ -199,6 +199,7 @@ function SavingControls({ return new Promise((res) => { const interval = setInterval(() => { + videoRef.currentTime = 0; if (thumbnailRes) { clearInterval(interval); res(thumbnailRes); diff --git a/spot/entrypoints/content/index.tsx b/spot/entrypoints/content/index.tsx index 1e638d76e..8bc641cee 100644 --- a/spot/entrypoints/content/index.tsx +++ b/spot/entrypoints/content/index.tsx @@ -6,7 +6,7 @@ import { stopClickRecording, } from "./eventTrackers"; import ControlsBox from "~/entrypoints/content/ControlsBox"; - +import { messages } from '~/utils/messages'; import { convertBlobToBase64, getChromeFullVersion } from "./utils"; import "./style.css"; import "~/assets/main.css"; @@ -16,6 +16,10 @@ export default defineContentScript({ cssInjectionMode: "ui", async main(ctx) { + if (!ctx.isValid) { + console.error("Spot: context is invalidated on mount") + return; + } const ui = await createShadowRootUi(ctx, { name: "spot-ui", position: "inline", @@ -55,7 +59,7 @@ export default defineContentScript({ const getMicStatus = async () => { return new Promise((res) => { browser.runtime.sendMessage({ - type: "ort:getMicStatus", + type: messages.content.from.checkMicStatus, }); let int = setInterval(() => { if (micResponse !== null) { @@ -124,7 +128,7 @@ export default defineContentScript({ recState = "stopped"; stopClickRecording(); stopLocationRecording(); - const result = await browser.runtime.sendMessage({ type: "ort:stop" }); + const result = await browser.runtime.sendMessage({ type: messages.content.from.toStop }); if (result.status === "full") { chunksReady = true; data = result; @@ -149,20 +153,20 @@ export default defineContentScript({ const pause = () => { recState = "paused"; - browser.runtime.sendMessage({ type: "ort:pause" }); + browser.runtime.sendMessage({ type: messages.content.from.pause }); }; const resume = () => { recState = "recording"; - browser.runtime.sendMessage({ type: "ort:resume" }); + browser.runtime.sendMessage({ type: messages.content.from.resume }); }; const muteMic = () => { - browser.runtime.sendMessage({ type: "ort:mute-microphone" }); + browser.runtime.sendMessage({ type: messages.content.from.muteMic }); }; const unmuteMic = () => { - browser.runtime.sendMessage({ type: "ort:unmute-microphone" }); + browser.runtime.sendMessage({ type: messages.content.from.unmuteMic }); }; const onClose = async ( @@ -202,14 +206,14 @@ export default defineContentScript({ try { await browser.runtime.sendMessage({ - type: "ort:save-spot", + type: messages.content.from.saveSpotData, spot, }); let index = 0; for (let part of videoData.result) { if (part) { await browser.runtime.sendMessage({ - type: "ort:save-spot-part", + type: messages.content.from.saveSpotVidChunk, part, index, total: videoData.result.length, @@ -237,25 +241,27 @@ export default defineContentScript({ } if (event.data.type === "orspot:token") { window.postMessage({ type: "orspot:logged" }, "*"); + const ingest = window.location.origin; void browser.runtime.sendMessage({ - type: "ort:login-token", + type: messages.content.from.setLoginToken, token: event.data.token, + ingest }); } if (event.data.type === "orspot:invalidate") { void browser.runtime.sendMessage({ - type: "ort:invalidate-token", + type: messages.content.from.invalidateToken, }); } if (event.data.type === "ort:bump-logs") { void chrome.runtime.sendMessage({ - type: "ort:bump-logs", + type: messages.injected.from.bumpLogs, logs: event.data.logs, }); } if (event.data.type === "ort:bump-network") { void chrome.runtime.sendMessage({ - type: "ort:bump-network", + type: messages.injected.from.bumpNetwork, event: event.data.event, }); } @@ -270,16 +276,16 @@ export default defineContentScript({ document.head.appendChild(scriptEl); } function startConsoleTracking() { - injectScript() + injectScript(); setTimeout(() => { window.postMessage({ type: "injected:c-start" }); }, 100); } function startNetworkTracking() { - injectScript() + injectScript(); setTimeout(() => { window.postMessage({ type: "injected:n-start" }); - }, 100) + }, 100); } function stopConsoleTracking() { @@ -292,7 +298,7 @@ export default defineContentScript({ function onRestart() { chrome.runtime.sendMessage({ - type: "ort:restart", + type: messages.content.from.restart, }); stopClickRecording(); stopLocationRecording(); @@ -316,7 +322,7 @@ export default defineContentScript({ let onEndObj = {}; async function countEnd(): Promise { return browser.runtime - .sendMessage({ ...onEndObj, type: "ort:countend" }) + .sendMessage({ ...onEndObj, type: messages.content.from.countEnd }) .then((r: boolean) => { onEndObj = {}; return r; @@ -324,11 +330,11 @@ export default defineContentScript({ } setInterval(() => { - void browser.runtime.sendMessage({ type: "ort:content-ready" }); - }, 250) + void browser.runtime.sendMessage({ type: messages.content.from.contentReady }); + }, 250); // @ts-ignore false positive browser.runtime.onMessage.addListener((message: any, resp) => { - if (message.type === "content:mount") { + if (message.type === messages.content.to.mount) { if (recState === "count") return; recState = "count"; onEndObj = { @@ -339,7 +345,7 @@ export default defineContentScript({ audioPerm = message.audioPerm; ui.mount(); } - if (message.type === "content:start") { + if (message.type === messages.content.to.start) { if (recState === "recording") return; clockStart = message.time; recState = "recording"; @@ -352,13 +358,13 @@ export default defineContentScript({ if (message.withNetwork) { startNetworkTracking(); } - browser.runtime.sendMessage({ type: "ort:started" }); + browser.runtime.sendMessage({ type: messages.content.from.started }); if (message.shouldMount) { ui.mount(); } return "pong"; } - if (message.type === "notif:display") { + if (message.type === messages.content.to.notification) { window.postMessage( { type: "ornotif:display", @@ -367,7 +373,7 @@ export default defineContentScript({ "*", ); } - if (message.type === "content:unmount") { + if (message.type === messages.content.to.unmount) { stopClickRecording(); stopLocationRecording(); stopConsoleTracking(); @@ -376,22 +382,22 @@ export default defineContentScript({ ui.remove(); return "unmounted"; } - if (message.type === "content:video-chunk") { + if (message.type === messages.content.to.videoChunk) { videoChunks[message.index] = message.data; if (message.total === message.index + 1) { chunksReady = true; } } - if (message.type === "content:spot-saved") { + if (message.type === messages.content.to.spotSaved) { window.postMessage({ type: "ornotif:copy", url: message.url }); } - if (message.type === "content:stop") { + if (message.type === messages.content.to.stop) { window.postMessage({ type: "content:trigger-stop" }, "*"); } - if (message.type === "content:mic-status") { + if (message.type === messages.content.to.micStatus) { micResponse = message.micStatus; } - if (message.type === "content:error-events") { + if (message.type === messages.content.to.updateErrorEvents) { errorsReady = true; errorData.push(...message.errorData); } diff --git a/spot/entrypoints/offscreen/main.js b/spot/entrypoints/offscreen/main.js index 6d93b2df7..8c2a48840 100644 --- a/spot/entrypoints/offscreen/main.js +++ b/spot/entrypoints/offscreen/main.js @@ -8,7 +8,7 @@ function getRecordingSettings(qualityValue) { "4k": { audioBitsPerSecond: 192000, videoBitsPerSecond: 40000000, width: 4096, height: 2160 }, "1080p": { audioBitsPerSecond: 192000, videoBitsPerSecond: 8000000, width: 1920, height: 1080 }, // @default - "720p": { audioBitsPerSecond: 96000, videoBitsPerSecond: 2500000, width: 1280, height: 720 }, + "720p": { audioBitsPerSecond: 128000, videoBitsPerSecond: 2500000, width: 1280, height: 720 }, "480p": { audioBitsPerSecond: 96000, videoBitsPerSecond: 2500000, width: 854, height: 480 }, "360p": { audioBitsPerSecond: 96000, videoBitsPerSecond: 1000000, width: 640, height: 360 }, "240p": { audioBitsPerSecond: 64000, videoBitsPerSecond: 500000, width: 426, height: 240 }, @@ -19,7 +19,9 @@ function getRecordingSettings(qualityValue) { const duration = 3 * 60 * 1000; // 3 minutes const mimeTypes = [ - // fastest trimming and HLS + // best support for backend + 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"', + // fast trimming if we "pretend" that its a webm "video/webm;codecs=h264", "video/webm;codecs=avc1", "video/webm;codecs=av1", diff --git a/spot/entrypoints/popup/Settings.tsx b/spot/entrypoints/popup/Settings.tsx index edf0bc113..45b73e9af 100644 --- a/spot/entrypoints/popup/Settings.tsx +++ b/spot/entrypoints/popup/Settings.tsx @@ -11,11 +11,11 @@ function Settings({ goBack }: { goBack: () => void }) { const [ingest, setIngest] = createSignal(defaultIngest); const [editIngest, setEditIngest] = createSignal(false); const [tempIngest, setTempIngest] = createSignal(""); + const [useDebugger, setUseDebugger] = createSignal(false); onMount(() => { chrome.storage.local.get("settings", (data: any) => { if (data.settings) { - console.log('update state', data.settings) const ingest = data.settings.ingestPoint || defaultIngest; const devToolsEnabled = @@ -26,6 +26,7 @@ function Settings({ goBack }: { goBack: () => void }) { setTempIngest(ingest); setShowIngest(ingest !== defaultIngest); setEditIngest(!data.settings.ingestPoint); + setUseDebugger(data.settings.useDebugger); } }); }); @@ -94,6 +95,16 @@ function Settings({ goBack }: { goBack: () => void }) { }); }; + const toggleUseDebugger = (e: Event) => { + e.stopPropagation(); + const value = useDebugger(); + setUseDebugger(!value); + chrome.runtime.sendMessage({ + type: "ort:settings", + settings: { useDebugger: !value }, + }); + } + return (
    @@ -153,6 +164,27 @@ function Settings({ goBack }: { goBack: () => void }) {

    +
    +
    +

    + Use Debugger +

    +
    + +
    +
    +

    + Enable the chrome debugger to track network requests with more precision. +

    +
    +

    Ingest Point

    diff --git a/spot/package.json b/spot/package.json index 120dbe12f..14e0dc732 100644 --- a/spot/package.json +++ b/spot/package.json @@ -2,7 +2,7 @@ "name": "spot", "description": "manifest.json description", "private": true, - "version": "1.0.13", + "version": "1.0.17", "type": "module", "scripts": { "dev": "wxt", @@ -16,23 +16,23 @@ "prettier": "prettier --write ." }, "dependencies": { - "@neodrag/solid": "^2.2.0", - "@openreplay/network-proxy": "^1.0.5", + "@neodrag/solid": "^2.3.0", + "@openreplay/network-proxy": "^1.1.0", "@thedutchcoder/postcss-rem-to-px": "^0.0.2", - "autoprefixer": "^10.4.19", + "autoprefixer": "^10.4.21", "install": "^0.13.0", - "npm": "^10.8.1", - "postcss": "^8.4.38", - "prettier": "^3.3.2", - "solid-js": "^1.8.17", + "npm": "^10.9.2", + "postcss": "^8.5.3", + "prettier": "^3.5.3", + "solid-js": "^1.9.5", "tailwindcss": "^3.4.4", - "web-vitals": "^4.2.2" + "web-vitals": "^4.2.4" }, "devDependencies": { "@wxt-dev/module-solid": "^1.1.3", "daisyui": "^4.12.10", - "typescript": "^5.7.2", - "wxt": "0.19.22" + "typescript": "^5.8.2", + "wxt": "0.19.29" }, "packageManager": "yarn@4.5.3" } diff --git a/spot/tsconfig.json b/spot/tsconfig.json index 70a38bef5..ffea6ad3b 100644 --- a/spot/tsconfig.json +++ b/spot/tsconfig.json @@ -11,6 +11,7 @@ "lib": ["es2022", "DOM"], "compilerOptions": { "jsx": "preserve", - "jsxImportSource": "solid-js" - } + "jsxImportSource": "solid-js", + "noImplicitAny": false, + }, } diff --git a/spot/utils/messages.ts b/spot/utils/messages.ts new file mode 100644 index 000000000..ce9366953 --- /dev/null +++ b/spot/utils/messages.ts @@ -0,0 +1,70 @@ +export const messages = { + popup: { + from: { + updateSettings: "ort:settings", + start: "popup:start", + }, + to: { + micStatus: "popup:mic-status", + stopped: "popup:stopped", + started: "popup:started", + noLogin: "popup:no-login", + }, + stop: "popup:stop", + checkStatus: "popup:check-status", + loginExist: "popup:login", + getAudioPerms: "popup:get-audio-perm", + }, + content: { + from: { + bumpVitals: "ort:bump-vitals", + bumpClicks: "ort:bump-clicks", + bumpLocation: "ort:bump-location", + discard: "ort:discard", + checkLogin: "ort:get-login", + checkRecStatus: "ort:check-status", + checkMicStatus: "ort:getMicStatus", + setLoginToken: "ort:login-token", + invalidateToken: "ort:invalidate-token", + saveSpotData: "ort:save-spot", + saveSpotVidChunk: "ort:save-spot-part", + countEnd: "ort:countend", + contentReady: "ort:content-ready", + checkNewTab: "ort:check-new-tab", + started: "ort:started", + stopped: "ort:stopped", + toStop: "ort:stop", + restart: "ort:restart", + getErrorEvents: "ort:get-error-events", + muteMic: "ort:mute-microphone", + unmuteMic: "ort:unmute-microphone", + resume: "ort:resume", + pause: "ort:pause", + }, + to: { + setJWT: "content:set-jwt", + micStatus: "content:mic-status", + unmount: "content:unmount", + mount: "content:mount", + start: "content:start", + notification: "notif:display", + updateErrorEvents: "content:error-events", + videoChunk: "content:video-chunk", + spotSaved: "content:spot-saved", + stop: "content:stop", + }, + }, + injected: { + from: { + bumpLogs: "ort:bump-logs", + bumpNetwork: "ort:bump-network", + }, + }, + offscreen: { + to: { + checkRecStatus: "offscr:check-status", + startRecording: "offscr:start-recording", + stopRecording: "offscr:stop-recording", + }, + }, +}; diff --git a/spot/utils/networkDebuggerTracking.ts b/spot/utils/networkDebuggerTracking.ts new file mode 100644 index 000000000..714ef10ea --- /dev/null +++ b/spot/utils/networkDebuggerTracking.ts @@ -0,0 +1,106 @@ +let requestMaps = {}; +const potentialActiveTabs: Array = []; + +export function resetMap(tabId?: string) { + if (tabId) delete requestMaps[tabId]; + else requestMaps = {}; +} + +export async function attachDebuggerToTab(tabId: string | number) { + if (requestMaps[tabId] && potentialActiveTabs.includes(tabId)) return; + await new Promise((resolve, reject) => { + chrome.debugger.attach({ tabId }, "1.3", () => { + if (chrome.runtime.lastError) return reject(`${chrome.runtime.lastError.message}, ${tabId}`); + if (!requestMaps[tabId]) requestMaps[tabId] = {}; + potentialActiveTabs.push(tabId); + chrome.debugger.sendCommand({ tabId }, "Network.enable", {}, resolve); + }); + chrome.debugger.onEvent.addListener(handleRequestIntercept); + }); +} + +export function stopDebugger(tabId?: string | number) { + if (tabId) { + chrome.debugger.detach({ tabId }); + const index = potentialActiveTabs.indexOf(tabId); + if (index > -1) potentialActiveTabs.splice(index, 1); + } else { + potentialActiveTabs.forEach((tabId) => { + chrome.debugger.detach({ tabId }); + }); + potentialActiveTabs.length = 0; + } +} + +const getType = (requestType: string) => { + switch (requestType.toLowerCase()) { + case "fetch": + case "xhr": + case "xmlhttprequest": + return 'xmlhttprequest' + default: + return requestType + } +} + +function handleRequestIntercept(source, method, params) { + if (!source.tabId) return; + const tabId = source.tabId; + if (!requestMaps[tabId]) return; + if (params.request && params.request.method === "OPTIONS") return; + const reqId = `${tabId}_${params.requestId}`; + + switch (method) { + case "Network.requestWillBeSent": + requestMaps[tabId][reqId] = { + encodedBodySize: 0, + responseBodySize: 0, + duration: 0, + method: params.request.method, + type: params.type ? getType(params.type) : "resource", + statusCode: 0, + url: params.request.url, + body: params.request.postData || "", + responseBody: "", + fromCache: false, + requestHeaders: params.request.headers || {}, + responseHeaders: {}, + timestamp: Date.now(), + time: Date.now(), + }; + break; + case "Network.responseReceived": + if (!requestMaps[tabId][reqId]) return; + requestMaps[tabId][reqId].statusCode = params.response.status; + requestMaps[tabId][reqId].responseHeaders = params.response.headers || {}; + if (params.response.fromDiskCache) requestMaps[tabId][reqId].fromCache = true; + break; + case "Network.dataReceived": + if (!requestMaps[tabId][reqId]) return; + requestMaps[tabId][reqId].encodedBodySize += params.dataLength; + break; + case "Network.loadingFinished": + if (!requestMaps[tabId][reqId]) return; + requestMaps[tabId][reqId].duration = Date.now() - requestMaps[tabId][reqId].timestamp; + requestMaps[tabId][reqId].responseBodySize = requestMaps[tabId][reqId].encodedBodySize; + chrome.debugger.sendCommand({ tabId }, "Network.getResponseBody", { requestId: params.requestId }, (res) => { + if (!res || res.error) { + requestMaps[tabId][reqId].error = res?.error || "Unknown"; + } else { + requestMaps[tabId][reqId].responseBody = res.base64Encoded ? 'base64 payload' : res.body; + } + }); + break; + case "Network.loadingFailed": + if (!requestMaps[tabId][reqId]) return; + requestMaps[tabId][reqId].error = params.errorText || "Unknown"; + break; + } +} + +export function getRequests(tabId?: string) { + if (tabId) { + return Object.values(requestMaps[tabId] || {}); + } + return Object.values(requestMaps).reduce((acc, curr) => acc.concat(Object.values(curr)), []); +} diff --git a/spot/utils/networkTracking.ts b/spot/utils/networkTracking.ts index e6e4d67fe..10dc7caac 100644 --- a/spot/utils/networkTracking.ts +++ b/spot/utils/networkTracking.ts @@ -1,169 +1,157 @@ -// import { -// SpotNetworkRequest, -// filterBody, -// filterHeaders, -// tryFilterUrl, -// TrackedRequest, -// } from "./networkTrackingUtils"; -// -// export const rawRequests: (TrackedRequest & { -// startTs: number; -// duration: number; -// })[] = []; -// -// export function createSpotNetworkRequestV1( -// trackedRequest: TrackedRequest, -// trackedTab?: number, -// ) { -// if (trackedRequest.tabId === -1) { -// return; -// } -// if (trackedTab && trackedTab !== trackedRequest.tabId) { -// return; -// } -// if ( -// ["ping", "beacon", "image", "script", "font"].includes(trackedRequest.type) -// ) { -// if (!trackedRequest.statusCode || trackedRequest.statusCode < 400) { -// return; -// } -// } -// const type = ["stylesheet", "script", "image", "media", "font"].includes( -// trackedRequest.type, -// ) -// ? "resource" -// : trackedRequest.type; -// -// const requestHeaders = trackedRequest.requestHeaders -// ? filterHeaders(trackedRequest.requestHeaders) -// : {}; -// const responseHeaders = trackedRequest.responseHeaders -// ? filterHeaders(trackedRequest.responseHeaders) -// : {}; -// -// const reqSize = trackedRequest.reqBody -// ? trackedRequest.requestSize || trackedRequest.reqBody.length -// : 0; -// -// const status = getRequestStatus(trackedRequest); -// let body; -// if (trackedRequest.reqBody) { -// try { -// body = filterBody(trackedRequest.reqBody); -// } catch (e) { -// body = "Error parsing body"; -// console.error(e); -// } -// } else { -// body = ""; -// } -// const request: SpotNetworkRequest = { -// method: trackedRequest.method, -// type, -// body, -// responseBody: "", -// requestHeaders, -// responseHeaders, -// time: trackedRequest.timeStamp, -// statusCode: status, -// error: trackedRequest.error, -// url: tryFilterUrl(trackedRequest.url), -// fromCache: trackedRequest.fromCache || false, -// encodedBodySize: reqSize, -// responseBodySize: trackedRequest.responseSize, -// duration: trackedRequest.duration, -// }; -// -// return request; -// } -// -// function modifyOnSpot(request: TrackedRequest) { -// const id = request.requestId; -// const index = rawRequests.findIndex((r) => r.requestId === id); -// const ts = Date.now(); -// const start = rawRequests[index]?.startTs ?? ts; -// rawRequests[index] = { -// ...rawRequests[index], -// ...request, -// duration: ts - start, -// }; -// } -// -// const trackOnBefore = ( -// details: WebRequest.OnBeforeRequestDetailsType & { reqBody: string }, -// ) => { -// if (details.method === "POST" && details.requestBody) { -// const requestBody = details.requestBody; -// if (requestBody.formData) { -// details.reqBody = JSON.stringify(requestBody.formData); -// } else if (requestBody.raw) { -// const raw = requestBody.raw[0]?.bytes; -// if (raw) { -// details.reqBody = new TextDecoder("utf-8").decode(raw); -// } -// } -// } -// rawRequests.push({ ...details, startTs: Date.now(), duration: 0 }); -// }; -// const trackOnCompleted = (details: WebRequest.OnCompletedDetailsType) => { -// modifyOnSpot(details); -// }; -// const trackOnHeaders = (details: WebRequest.OnBeforeSendHeadersDetailsType) => { -// modifyOnSpot(details); -// }; -// const trackOnError = (details: WebRequest.OnErrorOccurredDetailsType) => { -// modifyOnSpot(details); -// }; -// export function startTrackingNetwork() { -// rawRequests.length = 0; -// browser.webRequest.onBeforeRequest.addListener( -// // @ts-ignore -// trackOnBefore, -// { urls: [""] }, -// ["requestBody"], -// ); -// browser.webRequest.onBeforeSendHeaders.addListener( -// trackOnHeaders, -// { urls: [""] }, -// ["requestHeaders"], -// ); -// browser.webRequest.onCompleted.addListener( -// trackOnCompleted, -// { -// urls: [""], -// }, -// ["responseHeaders"], -// ); -// browser.webRequest.onErrorOccurred.addListener( -// trackOnError, -// { -// urls: [""], -// }, -// ["extraHeaders"], -// ); -// } -// -// export function stopTrackingNetwork() { -// browser.webRequest.onBeforeRequest.removeListener(trackOnBefore); -// browser.webRequest.onCompleted.removeListener(trackOnCompleted); -// browser.webRequest.onErrorOccurred.removeListener(trackOnError); -// } -// -// function getRequestStatus(request: any): number { -// if (request.statusCode) { -// return request.statusCode; -// } -// if (request.error) { -// return 0; -// } -// return 200; -// } -// -// export function getFinalRequests(tabId: number): SpotNetworkRequest[] { -// const finalRequests = rawRequests -// .map((r) => createSpotNetworkRequest(r, tabId)) -// .filter((r) => r !== undefined); -// rawRequests.length = 0; -// -// return finalRequests; -// } +import { + SpotNetworkRequest, + filterBody, + filterHeaders, + tryFilterUrl, + TrackedRequest, +} from "./networkTrackingUtils"; + +export const rawRequests: Array< + TrackedRequest & { startTs: number; duration: number } +> = []; + +function getRequestStatus(request: TrackedRequest): number { + if (request.statusCode) return request.statusCode; + if (request.error) return 0; + return 200; +} + +function modifyOnSpot(request: TrackedRequest) { + const id = request.requestId; + const index = rawRequests.findIndex((r) => r.requestId === id); + const ts = Date.now(); + const start = rawRequests[index]?.startTs ?? ts; + rawRequests[index] = { + ...rawRequests[index], + ...request, + duration: ts - start, + }; +} + +function trackOnBefore( + details: browser.webRequest._OnBeforeRequestDetails & { reqBody?: string }, +) { + if (details.method === "POST" && details.requestBody) { + if (details.requestBody.formData) { + details.reqBody = JSON.stringify(details.requestBody.formData); + } else if (details.requestBody.raw) { + const raw = details.requestBody.raw[0]?.bytes; + if (raw) details.reqBody = new TextDecoder("utf-8").decode(raw); + } + } + rawRequests.push({ ...details, startTs: Date.now(), duration: 0 }); +} + +function trackOnHeaders( + details: browser.webRequest._OnBeforeSendHeadersDetails, +) { + modifyOnSpot(details); +} + +function trackOnCompleted(details: browser.webRequest._OnCompletedDetails) { + modifyOnSpot(details); +} + +function trackOnError(details: browser.webRequest._OnErrorOccurredDetails) { + modifyOnSpot(details); +} + +// Build final SpotNetworkRequest objects +function createSpotNetworkRequest( + trackedRequest: TrackedRequest, + trackedTab?: number, +): SpotNetworkRequest | undefined { + if (trackedRequest.tabId === -1) return; + if (trackedTab && trackedTab !== trackedRequest.tabId) return; + + if ( + ["ping", "beacon", "image", "script", "font"].includes(trackedRequest.type) + ) { + if (!trackedRequest.statusCode || trackedRequest.statusCode < 400) return; + } + + const type = ["stylesheet", "script", "image", "media", "font"].includes( + trackedRequest.type, + ) + ? "resource" + : trackedRequest.type; + + const requestHeaders = trackedRequest.requestHeaders + ? filterHeaders(trackedRequest.requestHeaders) + : {}; + const responseHeaders = trackedRequest.responseHeaders + ? filterHeaders(trackedRequest.responseHeaders) + : {}; + + const reqSize = trackedRequest.reqBody + ? trackedRequest.requestSize || trackedRequest.reqBody.length + : 0; + const status = getRequestStatus(trackedRequest); + + let body = ""; + if (trackedRequest.reqBody) { + try { + body = filterBody(trackedRequest.reqBody); + } catch (e) { + body = "Error parsing body"; + console.error(e); + } + } + + const request: SpotNetworkRequest = { + method: trackedRequest.method, + type, + body, + responseBody: "", + requestHeaders, + responseHeaders, + time: trackedRequest.timeStamp, + timestamp: trackedRequest.timeStamp, + statusCode: status, + error: trackedRequest.error, + url: tryFilterUrl(trackedRequest.url), + fromCache: trackedRequest.fromCache || false, + encodedBodySize: reqSize, + responseBodySize: trackedRequest.responseSize, + duration: trackedRequest.duration, + }; + + return request; +} + +export function startTrackingNetwork() { + rawRequests.length = 0; + browser.webRequest.onBeforeRequest.addListener( + trackOnBefore, + { urls: [""] }, + ["requestBody"], // allows capturing POST bodies + ); + browser.webRequest.onBeforeSendHeaders.addListener( + trackOnHeaders, + { urls: [""] }, + ["requestHeaders"], + ); + browser.webRequest.onCompleted.addListener( + trackOnCompleted, + { urls: [""] }, + ["responseHeaders"], + ); + browser.webRequest.onErrorOccurred.addListener(trackOnError, { + urls: [""], + }); +} + +export function stopTrackingNetwork() { + browser.webRequest.onBeforeRequest.removeListener(trackOnBefore); + browser.webRequest.onBeforeSendHeaders.removeListener(trackOnHeaders); + browser.webRequest.onCompleted.removeListener(trackOnCompleted); + browser.webRequest.onErrorOccurred.removeListener(trackOnError); +} + +export function getFinalRequests(tabId?: number): SpotNetworkRequest[] { + const finalRequests = rawRequests + .map((r) => createSpotNetworkRequest(r, tabId)) + .filter((r) => r !== undefined) as SpotNetworkRequest[]; + rawRequests.length = 0; + return finalRequests; +} diff --git a/spot/utils/networkTrackingUtils.ts b/spot/utils/networkTrackingUtils.ts index b56e48ef8..5709a3143 100644 --- a/spot/utils/networkTrackingUtils.ts +++ b/spot/utils/networkTrackingUtils.ts @@ -82,7 +82,9 @@ export const sensitiveParams = new Set([ "account_key", ]); -export function filterHeaders(headers: Record | { name: string; value: string }[]) { +export function filterHeaders( + headers: Record | { name: string; value: string }[], +) { const filteredHeaders: Record = {}; if (Array.isArray(headers)) { headers.forEach(({ name, value }) => { diff --git a/spot/utils/proxyNetworkTracking.ts b/spot/utils/proxyNetworkTracking.ts index af000e622..af4d1363a 100644 --- a/spot/utils/proxyNetworkTracking.ts +++ b/spot/utils/proxyNetworkTracking.ts @@ -1,9 +1,6 @@ import createNetworkProxy, { INetworkMessage } from "@openreplay/network-proxy"; import { SpotNetworkRequest, - filterBody, - filterHeaders, - tryFilterUrl, getTopWindow, } from "./networkTrackingUtils"; @@ -36,7 +33,7 @@ function getBody(req: { body?: string | Record }): string { if (req.body) { try { - body = filterBody(req.body); + body = req.body; } catch (e) { body = "Error parsing body"; console.error(e); @@ -63,8 +60,8 @@ export function createSpotNetworkRequest( } catch (e) { console.error("Error parsing response", e); } - const reqHeaders = request.headers ? filterHeaders(request.headers) : {}; - const resHeaders = response.headers ? filterHeaders(response.headers) : {}; + const reqHeaders = request.headers ?? {}; + const resHeaders = response.headers ?? {}; const responseBodySize = msg.responseSize || 0; const reqSize = msg.request ? msg.request.length : 0; const body = getBody(request); @@ -81,7 +78,7 @@ export function createSpotNetworkRequest( timestamp: Date.now(), statusCode: msg.status || 0, error: undefined, - url: tryFilterUrl(msg.url), + url: msg.url, fromCache: false, encodedBodySize: reqSize, responseBodySize, diff --git a/spot/utils/smallUtils.ts b/spot/utils/smallUtils.ts new file mode 100644 index 000000000..8b46f0cdb --- /dev/null +++ b/spot/utils/smallUtils.ts @@ -0,0 +1,10 @@ +export function safeApiUrl(url: string) { + let str = url; + if (str.endsWith("/")) { + str = str.slice(0, -1); + } + if (str.includes("app.openreplay.com")) { + str = str.replace("app.openreplay.com", "api.openreplay.com"); + } + return str; +} \ No newline at end of file diff --git a/spot/wxt.config.ts b/spot/wxt.config.ts index 243d9f074..650d00e48 100644 --- a/spot/wxt.config.ts +++ b/spot/wxt.config.ts @@ -16,7 +16,7 @@ export default defineConfig({ }, web_accessible_resources: [ { - resources: ["injected.js", "notifications.js"], + resources: ["injected.js", "notifications.js", "/content-scripts/content.css"], matches: [""], }, ], @@ -26,6 +26,12 @@ export default defineConfig({ "offscreen", "unlimitedStorage", "webNavigation", + "webRequest", + "", + "debugger", ], }, + runner: { + chromiumArgs: ['--user-data-dir=./.wxt/chrome-data'], + } }); diff --git a/third-party.md b/third-party.md index 096446172..d6b5293f8 100644 --- a/third-party.md +++ b/third-party.md @@ -42,7 +42,7 @@ up to date with every new library you use. | elasticsearch-py | Apache2 | Python | | jira | BSD2 | Python | | redis-py | MIT | Python | -| clickhouse-driver | MIT | Python | +| clickhouse-connect | Apache2 | Python | | python3-saml | MIT | Python | | kubernetes | Apache2 | Python | | chalice | Apache2 | Python | diff --git a/tracker/bun.lock b/tracker/bun.lock new file mode 100644 index 000000000..e3d0b68b8 --- /dev/null +++ b/tracker/bun.lock @@ -0,0 +1,1192 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "tracker-family", + }, + "tracker": { + "name": "@openreplay/tracker", + "version": "16.1.1", + "dependencies": { + "@medv/finder": "^4.0.2", + "@openreplay/network-proxy": "^1.1.0", + "error-stack-parser": "^2.0.6", + "error-stack-parser-es": "^0.1.5", + "fflate": "^0.8.2", + "web-vitals": "^4.2.3", + }, + "devDependencies": { + "@babel/core": "^7.26.0", + "@jest/globals": "^29.7.0", + "@rollup/plugin-babel": "^6.0.4", + "@rollup/plugin-commonjs": "^28.0.1", + "@rollup/plugin-node-resolve": "^15.3.0", + "@rollup/plugin-replace": "^6.0.1", + "@rollup/plugin-terser": "0.4.4", + "@rollup/plugin-typescript": "^12.1.1", + "@typescript-eslint/eslint-plugin": "^8.14.0", + "@typescript-eslint/parser": "^8.14.0", + "eslint": "^9.15.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.2.1", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "prettier": "^3.3.3", + "replace-in-files": "^2.0.3", + "rollup": "^4.27.2", + "semver": "^6.3.0", + "ts-jest": "^29.2.5", + "tslib": "^2.8.1", + "typescript": "^5.6.3", + }, + }, + "tracker-assist": { + "name": "@openreplay/tracker-assist", + "version": "11.0.6", + "dependencies": { + "csstype": "^3.0.10", + "fflate": "^0.8.2", + "socket.io-client": "^4.8.1", + }, + "devDependencies": { + "@typescript-eslint/eslint-plugin": "^8.14.0", + "@typescript-eslint/parser": "^8.14.0", + "eslint": "^9.15.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.2.1", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "prettier": "^3.3.3", + "replace-in-files-cli": "^1.0.0", + "ts-jest": "^29.2.5", + "typescript": "^5.6.3", + }, + "peerDependencies": { + "@openreplay/tracker": ">=14.0.14", + }, + }, + }, + "packages": { + "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], + + "@babel/code-frame": ["@babel/code-frame@7.26.2", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ=="], + + "@babel/compat-data": ["@babel/compat-data@7.26.8", "", {}, "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ=="], + + "@babel/core": ["@babel/core@7.26.10", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", "@babel/generator": "^7.26.10", "@babel/helper-compilation-targets": "^7.26.5", "@babel/helper-module-transforms": "^7.26.0", "@babel/helpers": "^7.26.10", "@babel/parser": "^7.26.10", "@babel/template": "^7.26.9", "@babel/traverse": "^7.26.10", "@babel/types": "^7.26.10", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ=="], + + "@babel/generator": ["@babel/generator@7.27.0", "", { "dependencies": { "@babel/parser": "^7.27.0", "@babel/types": "^7.27.0", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw=="], + + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.0", "", { "dependencies": { "@babel/compat-data": "^7.26.8", "@babel/helper-validator-option": "^7.25.9", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA=="], + + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.25.9", "", { "dependencies": { "@babel/traverse": "^7.25.9", "@babel/types": "^7.25.9" } }, "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw=="], + + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.26.0", "", { "dependencies": { "@babel/helper-module-imports": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9", "@babel/traverse": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw=="], + + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.26.5", "", {}, "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.25.9", "", {}, "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="], + + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.25.9", "", {}, "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw=="], + + "@babel/helpers": ["@babel/helpers@7.27.0", "", { "dependencies": { "@babel/template": "^7.27.0", "@babel/types": "^7.27.0" } }, "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg=="], + + "@babel/parser": ["@babel/parser@7.27.0", "", { "dependencies": { "@babel/types": "^7.27.0" }, "bin": "./bin/babel-parser.js" }, "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg=="], + + "@babel/plugin-syntax-async-generators": ["@babel/plugin-syntax-async-generators@7.8.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw=="], + + "@babel/plugin-syntax-bigint": ["@babel/plugin-syntax-bigint@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg=="], + + "@babel/plugin-syntax-class-properties": ["@babel/plugin-syntax-class-properties@7.12.13", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA=="], + + "@babel/plugin-syntax-class-static-block": ["@babel/plugin-syntax-class-static-block@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw=="], + + "@babel/plugin-syntax-import-attributes": ["@babel/plugin-syntax-import-attributes@7.26.0", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A=="], + + "@babel/plugin-syntax-import-meta": ["@babel/plugin-syntax-import-meta@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g=="], + + "@babel/plugin-syntax-json-strings": ["@babel/plugin-syntax-json-strings@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA=="], + + "@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA=="], + + "@babel/plugin-syntax-logical-assignment-operators": ["@babel/plugin-syntax-logical-assignment-operators@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig=="], + + "@babel/plugin-syntax-nullish-coalescing-operator": ["@babel/plugin-syntax-nullish-coalescing-operator@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ=="], + + "@babel/plugin-syntax-numeric-separator": ["@babel/plugin-syntax-numeric-separator@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug=="], + + "@babel/plugin-syntax-object-rest-spread": ["@babel/plugin-syntax-object-rest-spread@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA=="], + + "@babel/plugin-syntax-optional-catch-binding": ["@babel/plugin-syntax-optional-catch-binding@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q=="], + + "@babel/plugin-syntax-optional-chaining": ["@babel/plugin-syntax-optional-chaining@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg=="], + + "@babel/plugin-syntax-private-property-in-object": ["@babel/plugin-syntax-private-property-in-object@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg=="], + + "@babel/plugin-syntax-top-level-await": ["@babel/plugin-syntax-top-level-await@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw=="], + + "@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ=="], + + "@babel/template": ["@babel/template@7.27.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "@babel/parser": "^7.27.0", "@babel/types": "^7.27.0" } }, "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA=="], + + "@babel/traverse": ["@babel/traverse@7.27.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "@babel/generator": "^7.27.0", "@babel/parser": "^7.27.0", "@babel/template": "^7.27.0", "@babel/types": "^7.27.0", "debug": "^4.3.1", "globals": "^11.1.0" } }, "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA=="], + + "@babel/types": ["@babel/types@7.27.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg=="], + + "@bcoe/v8-coverage": ["@bcoe/v8-coverage@0.2.3", "", {}, "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw=="], + + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.5.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w=="], + + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="], + + "@eslint/config-array": ["@eslint/config-array@0.19.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w=="], + + "@eslint/config-helpers": ["@eslint/config-helpers@0.2.0", "", {}, "sha512-yJLLmLexii32mGrhW29qvU3QBVTu0GUmEf/J4XsBtVhp4JkIUFN/BjWqTF63yRvGApIDpZm5fa97LtYtINmfeQ=="], + + "@eslint/core": ["@eslint/core@0.12.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg=="], + + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="], + + "@eslint/js": ["@eslint/js@9.23.0", "", {}, "sha512-35MJ8vCPU0ZMxo7zfev2pypqTwWTofFZO6m4KAtdoFhRpLJUpHTZZ+KB3C7Hb1d7bULYwO4lJXGCi5Se+8OMbw=="], + + "@eslint/object-schema": ["@eslint/object-schema@2.1.6", "", {}, "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="], + + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.2.7", "", { "dependencies": { "@eslint/core": "^0.12.0", "levn": "^0.4.1" } }, "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g=="], + + "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], + + "@humanfs/node": ["@humanfs/node@0.16.6", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" } }, "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw=="], + + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.2", "", {}, "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ=="], + + "@istanbuljs/load-nyc-config": ["@istanbuljs/load-nyc-config@1.1.0", "", { "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", "get-package-type": "^0.1.0", "js-yaml": "^3.13.1", "resolve-from": "^5.0.0" } }, "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ=="], + + "@istanbuljs/schema": ["@istanbuljs/schema@0.1.3", "", {}, "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA=="], + + "@jest/console": ["@jest/console@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0", "slash": "^3.0.0" } }, "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg=="], + + "@jest/core": ["@jest/core@29.7.0", "", { "dependencies": { "@jest/console": "^29.7.0", "@jest/reporters": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "ci-info": "^3.2.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", "jest-changed-files": "^29.7.0", "jest-config": "^29.7.0", "jest-haste-map": "^29.7.0", "jest-message-util": "^29.7.0", "jest-regex-util": "^29.6.3", "jest-resolve": "^29.7.0", "jest-resolve-dependencies": "^29.7.0", "jest-runner": "^29.7.0", "jest-runtime": "^29.7.0", "jest-snapshot": "^29.7.0", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "jest-watcher": "^29.7.0", "micromatch": "^4.0.4", "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-ansi": "^6.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"] }, "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg=="], + + "@jest/environment": ["@jest/environment@29.7.0", "", { "dependencies": { "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "jest-mock": "^29.7.0" } }, "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw=="], + + "@jest/expect": ["@jest/expect@29.7.0", "", { "dependencies": { "expect": "^29.7.0", "jest-snapshot": "^29.7.0" } }, "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ=="], + + "@jest/expect-utils": ["@jest/expect-utils@29.7.0", "", { "dependencies": { "jest-get-type": "^29.6.3" } }, "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA=="], + + "@jest/fake-timers": ["@jest/fake-timers@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", "jest-message-util": "^29.7.0", "jest-mock": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ=="], + + "@jest/globals": ["@jest/globals@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", "@jest/types": "^29.6.3", "jest-mock": "^29.7.0" } }, "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ=="], + + "@jest/reporters": ["@jest/reporters@29.7.0", "", { "dependencies": { "@bcoe/v8-coverage": "^0.2.3", "@jest/console": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@jridgewell/trace-mapping": "^0.3.18", "@types/node": "*", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", "exit": "^0.1.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0", "jest-worker": "^29.7.0", "slash": "^3.0.0", "string-length": "^4.0.1", "strip-ansi": "^6.0.0", "v8-to-istanbul": "^9.0.1" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"] }, "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg=="], + + "@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="], + + "@jest/source-map": ["@jest/source-map@29.6.3", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", "graceful-fs": "^4.2.9" } }, "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw=="], + + "@jest/test-result": ["@jest/test-result@29.7.0", "", { "dependencies": { "@jest/console": "^29.7.0", "@jest/types": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" } }, "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA=="], + + "@jest/test-sequencer": ["@jest/test-sequencer@29.7.0", "", { "dependencies": { "@jest/test-result": "^29.7.0", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "slash": "^3.0.0" } }, "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw=="], + + "@jest/transform": ["@jest/transform@29.7.0", "", { "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", "@jridgewell/trace-mapping": "^0.3.18", "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "jest-regex-util": "^29.6.3", "jest-util": "^29.7.0", "micromatch": "^4.0.4", "pirates": "^4.0.4", "slash": "^3.0.0", "write-file-atomic": "^4.0.2" } }, "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw=="], + + "@jest/types": ["@jest/types@29.6.3", "", { "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", "@types/yargs": "^17.0.8", "chalk": "^4.0.0" } }, "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/set-array": ["@jridgewell/set-array@1.2.1", "", {}, "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A=="], + + "@jridgewell/source-map": ["@jridgewell/source-map@0.3.6", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" } }, "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="], + + "@medv/finder": ["@medv/finder@4.0.2", "", {}, "sha512-RraNY9SCcx4KZV0Dh6BEW6XEW2swkqYca74pkFFRw6hHItSHiy+O/xMnpbofjYbzXj0tSpBGthUF1hHTsr3vIQ=="], + + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], + + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], + + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + + "@openreplay/network-proxy": ["@openreplay/network-proxy@1.1.0", "", {}, "sha512-p/LP+j8j4v3dXVgfYOIc3FwP8b+u+tSRiBcpBJsBktt29t1vAJW6ge0ww3lEUpP9HMIkkoayWIeYVfwhPpb7cg=="], + + "@openreplay/tracker": ["@openreplay/tracker@workspace:tracker"], + + "@openreplay/tracker-assist": ["@openreplay/tracker-assist@workspace:tracker-assist"], + + "@pkgr/core": ["@pkgr/core@0.2.0", "", {}, "sha512-vsJDAkYR6qCPu+ioGScGiMYR7LvZYIXh/dlQeviqoTWNCVfKTLYD/LkNWH4Mxsv2a5vpIRc77FN5DnmK1eBggQ=="], + + "@rollup/plugin-babel": ["@rollup/plugin-babel@6.0.4", "", { "dependencies": { "@babel/helper-module-imports": "^7.18.6", "@rollup/pluginutils": "^5.0.1" }, "peerDependencies": { "@babel/core": "^7.0.0", "@types/babel__core": "^7.1.9", "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["@types/babel__core", "rollup"] }, "sha512-YF7Y52kFdFT/xVSuVdjkV5ZdX/3YtmX0QulG+x0taQOtJdHYzVU61aSSkAgVJ7NOv6qPkIYiJSgSWWN/DM5sGw=="], + + "@rollup/plugin-commonjs": ["@rollup/plugin-commonjs@28.0.3", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "commondir": "^1.0.1", "estree-walker": "^2.0.2", "fdir": "^6.2.0", "is-reference": "1.2.1", "magic-string": "^0.30.3", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^2.68.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-pyltgilam1QPdn+Zd9gaCfOLcnjMEJ9gV+bTw6/r73INdvzf1ah9zLIJBm+kW7R6IUFIQ1YO+VqZtYxZNWFPEQ=="], + + "@rollup/plugin-node-resolve": ["@rollup/plugin-node-resolve@15.3.1", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "@types/resolve": "1.20.2", "deepmerge": "^4.2.2", "is-module": "^1.0.0", "resolve": "^1.22.1" }, "peerDependencies": { "rollup": "^2.78.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA=="], + + "@rollup/plugin-replace": ["@rollup/plugin-replace@6.0.2", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "magic-string": "^0.30.3" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-7QaYCf8bqF04dOy7w/eHmJeNExxTYwvKAmlSAH/EaWWUzbT0h5sbF6bktFoX/0F/0qwng5/dWFMyf3gzaM8DsQ=="], + + "@rollup/plugin-terser": ["@rollup/plugin-terser@0.4.4", "", { "dependencies": { "serialize-javascript": "^6.0.1", "smob": "^1.0.0", "terser": "^5.17.4" }, "peerDependencies": { "rollup": "^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A=="], + + "@rollup/plugin-typescript": ["@rollup/plugin-typescript@12.1.2", "", { "dependencies": { "@rollup/pluginutils": "^5.1.0", "resolve": "^1.22.1" }, "peerDependencies": { "rollup": "^2.14.0||^3.0.0||^4.0.0", "tslib": "*", "typescript": ">=3.7.0" }, "optionalPeers": ["rollup", "tslib"] }, "sha512-cdtSp154H5sv637uMr1a8OTWB0L1SWDSm1rDGiyfcGcvQ6cuTs4MDk2BVEBGysUWago4OJN4EQZqOTl/QY3Jgg=="], + + "@rollup/pluginutils": ["@rollup/pluginutils@5.1.4", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.38.0", "", { "os": "android", "cpu": "arm" }, "sha512-ldomqc4/jDZu/xpYU+aRxo3V4mGCV9HeTgUBANI3oIQMOL+SsxB+S2lxMpkFp5UamSS3XuTMQVbsS24R4J4Qjg=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.38.0", "", { "os": "android", "cpu": "arm64" }, "sha512-VUsgcy4GhhT7rokwzYQP+aV9XnSLkkhlEJ0St8pbasuWO/vwphhZQxYEKUP3ayeCYLhk6gEtacRpYP/cj3GjyQ=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.38.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-buA17AYXlW9Rn091sWMq1xGUvWQFOH4N1rqUxGJtEQzhChxWjldGCCup7r/wUnaI6Au8sKXpoh0xg58a7cgcpg=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.38.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Mgcmc78AjunP1SKXl624vVBOF2bzwNWFPMP4fpOu05vS0amnLcX8gHIge7q/lDAHy3T2HeR0TqrriZDQS2Woeg=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.38.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-zzJACgjLbQTsscxWqvrEQAEh28hqhebpRz5q/uUd1T7VTwUNZ4VIXQt5hE7ncs0GrF+s7d3S4on4TiXUY8KoQA=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.38.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-hCY/KAeYMCyDpEE4pTETam0XZS4/5GXzlLgpi5f0IaPExw9kuB+PDTOTLuPtM10TlRG0U9OSmXJ+Wq9J39LvAg=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.38.0", "", { "os": "linux", "cpu": "arm" }, "sha512-mimPH43mHl4JdOTD7bUMFhBdrg6f9HzMTOEnzRmXbOZqjijCw8LA5z8uL6LCjxSa67H2xiLFvvO67PT05PRKGg=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.38.0", "", { "os": "linux", "cpu": "arm" }, "sha512-tPiJtiOoNuIH8XGG8sWoMMkAMm98PUwlriOFCCbZGc9WCax+GLeVRhmaxjJtz6WxrPKACgrwoZ5ia/uapq3ZVg=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.38.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-wZco59rIVuB0tjQS0CSHTTUcEde+pXQWugZVxWaQFdQQ1VYub/sTrNdY76D1MKdN2NB48JDuGABP6o6fqos8mA=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.38.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-fQgqwKmW0REM4LomQ+87PP8w8xvU9LZfeLBKybeli+0yHT7VKILINzFEuggvnV9M3x1Ed4gUBmGUzCo/ikmFbQ=="], + + "@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.38.0", "", { "os": "linux", "cpu": "none" }, "sha512-hz5oqQLXTB3SbXpfkKHKXLdIp02/w3M+ajp8p4yWOWwQRtHWiEOCKtc9U+YXahrwdk+3qHdFMDWR5k+4dIlddg=="], + + "@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.38.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NXqygK/dTSibQ+0pzxsL3r4Xl8oPqVoWbZV9niqOnIHV/J92fe65pOir0xjkUZDRSPyFRvu+4YOpJF9BZHQImw=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.38.0", "", { "os": "linux", "cpu": "none" }, "sha512-GEAIabR1uFyvf/jW/5jfu8gjM06/4kZ1W+j1nWTSSB3w6moZEBm7iBtzwQ3a1Pxos2F7Gz+58aVEnZHU295QTg=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.38.0", "", { "os": "linux", "cpu": "none" }, "sha512-9EYTX+Gus2EGPbfs+fh7l95wVADtSQyYw4DfSBcYdUEAmP2lqSZY0Y17yX/3m5VKGGJ4UmIH5LHLkMJft3bYoA=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.38.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-Mpp6+Z5VhB9VDk7RwZXoG2qMdERm3Jw07RNlXHE0bOnEeX+l7Fy4bg+NxfyN15ruuY3/7Vrbpm75J9QHFqj5+Q=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.38.0", "", { "os": "linux", "cpu": "x64" }, "sha512-vPvNgFlZRAgO7rwncMeE0+8c4Hmc+qixnp00/Uv3ht2x7KYrJ6ERVd3/R0nUtlE6/hu7/HiiNHJ/rP6knRFt1w=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.38.0", "", { "os": "linux", "cpu": "x64" }, "sha512-q5Zv+goWvQUGCaL7fU8NuTw8aydIL/C9abAVGCzRReuj5h30TPx4LumBtAidrVOtXnlB+RZkBtExMsfqkMfb8g=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.38.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-u/Jbm1BU89Vftqyqbmxdq14nBaQjQX1HhmsdBWqSdGClNaKwhjsg5TpW+5Ibs1mb8Es9wJiMdl86BcmtUVXNZg=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.38.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-mqu4PzTrlpNHHbu5qleGvXJoGgHpChBlrBx/mEhTPpnAL1ZAYFlvHD7rLK839LLKQzqEQMFJfGrrOHItN4ZQqA=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.38.0", "", { "os": "win32", "cpu": "x64" }, "sha512-jjqy3uWlecfB98Psxb5cD6Fny9Fupv9LrDSPTQZUROqjvZmcCqNu4UMl7qqhlUUGpwiAkotj6GYu4SZdcr/nLw=="], + + "@sinclair/typebox": ["@sinclair/typebox@0.27.8", "", {}, "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA=="], + + "@sinonjs/commons": ["@sinonjs/commons@3.0.1", "", { "dependencies": { "type-detect": "4.0.8" } }, "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ=="], + + "@sinonjs/fake-timers": ["@sinonjs/fake-timers@10.3.0", "", { "dependencies": { "@sinonjs/commons": "^3.0.0" } }, "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA=="], + + "@socket.io/component-emitter": ["@socket.io/component-emitter@3.1.2", "", {}, "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="], + + "@tootallnate/once": ["@tootallnate/once@2.0.0", "", {}, "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A=="], + + "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], + + "@types/babel__generator": ["@types/babel__generator@7.6.8", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw=="], + + "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="], + + "@types/babel__traverse": ["@types/babel__traverse@7.20.7", "", { "dependencies": { "@babel/types": "^7.20.7" } }, "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng=="], + + "@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="], + + "@types/glob": ["@types/glob@7.2.0", "", { "dependencies": { "@types/minimatch": "*", "@types/node": "*" } }, "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA=="], + + "@types/graceful-fs": ["@types/graceful-fs@4.1.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ=="], + + "@types/istanbul-lib-coverage": ["@types/istanbul-lib-coverage@2.0.6", "", {}, "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w=="], + + "@types/istanbul-lib-report": ["@types/istanbul-lib-report@3.0.3", "", { "dependencies": { "@types/istanbul-lib-coverage": "*" } }, "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA=="], + + "@types/istanbul-reports": ["@types/istanbul-reports@3.0.4", "", { "dependencies": { "@types/istanbul-lib-report": "*" } }, "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ=="], + + "@types/jsdom": ["@types/jsdom@20.0.1", "", { "dependencies": { "@types/node": "*", "@types/tough-cookie": "*", "parse5": "^7.0.0" } }, "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ=="], + + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + + "@types/minimatch": ["@types/minimatch@5.1.2", "", {}, "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA=="], + + "@types/minimist": ["@types/minimist@1.2.5", "", {}, "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag=="], + + "@types/node": ["@types/node@22.13.16", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-15tM+qA4Ypml/N7kyRdvfRjBQT2RL461uF1Bldn06K0Nzn1lY3nAPgHlsVrJxdZ9WhZiW0Fmc1lOYMtDsAuB3w=="], + + "@types/normalize-package-data": ["@types/normalize-package-data@2.4.4", "", {}, "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA=="], + + "@types/resolve": ["@types/resolve@1.20.2", "", {}, "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="], + + "@types/stack-utils": ["@types/stack-utils@2.0.3", "", {}, "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw=="], + + "@types/tough-cookie": ["@types/tough-cookie@4.0.5", "", {}, "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA=="], + + "@types/yargs": ["@types/yargs@17.0.33", "", { "dependencies": { "@types/yargs-parser": "*" } }, "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA=="], + + "@types/yargs-parser": ["@types/yargs-parser@21.0.3", "", {}, "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ=="], + + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.29.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.29.0", "@typescript-eslint/type-utils": "8.29.0", "@typescript-eslint/utils": "8.29.0", "@typescript-eslint/visitor-keys": "8.29.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-PAIpk/U7NIS6H7TEtN45SPGLQaHNgB7wSjsQV/8+KYokAb2T/gloOA/Bee2yd4/yKVhPKe5LlaUGhAZk5zmSaQ=="], + + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.29.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.29.0", "@typescript-eslint/types": "8.29.0", "@typescript-eslint/typescript-estree": "8.29.0", "@typescript-eslint/visitor-keys": "8.29.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-8C0+jlNJOwQso2GapCVWWfW/rzaq7Lbme+vGUFKE31djwNncIpgXD7Cd4weEsDdkoZDjH0lwwr3QDQFuyrMg9g=="], + + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.29.0", "", { "dependencies": { "@typescript-eslint/types": "8.29.0", "@typescript-eslint/visitor-keys": "8.29.0" } }, "sha512-aO1PVsq7Gm+tcghabUpzEnVSFMCU4/nYIgC2GOatJcllvWfnhrgW0ZEbnTxm36QsikmCN1K/6ZgM7fok2I7xNw=="], + + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.29.0", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.29.0", "@typescript-eslint/utils": "8.29.0", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-ahaWQ42JAOx+NKEf5++WC/ua17q5l+j1GFrbbpVKzFL/tKVc0aYY8rVSYUpUvt2hUP1YBr7mwXzx+E/DfUWI9Q=="], + + "@typescript-eslint/types": ["@typescript-eslint/types@8.29.0", "", {}, "sha512-wcJL/+cOXV+RE3gjCyl/V2G877+2faqvlgtso/ZRbTCnZazh0gXhe+7gbAnfubzN2bNsBtZjDvlh7ero8uIbzg=="], + + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.29.0", "", { "dependencies": { "@typescript-eslint/types": "8.29.0", "@typescript-eslint/visitor-keys": "8.29.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-yOfen3jE9ISZR/hHpU/bmNvTtBW1NjRbkSFdZOksL1N+ybPEE7UVGMwqvS6CP022Rp00Sb0tdiIkhSCe6NI8ow=="], + + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.29.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "8.29.0", "@typescript-eslint/types": "8.29.0", "@typescript-eslint/typescript-estree": "8.29.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-gX/A0Mz9Bskm8avSWFcK0gP7cZpbY4AIo6B0hWYFCaIsz750oaiWR4Jr2CI+PQhfW1CpcQr9OlfPS+kMFegjXA=="], + + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.29.0", "", { "dependencies": { "@typescript-eslint/types": "8.29.0", "eslint-visitor-keys": "^4.2.0" } }, "sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg=="], + + "abab": ["abab@2.0.6", "", {}, "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA=="], + + "acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="], + + "acorn-globals": ["acorn-globals@7.0.1", "", { "dependencies": { "acorn": "^8.1.0", "acorn-walk": "^8.0.2" } }, "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q=="], + + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + + "acorn-walk": ["acorn-walk@8.3.4", "", { "dependencies": { "acorn": "^8.11.0" } }, "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g=="], + + "agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], + + "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], + + "ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="], + + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "array-union": ["array-union@2.1.0", "", {}, "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw=="], + + "arrify": ["arrify@2.0.1", "", {}, "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug=="], + + "async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="], + + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + + "babel-jest": ["babel-jest@29.7.0", "", { "dependencies": { "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", "babel-preset-jest": "^29.6.3", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "slash": "^3.0.0" }, "peerDependencies": { "@babel/core": "^7.8.0" } }, "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg=="], + + "babel-plugin-istanbul": ["babel-plugin-istanbul@6.1.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-instrument": "^5.0.4", "test-exclude": "^6.0.0" } }, "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA=="], + + "babel-plugin-jest-hoist": ["babel-plugin-jest-hoist@29.6.3", "", { "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", "@types/babel__core": "^7.1.14", "@types/babel__traverse": "^7.0.6" } }, "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg=="], + + "babel-preset-current-node-syntax": ["babel-preset-current-node-syntax@1.1.0", "", { "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-import-attributes": "^7.24.7", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-numeric-separator": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw=="], + + "babel-preset-jest": ["babel-preset-jest@29.6.3", "", { "dependencies": { "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "binaryextensions": ["binaryextensions@2.3.0", "", {}, "sha512-nAihlQsYGyc5Bwq6+EsubvANYGExeJKHDO3RjnvwU042fawQTQfM3Kxn7IHUXQOz4bzfwsGYYHGSvXyW4zOGLg=="], + + "brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "browserslist": ["browserslist@4.24.4", "", { "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" } }, "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A=="], + + "bs-logger": ["bs-logger@0.2.6", "", { "dependencies": { "fast-json-stable-stringify": "2.x" } }, "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog=="], + + "bser": ["bser@2.1.1", "", { "dependencies": { "node-int64": "^0.4.0" } }, "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ=="], + + "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + + "camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="], + + "camelcase-keys": ["camelcase-keys@6.2.2", "", { "dependencies": { "camelcase": "^5.3.1", "map-obj": "^4.0.0", "quick-lru": "^4.0.1" } }, "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001707", "", {}, "sha512-3qtRjw/HQSMlDWf+X79N206fepf4SOOU6SQLMaq/0KkZLmSjPxAkBOQQ+FxbHKfHmYLZFfdWsO3KA90ceHPSnw=="], + + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "char-regex": ["char-regex@1.0.2", "", {}, "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw=="], + + "ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="], + + "cjs-module-lexer": ["cjs-module-lexer@1.4.3", "", {}, "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q=="], + + "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "co": ["co@4.6.0", "", {}, "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ=="], + + "collect-v8-coverage": ["collect-v8-coverage@1.0.2", "", {}, "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], + + "commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], + + "commondir": ["commondir@1.0.1", "", {}, "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="], + + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "create-jest": ["create-jest@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", "jest-config": "^29.7.0", "jest-util": "^29.7.0", "prompts": "^2.0.1" }, "bin": { "create-jest": "bin/create-jest.js" } }, "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "cssom": ["cssom@0.5.0", "", {}, "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw=="], + + "cssstyle": ["cssstyle@2.3.0", "", { "dependencies": { "cssom": "~0.3.6" } }, "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A=="], + + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + + "data-urls": ["data-urls@3.0.2", "", { "dependencies": { "abab": "^2.0.6", "whatwg-mimetype": "^3.0.0", "whatwg-url": "^11.0.0" } }, "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ=="], + + "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], + + "decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="], + + "decamelize-keys": ["decamelize-keys@1.1.1", "", { "dependencies": { "decamelize": "^1.1.0", "map-obj": "^1.0.0" } }, "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg=="], + + "decimal.js": ["decimal.js@10.5.0", "", {}, "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw=="], + + "dedent": ["dedent@1.5.3", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ=="], + + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + + "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], + + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + + "detect-newline": ["detect-newline@3.1.0", "", {}, "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA=="], + + "diff-sequences": ["diff-sequences@29.6.3", "", {}, "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q=="], + + "dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="], + + "domexception": ["domexception@4.0.0", "", { "dependencies": { "webidl-conversions": "^7.0.0" } }, "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw=="], + + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + + "editions": ["editions@2.3.1", "", { "dependencies": { "errlop": "^2.0.0", "semver": "^6.3.0" } }, "sha512-ptGvkwTvGdGfC0hfhKg0MT+TRLRKGtUiWGBInxOm5pz7ssADezahjCUaYuZ8Dr+C05FW0AECIIPt4WBxVINEhA=="], + + "ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.129", "", {}, "sha512-JlXUemX4s0+9f8mLqib/bHH8gOHf5elKS6KeWG3sk3xozb/JTq/RLXIv8OKUWiK4Ah00Wm88EFj5PYkFr4RUPA=="], + + "emittery": ["emittery@0.13.1", "", {}, "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ=="], + + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "engine.io-client": ["engine.io-client@6.6.3", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", "ws": "~8.17.1", "xmlhttprequest-ssl": "~2.1.1" } }, "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w=="], + + "engine.io-parser": ["engine.io-parser@5.2.3", "", {}, "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q=="], + + "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + + "errlop": ["errlop@2.2.0", "", {}, "sha512-e64Qj9+4aZzjzzFpZC7p5kmm/ccCrbLhAJplhsDXQFs87XTsXwOpH4s1Io2s90Tau/8r2j9f4l/thhDevRjzxw=="], + + "error-ex": ["error-ex@1.3.2", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g=="], + + "error-stack-parser": ["error-stack-parser@2.1.4", "", { "dependencies": { "stackframe": "^1.3.4" } }, "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ=="], + + "error-stack-parser-es": ["error-stack-parser-es@0.1.5", "", {}, "sha512-xHku1X40RO+fO8yJ8Wh2f2rZWVjqyhb1zgq1yZ8aZRQkv6OOKhKWRUaht3eSCUbAOBaKIgM+ykwFLE+QUxgGeg=="], + + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], + + "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + + "es6-promisify": ["es6-promisify@6.1.1", "", {}, "sha512-HBL8I3mIki5C1Cc9QjKUenHtnG0A5/xA8Q/AllRcfiwl2CZFXGK7ddBiCoRwAix4i2KxcQfjtIVcrVbB3vbmwg=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "escodegen": ["escodegen@2.1.0", "", { "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", "esutils": "^2.0.2" }, "optionalDependencies": { "source-map": "~0.6.1" }, "bin": { "esgenerate": "bin/esgenerate.js", "escodegen": "bin/escodegen.js" } }, "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w=="], + + "eslint": ["eslint@9.23.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.19.2", "@eslint/config-helpers": "^0.2.0", "@eslint/core": "^0.12.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.23.0", "@eslint/plugin-kit": "^0.2.7", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.3.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-jV7AbNoFPAY1EkFYpLq5bslU9NLNO8xnEeQXwErNibVryjk67wHVmddTBilc5srIttJDBrB0eMHKZBFbSIABCw=="], + + "eslint-config-prettier": ["eslint-config-prettier@9.1.0", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw=="], + + "eslint-plugin-prettier": ["eslint-plugin-prettier@5.2.5", "", { "dependencies": { "prettier-linter-helpers": "^1.0.0", "synckit": "^0.10.2" }, "peerDependencies": { "@types/eslint": ">=8.0.0", "eslint": ">=8.0.0", "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", "prettier": ">=3.0.0" }, "optionalPeers": ["@types/eslint", "eslint-config-prettier"] }, "sha512-IKKP8R87pJyMl7WWamLgPkloB16dagPIdd2FjBDbyRYPKo93wS/NbCOPh6gH+ieNLC+XZrhJt/kWj0PS/DFdmg=="], + + "eslint-scope": ["eslint-scope@8.3.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ=="], + + "eslint-visitor-keys": ["eslint-visitor-keys@4.2.0", "", {}, "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw=="], + + "espree": ["espree@10.3.0", "", { "dependencies": { "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.0" } }, "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg=="], + + "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], + + "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + + "execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="], + + "exit": ["exit@0.1.2", "", {}, "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ=="], + + "expect": ["expect@29.7.0", "", { "dependencies": { "@jest/expect-utils": "^29.7.0", "jest-get-type": "^29.6.3", "jest-matcher-utils": "^29.7.0", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-diff": ["fast-diff@1.3.0", "", {}, "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw=="], + + "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + + "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], + + "fb-watchman": ["fb-watchman@2.0.2", "", { "dependencies": { "bser": "2.1.1" } }, "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA=="], + + "fdir": ["fdir@6.4.3", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw=="], + + "fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="], + + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], + + "filelist": ["filelist@1.0.4", "", { "dependencies": { "minimatch": "^5.0.1" } }, "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], + + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], + + "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], + + "form-data": ["form-data@4.0.2", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "mime-types": "^2.1.12" } }, "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w=="], + + "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-package-type": ["get-package-type@0.1.0", "", {}, "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q=="], + + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + + "get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="], + + "glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + + "globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="], + + "globby": ["globby@10.0.2", "", { "dependencies": { "@types/glob": "^7.1.1", "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.0.3", "glob": "^7.1.3", "ignore": "^5.1.1", "merge2": "^1.2.3", "slash": "^3.0.0" } }, "sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg=="], + + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], + + "hard-rejection": ["hard-rejection@2.1.0", "", {}, "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], + + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + + "hosted-git-info": ["hosted-git-info@2.8.9", "", {}, "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw=="], + + "html-encoding-sniffer": ["html-encoding-sniffer@3.0.0", "", { "dependencies": { "whatwg-encoding": "^2.0.0" } }, "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA=="], + + "html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="], + + "http-proxy-agent": ["http-proxy-agent@5.0.0", "", { "dependencies": { "@tootallnate/once": "2", "agent-base": "6", "debug": "4" } }, "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w=="], + + "https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], + + "human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="], + + "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + + "import-local": ["import-local@3.2.0", "", { "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" }, "bin": { "import-local-fixture": "fixtures/cli.js" } }, "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA=="], + + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + + "indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="], + + "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="], + + "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "is-generator-fn": ["is-generator-fn@2.1.0", "", {}, "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-module": ["is-module@1.0.0", "", {}, "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "is-plain-obj": ["is-plain-obj@1.1.0", "", {}, "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg=="], + + "is-potential-custom-element-name": ["is-potential-custom-element-name@1.0.1", "", {}, "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="], + + "is-reference": ["is-reference@1.2.1", "", { "dependencies": { "@types/estree": "*" } }, "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ=="], + + "is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], + + "is-typedarray": ["is-typedarray@1.0.0", "", {}, "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="], + + "istanbul-lib-instrument": ["istanbul-lib-instrument@6.0.3", "", { "dependencies": { "@babel/core": "^7.23.9", "@babel/parser": "^7.23.9", "@istanbuljs/schema": "^0.1.3", "istanbul-lib-coverage": "^3.2.0", "semver": "^7.5.4" } }, "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q=="], + + "istanbul-lib-report": ["istanbul-lib-report@3.0.1", "", { "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", "supports-color": "^7.1.0" } }, "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw=="], + + "istanbul-lib-source-maps": ["istanbul-lib-source-maps@4.0.1", "", { "dependencies": { "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", "source-map": "^0.6.1" } }, "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw=="], + + "istanbul-reports": ["istanbul-reports@3.1.7", "", { "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" } }, "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g=="], + + "istextorbinary": ["istextorbinary@2.6.0", "", { "dependencies": { "binaryextensions": "^2.1.2", "editions": "^2.2.0", "textextensions": "^2.5.0" } }, "sha512-+XRlFseT8B3L9KyjxxLjfXSLMuErKDsd8DBNrsaxoViABMEZlOSCstwmw0qpoFX3+U6yWU1yhLudAe6/lETGGA=="], + + "jake": ["jake@10.9.2", "", { "dependencies": { "async": "^3.2.3", "chalk": "^4.0.2", "filelist": "^1.0.4", "minimatch": "^3.1.2" }, "bin": { "jake": "bin/cli.js" } }, "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA=="], + + "jest": ["jest@29.7.0", "", { "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", "import-local": "^3.0.2", "jest-cli": "^29.7.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"], "bin": { "jest": "bin/jest.js" } }, "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw=="], + + "jest-changed-files": ["jest-changed-files@29.7.0", "", { "dependencies": { "execa": "^5.0.0", "jest-util": "^29.7.0", "p-limit": "^3.1.0" } }, "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w=="], + + "jest-circus": ["jest-circus@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", "dedent": "^1.0.0", "is-generator-fn": "^2.0.0", "jest-each": "^29.7.0", "jest-matcher-utils": "^29.7.0", "jest-message-util": "^29.7.0", "jest-runtime": "^29.7.0", "jest-snapshot": "^29.7.0", "jest-util": "^29.7.0", "p-limit": "^3.1.0", "pretty-format": "^29.7.0", "pure-rand": "^6.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" } }, "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw=="], + + "jest-cli": ["jest-cli@29.7.0", "", { "dependencies": { "@jest/core": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "chalk": "^4.0.0", "create-jest": "^29.7.0", "exit": "^0.1.2", "import-local": "^3.0.2", "jest-config": "^29.7.0", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "yargs": "^17.3.1" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"], "bin": { "jest": "bin/jest.js" } }, "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg=="], + + "jest-config": ["jest-config@29.7.0", "", { "dependencies": { "@babel/core": "^7.11.6", "@jest/test-sequencer": "^29.7.0", "@jest/types": "^29.6.3", "babel-jest": "^29.7.0", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", "jest-circus": "^29.7.0", "jest-environment-node": "^29.7.0", "jest-get-type": "^29.6.3", "jest-regex-util": "^29.6.3", "jest-resolve": "^29.7.0", "jest-runner": "^29.7.0", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "micromatch": "^4.0.4", "parse-json": "^5.2.0", "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, "peerDependencies": { "@types/node": "*", "ts-node": ">=9.0.0" }, "optionalPeers": ["@types/node", "ts-node"] }, "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ=="], + + "jest-diff": ["jest-diff@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.6.3", "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw=="], + + "jest-docblock": ["jest-docblock@29.7.0", "", { "dependencies": { "detect-newline": "^3.0.0" } }, "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g=="], + + "jest-each": ["jest-each@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", "jest-get-type": "^29.6.3", "jest-util": "^29.7.0", "pretty-format": "^29.7.0" } }, "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ=="], + + "jest-environment-jsdom": ["jest-environment-jsdom@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/jsdom": "^20.0.0", "@types/node": "*", "jest-mock": "^29.7.0", "jest-util": "^29.7.0", "jsdom": "^20.0.0" }, "peerDependencies": { "canvas": "^2.5.0" }, "optionalPeers": ["canvas"] }, "sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA=="], + + "jest-environment-node": ["jest-environment-node@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "jest-mock": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw=="], + + "jest-get-type": ["jest-get-type@29.6.3", "", {}, "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw=="], + + "jest-haste-map": ["jest-haste-map@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "graceful-fs": "^4.2.9", "jest-regex-util": "^29.6.3", "jest-util": "^29.7.0", "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "walker": "^1.0.8" }, "optionalDependencies": { "fsevents": "^2.3.2" } }, "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA=="], + + "jest-leak-detector": ["jest-leak-detector@29.7.0", "", { "dependencies": { "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw=="], + + "jest-matcher-utils": ["jest-matcher-utils@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "jest-diff": "^29.7.0", "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g=="], + + "jest-message-util": ["jest-message-util@29.7.0", "", { "dependencies": { "@babel/code-frame": "^7.12.13", "@jest/types": "^29.6.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" } }, "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w=="], + + "jest-mock": ["jest-mock@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "jest-util": "^29.7.0" } }, "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw=="], + + "jest-pnp-resolver": ["jest-pnp-resolver@1.2.3", "", { "peerDependencies": { "jest-resolve": "*" }, "optionalPeers": ["jest-resolve"] }, "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w=="], + + "jest-regex-util": ["jest-regex-util@29.6.3", "", {}, "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg=="], + + "jest-resolve": ["jest-resolve@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "jest-pnp-resolver": "^1.2.2", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "resolve": "^1.20.0", "resolve.exports": "^2.0.0", "slash": "^3.0.0" } }, "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA=="], + + "jest-resolve-dependencies": ["jest-resolve-dependencies@29.7.0", "", { "dependencies": { "jest-regex-util": "^29.6.3", "jest-snapshot": "^29.7.0" } }, "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA=="], + + "jest-runner": ["jest-runner@29.7.0", "", { "dependencies": { "@jest/console": "^29.7.0", "@jest/environment": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "emittery": "^0.13.1", "graceful-fs": "^4.2.9", "jest-docblock": "^29.7.0", "jest-environment-node": "^29.7.0", "jest-haste-map": "^29.7.0", "jest-leak-detector": "^29.7.0", "jest-message-util": "^29.7.0", "jest-resolve": "^29.7.0", "jest-runtime": "^29.7.0", "jest-util": "^29.7.0", "jest-watcher": "^29.7.0", "jest-worker": "^29.7.0", "p-limit": "^3.1.0", "source-map-support": "0.5.13" } }, "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ=="], + + "jest-runtime": ["jest-runtime@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", "@jest/globals": "^29.7.0", "@jest/source-map": "^29.6.3", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "cjs-module-lexer": "^1.0.0", "collect-v8-coverage": "^1.0.0", "glob": "^7.1.3", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "jest-message-util": "^29.7.0", "jest-mock": "^29.7.0", "jest-regex-util": "^29.6.3", "jest-resolve": "^29.7.0", "jest-snapshot": "^29.7.0", "jest-util": "^29.7.0", "slash": "^3.0.0", "strip-bom": "^4.0.0" } }, "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ=="], + + "jest-snapshot": ["jest-snapshot@29.7.0", "", { "dependencies": { "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", "@babel/plugin-syntax-jsx": "^7.7.2", "@babel/plugin-syntax-typescript": "^7.7.2", "@babel/types": "^7.3.3", "@jest/expect-utils": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", "expect": "^29.7.0", "graceful-fs": "^4.2.9", "jest-diff": "^29.7.0", "jest-get-type": "^29.6.3", "jest-matcher-utils": "^29.7.0", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0", "natural-compare": "^1.4.0", "pretty-format": "^29.7.0", "semver": "^7.5.3" } }, "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw=="], + + "jest-util": ["jest-util@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", "picomatch": "^2.2.3" } }, "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA=="], + + "jest-validate": ["jest-validate@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "camelcase": "^6.2.0", "chalk": "^4.0.0", "jest-get-type": "^29.6.3", "leven": "^3.1.0", "pretty-format": "^29.7.0" } }, "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw=="], + + "jest-watcher": ["jest-watcher@29.7.0", "", { "dependencies": { "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "emittery": "^0.13.1", "jest-util": "^29.7.0", "string-length": "^4.0.1" } }, "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g=="], + + "jest-worker": ["jest-worker@29.7.0", "", { "dependencies": { "@types/node": "*", "jest-util": "^29.7.0", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" } }, "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], + + "jsdom": ["jsdom@20.0.3", "", { "dependencies": { "abab": "^2.0.6", "acorn": "^8.8.1", "acorn-globals": "^7.0.0", "cssom": "^0.5.0", "cssstyle": "^2.3.0", "data-urls": "^3.0.2", "decimal.js": "^10.4.2", "domexception": "^4.0.0", "escodegen": "^2.0.0", "form-data": "^4.0.0", "html-encoding-sniffer": "^3.0.0", "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.1", "is-potential-custom-element-name": "^1.0.1", "nwsapi": "^2.2.2", "parse5": "^7.1.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^4.1.2", "w3c-xmlserializer": "^4.0.0", "webidl-conversions": "^7.0.0", "whatwg-encoding": "^2.0.0", "whatwg-mimetype": "^3.0.0", "whatwg-url": "^11.0.0", "ws": "^8.11.0", "xml-name-validator": "^4.0.0" }, "peerDependencies": { "canvas": "^2.5.0" }, "optionalPeers": ["canvas"] }, "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ=="], + + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + + "json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="], + + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + + "kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="], + + "kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], + + "leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="], + + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + + "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], + + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + + "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], + + "lodash.memoize": ["lodash.memoize@4.1.2", "", {}, "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag=="], + + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + + "magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="], + + "make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="], + + "make-error": ["make-error@1.3.6", "", {}, "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw=="], + + "makeerror": ["makeerror@1.0.12", "", { "dependencies": { "tmpl": "1.0.5" } }, "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg=="], + + "map-obj": ["map-obj@4.3.0", "", {}, "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ=="], + + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "meow": ["meow@7.1.1", "", { "dependencies": { "@types/minimist": "^1.2.0", "camelcase-keys": "^6.2.2", "decamelize-keys": "^1.1.0", "hard-rejection": "^2.1.0", "minimist-options": "4.1.0", "normalize-package-data": "^2.5.0", "read-pkg-up": "^7.0.1", "redent": "^3.0.0", "trim-newlines": "^3.0.0", "type-fest": "^0.13.1", "yargs-parser": "^18.1.3" } }, "sha512-GWHvA5QOcS412WCo8vwKDlTelGLsCGBVevQB5Kva961rmNfun0PCbv5+xta2kUMFJyR8/oWnn7ddeKdosbAPbA=="], + + "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="], + + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], + + "min-indent": ["min-indent@1.0.1", "", {}, "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="], + + "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + + "minimist-options": ["minimist-options@4.1.0", "", { "dependencies": { "arrify": "^1.0.1", "is-plain-obj": "^1.1.0", "kind-of": "^6.0.3" } }, "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + + "node-int64": ["node-int64@0.4.0", "", {}, "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw=="], + + "node-releases": ["node-releases@2.0.19", "", {}, "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="], + + "normalize-package-data": ["normalize-package-data@2.5.0", "", { "dependencies": { "hosted-git-info": "^2.1.4", "resolve": "^1.10.0", "semver": "2 || 3 || 4 || 5", "validate-npm-package-license": "^3.0.1" } }, "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA=="], + + "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], + + "npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="], + + "nwsapi": ["nwsapi@2.2.20", "", {}, "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA=="], + + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + + "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], + + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + + "p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="], + + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + + "parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], + + "parse5": ["parse5@7.2.1", "", { "dependencies": { "entities": "^4.5.0" } }, "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ=="], + + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + + "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], + + "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="], + + "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="], + + "pkg-dir": ["pkg-dir@4.2.0", "", { "dependencies": { "find-up": "^4.0.0" } }, "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ=="], + + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + + "prettier": ["prettier@3.5.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw=="], + + "prettier-linter-helpers": ["prettier-linter-helpers@1.0.0", "", { "dependencies": { "fast-diff": "^1.1.2" } }, "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w=="], + + "pretty-format": ["pretty-format@29.7.0", "", { "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" } }, "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ=="], + + "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="], + + "psl": ["psl@1.15.0", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w=="], + + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="], + + "querystringify": ["querystringify@2.2.0", "", {}, "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="], + + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + + "quick-lru": ["quick-lru@4.0.1", "", {}, "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g=="], + + "randombytes": ["randombytes@2.1.0", "", { "dependencies": { "safe-buffer": "^5.1.0" } }, "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ=="], + + "react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="], + + "read-pkg": ["read-pkg@5.2.0", "", { "dependencies": { "@types/normalize-package-data": "^2.4.0", "normalize-package-data": "^2.5.0", "parse-json": "^5.0.0", "type-fest": "^0.6.0" } }, "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg=="], + + "read-pkg-up": ["read-pkg-up@7.0.1", "", { "dependencies": { "find-up": "^4.1.0", "read-pkg": "^5.2.0", "type-fest": "^0.8.1" } }, "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg=="], + + "redent": ["redent@3.0.0", "", { "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" } }, "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg=="], + + "replace-in-files": ["replace-in-files@2.0.3", "", { "dependencies": { "co": "^4.6.0", "es6-promisify": "^6.0.2", "globby": "^10.0.1", "istextorbinary": "^2.5.1", "lodash": "^4.17.15" } }, "sha512-wm9kg+WhKQ10CFaCS9jvUtgw9JnaPilm4TqSRq57H06v5EgX7hTrr/qwgyvq3G+IeNhmr6VTW84LvfnpZfq5/g=="], + + "replace-in-files-cli": ["replace-in-files-cli@1.0.0", "", { "dependencies": { "arrify": "^2.0.1", "escape-string-regexp": "^4.0.0", "globby": "^11.0.1", "meow": "^7.1.1", "normalize-path": "^3.0.0", "write-file-atomic": "^3.0.0" }, "bin": { "replace-in-files": "cli.js" } }, "sha512-/HMPLZeCA24CBUQ59ymHji6LyMKM+gEgDZlYsiPvXW6+3PdfOw6SsMCVd9KC2B+KlAEe/8vkJA6gfnexVdF15A=="], + + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + + "requires-port": ["requires-port@1.0.0", "", {}, "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="], + + "resolve": ["resolve@1.22.10", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="], + + "resolve-cwd": ["resolve-cwd@3.0.0", "", { "dependencies": { "resolve-from": "^5.0.0" } }, "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg=="], + + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + + "resolve.exports": ["resolve.exports@2.0.3", "", {}, "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A=="], + + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + + "rollup": ["rollup@4.38.0", "", { "dependencies": { "@types/estree": "1.0.7" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.38.0", "@rollup/rollup-android-arm64": "4.38.0", "@rollup/rollup-darwin-arm64": "4.38.0", "@rollup/rollup-darwin-x64": "4.38.0", "@rollup/rollup-freebsd-arm64": "4.38.0", "@rollup/rollup-freebsd-x64": "4.38.0", "@rollup/rollup-linux-arm-gnueabihf": "4.38.0", "@rollup/rollup-linux-arm-musleabihf": "4.38.0", "@rollup/rollup-linux-arm64-gnu": "4.38.0", "@rollup/rollup-linux-arm64-musl": "4.38.0", "@rollup/rollup-linux-loongarch64-gnu": "4.38.0", "@rollup/rollup-linux-powerpc64le-gnu": "4.38.0", "@rollup/rollup-linux-riscv64-gnu": "4.38.0", "@rollup/rollup-linux-riscv64-musl": "4.38.0", "@rollup/rollup-linux-s390x-gnu": "4.38.0", "@rollup/rollup-linux-x64-gnu": "4.38.0", "@rollup/rollup-linux-x64-musl": "4.38.0", "@rollup/rollup-win32-arm64-msvc": "4.38.0", "@rollup/rollup-win32-ia32-msvc": "4.38.0", "@rollup/rollup-win32-x64-msvc": "4.38.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-5SsIRtJy9bf1ErAOiFMFzl64Ex9X5V7bnJ+WlFMb+zmP459OSWCEG7b0ERZ+PEU7xPt4OG3RHbrp1LJlXxYTrw=="], + + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "saxes": ["saxes@6.0.0", "", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA=="], + + "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "serialize-javascript": ["serialize-javascript@6.0.2", "", { "dependencies": { "randombytes": "^2.1.0" } }, "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], + + "slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], + + "smob": ["smob@1.5.0", "", {}, "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig=="], + + "socket.io-client": ["socket.io-client@4.8.1", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.2", "engine.io-client": "~6.6.1", "socket.io-parser": "~4.2.4" } }, "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ=="], + + "socket.io-parser": ["socket.io-parser@4.2.4", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1" } }, "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew=="], + + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], + + "spdx-correct": ["spdx-correct@3.2.0", "", { "dependencies": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" } }, "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA=="], + + "spdx-exceptions": ["spdx-exceptions@2.5.0", "", {}, "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w=="], + + "spdx-expression-parse": ["spdx-expression-parse@3.0.1", "", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q=="], + + "spdx-license-ids": ["spdx-license-ids@3.0.21", "", {}, "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg=="], + + "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="], + + "stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="], + + "stackframe": ["stackframe@1.3.4", "", {}, "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw=="], + + "string-length": ["string-length@4.0.2", "", { "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" } }, "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ=="], + + "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-bom": ["strip-bom@4.0.0", "", {}, "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w=="], + + "strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="], + + "strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="], + + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], + + "symbol-tree": ["symbol-tree@3.2.4", "", {}, "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="], + + "synckit": ["synckit@0.10.3", "", { "dependencies": { "@pkgr/core": "^0.2.0", "tslib": "^2.8.1" } }, "sha512-R1urvuyiTaWfeCggqEvpDJwAlDVdsT9NM+IP//Tk2x7qHCkSvBk/fwFgw/TLAHzZlrAnnazMcRw0ZD8HlYFTEQ=="], + + "terser": ["terser@5.39.0", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw=="], + + "test-exclude": ["test-exclude@6.0.0", "", { "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", "minimatch": "^3.0.4" } }, "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w=="], + + "textextensions": ["textextensions@2.6.0", "", {}, "sha512-49WtAWS+tcsy93dRt6P0P3AMD2m5PvXRhuEA0kaXos5ZLlujtYmpmFsB+QvWUSxE1ZsstmYXfQ7L40+EcQgpAQ=="], + + "tmpl": ["tmpl@1.0.5", "", {}, "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "tough-cookie": ["tough-cookie@4.1.4", "", { "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", "universalify": "^0.2.0", "url-parse": "^1.5.3" } }, "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag=="], + + "tr46": ["tr46@3.0.0", "", { "dependencies": { "punycode": "^2.1.1" } }, "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA=="], + + "trim-newlines": ["trim-newlines@3.0.1", "", {}, "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw=="], + + "ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="], + + "ts-jest": ["ts-jest@29.3.1", "", { "dependencies": { "bs-logger": "^0.2.6", "ejs": "^3.1.10", "fast-json-stable-stringify": "^2.1.0", "jest-util": "^29.0.0", "json5": "^2.2.3", "lodash.memoize": "^4.1.2", "make-error": "^1.3.6", "semver": "^7.7.1", "type-fest": "^4.38.0", "yargs-parser": "^21.1.1" }, "peerDependencies": { "@babel/core": ">=7.0.0-beta.0 <8", "@jest/transform": "^29.0.0", "@jest/types": "^29.0.0", "babel-jest": "^29.0.0", "jest": "^29.0.0", "typescript": ">=4.3 <6" }, "optionalPeers": ["@babel/core", "@jest/transform", "@jest/types", "babel-jest"], "bin": { "ts-jest": "cli.js" } }, "sha512-FT2PIRtZABwl6+ZCry8IY7JZ3xMuppsEV9qFVHOVe8jDzggwUZ9TsM4chyJxL9yi6LvkqcZYU3LmapEE454zBQ=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], + + "type-detect": ["type-detect@4.0.8", "", {}, "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g=="], + + "type-fest": ["type-fest@4.38.0", "", {}, "sha512-2dBz5D5ycHIoliLYLi0Q2V7KRaDlH0uWIvmk7TYlAg5slqwiPv1ezJdZm1QEM0xgk29oYWMCbIG7E6gHpvChlg=="], + + "typedarray-to-buffer": ["typedarray-to-buffer@3.1.5", "", { "dependencies": { "is-typedarray": "^1.0.0" } }, "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q=="], + + "typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="], + + "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], + + "universalify": ["universalify@0.2.0", "", {}, "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg=="], + + "update-browserslist-db": ["update-browserslist-db@1.1.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw=="], + + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + + "url-parse": ["url-parse@1.5.10", "", { "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" } }, "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ=="], + + "v8-to-istanbul": ["v8-to-istanbul@9.3.0", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", "convert-source-map": "^2.0.0" } }, "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA=="], + + "validate-npm-package-license": ["validate-npm-package-license@3.0.4", "", { "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" } }, "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew=="], + + "w3c-xmlserializer": ["w3c-xmlserializer@4.0.0", "", { "dependencies": { "xml-name-validator": "^4.0.0" } }, "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw=="], + + "walker": ["walker@1.0.8", "", { "dependencies": { "makeerror": "1.0.12" } }, "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ=="], + + "web-vitals": ["web-vitals@4.2.4", "", {}, "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw=="], + + "webidl-conversions": ["webidl-conversions@7.0.0", "", {}, "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="], + + "whatwg-encoding": ["whatwg-encoding@2.0.0", "", { "dependencies": { "iconv-lite": "0.6.3" } }, "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg=="], + + "whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="], + + "whatwg-url": ["whatwg-url@11.0.0", "", { "dependencies": { "tr46": "^3.0.0", "webidl-conversions": "^7.0.0" } }, "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + + "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + + "write-file-atomic": ["write-file-atomic@3.0.3", "", { "dependencies": { "imurmurhash": "^0.1.4", "is-typedarray": "^1.0.0", "signal-exit": "^3.0.2", "typedarray-to-buffer": "^3.1.5" } }, "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q=="], + + "ws": ["ws@8.18.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w=="], + + "xml-name-validator": ["xml-name-validator@4.0.0", "", {}, "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw=="], + + "xmlchars": ["xmlchars@2.2.0", "", {}, "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="], + + "xmlhttprequest-ssl": ["xmlhttprequest-ssl@2.1.2", "", {}, "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ=="], + + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + + "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], + + "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + + "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], + + "@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="], + + "@istanbuljs/load-nyc-config/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="], + + "@istanbuljs/load-nyc-config/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], + + "@istanbuljs/load-nyc-config/js-yaml": ["js-yaml@3.14.1", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g=="], + + "@istanbuljs/load-nyc-config/resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], + + "@jest/transform/write-file-atomic": ["write-file-atomic@4.0.2", "", { "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" } }, "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg=="], + + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "@typescript-eslint/typescript-estree/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], + + "ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="], + + "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + + "babel-plugin-istanbul/istanbul-lib-instrument": ["istanbul-lib-instrument@5.2.1", "", { "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-coverage": "^3.2.0", "semver": "^6.3.0" } }, "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg=="], + + "camelcase-keys/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="], + + "cssstyle/cssom": ["cssom@0.3.8", "", {}, "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg=="], + + "decamelize-keys/map-obj": ["map-obj@1.0.1", "", {}, "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg=="], + + "engine.io-client/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="], + + "engine.io-client/ws": ["ws@8.17.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ=="], + + "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + + "filelist/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="], + + "istanbul-lib-instrument/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], + + "jest-runner/source-map-support": ["source-map-support@0.5.13", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w=="], + + "jest-snapshot/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], + + "jest-util/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + + "jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], + + "make-dir/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], + + "meow/type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="], + + "meow/yargs-parser": ["yargs-parser@18.1.3", "", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="], + + "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + + "minimist-options/arrify": ["arrify@1.0.1", "", {}, "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA=="], + + "normalize-package-data/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], + + "pkg-dir/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], + + "pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], + + "read-pkg/type-fest": ["type-fest@0.6.0", "", {}, "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg=="], + + "read-pkg-up/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], + + "read-pkg-up/type-fest": ["type-fest@0.8.1", "", {}, "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA=="], + + "replace-in-files-cli/globby": ["globby@11.1.0", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.2.9", "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^3.0.0" } }, "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g=="], + + "resolve-cwd/resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], + + "socket.io-client/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="], + + "socket.io-parser/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="], + + "stack-utils/escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="], + + "ts-jest/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], + + "@istanbuljs/load-nyc-config/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], + + "@istanbuljs/load-nyc-config/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], + + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], + + "filelist/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], + + "meow/yargs-parser/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="], + + "pkg-dir/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], + + "read-pkg-up/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], + + "@istanbuljs/load-nyc-config/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], + + "pkg-dir/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], + + "read-pkg-up/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], + + "@istanbuljs/load-nyc-config/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], + + "pkg-dir/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], + + "read-pkg-up/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], + } +} diff --git a/tracker/package.json b/tracker/package.json new file mode 100644 index 000000000..dbf2258d3 --- /dev/null +++ b/tracker/package.json @@ -0,0 +1,8 @@ +{ + "name": "tracker-family", + "private": true, + "workspaces": [ + "tracker", + "tracker-assist" + ] +} diff --git a/tracker/tracker-assist/.yarn/install-state.gz b/tracker/tracker-assist/.yarn/install-state.gz index a6fe4294c..064317e97 100644 Binary files a/tracker/tracker-assist/.yarn/install-state.gz and b/tracker/tracker-assist/.yarn/install-state.gz differ diff --git a/tracker/tracker-assist/CHANGELOG.md b/tracker/tracker-assist/CHANGELOG.md index 0985fc80b..f4ab7dc9d 100644 --- a/tracker/tracker-assist/CHANGELOG.md +++ b/tracker/tracker-assist/CHANGELOG.md @@ -1,3 +1,28 @@ +## 11.0.6 + +- fix for canvas assist + +## 11.0.5 + +- fix ice candidates usage + +## 11.0.4 + +- pass stun/turn credentials (if present) from ui + +## 11.0.3 + +- fix for remote-control clicks on svg elements (+ bubbling) + +## 11.0.2 + +- add sessionId header on socket.connect for sticky sessions + +## 11.0.1 + +- fixed rare issue causing videocam feed to be black during calls +- new call widget url to prepare for multi-user calls + ## 11.0.0 - migrate to native webrtc, remove peerjs @@ -72,7 +97,7 @@ ## 5.0.0 -- fix recording state import +- fix recording state import ## 4.1.5 diff --git a/tracker/tracker-assist/package.json b/tracker/tracker-assist/package.json index ea95c6718..f57d9b489 100644 --- a/tracker/tracker-assist/package.json +++ b/tracker/tracker-assist/package.json @@ -1,7 +1,7 @@ { "name": "@openreplay/tracker-assist", "description": "Tracker plugin for screen assistance through the WebRTC", - "version": "11.0.0", + "version": "11.0.6", "keywords": [ "WebRTC", "assistance", @@ -20,7 +20,7 @@ "build-cjs": "rm -Rf cjs && tsc --project tsconfig-cjs.json && echo '{ \"type\": \"commonjs\" }' > cjs/package.json && bun run replace-req-version", "replace-paths": "replace-in-files cjs/* --string='@openreplay/tracker' --replacement='@openreplay/tracker/cjs' && replace-in-files cjs/* --string='/lib/' --replacement='/'", "replace-pkg-version": "sh pkgver.sh", - "replace-req-version": "replace-in-files lib/* cjs/* --string='REQUIRED_TRACKER_VERSION' --replacement='14.0.10'", + "replace-req-version": "replace-in-files lib/* cjs/* --string='REQUIRED_TRACKER_VERSION' --replacement='14.0.14'", "prepublishOnly": "bun run test && bun run build", "lint-front": "lint-staged", "test": "jest --coverage=false", @@ -36,7 +36,7 @@ "@openreplay/tracker": ">=14.0.14" }, "devDependencies": { - "@openreplay/tracker": "file:../tracker", + "@openreplay/tracker": "workspace:*", "@typescript-eslint/eslint-plugin": "^8.14.0", "@typescript-eslint/parser": "^8.14.0", "eslint": "^9.15.0", @@ -54,5 +54,5 @@ "eslint --fix --quiet" ] }, - "packageManager": "yarn@4.6.0" + "packageManager": "bun@1.2.7" } diff --git a/tracker/tracker-assist/src/Assist.ts b/tracker/tracker-assist/src/Assist.ts index 38ad0d4fe..985f71ce5 100644 --- a/tracker/tracker-assist/src/Assist.ts +++ b/tracker/tracker-assist/src/Assist.ts @@ -270,7 +270,10 @@ export default class Assist { ...this.app.getSessionInfo(), }), }, - transports: ['websocket',], + extraHeaders: { + sessionId, + }, + transports: ["websocket"], withCredentials: true, reconnection: true, reconnectionAttempts: 30, @@ -496,11 +499,12 @@ export default class Assist { if (recordingState.isActive) recordingState.stopRecording(); }); - socket.on("call_end", (socketId, { data: callId }) => { - if (!callingAgents.has(socketId)) { + socket.on("call_end", (socketId, msg) => { + if (!callingAgents.has(socketId) || !msg) { app.debug.warn("Received call_end from unknown agent", socketId); return; } + const { data: callId } = msg; endAgentCall({ socketId, callId }); }); @@ -719,9 +723,9 @@ export default class Assist { const agreed = await confirmAnswer; // if rejected, then terminate the call if (!agreed) { - initiateCallEnd() - this.options.onCallDeny?.() - return + initiateCallEnd(); + this.options.onCallDeny?.(); + return; } // create a new RTCPeerConnection with ice server config @@ -731,30 +735,33 @@ export default class Assist { this.calls.set(from, pc); if (!callUI) { - callUI = new CallWindow(app.debug.error, this.options.callUITemplate) - callUI.setVideoToggleCallback((args: { enabled: boolean }) => - this.emit('videofeed', { streamId: from, enabled: args.enabled }) - ); + callUI = new CallWindow(app.debug.error, this.options.callUITemplate); + callUI.setVideoToggleCallback((args: { enabled: boolean }) => { + this.emit("videofeed", { streamId: from, enabled: args.enabled }); + }); } // show buttons in the call window - callUI.showControls(initiateCallEnd) + callUI.showControls(initiateCallEnd); if (!annot) { - annot = new AnnotationCanvas() - annot.mount() + annot = new AnnotationCanvas(); + annot.mount(); } // callUI.setLocalStreams(Object.values(lStreams)) try { - // if there are no local streams in lStrems then we set + // if there are no local streams in lStrems then we set if (!lStreams[from]) { - app.debug.log('starting new stream for', from) + app.debug.log("starting new stream for", from); // request a local stream, and set it to lStreams - lStreams[from] = await RequestLocalStream(pc, renegotiateConnection.bind(null, { pc, from })) + lStreams[from] = await RequestLocalStream( + pc, + renegotiateConnection.bind(null, { pc, from }) + ); } - // we pass the received tracks to Call ui - callUI.setLocalStreams(Object.values(lStreams)) + // we pass the received tracks to Call ui + callUI.setLocalStreams(Object.values(lStreams)); } catch (e) { - app.debug.error('Error requesting local stream', e); + app.debug.error("Error requesting local stream", e); // if something didn't work out, we terminate the call initiateCallEnd(); this.options.onCallDeny?.(); diff --git a/tracker/tracker-assist/src/Mouse.ts b/tracker/tracker-assist/src/Mouse.ts index 989c5da21..1be87cad4 100644 --- a/tracker/tracker-assist/src/Mouse.ts +++ b/tracker/tracker-assist/src/Mouse.ts @@ -66,8 +66,21 @@ export default class Mouse { click(pos: XY) { const el = document.elementFromPoint(pos[0], pos[1]) - if (el instanceof HTMLElement) { - el.click() + if (el instanceof HTMLElement || el instanceof SVGElement) { + try { + const clickEvent = new MouseEvent('click', { + bubbles: true, + cancelable: true, + view: window, + clientX: pos[0], + clientY: pos[1] + }) + el.dispatchEvent(clickEvent) + } catch (e) { + console.error(e); + // @ts-ignore + el.click && el.click() + } el.focus() return el } diff --git a/tracker/tracker-assist/src/RemoteControl.ts b/tracker/tracker-assist/src/RemoteControl.ts index 7c9b426a8..c0c7e2468 100644 --- a/tracker/tracker-assist/src/RemoteControl.ts +++ b/tracker/tracker-assist/src/RemoteControl.ts @@ -115,7 +115,7 @@ export default class RemoteControl { move = (id, xy) => { return id === this.agentID && this.mouse?.move(xy) } - private focused: HTMLElement | null = null + private focused: HTMLElement | SVGElement | null = null click = (id, xy) => { if (id !== this.agentID || !this.mouse) { return } this.focused = this.mouse.click(xy) @@ -141,7 +141,9 @@ export default class RemoteControl { setInputValue.call(this.focused, value) const ev = new Event('input', { bubbles: true,}) this.focused.dispatchEvent(ev) + // @ts-ignore } else if (this.focused.isContentEditable) { + // @ts-ignore this.focused.innerText = value } } diff --git a/tracker/tracker-assist/src/version.ts b/tracker/tracker-assist/src/version.ts index f22966919..c0277806f 100644 --- a/tracker/tracker-assist/src/version.ts +++ b/tracker/tracker-assist/src/version.ts @@ -1 +1 @@ -export const pkgVersion = "11.0.0"; +export const pkgVersion = "11.0.6"; diff --git a/tracker/tracker/.yarn/install-state.gz b/tracker/tracker/.yarn/install-state.gz deleted file mode 100644 index 39932e5b3..000000000 Binary files a/tracker/tracker/.yarn/install-state.gz and /dev/null differ diff --git a/tracker/tracker/.yarnrc.yml b/tracker/tracker/.yarnrc.yml deleted file mode 100644 index 3186f3f07..000000000 --- a/tracker/tracker/.yarnrc.yml +++ /dev/null @@ -1 +0,0 @@ -nodeLinker: node-modules diff --git a/tracker/tracker/CHANGELOG.md b/tracker/tracker/CHANGELOG.md index ef8e97fd4..2124ee2c0 100644 --- a/tracker/tracker/CHANGELOG.md +++ b/tracker/tracker/CHANGELOG.md @@ -1,3 +1,22 @@ +## 16.1.1 + +- fixing debug logs from 16.1.0 + +## 16.1.0 + +- new `privateMode` option to hide all possible data from tracking +- update `networkProxy` to 1.1.0 (auto sanitizer for sensitive parameters in network requests) +- reduced the frequency of performance tracker calls +- reduced the number of events when the user is idle + +## 16.0.3 + +- better handling for local svg spritemaps + +## 16.0.2 + +- fix attributeSender key generation to prevent calling native methods on objects + ## 16.0.1 - drop computing ts digits @@ -36,7 +55,7 @@ tracker.start() ## 15.0.5 - update medv/finder to 4.0.2 for better support of css-in-js libs -- fixes for single tab recording +- fixes for single tab recording - add option to disable network completely `{ network: { disabled: true } }` - fix for batching during offline recording syncs diff --git a/tracker/tracker/jest.config.js b/tracker/tracker/jest.config.js index 0f5bd56b9..da1a2b887 100644 --- a/tracker/tracker/jest.config.js +++ b/tracker/tracker/jest.config.js @@ -8,6 +8,14 @@ const config = { moduleNameMapper: { '(.+)\\.js': '$1', }, + globals: { + 'ts-jest': { + tsConfig: { + target: 'es2020', + lib: ['DOM', 'ES2022'], + }, + }, + }, } export default config diff --git a/tracker/tracker/package.json b/tracker/tracker/package.json index ea0fbec48..7ea29df56 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": "16.0.1", + "version": "16.1.2-beta.0", "keywords": [ "logging", "replay" @@ -43,7 +43,7 @@ "build:common": "tsc -b src/common", "compile": "tsc --project src/main/tsconfig.json", "create-types": "mkdir dist/lib/ dist/cjs && cp -r dist/types/* dist/lib/ && cp -r dist/types/* dist/cjs/", - "build": "yarn run clean && yarn compile && yarn create-types && rollup --config rollup.config.js", + "build": "bun run clean && bun compile && bun create-types && rollup --config rollup.config.js", "lint-front": "lint-staged", "test": "jest --coverage=false", "test:ci": "jest --coverage=true", @@ -76,7 +76,7 @@ }, "dependencies": { "@medv/finder": "^4.0.2", - "@openreplay/network-proxy": "^1.0.5", + "@openreplay/network-proxy": "^1.1.0", "error-stack-parser": "^2.0.6", "error-stack-parser-es": "^0.1.5", "fflate": "^0.8.2", @@ -85,5 +85,5 @@ "engines": { "node": ">=14.0" }, - "packageManager": "yarn@4.6.0" + "packageManager": "bun@1.2.7" } diff --git a/tracker/tracker/src/main/app/index.ts b/tracker/tracker/src/main/app/index.ts index 4a4684f9f..7684cefd8 100644 --- a/tracker/tracker/src/main/app/index.ts +++ b/tracker/tracker/src/main/app/index.ts @@ -272,6 +272,7 @@ export default class App { 'feature-flags': true, 'usability-test': true, } + private emptyBatchCounter = 0 constructor( projectKey: string, @@ -431,6 +432,7 @@ export default class App { if (ev.data.context === this.contextId) { return } + this.debug.log(ev) if (ev.data.line === proto.resp) { const sessionToken = ev.data.token this.session.setSessionToken(sessionToken) @@ -848,8 +850,7 @@ export default class App { * */ private _nCommit(): void { if (this.socketMode) { - this.messages.unshift(TabData(this.session.getTabId())) - this.messages.unshift(Timestamp(this.timestamp())) + this.messages.unshift(Timestamp(this.timestamp()), TabData(this.session.getTabId())) this.commitCallbacks.forEach((cb) => cb(this.messages)) this.messages.length = 0 return @@ -872,10 +873,19 @@ export default class App { return } + if (!this.messages.length) { + // Release empty batches every 30 secs (1000 * 30ms) + if (this.emptyBatchCounter < 1000) { + this.emptyBatchCounter++; + return; + } + } + + this.emptyBatchCounter = 0 + try { requestIdleCb(() => { - this.messages.unshift(TabData(this.session.getTabId())) - this.messages.unshift(Timestamp(this.timestamp())) + this.messages.unshift(Timestamp(this.timestamp()), TabData(this.session.getTabId())) this.worker?.postMessage(this.messages) this.commitCallbacks.forEach((cb) => cb(this.messages)) this.messages.length = 0 @@ -900,10 +910,9 @@ export default class App { private _cStartCommit(): void { this.coldStartCommitN += 1 if (this.coldStartCommitN === 2) { - this.bufferedMessages1.push(Timestamp(this.timestamp())) - this.bufferedMessages1.push(TabData(this.session.getTabId())) - this.bufferedMessages2.push(Timestamp(this.timestamp())) - this.bufferedMessages2.push(TabData(this.session.getTabId())) + const payload = [Timestamp(this.timestamp()), TabData(this.session.getTabId())] + this.bufferedMessages1.push(...payload) + this.bufferedMessages2.push(...payload) this.coldStartCommitN = 0 } } diff --git a/tracker/tracker/src/main/app/observer/observer.ts b/tracker/tracker/src/main/app/observer/observer.ts index d2c47e13f..054159631 100644 --- a/tracker/tracker/src/main/app/observer/observer.ts +++ b/tracker/tracker/src/main/app/observer/observer.ts @@ -37,9 +37,29 @@ async function parseUseEl( return } - const [url, symbolId] = href.split('#') - if (!url || !symbolId) { - console.debug('Openreplay: Invalid xlink:href or href found on .') + let [url, symbolId] = href.split('#') + + // happens if svg spritemap is local, fastest case for us + if (!url && symbolId) { + const symbol = document.querySelector(href) + if (symbol) { + const inlineSvg = ` + + ${symbol.innerHTML} + + `.trim() + + iconCache[symbolId] = inlineSvg + + return inlineSvg + } else { + console.warn('Openreplay: Sprite symbol not found in the document.') + return + } + } + + if (!url && !symbolId) { + console.warn('Openreplay: Invalid xlink:href or href found on .') return } @@ -337,6 +357,9 @@ export default abstract class Observer { if (name === 'href' || value.length > 1e5) { value = '' } + if (['alt', 'placeholder'].includes(name) && this.app.sanitizer.privateMode) { + value = value.replaceAll(/./g, '*') + } this.app.attributeSender.sendSetAttribute(id, name, value) } @@ -369,7 +392,7 @@ export default abstract class Observer { { acceptNode: (node) => { if (this.app.nodes.getID(node) !== undefined) { - this.app.debug.warn('! Node is already bound', node) + this.app.debug.info('! Node is already bound', node) } return isIgnored(node) || this.app.nodes.getID(node) !== undefined ? NodeFilter.FILTER_REJECT diff --git a/tracker/tracker/src/main/app/sanitizer.ts b/tracker/tracker/src/main/app/sanitizer.ts index 32ec1468c..1d037a51b 100644 --- a/tracker/tracker/src/main/app/sanitizer.ts +++ b/tracker/tracker/src/main/app/sanitizer.ts @@ -1,6 +1,6 @@ import type App from './index.js' import { stars, hasOpenreplayAttribute } from '../utils.js' -import { isElementNode } from './guards.js' +import { isElementNode, isTextNode } from './guards.js' export enum SanitizeLevel { Plain, @@ -32,31 +32,46 @@ export interface Options { * * */ domSanitizer?: (node: Element) => SanitizeLevel + /** + * private by default mode that will mask all elements not marked by data-openreplay-unmask + * */ + privateMode?: boolean } export const stringWiper = (input: string) => input .trim() - .replace(/[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]/g, '█') + .replace(/[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff\s]/g, '*') export default class Sanitizer { private readonly obscured: Set = new Set() private readonly hidden: Set = new Set() private readonly options: Options + public readonly privateMode: boolean private readonly app: App constructor(params: { app: App; options?: Partial }) { this.app = params.app - this.options = Object.assign( - { - obscureTextEmails: true, - obscureTextNumbers: false, - }, - params.options, - ) + const defaultOptions: Options = { + obscureTextEmails: true, + obscureTextNumbers: false, + privateMode: false, + domSanitizer: undefined, + } + this.privateMode = params.options?.privateMode ?? false + this.options = Object.assign(defaultOptions, params.options) } handleNode(id: number, parentID: number, node: Node) { + if (this.options.privateMode) { + if (isElementNode(node) && !hasOpenreplayAttribute(node, 'unmask')) { + return this.obscured.add(id) + } + if (isTextNode(node) && !hasOpenreplayAttribute(node.parentNode as Element, 'unmask')) { + return this.obscured.add(id) + } + } + if ( this.obscured.has(parentID) || (isElementNode(node) && diff --git a/tracker/tracker/src/main/modules/attributeSender.ts b/tracker/tracker/src/main/modules/attributeSender.ts index 200de919b..70f7cff43 100644 --- a/tracker/tracker/src/main/modules/attributeSender.ts +++ b/tracker/tracker/src/main/modules/attributeSender.ts @@ -15,7 +15,9 @@ export class StringDictionary { getKey = (str: string): [number, boolean] => { let isNew = false - if (!this.backDict[str]) { + // avoiding potential native object properties + const safeKey = `__${str}` + if (!this.backDict[safeKey]) { isNew = true // shaving the first 2 digits of the timestamp (since they are irrelevant for next millennia) const shavedTs = Date.now() % 10 ** (13 - 2) @@ -26,10 +28,10 @@ export class StringDictionary { } else { this.lastSuffix = 1 } - this.backDict[str] = id + this.backDict[safeKey] = id this.lastTs = shavedTs } - return [this.backDict[str], isNew] + return [this.backDict[safeKey], isNew] } } diff --git a/tracker/tracker/src/main/modules/console.ts b/tracker/tracker/src/main/modules/console.ts index efdd3be88..1a30ae5e8 100644 --- a/tracker/tracker/src/main/modules/console.ts +++ b/tracker/tracker/src/main/modules/console.ts @@ -108,9 +108,13 @@ export default function (app: App, opts: Partial): void { return } - const sendConsoleLog = app.safe((level: string, args: unknown[]): void => - app.send(ConsoleLog(level, printf(args))), - ) + const sendConsoleLog = app.safe((level: string, args: unknown[]): void => { + let logMsg = printf(args) + if (app.sanitizer.privateMode) { + logMsg = logMsg.replaceAll(/./g, '*') + } + app.send(ConsoleLog(level, logMsg)) + }) let n = 0 const reset = (): void => { diff --git a/tracker/tracker/src/main/modules/input.ts b/tracker/tracker/src/main/modules/input.ts index e8297f2b8..7fc3626ba 100644 --- a/tracker/tracker/src/main/modules/input.ts +++ b/tracker/tracker/src/main/modules/input.ts @@ -205,7 +205,10 @@ export default function (app: App, opts: Partial): void { inputTime: number, ) { const { value, mask } = getInputValue(id, node) - const label = getInputLabel(node) + let label = getInputLabel(node) + if (app.sanitizer.privateMode) { + label = label.replaceAll(/./g, '*') + } app.send(InputChange(id, value, mask !== 0, label, hesitationTime, inputTime)) } diff --git a/tracker/tracker/src/main/modules/mouse.ts b/tracker/tracker/src/main/modules/mouse.ts index 262005757..ab15a2303 100644 --- a/tracker/tracker/src/main/modules/mouse.ts +++ b/tracker/tracker/src/main/modules/mouse.ts @@ -230,11 +230,12 @@ export default function (app: App, options?: MouseHandlerOptions): void { const normalizedY = roundNumber(clickY / contentHeight) sendMouseMove() + const label = getTargetLabel(target) app.send( MouseClick( id, mouseTarget === target ? Math.round(performance.now() - mouseTargetTime) : 0, - getTargetLabel(target), + app.sanitizer.privateMode ? label.replaceAll(/./g, '*') : label, isClickable(target) && !disableClickmaps ? getSelector(id, target, options) : '', normalizedX, normalizedY, diff --git a/tracker/tracker/src/main/modules/network.ts b/tracker/tracker/src/main/modules/network.ts index 135a14a75..453406dcf 100644 --- a/tracker/tracker/src/main/modules/network.ts +++ b/tracker/tracker/src/main/modules/network.ts @@ -101,7 +101,7 @@ export default function (app: App, opts: Partial = {}) { } function sanitize(reqResInfo: RequestResponseData) { - if (!options.capturePayload) { + if (!options.capturePayload || app.sanitizer.privateMode) { // @ts-ignore delete reqResInfo.request.body delete reqResInfo.response.body @@ -136,18 +136,19 @@ export default function (app: App, opts: Partial = {}) { if (options.useProxy) { return createNetworkProxy( context, - options.ignoreHeaders, + app.sanitizer.privateMode ? true : options.ignoreHeaders, setSessionTokenHeader, sanitize, (message) => { if (options.failuresOnly && message.status < 400) { return } + const url = app.sanitizer.privateMode ? '************' : message.url app.send( NetworkRequest( message.requestType, message.method, - message.url, + url, message.request, message.response, message.status, diff --git a/tracker/tracker/src/main/modules/performance.ts b/tracker/tracker/src/main/modules/performance.ts index 3d6ad1458..f966a2f2b 100644 --- a/tracker/tracker/src/main/modules/performance.ts +++ b/tracker/tracker/src/main/modules/performance.ts @@ -80,7 +80,7 @@ export default function (app: App, opts: Partial): void { ticks = frames = undefined }) - app.ticker.attach(sendPerformanceTrack, 40, false) + app.ticker.attach(sendPerformanceTrack, 165, false) if (document.hidden !== undefined) { app.attachEventListener( diff --git a/tracker/tracker/src/main/modules/timing.ts b/tracker/tracker/src/main/modules/timing.ts index f8e7d63cf..ff3e4cfae 100644 --- a/tracker/tracker/src/main/modules/timing.ts +++ b/tracker/tracker/src/main/modules/timing.ts @@ -147,7 +147,7 @@ export default function (app: App, opts: Partial): void { entry.transferSize > entry.encodedBodySize ? entry.transferSize - entry.encodedBodySize : 0, entry.encodedBodySize || 0, entry.decodedBodySize || 0, - entry.name, + app.sanitizer.privateMode ? entry.name.replaceAll(/./g, '*') : entry.name, entry.initiatorType, entry.transferSize, // @ts-ignore diff --git a/tracker/tracker/src/main/modules/viewport.ts b/tracker/tracker/src/main/modules/viewport.ts index a17332855..b43306aa1 100644 --- a/tracker/tracker/src/main/modules/viewport.ts +++ b/tracker/tracker/src/main/modules/viewport.ts @@ -1,6 +1,7 @@ import type App from '../app/index.js' import { getTimeOrigin } from '../utils.js' import { SetPageLocation, SetViewportSize, SetPageVisibility } from '../app/messages.gen.js' +import { stringWiper } from '../app/sanitizer.js' export default function (app: App): void { let url: string | null, width: number, height: number @@ -11,7 +12,10 @@ export default function (app: App): void { const { URL } = document if (URL !== url) { url = URL - app.send(SetPageLocation(url, referrer, navigationStart, document.title)) + const safeTitle = app.sanitizer.privateMode ? stringWiper(document.title) : document.title + const safeUrl = app.sanitizer.privateMode ? stringWiper(url) : url + const safeReferrer = app.sanitizer.privateMode ? stringWiper(referrer) : referrer + app.send(SetPageLocation(safeUrl, safeReferrer, navigationStart, safeTitle)) navigationStart = 0 referrer = url } diff --git a/tracker/tracker/src/tests/console.test.ts b/tracker/tracker/src/tests/console.test.ts index 085b008be..749da72e6 100644 --- a/tracker/tracker/src/tests/console.test.ts +++ b/tracker/tracker/src/tests/console.test.ts @@ -23,6 +23,9 @@ describe('Console logging module', () => { safe: jest.fn((callback) => callback), send: jest.fn(), attachStartCallback: jest.fn(), + sanitizer: { + privateMode: false, + }, ticker: { attach: jest.fn(), }, diff --git a/tracker/tracker/src/tests/sanitizer.unit.test.ts b/tracker/tracker/src/tests/sanitizer.unit.test.ts index 295a570a8..5d02bf83f 100644 --- a/tracker/tracker/src/tests/sanitizer.unit.test.ts +++ b/tracker/tracker/src/tests/sanitizer.unit.test.ts @@ -2,8 +2,8 @@ import { describe, expect, jest, afterEach, beforeEach, test } from '@jest/globa import Sanitizer, { SanitizeLevel, Options, stringWiper } from '../main/app/sanitizer.js' describe('stringWiper', () => { - test('should replace all characters with █', () => { - expect(stringWiper('Sensitive Data')).toBe('██████████████') + test('should replace all characters with *', () => { + expect(stringWiper('Sensitive Data')).toBe('********* ****') }) }) @@ -126,7 +126,7 @@ describe('Sanitizer', () => { element.mockId = 1 element.innerText = 'Sensitive Data' const sanitizedText = sanitizer.getInnerTextSecure(element) - expect(sanitizedText).toEqual('██████████████') + expect(sanitizedText).toEqual('********* ****') }) test('should return empty string if node element does not exist', () => { diff --git a/tracker/tracker/src/webworker/BatchWriter.ts b/tracker/tracker/src/webworker/BatchWriter.ts index 77fb352ba..db0dca2d6 100644 --- a/tracker/tracker/src/webworker/BatchWriter.ts +++ b/tracker/tracker/src/webworker/BatchWriter.ts @@ -52,10 +52,13 @@ export default class BatchWriter { this.url, ] + const timestamp: Messages.Timestamp = [Messages.Type.Timestamp, this.timestamp] + const tabData: Messages.TabData = [Messages.Type.TabData, this.tabId] this.writeType(batchMetadata) this.writeFields(batchMetadata) + this.writeWithSize(timestamp as Message) this.writeWithSize(tabData as Message) this.isEmpty = true } diff --git a/tracker/tracker/src/webworker/BatchWriter.unit.test.ts b/tracker/tracker/src/webworker/BatchWriter.unit.test.ts index b4c1030ec..48bfed9f6 100644 --- a/tracker/tracker/src/webworker/BatchWriter.unit.test.ts +++ b/tracker/tracker/src/webworker/BatchWriter.unit.test.ts @@ -21,8 +21,8 @@ describe('BatchWriter', () => { expect(batchWriter['timestamp']).toBe(123456789) expect(batchWriter['url']).toBe('example.com') expect(batchWriter['onBatch']).toBe(onBatchMock) - // we add tab id as first in the batch - expect(batchWriter['nextIndex']).toBe(1) + // we add tab and timestamp as first in the batch + expect(batchWriter['nextIndex']).toBe(2) expect(batchWriter['beaconSize']).toBe(200000) expect(batchWriter['encoder']).toBeDefined() expect(batchWriter['sizeBuffer']).toHaveLength(3)