Compare commits
10
Commits
d4b6f3d25f
...
8b9f31c77c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b9f31c77c | ||
|
|
af1b6c6efc | ||
|
|
2f6e2b5ff2 | ||
|
|
17bd781de5 | ||
|
|
8b479e3b32 | ||
|
|
42765249ee | ||
|
|
8a0bdc2727 | ||
|
|
3888ec6f4f | ||
|
|
3b792720af | ||
|
|
9defe7e3e0 |
@@ -4,6 +4,7 @@ Dockerfile
|
||||
.gitignore
|
||||
.DS_Store
|
||||
node_modules
|
||||
.next
|
||||
.idea
|
||||
.env
|
||||
.env.*
|
||||
|
||||
+42
-54
@@ -1,79 +1,67 @@
|
||||
ARG NODE_IMAGE_VERSION="22-alpine"
|
||||
ARG PNPM_VERSION="10.15.1"
|
||||
# Keep in sync with the prisma/@prisma/* versions in package.json
|
||||
ARG PRISMA_VERSION="7.8.0"
|
||||
ARG NODE_IMAGE_VERSION=22-alpine
|
||||
ARG PNPM_VERSION=10.15.1
|
||||
|
||||
# Install dependencies only when needed
|
||||
FROM node:${NODE_IMAGE_VERSION} AS deps
|
||||
|
||||
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
|
||||
RUN apk add --no-cache libc6-compat
|
||||
ARG PNPM_VERSION
|
||||
|
||||
RUN apk add --no-cache libc6-compat \
|
||||
&& npm install --global pnpm@${PNPM_VERSION}
|
||||
|
||||
WORKDIR /app
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
RUN npm install -g pnpm
|
||||
|
||||
RUN printf 'strictDepBuilds: false\n' > pnpm-workspace.yaml
|
||||
|
||||
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
# Rebuild the source code only when needed
|
||||
FROM node:${NODE_IMAGE_VERSION} AS builder
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
COPY docker/proxy.ts ./src
|
||||
|
||||
ARG PNPM_VERSION
|
||||
ARG BASE_PATH
|
||||
|
||||
ENV BASE_PATH=$BASE_PATH
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
ENV DATABASE_URL="postgresql://user:pass@localhost:5432/dummy"
|
||||
RUN apk add --no-cache libc6-compat \
|
||||
&& npm install --global pnpm@${PNPM_VERSION}
|
||||
|
||||
RUN npm run build-docker
|
||||
|
||||
# Production image, copy all the files and run next
|
||||
FROM node:${NODE_IMAGE_VERSION} AS runner
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
ENV BASE_PATH=${BASE_PATH}
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
ENV DATABASE_URL=postgresql://user:pass@localhost:5432/dummy
|
||||
ENV SKIP_DB_CHECK=1
|
||||
ENV SKIP_BUILD_GEO=1
|
||||
|
||||
RUN pnpm build
|
||||
|
||||
FROM node:${NODE_IMAGE_VERSION} AS runner
|
||||
|
||||
ARG PNPM_VERSION
|
||||
ARG NODE_OPTIONS
|
||||
ARG PRISMA_VERSION
|
||||
|
||||
RUN apk add --no-cache curl libc6-compat \
|
||||
&& npm install --global pnpm@${PNPM_VERSION} \
|
||||
&& addgroup --system --gid 1001 nodejs \
|
||||
&& adduser --system --uid 1001 --ingroup nodejs nextjs
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
ENV NODE_OPTIONS=$NODE_OPTIONS
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
RUN set -x \
|
||||
&& apk add --no-cache curl libc6-compat \
|
||||
&& npm install -g pnpm
|
||||
|
||||
RUN echo {} > package.json
|
||||
|
||||
RUN printf "allowBuilds:\n '@prisma/engines': true\n prisma: false\nverifyDepsBeforeRun: false\n" > pnpm-workspace.yaml
|
||||
|
||||
# Script dependencies
|
||||
RUN pnpm add npm-run-all dotenv chalk semver \
|
||||
prisma@${PRISMA_VERSION} \
|
||||
@prisma/client@${PRISMA_VERSION} \
|
||||
@prisma/adapter-pg@${PRISMA_VERSION}
|
||||
ENV NODE_OPTIONS=${NODE_OPTIONS}
|
||||
ENV HOSTNAME=0.0.0.0
|
||||
ENV PORT=3000
|
||||
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/package.json ./package.json
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/pnpm-lock.yaml ./pnpm-lock.yaml
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/pnpm-workspace.yaml ./pnpm-workspace.yaml
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/next.config.ts ./next.config.ts
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/node_modules ./node_modules
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
|
||||
COPY --from=builder /app/prisma ./prisma
|
||||
COPY --from=builder /app/prisma.config.ts ./prisma.config.ts
|
||||
COPY --from=builder /app/scripts ./scripts
|
||||
COPY --from=builder /app/generated ./generated
|
||||
|
||||
# Automatically leverage output traces to reduce image size
|
||||
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next
|
||||
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENV HOSTNAME=0.0.0.0
|
||||
ENV PORT=3000
|
||||
|
||||
CMD ["npm", "run", "start-docker"]
|
||||
CMD ["pnpm", "start"]
|
||||
@@ -7,7 +7,6 @@ CREATE TABLE umami.heatmap_event
|
||||
visit_id UUID,
|
||||
url_path String,
|
||||
event_type UInt8,
|
||||
node_id Nullable(Int32),
|
||||
x Nullable(Int32),
|
||||
y Nullable(Int32),
|
||||
page_x Nullable(Int32),
|
||||
@@ -17,34 +16,9 @@ CREATE TABLE umami.heatmap_event
|
||||
viewport_h Nullable(Int32),
|
||||
page_h Nullable(Int32),
|
||||
scroll_pct Nullable(UInt8),
|
||||
replay_chunk_index Nullable(UInt32),
|
||||
replay_event_index Nullable(UInt32),
|
||||
replay_time_ms Nullable(Int64),
|
||||
created_at DateTime('UTC')
|
||||
)
|
||||
ENGINE = MergeTree
|
||||
PARTITION BY toYYYYMM(created_at)
|
||||
ORDER BY (website_id, url_path, event_type, created_at)
|
||||
SETTINGS index_granularity = 8192;
|
||||
|
||||
-- Create heatmap_snapshot
|
||||
CREATE TABLE umami.heatmap_snapshot
|
||||
(
|
||||
snapshot_id UUID,
|
||||
website_id UUID,
|
||||
url_path String,
|
||||
viewport_w UInt32,
|
||||
viewport_h UInt32,
|
||||
page_w UInt32,
|
||||
page_h UInt32,
|
||||
status UInt8,
|
||||
mime_type LowCardinality(String),
|
||||
object_key String,
|
||||
image_size Nullable(UInt32),
|
||||
error Nullable(String),
|
||||
created_at DateTime('UTC')
|
||||
)
|
||||
ENGINE = MergeTree
|
||||
PARTITION BY toYYYYMM(created_at)
|
||||
ORDER BY (website_id, url_path, viewport_w, viewport_h, created_at)
|
||||
SETTINGS index_granularity = 8192;
|
||||
SETTINGS index_granularity = 8192;
|
||||
@@ -409,7 +409,6 @@ CREATE TABLE umami.heatmap_event
|
||||
visit_id UUID,
|
||||
url_path String,
|
||||
event_type UInt8,
|
||||
node_id Nullable(Int32),
|
||||
x Nullable(Int32),
|
||||
y Nullable(Int32),
|
||||
page_x Nullable(Int32),
|
||||
@@ -419,34 +418,9 @@ CREATE TABLE umami.heatmap_event
|
||||
viewport_h Nullable(Int32),
|
||||
page_h Nullable(Int32),
|
||||
scroll_pct Nullable(UInt8),
|
||||
replay_chunk_index Nullable(UInt32),
|
||||
replay_event_index Nullable(UInt32),
|
||||
replay_time_ms Nullable(Int64),
|
||||
created_at DateTime('UTC')
|
||||
)
|
||||
ENGINE = MergeTree
|
||||
PARTITION BY toYYYYMM(created_at)
|
||||
ORDER BY (website_id, url_path, event_type, created_at)
|
||||
SETTINGS index_granularity = 8192;
|
||||
|
||||
-- Create heatmap_snapshot
|
||||
CREATE TABLE umami.heatmap_snapshot
|
||||
(
|
||||
snapshot_id UUID,
|
||||
website_id UUID,
|
||||
url_path String,
|
||||
viewport_w UInt32,
|
||||
viewport_h UInt32,
|
||||
page_w UInt32,
|
||||
page_h UInt32,
|
||||
status UInt8,
|
||||
mime_type LowCardinality(String),
|
||||
object_key String,
|
||||
image_size Nullable(UInt32),
|
||||
error Nullable(String),
|
||||
created_at DateTime('UTC')
|
||||
)
|
||||
ENGINE = MergeTree
|
||||
PARTITION BY toYYYYMM(created_at)
|
||||
ORDER BY (website_id, url_path, viewport_w, viewport_h, created_at)
|
||||
SETTINGS index_granularity = 8192;
|
||||
+7
-2
@@ -1,12 +1,17 @@
|
||||
---
|
||||
services:
|
||||
umami:
|
||||
image: ghcr.io/umami-software/umami:latest
|
||||
image: umami-local:latest
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "3000:3000"
|
||||
- "3003:3000"
|
||||
environment:
|
||||
DATABASE_URL: postgresql://umami:umami@db:5432/umami
|
||||
APP_SECRET: replace-me-with-a-random-string
|
||||
volumes:
|
||||
- ./geo/GeoLite2-City.mmdb:/app/geo/GeoLite2-City.mmdb:ro
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
|
||||
@@ -52,7 +52,6 @@
|
||||
".next/cache"
|
||||
],
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.1068.0",
|
||||
"@clickhouse/client": "^1.20.0",
|
||||
"@date-fns/utc": "^2.1.1",
|
||||
"@dicebear/collection": "^9.4.2",
|
||||
@@ -98,7 +97,6 @@
|
||||
"npm-run-all": "^4.1.5",
|
||||
"papaparse": "^5.5.3",
|
||||
"pg": "^8.21.0",
|
||||
"playwright-core": "^1.60.0",
|
||||
"prisma": "^7.8.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "^19.2.7",
|
||||
|
||||
Generated
-537
@@ -8,9 +8,6 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@aws-sdk/client-s3':
|
||||
specifier: ^3.1068.0
|
||||
version: 3.1068.0
|
||||
'@clickhouse/client':
|
||||
specifier: ^1.20.0
|
||||
version: 1.20.0
|
||||
@@ -146,9 +143,6 @@ importers:
|
||||
pg:
|
||||
specifier: ^8.21.0
|
||||
version: 8.21.0
|
||||
playwright-core:
|
||||
specifier: ^1.60.0
|
||||
version: 1.60.0
|
||||
prisma:
|
||||
specifier: ^7.8.0
|
||||
version: 7.8.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)
|
||||
@@ -331,8 +325,6 @@ importers:
|
||||
specifier: ^4.1.8
|
||||
version: 4.1.8(@types/node@25.9.3)(jsdom@29.1.1)(msw@2.14.6(@types/node@25.9.3)(typescript@6.0.3))(vite@8.0.11(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.46.1)(tsx@4.22.4))
|
||||
|
||||
dist: {}
|
||||
|
||||
packages:
|
||||
|
||||
'@adobe/css-tools@4.4.4':
|
||||
@@ -357,109 +349,6 @@ packages:
|
||||
'@asamuzakjp/nwsapi@2.3.9':
|
||||
resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==}
|
||||
|
||||
'@aws-crypto/crc32@5.2.0':
|
||||
resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
||||
'@aws-crypto/crc32c@5.2.0':
|
||||
resolution: {integrity: sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==}
|
||||
|
||||
'@aws-crypto/sha1-browser@5.2.0':
|
||||
resolution: {integrity: sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==}
|
||||
|
||||
'@aws-crypto/sha256-browser@5.2.0':
|
||||
resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==}
|
||||
|
||||
'@aws-crypto/sha256-js@5.2.0':
|
||||
resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
||||
'@aws-crypto/supports-web-crypto@5.2.0':
|
||||
resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==}
|
||||
|
||||
'@aws-crypto/util@5.2.0':
|
||||
resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==}
|
||||
|
||||
'@aws-sdk/checksums@3.1000.5':
|
||||
resolution: {integrity: sha512-zOXUUnilC6lgCsQtp77p/QNPmRlTES9Xi6tlDwbR6kfC/kz5PCzZckgHWm5z+8DskdwuMAbFDq61x3zr10GEEQ==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/client-s3@3.1068.0':
|
||||
resolution: {integrity: sha512-lFgaIpxZvloNbJvQ337YPdMXhzI2zJdDw13nATVGnkAGNoNPx4ksD84AQAcuW75hsaaMaIuNmXU9sSx6+FTirA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/core@3.974.20':
|
||||
resolution: {integrity: sha512-7sDi2B2N3mc3nf1nz6FyEx/FCrJ1N1QnBmraHHQNabFaeAh2IaOOLml48/rHOD1bICHgTRkbBgNTvUzEr5Z35g==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/credential-provider-env@3.972.46':
|
||||
resolution: {integrity: sha512-+GPXVS2srMOlH74S+SmC1gVuP2TvUZ0siuC0onKO93q+udP+M72dmY8wJfVQ5CX9z/9X5A1HHwz5yRIGBtskvQ==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/credential-provider-http@3.972.48':
|
||||
resolution: {integrity: sha512-fA5loSdlocacRxyUXtpoHSMuk5rsIKRDzQYVMnMxjcmFeZshaJlJ8lymy/hYKji6sne/UmNGj5pxuEs6kq/Qcg==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/credential-provider-ini@3.972.53':
|
||||
resolution: {integrity: sha512-ZfdhIOR41q8TcWEnUac+gCOb+O2LBWdHLmjedXpXz4IEFW2ppNuFcm6p0sMTavpM+zD5TYfpH5Gp7guRyqSgsQ==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/credential-provider-login@3.972.52':
|
||||
resolution: {integrity: sha512-9hu2oR0qH7Fst5Tzdx+UWxm+w5zCXtErTLtOOW5hwwQc170CLwOeniRxyFY6s9mHfGEfC5zFukNBdKBwJR8mhQ==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/credential-provider-node@3.972.55':
|
||||
resolution: {integrity: sha512-zMGLa/dhESVqmCD7mmIFFKSwSFrJGScvCXcjvBZEVOOMauFS5JRQvLTMukFpMEFWiV6dTAlsen2ATDBulLPtbg==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/credential-provider-process@3.972.46':
|
||||
resolution: {integrity: sha512-VUoNFBIjWrUN8NbFiQiuxQEgFjvziAlBRPK+ddh27aj65gk0BYu6bLZnrdrNZwpW6vAihtSUtEMQ1PUJ32QRPA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/credential-provider-sso@3.972.52':
|
||||
resolution: {integrity: sha512-nb2/n4o/HQf+FVpVbZe9vCTFngmuDoIsltMgLAtjixaKzvzhB4J8WSDFyWgnErgLHk55ctWH+I4PU+LIHhyffg==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/credential-provider-web-identity@3.972.52':
|
||||
resolution: {integrity: sha512-lKj6aRSGbqLmpYmM24bY7a1Xmfcq2vkE3hv8CSPYfc1yCu0BPu/XEJ1L4Fm61MsU6ULLNSG8UGsffNoFUBjESA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/middleware-flexible-checksums@3.974.30':
|
||||
resolution: {integrity: sha512-OaIhub+3yTgfFWPzKO8OzOZFIMUoJaiS5v67y3spQg7SoULGoMx4jKVBbE+uhnzkiZXQ+rEDS0RqrK4/aD1yJw==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/middleware-sdk-s3@3.972.51':
|
||||
resolution: {integrity: sha512-keQgcIUTcHL0Qn7guhsuLaxQU36r9norCrxgaPH4DNCwon4TPtXdI/UdYuycl9vj3Dlwc3YR1dfL3U+6iIwJ6w==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/nested-clients@3.997.20':
|
||||
resolution: {integrity: sha512-IYJuLpXp2DEILVQpQOy0PMpkftv0AHEOCn52o0atyOaumA0CdWQ3klPyXdViGYLbNpESsVFMVybvHUeZAuiGxA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/signature-v4-multi-region@3.996.34':
|
||||
resolution: {integrity: sha512-mx1L5qlumSOt/nKM3BFaHE2HVkWwz0i4Bw0pyYO42FfX/FeLlo8YI6csC0gSPprEk6fTIqI+CZN9RwUwKd5krQ==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/token-providers@3.1066.0':
|
||||
resolution: {integrity: sha512-UqEUJq7dqa44hneLDUcX7UJy95cg8YqEWyakRpvIPnrNS3Mq+UlQHgCDGu5pvwAPtlIW4qcYbvW6reG6++FyvA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/types@3.973.12':
|
||||
resolution: {integrity: sha512-43ajd1NF0RMgX5k0hxCNUyEdrtFUsb2aHT2QvpktSC/2Eyb2Jr/JPVqdp0XIoaHWikZJq5tNWSLO6kB5q2eMCA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/util-locate-window@3.965.5':
|
||||
resolution: {integrity: sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/xml-builder@3.972.29':
|
||||
resolution: {integrity: sha512-fk0niuGFxfi8yIJuMVM4mhwObkiQSuwZFj3tAPrLVx64Pk3BkrEIpqjzHKY4hKoEBUD6Jg/S74Zj9jy+5F3DnQ==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws/lambda-invoke-store@0.2.4':
|
||||
resolution: {integrity: sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@babel/code-frame@7.27.1':
|
||||
resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -632,28 +521,24 @@ packages:
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@biomejs/cli-linux-arm64@2.5.0':
|
||||
resolution: {integrity: sha512-tl+LW8fdD96/xdeWtWwc82LIOc5CoY7N2AsogLTp5R4ECErYt+8Jl/N68ezN9vzSiqPTxw6vjcihoLPYKZHrlw==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@biomejs/cli-linux-x64-musl@2.5.0':
|
||||
resolution: {integrity: sha512-+9hIcMngJ+yGUahXqZuZ8CoWKJE9SAZsFsM3QDvXpNsLbXZ9lqVzgBhOk/jTSYkOA0GLP9eu3teukqpLUojHMg==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@biomejs/cli-linux-x64@2.5.0':
|
||||
resolution: {integrity: sha512-zpEGf4RQbFEh8Vt7OmavLyyOzRbtcE9osCqrS1kfvt8jDvxwhKXLSf7n0ebr/ov0RJ9ssP+lhs6C8a9WwFvrQA==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@biomejs/cli-win32-arm64@2.5.0':
|
||||
resolution: {integrity: sha512-jB0wAvTLI4itx5VidqVUejPQFhRUxiZ9l9FvZ26D5fl6t3qme+ZB4PD3bTSeL1vZ8NI2Rx/zj6H9zcESuGHKGw==}
|
||||
@@ -1634,105 +1519,89 @@ packages:
|
||||
resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-arm@1.2.4':
|
||||
resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-ppc64@1.2.4':
|
||||
resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-riscv64@1.2.4':
|
||||
resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-s390x@1.2.4':
|
||||
resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-x64@1.2.4':
|
||||
resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-arm64@1.2.4':
|
||||
resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-x64@1.2.4':
|
||||
resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-linux-arm64@0.34.5':
|
||||
resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-arm@0.34.5':
|
||||
resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-ppc64@0.34.5':
|
||||
resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-riscv64@0.34.5':
|
||||
resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-s390x@0.34.5':
|
||||
resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-x64@0.34.5':
|
||||
resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linuxmusl-arm64@0.34.5':
|
||||
resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-linuxmusl-x64@0.34.5':
|
||||
resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-wasm32@0.34.5':
|
||||
resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==}
|
||||
@@ -1874,28 +1743,24 @@ packages:
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@next/swc-linux-arm64-musl@16.2.6':
|
||||
resolution: {integrity: sha512-URUTu1+dMkxJsPFgm+OeEvq9wf5sujw0EvgYy80TDGHTSLTnIHeqb0Eu8A3sC95IRgjejQL+kC4mw+4yPxiAXA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@next/swc-linux-x64-gnu@16.2.6':
|
||||
resolution: {integrity: sha512-DOj182mPV8G3UkrayLoREM5YEYI+Dk5wv7Ox9xl1fFibAELEsFD0lDPfHIeILlutMMfdyhlzYPELG3peuKaurw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@next/swc-linux-x64-musl@16.2.6':
|
||||
resolution: {integrity: sha512-HKQ5SP/V/ub73UvF7n/zeJlxk2kLmtL7Wzrg4WfmkjmNos5onJ2tKu7yZOPdL18A6Svfn3max29ym+ry7NkK4g==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@next/swc-win32-arm64-msvc@16.2.6':
|
||||
resolution: {integrity: sha512-LZXpTlPyS5v7HhSmnvsLGP3iIYgYOBnc8r8ArlT55sGHV89bR2HlDdBjWQ+PY6SJMmk8TuVGFuxalnP3k/0Dwg==}
|
||||
@@ -1909,9 +1774,6 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@nodable/entities@2.2.0':
|
||||
resolution: {integrity: sha512-9uGyhaQavEUMC8AIddIjau4NsnsXhou+j5sBAGojCM1oxmQpVKTWR/9JxABD6UAv12vpIms55fPZKFQEhG6uBg==}
|
||||
|
||||
'@nodelib/fs.scandir@2.1.5':
|
||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -1968,42 +1830,36 @@ packages:
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@parcel/watcher-linux-arm-musl@2.5.6':
|
||||
resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@parcel/watcher-linux-arm64-glibc@2.5.6':
|
||||
resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@parcel/watcher-linux-arm64-musl@2.5.6':
|
||||
resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@parcel/watcher-linux-x64-glibc@2.5.6':
|
||||
resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@parcel/watcher-linux-x64-musl@2.5.6':
|
||||
resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@parcel/watcher-win32-arm64@2.5.6':
|
||||
resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==}
|
||||
@@ -2318,42 +2174,36 @@ packages:
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rolldown/binding-linux-arm64-musl@1.0.0-rc.18':
|
||||
resolution: {integrity: sha512-QWjdxN1HJCpBTAcZ5N5F7wju3gVPzRzSpmGzx7na0c/1qpN9CFil+xt+l9lV/1M6/gqHSNXCiqPfwhVJPeLnug==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.18':
|
||||
resolution: {integrity: sha512-ugCOyj7a4d9h3q9B+wXmf6g3a68UsjGh6dob5DHevHGMwDUbhsYNbSPxJsENcIttJZ9jv7qGM2UesLw5jqIhdg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rolldown/binding-linux-s390x-gnu@1.0.0-rc.18':
|
||||
resolution: {integrity: sha512-kKWRhbsotpXkGbcd5dllUWg5gEXcDAa8u5YnP9AV5DYNbvJHGzzuwv7dpmhc8NqKMJldl0a+x76IHbspEpEmdA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rolldown/binding-linux-x64-gnu@1.0.0-rc.18':
|
||||
resolution: {integrity: sha512-uCo8ElcCIAMyYAZyuIZ81oFkhTSIllNvUCHCAlbhlN4ji3uC28h7IIdlXyIvGO7HsuqnV9p3rD/bpH7XhIyhRw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rolldown/binding-linux-x64-musl@1.0.0-rc.18':
|
||||
resolution: {integrity: sha512-XNOQZtuE6yUIvx4rwGemwh8kpL1xvU41FXy/s9K7T/3JVcqGzo3NfKM2HrbrGgfPYGFW42f07Wk++aOC6B9NWA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rolldown/binding-openharmony-arm64@1.0.0-rc.18':
|
||||
resolution: {integrity: sha512-tSn/kzrfa7tNOXr7sEacDBN4YsIqTyLqh45IO0nHDwtpKIDNDJr+VFojt+4klSpChxB29JLyduSsE0MKEwa65A==}
|
||||
@@ -2500,79 +2350,66 @@ packages:
|
||||
resolution: {integrity: sha512-Q8CBCCQtDFrYtXoeUXSrnFXKOnyUhx6bz+SkL6A0E7V8kAiCJ5pamq1WtbfpVGhR5TSpXY6ak3avmDc5fHTyJA==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.61.1':
|
||||
resolution: {integrity: sha512-nwnhk1581l0FBVellGcVCAT0Oi06onEA3WB53sf01VO3I0UPBkMH9sXONYME2K0ovXcNayJfNtHfm6mpJElatQ==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.61.1':
|
||||
resolution: {integrity: sha512-x5Xr49hwt3hdW75UOZm3395YwwzPyauktslv29KpWL/T+vVAzoT3azLcTWv0eMciBNrx+DYjH4paehHoLpPvpg==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.61.1':
|
||||
resolution: {integrity: sha512-unMS3H73DpaoPyyEVPjGKleM/s0mkmsauTENpw4INQY8y4+IuLNjkueQ5QCtC0D3N38Y38yhAU8OoZ20S2Tm6w==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-loong64-gnu@4.61.1':
|
||||
resolution: {integrity: sha512-zNZzGRnAhwjFEYmvphJRV5XaQGjs62cCmeYYHUT//NbvEnHauw+I85nGG+SiVg5ld4GX8D1IbKIX+ozITQnhMQ==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-loong64-musl@4.61.1':
|
||||
resolution: {integrity: sha512-LdpWGL8X209B2SIvWjqlc8VZgM6PKfontSerGepuldQmHYrAOtnMCXeJkxXGbC+PPZVOuu5czJo7fNV6aeW8rQ==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-gnu@4.61.1':
|
||||
resolution: {integrity: sha512-EC5kTtNaNGOmbMGqar8dvJy6y/hg99GAwjfBz++pxZhQATXGcRjd6c5en5wcbru0vkRmiMGsQKdMJOOf6sza4g==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-musl@4.61.1':
|
||||
resolution: {integrity: sha512-8hiwp6D4acEcNK78I4rP0/XtS1sknWIAMJBPdR4l6zUtyTm5KiTDr5bXmWt4foY7nAN7AThDHgkLIEZOWKbzWw==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.61.1':
|
||||
resolution: {integrity: sha512-10dh/h/BqA7DuMPWSxkR8uks18FRwnwOEqr5zOTEl+NOwP/OMzKX8OFR/Of9xxDA7D5qef1Nzar5WDD2kCCr1g==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.61.1':
|
||||
resolution: {integrity: sha512-YKJ5lg35DP17gcAOggnihe+APw9HLyj1Xn7gsmGumBJAUDa6NGXNixJzmkWLhcK9TOuuyQjdamzvJefkO7qHZQ==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.61.1':
|
||||
resolution: {integrity: sha512-Mlil5G2Jj6a7B3LWGctg+XPL9vdXYuzCtNXfxOQ0nPjc2m6ueUktocPGH9bnAM0bNRKb/bAWTujUU7IJQdQA+g==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.61.1':
|
||||
resolution: {integrity: sha512-bVWIOIk6pV01p4CdUbPP7CJ/434z+OooYjDuFcR+44N35YvKUC66G8MGnvcWx5mWKW3g61J+t74l3Kj15Kwn2Q==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.61.1':
|
||||
resolution: {integrity: sha512-qy5pBvZbqNFheBz61R1rzsezjm0J7O2oNGoWtGoY89SZYLUfxAJTBAqDChqAIdB4rCiIbi9nF7yZ83GnNiLwSw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-openbsd-x64@4.61.1':
|
||||
resolution: {integrity: sha512-E83TXjI4zm0+5f2qO+UOudaCYIhYwpJ5jq6YCZNIZ+6CbfhKrkAGezeiASBL9ElxAxFsRS9ZhESv8mfnj6TKeg==}
|
||||
@@ -2623,42 +2460,6 @@ packages:
|
||||
resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@smithy/core@3.24.7':
|
||||
resolution: {integrity: sha512-KoUi4M1f3BG6kzN1FnCwL7oyFptTbyBJKjR6yhSib+JHRdUmM1o+VwsFtJ66NZCkCzVfJMWRHJNo0R0jznp0Pg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/credential-provider-imds@4.3.9':
|
||||
resolution: {integrity: sha512-ZlfJ/4Fa3jYb+3eaohPfG9utX9HmdhFNcFtpoGAhUhdynAOmGXtmigbi7eEiONKM+ykHw8RwKuDEb85Lx7t7fA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/fetch-http-handler@5.4.7':
|
||||
resolution: {integrity: sha512-NslaM2ir0N2hisDmzXLstPaVINZheh8SokyOC++kzFPloZucL2R7Y7bS57mSzx/1Fc/fqmn7twjkeezTTrV0EA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/is-array-buffer@2.2.0':
|
||||
resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
'@smithy/node-http-handler@4.7.8':
|
||||
resolution: {integrity: sha512-f+DbsWUwSbtMu1a/j8Y93KiU1SRg9nyzfjereqn1BJ33QOTUXxdlYvVXMhAYl1vuR1Kmna5aIJe09KSIfyFNYw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/signature-v4@5.4.7':
|
||||
resolution: {integrity: sha512-LwQZazFayImv+IOm0S0enoLeUJwmAlhGC5O6YCcLWezyu08dF46GOxPOq35OpBIHkgd7OvNvBStIFwVNyrvoBw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/types@4.14.4':
|
||||
resolution: {integrity: sha512-B2S9+UGm1+/pHkcx3ZoLVX1a+pmSk8rqxRR+ZsNqZaJ5q9FWX9AFGQVM4qG5+OBeQUZVy99HY8HqW8gK/wgXzQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/util-buffer-from@2.2.0':
|
||||
resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
'@smithy/util-utf8@2.3.0':
|
||||
resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
'@standard-schema/spec@1.1.0':
|
||||
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
|
||||
|
||||
@@ -2770,42 +2571,36 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@swc/core-linux-arm64-musl@1.15.33':
|
||||
resolution: {integrity: sha512-il7tYM+CpUNzieQbwAjFT1P8zqAhmGWNAGhQZBnxurXZ0aNn+5nqYFTEUKNZl7QibtT0uQXzTZrNGHCIj6Y1Og==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@swc/core-linux-ppc64-gnu@1.15.33':
|
||||
resolution: {integrity: sha512-ZtNBwN0Z7CFj9Il0FcPaKdjgP7URyKu/3RfH46vq+0paOBqLj4NYldD6Qo//Duif/7IOtAraUfDOmp0PLAufog==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@swc/core-linux-s390x-gnu@1.15.33':
|
||||
resolution: {integrity: sha512-De1IyajoOmhOYYjw/lx66bKlyDpHZTueqwpDrWgf5O7T6d1ODeJJO9/OqMBmrBQc5C+dNnlmIufHsp4QVCWufA==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@swc/core-linux-x64-gnu@1.15.33':
|
||||
resolution: {integrity: sha512-mGTH0YxmUN+x6vRN/I6NOk5X0ogNktkwPnJ94IMvR7QjhRDwL0O8RXEDhyUM0YtwWrryBOqaJQBX4zruxEPRGw==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@swc/core-linux-x64-musl@1.15.33':
|
||||
resolution: {integrity: sha512-hj628ZkSEJf6zMf5VMbYrG2O6QqyTIp2qwY6VlCjvIa9lAEZ5c2lfPblCLVGYubTeLJDxadLB/CxqQYOQABeEQ==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@swc/core-win32-arm64-msvc@1.15.33':
|
||||
resolution: {integrity: sha512-GV2oohtN2/5+KSccl86VULu3aT+LrISC8uzgSq0FRnikpD+Zwc+sBlXmoKQ+Db6jI57ITUOIB8jRkdGMABC29g==}
|
||||
@@ -3051,9 +2846,6 @@ packages:
|
||||
any-promise@1.3.0:
|
||||
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
|
||||
|
||||
anynum@1.0.0:
|
||||
resolution: {integrity: sha512-xjR9/zBVnUOP6ztMIIgShjsxui80nQUQH+5xJnvrYLs+90bF25/KJqaAi8mk+B4RDtX1Nspi6fmp4YTEts8SfA==}
|
||||
|
||||
arg@4.1.3:
|
||||
resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
|
||||
|
||||
@@ -3144,9 +2936,6 @@ packages:
|
||||
boolbase@1.0.0:
|
||||
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
|
||||
|
||||
bowser@2.14.1:
|
||||
resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==}
|
||||
|
||||
brace-expansion@1.1.12:
|
||||
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
|
||||
|
||||
@@ -3792,13 +3581,6 @@ packages:
|
||||
fast-wrap-ansi@0.2.0:
|
||||
resolution: {integrity: sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w==}
|
||||
|
||||
fast-xml-builder@1.2.0:
|
||||
resolution: {integrity: sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==}
|
||||
|
||||
fast-xml-parser@5.7.3:
|
||||
resolution: {integrity: sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg==}
|
||||
hasBin: true
|
||||
|
||||
fastq@1.19.1:
|
||||
resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
|
||||
|
||||
@@ -4347,28 +4129,24 @@ packages:
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-arm64-musl@1.32.0:
|
||||
resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-linux-x64-gnu@1.32.0:
|
||||
resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-x64-musl@1.32.0:
|
||||
resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-win32-arm64-msvc@1.32.0:
|
||||
resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
|
||||
@@ -4764,10 +4542,6 @@ packages:
|
||||
path-browserify@1.0.1:
|
||||
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
|
||||
|
||||
path-expression-matcher@1.5.0:
|
||||
resolution: {integrity: sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
path-is-absolute@1.0.1:
|
||||
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -5868,9 +5642,6 @@ packages:
|
||||
resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
strnum@2.4.0:
|
||||
resolution: {integrity: sha512-sHrVyWWdq28RbhjuJdZsA1SnGRJV6NiXbk6AXBxDOsgAcA+lmpUZCYjOdLBxkXMwis6RRe7dlZt4VlIWFVzkmg==}
|
||||
|
||||
style-inject@0.3.0:
|
||||
resolution: {integrity: sha512-IezA2qp+vcdlhJaVm5SOdPPTUu0FCEqfNSli2vRuSIBbu5Nq5UvygTk/VzeCqfLz2Atj3dVII5QBKGZRZ0edzw==}
|
||||
|
||||
@@ -6325,10 +6096,6 @@ packages:
|
||||
resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
xml-naming@0.1.0:
|
||||
resolution: {integrity: sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
||||
xmlchars@2.2.0:
|
||||
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
|
||||
|
||||
@@ -6416,236 +6183,6 @@ snapshots:
|
||||
|
||||
'@asamuzakjp/nwsapi@2.3.9': {}
|
||||
|
||||
'@aws-crypto/crc32@5.2.0':
|
||||
dependencies:
|
||||
'@aws-crypto/util': 5.2.0
|
||||
'@aws-sdk/types': 3.973.12
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-crypto/crc32c@5.2.0':
|
||||
dependencies:
|
||||
'@aws-crypto/util': 5.2.0
|
||||
'@aws-sdk/types': 3.973.12
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-crypto/sha1-browser@5.2.0':
|
||||
dependencies:
|
||||
'@aws-crypto/supports-web-crypto': 5.2.0
|
||||
'@aws-crypto/util': 5.2.0
|
||||
'@aws-sdk/types': 3.973.12
|
||||
'@aws-sdk/util-locate-window': 3.965.5
|
||||
'@smithy/util-utf8': 2.3.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-crypto/sha256-browser@5.2.0':
|
||||
dependencies:
|
||||
'@aws-crypto/sha256-js': 5.2.0
|
||||
'@aws-crypto/supports-web-crypto': 5.2.0
|
||||
'@aws-crypto/util': 5.2.0
|
||||
'@aws-sdk/types': 3.973.12
|
||||
'@aws-sdk/util-locate-window': 3.965.5
|
||||
'@smithy/util-utf8': 2.3.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-crypto/sha256-js@5.2.0':
|
||||
dependencies:
|
||||
'@aws-crypto/util': 5.2.0
|
||||
'@aws-sdk/types': 3.973.12
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-crypto/supports-web-crypto@5.2.0':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-crypto/util@5.2.0':
|
||||
dependencies:
|
||||
'@aws-sdk/types': 3.973.12
|
||||
'@smithy/util-utf8': 2.3.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/checksums@3.1000.5':
|
||||
dependencies:
|
||||
'@aws-crypto/crc32': 5.2.0
|
||||
'@aws-crypto/crc32c': 5.2.0
|
||||
'@aws-crypto/util': 5.2.0
|
||||
'@aws-sdk/core': 3.974.20
|
||||
'@aws-sdk/types': 3.973.12
|
||||
'@smithy/core': 3.24.7
|
||||
'@smithy/types': 4.14.4
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/client-s3@3.1068.0':
|
||||
dependencies:
|
||||
'@aws-crypto/sha1-browser': 5.2.0
|
||||
'@aws-crypto/sha256-browser': 5.2.0
|
||||
'@aws-crypto/sha256-js': 5.2.0
|
||||
'@aws-sdk/core': 3.974.20
|
||||
'@aws-sdk/credential-provider-node': 3.972.55
|
||||
'@aws-sdk/middleware-flexible-checksums': 3.974.30
|
||||
'@aws-sdk/middleware-sdk-s3': 3.972.51
|
||||
'@aws-sdk/signature-v4-multi-region': 3.996.34
|
||||
'@aws-sdk/types': 3.973.12
|
||||
'@smithy/core': 3.24.7
|
||||
'@smithy/fetch-http-handler': 5.4.7
|
||||
'@smithy/node-http-handler': 4.7.8
|
||||
'@smithy/types': 4.14.4
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/core@3.974.20':
|
||||
dependencies:
|
||||
'@aws-sdk/types': 3.973.12
|
||||
'@aws-sdk/xml-builder': 3.972.29
|
||||
'@aws/lambda-invoke-store': 0.2.4
|
||||
'@smithy/core': 3.24.7
|
||||
'@smithy/signature-v4': 5.4.7
|
||||
'@smithy/types': 4.14.4
|
||||
bowser: 2.14.1
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/credential-provider-env@3.972.46':
|
||||
dependencies:
|
||||
'@aws-sdk/core': 3.974.20
|
||||
'@aws-sdk/types': 3.973.12
|
||||
'@smithy/core': 3.24.7
|
||||
'@smithy/types': 4.14.4
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/credential-provider-http@3.972.48':
|
||||
dependencies:
|
||||
'@aws-sdk/core': 3.974.20
|
||||
'@aws-sdk/types': 3.973.12
|
||||
'@smithy/core': 3.24.7
|
||||
'@smithy/fetch-http-handler': 5.4.7
|
||||
'@smithy/node-http-handler': 4.7.8
|
||||
'@smithy/types': 4.14.4
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/credential-provider-ini@3.972.53':
|
||||
dependencies:
|
||||
'@aws-sdk/core': 3.974.20
|
||||
'@aws-sdk/credential-provider-env': 3.972.46
|
||||
'@aws-sdk/credential-provider-http': 3.972.48
|
||||
'@aws-sdk/credential-provider-login': 3.972.52
|
||||
'@aws-sdk/credential-provider-process': 3.972.46
|
||||
'@aws-sdk/credential-provider-sso': 3.972.52
|
||||
'@aws-sdk/credential-provider-web-identity': 3.972.52
|
||||
'@aws-sdk/nested-clients': 3.997.20
|
||||
'@aws-sdk/types': 3.973.12
|
||||
'@smithy/core': 3.24.7
|
||||
'@smithy/credential-provider-imds': 4.3.9
|
||||
'@smithy/types': 4.14.4
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/credential-provider-login@3.972.52':
|
||||
dependencies:
|
||||
'@aws-sdk/core': 3.974.20
|
||||
'@aws-sdk/nested-clients': 3.997.20
|
||||
'@aws-sdk/types': 3.973.12
|
||||
'@smithy/core': 3.24.7
|
||||
'@smithy/types': 4.14.4
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/credential-provider-node@3.972.55':
|
||||
dependencies:
|
||||
'@aws-sdk/credential-provider-env': 3.972.46
|
||||
'@aws-sdk/credential-provider-http': 3.972.48
|
||||
'@aws-sdk/credential-provider-ini': 3.972.53
|
||||
'@aws-sdk/credential-provider-process': 3.972.46
|
||||
'@aws-sdk/credential-provider-sso': 3.972.52
|
||||
'@aws-sdk/credential-provider-web-identity': 3.972.52
|
||||
'@aws-sdk/types': 3.973.12
|
||||
'@smithy/core': 3.24.7
|
||||
'@smithy/credential-provider-imds': 4.3.9
|
||||
'@smithy/types': 4.14.4
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/credential-provider-process@3.972.46':
|
||||
dependencies:
|
||||
'@aws-sdk/core': 3.974.20
|
||||
'@aws-sdk/types': 3.973.12
|
||||
'@smithy/core': 3.24.7
|
||||
'@smithy/types': 4.14.4
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/credential-provider-sso@3.972.52':
|
||||
dependencies:
|
||||
'@aws-sdk/core': 3.974.20
|
||||
'@aws-sdk/nested-clients': 3.997.20
|
||||
'@aws-sdk/token-providers': 3.1066.0
|
||||
'@aws-sdk/types': 3.973.12
|
||||
'@smithy/core': 3.24.7
|
||||
'@smithy/types': 4.14.4
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/credential-provider-web-identity@3.972.52':
|
||||
dependencies:
|
||||
'@aws-sdk/core': 3.974.20
|
||||
'@aws-sdk/nested-clients': 3.997.20
|
||||
'@aws-sdk/types': 3.973.12
|
||||
'@smithy/core': 3.24.7
|
||||
'@smithy/types': 4.14.4
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/middleware-flexible-checksums@3.974.30':
|
||||
dependencies:
|
||||
'@aws-sdk/checksums': 3.1000.5
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/middleware-sdk-s3@3.972.51':
|
||||
dependencies:
|
||||
'@aws-sdk/core': 3.974.20
|
||||
'@aws-sdk/signature-v4-multi-region': 3.996.34
|
||||
'@aws-sdk/types': 3.973.12
|
||||
'@smithy/core': 3.24.7
|
||||
'@smithy/types': 4.14.4
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/nested-clients@3.997.20':
|
||||
dependencies:
|
||||
'@aws-crypto/sha256-browser': 5.2.0
|
||||
'@aws-crypto/sha256-js': 5.2.0
|
||||
'@aws-sdk/core': 3.974.20
|
||||
'@aws-sdk/signature-v4-multi-region': 3.996.34
|
||||
'@aws-sdk/types': 3.973.12
|
||||
'@smithy/core': 3.24.7
|
||||
'@smithy/fetch-http-handler': 5.4.7
|
||||
'@smithy/node-http-handler': 4.7.8
|
||||
'@smithy/types': 4.14.4
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/signature-v4-multi-region@3.996.34':
|
||||
dependencies:
|
||||
'@aws-sdk/types': 3.973.12
|
||||
'@smithy/signature-v4': 5.4.7
|
||||
'@smithy/types': 4.14.4
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/token-providers@3.1066.0':
|
||||
dependencies:
|
||||
'@aws-sdk/core': 3.974.20
|
||||
'@aws-sdk/nested-clients': 3.997.20
|
||||
'@aws-sdk/types': 3.973.12
|
||||
'@smithy/core': 3.24.7
|
||||
'@smithy/types': 4.14.4
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/types@3.973.12':
|
||||
dependencies:
|
||||
'@smithy/types': 4.14.4
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/util-locate-window@3.965.5':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/xml-builder@3.972.29':
|
||||
dependencies:
|
||||
'@smithy/types': 4.14.4
|
||||
fast-xml-parser: 5.7.3
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws/lambda-invoke-store@0.2.4': {}
|
||||
|
||||
'@babel/code-frame@7.27.1':
|
||||
dependencies:
|
||||
'@babel/helper-validator-identifier': 7.27.1
|
||||
@@ -7896,8 +7433,6 @@ snapshots:
|
||||
'@next/swc-win32-x64-msvc@16.2.6':
|
||||
optional: true
|
||||
|
||||
'@nodable/entities@2.2.0': {}
|
||||
|
||||
'@nodelib/fs.scandir@2.1.5':
|
||||
dependencies:
|
||||
'@nodelib/fs.stat': 2.0.5
|
||||
@@ -8470,54 +8005,6 @@ snapshots:
|
||||
|
||||
'@sindresorhus/merge-streams@2.3.0': {}
|
||||
|
||||
'@smithy/core@3.24.7':
|
||||
dependencies:
|
||||
'@aws-crypto/crc32': 5.2.0
|
||||
'@smithy/types': 4.14.4
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/credential-provider-imds@4.3.9':
|
||||
dependencies:
|
||||
'@smithy/core': 3.24.7
|
||||
'@smithy/types': 4.14.4
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/fetch-http-handler@5.4.7':
|
||||
dependencies:
|
||||
'@smithy/core': 3.24.7
|
||||
'@smithy/types': 4.14.4
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/is-array-buffer@2.2.0':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/node-http-handler@4.7.8':
|
||||
dependencies:
|
||||
'@smithy/core': 3.24.7
|
||||
'@smithy/types': 4.14.4
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/signature-v4@5.4.7':
|
||||
dependencies:
|
||||
'@smithy/core': 3.24.7
|
||||
'@smithy/types': 4.14.4
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/types@4.14.4':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/util-buffer-from@2.2.0':
|
||||
dependencies:
|
||||
'@smithy/is-array-buffer': 2.2.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/util-utf8@2.3.0':
|
||||
dependencies:
|
||||
'@smithy/util-buffer-from': 2.2.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@standard-schema/spec@1.1.0': {}
|
||||
|
||||
'@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.28.3)':
|
||||
@@ -8913,8 +8400,6 @@ snapshots:
|
||||
|
||||
any-promise@1.3.0: {}
|
||||
|
||||
anynum@1.0.0: {}
|
||||
|
||||
arg@4.1.3: {}
|
||||
|
||||
argparse@2.0.1: {}
|
||||
@@ -8991,8 +8476,6 @@ snapshots:
|
||||
|
||||
boolbase@1.0.0: {}
|
||||
|
||||
bowser@2.14.1: {}
|
||||
|
||||
brace-expansion@1.1.12:
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
@@ -9750,18 +9233,6 @@ snapshots:
|
||||
dependencies:
|
||||
fast-string-width: 3.0.2
|
||||
|
||||
fast-xml-builder@1.2.0:
|
||||
dependencies:
|
||||
path-expression-matcher: 1.5.0
|
||||
xml-naming: 0.1.0
|
||||
|
||||
fast-xml-parser@5.7.3:
|
||||
dependencies:
|
||||
'@nodable/entities': 2.2.0
|
||||
fast-xml-builder: 1.2.0
|
||||
path-expression-matcher: 1.5.0
|
||||
strnum: 2.4.0
|
||||
|
||||
fastq@1.19.1:
|
||||
dependencies:
|
||||
reusify: 1.1.0
|
||||
@@ -10702,8 +10173,6 @@ snapshots:
|
||||
|
||||
path-browserify@1.0.1: {}
|
||||
|
||||
path-expression-matcher@1.5.0: {}
|
||||
|
||||
path-is-absolute@1.0.1: {}
|
||||
|
||||
path-key@2.0.1: {}
|
||||
@@ -11938,10 +11407,6 @@ snapshots:
|
||||
dependencies:
|
||||
min-indent: 1.0.1
|
||||
|
||||
strnum@2.4.0:
|
||||
dependencies:
|
||||
anynum: 1.0.0
|
||||
|
||||
style-inject@0.3.0: {}
|
||||
|
||||
styled-jsx@5.1.6(@babel/core@7.29.0)(react@19.2.7):
|
||||
@@ -12391,8 +11856,6 @@ snapshots:
|
||||
|
||||
xml-name-validator@5.0.0: {}
|
||||
|
||||
xml-naming@0.1.0: {}
|
||||
|
||||
xmlchars@2.2.0: {}
|
||||
|
||||
xtend@4.0.2: {}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
packages:
|
||||
- '**'
|
||||
- '!dist'
|
||||
- '!**/dist/**'
|
||||
allowBuilds:
|
||||
'@parcel/watcher': false
|
||||
'@prisma/engines': true
|
||||
|
||||
@@ -12,7 +12,6 @@ CREATE TABLE "heatmap_event" (
|
||||
"visit_id" UUID NOT NULL,
|
||||
"url_path" VARCHAR(500) NOT NULL,
|
||||
"event_type" INTEGER NOT NULL,
|
||||
"node_id" INTEGER,
|
||||
"x" INTEGER,
|
||||
"y" INTEGER,
|
||||
"page_x" INTEGER,
|
||||
@@ -22,9 +21,6 @@ CREATE TABLE "heatmap_event" (
|
||||
"viewport_h" INTEGER,
|
||||
"page_h" INTEGER,
|
||||
"scroll_pct" INTEGER,
|
||||
"replay_chunk_index" INTEGER,
|
||||
"replay_event_index" INTEGER,
|
||||
"replay_time_ms" BIGINT,
|
||||
"created_at" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "heatmap_event_pkey" PRIMARY KEY ("heatmap_event_id")
|
||||
@@ -34,32 +30,4 @@ CREATE TABLE "heatmap_event" (
|
||||
CREATE INDEX "heatmap_event_website_id_idx" ON "heatmap_event"("website_id");
|
||||
CREATE INDEX "heatmap_event_visit_id_idx" ON "heatmap_event"("visit_id");
|
||||
CREATE INDEX "heatmap_event_website_id_created_at_idx" ON "heatmap_event"("website_id", "created_at");
|
||||
CREATE INDEX "heatmap_event_website_id_url_path_event_type_created_at_idx" ON "heatmap_event"("website_id", "url_path", "event_type", "created_at");
|
||||
CREATE INDEX "heatmap_event_website_id_visit_id_replay_chunk_index_replay_event_index_idx" ON "heatmap_event"("website_id", "visit_id", "replay_chunk_index", "replay_event_index");
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "heatmap_snapshot" (
|
||||
"snapshot_id" UUID NOT NULL,
|
||||
"website_id" UUID NOT NULL,
|
||||
"url_path" VARCHAR(500) NOT NULL,
|
||||
"viewport_w" INTEGER NOT NULL,
|
||||
"viewport_h" INTEGER NOT NULL,
|
||||
"page_w" INTEGER NOT NULL,
|
||||
"page_h" INTEGER NOT NULL,
|
||||
"status" VARCHAR(20) NOT NULL,
|
||||
"mime_type" VARCHAR(100),
|
||||
"image_data" BYTEA,
|
||||
"image_size" INTEGER,
|
||||
"error" VARCHAR(500),
|
||||
"created_at" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMPTZ(6),
|
||||
|
||||
CONSTRAINT "heatmap_snapshot_pkey" PRIMARY KEY ("snapshot_id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "heatmap_snapshot_website_id_url_path_viewport_w_viewport_h_key"
|
||||
ON "heatmap_snapshot"("website_id", "url_path", "viewport_w", "viewport_h");
|
||||
CREATE INDEX "heatmap_snapshot_website_id_idx" ON "heatmap_snapshot"("website_id");
|
||||
CREATE INDEX "heatmap_snapshot_website_id_url_path_idx" ON "heatmap_snapshot"("website_id", "url_path");
|
||||
CREATE INDEX "heatmap_snapshot_website_id_updated_at_idx" ON "heatmap_snapshot"("website_id", "updated_at");
|
||||
CREATE INDEX "heatmap_event_website_id_url_path_event_type_created_at_idx" ON "heatmap_event"("website_id", "url_path", "event_type", "created_at");
|
||||
@@ -1,38 +0,0 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "heatmap_snapshot" ALTER COLUMN "created_at" DROP NOT NULL;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "heatmap_replay_preview" (
|
||||
"preview_id" UUID NOT NULL,
|
||||
"website_id" UUID NOT NULL,
|
||||
"session_id" UUID NOT NULL,
|
||||
"visit_id" UUID NOT NULL,
|
||||
"url_path" VARCHAR(500) NOT NULL,
|
||||
"viewport_w" INTEGER NOT NULL,
|
||||
"viewport_h" INTEGER NOT NULL,
|
||||
"replay_chunk_index" INTEGER NOT NULL,
|
||||
"replay_event_index" INTEGER NOT NULL,
|
||||
"replay_time_ms" BIGINT,
|
||||
"created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMPTZ(6),
|
||||
|
||||
CONSTRAINT "heatmap_replay_preview_pkey" PRIMARY KEY ("preview_id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "heatmap_replay_preview_website_id_idx" ON "heatmap_replay_preview"("website_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "heatmap_replay_preview_visit_id_idx" ON "heatmap_replay_preview"("visit_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "heatmap_replay_preview_website_id_url_path_idx" ON "heatmap_replay_preview"("website_id", "url_path");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "heatmap_replay_preview_website_id_url_path_viewport_w_viewp_key" ON "heatmap_replay_preview"("website_id", "url_path", "viewport_w", "viewport_h");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "session_replay_visit_id_idx" ON "session_replay"("visit_id");
|
||||
|
||||
-- RenameIndex
|
||||
ALTER INDEX "heatmap_event_website_id_visit_id_replay_chunk_index_replay_eve" RENAME TO "heatmap_event_website_id_visit_id_replay_chunk_index_replay_idx";
|
||||
@@ -89,8 +89,6 @@ model Website {
|
||||
sessionReplays SessionReplay[]
|
||||
sessionReplaysSaved SessionReplaySaved[]
|
||||
heatmapEvents HeatmapEvent[]
|
||||
heatmapReplayPreviews HeatmapReplayPreview[]
|
||||
heatmapSnapshots HeatmapSnapshot[]
|
||||
|
||||
@@index([userId])
|
||||
@@index([teamId])
|
||||
@@ -410,7 +408,6 @@ model HeatmapEvent {
|
||||
visitId String @map("visit_id") @db.Uuid
|
||||
urlPath String @map("url_path") @db.VarChar(500)
|
||||
eventType Int @map("event_type") @db.Integer
|
||||
nodeId Int? @map("node_id") @db.Integer
|
||||
x Int? @db.Integer
|
||||
y Int? @db.Integer
|
||||
pageX Int? @map("page_x") @db.Integer
|
||||
@@ -420,9 +417,6 @@ model HeatmapEvent {
|
||||
viewportH Int? @map("viewport_h") @db.Integer
|
||||
pageH Int? @map("page_h") @db.Integer
|
||||
scrollPct Int? @map("scroll_pct") @db.Integer
|
||||
replayChunkIndex Int? @map("replay_chunk_index") @db.Integer
|
||||
replayEventIndex Int? @map("replay_event_index") @db.Integer
|
||||
replayTimeMs BigInt? @map("replay_time_ms") @db.BigInt
|
||||
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
|
||||
|
||||
website Website @relation(fields: [websiteId], references: [id])
|
||||
@@ -431,54 +425,5 @@ model HeatmapEvent {
|
||||
@@index([visitId])
|
||||
@@index([websiteId, createdAt])
|
||||
@@index([websiteId, urlPath, eventType, createdAt])
|
||||
@@index([websiteId, visitId, replayChunkIndex, replayEventIndex])
|
||||
@@map("heatmap_event")
|
||||
}
|
||||
|
||||
model HeatmapReplayPreview {
|
||||
id String @id() @map("preview_id") @db.Uuid
|
||||
websiteId String @map("website_id") @db.Uuid
|
||||
sessionId String @map("session_id") @db.Uuid
|
||||
visitId String @map("visit_id") @db.Uuid
|
||||
urlPath String @map("url_path") @db.VarChar(500)
|
||||
viewportW Int @map("viewport_w") @db.Integer
|
||||
viewportH Int @map("viewport_h") @db.Integer
|
||||
replayChunkIndex Int @map("replay_chunk_index") @db.Integer
|
||||
replayEventIndex Int @map("replay_event_index") @db.Integer
|
||||
replayTimeMs BigInt? @map("replay_time_ms") @db.BigInt
|
||||
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
|
||||
updatedAt DateTime? @updatedAt @map("updated_at") @db.Timestamptz(6)
|
||||
|
||||
website Website @relation(fields: [websiteId], references: [id])
|
||||
|
||||
@@unique([websiteId, urlPath, viewportW, viewportH])
|
||||
@@index([websiteId])
|
||||
@@index([visitId])
|
||||
@@index([websiteId, urlPath])
|
||||
@@map("heatmap_replay_preview")
|
||||
}
|
||||
|
||||
model HeatmapSnapshot {
|
||||
id String @id() @map("snapshot_id") @db.Uuid
|
||||
websiteId String @map("website_id") @db.Uuid
|
||||
urlPath String @map("url_path") @db.VarChar(500)
|
||||
viewportW Int @map("viewport_w") @db.Integer
|
||||
viewportH Int @map("viewport_h") @db.Integer
|
||||
pageW Int @map("page_w") @db.Integer
|
||||
pageH Int @map("page_h") @db.Integer
|
||||
status String @db.VarChar(20)
|
||||
mimeType String? @map("mime_type") @db.VarChar(100)
|
||||
imageData Bytes? @map("image_data")
|
||||
imageSize Int? @map("image_size") @db.Integer
|
||||
error String? @db.VarChar(500)
|
||||
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
|
||||
updatedAt DateTime? @updatedAt @map("updated_at") @db.Timestamptz(6)
|
||||
|
||||
website Website @relation(fields: [websiteId], references: [id])
|
||||
|
||||
@@unique([websiteId, urlPath, viewportW, viewportH])
|
||||
@@index([websiteId])
|
||||
@@index([websiteId, urlPath])
|
||||
@@index([websiteId, updatedAt])
|
||||
@@map("heatmap_snapshot")
|
||||
}
|
||||
+8
-4
@@ -3,10 +3,10 @@ import 'dotenv/config';
|
||||
import { execSync } from 'node:child_process';
|
||||
import { PrismaPg } from '@prisma/adapter-pg';
|
||||
import chalk from 'chalk';
|
||||
import semver from 'semver';
|
||||
import { PrismaClient } from '../generated/prisma/client.js';
|
||||
|
||||
const MIN_VERSION = '9.4.0';
|
||||
const MIN_VERSION_NUM = 90400;
|
||||
|
||||
if (process.env.SKIP_DB_CHECK) {
|
||||
console.log('Skipping database check.');
|
||||
@@ -53,10 +53,14 @@ async function checkConnection() {
|
||||
}
|
||||
|
||||
async function checkDatabaseVersion() {
|
||||
const query = await prisma.$queryRaw`select version() as version`;
|
||||
const version = semver.valid(semver.coerce(query[0].version));
|
||||
const query = await prisma.$queryRaw`select current_setting('server_version_num') as version_num`;
|
||||
const version = Number(query[0]?.version_num);
|
||||
|
||||
if (semver.lt(version, MIN_VERSION)) {
|
||||
if (!Number.isFinite(version)) {
|
||||
throw new Error('Unable to determine database version.');
|
||||
}
|
||||
|
||||
if (version < MIN_VERSION_NUM) {
|
||||
throw new Error(
|
||||
`Database version is not compatible. Please upgrade to ${MIN_VERSION} or greater.`,
|
||||
);
|
||||
|
||||
@@ -14,7 +14,6 @@ import { Laptop, Monitor, Smartphone, Tablet } from 'lucide-react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { LoadingPanel } from '@/components/common/LoadingPanel';
|
||||
import { useResultQuery } from '@/components/hooks';
|
||||
import { getClientAuthToken } from '@/lib/client';
|
||||
import { formatLongNumber } from '@/lib/format';
|
||||
import type { HeatmapMode, HeatmapPoint, HeatmapResult, HeatmapSnapshot } from '@/queries/sql';
|
||||
import styles from './Heatmap.module.css';
|
||||
@@ -882,90 +881,14 @@ function SnapshotPreview({
|
||||
snapshot: HeatmapSnapshot;
|
||||
onReady: () => void;
|
||||
}) {
|
||||
if (snapshot.kind === 'iframe') {
|
||||
return <IframeSnapshot snapshot={snapshot} onReady={onReady} />;
|
||||
}
|
||||
|
||||
return <SnapshotImage snapshot={snapshot} onReady={onReady} />;
|
||||
}
|
||||
|
||||
function SnapshotImage({
|
||||
snapshot,
|
||||
onReady,
|
||||
}: {
|
||||
snapshot: Extract<HeatmapSnapshot, { kind: 'image' }>;
|
||||
onReady: () => void;
|
||||
}) {
|
||||
const [src, setSrc] = useState<string | null>(null);
|
||||
const imageUrl = snapshot.imageUrl;
|
||||
|
||||
useEffect(() => {
|
||||
if (!imageUrl) {
|
||||
setSrc(null);
|
||||
onReady();
|
||||
return;
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
const token = getClientAuthToken();
|
||||
let objectUrl: string | null = null;
|
||||
|
||||
setSrc(null);
|
||||
|
||||
fetch(imageUrl, {
|
||||
signal: controller.signal,
|
||||
headers: {
|
||||
...(token ? { authorization: `Bearer ${token}` } : {}),
|
||||
},
|
||||
})
|
||||
.then(async response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`Snapshot image request failed: ${response.status}`);
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
objectUrl = URL.createObjectURL(blob);
|
||||
setSrc(objectUrl);
|
||||
})
|
||||
.catch(() => {
|
||||
setSrc(null);
|
||||
onReady();
|
||||
});
|
||||
|
||||
return () => {
|
||||
controller.abort();
|
||||
|
||||
if (objectUrl) {
|
||||
URL.revokeObjectURL(objectUrl);
|
||||
}
|
||||
};
|
||||
}, [imageUrl, onReady, snapshot.id]);
|
||||
|
||||
const handleLoad = useCallback(() => onReady(), [onReady]);
|
||||
const imageWidth = Math.max(snapshot.pageW, snapshot.viewportW);
|
||||
|
||||
return (
|
||||
<div className={styles.snapshot}>
|
||||
<img
|
||||
className={styles.snapshotImage}
|
||||
src={src || undefined}
|
||||
alt=""
|
||||
draggable={false}
|
||||
onLoad={handleLoad}
|
||||
style={{
|
||||
width: imageWidth,
|
||||
height: snapshot.pageH,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
return <IframeSnapshot snapshot={snapshot} onReady={onReady} />;
|
||||
}
|
||||
|
||||
function IframeSnapshot({
|
||||
snapshot,
|
||||
onReady,
|
||||
}: {
|
||||
snapshot: Extract<HeatmapSnapshot, { kind: 'iframe' }>;
|
||||
snapshot: HeatmapSnapshot;
|
||||
onReady: () => void;
|
||||
}) {
|
||||
const [available, setAvailable] = useState(true);
|
||||
|
||||
@@ -217,7 +217,6 @@ export async function POST(request: Request) {
|
||||
sessionId,
|
||||
visitId,
|
||||
eventType: event.type === 'click' ? HEATMAP_EVENT_TYPE.click : HEATMAP_EVENT_TYPE.scroll,
|
||||
nodeId: null,
|
||||
x: event.type === 'click' ? (event.x ?? null) : null,
|
||||
y: event.type === 'click' ? (event.y ?? null) : null,
|
||||
pageX: event.type === 'click' ? (event.pageX ?? null) : null,
|
||||
@@ -229,9 +228,6 @@ export async function POST(request: Request) {
|
||||
scrollPct: event.type === 'scroll' ? (event.scrollPct ?? null) : null,
|
||||
urlPath: getUrlPath(event.url),
|
||||
createdAt: new Date(event.timestamp ?? fallbackMs),
|
||||
replayChunkIndex: null,
|
||||
replayEventIndex: null,
|
||||
replayTimeMs: null,
|
||||
}));
|
||||
|
||||
if (heatmapRows.length) {
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import { parseRequest } from '@/lib/request';
|
||||
import { notFound, unauthorized } from '@/lib/response';
|
||||
import { canViewAuthenticatedWebsite } from '@/permissions';
|
||||
import { getHeatmapSnapshotImage } from '@/queries/sql/heatmap/ensureHeatmapSnapshot';
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: Promise<{ websiteId: string; snapshotId: string }> },
|
||||
) {
|
||||
const { auth, error } = await parseRequest(request);
|
||||
const { websiteId, snapshotId } = await params;
|
||||
|
||||
if (error) {
|
||||
return error();
|
||||
}
|
||||
|
||||
if (!(await canViewAuthenticatedWebsite(auth, websiteId))) {
|
||||
return unauthorized();
|
||||
}
|
||||
|
||||
const snapshot = await getHeatmapSnapshotImage(websiteId, snapshotId);
|
||||
|
||||
if (!snapshot) {
|
||||
return notFound({ message: 'Snapshot not found.' });
|
||||
}
|
||||
|
||||
return new Response(new Uint8Array(snapshot.imageData), {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': snapshot.mimeType,
|
||||
'Cache-Control': 'private, max-age=300',
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -66,6 +66,15 @@ describe('checkAuth password fingerprint', () => {
|
||||
expect(result?.user?.id).toBe('user-1');
|
||||
});
|
||||
|
||||
test('authorizes a legacy stateless token that does not include a password fingerprint', async () => {
|
||||
parseSecureTokenMock.mockReturnValue({ userId: 'user-1' } as any);
|
||||
mockUser();
|
||||
|
||||
const result = await checkAuth(authedRequest());
|
||||
|
||||
expect(result?.user?.id).toBe('user-1');
|
||||
});
|
||||
|
||||
test('rejects a stateless token whose fingerprint predates a password change', async () => {
|
||||
// Token minted against the old password must stop working once the password changes.
|
||||
parseSecureTokenMock.mockReturnValue({
|
||||
|
||||
+4
-2
@@ -32,7 +32,8 @@ export async function checkAuth(request: Request) {
|
||||
user = await getUser(userId, { includePassword: true });
|
||||
|
||||
// Reject tokens issued before the current password.
|
||||
if (user && hash(user.password) !== payload.pwd) {
|
||||
// Allow legacy stateless tokens that were minted without a password fingerprint.
|
||||
if (user && payload.pwd && hash(user.password) !== payload.pwd) {
|
||||
user = null;
|
||||
}
|
||||
} else if (redis.enabled && authKey) {
|
||||
@@ -41,7 +42,8 @@ export async function checkAuth(request: Request) {
|
||||
if (key?.userId) {
|
||||
user = await getUser(key.userId, { includePassword: true });
|
||||
|
||||
if (user && hash(user.password) !== key.pwd) {
|
||||
// Only enforce password-change invalidation for sessions that include a password fingerprint.
|
||||
if (user && key.pwd && hash(user.password) !== key.pwd) {
|
||||
user = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
import { GetObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
|
||||
|
||||
let client: S3Client | null = null;
|
||||
|
||||
function getBucket() {
|
||||
const bucket = process.env.R2_BUCKET;
|
||||
|
||||
if (!bucket) {
|
||||
throw new Error('R2_BUCKET is not set.');
|
||||
}
|
||||
|
||||
return bucket;
|
||||
}
|
||||
|
||||
function getAccountId() {
|
||||
const accountId = process.env.R2_ACCOUNT_ID;
|
||||
|
||||
if (!accountId) {
|
||||
throw new Error('R2_ACCOUNT_ID is not set.');
|
||||
}
|
||||
|
||||
return accountId;
|
||||
}
|
||||
|
||||
function getCredentials() {
|
||||
const accessKeyId = process.env.R2_ACCESS_KEY_ID;
|
||||
const secretAccessKey = process.env.R2_SECRET_ACCESS_KEY;
|
||||
|
||||
if (!accessKeyId) {
|
||||
throw new Error('R2_ACCESS_KEY_ID is not set.');
|
||||
}
|
||||
|
||||
if (!secretAccessKey) {
|
||||
throw new Error('R2_SECRET_ACCESS_KEY is not set.');
|
||||
}
|
||||
|
||||
return {
|
||||
accessKeyId,
|
||||
secretAccessKey,
|
||||
};
|
||||
}
|
||||
|
||||
function getClient() {
|
||||
if (!client) {
|
||||
client = new S3Client({
|
||||
region: 'auto',
|
||||
endpoint: `https://${getAccountId()}.r2.cloudflarestorage.com`,
|
||||
credentials: getCredentials(),
|
||||
});
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
export async function putHeatmapSnapshot(objectKey: string, imageData: Buffer, mimeType: string) {
|
||||
await getClient().send(
|
||||
new PutObjectCommand({
|
||||
Bucket: getBucket(),
|
||||
Key: objectKey,
|
||||
Body: imageData,
|
||||
ContentType: mimeType,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export async function getHeatmapSnapshot(objectKey: string) {
|
||||
let response;
|
||||
|
||||
try {
|
||||
response = await getClient().send(
|
||||
new GetObjectCommand({
|
||||
Bucket: getBucket(),
|
||||
Key: objectKey,
|
||||
}),
|
||||
);
|
||||
} catch (error: any) {
|
||||
if (
|
||||
error?.name === 'NoSuchKey' ||
|
||||
error?.Code === 'NoSuchKey' ||
|
||||
error?.$metadata?.httpStatusCode === 404
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (!response.Body) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
mimeType: response.ContentType || 'application/octet-stream',
|
||||
imageData: Buffer.from(await response.Body.transformToByteArray()),
|
||||
};
|
||||
}
|
||||
@@ -1,877 +0,0 @@
|
||||
import { serializeError } from 'serialize-error';
|
||||
import { getApiUrl } from '@/lib/api-url';
|
||||
import clickhouse from '@/lib/clickhouse';
|
||||
import { uuid } from '@/lib/crypto';
|
||||
import { getHeatmapSnapshot, putHeatmapSnapshot } from '@/lib/heatmap-r2';
|
||||
import prisma from '@/lib/prisma';
|
||||
import { getWebsite } from '@/queries/prisma';
|
||||
|
||||
const SNAPSHOT_STATUS = {
|
||||
pending: 'pending',
|
||||
ready: 'ready',
|
||||
failed: 'failed',
|
||||
} as const;
|
||||
|
||||
const SNAPSHOT_RETRY_DELAY_MS = 15 * 60 * 1000;
|
||||
const SNAPSHOT_PENDING_WINDOW_MS = 30 * 1000;
|
||||
const SNAPSHOT_DEVICE_SCALE_FACTOR = 1;
|
||||
const SNAPSHOT_UNAVAILABLE_ERROR = 'Page screenshot unavailable.';
|
||||
const SNAPSHOT_ERROR_MAX_LENGTH = 500;
|
||||
export type HeatmapSnapshotStatus = (typeof SNAPSHOT_STATUS)[keyof typeof SNAPSHOT_STATUS];
|
||||
|
||||
export interface HeatmapSnapshotImage {
|
||||
kind: 'image';
|
||||
id: string;
|
||||
imageUrl: string | null;
|
||||
status: HeatmapSnapshotStatus;
|
||||
mimeType: string | null;
|
||||
pageW: number;
|
||||
pageH: number;
|
||||
viewportW: number;
|
||||
viewportH: number;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
interface SnapshotRecord {
|
||||
id: string;
|
||||
websiteId: string;
|
||||
urlPath: string;
|
||||
viewportW: number;
|
||||
viewportH: number;
|
||||
pageW: number;
|
||||
pageH: number;
|
||||
status: HeatmapSnapshotStatus;
|
||||
mimeType: string | null;
|
||||
objectKey: string | null;
|
||||
imageSize: number | null;
|
||||
error: string | null;
|
||||
hasImage: boolean;
|
||||
updatedAt: Date | string | null;
|
||||
}
|
||||
|
||||
interface EnsureHeatmapSnapshotOptions {
|
||||
websiteId: string;
|
||||
urlPath: string;
|
||||
viewportW: number | null;
|
||||
viewportH: number | null;
|
||||
pageW: number | null;
|
||||
pageH: number | null;
|
||||
}
|
||||
|
||||
interface CaptureResult {
|
||||
imageData: Buffer;
|
||||
mimeType: string;
|
||||
pageW: number;
|
||||
pageH: number;
|
||||
}
|
||||
|
||||
const CLICKHOUSE_SNAPSHOT_STATUS = {
|
||||
pending: 0,
|
||||
ready: 1,
|
||||
failed: 2,
|
||||
} as const;
|
||||
|
||||
async function measurePage(page: any) {
|
||||
return page.evaluate(() => {
|
||||
const doc = document.documentElement;
|
||||
const body = document.body;
|
||||
const root = document.scrollingElement || doc || body;
|
||||
const rootClientWidth = root?.clientWidth || 0;
|
||||
const docClientWidth = doc?.clientWidth || 0;
|
||||
const bodyClientWidth = body?.clientWidth || 0;
|
||||
const visibleWidth = Math.max(
|
||||
window.innerWidth,
|
||||
rootClientWidth,
|
||||
docClientWidth,
|
||||
bodyClientWidth,
|
||||
);
|
||||
const rootScrollWidth = root?.scrollWidth || 0;
|
||||
const docScrollWidth = doc?.scrollWidth || 0;
|
||||
const bodyScrollWidth = body?.scrollWidth || 0;
|
||||
const horizontalOverflow = Math.max(
|
||||
rootScrollWidth - rootClientWidth,
|
||||
docScrollWidth - docClientWidth,
|
||||
bodyScrollWidth - bodyClientWidth,
|
||||
0,
|
||||
);
|
||||
let maxRight = 0;
|
||||
let maxBottom = 0;
|
||||
|
||||
if (body) {
|
||||
const walker = document.createTreeWalker(body, NodeFilter.SHOW_ELEMENT);
|
||||
let node = walker.currentNode as Element | null;
|
||||
|
||||
while (node) {
|
||||
const rect = node.getBoundingClientRect?.();
|
||||
|
||||
if (rect && (rect.width > 0 || rect.height > 0)) {
|
||||
maxRight = Math.max(maxRight, rect.right);
|
||||
maxBottom = Math.max(maxBottom, rect.bottom);
|
||||
}
|
||||
|
||||
node = walker.nextNode() as Element | null;
|
||||
}
|
||||
}
|
||||
|
||||
const pageW =
|
||||
horizontalOverflow > 24
|
||||
? Math.max(visibleWidth, rootScrollWidth, docScrollWidth, bodyScrollWidth)
|
||||
: visibleWidth;
|
||||
const pageH = Math.max(
|
||||
window.innerHeight,
|
||||
root?.scrollHeight || 0,
|
||||
doc?.scrollHeight || 0,
|
||||
body?.scrollHeight || 0,
|
||||
Math.ceil(maxBottom),
|
||||
);
|
||||
|
||||
return {
|
||||
pageW: Math.ceil(pageW),
|
||||
pageH: Math.ceil(pageH),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async function warmLazyContent(page: any) {
|
||||
await page.evaluate(async () => {
|
||||
const root = document.scrollingElement || document.documentElement;
|
||||
|
||||
if (!root) {
|
||||
return;
|
||||
}
|
||||
|
||||
const maxScrollTop = Math.max(0, root.scrollHeight - window.innerHeight);
|
||||
|
||||
if (maxScrollTop <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targets = [
|
||||
Math.min(window.innerHeight, maxScrollTop),
|
||||
Math.min(Math.round(maxScrollTop * 0.25), maxScrollTop),
|
||||
Math.min(Math.round(maxScrollTop * 0.5), maxScrollTop),
|
||||
Math.min(Math.round(maxScrollTop * 0.75), maxScrollTop),
|
||||
maxScrollTop,
|
||||
].filter((value, index, values) => value > 0 && values.indexOf(value) === index);
|
||||
|
||||
for (const top of targets) {
|
||||
window.scrollTo(0, top);
|
||||
await new Promise(resolve => window.setTimeout(resolve, 350));
|
||||
}
|
||||
|
||||
window.scrollTo(0, 0);
|
||||
await new Promise(resolve => window.setTimeout(resolve, 250));
|
||||
});
|
||||
}
|
||||
|
||||
function getSchema() {
|
||||
const databaseUrl = process.env.DATABASE_URL;
|
||||
|
||||
if (!databaseUrl) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const connectionUrl = new URL(databaseUrl);
|
||||
|
||||
return connectionUrl.searchParams.get('schema');
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function rawExecute(sql: string, data: Record<string, any> = {}) {
|
||||
const params: any[] = [];
|
||||
const schema = getSchema();
|
||||
|
||||
if (schema) {
|
||||
await prisma.client.$executeRawUnsafe(`SET search_path TO "${schema}";`);
|
||||
}
|
||||
|
||||
const query = sql.replaceAll(/\{\{\s*(\w+)(::\w+)?\s*}}/g, (...args) => {
|
||||
const [, name, type] = args;
|
||||
|
||||
params.push(data[name]);
|
||||
|
||||
return `$${params.length}${type ?? ''}`;
|
||||
});
|
||||
|
||||
return prisma.client.$executeRawUnsafe(query, ...params);
|
||||
}
|
||||
|
||||
async function findSnapshot(
|
||||
websiteId: string,
|
||||
urlPath: string,
|
||||
viewportW: number,
|
||||
viewportH: number,
|
||||
): Promise<SnapshotRecord | null> {
|
||||
if (clickhouse.enabled) {
|
||||
return findClickhouseSnapshot(websiteId, urlPath, viewportW, viewportH);
|
||||
}
|
||||
|
||||
return findRelationalSnapshot(websiteId, urlPath, viewportW, viewportH);
|
||||
}
|
||||
|
||||
async function findRelationalSnapshot(
|
||||
websiteId: string,
|
||||
urlPath: string,
|
||||
viewportW: number,
|
||||
viewportH: number,
|
||||
): Promise<SnapshotRecord | null> {
|
||||
const rows = await prisma.rawQuery(
|
||||
`
|
||||
select
|
||||
snapshot_id as id,
|
||||
website_id as "websiteId",
|
||||
url_path as "urlPath",
|
||||
viewport_w as "viewportW",
|
||||
viewport_h as "viewportH",
|
||||
page_w as "pageW",
|
||||
page_h as "pageH",
|
||||
status,
|
||||
mime_type as "mimeType",
|
||||
null as "objectKey",
|
||||
image_size as "imageSize",
|
||||
error,
|
||||
image_data is not null as "hasImage",
|
||||
updated_at as "updatedAt"
|
||||
from heatmap_snapshot
|
||||
where website_id = {{websiteId::uuid}}
|
||||
and url_path = {{urlPath}}
|
||||
and viewport_w = {{viewportW}}
|
||||
and viewport_h = {{viewportH}}
|
||||
limit 1
|
||||
`,
|
||||
{ websiteId, urlPath, viewportW, viewportH },
|
||||
'findHeatmapSnapshot',
|
||||
);
|
||||
|
||||
return rows?.[0] ?? null;
|
||||
}
|
||||
|
||||
async function findClickhouseSnapshot(
|
||||
websiteId: string,
|
||||
urlPath: string,
|
||||
viewportW: number,
|
||||
viewportH: number,
|
||||
): Promise<SnapshotRecord | null> {
|
||||
const rows = await clickhouse.rawQuery<
|
||||
{
|
||||
id: string;
|
||||
websiteId: string;
|
||||
urlPath: string;
|
||||
viewportW: number;
|
||||
viewportH: number;
|
||||
pageW: number;
|
||||
pageH: number;
|
||||
status: number;
|
||||
mimeType: string | null;
|
||||
objectKey: string;
|
||||
imageSize: number | null;
|
||||
error: string | null;
|
||||
createdAt: string;
|
||||
}[]
|
||||
>(
|
||||
`
|
||||
select
|
||||
snapshot_id as id,
|
||||
website_id as websiteId,
|
||||
url_path as urlPath,
|
||||
viewport_w as viewportW,
|
||||
viewport_h as viewportH,
|
||||
page_w as pageW,
|
||||
page_h as pageH,
|
||||
status,
|
||||
mime_type as mimeType,
|
||||
object_key as objectKey,
|
||||
image_size as imageSize,
|
||||
error,
|
||||
created_at as createdAt
|
||||
from heatmap_snapshot
|
||||
where website_id = {websiteId:UUID}
|
||||
and url_path = {urlPath:String}
|
||||
and viewport_w = {viewportW:UInt32}
|
||||
and viewport_h = {viewportH:UInt32}
|
||||
order by created_at desc
|
||||
limit 1
|
||||
`,
|
||||
{ websiteId, urlPath, viewportW, viewportH },
|
||||
'findHeatmapSnapshot',
|
||||
);
|
||||
|
||||
const row = rows?.[0];
|
||||
|
||||
if (!row) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const status = Object.entries(CLICKHOUSE_SNAPSHOT_STATUS).find(
|
||||
([, value]) => value === row.status,
|
||||
)?.[0];
|
||||
|
||||
if (!status) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...row,
|
||||
status: SNAPSHOT_STATUS[status as keyof typeof SNAPSHOT_STATUS],
|
||||
mimeType: row.mimeType || null,
|
||||
objectKey: row.objectKey || null,
|
||||
hasImage: row.status === CLICKHOUSE_SNAPSHOT_STATUS.ready && Boolean(row.objectKey),
|
||||
updatedAt: row.createdAt,
|
||||
};
|
||||
}
|
||||
|
||||
function getSnapshotImageUrl(websiteId: string, snapshotId: string) {
|
||||
return getApiUrl(`/websites/${websiteId}/heatmaps/snapshots/${snapshotId}`, {
|
||||
apiUrl: process.env.API_URL,
|
||||
basePath: process.env.BASE_PATH,
|
||||
});
|
||||
}
|
||||
|
||||
function mapSnapshot(websiteId: string, row: SnapshotRecord): HeatmapSnapshotImage {
|
||||
return {
|
||||
kind: 'image',
|
||||
id: row.id,
|
||||
imageUrl:
|
||||
row.status === SNAPSHOT_STATUS.ready && row.hasImage
|
||||
? getSnapshotImageUrl(websiteId, row.id)
|
||||
: null,
|
||||
status: row.status,
|
||||
mimeType: row.mimeType,
|
||||
pageW: Number(row.pageW),
|
||||
pageH: Number(row.pageH),
|
||||
viewportW: Number(row.viewportW),
|
||||
viewportH: Number(row.viewportH),
|
||||
error: row.error,
|
||||
};
|
||||
}
|
||||
|
||||
function getFirstDomain(domain?: string | null) {
|
||||
return domain?.split(',')[0]?.trim() || null;
|
||||
}
|
||||
|
||||
function getWebsiteOrigin(domain?: string | null) {
|
||||
const host = getFirstDomain(domain);
|
||||
|
||||
if (!host) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (host.startsWith('http://') || host.startsWith('https://')) {
|
||||
return new URL(host);
|
||||
}
|
||||
|
||||
const protocol =
|
||||
host.startsWith('localhost') || host.startsWith('127.0.0.1') || host.startsWith('[::1]')
|
||||
? 'http'
|
||||
: 'https';
|
||||
|
||||
return new URL(`${protocol}://${host}`);
|
||||
}
|
||||
|
||||
export function buildHeatmapPageUrl(domain: string | null | undefined, urlPath: string) {
|
||||
try {
|
||||
const origin = getWebsiteOrigin(domain);
|
||||
|
||||
if (!origin) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new URL(urlPath || '/', origin).toString();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function shouldSkipSnapshot(urlPath: string) {
|
||||
// Internal Umami app routes cannot be rendered from the tracked website domain.
|
||||
return urlPath.startsWith('/teams/');
|
||||
}
|
||||
|
||||
async function upsertSnapshotRecord({
|
||||
id,
|
||||
websiteId,
|
||||
urlPath,
|
||||
viewportW,
|
||||
viewportH,
|
||||
pageW,
|
||||
pageH,
|
||||
status,
|
||||
mimeType,
|
||||
imageData,
|
||||
objectKey,
|
||||
error,
|
||||
}: {
|
||||
id: string;
|
||||
websiteId: string;
|
||||
urlPath: string;
|
||||
viewportW: number;
|
||||
viewportH: number;
|
||||
pageW: number;
|
||||
pageH: number;
|
||||
status: HeatmapSnapshotStatus;
|
||||
mimeType: string | null;
|
||||
imageData: Buffer | null;
|
||||
objectKey?: string | null;
|
||||
error: string | null;
|
||||
}) {
|
||||
if (clickhouse.enabled) {
|
||||
return insertClickhouseSnapshotRecord({
|
||||
id,
|
||||
websiteId,
|
||||
urlPath,
|
||||
viewportW,
|
||||
viewportH,
|
||||
pageW,
|
||||
pageH,
|
||||
status,
|
||||
mimeType,
|
||||
objectKey: objectKey ?? null,
|
||||
imageSize: imageData?.byteLength ?? null,
|
||||
error,
|
||||
});
|
||||
}
|
||||
|
||||
return upsertRelationalSnapshotRecord({
|
||||
id,
|
||||
websiteId,
|
||||
urlPath,
|
||||
viewportW,
|
||||
viewportH,
|
||||
pageW,
|
||||
pageH,
|
||||
status,
|
||||
mimeType,
|
||||
imageData,
|
||||
error,
|
||||
});
|
||||
}
|
||||
|
||||
async function upsertRelationalSnapshotRecord({
|
||||
id,
|
||||
websiteId,
|
||||
urlPath,
|
||||
viewportW,
|
||||
viewportH,
|
||||
pageW,
|
||||
pageH,
|
||||
status,
|
||||
mimeType,
|
||||
imageData,
|
||||
error,
|
||||
}: {
|
||||
id: string;
|
||||
websiteId: string;
|
||||
urlPath: string;
|
||||
viewportW: number;
|
||||
viewportH: number;
|
||||
pageW: number;
|
||||
pageH: number;
|
||||
status: HeatmapSnapshotStatus;
|
||||
mimeType: string | null;
|
||||
imageData: Buffer | null;
|
||||
error: string | null;
|
||||
}) {
|
||||
return rawExecute(
|
||||
`
|
||||
insert into heatmap_snapshot (
|
||||
snapshot_id,
|
||||
website_id,
|
||||
url_path,
|
||||
viewport_w,
|
||||
viewport_h,
|
||||
page_w,
|
||||
page_h,
|
||||
status,
|
||||
mime_type,
|
||||
image_data,
|
||||
image_size,
|
||||
error,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
values (
|
||||
{{id::uuid}},
|
||||
{{websiteId::uuid}},
|
||||
{{urlPath}},
|
||||
{{viewportW}},
|
||||
{{viewportH}},
|
||||
{{pageW}},
|
||||
{{pageH}},
|
||||
{{status}},
|
||||
{{mimeType}},
|
||||
{{imageData}},
|
||||
{{imageSize}},
|
||||
{{error}},
|
||||
now(),
|
||||
now()
|
||||
)
|
||||
on conflict (website_id, url_path, viewport_w, viewport_h) do update
|
||||
set
|
||||
page_w = excluded.page_w,
|
||||
page_h = excluded.page_h,
|
||||
status = excluded.status,
|
||||
mime_type = excluded.mime_type,
|
||||
image_data = excluded.image_data,
|
||||
image_size = excluded.image_size,
|
||||
error = excluded.error,
|
||||
updated_at = now()
|
||||
`,
|
||||
{
|
||||
id,
|
||||
websiteId,
|
||||
urlPath,
|
||||
viewportW,
|
||||
viewportH,
|
||||
pageW,
|
||||
pageH,
|
||||
status,
|
||||
mimeType,
|
||||
imageData,
|
||||
imageSize: imageData?.byteLength ?? null,
|
||||
error,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async function insertClickhouseSnapshotRecord({
|
||||
id,
|
||||
websiteId,
|
||||
urlPath,
|
||||
viewportW,
|
||||
viewportH,
|
||||
pageW,
|
||||
pageH,
|
||||
status,
|
||||
mimeType,
|
||||
objectKey,
|
||||
imageSize,
|
||||
error,
|
||||
}: {
|
||||
id: string;
|
||||
websiteId: string;
|
||||
urlPath: string;
|
||||
viewportW: number;
|
||||
viewportH: number;
|
||||
pageW: number;
|
||||
pageH: number;
|
||||
status: HeatmapSnapshotStatus;
|
||||
mimeType: string | null;
|
||||
objectKey: string | null;
|
||||
imageSize: number | null;
|
||||
error: string | null;
|
||||
}) {
|
||||
return clickhouse.insert('heatmap_snapshot', [
|
||||
{
|
||||
snapshot_id: id,
|
||||
website_id: websiteId,
|
||||
url_path: urlPath,
|
||||
viewport_w: viewportW,
|
||||
viewport_h: viewportH,
|
||||
page_w: pageW,
|
||||
page_h: pageH,
|
||||
status: CLICKHOUSE_SNAPSHOT_STATUS[status],
|
||||
mime_type: mimeType || '',
|
||||
object_key: objectKey || '',
|
||||
image_size: imageSize,
|
||||
error,
|
||||
created_at: clickhouse.getUTCString(),
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
function getSnapshotObjectKey(
|
||||
websiteId: string,
|
||||
snapshotId: string,
|
||||
viewportW: number,
|
||||
viewportH: number,
|
||||
) {
|
||||
return `${websiteId}/${viewportW}x${viewportH}/${snapshotId}.png`;
|
||||
}
|
||||
|
||||
function getSnapshotErrorMessage(error: unknown) {
|
||||
const message =
|
||||
error instanceof Error
|
||||
? [error.name, error.message].filter(Boolean).join(': ')
|
||||
: typeof error === 'string'
|
||||
? error
|
||||
: SNAPSHOT_UNAVAILABLE_ERROR;
|
||||
|
||||
return message.slice(0, SNAPSHOT_ERROR_MAX_LENGTH) || SNAPSHOT_UNAVAILABLE_ERROR;
|
||||
}
|
||||
|
||||
async function createSnapshotBrowser() {
|
||||
const endpoint = process.env.PLAYWRIGHT_URL?.trim();
|
||||
|
||||
if (!endpoint) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { chromium } = await import('playwright-core');
|
||||
return chromium.connect(endpoint);
|
||||
}
|
||||
|
||||
async function captureSnapshot(
|
||||
url: string,
|
||||
viewportW: number,
|
||||
viewportH: number,
|
||||
): Promise<CaptureResult> {
|
||||
const browser = await createSnapshotBrowser();
|
||||
|
||||
if (!browser) {
|
||||
throw new Error(SNAPSHOT_UNAVAILABLE_ERROR);
|
||||
}
|
||||
|
||||
const initialViewportW = viewportW;
|
||||
|
||||
try {
|
||||
const context = await browser.newContext({
|
||||
viewport: { width: initialViewportW, height: viewportH },
|
||||
screen: { width: initialViewportW, height: viewportH },
|
||||
deviceScaleFactor: SNAPSHOT_DEVICE_SCALE_FACTOR,
|
||||
ignoreHTTPSErrors: true,
|
||||
});
|
||||
|
||||
const page = await context.newPage();
|
||||
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 15000 });
|
||||
await page.waitForLoadState('networkidle', { timeout: 5000 }).catch(() => undefined);
|
||||
await page.waitForTimeout(500);
|
||||
await warmLazyContent(page);
|
||||
await page.waitForLoadState('networkidle', { timeout: 3000 }).catch(() => undefined);
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
let dimensions = await measurePage(page);
|
||||
let currentWidth = initialViewportW;
|
||||
let captureWidth =
|
||||
dimensions.pageW > initialViewportW + 24
|
||||
? Math.max(initialViewportW, dimensions.pageW)
|
||||
: initialViewportW;
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
if (captureWidth <= currentWidth) {
|
||||
break;
|
||||
}
|
||||
|
||||
currentWidth = captureWidth;
|
||||
|
||||
await page.setViewportSize({
|
||||
width: currentWidth,
|
||||
height: viewportH,
|
||||
});
|
||||
await page.waitForLoadState('networkidle', { timeout: 3000 }).catch(() => undefined);
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
dimensions = await measurePage(page);
|
||||
captureWidth = Math.max(currentWidth, dimensions.pageW);
|
||||
}
|
||||
|
||||
const imageData = Buffer.from(await page.screenshot({ fullPage: true, type: 'png' }));
|
||||
|
||||
await context.close();
|
||||
|
||||
return {
|
||||
imageData,
|
||||
mimeType: 'image/png',
|
||||
pageW: dimensions.pageW,
|
||||
pageH: dimensions.pageH,
|
||||
};
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
export async function ensureHeatmapSnapshot({
|
||||
websiteId,
|
||||
urlPath,
|
||||
viewportW,
|
||||
viewportH,
|
||||
pageW,
|
||||
pageH,
|
||||
}: EnsureHeatmapSnapshotOptions): Promise<HeatmapSnapshotImage | null> {
|
||||
if (!urlPath || !viewportW || !viewportH || !pageW || !pageH) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (shouldSkipSnapshot(urlPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!process.env.PLAYWRIGHT_URL?.trim()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const existing = await findSnapshot(websiteId, urlPath, viewportW, viewportH);
|
||||
|
||||
if (existing?.status === SNAPSHOT_STATUS.ready && existing.hasImage) {
|
||||
return mapSnapshot(websiteId, existing);
|
||||
}
|
||||
|
||||
const updatedAt = existing?.updatedAt ? new Date(existing.updatedAt) : null;
|
||||
const ageMs = updatedAt ? Date.now() - updatedAt.getTime() : Number.POSITIVE_INFINITY;
|
||||
|
||||
if (existing?.status === SNAPSHOT_STATUS.pending && ageMs < SNAPSHOT_PENDING_WINDOW_MS) {
|
||||
return mapSnapshot(websiteId, existing);
|
||||
}
|
||||
|
||||
if (existing?.status === SNAPSHOT_STATUS.failed && ageMs < SNAPSHOT_RETRY_DELAY_MS) {
|
||||
return mapSnapshot(websiteId, existing);
|
||||
}
|
||||
|
||||
const snapshotId = existing?.id ?? uuid();
|
||||
const website = await getWebsite(websiteId);
|
||||
const captureUrl = buildHeatmapPageUrl(website?.domain, urlPath);
|
||||
|
||||
if (!captureUrl) {
|
||||
await upsertSnapshotRecord({
|
||||
id: snapshotId,
|
||||
websiteId,
|
||||
urlPath,
|
||||
viewportW,
|
||||
viewportH,
|
||||
pageW,
|
||||
pageH,
|
||||
status: SNAPSHOT_STATUS.failed,
|
||||
mimeType: null,
|
||||
imageData: null,
|
||||
error: SNAPSHOT_UNAVAILABLE_ERROR,
|
||||
});
|
||||
|
||||
const failed = await findSnapshot(websiteId, urlPath, viewportW, viewportH);
|
||||
|
||||
return failed ? mapSnapshot(websiteId, failed) : null;
|
||||
}
|
||||
|
||||
await upsertSnapshotRecord({
|
||||
id: snapshotId,
|
||||
websiteId,
|
||||
urlPath,
|
||||
viewportW,
|
||||
viewportH,
|
||||
pageW,
|
||||
pageH,
|
||||
status: SNAPSHOT_STATUS.pending,
|
||||
mimeType: null,
|
||||
imageData: null,
|
||||
error: null,
|
||||
});
|
||||
|
||||
try {
|
||||
const capture = await captureSnapshot(captureUrl, viewportW, viewportH);
|
||||
const objectKey = clickhouse.enabled
|
||||
? getSnapshotObjectKey(websiteId, snapshotId, viewportW, viewportH)
|
||||
: null;
|
||||
|
||||
if (objectKey) {
|
||||
await putHeatmapSnapshot(objectKey, capture.imageData, capture.mimeType);
|
||||
}
|
||||
|
||||
await upsertSnapshotRecord({
|
||||
id: snapshotId,
|
||||
websiteId,
|
||||
urlPath,
|
||||
viewportW,
|
||||
viewportH,
|
||||
pageW: capture.pageW,
|
||||
pageH: capture.pageH,
|
||||
status: SNAPSHOT_STATUS.ready,
|
||||
mimeType: capture.mimeType,
|
||||
imageData: clickhouse.enabled ? null : capture.imageData,
|
||||
objectKey,
|
||||
error: null,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Heatmap snapshot capture failed', {
|
||||
captureUrl,
|
||||
websiteId,
|
||||
urlPath,
|
||||
viewportW,
|
||||
viewportH,
|
||||
pageW,
|
||||
pageH,
|
||||
error: serializeError(error),
|
||||
});
|
||||
|
||||
await upsertSnapshotRecord({
|
||||
id: snapshotId,
|
||||
websiteId,
|
||||
urlPath,
|
||||
viewportW,
|
||||
viewportH,
|
||||
pageW,
|
||||
pageH,
|
||||
status: SNAPSHOT_STATUS.failed,
|
||||
mimeType: null,
|
||||
imageData: null,
|
||||
error: getSnapshotErrorMessage(error),
|
||||
});
|
||||
}
|
||||
|
||||
const snapshot = await findSnapshot(websiteId, urlPath, viewportW, viewportH);
|
||||
|
||||
return snapshot ? mapSnapshot(websiteId, snapshot) : null;
|
||||
}
|
||||
|
||||
export async function getHeatmapSnapshotImage(
|
||||
websiteId: string,
|
||||
snapshotId: string,
|
||||
): Promise<{ mimeType: string; imageData: Buffer } | null> {
|
||||
if (clickhouse.enabled) {
|
||||
const rows = await clickhouse.rawQuery<{ mimeType: string; objectKey: string }[]>(
|
||||
`
|
||||
select
|
||||
mime_type as mimeType,
|
||||
object_key as objectKey
|
||||
from heatmap_snapshot
|
||||
where snapshot_id = {snapshotId:UUID}
|
||||
and website_id = {websiteId:UUID}
|
||||
and status = {status:UInt8}
|
||||
and object_key != ''
|
||||
order by created_at desc
|
||||
limit 1
|
||||
`,
|
||||
{
|
||||
websiteId,
|
||||
snapshotId,
|
||||
status: CLICKHOUSE_SNAPSHOT_STATUS.ready,
|
||||
},
|
||||
'getHeatmapSnapshotImage',
|
||||
);
|
||||
|
||||
const row = rows?.[0];
|
||||
|
||||
if (!row?.objectKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return getHeatmapSnapshot(row.objectKey);
|
||||
}
|
||||
|
||||
const rows = await prisma.rawQuery(
|
||||
`
|
||||
select
|
||||
mime_type as "mimeType",
|
||||
image_data as "imageData"
|
||||
from heatmap_snapshot
|
||||
where snapshot_id = {{snapshotId::uuid}}
|
||||
and website_id = {{websiteId::uuid}}
|
||||
and status = 'ready'
|
||||
and image_data is not null
|
||||
limit 1
|
||||
`,
|
||||
{ websiteId, snapshotId },
|
||||
'getHeatmapSnapshotImage',
|
||||
);
|
||||
|
||||
const row = rows?.[0];
|
||||
|
||||
if (!row?.imageData || !row?.mimeType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
mimeType: row.mimeType,
|
||||
imageData: Buffer.from(row.imageData),
|
||||
};
|
||||
}
|
||||
@@ -9,7 +9,6 @@ const RRWEB_MOUSE_CLICK = 2;
|
||||
|
||||
export interface ExtractedHeatmapEvent {
|
||||
eventType: number;
|
||||
nodeId: number | null;
|
||||
x: number | null;
|
||||
y: number | null;
|
||||
viewportW: number | null;
|
||||
@@ -18,9 +17,6 @@ export interface ExtractedHeatmapEvent {
|
||||
scrollPct: number | null;
|
||||
urlPath: string;
|
||||
createdAt: Date;
|
||||
replayChunkIndex: number | null;
|
||||
replayEventIndex: number | null;
|
||||
replayTimeMs: number | null;
|
||||
}
|
||||
|
||||
interface ExtractHeatmapEventOptions {
|
||||
@@ -36,10 +32,7 @@ function safePathname(href: unknown): string | null {
|
||||
}
|
||||
}
|
||||
|
||||
export function extractHeatmapEvents(
|
||||
events: any[],
|
||||
{ chunkIndex }: ExtractHeatmapEventOptions = {},
|
||||
): ExtractedHeatmapEvent[] {
|
||||
export function extractHeatmapEvents(events: any[], _options: ExtractHeatmapEventOptions = {}) {
|
||||
if (!Array.isArray(events) || events.length === 0) return [];
|
||||
|
||||
let urlPath: string | null = null;
|
||||
@@ -47,7 +40,7 @@ export function extractHeatmapEvents(
|
||||
let viewportH: number | null = null;
|
||||
const out: ExtractedHeatmapEvent[] = [];
|
||||
|
||||
for (const [eventIndex, ev] of events.entries()) {
|
||||
for (const ev of events) {
|
||||
if (!ev || typeof ev !== 'object') continue;
|
||||
const replayTimeMs =
|
||||
typeof ev.timestamp === 'number' && Number.isFinite(ev.timestamp) ? ev.timestamp : null;
|
||||
@@ -73,7 +66,6 @@ export function extractHeatmapEvents(
|
||||
if (path === null) continue;
|
||||
out.push({
|
||||
eventType: HEATMAP_EVENT_TYPE.scroll,
|
||||
nodeId: null,
|
||||
x: null,
|
||||
y: null,
|
||||
viewportW: typeof p.viewportW === 'number' ? p.viewportW : viewportW,
|
||||
@@ -85,9 +77,6 @@ export function extractHeatmapEvents(
|
||||
: null,
|
||||
urlPath: path,
|
||||
createdAt: new Date(replayTimeMs ?? Date.now()),
|
||||
replayChunkIndex: chunkIndex ?? null,
|
||||
replayEventIndex: eventIndex,
|
||||
replayTimeMs,
|
||||
});
|
||||
}
|
||||
continue;
|
||||
@@ -110,7 +99,6 @@ export function extractHeatmapEvents(
|
||||
) {
|
||||
out.push({
|
||||
eventType: HEATMAP_EVENT_TYPE.click,
|
||||
nodeId: typeof d.id === 'number' ? d.id : null,
|
||||
x: typeof d.x === 'number' ? Math.round(d.x) : null,
|
||||
y: typeof d.y === 'number' ? Math.round(d.y) : null,
|
||||
viewportW,
|
||||
@@ -119,9 +107,6 @@ export function extractHeatmapEvents(
|
||||
scrollPct: null,
|
||||
urlPath,
|
||||
createdAt: new Date(replayTimeMs ?? Date.now()),
|
||||
replayChunkIndex: chunkIndex ?? null,
|
||||
replayEventIndex: eventIndex,
|
||||
replayTimeMs,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,6 @@ import { filtersObjectToArray } from '@/lib/params';
|
||||
import prisma from '@/lib/prisma';
|
||||
import type { QueryFilters } from '@/lib/types';
|
||||
import { getWebsite } from '@/queries/prisma';
|
||||
import {
|
||||
buildHeatmapPageUrl,
|
||||
ensureHeatmapSnapshot,
|
||||
type HeatmapSnapshotImage,
|
||||
shouldSkipSnapshot,
|
||||
} from './ensureHeatmapSnapshot';
|
||||
|
||||
const FUNCTION_NAME = 'getHeatmap';
|
||||
|
||||
@@ -32,7 +26,6 @@ export interface HeatmapPage {
|
||||
}
|
||||
|
||||
export interface HeatmapPoint {
|
||||
nodeId: number | null;
|
||||
x: number;
|
||||
y: number;
|
||||
pageX: number;
|
||||
@@ -63,7 +56,7 @@ export interface HeatmapSnapshotIframe {
|
||||
viewportH: number;
|
||||
}
|
||||
|
||||
export type HeatmapSnapshot = HeatmapSnapshotImage | HeatmapSnapshotIframe;
|
||||
export type HeatmapSnapshot = HeatmapSnapshotIframe;
|
||||
|
||||
export interface HeatmapResult {
|
||||
mode: HeatmapMode;
|
||||
@@ -144,7 +137,7 @@ async function relationalQuery(
|
||||
{ ...filterContext.queryParams, websiteId, eventType, startDate, endDate },
|
||||
FUNCTION_NAME,
|
||||
);
|
||||
const pages = rawPages.filter(page => !shouldSkipSnapshot(page.urlPath));
|
||||
const pages = rawPages;
|
||||
|
||||
if (!urlPath) {
|
||||
return { mode, pages, points: [], snapshot: null, scroll: emptyScroll() };
|
||||
@@ -234,7 +227,6 @@ async function relationalQuery(
|
||||
const rawPoints: HeatmapPoint[] = await rawQuery(
|
||||
`
|
||||
select
|
||||
h.node_id as "nodeId",
|
||||
h.x,
|
||||
h.y,
|
||||
h.page_x as "pageX",
|
||||
@@ -260,7 +252,6 @@ async function relationalQuery(
|
||||
and h.viewport_w is not null
|
||||
and h.viewport_h is not null
|
||||
group by
|
||||
h.node_id,
|
||||
h.x,
|
||||
h.y,
|
||||
h.page_x,
|
||||
@@ -345,8 +336,7 @@ async function clickhouseQuery(
|
||||
urlPath: p.urlPath,
|
||||
count: Number(p.count),
|
||||
sessions: Number(p.sessions),
|
||||
}))
|
||||
.filter(page => !shouldSkipSnapshot(page.urlPath));
|
||||
}));
|
||||
|
||||
if (!urlPath) {
|
||||
return { mode, pages, points: [], snapshot: null, scroll: emptyScroll() };
|
||||
@@ -441,7 +431,6 @@ async function clickhouseQuery(
|
||||
|
||||
const pointRows = await rawQuery<
|
||||
{
|
||||
nodeId: number | null;
|
||||
x: number;
|
||||
y: number;
|
||||
pageX: number;
|
||||
@@ -455,7 +444,6 @@ async function clickhouseQuery(
|
||||
>(
|
||||
`
|
||||
select
|
||||
h.node_id as nodeId,
|
||||
h.x,
|
||||
h.y,
|
||||
h.page_x as pageX,
|
||||
@@ -481,7 +469,6 @@ async function clickhouseQuery(
|
||||
and h.viewport_w is not null
|
||||
and h.viewport_h is not null
|
||||
group by
|
||||
h.node_id,
|
||||
h.x,
|
||||
h.y,
|
||||
h.page_x,
|
||||
@@ -498,7 +485,6 @@ async function clickhouseQuery(
|
||||
);
|
||||
|
||||
const points: HeatmapPoint[] = pointRows.map(p => ({
|
||||
nodeId: p.nodeId === null || p.nodeId === undefined ? null : Number(p.nodeId),
|
||||
x: Number(p.x),
|
||||
y: Number(p.y),
|
||||
pageX: Number(p.pageX),
|
||||
@@ -549,19 +535,6 @@ async function resolveHeatmapSnapshot({
|
||||
pageW: number | null;
|
||||
pageH: number | null;
|
||||
}): Promise<HeatmapSnapshot | null> {
|
||||
const imageSnapshot = await ensureHeatmapSnapshot({
|
||||
websiteId,
|
||||
urlPath,
|
||||
viewportW,
|
||||
viewportH,
|
||||
pageW,
|
||||
pageH,
|
||||
});
|
||||
|
||||
if (imageSnapshot?.status === 'ready' && imageSnapshot.imageUrl) {
|
||||
return imageSnapshot;
|
||||
}
|
||||
|
||||
return getIframeSnapshot({
|
||||
websiteId,
|
||||
urlPath,
|
||||
@@ -587,7 +560,7 @@ async function getIframeSnapshot({
|
||||
pageW: number | null;
|
||||
pageH: number | null;
|
||||
}): Promise<HeatmapSnapshotIframe | null> {
|
||||
if (!urlPath || !viewportW || !pageW || !pageH || shouldSkipSnapshot(urlPath)) {
|
||||
if (!urlPath || !viewportW || !pageW || !pageH) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -612,6 +585,43 @@ async function getIframeSnapshot({
|
||||
};
|
||||
}
|
||||
|
||||
function getFirstDomain(domain?: string | null) {
|
||||
return domain?.split(',')[0]?.trim() || null;
|
||||
}
|
||||
|
||||
function getWebsiteOrigin(domain?: string | null) {
|
||||
const host = getFirstDomain(domain);
|
||||
|
||||
if (!host) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (host.startsWith('http://') || host.startsWith('https://')) {
|
||||
return new URL(host);
|
||||
}
|
||||
|
||||
const protocol =
|
||||
host.startsWith('localhost') || host.startsWith('127.0.0.1') || host.startsWith('[::1]')
|
||||
? 'http'
|
||||
: 'https';
|
||||
|
||||
return new URL(`${protocol}://${host}`);
|
||||
}
|
||||
|
||||
function buildHeatmapPageUrl(domain: string | null | undefined, urlPath: string) {
|
||||
try {
|
||||
const origin = getWebsiteOrigin(domain);
|
||||
|
||||
if (!origin) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new URL(urlPath || '/', origin).toString();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function pickSnapshotViewport(
|
||||
points: HeatmapPoint[],
|
||||
): { width: number; height: number; pageW: number; pageH: number } | null {
|
||||
|
||||
@@ -10,7 +10,6 @@ export interface HeatmapEventRow {
|
||||
visitId: string;
|
||||
urlPath: string;
|
||||
eventType: number;
|
||||
nodeId: number | null;
|
||||
x: number | null;
|
||||
y: number | null;
|
||||
pageX: number | null;
|
||||
@@ -21,9 +20,6 @@ export interface HeatmapEventRow {
|
||||
pageH: number | null;
|
||||
scrollPct: number | null;
|
||||
createdAt: Date;
|
||||
replayChunkIndex: number | null;
|
||||
replayEventIndex: number | null;
|
||||
replayTimeMs: number | null;
|
||||
}
|
||||
|
||||
export async function saveHeatmapEvents(rows: HeatmapEventRow[]) {
|
||||
@@ -31,7 +27,6 @@ export async function saveHeatmapEvents(rows: HeatmapEventRow[]) {
|
||||
|
||||
const normalizedRows = rows.map(r => ({
|
||||
...r,
|
||||
nodeId: toInt(r.nodeId),
|
||||
x: toInt(r.x),
|
||||
y: toInt(r.y),
|
||||
pageX: toInt(r.pageX),
|
||||
@@ -70,7 +65,6 @@ async function relationalQuery(rows: HeatmapEventRow[]) {
|
||||
visitId: r.visitId,
|
||||
urlPath: r.urlPath,
|
||||
eventType: r.eventType,
|
||||
nodeId: r.nodeId,
|
||||
x: r.x,
|
||||
y: r.y,
|
||||
pageX: r.pageX,
|
||||
@@ -81,9 +75,6 @@ async function relationalQuery(rows: HeatmapEventRow[]) {
|
||||
pageH: r.pageH,
|
||||
scrollPct: r.scrollPct,
|
||||
createdAt: r.createdAt,
|
||||
replayChunkIndex: r.replayChunkIndex,
|
||||
replayEventIndex: r.replayEventIndex,
|
||||
replayTimeMs: r.replayTimeMs,
|
||||
})) as any,
|
||||
});
|
||||
}
|
||||
@@ -99,7 +90,6 @@ async function clickhouseQuery(rows: HeatmapEventRow[]) {
|
||||
visit_id: r.visitId,
|
||||
url_path: r.urlPath,
|
||||
event_type: r.eventType,
|
||||
node_id: r.nodeId,
|
||||
x: r.x,
|
||||
y: r.y,
|
||||
page_x: r.pageX,
|
||||
@@ -110,9 +100,6 @@ async function clickhouseQuery(rows: HeatmapEventRow[]) {
|
||||
page_h: r.pageH,
|
||||
scroll_pct: r.scrollPct,
|
||||
created_at: getUTCString(r.createdAt),
|
||||
replay_chunk_index: r.replayChunkIndex,
|
||||
replay_event_index: r.replayEventIndex,
|
||||
replay_time_ms: r.replayTimeMs,
|
||||
}));
|
||||
|
||||
if (kafka.enabled) {
|
||||
|
||||
Reference in New Issue
Block a user