From aa81bbfe771949a1b4063894ff1e006c4f7ef736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carl-Gerhard=20Lindesv=C3=A4rd?= <1987198+lindesvard@users.noreply.github.com> Date: Thu, 26 Feb 2026 14:09:53 +0100 Subject: [PATCH] feat: session replay * wip * wip * wip * wip * final fixes * comments * fix --- .claude/CLAUDE.md | 2 + apps/api/src/controllers/event.controller.ts | 53 +-- apps/api/src/controllers/track.controller.ts | 190 +++++---- apps/api/src/hooks/duplicate.hook.ts | 7 +- apps/api/src/hooks/request-logging.hook.ts | 8 +- apps/api/src/index.ts | 41 +- apps/api/src/routes/track.router.ts | 1 + apps/api/src/utils/ids.ts | 158 ++++++++ .../docs/(tracking)/revenue-tracking.mdx | 6 +- .../content/docs/(tracking)/sdks/astro.mdx | 3 +- .../content/docs/(tracking)/sdks/nextjs.mdx | 7 +- .../content/guides/ecommerce-tracking.mdx | 2 +- .../content/guides/nextjs-analytics.mdx | 2 +- apps/public/public/op1-replay.js | 77 ++++ apps/public/public/op1.js | 2 +- apps/public/src/app/layout.tsx | 14 +- apps/start/package.json | 4 +- .../src/components/events/table/columns.tsx | 15 +- .../sessions/replay/browser-chrome.tsx | 42 ++ .../src/components/sessions/replay/index.tsx | 242 +++++++++++ .../sessions/replay/replay-context.tsx | 205 ++++++++++ .../sessions/replay/replay-controls.tsx | 33 ++ .../sessions/replay/replay-event-feed.tsx | 107 +++++ .../sessions/replay/replay-event-item.tsx | 51 +++ .../sessions/replay/replay-player.tsx | 184 +++++++++ .../sessions/replay/replay-timeline.tsx | 261 ++++++++++++ .../sessions/replay/replay-utils.ts | 20 + .../src/components/sessions/table/columns.tsx | 45 ++- apps/start/src/components/ui/scroll-area.tsx | 2 +- apps/start/src/hooks/use-page-tabs.ts | 3 +- apps/start/src/modals/event-details.tsx | 177 ++++---- apps/start/src/routeTree.gen.ts | 23 ++ ...tId.profiles.$profileId._tabs.sessions.tsx | 33 ++ ...d.$projectId.profiles.$profileId._tabs.tsx | 1 + ...tionId.$projectId.sessions_.$sessionId.tsx | 378 +++++++++++++++--- apps/start/src/types/rrweb-player.d.ts | 47 +++ apps/start/src/utils/op.ts | 27 +- apps/worker/src/boot-cron.ts | 5 + apps/worker/src/jobs/cron.ts | 5 +- apps/worker/src/jobs/events.incoming-event.ts | 25 +- .../src/jobs/events.incoming-events.test.ts | 13 +- apps/worker/src/metrics.ts | 12 + apps/worker/src/utils/session-handler.ts | 37 +- packages/common/src/id.ts | 4 +- .../code-migrations/10-add-session-replay.ts | 60 +++ packages/db/code-migrations/migrate.ts | 19 +- packages/db/src/buffers/index.ts | 3 + packages/db/src/buffers/replay-buffer.ts | 92 +++++ packages/db/src/buffers/session-buffer.ts | 23 +- packages/db/src/clickhouse/client.ts | 1 + packages/db/src/services/session.service.ts | 113 ++++-- packages/queue/src/queues.ts | 11 +- .../sdks/astro/src/OpenPanelComponent.astro | 6 +- packages/sdks/nextjs/index.tsx | 49 +-- packages/sdks/sdk/src/index.ts | 87 ++-- packages/sdks/web/package.json | 6 +- packages/sdks/web/src/index.ts | 101 +++++ packages/sdks/web/src/replay/index.ts | 2 + packages/sdks/web/src/replay/recorder.ts | 160 ++++++++ packages/sdks/web/src/types.d.ts | 6 +- packages/sdks/web/src/types.debug.ts | 2 + packages/sdks/web/tsup.config.ts | 77 +++- packages/trpc/src/routers/session.ts | 28 +- packages/validation/src/track.validation.ts | 14 + pnpm-lock.yaml | 137 ++++++- test.ts | 41 -- tooling/publish/publish.ts | 3 + 67 files changed, 3059 insertions(+), 556 deletions(-) create mode 100644 apps/api/src/utils/ids.ts create mode 100644 apps/public/public/op1-replay.js create mode 100644 apps/start/src/components/sessions/replay/browser-chrome.tsx create mode 100644 apps/start/src/components/sessions/replay/index.tsx create mode 100644 apps/start/src/components/sessions/replay/replay-context.tsx create mode 100644 apps/start/src/components/sessions/replay/replay-controls.tsx create mode 100644 apps/start/src/components/sessions/replay/replay-event-feed.tsx create mode 100644 apps/start/src/components/sessions/replay/replay-event-item.tsx create mode 100644 apps/start/src/components/sessions/replay/replay-player.tsx create mode 100644 apps/start/src/components/sessions/replay/replay-timeline.tsx create mode 100644 apps/start/src/components/sessions/replay/replay-utils.ts create mode 100644 apps/start/src/routes/_app.$organizationId.$projectId.profiles.$profileId._tabs.sessions.tsx create mode 100644 apps/start/src/types/rrweb-player.d.ts create mode 100644 packages/db/code-migrations/10-add-session-replay.ts create mode 100644 packages/db/src/buffers/replay-buffer.ts create mode 100644 packages/sdks/web/src/replay/index.ts create mode 100644 packages/sdks/web/src/replay/recorder.ts delete mode 100644 test.ts diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 8d33962e..6ada80df 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -1,5 +1,7 @@ # CLAUDE.md +NEVER CALL FORMAT! WE'LL FORMAT IN THE FUTURE WHEN WE HAVE MERGED ALL BIG PRS! + This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview diff --git a/apps/api/src/controllers/event.controller.ts b/apps/api/src/controllers/event.controller.ts index ba67f119..acea9861 100644 --- a/apps/api/src/controllers/event.controller.ts +++ b/apps/api/src/controllers/event.controller.ts @@ -1,26 +1,25 @@ -import type { FastifyReply, FastifyRequest } from 'fastify'; - -import { generateDeviceId, parseUserAgent } from '@openpanel/common/server'; -import { getSalts } from '@openpanel/db'; -import { getEventsGroupQueueShard } from '@openpanel/queue'; - import { generateId, slug } from '@openpanel/common'; +import { parseUserAgent } from '@openpanel/common/server'; +import { getSalts } from '@openpanel/db'; import { getGeoLocation } from '@openpanel/geo'; +import { getEventsGroupQueueShard } from '@openpanel/queue'; import type { DeprecatedPostEventPayload } from '@openpanel/validation'; +import type { FastifyReply, FastifyRequest } from 'fastify'; import { getStringHeaders, getTimestamp } from './track.controller'; +import { getDeviceId } from '@/utils/ids'; export async function postEvent( request: FastifyRequest<{ Body: DeprecatedPostEventPayload; }>, - reply: FastifyReply, + reply: FastifyReply ) { const { timestamp, isTimestampFromThePast } = getTimestamp( request.timestamp, - request.body, + request.body ); const ip = request.clientIp; - const ua = request.headers['user-agent']; + const ua = request.headers['user-agent'] ?? 'unknown/1.0'; const projectId = request.client?.projectId; const headers = getStringHeaders(request.headers); @@ -30,34 +29,22 @@ export async function postEvent( } const [salts, geo] = await Promise.all([getSalts(), getGeoLocation(ip)]); - const currentDeviceId = ua - ? generateDeviceId({ - salt: salts.current, - origin: projectId, - ip, - ua, - }) - : ''; - const previousDeviceId = ua - ? generateDeviceId({ - salt: salts.previous, - origin: projectId, - ip, - ua, - }) - : ''; + const { deviceId, sessionId } = await getDeviceId({ + projectId, + ip, + ua, + salts, + }); const uaInfo = parseUserAgent(ua, request.body?.properties); const groupId = uaInfo.isServer - ? request.body?.profileId - ? `${projectId}:${request.body?.profileId}` - : `${projectId}:${generateId()}` - : currentDeviceId; + ? `${projectId}:${request.body?.profileId ?? generateId()}` + : deviceId; const jobId = [ slug(request.body.name), timestamp, projectId, - currentDeviceId, + deviceId, groupId, ] .filter(Boolean) @@ -74,8 +61,10 @@ export async function postEvent( }, uaInfo, geo, - currentDeviceId, - previousDeviceId, + currentDeviceId: '', + previousDeviceId: '', + deviceId, + sessionId: sessionId ?? '', }, groupId, jobId, diff --git a/apps/api/src/controllers/track.controller.ts b/apps/api/src/controllers/track.controller.ts index 2f6fbe7c..4b0de8b8 100644 --- a/apps/api/src/controllers/track.controller.ts +++ b/apps/api/src/controllers/track.controller.ts @@ -1,22 +1,27 @@ -import type { FastifyReply, FastifyRequest } from 'fastify'; -import { assocPath, pathOr, pick } from 'ramda'; - -import { HttpError } from '@/utils/errors'; import { generateId, slug } from '@openpanel/common'; import { generateDeviceId, parseUserAgent } from '@openpanel/common/server'; -import { getProfileById, getSalts, upsertProfile } from '@openpanel/db'; +import { + getProfileById, + getSalts, + replayBuffer, + upsertProfile, +} from '@openpanel/db'; import { type GeoLocation, getGeoLocation } from '@openpanel/geo'; import { getEventsGroupQueueShard } from '@openpanel/queue'; import { getRedisCache } from '@openpanel/redis'; - import { type IDecrementPayload, type IIdentifyPayload, type IIncrementPayload, + type IReplayPayload, type ITrackHandlerPayload, type ITrackPayload, zTrackHandlerPayload, } from '@openpanel/validation'; +import type { FastifyReply, FastifyRequest } from 'fastify'; +import { assocPath, pathOr, pick } from 'ramda'; +import { HttpError } from '@/utils/errors'; +import { getDeviceId } from '@/utils/ids'; export function getStringHeaders(headers: FastifyRequest['headers']) { return Object.entries( @@ -28,14 +33,14 @@ export function getStringHeaders(headers: FastifyRequest['headers']) { 'openpanel-client-id', 'request-id', ], - headers, - ), + headers + ) ).reduce( (acc, [key, value]) => ({ ...acc, [key]: value ? String(value) : undefined, }), - {}, + {} ); } @@ -45,14 +50,15 @@ function getIdentity(body: ITrackHandlerPayload): IIdentifyPayload | undefined { | IIdentifyPayload | undefined; - return ( - identity || - (body.payload.profileId - ? { - profileId: String(body.payload.profileId), - } - : undefined) - ); + if (identity) { + return identity; + } + + return body.payload.profileId + ? { + profileId: String(body.payload.profileId), + } + : undefined; } return undefined; @@ -60,7 +66,7 @@ function getIdentity(body: ITrackHandlerPayload): IIdentifyPayload | undefined { export function getTimestamp( timestamp: FastifyRequest['timestamp'], - payload: ITrackHandlerPayload['payload'], + payload: ITrackHandlerPayload['payload'] ) { const safeTimestamp = timestamp || Date.now(); const userDefinedTimestamp = @@ -104,8 +110,8 @@ interface TrackContext { headers: Record; timestamp: { value: number; isFromPast: boolean }; identity?: IIdentifyPayload; - currentDeviceId?: string; - previousDeviceId?: string; + deviceId: string; + sessionId: string; geo: GeoLocation; } @@ -113,7 +119,7 @@ async function buildContext( request: FastifyRequest<{ Body: ITrackHandlerPayload; }>, - validatedBody: ITrackHandlerPayload, + validatedBody: ITrackHandlerPayload ): Promise { const projectId = request.client?.projectId; if (!projectId) { @@ -128,49 +134,27 @@ async function buildContext( const ua = request.headers['user-agent'] ?? 'unknown/1.0'; const headers = getStringHeaders(request.headers); - const identity = getIdentity(validatedBody); const profileId = identity?.profileId; - // We might get a profileId from the alias table - // If we do, we should use that instead of the one from the payload if (profileId && validatedBody.type === 'track') { validatedBody.payload.profileId = profileId; } // Get geo location (needed for track and identify) - const geo = await getGeoLocation(ip); + const [geo, salts] = await Promise.all([getGeoLocation(ip), getSalts()]); - // Generate device IDs if needed (for track) - let currentDeviceId: string | undefined; - let previousDeviceId: string | undefined; - - if (validatedBody.type === 'track') { - const overrideDeviceId = - typeof validatedBody.payload.properties?.__deviceId === 'string' - ? validatedBody.payload.properties.__deviceId - : undefined; - - const salts = await getSalts(); - currentDeviceId = - overrideDeviceId || - (ua - ? generateDeviceId({ - salt: salts.current, - origin: projectId, - ip, - ua, - }) - : ''); - previousDeviceId = ua - ? generateDeviceId({ - salt: salts.previous, - origin: projectId, - ip, - ua, - }) - : ''; - } + const { deviceId, sessionId } = await getDeviceId({ + projectId, + ip, + ua, + salts, + overrideDeviceId: + validatedBody.type === 'track' && + typeof validatedBody.payload?.properties?.__deviceId === 'string' + ? validatedBody.payload?.properties.__deviceId + : undefined, + }); return { projectId, @@ -182,46 +166,35 @@ async function buildContext( isFromPast: timestamp.isTimestampFromThePast, }, identity, - currentDeviceId, - previousDeviceId, + deviceId, + sessionId, geo, }; } async function handleTrack( payload: ITrackPayload, - context: TrackContext, + context: TrackContext ): Promise { - const { - projectId, - currentDeviceId, - previousDeviceId, - geo, - headers, - timestamp, - } = context; - - if (!currentDeviceId || !previousDeviceId) { - throw new HttpError('Device ID generation failed', { status: 500 }); - } + const { projectId, deviceId, geo, headers, timestamp, sessionId } = context; const uaInfo = parseUserAgent(headers['user-agent'], payload.properties); const groupId = uaInfo.isServer ? payload.profileId ? `${projectId}:${payload.profileId}` : `${projectId}:${generateId()}` - : currentDeviceId; + : deviceId; const jobId = [ slug(payload.name), timestamp.value, projectId, - currentDeviceId, + deviceId, groupId, ] .filter(Boolean) .join('-'); - const promises = []; + const promises: Promise[] = []; // If we have more than one property in the identity object, we should identify the user // Otherwise its only a profileId and we should not identify the user @@ -242,12 +215,14 @@ async function handleTrack( }, uaInfo, geo, - currentDeviceId, - previousDeviceId, + deviceId, + sessionId, + currentDeviceId: '', // TODO: Remove + previousDeviceId: '', // TODO: Remove }, groupId, jobId, - }), + }) ); await Promise.all(promises); @@ -255,7 +230,7 @@ async function handleTrack( async function handleIdentify( payload: IIdentifyPayload, - context: TrackContext, + context: TrackContext ): Promise { const { projectId, geo, ua } = context; const uaInfo = parseUserAgent(ua, payload.properties); @@ -285,7 +260,7 @@ async function handleIdentify( async function adjustProfileProperty( payload: IIncrementPayload | IDecrementPayload, projectId: string, - direction: 1 | -1, + direction: 1 | -1 ): Promise { const { profileId, property, value } = payload; const profile = await getProfileById(profileId, projectId); @@ -295,7 +270,7 @@ async function adjustProfileProperty( const parsed = Number.parseInt( pathOr('0', property.split('.'), profile.properties), - 10, + 10 ); if (Number.isNaN(parsed)) { @@ -305,7 +280,7 @@ async function adjustProfileProperty( profile.properties = assocPath( property.split('.'), parsed + direction * (value || 1), - profile.properties, + profile.properties ); await upsertProfile({ @@ -318,23 +293,44 @@ async function adjustProfileProperty( async function handleIncrement( payload: IIncrementPayload, - context: TrackContext, + context: TrackContext ): Promise { await adjustProfileProperty(payload, context.projectId, 1); } async function handleDecrement( payload: IDecrementPayload, - context: TrackContext, + context: TrackContext ): Promise { await adjustProfileProperty(payload, context.projectId, -1); } +async function handleReplay( + payload: IReplayPayload, + context: TrackContext +): Promise { + if (!context.sessionId) { + throw new HttpError('Session ID is required for replay', { status: 400 }); + } + + const row = { + project_id: context.projectId, + session_id: context.sessionId, + chunk_index: payload.chunk_index, + started_at: payload.started_at, + ended_at: payload.ended_at, + events_count: payload.events_count, + is_full_snapshot: payload.is_full_snapshot, + payload: payload.payload, + }; + await replayBuffer.add(row); +} + export async function handler( request: FastifyRequest<{ Body: ITrackHandlerPayload; }>, - reply: FastifyReply, + reply: FastifyReply ) { // Validate request body with Zod const validationResult = zTrackHandlerPayload.safeParse(request.body); @@ -375,6 +371,9 @@ export async function handler( case 'decrement': await handleDecrement(validatedBody.payload, context); break; + case 'replay': + await handleReplay(validatedBody.payload, context); + break; default: return reply.status(400).send({ status: 400, @@ -383,12 +382,15 @@ export async function handler( }); } - reply.status(200).send(); + reply.status(200).send({ + deviceId: context.deviceId, + sessionId: context.sessionId, + }); } export async function fetchDeviceId( request: FastifyRequest, - reply: FastifyReply, + reply: FastifyReply ) { const salts = await getSalts(); const projectId = request.client?.projectId; @@ -421,20 +423,31 @@ export async function fetchDeviceId( try { const multi = getRedisCache().multi(); - multi.exists(`bull:sessions:sessionEnd:${projectId}:${currentDeviceId}`); - multi.exists(`bull:sessions:sessionEnd:${projectId}:${previousDeviceId}`); + multi.hget( + `bull:sessions:sessionEnd:${projectId}:${currentDeviceId}`, + 'data' + ); + multi.hget( + `bull:sessions:sessionEnd:${projectId}:${previousDeviceId}`, + 'data' + ); const res = await multi.exec(); - if (res?.[0]?.[1]) { + const data = JSON.parse(res?.[0]?.[1] as string); + const sessionId = data.payload.sessionId; return reply.status(200).send({ deviceId: currentDeviceId, + sessionId, message: 'current session exists for this device id', }); } if (res?.[1]?.[1]) { + const data = JSON.parse(res?.[1]?.[1] as string); + const sessionId = data.payload.sessionId; return reply.status(200).send({ deviceId: previousDeviceId, + sessionId, message: 'previous session exists for this device id', }); } @@ -444,6 +457,7 @@ export async function fetchDeviceId( return reply.status(200).send({ deviceId: currentDeviceId, + sessionId: '', message: 'No session exists for this device id', }); } diff --git a/apps/api/src/hooks/duplicate.hook.ts b/apps/api/src/hooks/duplicate.hook.ts index b22b807d..7b7655b5 100644 --- a/apps/api/src/hooks/duplicate.hook.ts +++ b/apps/api/src/hooks/duplicate.hook.ts @@ -1,20 +1,21 @@ -import { isDuplicatedEvent } from '@/utils/deduplicate'; import type { DeprecatedPostEventPayload, ITrackHandlerPayload, } from '@openpanel/validation'; import type { FastifyReply, FastifyRequest } from 'fastify'; +import { isDuplicatedEvent } from '@/utils/deduplicate'; export async function duplicateHook( req: FastifyRequest<{ Body: ITrackHandlerPayload | DeprecatedPostEventPayload; }>, - reply: FastifyReply, + reply: FastifyReply ) { const ip = req.clientIp; const origin = req.headers.origin; const clientId = req.headers['openpanel-client-id']; - const shouldCheck = ip && origin && clientId; + const isReplay = 'type' in req.body && req.body.type === 'replay'; + const shouldCheck = ip && origin && clientId && !isReplay; const isDuplicate = shouldCheck ? await isDuplicatedEvent({ diff --git a/apps/api/src/hooks/request-logging.hook.ts b/apps/api/src/hooks/request-logging.hook.ts index bd6af2ca..7b9644ba 100644 --- a/apps/api/src/hooks/request-logging.hook.ts +++ b/apps/api/src/hooks/request-logging.hook.ts @@ -1,4 +1,3 @@ -import { DEFAULT_IP_HEADER_ORDER } from '@openpanel/common'; import type { FastifyReply, FastifyRequest } from 'fastify'; import { path, pick } from 'ramda'; @@ -6,7 +5,7 @@ const ignoreLog = ['/healthcheck', '/healthz', '/metrics', '/misc']; const ignoreMethods = ['OPTIONS']; const getTrpcInput = ( - request: FastifyRequest, + request: FastifyRequest ): Record | undefined => { const input = path(['query', 'input'], request); try { @@ -18,7 +17,7 @@ const getTrpcInput = ( export async function requestLoggingHook( request: FastifyRequest, - reply: FastifyReply, + reply: FastifyReply ) { if (ignoreMethods.includes(request.method)) { return; @@ -40,9 +39,8 @@ export async function requestLoggingHook( elapsed: reply.elapsedTime, headers: pick( ['openpanel-client-id', 'openpanel-sdk-name', 'openpanel-sdk-version'], - request.headers, + request.headers ), - body: request.body, }); } } diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index b0829c0d..d55ceec6 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -3,12 +3,12 @@ process.env.TZ = 'UTC'; import compress from '@fastify/compress'; import cookie from '@fastify/cookie'; import cors, { type FastifyCorsOptions } from '@fastify/cors'; -import type { FastifyTRPCPluginOptions } from '@trpc/server/adapters/fastify'; -import { fastifyTRPCPlugin } from '@trpc/server/adapters/fastify'; -import type { FastifyBaseLogger, FastifyRequest } from 'fastify'; -import Fastify from 'fastify'; -import metricsPlugin from 'fastify-metrics'; - +import { + decodeSessionToken, + EMPTY_SESSION, + type SessionValidationResult, + validateSessionToken, +} from '@openpanel/auth'; import { generateId } from '@openpanel/common'; import { type IServiceClientWithProject, @@ -17,13 +17,11 @@ import { import { getRedisPub } from '@openpanel/redis'; import type { AppRouter } from '@openpanel/trpc'; import { appRouter, createContext } from '@openpanel/trpc'; - -import { - EMPTY_SESSION, - type SessionValidationResult, - decodeSessionToken, - validateSessionToken, -} from '@openpanel/auth'; +import type { FastifyTRPCPluginOptions } from '@trpc/server/adapters/fastify'; +import { fastifyTRPCPlugin } from '@trpc/server/adapters/fastify'; +import type { FastifyBaseLogger, FastifyRequest } from 'fastify'; +import Fastify from 'fastify'; +import metricsPlugin from 'fastify-metrics'; import sourceMapSupport from 'source-map-support'; import { healthcheck, @@ -72,7 +70,7 @@ const startServer = async () => { try { const fastify = Fastify({ maxParamLength: 15_000, - bodyLimit: 1048576 * 500, // 500MB + bodyLimit: 1_048_576 * 500, // 500MB loggerInstance: logger as unknown as FastifyBaseLogger, disableRequestLogging: true, genReqId: (req) => @@ -84,7 +82,7 @@ const startServer = async () => { fastify.register(cors, () => { return ( req: FastifyRequest, - callback: (error: Error | null, options: FastifyCorsOptions) => void, + callback: (error: Error | null, options: FastifyCorsOptions) => void ) => { // TODO: set prefix on dashboard routes const corsPaths = [ @@ -97,7 +95,7 @@ const startServer = async () => { ]; const isPrivatePath = corsPaths.some((path) => - req.url.startsWith(path), + req.url.startsWith(path) ); if (isPrivatePath) { @@ -118,6 +116,7 @@ const startServer = async () => { return callback(null, { origin: '*', + maxAge: 86_400 * 7, // cache preflight for 7 days }); }; }); @@ -149,7 +148,7 @@ const startServer = async () => { try { const sessionId = decodeSessionToken(req.cookies?.session); const session = await runWithAlsSession(sessionId, () => - validateSessionToken(req.cookies.session), + validateSessionToken(req.cookies.session) ); req.session = session; } catch (e) { @@ -158,7 +157,7 @@ const startServer = async () => { } else if (process.env.DEMO_USER_ID) { try { const session = await runWithAlsSession('1', () => - validateSessionToken(null), + validateSessionToken(null) ); req.session = session; } catch (e) { @@ -173,7 +172,7 @@ const startServer = async () => { prefix: '/trpc', trpcOptions: { router: appRouter, - createContext: createContext, + createContext, onError(ctx) { if ( ctx.error.code === 'UNAUTHORIZED' && @@ -217,7 +216,7 @@ const startServer = async () => { reply.send({ status: 'ok', message: 'Successfully running OpenPanel.dev API', - }), + }) ); }); @@ -274,7 +273,7 @@ const startServer = async () => { } catch (error) { logger.warn('Failed to set redis notify-keyspace-events', error); logger.warn( - 'If you use a managed Redis service, you may need to set this manually.', + 'If you use a managed Redis service, you may need to set this manually.' ); logger.warn('Otherwise some functions may not work as expected.'); } diff --git a/apps/api/src/routes/track.router.ts b/apps/api/src/routes/track.router.ts index d3fb92c6..2f94a8eb 100644 --- a/apps/api/src/routes/track.router.ts +++ b/apps/api/src/routes/track.router.ts @@ -26,6 +26,7 @@ const trackRouter: FastifyPluginCallback = async (fastify) => { type: 'object', properties: { deviceId: { type: 'string' }, + sessionId: { type: 'string' }, message: { type: 'string', optional: true }, }, }, diff --git a/apps/api/src/utils/ids.ts b/apps/api/src/utils/ids.ts new file mode 100644 index 00000000..db6b9aa5 --- /dev/null +++ b/apps/api/src/utils/ids.ts @@ -0,0 +1,158 @@ +import crypto from 'node:crypto'; +import { generateDeviceId } from '@openpanel/common/server'; +import { getSafeJson } from '@openpanel/json'; +import { getRedisCache } from '@openpanel/redis'; + +export async function getDeviceId({ + projectId, + ip, + ua, + salts, + overrideDeviceId, +}: { + projectId: string; + ip: string; + ua: string | undefined; + salts: { current: string; previous: string }; + overrideDeviceId?: string; +}) { + if (overrideDeviceId) { + return { deviceId: overrideDeviceId, sessionId: '' }; + } + + if (!ua) { + return { deviceId: '', sessionId: '' }; + } + + const currentDeviceId = generateDeviceId({ + salt: salts.current, + origin: projectId, + ip, + ua, + }); + const previousDeviceId = generateDeviceId({ + salt: salts.previous, + origin: projectId, + ip, + ua, + }); + + return await getDeviceIdFromSession({ + projectId, + currentDeviceId, + previousDeviceId, + }); +} + +async function getDeviceIdFromSession({ + projectId, + currentDeviceId, + previousDeviceId, +}: { + projectId: string; + currentDeviceId: string; + previousDeviceId: string; +}) { + try { + const multi = getRedisCache().multi(); + multi.hget( + `bull:sessions:sessionEnd:${projectId}:${currentDeviceId}`, + 'data' + ); + multi.hget( + `bull:sessions:sessionEnd:${projectId}:${previousDeviceId}`, + 'data' + ); + const res = await multi.exec(); + if (res?.[0]?.[1]) { + const data = getSafeJson<{ payload: { sessionId: string } }>( + (res?.[0]?.[1] as string) ?? '' + ); + if (data) { + const sessionId = data.payload.sessionId; + return { deviceId: currentDeviceId, sessionId }; + } + } + if (res?.[1]?.[1]) { + const data = getSafeJson<{ payload: { sessionId: string } }>( + (res?.[1]?.[1] as string) ?? '' + ); + if (data) { + const sessionId = data.payload.sessionId; + return { deviceId: previousDeviceId, sessionId }; + } + } + } catch (error) { + console.error('Error getting session end GET /track/device-id', error); + } + + return { + deviceId: currentDeviceId, + sessionId: getSessionId({ + projectId, + deviceId: currentDeviceId, + graceMs: 5 * 1000, + windowMs: 1000 * 60 * 30, + }), + }; +} + +/** + * Deterministic session id for (projectId, deviceId) within a time window, + * with a grace period at the *start* of each window to avoid boundary splits. + * + * - windowMs: 30 minutes by default + * - graceMs: 1 minute by default (events in first minute of a bucket map to previous bucket) + * - Output: base64url, 128-bit (16 bytes) truncated from SHA-256 + */ +function getSessionId(params: { + projectId: string; + deviceId: string; + eventMs?: number; // use event timestamp; defaults to Date.now() + windowMs?: number; // default 5 min + graceMs?: number; // default 1 min + bytes?: number; // default 16 (128-bit). You can set 24 or 32 for longer ids. +}): string { + const { + projectId, + deviceId, + eventMs = Date.now(), + windowMs = 5 * 60 * 1000, + graceMs = 60 * 1000, + bytes = 16, + } = params; + + if (!projectId) { + throw new Error('projectId is required'); + } + if (!deviceId) { + throw new Error('deviceId is required'); + } + if (windowMs <= 0) { + throw new Error('windowMs must be > 0'); + } + if (graceMs < 0 || graceMs >= windowMs) { + throw new Error('graceMs must be >= 0 and < windowMs'); + } + if (bytes < 8 || bytes > 32) { + throw new Error('bytes must be between 8 and 32'); + } + + const bucket = Math.floor(eventMs / windowMs); + const offset = eventMs - bucket * windowMs; + + // Grace at the start of the bucket: stick to the previous bucket. + const chosenBucket = offset < graceMs ? bucket - 1 : bucket; + + const input = `sess:v1:${projectId}:${deviceId}:${chosenBucket}`; + + const digest = crypto.createHash('sha256').update(input).digest(); + const truncated = digest.subarray(0, bytes); + + // base64url + return truncated + .toString('base64') + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=+$/g, ''); +} diff --git a/apps/public/content/docs/(tracking)/revenue-tracking.mdx b/apps/public/content/docs/(tracking)/revenue-tracking.mdx index 67641843..00bf4349 100644 --- a/apps/public/content/docs/(tracking)/revenue-tracking.mdx +++ b/apps/public/content/docs/(tracking)/revenue-tracking.mdx @@ -33,7 +33,7 @@ This is the most common flow and most secure one. Your backend receives webhooks -When you create the checkout, you should first call `op.fetchDeviceId()`, which will return your visitor's current `deviceId`. Pass this to your checkout endpoint. +When you create the checkout, you should first call `op.getDeviceId()`, which will return your visitor's current `deviceId`. Pass this to your checkout endpoint. ```javascript fetch('https://domain.com/api/checkout', { @@ -42,7 +42,7 @@ fetch('https://domain.com/api/checkout', { 'Content-Type': 'application/json', }, body: JSON.stringify({ - deviceId: await op.fetchDeviceId(), // ✅ since deviceId is here we can link the payment now + deviceId: op.getDeviceId(), // ✅ since deviceId is here we can link the payment now // ... other checkout data }), }) @@ -360,5 +360,5 @@ op.clearRevenue(): void ### Fetch your current users device id ```javascript -op.fetchDeviceId(): Promise +op.getDeviceId(): string ``` diff --git a/apps/public/content/docs/(tracking)/sdks/astro.mdx b/apps/public/content/docs/(tracking)/sdks/astro.mdx index 3826000d..12dac9d5 100644 --- a/apps/public/content/docs/(tracking)/sdks/astro.mdx +++ b/apps/public/content/docs/(tracking)/sdks/astro.mdx @@ -54,7 +54,8 @@ import { OpenPanelComponent } from '@openpanel/astro'; ##### Astro options - `profileId` - If you have a user id, you can pass it here to identify the user -- `cdnUrl` - The url to the OpenPanel SDK (default: `https://openpanel.dev/op1.js`) +- `cdnUrl` (deprecated) - The url to the OpenPanel SDK (default: `https://openpanel.dev/op1.js`) +- `scriptUrl` - The url to the OpenPanel SDK (default: `https://openpanel.dev/op1.js`) - `filter` - This is a function that will be called before tracking an event. If it returns false the event will not be tracked. [Read more](#filter) - `globalProperties` - This is an object of properties that will be sent with every event. diff --git a/apps/public/content/docs/(tracking)/sdks/nextjs.mdx b/apps/public/content/docs/(tracking)/sdks/nextjs.mdx index 6f317326..3877d1b6 100644 --- a/apps/public/content/docs/(tracking)/sdks/nextjs.mdx +++ b/apps/public/content/docs/(tracking)/sdks/nextjs.mdx @@ -62,7 +62,8 @@ export default function RootLayout({ children }) { ##### NextJS options - `profileId` - If you have a user id, you can pass it here to identify the user -- `cdnUrl` - The url to the OpenPanel SDK (default: `https://openpanel.dev/op1.js`) +- `cdnUrl` (deprecated) - The url to the OpenPanel SDK (default: `https://openpanel.dev/op1.js`) +- `scriptUrl` - The url to the OpenPanel SDK (default: `https://openpanel.dev/op1.js`) - `filter` - This is a function that will be called before tracking an event. If it returns false the event will not be tracked. [Read more](#filter) - `globalProperties` - This is an object of properties that will be sent with every event. @@ -286,12 +287,12 @@ import { createRouteHandler } from '@openpanel/nextjs/server'; export const { GET, POST } = createRouteHandler(); ``` -Remember to change the `apiUrl` and `cdnUrl` in the `OpenPanelComponent` to your own server. +Remember to change the `apiUrl` and `scriptUrl` in the `OpenPanelComponent` to your own server. ```tsx diff --git a/apps/public/content/guides/ecommerce-tracking.mdx b/apps/public/content/guides/ecommerce-tracking.mdx index e554ba2d..cfa03304 100644 --- a/apps/public/content/guides/ecommerce-tracking.mdx +++ b/apps/public/content/guides/ecommerce-tracking.mdx @@ -136,7 +136,7 @@ For more accurate tracking, handle revenue in your backend webhook. This ensures ```tsx // Frontend: include deviceId when starting checkout -const deviceId = await op.fetchDeviceId(); +const deviceId = op.getDeviceId(); const response = await fetch('/api/checkout', { method: 'POST', diff --git a/apps/public/content/guides/nextjs-analytics.mdx b/apps/public/content/guides/nextjs-analytics.mdx index 808b7cdd..4b85f20c 100644 --- a/apps/public/content/guides/nextjs-analytics.mdx +++ b/apps/public/content/guides/nextjs-analytics.mdx @@ -225,7 +225,7 @@ Then update your OpenPanelComponent to use the proxy endpoint. ```tsx diff --git a/apps/public/public/op1-replay.js b/apps/public/public/op1-replay.js new file mode 100644 index 00000000..361b0b8a --- /dev/null +++ b/apps/public/public/op1-replay.js @@ -0,0 +1,77 @@ +"use strict";var __openpanel_replay=(()=>{var yr=Object.defineProperty;var Zo=Object.getOwnPropertyDescriptor;var Xo=Object.getOwnPropertyNames;var Jo=Object.prototype.hasOwnProperty;var Ko=(s,e)=>{for(var t in e)yr(s,t,{get:e[t],enumerable:!0})},Qo=(s,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of Xo(e))!Jo.call(s,i)&&i!==t&&yr(s,i,{get:()=>e[i],enumerable:!(r=Zo(e,i))||r.enumerable});return s};var qo=s=>Qo(yr({},"__esModule",{value:!0}),s);var If={};Ko(If,{startReplayRecorder:()=>jo,stopReplayRecorder:()=>Yo});var ea=Object.defineProperty,ta=(s,e,t)=>e in s?ea(s,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):s[e]=t,w=(s,e,t)=>ta(s,typeof e!="symbol"?e+"":e,t),Zs,ra=Object.defineProperty,sa=(s,e,t)=>e in s?ra(s,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):s[e]=t,Xs=(s,e,t)=>sa(s,typeof e!="symbol"?e+"":e,t),j=(s=>(s[s.Document=0]="Document",s[s.DocumentType=1]="DocumentType",s[s.Element=2]="Element",s[s.Text=3]="Text",s[s.CDATA=4]="CDATA",s[s.Comment=5]="Comment",s))(j||{}),Js={Node:["childNodes","parentNode","parentElement","textContent","ownerDocument"],ShadowRoot:["host","styleSheets"],Element:["shadowRoot","querySelector","querySelectorAll"],MutationObserver:[]},Ks={Node:["contains","getRootNode"],ShadowRoot:["getSelection"],Element:[],MutationObserver:["constructor"]},ct={},ia=()=>!!globalThis.Zone;function vs(s){if(ct[s])return ct[s];let e=globalThis[s],t=e.prototype,r=s in Js?Js[s]:void 0,i=!!(r&&r.every(l=>{var a,u;return!!((u=(a=Object.getOwnPropertyDescriptor(t,l))==null?void 0:a.get)!=null&&u.toString().includes("[native code]"))})),n=s in Ks?Ks[s]:void 0,o=!!(n&&n.every(l=>{var a;return typeof t[l]=="function"&&((a=t[l])==null?void 0:a.toString().includes("[native code]"))}));if(i&&o&&!ia())return ct[s]=e.prototype,e.prototype;try{let l=document.createElement("iframe");document.body.appendChild(l);let a=l.contentWindow;if(!a)return e.prototype;let u=a[s].prototype;return document.body.removeChild(l),u?ct[s]=u:t}catch{return t}}var wr={};function fe(s,e,t){var r;let i=`${s}.${String(t)}`;if(wr[i])return wr[i].call(e);let n=vs(s),o=(r=Object.getOwnPropertyDescriptor(n,t))==null?void 0:r.get;return o?(wr[i]=o,o.call(e)):e[t]}var br={};function Zi(s,e,t){let r=`${s}.${String(t)}`;if(br[r])return br[r].bind(e);let n=vs(s)[t];return typeof n!="function"?e[t]:(br[r]=n,n.bind(e))}function na(s){return fe("Node",s,"ownerDocument")}function oa(s){return fe("Node",s,"childNodes")}function aa(s){return fe("Node",s,"parentNode")}function la(s){return fe("Node",s,"parentElement")}function ua(s){return fe("Node",s,"textContent")}function ca(s,e){return Zi("Node",s,"contains")(e)}function ha(s){return Zi("Node",s,"getRootNode")()}function fa(s){return!s||!("host"in s)?null:fe("ShadowRoot",s,"host")}function da(s){return s.styleSheets}function pa(s){return!s||!("shadowRoot"in s)?null:fe("Element",s,"shadowRoot")}function ma(s,e){return fe("Element",s,"querySelector")(e)}function ga(s,e){return fe("Element",s,"querySelectorAll")(e)}function ya(){return vs("MutationObserver").constructor}function wa(s,e,t){try{if(!(e in s))return()=>{};let r=s[e],i=t(r);return typeof i=="function"&&(i.prototype=i.prototype||{},Object.defineProperties(i,{__rrweb_original__:{enumerable:!1,value:r}})),s[e]=i,()=>{s[e]=r}}catch{return()=>{}}}var H={ownerDocument:na,childNodes:oa,parentNode:aa,parentElement:la,textContent:ua,contains:ca,getRootNode:ha,host:fa,styleSheets:da,shadowRoot:pa,querySelector:ma,querySelectorAll:ga,mutationObserver:ya,patch:wa};function Xi(s){return s.nodeType===s.ELEMENT_NODE}function Ge(s){let e=s&&"host"in s&&"mode"in s&&H.host(s)||null;return!!(e&&"shadowRoot"in e&&H.shadowRoot(e)===s)}function je(s){return Object.prototype.toString.call(s)==="[object ShadowRoot]"}function ba(s){return s.includes(" background-clip: text;")&&!s.includes(" -webkit-background-clip: text;")&&(s=s.replace(/\sbackground-clip:\s*text;/g," -webkit-background-clip: text; background-clip: text;")),s}function Sa(s){let{cssText:e}=s;if(e.split('"').length<3)return e;let t=["@import",`url(${JSON.stringify(s.href)})`];return s.layerName===""?t.push("layer"):s.layerName&&t.push(`layer(${s.layerName})`),s.supportsText&&t.push(`supports(${s.supportsText})`),s.media.length&&t.push(s.media.mediaText),t.join(" ")+";"}function Tr(s){try{let e=s.rules||s.cssRules;if(!e)return null;let t=s.href;!t&&s.ownerNode&&(t=s.ownerNode.baseURI);let r=Array.from(e,i=>Ji(i,t)).join("");return ba(r)}catch{return null}}function Ji(s,e){if(Ca(s)){let t;try{t=Tr(s.styleSheet)||Sa(s)}catch{t=s.cssText}return s.styleSheet.href?zt(t,s.styleSheet.href):t}else{let t=s.cssText;return Ia(s)&&s.selectorText.includes(":")&&(t=va(t)),e?zt(t,e):t}}function va(s){let e=/(\[(?:[\w-]+)[^\\])(:(?:[\w-]+)\])/gm;return s.replace(e,"$1\\$2")}function Ca(s){return"styleSheet"in s}function Ia(s){return"selectorText"in s}var Ut=class{constructor(){Xs(this,"idNodeMap",new Map),Xs(this,"nodeMetaMap",new WeakMap)}getId(e){var t;return e?((t=this.getMeta(e))==null?void 0:t.id)??-1:-1}getNode(e){return this.idNodeMap.get(e)||null}getIds(){return Array.from(this.idNodeMap.keys())}getMeta(e){return this.nodeMetaMap.get(e)||null}removeNodeFromMap(e){let t=this.getId(e);this.idNodeMap.delete(t),e.childNodes&&e.childNodes.forEach(r=>this.removeNodeFromMap(r))}has(e){return this.idNodeMap.has(e)}hasNode(e){return this.nodeMetaMap.has(e)}add(e,t){let r=t.id;this.idNodeMap.set(r,e),this.nodeMetaMap.set(e,t)}replace(e,t){let r=this.getNode(e);if(r){let i=this.nodeMetaMap.get(r);i&&this.nodeMetaMap.set(t,i)}this.idNodeMap.set(e,t)}reset(){this.idNodeMap=new Map,this.nodeMetaMap=new WeakMap}};function Ea(){return new Ut}function Bt({element:s,maskInputOptions:e,tagName:t,type:r,value:i,maskInputFn:n}){let o=i||"",l=r&&Se(r);return(e[t.toLowerCase()]||l&&e[l])&&(n?o=n(o,s):o="*".repeat(o.length)),o}function Se(s){return s.toLowerCase()}var Qs="__rrweb_original__";function xa(s){let e=s.getContext("2d");if(!e)return!0;let t=50;for(let r=0;ra!==0))return!1}return!0}function Wt(s){let e=s.type;return s.hasAttribute("data-rr-is-password")?"password":e?Se(e):null}function Ki(s,e){let t;try{t=new URL(s,e??window.location.href)}catch{return null}let r=/\.([0-9a-z]+)(?:$)/i,i=t.pathname.match(r);return i?.[1]??null}function Ma(s){let e="";return s.indexOf("//")>-1?e=s.split("/").slice(0,3).join("/"):e=s.split("/")[0],e=e.split("?")[0],e}var Na=/url\((?:(')([^']*)'|(")(.*?)"|([^)]*))\)/gm,Ra=/^(?:[a-z+]+:)?\/\//i,Aa=/^www\..*/i,Oa=/^(data:)([^,]*),(.*)/i;function zt(s,e){return(s||"").replace(Na,(t,r,i,n,o,l)=>{let a=i||o||l,u=r||n||"";if(!a)return t;if(Ra.test(a)||Aa.test(a))return`url(${u}${a}${u})`;if(Oa.test(a))return`url(${u}${a}${u})`;if(a[0]==="/")return`url(${u}${Ma(e)+a}${u})`;let c=e.split("/"),h=a.split("/");c.pop();for(let m of h)m!=="."&&(m===".."?c.pop():c.push(m));return`url(${u}${c.join("/")}${u})`})}function ht(s,e=!1){return e?s.replace(/(\/\*[^*]*\*\/)|[\s;]/g,""):s.replace(/(\/\*[^*]*\*\/)|[\s;]/g,"").replace(/0px/g,"0")}function Da(s,e,t=!1){let r=Array.from(e.childNodes),i=[],n=0;if(r.length>1&&s&&typeof s=="string"){let o=ht(s,t),l=o.length/s.length;for(let a=1;a2&&g[0]===""&&r[a-1].textContent!=="")p=o.indexOf(m,1);else if(g.length===1){if(m=m.substring(0,m.length-1),g=o.split(m),g.length<=1)return i.push(s),i;h=c+1}else h===u.length-1&&(p=o.indexOf(m));if(g.length>=2&&h>c){let d=r[a-1].textContent;if(d&&typeof d=="string"){let f=ht(d).length;p=o.indexOf(m,f)}p===-1&&(p=g[0].length)}if(p!==-1){let d=Math.floor(p/l);for(;d>0&&d50*r.length)return i.push(s),i;let f=ht(s.substring(0,d),t);if(f.length===p){i.push(s.substring(0,d)),s=s.substring(d),o=o.substring(p);break}else f.length=e.length);){let n=r($a);if(n.slice(-1)===",")n=Ne(s,n.substring(0,n.length-1)),i.push(n);else{let o="";n=Ne(s,n);let l=!1;for(;;){let a=e.charAt(t);if(a===""){i.push((n+o).trim());break}else if(l)a===")"&&(l=!1);else if(a===","){t+=1,i.push((n+o).trim());break}else a==="("&&(l=!0);o+=a,t+=1}}}return i.join(", ")}var ei=new WeakMap;function Ne(s,e){return!e||e.trim()===""?e:Cs(s,e)}function Ua(s){return!!(s.tagName==="svg"||s.ownerSVGElement)}function Cs(s,e){let t=ei.get(s);if(t||(t=s.createElement("a"),ei.set(s,t)),!e)e="";else if(e.startsWith("blob:")||e.startsWith("data:"))return e;return t.setAttribute("href",e),t.href}function qi(s,e,t,r){return r&&(t==="src"||t==="href"&&!(e==="use"&&r[0]==="#")||t==="xlink:href"&&r[0]!=="#"||t==="background"&&["table","td","th"].includes(e)?Ne(s,r):t==="srcset"?Fa(s,r):t==="style"?zt(r,Cs(s)):e==="object"&&t==="data"?Ne(s,r):r)}function en(s,e,t){return["video","audio"].includes(s)&&e==="autoplay"}function Ba(s,e,t){try{if(typeof e=="string"){if(s.classList.contains(e))return!0}else for(let r=s.classList.length;r--;){let i=s.classList[r];if(e.test(i))return!0}if(t)return s.matches(t)}catch{}return!1}function Vt(s,e,t){if(!s)return!1;if(s.nodeType!==s.ELEMENT_NODE)return t?Vt(H.parentNode(s),e,t):!1;for(let r=s.classList.length;r--;){let i=s.classList[r];if(e.test(i))return!0}return t?Vt(H.parentNode(s),e,t):!1}function tn(s,e,t,r){let i;if(Xi(s)){if(i=s,!H.childNodes(i).length)return!1}else{if(H.parentElement(s)===null)return!1;i=H.parentElement(s)}try{if(typeof e=="string"){if(r){if(i.closest(`.${e}`))return!0}else if(i.classList.contains(e))return!0}else if(Vt(i,e,r))return!0;if(t){if(r){if(i.closest(t))return!0}else if(i.matches(t))return!0}}catch{}return!1}function Wa(s,e,t){let r=s.contentWindow;if(!r)return;let i=!1,n;try{n=r.document.readyState}catch{return}if(n!=="complete"){let l=setTimeout(()=>{i||(e(),i=!0)},t);s.addEventListener("load",()=>{clearTimeout(l),i=!0,e()});return}let o="about:blank";if(r.location.href!==o||s.src===o||s.src==="")return setTimeout(e,0),s.addEventListener("load",e);s.addEventListener("load",e)}function za(s,e,t){let r=!1,i;try{i=s.sheet}catch{return}if(i)return;let n=setTimeout(()=>{r||(e(),r=!0)},t);s.addEventListener("load",()=>{clearTimeout(n),r=!0,e()})}function Va(s,e){let{doc:t,mirror:r,blockClass:i,blockSelector:n,needsMask:o,inlineStylesheet:l,maskInputOptions:a={},maskTextFn:u,maskInputFn:c,dataURLOptions:h={},inlineImages:m,recordCanvas:g,keepIframeSrcFn:p,newlyAddedElement:d=!1,cssCaptured:f=!1}=e,b=Ga(t,r);switch(s.nodeType){case s.DOCUMENT_NODE:return s.compatMode!=="CSS1Compat"?{type:j.Document,childNodes:[],compatMode:s.compatMode}:{type:j.Document,childNodes:[]};case s.DOCUMENT_TYPE_NODE:return{type:j.DocumentType,name:s.name,publicId:s.publicId,systemId:s.systemId,rootId:b};case s.ELEMENT_NODE:return Ya(s,{doc:t,blockClass:i,blockSelector:n,inlineStylesheet:l,maskInputOptions:a,maskInputFn:c,dataURLOptions:h,inlineImages:m,recordCanvas:g,keepIframeSrcFn:p,newlyAddedElement:d,rootId:b});case s.TEXT_NODE:return ja(s,{doc:t,needsMask:o,maskTextFn:u,rootId:b,cssCaptured:f});case s.CDATA_SECTION_NODE:return{type:j.CDATA,textContent:"",rootId:b};case s.COMMENT_NODE:return{type:j.Comment,textContent:H.textContent(s)||"",rootId:b};default:return!1}}function Ga(s,e){if(!e.hasNode(s))return;let t=e.getId(s);return t===1?void 0:t}function ja(s,e){let{needsMask:t,maskTextFn:r,rootId:i,cssCaptured:n}=e,o=H.parentNode(s),l=o&&o.tagName,a="",u=l==="STYLE"?!0:void 0,c=l==="SCRIPT"?!0:void 0;return c?a="SCRIPT_PLACEHOLDER":n||(a=H.textContent(s),u&&a&&(a=zt(a,Cs(e.doc)))),!u&&!c&&a&&t&&(a=r?r(a,H.parentElement(s)):a.replace(/[\S]/g,"*")),{type:j.Text,textContent:a||"",rootId:i}}function Ya(s,e){let{doc:t,blockClass:r,blockSelector:i,inlineStylesheet:n,maskInputOptions:o={},maskInputFn:l,dataURLOptions:a={},inlineImages:u,recordCanvas:c,keepIframeSrcFn:h,newlyAddedElement:m=!1,rootId:g}=e,p=Ba(s,r,i),d=Pa(s),f={},b=s.attributes.length;for(let y=0;yO.href===s.href),v=null;y&&(v=Tr(y)),v&&(delete f.rel,delete f.href,f._cssText=v)}if(d==="style"&&s.sheet){let y=Tr(s.sheet);y&&(s.childNodes.length>1&&(y=Ta(y,s)),f._cssText=y)}if(["input","textarea","select"].includes(d)){let y=s.value,v=s.checked;f.type!=="radio"&&f.type!=="checkbox"&&f.type!=="submit"&&f.type!=="button"&&y?f.value=Bt({element:s,type:Wt(s),tagName:d,value:y,maskInputOptions:o,maskInputFn:l}):v&&(f.checked=v)}if(d==="option"&&(s.selected&&!o.select?f.selected=!0:delete f.selected),d==="dialog"&&s.open&&(f.rr_open_mode=s.matches("dialog:modal")?"modal":"non-modal"),d==="canvas"&&c){if(s.__context==="2d")xa(s)||(f.rr_dataURL=s.toDataURL(a.type,a.quality));else if(!("__context"in s)){let y=s.toDataURL(a.type,a.quality),v=t.createElement("canvas");v.width=s.width,v.height=s.height;let O=v.toDataURL(a.type,a.quality);y!==O&&(f.rr_dataURL=y)}}if(d==="img"&&u){Ee||(Ee=t.createElement("canvas"),qs=Ee.getContext("2d"));let y=s,v=y.currentSrc||y.getAttribute("src")||"",O=y.crossOrigin,T=()=>{y.removeEventListener("load",T);try{Ee.width=y.naturalWidth,Ee.height=y.naturalHeight,qs.drawImage(y,0,0),f.rr_dataURL=Ee.toDataURL(a.type,a.quality)}catch(V){if(y.crossOrigin!=="anonymous"){y.crossOrigin="anonymous",y.complete&&y.naturalWidth!==0?T():y.addEventListener("load",T);return}else console.warn(`Cannot inline img src=${v}! Error: ${V}`)}y.crossOrigin==="anonymous"&&(O?f.crossOrigin=O:y.removeAttribute("crossorigin"))};y.complete&&y.naturalWidth!==0?T():y.addEventListener("load",T)}if(["audio","video"].includes(d)){let y=f;y.rr_mediaState=s.paused?"paused":"played",y.rr_mediaCurrentTime=s.currentTime,y.rr_mediaPlaybackRate=s.playbackRate,y.rr_mediaMuted=s.muted,y.rr_mediaLoop=s.loop,y.rr_mediaVolume=s.volume}if(m||(s.scrollLeft&&(f.rr_scrollLeft=s.scrollLeft),s.scrollTop&&(f.rr_scrollTop=s.scrollTop)),p){let{width:y,height:v}=s.getBoundingClientRect();f={class:f.class,rr_width:`${y}px`,rr_height:`${v}px`}}d==="iframe"&&!h(f.src)&&(s.contentDocument||(f.rr_src=f.src),delete f.src);let S;try{customElements.get(d)&&(S=!0)}catch{}return{type:j.Element,tagName:d,attributes:f,childNodes:[],isSVG:Ua(s)||void 0,needBlock:p,rootId:g,isCustom:S}}function _(s){return s==null?"":s.toLowerCase()}function rn(s){return s===!0||s==="all"?{script:!0,comment:!0,headFavicon:!0,headWhitespace:!0,headMetaSocial:!0,headMetaRobots:!0,headMetaHttpEquiv:!0,headMetaVerification:!0,headMetaAuthorship:s==="all",headMetaDescKeywords:s==="all",headTitleMutations:s==="all"}:s||{}}function Ha(s,e){if(e.comment&&s.type===j.Comment)return!0;if(s.type===j.Element){if(e.script&&(s.tagName==="script"||s.tagName==="link"&&(s.attributes.rel==="preload"&&s.attributes.as==="script"||s.attributes.rel==="modulepreload")||s.tagName==="link"&&s.attributes.rel==="prefetch"&&typeof s.attributes.href=="string"&&Ki(s.attributes.href)==="js"))return!0;if(e.headFavicon&&(s.tagName==="link"&&s.attributes.rel==="shortcut icon"||s.tagName==="meta"&&(_(s.attributes.name).match(/^msapplication-tile(image|color)$/)||_(s.attributes.name)==="application-name"||_(s.attributes.rel)==="icon"||_(s.attributes.rel)==="apple-touch-icon"||_(s.attributes.rel)==="shortcut icon")))return!0;if(s.tagName==="meta"){if(e.headMetaDescKeywords&&_(s.attributes.name).match(/^description|keywords$/))return!0;if(e.headMetaSocial&&(_(s.attributes.property).match(/^(og|twitter|fb):/)||_(s.attributes.name).match(/^(og|twitter):/)||_(s.attributes.name)==="pinterest"))return!0;if(e.headMetaRobots&&(_(s.attributes.name)==="robots"||_(s.attributes.name)==="googlebot"||_(s.attributes.name)==="bingbot"))return!0;if(e.headMetaHttpEquiv&&s.attributes["http-equiv"]!==void 0)return!0;if(e.headMetaAuthorship&&(_(s.attributes.name)==="author"||_(s.attributes.name)==="generator"||_(s.attributes.name)==="framework"||_(s.attributes.name)==="publisher"||_(s.attributes.name)==="progid"||_(s.attributes.property).match(/^article:/)||_(s.attributes.property).match(/^product:/)))return!0;if(e.headMetaVerification&&(_(s.attributes.name)==="google-site-verification"||_(s.attributes.name)==="yandex-verification"||_(s.attributes.name)==="csrf-token"||_(s.attributes.name)==="p:domain_verify"||_(s.attributes.name)==="verify-v1"||_(s.attributes.name)==="verification"||_(s.attributes.name)==="shopify-checkout-api-token"))return!0}}return!1}function Re(s,e){let{doc:t,mirror:r,blockClass:i,blockSelector:n,maskTextClass:o,maskTextSelector:l,skipChild:a=!1,inlineStylesheet:u=!0,maskInputOptions:c={},maskTextFn:h,maskInputFn:m,slimDOMOptions:g,dataURLOptions:p={},inlineImages:d=!1,recordCanvas:f=!1,onSerialize:b,onIframeLoad:S,iframeLoadTimeout:y=5e3,onStylesheetLoad:v,stylesheetLoadTimeout:O=5e3,keepIframeSrcFn:T=()=>!1,newlyAddedElement:V=!1,cssCaptured:$=!1}=e,{needsMask:z}=e,{preserveWhiteSpace:Y=!0}=e;z||(z=tn(s,o,l,z===void 0));let Q=Va(s,{doc:t,mirror:r,blockClass:i,blockSelector:n,needsMask:z,inlineStylesheet:u,maskInputOptions:c,maskTextFn:h,maskInputFn:m,dataURLOptions:p,inlineImages:d,recordCanvas:f,keepIframeSrcFn:T,newlyAddedElement:V,cssCaptured:$});if(!Q)return console.warn(s,"not serialized"),null;let ne;r.hasNode(s)?ne=r.getId(s):Ha(Q,g)||!Y&&Q.type===j.Text&&!Q.textContent.replace(/^\s+|\s+$/gm,"").length?ne=He:ne=Qi();let M=Object.assign(Q,{id:ne});if(r.add(s,M),ne===He)return null;b&&b(s);let Le=!a;if(M.type===j.Element){Le=Le&&!M.needBlock,delete M.needBlock;let G=H.shadowRoot(s);G&&je(G)&&(M.isShadowHost=!0)}if((M.type===j.Document||M.type===j.Element)&&Le){g.headWhitespace&&M.type===j.Element&&M.tagName==="head"&&(Y=!1);let G={doc:t,mirror:r,blockClass:i,blockSelector:n,needsMask:z,maskTextClass:o,maskTextSelector:l,skipChild:a,inlineStylesheet:u,maskInputOptions:c,maskTextFn:h,maskInputFn:m,slimDOMOptions:g,dataURLOptions:p,inlineImages:d,recordCanvas:f,preserveWhiteSpace:Y,onSerialize:b,onIframeLoad:S,iframeLoadTimeout:y,onStylesheetLoad:v,stylesheetLoadTimeout:O,keepIframeSrcFn:T,cssCaptured:!1};if(!(M.type===j.Element&&M.tagName==="textarea"&&M.attributes.value!==void 0)){M.type===j.Element&&M.attributes._cssText!==void 0&&typeof M.attributes._cssText=="string"&&(G.cssCaptured=!0);for(let ge of Array.from(H.childNodes(s))){let oe=Re(ge,G);oe&&M.childNodes.push(oe)}}let q=null;if(Xi(s)&&(q=H.shadowRoot(s)))for(let ge of Array.from(H.childNodes(q))){let oe=Re(ge,G);oe&&(je(q)&&(oe.isShadow=!0),M.childNodes.push(oe))}}let Fe=H.parentNode(s);return Fe&&Ge(Fe)&&je(Fe)&&(M.isShadow=!0),M.type===j.Element&&M.tagName==="iframe"&&Wa(s,()=>{let G=s.contentDocument;if(G&&S){let q=Re(G,{doc:G,mirror:r,blockClass:i,blockSelector:n,needsMask:z,maskTextClass:o,maskTextSelector:l,skipChild:!1,inlineStylesheet:u,maskInputOptions:c,maskTextFn:h,maskInputFn:m,slimDOMOptions:g,dataURLOptions:p,inlineImages:d,recordCanvas:f,preserveWhiteSpace:Y,onSerialize:b,onIframeLoad:S,iframeLoadTimeout:y,onStylesheetLoad:v,stylesheetLoadTimeout:O,keepIframeSrcFn:T});q&&S(s,q)}},y),M.type===j.Element&&M.tagName==="link"&&typeof M.attributes.rel=="string"&&(M.attributes.rel==="stylesheet"||M.attributes.rel==="preload"&&typeof M.attributes.href=="string"&&Ki(M.attributes.href)==="css")&&za(s,()=>{if(v){let G=Re(s,{doc:t,mirror:r,blockClass:i,blockSelector:n,needsMask:z,maskTextClass:o,maskTextSelector:l,skipChild:!1,inlineStylesheet:u,maskInputOptions:c,maskTextFn:h,maskInputFn:m,slimDOMOptions:g,dataURLOptions:p,inlineImages:d,recordCanvas:f,preserveWhiteSpace:Y,onSerialize:b,onIframeLoad:S,iframeLoadTimeout:y,onStylesheetLoad:v,stylesheetLoadTimeout:O,keepIframeSrcFn:T});G&&v(s,G)}},O),M}function Za(s,e){let{mirror:t=new Ut,blockClass:r="rr-block",blockSelector:i=null,maskTextClass:n="rr-mask",maskTextSelector:o=null,inlineStylesheet:l=!0,inlineImages:a=!1,recordCanvas:u=!1,maskAllInputs:c=!1,maskTextFn:h,maskInputFn:m,slimDOM:g=!1,dataURLOptions:p,preserveWhiteSpace:d,onSerialize:f,onIframeLoad:b,iframeLoadTimeout:S,onStylesheetLoad:y,stylesheetLoadTimeout:v,keepIframeSrcFn:O=()=>!1}=e||{},T=c===!0?{color:!0,date:!0,"datetime-local":!0,email:!0,month:!0,number:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0,textarea:!0,select:!0,password:!0}:c===!1?{password:!0}:c,V=rn(g);return Re(s,{doc:s,mirror:t,blockClass:r,blockSelector:i,maskTextClass:n,maskTextSelector:o,skipChild:!1,inlineStylesheet:l,maskInputOptions:T,maskTextFn:h,maskInputFn:m,slimDOMOptions:V,dataURLOptions:p,inlineImages:a,recordCanvas:u,preserveWhiteSpace:d,onSerialize:f,onIframeLoad:b,iframeLoadTimeout:S,onStylesheetLoad:y,stylesheetLoadTimeout:v,keepIframeSrcFn:O,newlyAddedElement:!1})}var Xa=/(max|min)-device-(width|height)/,xf=new RegExp(Xa.source,"g");function Ja(s){return s&&s.__esModule&&Object.prototype.hasOwnProperty.call(s,"default")?s.default:s}function Ka(s){if(s.__esModule)return s;var e=s.default;if(typeof e=="function"){var t=function r(){return this instanceof r?Reflect.construct(e,arguments,this.constructor):e.apply(this,arguments)};t.prototype=e.prototype}else t={};return Object.defineProperty(t,"__esModule",{value:!0}),Object.keys(s).forEach(function(r){var i=Object.getOwnPropertyDescriptor(s,r);Object.defineProperty(t,r,i.get?i:{enumerable:!0,get:function(){return s[r]}})}),t}var Is={exports:{}},k=String,sn=function(){return{isColorSupported:!1,reset:k,bold:k,dim:k,italic:k,underline:k,inverse:k,hidden:k,strikethrough:k,black:k,red:k,green:k,yellow:k,blue:k,magenta:k,cyan:k,white:k,gray:k,bgBlack:k,bgRed:k,bgGreen:k,bgYellow:k,bgBlue:k,bgMagenta:k,bgCyan:k,bgWhite:k}};Is.exports=sn();Is.exports.createColors=sn;var Qa=Is.exports,qa={},el=Object.freeze(Object.defineProperty({__proto__:null,default:qa},Symbol.toStringTag,{value:"Module"})),se=Ka(el),ti=Qa,ri=se,_r=class nn extends Error{constructor(e,t,r,i,n,o){super(e),this.name="CssSyntaxError",this.reason=e,n&&(this.file=n),i&&(this.source=i),o&&(this.plugin=o),typeof t<"u"&&typeof r<"u"&&(typeof t=="number"?(this.line=t,this.column=r):(this.line=t.line,this.column=t.column,this.endLine=r.line,this.endColumn=r.column)),this.setMessage(),Error.captureStackTrace&&Error.captureStackTrace(this,nn)}setMessage(){this.message=this.plugin?this.plugin+": ":"",this.message+=this.file?this.file:"",typeof this.line<"u"&&(this.message+=":"+this.line+":"+this.column),this.message+=": "+this.reason}showSourceCode(e){if(!this.source)return"";let t=this.source;e==null&&(e=ti.isColorSupported),ri&&e&&(t=ri(t));let r=t.split(/\r?\n/),i=Math.max(this.line-3,0),n=Math.min(this.line+2,r.length),o=String(n).length,l,a;if(e){let{bold:u,gray:c,red:h}=ti.createColors(!0);l=m=>u(h(m)),a=m=>c(m)}else l=a=u=>u;return r.slice(i,n).map((u,c)=>{let h=i+1+c,m=" "+(" "+h).slice(-o)+" | ";if(h===this.line){let g=a(m.replace(/\d/g," "))+u.slice(0,this.column-1).replace(/[^\t]/g," ");return l(">")+a(m)+u+` + `+g+l("^")}return" "+a(m)+u}).join(` +`)}toString(){let e=this.showSourceCode();return e&&(e=` + +`+e+` +`),this.name+": "+this.message+e}},Es=_r;_r.default=_r;var st={};st.isClean=Symbol("isClean");st.my=Symbol("my");var si={after:` +`,beforeClose:` +`,beforeComment:` +`,beforeDecl:` +`,beforeOpen:" ",beforeRule:` +`,colon:": ",commentLeft:" ",commentRight:" ",emptyBody:"",indent:" ",semicolon:!1};function tl(s){return s[0].toUpperCase()+s.slice(1)}var kr=class{constructor(e){this.builder=e}atrule(e,t){let r="@"+e.name,i=e.params?this.rawValue(e,"params"):"";if(typeof e.raws.afterName<"u"?r+=e.raws.afterName:i&&(r+=" "),e.nodes)this.block(e,r+i);else{let n=(e.raws.between||"")+(t?";":"");this.builder(r+i+n,e)}}beforeAfter(e,t){let r;e.type==="decl"?r=this.raw(e,null,"beforeDecl"):e.type==="comment"?r=this.raw(e,null,"beforeComment"):t==="before"?r=this.raw(e,null,"beforeRule"):r=this.raw(e,null,"beforeClose");let i=e.parent,n=0;for(;i&&i.type!=="root";)n+=1,i=i.parent;if(r.includes(` +`)){let o=this.raw(e,null,"indent");if(o.length)for(let l=0;l0&&e.nodes[t].type==="comment";)t-=1;let r=this.raw(e,"semicolon");for(let i=0;i{if(i=a.raws[t],typeof i<"u")return!1})}return typeof i>"u"&&(i=si[r]),o.rawCache[r]=i,i}rawBeforeClose(e){let t;return e.walk(r=>{if(r.nodes&&r.nodes.length>0&&typeof r.raws.after<"u")return t=r.raws.after,t.includes(` +`)&&(t=t.replace(/[^\n]+$/,"")),!1}),t&&(t=t.replace(/\S/g,"")),t}rawBeforeComment(e,t){let r;return e.walkComments(i=>{if(typeof i.raws.before<"u")return r=i.raws.before,r.includes(` +`)&&(r=r.replace(/[^\n]+$/,"")),!1}),typeof r>"u"?r=this.raw(t,null,"beforeDecl"):r&&(r=r.replace(/\S/g,"")),r}rawBeforeDecl(e,t){let r;return e.walkDecls(i=>{if(typeof i.raws.before<"u")return r=i.raws.before,r.includes(` +`)&&(r=r.replace(/[^\n]+$/,"")),!1}),typeof r>"u"?r=this.raw(t,null,"beforeRule"):r&&(r=r.replace(/\S/g,"")),r}rawBeforeOpen(e){let t;return e.walk(r=>{if(r.type!=="decl"&&(t=r.raws.between,typeof t<"u"))return!1}),t}rawBeforeRule(e){let t;return e.walk(r=>{if(r.nodes&&(r.parent!==e||e.first!==r)&&typeof r.raws.before<"u")return t=r.raws.before,t.includes(` +`)&&(t=t.replace(/[^\n]+$/,"")),!1}),t&&(t=t.replace(/\S/g,"")),t}rawColon(e){let t;return e.walkDecls(r=>{if(typeof r.raws.between<"u")return t=r.raws.between.replace(/[^\s:]/g,""),!1}),t}rawEmptyBody(e){let t;return e.walk(r=>{if(r.nodes&&r.nodes.length===0&&(t=r.raws.after,typeof t<"u"))return!1}),t}rawIndent(e){if(e.raws.indent)return e.raws.indent;let t;return e.walk(r=>{let i=r.parent;if(i&&i!==e&&i.parent&&i.parent===e&&typeof r.raws.before<"u"){let n=r.raws.before.split(` +`);return t=n[n.length-1],t=t.replace(/\S/g,""),!1}}),t}rawSemicolon(e){let t;return e.walk(r=>{if(r.nodes&&r.nodes.length&&r.last.type==="decl"&&(t=r.raws.semicolon,typeof t<"u"))return!1}),t}rawValue(e,t){let r=e[t],i=e.raws[t];return i&&i.value===r?i.raw:r}root(e){this.body(e),e.raws.after&&this.builder(e.raws.after)}rule(e){this.block(e,this.rawValue(e,"selector")),e.raws.ownSemicolon&&this.builder(e.raws.ownSemicolon,e,"end")}stringify(e,t){if(!this[e.type])throw new Error("Unknown AST node type "+e.type+". Maybe you need to change PostCSS stringifier.");this[e.type](e,t)}},on=kr;kr.default=kr;var rl=on;function Pr(s,e){new rl(e).stringify(s)}var rr=Pr;Pr.default=Pr;var{isClean:ft,my:sl}=st,il=Es,nl=on,ol=rr;function $r(s,e){let t=new s.constructor;for(let r in s){if(!Object.prototype.hasOwnProperty.call(s,r)||r==="proxyCache")continue;let i=s[r],n=typeof i;r==="parent"&&n==="object"?e&&(t[r]=e):r==="source"?t[r]=i:Array.isArray(i)?t[r]=i.map(o=>$r(o,t)):(n==="object"&&i!==null&&(i=$r(i)),t[r]=i)}return t}var Lr=class{constructor(e={}){this.raws={},this[ft]=!1,this[sl]=!0;for(let t in e)if(t==="nodes"){this.nodes=[];for(let r of e[t])typeof r.clone=="function"?this.append(r.clone()):this.append(r)}else this[t]=e[t]}addToError(e){if(e.postcssNode=this,e.stack&&this.source&&/\n\s{4}at /.test(e.stack)){let t=this.source;e.stack=e.stack.replace(/\n\s{4}at /,`$&${t.input.from}:${t.start.line}:${t.start.column}$&`)}return e}after(e){return this.parent.insertAfter(this,e),this}assign(e={}){for(let t in e)this[t]=e[t];return this}before(e){return this.parent.insertBefore(this,e),this}cleanRaws(e){delete this.raws.before,delete this.raws.after,e||delete this.raws.between}clone(e={}){let t=$r(this);for(let r in e)t[r]=e[r];return t}cloneAfter(e={}){let t=this.clone(e);return this.parent.insertAfter(this,t),t}cloneBefore(e={}){let t=this.clone(e);return this.parent.insertBefore(this,t),t}error(e,t={}){if(this.source){let{end:r,start:i}=this.rangeBy(t);return this.source.input.error(e,{column:i.column,line:i.line},{column:r.column,line:r.line},t)}return new il(e)}getProxyProcessor(){return{get(e,t){return t==="proxyOf"?e:t==="root"?()=>e.root().toProxy():e[t]},set(e,t,r){return e[t]===r||(e[t]=r,(t==="prop"||t==="value"||t==="name"||t==="params"||t==="important"||t==="text")&&e.markDirty()),!0}}}markDirty(){if(this[ft]){this[ft]=!1;let e=this;for(;e=e.parent;)e[ft]=!1}}next(){if(!this.parent)return;let e=this.parent.index(this);return this.parent.nodes[e+1]}positionBy(e,t){let r=this.source.start;if(e.index)r=this.positionInside(e.index,t);else if(e.word){t=this.toString();let i=t.indexOf(e.word);i!==-1&&(r=this.positionInside(i,t))}return r}positionInside(e,t){let r=t||this.toString(),i=this.source.start.column,n=this.source.start.line;for(let o=0;otypeof a=="object"&&a.toJSON?a.toJSON(null,t):a);else if(typeof l=="object"&&l.toJSON)r[o]=l.toJSON(null,t);else if(o==="source"){let a=t.get(l.input);a==null&&(a=n,t.set(l.input,n),n++),r[o]={end:l.end,inputId:a,start:l.start}}else r[o]=l}return i&&(r.inputs=[...t.keys()].map(o=>o.toJSON())),r}toProxy(){return this.proxyCache||(this.proxyCache=new Proxy(this,this.getProxyProcessor())),this.proxyCache}toString(e=ol){e.stringify&&(e=e.stringify);let t="";return e(this,r=>{t+=r}),t}warn(e,t,r){let i={node:this};for(let n in r)i[n]=r[n];return e.warn(t,i)}get proxyOf(){return this}},sr=Lr;Lr.default=Lr;var al=sr,Fr=class extends al{constructor(e){e&&typeof e.value<"u"&&typeof e.value!="string"&&(e={...e,value:String(e.value)}),super(e),this.type="decl"}get variable(){return this.prop.startsWith("--")||this.prop[0]==="$"}},ir=Fr;Fr.default=Fr;var ll="useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict",ul=(s,e=21)=>(t=e)=>{let r="",i=t;for(;i--;)r+=s[Math.random()*s.length|0];return r},cl=(s=21)=>{let e="",t=s;for(;t--;)e+=ll[Math.random()*64|0];return e},hl={nanoid:cl,customAlphabet:ul},{SourceMapConsumer:ii,SourceMapGenerator:ni}=se,{existsSync:fl,readFileSync:dl}=se,{dirname:Sr,join:pl}=se;function ml(s){return Buffer?Buffer.from(s,"base64").toString():window.atob(s)}var Ur=class{constructor(e,t){if(t.map===!1)return;this.loadAnnotation(e),this.inline=this.startWith(this.annotation,"data:");let r=t.map?t.map.prev:void 0,i=this.loadMap(t.from,r);!this.mapFile&&t.from&&(this.mapFile=t.from),this.mapFile&&(this.root=Sr(this.mapFile)),i&&(this.text=i)}consumer(){return this.consumerCache||(this.consumerCache=new ii(this.text)),this.consumerCache}decodeInline(e){let t=/^data:application\/json;charset=utf-?8;base64,/,r=/^data:application\/json;base64,/,i=/^data:application\/json;charset=utf-?8,/,n=/^data:application\/json,/;if(i.test(e)||n.test(e))return decodeURIComponent(e.substr(RegExp.lastMatch.length));if(t.test(e)||r.test(e))return ml(e.substr(RegExp.lastMatch.length));let o=e.match(/data:application\/json;([^,]+),/)[1];throw new Error("Unsupported source map encoding "+o)}getAnnotationURL(e){return e.replace(/^\/\*\s*# sourceMappingURL=/,"").trim()}isMap(e){return typeof e!="object"?!1:typeof e.mappings=="string"||typeof e._mappings=="string"||Array.isArray(e.sections)}loadAnnotation(e){let t=e.match(/\/\*\s*# sourceMappingURL=/gm);if(!t)return;let r=e.lastIndexOf(t.pop()),i=e.indexOf("*/",r);r>-1&&i>-1&&(this.annotation=this.getAnnotationURL(e.substring(r,i)))}loadFile(e){if(this.root=Sr(e),fl(e))return this.mapFile=e,dl(e,"utf-8").toString().trim()}loadMap(e,t){if(t===!1)return!1;if(t){if(typeof t=="string")return t;if(typeof t=="function"){let r=t(e);if(r){let i=this.loadFile(r);if(!i)throw new Error("Unable to load previous source map: "+r.toString());return i}}else{if(t instanceof ii)return ni.fromSourceMap(t).toString();if(t instanceof ni)return t.toString();if(this.isMap(t))return JSON.stringify(t);throw new Error("Unsupported previous source map format: "+t.toString())}}else{if(this.inline)return this.decodeInline(this.annotation);if(this.annotation){let r=this.annotation;return e&&(r=pl(Sr(e),r)),this.loadFile(r)}}}startWith(e,t){return e?e.substr(0,t.length)===t:!1}withContent(){return!!(this.consumer().sourcesContent&&this.consumer().sourcesContent.length>0)}},an=Ur;Ur.default=Ur;var{SourceMapConsumer:gl,SourceMapGenerator:yl}=se,{fileURLToPath:oi,pathToFileURL:dt}=se,{isAbsolute:Br,resolve:Wr}=se,{nanoid:wl}=hl,vr=se,ai=Es,bl=an,Cr=Symbol("fromOffsetCache"),Sl=!!(gl&&yl),li=!!(Wr&&Br),Gt=class{constructor(e,t={}){if(e===null||typeof e>"u"||typeof e=="object"&&!e.toString)throw new Error(`PostCSS received ${e} instead of CSS string`);if(this.css=e.toString(),this.css[0]==="\uFEFF"||this.css[0]==="\uFFFE"?(this.hasBOM=!0,this.css=this.css.slice(1)):this.hasBOM=!1,t.from&&(!li||/^\w+:\/\//.test(t.from)||Br(t.from)?this.file=t.from:this.file=Wr(t.from)),li&&Sl){let r=new bl(this.css,t);if(r.text){this.map=r;let i=r.consumer().file;!this.file&&i&&(this.file=this.mapResolve(i))}}this.file||(this.id=""),this.map&&(this.map.file=this.from)}error(e,t,r,i={}){let n,o,l;if(t&&typeof t=="object"){let u=t,c=r;if(typeof u.offset=="number"){let h=this.fromOffset(u.offset);t=h.line,r=h.col}else t=u.line,r=u.column;if(typeof c.offset=="number"){let h=this.fromOffset(c.offset);o=h.line,l=h.col}else o=c.line,l=c.column}else if(!r){let u=this.fromOffset(t);t=u.line,r=u.col}let a=this.origin(t,r,o,l);return a?n=new ai(e,a.endLine===void 0?a.line:{column:a.column,line:a.line},a.endLine===void 0?a.column:{column:a.endColumn,line:a.endLine},a.source,a.file,i.plugin):n=new ai(e,o===void 0?t:{column:r,line:t},o===void 0?r:{column:l,line:o},this.css,this.file,i.plugin),n.input={column:r,endColumn:l,endLine:o,line:t,source:this.css},this.file&&(dt&&(n.input.url=dt(this.file).toString()),n.input.file=this.file),n}fromOffset(e){let t,r;if(this[Cr])r=this[Cr];else{let n=this.css.split(` +`);r=new Array(n.length);let o=0;for(let l=0,a=n.length;l=t)i=r.length-1;else{let n=r.length-2,o;for(;i>1),e=r[o+1])i=o+1;else{i=o;break}}return{col:e-r[i]+1,line:i+1}}mapResolve(e){return/^\w+:\/\//.test(e)?e:Wr(this.map.consumer().sourceRoot||this.map.root||".",e)}origin(e,t,r,i){if(!this.map)return!1;let n=this.map.consumer(),o=n.originalPositionFor({column:t,line:e});if(!o.source)return!1;let l;typeof r=="number"&&(l=n.originalPositionFor({column:i,line:r}));let a;Br(o.source)?a=dt(o.source):a=new URL(o.source,this.map.consumer().sourceRoot||dt(this.map.mapFile));let u={column:o.column,endColumn:l&&l.column,endLine:l&&l.line,line:o.line,url:a.toString()};if(a.protocol==="file:")if(oi)u.file=oi(a);else throw new Error("file: protocol is not available in this PostCSS build");let c=n.sourceContentFor(o.source);return c&&(u.source=c),u}toJSON(){let e={};for(let t of["hasBOM","css","file","id"])this[t]!=null&&(e[t]=this[t]);return this.map&&(e.map={...this.map},e.map.consumerCache&&(e.map.consumerCache=void 0)),e}get from(){return this.file||this.id}},nr=Gt;Gt.default=Gt;vr&&vr.registerInput&&vr.registerInput(Gt);var{SourceMapConsumer:ln,SourceMapGenerator:_t}=se,{dirname:kt,relative:un,resolve:cn,sep:hn}=se,{pathToFileURL:ui}=se,vl=nr,Cl=!!(ln&&_t),Il=!!(kt&&cn&&un&&hn),El=class{constructor(e,t,r,i){this.stringify=e,this.mapOpts=r.map||{},this.root=t,this.opts=r,this.css=i,this.originalCSS=i,this.usesFileUrls=!this.mapOpts.from&&this.mapOpts.absolute,this.memoizedFileURLs=new Map,this.memoizedPaths=new Map,this.memoizedURLs=new Map}addAnnotation(){let e;this.isInline()?e="data:application/json;base64,"+this.toBase64(this.map.toString()):typeof this.mapOpts.annotation=="string"?e=this.mapOpts.annotation:typeof this.mapOpts.annotation=="function"?e=this.mapOpts.annotation(this.opts.to,this.root):e=this.outputFile()+".map";let t=` +`;this.css.includes(`\r +`)&&(t=`\r +`),this.css+=t+"/*# sourceMappingURL="+e+" */"}applyPrevMaps(){for(let e of this.previous()){let t=this.toUrl(this.path(e.file)),r=e.root||kt(e.file),i;this.mapOpts.sourcesContent===!1?(i=new ln(e.text),i.sourcesContent&&(i.sourcesContent=null)):i=e.consumer(),this.map.applySourceMap(i,t,this.toUrl(this.path(r)))}}clearAnnotation(){if(this.mapOpts.annotation!==!1)if(this.root){let e;for(let t=this.root.nodes.length-1;t>=0;t--)e=this.root.nodes[t],e.type==="comment"&&e.text.indexOf("# sourceMappingURL=")===0&&this.root.removeChild(t)}else this.css&&(this.css=this.css.replace(/\n*?\/\*#[\S\s]*?\*\/$/gm,""))}generate(){if(this.clearAnnotation(),Il&&Cl&&this.isMap())return this.generateMap();{let e="";return this.stringify(this.root,t=>{e+=t}),[e]}}generateMap(){if(this.root)this.generateString();else if(this.previous().length===1){let e=this.previous()[0].consumer();e.file=this.outputFile(),this.map=_t.fromSourceMap(e,{ignoreInvalidMapping:!0})}else this.map=new _t({file:this.outputFile(),ignoreInvalidMapping:!0}),this.map.addMapping({generated:{column:0,line:1},original:{column:0,line:1},source:this.opts.from?this.toUrl(this.path(this.opts.from)):""});return this.isSourcesContent()&&this.setSourcesContent(),this.root&&this.previous().length>0&&this.applyPrevMaps(),this.isAnnotation()&&this.addAnnotation(),this.isInline()?[this.css]:[this.css,this.map]}generateString(){this.css="",this.map=new _t({file:this.outputFile(),ignoreInvalidMapping:!0});let e=1,t=1,r="",i={generated:{column:0,line:0},original:{column:0,line:0},source:""},n,o;this.stringify(this.root,(l,a,u)=>{if(this.css+=l,a&&u!=="end"&&(i.generated.line=e,i.generated.column=t-1,a.source&&a.source.start?(i.source=this.sourcePath(a),i.original.line=a.source.start.line,i.original.column=a.source.start.column-1,this.map.addMapping(i)):(i.source=r,i.original.line=1,i.original.column=0,this.map.addMapping(i))),n=l.match(/\n/g),n?(e+=n.length,o=l.lastIndexOf(` +`),t=l.length-o):t+=l.length,a&&u!=="start"){let c=a.parent||{raws:{}};(!(a.type==="decl"||a.type==="atrule"&&!a.nodes)||a!==c.last||c.raws.semicolon)&&(a.source&&a.source.end?(i.source=this.sourcePath(a),i.original.line=a.source.end.line,i.original.column=a.source.end.column-1,i.generated.line=e,i.generated.column=t-2,this.map.addMapping(i)):(i.source=r,i.original.line=1,i.original.column=0,i.generated.line=e,i.generated.column=t-1,this.map.addMapping(i)))}})}isAnnotation(){return this.isInline()?!0:typeof this.mapOpts.annotation<"u"?this.mapOpts.annotation:this.previous().length?this.previous().some(e=>e.annotation):!0}isInline(){if(typeof this.mapOpts.inline<"u")return this.mapOpts.inline;let e=this.mapOpts.annotation;return typeof e<"u"&&e!==!0?!1:this.previous().length?this.previous().some(t=>t.inline):!0}isMap(){return typeof this.opts.map<"u"?!!this.opts.map:this.previous().length>0}isSourcesContent(){return typeof this.mapOpts.sourcesContent<"u"?this.mapOpts.sourcesContent:this.previous().length?this.previous().some(e=>e.withContent()):!0}outputFile(){return this.opts.to?this.path(this.opts.to):this.opts.from?this.path(this.opts.from):"to.css"}path(e){if(this.mapOpts.absolute||e.charCodeAt(0)===60||/^\w+:\/\//.test(e))return e;let t=this.memoizedPaths.get(e);if(t)return t;let r=this.opts.to?kt(this.opts.to):".";typeof this.mapOpts.annotation=="string"&&(r=kt(cn(r,this.mapOpts.annotation)));let i=un(r,e);return this.memoizedPaths.set(e,i),i}previous(){if(!this.previousMaps)if(this.previousMaps=[],this.root)this.root.walk(e=>{if(e.source&&e.source.input.map){let t=e.source.input.map;this.previousMaps.includes(t)||this.previousMaps.push(t)}});else{let e=new vl(this.originalCSS,this.opts);e.map&&this.previousMaps.push(e.map)}return this.previousMaps}setSourcesContent(){let e={};if(this.root)this.root.walk(t=>{if(t.source){let r=t.source.input.from;if(r&&!e[r]){e[r]=!0;let i=this.usesFileUrls?this.toFileUrl(r):this.toUrl(this.path(r));this.map.setSourceContent(i,t.source.input.css)}}});else if(this.css){let t=this.opts.from?this.toUrl(this.path(this.opts.from)):"";this.map.setSourceContent(t,this.css)}}sourcePath(e){return this.mapOpts.from?this.toUrl(this.mapOpts.from):this.usesFileUrls?this.toFileUrl(e.source.input.from):this.toUrl(this.path(e.source.input.from))}toBase64(e){return Buffer?Buffer.from(e).toString("base64"):window.btoa(unescape(encodeURIComponent(e)))}toFileUrl(e){let t=this.memoizedFileURLs.get(e);if(t)return t;if(ui){let r=ui(e).toString();return this.memoizedFileURLs.set(e,r),r}else throw new Error("`map.absolute` option is not available in this PostCSS build")}toUrl(e){let t=this.memoizedURLs.get(e);if(t)return t;hn==="\\"&&(e=e.replace(/\\/g,"/"));let r=encodeURI(e).replace(/[#?]/g,encodeURIComponent);return this.memoizedURLs.set(e,r),r}},fn=El,xl=sr,zr=class extends xl{constructor(e){super(e),this.type="comment"}},or=zr;zr.default=zr;var{isClean:dn,my:pn}=st,mn=ir,gn=or,Ml=sr,yn,xs,Ms,wn;function bn(s){return s.map(e=>(e.nodes&&(e.nodes=bn(e.nodes)),delete e.source,e))}function Sn(s){if(s[dn]=!1,s.proxyOf.nodes)for(let e of s.proxyOf.nodes)Sn(e)}var ce=class vn extends Ml{append(...e){for(let t of e){let r=this.normalize(t,this.last);for(let i of r)this.proxyOf.nodes.push(i)}return this.markDirty(),this}cleanRaws(e){if(super.cleanRaws(e),this.nodes)for(let t of this.nodes)t.cleanRaws(e)}each(e){if(!this.proxyOf.nodes)return;let t=this.getIterator(),r,i;for(;this.indexes[t]e[t](...r.map(i=>typeof i=="function"?(n,o)=>i(n.toProxy(),o):i)):t==="every"||t==="some"?r=>e[t]((i,...n)=>r(i.toProxy(),...n)):t==="root"?()=>e.root().toProxy():t==="nodes"?e.nodes.map(r=>r.toProxy()):t==="first"||t==="last"?e[t].toProxy():e[t]:e[t]},set(e,t,r){return e[t]===r||(e[t]=r,(t==="name"||t==="params"||t==="selector")&&e.markDirty()),!0}}}index(e){return typeof e=="number"?e:(e.proxyOf&&(e=e.proxyOf),this.proxyOf.nodes.indexOf(e))}insertAfter(e,t){let r=this.index(e),i=this.normalize(t,this.proxyOf.nodes[r]).reverse();r=this.index(e);for(let o of i)this.proxyOf.nodes.splice(r+1,0,o);let n;for(let o in this.indexes)n=this.indexes[o],r"u")e=[];else if(Array.isArray(e)){e=e.slice(0);for(let i of e)i.parent&&i.parent.removeChild(i,"ignore")}else if(e.type==="root"&&this.type!=="document"){e=e.nodes.slice(0);for(let i of e)i.parent&&i.parent.removeChild(i,"ignore")}else if(e.type)e=[e];else if(e.prop){if(typeof e.value>"u")throw new Error("Value field is missed in node creation");typeof e.value!="string"&&(e.value=String(e.value)),e=[new mn(e)]}else if(e.selector)e=[new xs(e)];else if(e.name)e=[new Ms(e)];else if(e.text)e=[new gn(e)];else throw new Error("Unknown node type in node creation");return e.map(i=>(i[pn]||vn.rebuild(i),i=i.proxyOf,i.parent&&i.parent.removeChild(i),i[dn]&&Sn(i),typeof i.raws.before>"u"&&t&&typeof t.raws.before<"u"&&(i.raws.before=t.raws.before.replace(/\S/g,"")),i.parent=this.proxyOf,i))}prepend(...e){e=e.reverse();for(let t of e){let r=this.normalize(t,this.first,"prepend").reverse();for(let i of r)this.proxyOf.nodes.unshift(i);for(let i in this.indexes)this.indexes[i]=this.indexes[i]+r.length}return this.markDirty(),this}push(e){return e.parent=this,this.proxyOf.nodes.push(e),this}removeAll(){for(let e of this.proxyOf.nodes)e.parent=void 0;return this.proxyOf.nodes=[],this.markDirty(),this}removeChild(e){e=this.index(e),this.proxyOf.nodes[e].parent=void 0,this.proxyOf.nodes.splice(e,1);let t;for(let r in this.indexes)t=this.indexes[r],t>=e&&(this.indexes[r]=t-1);return this.markDirty(),this}replaceValues(e,t,r){return r||(r=t,t={}),this.walkDecls(i=>{t.props&&!t.props.includes(i.prop)||t.fast&&!i.value.includes(t.fast)||(i.value=i.value.replace(e,r))}),this.markDirty(),this}some(e){return this.nodes.some(e)}walk(e){return this.each((t,r)=>{let i;try{i=e(t,r)}catch(n){throw t.addToError(n)}return i!==!1&&t.walk&&(i=t.walk(e)),i})}walkAtRules(e,t){return t?e instanceof RegExp?this.walk((r,i)=>{if(r.type==="atrule"&&e.test(r.name))return t(r,i)}):this.walk((r,i)=>{if(r.type==="atrule"&&r.name===e)return t(r,i)}):(t=e,this.walk((r,i)=>{if(r.type==="atrule")return t(r,i)}))}walkComments(e){return this.walk((t,r)=>{if(t.type==="comment")return e(t,r)})}walkDecls(e,t){return t?e instanceof RegExp?this.walk((r,i)=>{if(r.type==="decl"&&e.test(r.prop))return t(r,i)}):this.walk((r,i)=>{if(r.type==="decl"&&r.prop===e)return t(r,i)}):(t=e,this.walk((r,i)=>{if(r.type==="decl")return t(r,i)}))}walkRules(e,t){return t?e instanceof RegExp?this.walk((r,i)=>{if(r.type==="rule"&&e.test(r.selector))return t(r,i)}):this.walk((r,i)=>{if(r.type==="rule"&&r.selector===e)return t(r,i)}):(t=e,this.walk((r,i)=>{if(r.type==="rule")return t(r,i)}))}get first(){if(this.proxyOf.nodes)return this.proxyOf.nodes[0]}get last(){if(this.proxyOf.nodes)return this.proxyOf.nodes[this.proxyOf.nodes.length-1]}};ce.registerParse=s=>{yn=s};ce.registerRule=s=>{xs=s};ce.registerAtRule=s=>{Ms=s};ce.registerRoot=s=>{wn=s};var ve=ce;ce.default=ce;ce.rebuild=s=>{s.type==="atrule"?Object.setPrototypeOf(s,Ms.prototype):s.type==="rule"?Object.setPrototypeOf(s,xs.prototype):s.type==="decl"?Object.setPrototypeOf(s,mn.prototype):s.type==="comment"?Object.setPrototypeOf(s,gn.prototype):s.type==="root"&&Object.setPrototypeOf(s,wn.prototype),s[pn]=!0,s.nodes&&s.nodes.forEach(e=>{ce.rebuild(e)})};var Nl=ve,Cn,In,Ze=class extends Nl{constructor(e){super({type:"document",...e}),this.nodes||(this.nodes=[])}toResult(e={}){return new Cn(new In,this,e).stringify()}};Ze.registerLazyResult=s=>{Cn=s};Ze.registerProcessor=s=>{In=s};var Ns=Ze;Ze.default=Ze;var ci={},En=function(e){ci[e]||(ci[e]=!0,typeof console<"u"&&console.warn&&console.warn(e))},Vr=class{constructor(e,t={}){if(this.type="warning",this.text=e,t.node&&t.node.source){let r=t.node.rangeBy(t);this.line=r.start.line,this.column=r.start.column,this.endLine=r.end.line,this.endColumn=r.end.column}for(let r in t)this[r]=t[r]}toString(){return this.node?this.node.error(this.text,{index:this.index,plugin:this.plugin,word:this.word}).message:this.plugin?this.plugin+": "+this.text:this.text}},xn=Vr;Vr.default=Vr;var Rl=xn,Gr=class{constructor(e,t,r){this.processor=e,this.messages=[],this.root=t,this.opts=r,this.css=void 0,this.map=void 0}toString(){return this.css}warn(e,t={}){t.plugin||this.lastPlugin&&this.lastPlugin.postcssPlugin&&(t.plugin=this.lastPlugin.postcssPlugin);let r=new Rl(e,t);return this.messages.push(r),r}warnings(){return this.messages.filter(e=>e.type==="warning")}get content(){return this.css}},Rs=Gr;Gr.default=Gr;var Ir=39,hi=34,pt=92,fi=47,mt=10,Ue=32,gt=12,yt=9,wt=13,Al=91,Ol=93,Dl=40,Tl=41,_l=123,kl=125,Pl=59,$l=42,Ll=58,Fl=64,bt=/[\t\n\f\r "#'()/;[\\\]{}]/g,St=/[\t\n\f\r !"#'():;@[\\\]{}]|\/(?=\*)/g,Ul=/.[\r\n"'(/\\]/,di=/[\da-f]/i,Bl=function(e,t={}){let r=e.css.valueOf(),i=t.ignoreErrors,n,o,l,a,u,c,h,m,g,p,d=r.length,f=0,b=[],S=[];function y(){return f}function v($){throw e.error("Unclosed "+$,f)}function O(){return S.length===0&&f>=d}function T($){if(S.length)return S.pop();if(f>=d)return;let z=$?$.ignoreUnclosed:!1;switch(n=r.charCodeAt(f),n){case mt:case Ue:case yt:case wt:case gt:{o=f;do o+=1,n=r.charCodeAt(o);while(n===Ue||n===mt||n===yt||n===wt||n===gt);p=["space",r.slice(f,o)],f=o-1;break}case Al:case Ol:case _l:case kl:case Ll:case Pl:case Tl:{let Y=String.fromCharCode(n);p=[Y,Y,f];break}case Dl:{if(m=b.length?b.pop()[1]:"",g=r.charCodeAt(f+1),m==="url"&&g!==Ir&&g!==hi&&g!==Ue&&g!==mt&&g!==yt&&g!==gt&&g!==wt){o=f;do{if(c=!1,o=r.indexOf(")",o+1),o===-1)if(i||z){o=f;break}else v("bracket");for(h=o;r.charCodeAt(h-1)===pt;)h-=1,c=!c}while(c);p=["brackets",r.slice(f,o+1),f,o],f=o}else o=r.indexOf(")",f+1),a=r.slice(f,o+1),o===-1||Ul.test(a)?p=["(","(",f]:(p=["brackets",a,f,o],f=o);break}case Ir:case hi:{l=n===Ir?"'":'"',o=f;do{if(c=!1,o=r.indexOf(l,o+1),o===-1)if(i||z){o=f+1;break}else v("string");for(h=o;r.charCodeAt(h-1)===pt;)h-=1,c=!c}while(c);p=["string",r.slice(f,o+1),f,o],f=o;break}case Fl:{bt.lastIndex=f+1,bt.test(r),bt.lastIndex===0?o=r.length-1:o=bt.lastIndex-2,p=["at-word",r.slice(f,o+1),f,o],f=o;break}case pt:{for(o=f,u=!0;r.charCodeAt(o+1)===pt;)o+=1,u=!u;if(n=r.charCodeAt(o+1),u&&n!==fi&&n!==Ue&&n!==mt&&n!==yt&&n!==wt&&n!==gt&&(o+=1,di.test(r.charAt(o)))){for(;di.test(r.charAt(o+1));)o+=1;r.charCodeAt(o+1)===Ue&&(o+=1)}p=["word",r.slice(f,o+1),f,o],f=o;break}default:{n===fi&&r.charCodeAt(f+1)===$l?(o=r.indexOf("*/",f+2)+1,o===0&&(i||z?o=r.length:v("comment")),p=["comment",r.slice(f,o+1),f,o],f=o):(St.lastIndex=f+1,St.test(r),St.lastIndex===0?o=r.length-1:o=St.lastIndex-2,p=["word",r.slice(f,o+1),f,o],b.push(p),f=o);break}}return f++,p}function V($){S.push($)}return{back:V,endOfFile:O,nextToken:T,position:y}},Mn=ve,jt=class extends Mn{constructor(e){super(e),this.type="atrule"}append(...e){return this.proxyOf.nodes||(this.nodes=[]),super.append(...e)}prepend(...e){return this.proxyOf.nodes||(this.nodes=[]),super.prepend(...e)}},As=jt;jt.default=jt;Mn.registerAtRule(jt);var Nn=ve,Rn,An,Oe=class extends Nn{constructor(e){super(e),this.type="root",this.nodes||(this.nodes=[])}normalize(e,t,r){let i=super.normalize(e);if(t){if(r==="prepend")this.nodes.length>1?t.raws.before=this.nodes[1].raws.before:delete t.raws.before;else if(this.first!==t)for(let n of i)n.raws.before=t.raws.before}return i}removeChild(e,t){let r=this.index(e);return!t&&r===0&&this.nodes.length>1&&(this.nodes[1].raws.before=this.nodes[r].raws.before),super.removeChild(e)}toResult(e={}){return new Rn(new An,this,e).stringify()}};Oe.registerLazyResult=s=>{Rn=s};Oe.registerProcessor=s=>{An=s};var it=Oe;Oe.default=Oe;Nn.registerRoot(Oe);var Xe={comma(s){return Xe.split(s,[","],!0)},space(s){let e=[" ",` +`," "];return Xe.split(s,e)},split(s,e,t){let r=[],i="",n=!1,o=0,l=!1,a="",u=!1;for(let c of s)u?u=!1:c==="\\"?u=!0:l?c===a&&(l=!1):c==='"'||c==="'"?(l=!0,a=c):c==="("?o+=1:c===")"?o>0&&(o-=1):o===0&&e.includes(c)&&(n=!0),n?(i!==""&&r.push(i.trim()),i="",n=!1):i+=c;return(t||i!=="")&&r.push(i.trim()),r}},On=Xe;Xe.default=Xe;var Dn=ve,Wl=On,Yt=class extends Dn{constructor(e){super(e),this.type="rule",this.nodes||(this.nodes=[])}get selectors(){return Wl.comma(this.selector)}set selectors(e){let t=this.selector?this.selector.match(/,\s*/):null,r=t?t[0]:","+this.raw("between","beforeOpen");this.selector=e.join(r)}},Os=Yt;Yt.default=Yt;Dn.registerRule(Yt);var zl=ir,Vl=Bl,Gl=or,jl=As,Yl=it,pi=Os,mi={empty:!0,space:!0};function Hl(s){for(let e=s.length-1;e>=0;e--){let t=s[e],r=t[3]||t[2];if(r)return r}}var Zl=class{constructor(e){this.input=e,this.root=new Yl,this.current=this.root,this.spaces="",this.semicolon=!1,this.createTokenizer(),this.root.source={input:e,start:{column:1,line:1,offset:0}}}atrule(e){let t=new jl;t.name=e[1].slice(1),t.name===""&&this.unnamedAtrule(t,e),this.init(t,e[2]);let r,i,n,o=!1,l=!1,a=[],u=[];for(;!this.tokenizer.endOfFile();){if(e=this.tokenizer.nextToken(),r=e[0],r==="("||r==="["?u.push(r==="("?")":"]"):r==="{"&&u.length>0?u.push("}"):r===u[u.length-1]&&u.pop(),u.length===0)if(r===";"){t.source.end=this.getPosition(e[2]),t.source.end.offset++,this.semicolon=!0;break}else if(r==="{"){l=!0;break}else if(r==="}"){if(a.length>0){for(n=a.length-1,i=a[n];i&&i[0]==="space";)i=a[--n];i&&(t.source.end=this.getPosition(i[3]||i[2]),t.source.end.offset++)}this.end(e);break}else a.push(e);else a.push(e);if(this.tokenizer.endOfFile()){o=!0;break}}t.raws.between=this.spacesAndCommentsFromEnd(a),a.length?(t.raws.afterName=this.spacesAndCommentsFromStart(a),this.raw(t,"params",a),o&&(e=a[a.length-1],t.source.end=this.getPosition(e[3]||e[2]),t.source.end.offset++,this.spaces=t.raws.between,t.raws.between="")):(t.raws.afterName="",t.params=""),l&&(t.nodes=[],this.current=t)}checkMissedSemicolon(e){let t=this.colon(e);if(t===!1)return;let r=0,i;for(let n=t-1;n>=0&&(i=e[n],!(i[0]!=="space"&&(r+=1,r===2)));n--);throw this.input.error("Missed semicolon",i[0]==="word"?i[3]+1:i[2])}colon(e){let t=0,r,i,n;for(let[o,l]of e.entries()){if(r=l,i=r[0],i==="("&&(t+=1),i===")"&&(t-=1),t===0&&i===":")if(!n)this.doubleColon(r);else{if(n[0]==="word"&&n[1]==="progid")continue;return o}n=r}return!1}comment(e){let t=new Gl;this.init(t,e[2]),t.source.end=this.getPosition(e[3]||e[2]),t.source.end.offset++;let r=e[1].slice(2,-2);if(/^\s*$/.test(r))t.text="",t.raws.left=r,t.raws.right="";else{let i=r.match(/^(\s*)([^]*\S)(\s*)$/);t.text=i[2],t.raws.left=i[1],t.raws.right=i[3]}}createTokenizer(){this.tokenizer=Vl(this.input)}decl(e,t){let r=new zl;this.init(r,e[0][2]);let i=e[e.length-1];for(i[0]===";"&&(this.semicolon=!0,e.pop()),r.source.end=this.getPosition(i[3]||i[2]||Hl(e)),r.source.end.offset++;e[0][0]!=="word";)e.length===1&&this.unknownWord(e),r.raws.before+=e.shift()[1];for(r.source.start=this.getPosition(e[0][2]),r.prop="";e.length;){let u=e[0][0];if(u===":"||u==="space"||u==="comment")break;r.prop+=e.shift()[1]}r.raws.between="";let n;for(;e.length;)if(n=e.shift(),n[0]===":"){r.raws.between+=n[1];break}else n[0]==="word"&&/\w/.test(n[1])&&this.unknownWord([n]),r.raws.between+=n[1];(r.prop[0]==="_"||r.prop[0]==="*")&&(r.raws.before+=r.prop[0],r.prop=r.prop.slice(1));let o=[],l;for(;e.length&&(l=e[0][0],!(l!=="space"&&l!=="comment"));)o.push(e.shift());this.precheckMissedSemicolon(e);for(let u=e.length-1;u>=0;u--){if(n=e[u],n[1].toLowerCase()==="!important"){r.important=!0;let c=this.stringFrom(e,u);c=this.spacesFromEnd(e)+c,c!==" !important"&&(r.raws.important=c);break}else if(n[1].toLowerCase()==="important"){let c=e.slice(0),h="";for(let m=u;m>0;m--){let g=c[m][0];if(h.trim().indexOf("!")===0&&g!=="space")break;h=c.pop()[1]+h}h.trim().indexOf("!")===0&&(r.important=!0,r.raws.important=h,e=c)}if(n[0]!=="space"&&n[0]!=="comment")break}e.some(u=>u[0]!=="space"&&u[0]!=="comment")&&(r.raws.between+=o.map(u=>u[1]).join(""),o=[]),this.raw(r,"value",o.concat(e),t),r.value.includes(":")&&!t&&this.checkMissedSemicolon(e)}doubleColon(e){throw this.input.error("Double colon",{offset:e[2]},{offset:e[2]+e[1].length})}emptyRule(e){let t=new pi;this.init(t,e[2]),t.selector="",t.raws.between="",this.current=t}end(e){this.current.nodes&&this.current.nodes.length&&(this.current.raws.semicolon=this.semicolon),this.semicolon=!1,this.current.raws.after=(this.current.raws.after||"")+this.spaces,this.spaces="",this.current.parent?(this.current.source.end=this.getPosition(e[2]),this.current.source.end.offset++,this.current=this.current.parent):this.unexpectedClose(e)}endFile(){this.current.parent&&this.unclosedBlock(),this.current.nodes&&this.current.nodes.length&&(this.current.raws.semicolon=this.semicolon),this.current.raws.after=(this.current.raws.after||"")+this.spaces,this.root.source.end=this.getPosition(this.tokenizer.position())}freeSemicolon(e){if(this.spaces+=e[1],this.current.nodes){let t=this.current.nodes[this.current.nodes.length-1];t&&t.type==="rule"&&!t.raws.ownSemicolon&&(t.raws.ownSemicolon=this.spaces,this.spaces="")}}getPosition(e){let t=this.input.fromOffset(e);return{column:t.col,line:t.line,offset:e}}init(e,t){this.current.push(e),e.source={input:this.input,start:this.getPosition(t)},e.raws.before=this.spaces,this.spaces="",e.type!=="comment"&&(this.semicolon=!1)}other(e){let t=!1,r=null,i=!1,n=null,o=[],l=e[1].startsWith("--"),a=[],u=e;for(;u;){if(r=u[0],a.push(u),r==="("||r==="[")n||(n=u),o.push(r==="("?")":"]");else if(l&&i&&r==="{")n||(n=u),o.push("}");else if(o.length===0)if(r===";")if(i){this.decl(a,l);return}else break;else if(r==="{"){this.rule(a);return}else if(r==="}"){this.tokenizer.back(a.pop()),t=!0;break}else r===":"&&(i=!0);else r===o[o.length-1]&&(o.pop(),o.length===0&&(n=null));u=this.tokenizer.nextToken()}if(this.tokenizer.endOfFile()&&(t=!0),o.length>0&&this.unclosedBracket(n),t&&i){if(!l)for(;a.length&&(u=a[a.length-1][0],!(u!=="space"&&u!=="comment"));)this.tokenizer.back(a.pop());this.decl(a,l)}else this.unknownWord(a)}parse(){let e;for(;!this.tokenizer.endOfFile();)switch(e=this.tokenizer.nextToken(),e[0]){case"space":this.spaces+=e[1];break;case";":this.freeSemicolon(e);break;case"}":this.end(e);break;case"comment":this.comment(e);break;case"at-word":this.atrule(e);break;case"{":this.emptyRule(e);break;default:this.other(e);break}this.endFile()}precheckMissedSemicolon(){}raw(e,t,r,i){let n,o,l=r.length,a="",u=!0,c,h;for(let m=0;mg+p[1],"");e.raws[t]={raw:m,value:a}}e[t]=a}rule(e){e.pop();let t=new pi;this.init(t,e[0][2]),t.raws.between=this.spacesAndCommentsFromEnd(e),this.raw(t,"selector",e),this.current=t}spacesAndCommentsFromEnd(e){let t,r="";for(;e.length&&(t=e[e.length-1][0],!(t!=="space"&&t!=="comment"));)r=e.pop()[1]+r;return r}spacesAndCommentsFromStart(e){let t,r="";for(;e.length&&(t=e[0][0],!(t!=="space"&&t!=="comment"));)r+=e.shift()[1];return r}spacesFromEnd(e){let t,r="";for(;e.length&&(t=e[e.length-1][0],t==="space");)r=e.pop()[1]+r;return r}stringFrom(e,t){let r="";for(let i=t;ijr(e)),s}var Yr={},Te=class _n{constructor(e,t,r){this.stringified=!1,this.processed=!1;let i;if(typeof t=="object"&&t!==null&&(t.type==="root"||t.type==="document"))i=jr(t);else if(t instanceof _n||t instanceof gi)i=jr(t.root),t.map&&(typeof r.map>"u"&&(r.map={}),r.map.inline||(r.map.inline=!1),r.map.prev=t.map);else{let n=nu;r.syntax&&(n=r.syntax.parse),r.parser&&(n=r.parser),n.parse&&(n=n.parse);try{i=n(t,r)}catch(o){this.processed=!0,this.error=o}i&&!i[ql]&&ru.rebuild(i)}this.result=new gi(e,i,r),this.helpers={...Yr,postcss:Yr,result:this.result},this.plugins=this.processor.plugins.map(n=>typeof n=="object"&&n.prepare?{...n,...n.prepare(this.result)}:n)}async(){return this.error?Promise.reject(this.error):this.processed?Promise.resolve(this.result):(this.processing||(this.processing=this.runAsync()),this.processing)}catch(e){return this.async().catch(e)}finally(e){return this.async().then(e,e)}getAsyncError(){throw new Error("Use process(css).then(cb) to work with async plugins")}handleError(e,t){let r=this.result.lastPlugin;try{if(t&&t.addToError(e),this.error=e,e.name==="CssSyntaxError"&&!e.plugin)e.plugin=r.postcssPlugin,e.setMessage();else if(r.postcssVersion&&process.env.NODE_ENV!=="production"){let i=r.postcssPlugin,n=r.postcssVersion,o=this.result.processor.version,l=n.split("."),a=o.split(".");(l[0]!==a[0]||parseInt(l[1])>parseInt(a[1]))&&console.error("Unknown error from PostCSS plugin. Your current PostCSS version is "+o+", but "+i+" uses "+n+". Perhaps this is the source of the error below.")}}catch(i){console&&console.error&&console.error(i)}return e}prepareVisitors(){this.listeners={};let e=(t,r,i)=>{this.listeners[r]||(this.listeners[r]=[]),this.listeners[r].push([t,i])};for(let t of this.plugins)if(typeof t=="object")for(let r in t){if(!lu[r]&&/^[A-Z]/.test(r))throw new Error(`Unknown event ${r} in ${t.postcssPlugin}. Try to update PostCSS (${this.processor.version} now).`);if(!uu[r])if(typeof t[r]=="object")for(let i in t[r])i==="*"?e(t,r,t[r][i]):e(t,r+"-"+i.toLowerCase(),t[r][i]);else typeof t[r]=="function"&&e(t,r,t[r])}this.hasListener=Object.keys(this.listeners).length>0}async runAsync(){this.plugin=0;for(let e=0;e0;){let r=this.visitTick(t);if(Be(r))try{await r}catch(i){let n=t[t.length-1].node;throw this.handleError(i,n)}}}if(this.listeners.OnceExit)for(let[t,r]of this.listeners.OnceExit){this.result.lastPlugin=t;try{if(e.type==="document"){let i=e.nodes.map(n=>r(n,this.helpers));await Promise.all(i)}else await r(e,this.helpers)}catch(i){throw this.handleError(i)}}}return this.processed=!0,this.stringify()}runOnRoot(e){this.result.lastPlugin=e;try{if(typeof e=="object"&&e.Once){if(this.result.root.type==="document"){let t=this.result.root.nodes.map(r=>e.Once(r,this.helpers));return Be(t[0])?Promise.all(t):t}return e.Once(this.result.root,this.helpers)}else if(typeof e=="function")return e(this.result.root,this.result)}catch(t){throw this.handleError(t)}}stringify(){if(this.error)throw this.error;if(this.stringified)return this.result;this.stringified=!0,this.sync();let e=this.result.opts,t=tu;e.syntax&&(t=e.syntax.stringify),e.stringifier&&(t=e.stringifier),t.stringify&&(t=t.stringify);let i=new eu(t,this.result.root,this.result.opts).generate();return this.result.css=i[0],this.result.map=i[1],this.result}sync(){if(this.error)throw this.error;if(this.processed)return this.result;if(this.processed=!0,this.processing)throw this.getAsyncError();for(let e of this.plugins){let t=this.runOnRoot(e);if(Be(t))throw this.getAsyncError()}if(this.prepareVisitors(),this.hasListener){let e=this.result.root;for(;!e[ae];)e[ae]=!0,this.walkSync(e);if(this.listeners.OnceExit)if(e.type==="document")for(let t of e.nodes)this.visitSync(this.listeners.OnceExit,t);else this.visitSync(this.listeners.OnceExit,e)}return this.result}then(e,t){return process.env.NODE_ENV!=="production"&&("from"in this.opts||iu("Without `from` option PostCSS could generate wrong source map and will not find Browserslist config. Set it to CSS file path or to `undefined` to prevent this warning.")),this.async().then(e,t)}toString(){return this.css}visitSync(e,t){for(let[r,i]of e){this.result.lastPlugin=r;let n;try{n=i(t,this.helpers)}catch(o){throw this.handleError(o,t.proxyOf)}if(t.type!=="root"&&t.type!=="document"&&!t.parent)return!0;if(Be(n))throw this.getAsyncError()}}visitTick(e){let t=e[e.length-1],{node:r,visitors:i}=t;if(r.type!=="root"&&r.type!=="document"&&!r.parent){e.pop();return}if(i.length>0&&t.visitorIndex{i[ae]||this.walkSync(i)});else{let i=this.listeners[r];if(i&&this.visitSync(i,e.toProxy()))return}}warnings(){return this.sync().warnings()}get content(){return this.stringify().content}get css(){return this.stringify().css}get map(){return this.stringify().map}get messages(){return this.sync().messages}get opts(){return this.result.opts}get processor(){return this.result.processor}get root(){return this.sync().root}get[Symbol.toStringTag](){return"LazyResult"}};Te.registerPostcss=s=>{Yr=s};var kn=Te;Te.default=Te;ou.registerLazyResult(Te);su.registerLazyResult(Te);var cu=fn,hu=rr,fu=En,du=Ds,pu=Rs,Hr=class{constructor(e,t,r){t=t.toString(),this.stringified=!1,this._processor=e,this._css=t,this._opts=r,this._map=void 0;let i,n=hu;this.result=new pu(this._processor,i,this._opts),this.result.css=t;let o=this;Object.defineProperty(this.result,"root",{get(){return o.root}});let l=new cu(n,i,this._opts,t);if(l.isMap()){let[a,u]=l.generate();a&&(this.result.css=a),u&&(this.result.map=u)}else l.clearAnnotation(),this.result.css=l.css}async(){return this.error?Promise.reject(this.error):Promise.resolve(this.result)}catch(e){return this.async().catch(e)}finally(e){return this.async().then(e,e)}sync(){if(this.error)throw this.error;return this.result}then(e,t){return process.env.NODE_ENV!=="production"&&("from"in this._opts||fu("Without `from` option PostCSS could generate wrong source map and will not find Browserslist config. Set it to CSS file path or to `undefined` to prevent this warning.")),this.async().then(e,t)}toString(){return this._css}warnings(){return[]}get content(){return this.result.css}get css(){return this.result.css}get map(){return this.result.map}get messages(){return[]}get opts(){return this.result.opts}get processor(){return this.result.processor}get root(){if(this._root)return this._root;let e,t=du;try{e=t(this._css,this._opts)}catch(r){this.error=r}if(this.error)throw this.error;return this._root=e,e}get[Symbol.toStringTag](){return"NoWorkResult"}},mu=Hr;Hr.default=Hr;var gu=mu,yu=kn,wu=Ns,bu=it,Je=class{constructor(e=[]){this.version="8.4.38",this.plugins=this.normalize(e)}normalize(e){let t=[];for(let r of e)if(r.postcss===!0?r=r():r.postcss&&(r=r.postcss),typeof r=="object"&&Array.isArray(r.plugins))t=t.concat(r.plugins);else if(typeof r=="object"&&r.postcssPlugin)t.push(r);else if(typeof r=="function")t.push(r);else if(typeof r=="object"&&(r.parse||r.stringify)){if(process.env.NODE_ENV!=="production")throw new Error("PostCSS syntaxes cannot be used as plugins. Instead, please use one of the syntax/parser/stringifier options as outlined in your PostCSS runner documentation.")}else throw new Error(r+" is not a PostCSS plugin");return t}process(e,t={}){return!this.plugins.length&&!t.parser&&!t.stringifier&&!t.syntax?new gu(this,e,t):new yu(this,e,t)}use(e){return this.plugins=this.plugins.concat(this.normalize([e])),this}},Su=Je;Je.default=Je;bu.registerProcessor(Je);wu.registerProcessor(Je);var vu=ir,Cu=an,Iu=or,Eu=As,xu=nr,Mu=it,Nu=Os;function Ke(s,e){if(Array.isArray(s))return s.map(i=>Ke(i));let{inputs:t,...r}=s;if(t){e=[];for(let i of t){let n={...i,__proto__:xu.prototype};n.map&&(n.map={...n.map,__proto__:Cu.prototype}),e.push(n)}}if(r.nodes&&(r.nodes=s.nodes.map(i=>Ke(i,e))),r.source){let{inputId:i,...n}=r.source;r.source=n,i!=null&&(r.source.input=e[i])}if(r.type==="root")return new Mu(r);if(r.type==="decl")return new vu(r);if(r.type==="rule")return new Nu(r);if(r.type==="comment")return new Iu(r);if(r.type==="atrule")return new Eu(r);throw new Error("Unknown node type: "+s.type)}var Ru=Ke;Ke.default=Ke;var Au=Es,Pn=ir,Ou=kn,Du=ve,Ts=Su,Tu=rr,_u=Ru,$n=Ns,ku=xn,Ln=or,Fn=As,Pu=Rs,$u=nr,Lu=Ds,Fu=On,Un=Os,Bn=it,Uu=sr;function R(...s){return s.length===1&&Array.isArray(s[0])&&(s=s[0]),new Ts(s)}R.plugin=function(e,t){let r=!1;function i(...o){console&&console.warn&&!r&&(r=!0,console.warn(e+`: postcss.plugin was deprecated. Migration guide: +https://evilmartians.com/chronicles/postcss-8-plugin-migration`),process.env.LANG&&process.env.LANG.startsWith("cn")&&console.warn(e+`: \u91CC\u9762 postcss.plugin \u88AB\u5F03\u7528. \u8FC1\u79FB\u6307\u5357: +https://www.w3ctech.com/topic/2226`));let l=t(...o);return l.postcssPlugin=e,l.postcssVersion=new Ts().version,l}let n;return Object.defineProperty(i,"postcss",{get(){return n||(n=i()),n}}),i.process=function(o,l,a){return R([i(a)]).process(o,l)},i};R.stringify=Tu;R.parse=Lu;R.fromJSON=_u;R.list=Fu;R.comment=s=>new Ln(s);R.atRule=s=>new Fn(s);R.decl=s=>new Pn(s);R.rule=s=>new Un(s);R.root=s=>new Bn(s);R.document=s=>new $n(s);R.CssSyntaxError=Au;R.Declaration=Pn;R.Container=Du;R.Processor=Ts;R.Document=$n;R.Comment=Ln;R.Warning=ku;R.AtRule=Fn;R.Result=Pu;R.Input=$u;R.Rule=Un;R.Root=Bn;R.Node=Uu;Ou.registerPostcss(R);var Bu=R;R.default=R;var L=Ja(Bu);L.stringify;L.fromJSON;L.plugin;L.parse;L.list;L.document;L.comment;L.atRule;L.rule;L.decl;L.root;L.CssSyntaxError;L.Declaration;L.Container;L.Processor;L.Document;L.Comment;L.Warning;L.AtRule;L.Result;L.Input;L.Rule;L.Root;L.Node;var Wu=Object.defineProperty,zu=(s,e,t)=>e in s?Wu(s,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):s[e]=t,ee=(s,e,t)=>zu(s,typeof e!="symbol"?e+"":e,t);function Vu(s){return s&&s.__esModule&&Object.prototype.hasOwnProperty.call(s,"default")?s.default:s}function Gu(s){if(s.__esModule)return s;var e=s.default;if(typeof e=="function"){var t=function r(){return this instanceof r?Reflect.construct(e,arguments,this.constructor):e.apply(this,arguments)};t.prototype=e.prototype}else t={};return Object.defineProperty(t,"__esModule",{value:!0}),Object.keys(s).forEach(function(r){var i=Object.getOwnPropertyDescriptor(s,r);Object.defineProperty(t,r,i.get?i:{enumerable:!0,get:function(){return s[r]}})}),t}var _s={exports:{}},P=String,Wn=function(){return{isColorSupported:!1,reset:P,bold:P,dim:P,italic:P,underline:P,inverse:P,hidden:P,strikethrough:P,black:P,red:P,green:P,yellow:P,blue:P,magenta:P,cyan:P,white:P,gray:P,bgBlack:P,bgRed:P,bgGreen:P,bgYellow:P,bgBlue:P,bgMagenta:P,bgCyan:P,bgWhite:P}};_s.exports=Wn();_s.exports.createColors=Wn;var ju=_s.exports,Yu={},Hu=Object.freeze(Object.defineProperty({__proto__:null,default:Yu},Symbol.toStringTag,{value:"Module"})),ie=Gu(Hu),wi=ju,bi=ie,Zr=class zn extends Error{constructor(e,t,r,i,n,o){super(e),this.name="CssSyntaxError",this.reason=e,n&&(this.file=n),i&&(this.source=i),o&&(this.plugin=o),typeof t<"u"&&typeof r<"u"&&(typeof t=="number"?(this.line=t,this.column=r):(this.line=t.line,this.column=t.column,this.endLine=r.line,this.endColumn=r.column)),this.setMessage(),Error.captureStackTrace&&Error.captureStackTrace(this,zn)}setMessage(){this.message=this.plugin?this.plugin+": ":"",this.message+=this.file?this.file:"",typeof this.line<"u"&&(this.message+=":"+this.line+":"+this.column),this.message+=": "+this.reason}showSourceCode(e){if(!this.source)return"";let t=this.source;e==null&&(e=wi.isColorSupported),bi&&e&&(t=bi(t));let r=t.split(/\r?\n/),i=Math.max(this.line-3,0),n=Math.min(this.line+2,r.length),o=String(n).length,l,a;if(e){let{bold:u,gray:c,red:h}=wi.createColors(!0);l=m=>u(h(m)),a=m=>c(m)}else l=a=u=>u;return r.slice(i,n).map((u,c)=>{let h=i+1+c,m=" "+(" "+h).slice(-o)+" | ";if(h===this.line){let g=a(m.replace(/\d/g," "))+u.slice(0,this.column-1).replace(/[^\t]/g," ");return l(">")+a(m)+u+` + `+g+l("^")}return" "+a(m)+u}).join(` +`)}toString(){let e=this.showSourceCode();return e&&(e=` + +`+e+` +`),this.name+": "+this.message+e}},ks=Zr;Zr.default=Zr;var nt={};nt.isClean=Symbol("isClean");nt.my=Symbol("my");var Si={after:` +`,beforeClose:` +`,beforeComment:` +`,beforeDecl:` +`,beforeOpen:" ",beforeRule:` +`,colon:": ",commentLeft:" ",commentRight:" ",emptyBody:"",indent:" ",semicolon:!1};function Zu(s){return s[0].toUpperCase()+s.slice(1)}var Xr=class{constructor(e){this.builder=e}atrule(e,t){let r="@"+e.name,i=e.params?this.rawValue(e,"params"):"";if(typeof e.raws.afterName<"u"?r+=e.raws.afterName:i&&(r+=" "),e.nodes)this.block(e,r+i);else{let n=(e.raws.between||"")+(t?";":"");this.builder(r+i+n,e)}}beforeAfter(e,t){let r;e.type==="decl"?r=this.raw(e,null,"beforeDecl"):e.type==="comment"?r=this.raw(e,null,"beforeComment"):t==="before"?r=this.raw(e,null,"beforeRule"):r=this.raw(e,null,"beforeClose");let i=e.parent,n=0;for(;i&&i.type!=="root";)n+=1,i=i.parent;if(r.includes(` +`)){let o=this.raw(e,null,"indent");if(o.length)for(let l=0;l0&&e.nodes[t].type==="comment";)t-=1;let r=this.raw(e,"semicolon");for(let i=0;i{if(i=a.raws[t],typeof i<"u")return!1})}return typeof i>"u"&&(i=Si[r]),o.rawCache[r]=i,i}rawBeforeClose(e){let t;return e.walk(r=>{if(r.nodes&&r.nodes.length>0&&typeof r.raws.after<"u")return t=r.raws.after,t.includes(` +`)&&(t=t.replace(/[^\n]+$/,"")),!1}),t&&(t=t.replace(/\S/g,"")),t}rawBeforeComment(e,t){let r;return e.walkComments(i=>{if(typeof i.raws.before<"u")return r=i.raws.before,r.includes(` +`)&&(r=r.replace(/[^\n]+$/,"")),!1}),typeof r>"u"?r=this.raw(t,null,"beforeDecl"):r&&(r=r.replace(/\S/g,"")),r}rawBeforeDecl(e,t){let r;return e.walkDecls(i=>{if(typeof i.raws.before<"u")return r=i.raws.before,r.includes(` +`)&&(r=r.replace(/[^\n]+$/,"")),!1}),typeof r>"u"?r=this.raw(t,null,"beforeRule"):r&&(r=r.replace(/\S/g,"")),r}rawBeforeOpen(e){let t;return e.walk(r=>{if(r.type!=="decl"&&(t=r.raws.between,typeof t<"u"))return!1}),t}rawBeforeRule(e){let t;return e.walk(r=>{if(r.nodes&&(r.parent!==e||e.first!==r)&&typeof r.raws.before<"u")return t=r.raws.before,t.includes(` +`)&&(t=t.replace(/[^\n]+$/,"")),!1}),t&&(t=t.replace(/\S/g,"")),t}rawColon(e){let t;return e.walkDecls(r=>{if(typeof r.raws.between<"u")return t=r.raws.between.replace(/[^\s:]/g,""),!1}),t}rawEmptyBody(e){let t;return e.walk(r=>{if(r.nodes&&r.nodes.length===0&&(t=r.raws.after,typeof t<"u"))return!1}),t}rawIndent(e){if(e.raws.indent)return e.raws.indent;let t;return e.walk(r=>{let i=r.parent;if(i&&i!==e&&i.parent&&i.parent===e&&typeof r.raws.before<"u"){let n=r.raws.before.split(` +`);return t=n[n.length-1],t=t.replace(/\S/g,""),!1}}),t}rawSemicolon(e){let t;return e.walk(r=>{if(r.nodes&&r.nodes.length&&r.last.type==="decl"&&(t=r.raws.semicolon,typeof t<"u"))return!1}),t}rawValue(e,t){let r=e[t],i=e.raws[t];return i&&i.value===r?i.raw:r}root(e){this.body(e),e.raws.after&&this.builder(e.raws.after)}rule(e){this.block(e,this.rawValue(e,"selector")),e.raws.ownSemicolon&&this.builder(e.raws.ownSemicolon,e,"end")}stringify(e,t){if(!this[e.type])throw new Error("Unknown AST node type "+e.type+". Maybe you need to change PostCSS stringifier.");this[e.type](e,t)}},Vn=Xr;Xr.default=Xr;var Xu=Vn;function Jr(s,e){new Xu(e).stringify(s)}var ar=Jr;Jr.default=Jr;var{isClean:vt,my:Ju}=nt,Ku=ks,Qu=Vn,qu=ar;function Kr(s,e){let t=new s.constructor;for(let r in s){if(!Object.prototype.hasOwnProperty.call(s,r)||r==="proxyCache")continue;let i=s[r],n=typeof i;r==="parent"&&n==="object"?e&&(t[r]=e):r==="source"?t[r]=i:Array.isArray(i)?t[r]=i.map(o=>Kr(o,t)):(n==="object"&&i!==null&&(i=Kr(i)),t[r]=i)}return t}var Qr=class{constructor(e={}){this.raws={},this[vt]=!1,this[Ju]=!0;for(let t in e)if(t==="nodes"){this.nodes=[];for(let r of e[t])typeof r.clone=="function"?this.append(r.clone()):this.append(r)}else this[t]=e[t]}addToError(e){if(e.postcssNode=this,e.stack&&this.source&&/\n\s{4}at /.test(e.stack)){let t=this.source;e.stack=e.stack.replace(/\n\s{4}at /,`$&${t.input.from}:${t.start.line}:${t.start.column}$&`)}return e}after(e){return this.parent.insertAfter(this,e),this}assign(e={}){for(let t in e)this[t]=e[t];return this}before(e){return this.parent.insertBefore(this,e),this}cleanRaws(e){delete this.raws.before,delete this.raws.after,e||delete this.raws.between}clone(e={}){let t=Kr(this);for(let r in e)t[r]=e[r];return t}cloneAfter(e={}){let t=this.clone(e);return this.parent.insertAfter(this,t),t}cloneBefore(e={}){let t=this.clone(e);return this.parent.insertBefore(this,t),t}error(e,t={}){if(this.source){let{end:r,start:i}=this.rangeBy(t);return this.source.input.error(e,{column:i.column,line:i.line},{column:r.column,line:r.line},t)}return new Ku(e)}getProxyProcessor(){return{get(e,t){return t==="proxyOf"?e:t==="root"?()=>e.root().toProxy():e[t]},set(e,t,r){return e[t]===r||(e[t]=r,(t==="prop"||t==="value"||t==="name"||t==="params"||t==="important"||t==="text")&&e.markDirty()),!0}}}markDirty(){if(this[vt]){this[vt]=!1;let e=this;for(;e=e.parent;)e[vt]=!1}}next(){if(!this.parent)return;let e=this.parent.index(this);return this.parent.nodes[e+1]}positionBy(e,t){let r=this.source.start;if(e.index)r=this.positionInside(e.index,t);else if(e.word){t=this.toString();let i=t.indexOf(e.word);i!==-1&&(r=this.positionInside(i,t))}return r}positionInside(e,t){let r=t||this.toString(),i=this.source.start.column,n=this.source.start.line;for(let o=0;otypeof a=="object"&&a.toJSON?a.toJSON(null,t):a);else if(typeof l=="object"&&l.toJSON)r[o]=l.toJSON(null,t);else if(o==="source"){let a=t.get(l.input);a==null&&(a=n,t.set(l.input,n),n++),r[o]={end:l.end,inputId:a,start:l.start}}else r[o]=l}return i&&(r.inputs=[...t.keys()].map(o=>o.toJSON())),r}toProxy(){return this.proxyCache||(this.proxyCache=new Proxy(this,this.getProxyProcessor())),this.proxyCache}toString(e=qu){e.stringify&&(e=e.stringify);let t="";return e(this,r=>{t+=r}),t}warn(e,t,r){let i={node:this};for(let n in r)i[n]=r[n];return e.warn(t,i)}get proxyOf(){return this}},lr=Qr;Qr.default=Qr;var ec=lr,qr=class extends ec{constructor(e){e&&typeof e.value<"u"&&typeof e.value!="string"&&(e={...e,value:String(e.value)}),super(e),this.type="decl"}get variable(){return this.prop.startsWith("--")||this.prop[0]==="$"}},ur=qr;qr.default=qr;var tc="useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict",rc=(s,e=21)=>(t=e)=>{let r="",i=t;for(;i--;)r+=s[Math.random()*s.length|0];return r},sc=(s=21)=>{let e="",t=s;for(;t--;)e+=tc[Math.random()*64|0];return e},ic={nanoid:sc,customAlphabet:rc},{SourceMapConsumer:vi,SourceMapGenerator:Ci}=ie,{existsSync:nc,readFileSync:oc}=ie,{dirname:Er,join:ac}=ie;function lc(s){return Buffer?Buffer.from(s,"base64").toString():window.atob(s)}var es=class{constructor(e,t){if(t.map===!1)return;this.loadAnnotation(e),this.inline=this.startWith(this.annotation,"data:");let r=t.map?t.map.prev:void 0,i=this.loadMap(t.from,r);!this.mapFile&&t.from&&(this.mapFile=t.from),this.mapFile&&(this.root=Er(this.mapFile)),i&&(this.text=i)}consumer(){return this.consumerCache||(this.consumerCache=new vi(this.text)),this.consumerCache}decodeInline(e){let t=/^data:application\/json;charset=utf-?8;base64,/,r=/^data:application\/json;base64,/,i=/^data:application\/json;charset=utf-?8,/,n=/^data:application\/json,/;if(i.test(e)||n.test(e))return decodeURIComponent(e.substr(RegExp.lastMatch.length));if(t.test(e)||r.test(e))return lc(e.substr(RegExp.lastMatch.length));let o=e.match(/data:application\/json;([^,]+),/)[1];throw new Error("Unsupported source map encoding "+o)}getAnnotationURL(e){return e.replace(/^\/\*\s*# sourceMappingURL=/,"").trim()}isMap(e){return typeof e!="object"?!1:typeof e.mappings=="string"||typeof e._mappings=="string"||Array.isArray(e.sections)}loadAnnotation(e){let t=e.match(/\/\*\s*# sourceMappingURL=/gm);if(!t)return;let r=e.lastIndexOf(t.pop()),i=e.indexOf("*/",r);r>-1&&i>-1&&(this.annotation=this.getAnnotationURL(e.substring(r,i)))}loadFile(e){if(this.root=Er(e),nc(e))return this.mapFile=e,oc(e,"utf-8").toString().trim()}loadMap(e,t){if(t===!1)return!1;if(t){if(typeof t=="string")return t;if(typeof t=="function"){let r=t(e);if(r){let i=this.loadFile(r);if(!i)throw new Error("Unable to load previous source map: "+r.toString());return i}}else{if(t instanceof vi)return Ci.fromSourceMap(t).toString();if(t instanceof Ci)return t.toString();if(this.isMap(t))return JSON.stringify(t);throw new Error("Unsupported previous source map format: "+t.toString())}}else{if(this.inline)return this.decodeInline(this.annotation);if(this.annotation){let r=this.annotation;return e&&(r=ac(Er(e),r)),this.loadFile(r)}}}startWith(e,t){return e?e.substr(0,t.length)===t:!1}withContent(){return!!(this.consumer().sourcesContent&&this.consumer().sourcesContent.length>0)}},Gn=es;es.default=es;var{SourceMapConsumer:uc,SourceMapGenerator:cc}=ie,{fileURLToPath:Ii,pathToFileURL:Ct}=ie,{isAbsolute:ts,resolve:rs}=ie,{nanoid:hc}=ic,xr=ie,Ei=ks,fc=Gn,Mr=Symbol("fromOffsetCache"),dc=!!(uc&&cc),xi=!!(rs&&ts),Zt=class{constructor(e,t={}){if(e===null||typeof e>"u"||typeof e=="object"&&!e.toString)throw new Error(`PostCSS received ${e} instead of CSS string`);if(this.css=e.toString(),this.css[0]==="\uFEFF"||this.css[0]==="\uFFFE"?(this.hasBOM=!0,this.css=this.css.slice(1)):this.hasBOM=!1,t.from&&(!xi||/^\w+:\/\//.test(t.from)||ts(t.from)?this.file=t.from:this.file=rs(t.from)),xi&&dc){let r=new fc(this.css,t);if(r.text){this.map=r;let i=r.consumer().file;!this.file&&i&&(this.file=this.mapResolve(i))}}this.file||(this.id=""),this.map&&(this.map.file=this.from)}error(e,t,r,i={}){let n,o,l;if(t&&typeof t=="object"){let u=t,c=r;if(typeof u.offset=="number"){let h=this.fromOffset(u.offset);t=h.line,r=h.col}else t=u.line,r=u.column;if(typeof c.offset=="number"){let h=this.fromOffset(c.offset);o=h.line,l=h.col}else o=c.line,l=c.column}else if(!r){let u=this.fromOffset(t);t=u.line,r=u.col}let a=this.origin(t,r,o,l);return a?n=new Ei(e,a.endLine===void 0?a.line:{column:a.column,line:a.line},a.endLine===void 0?a.column:{column:a.endColumn,line:a.endLine},a.source,a.file,i.plugin):n=new Ei(e,o===void 0?t:{column:r,line:t},o===void 0?r:{column:l,line:o},this.css,this.file,i.plugin),n.input={column:r,endColumn:l,endLine:o,line:t,source:this.css},this.file&&(Ct&&(n.input.url=Ct(this.file).toString()),n.input.file=this.file),n}fromOffset(e){let t,r;if(this[Mr])r=this[Mr];else{let n=this.css.split(` +`);r=new Array(n.length);let o=0;for(let l=0,a=n.length;l=t)i=r.length-1;else{let n=r.length-2,o;for(;i>1),e=r[o+1])i=o+1;else{i=o;break}}return{col:e-r[i]+1,line:i+1}}mapResolve(e){return/^\w+:\/\//.test(e)?e:rs(this.map.consumer().sourceRoot||this.map.root||".",e)}origin(e,t,r,i){if(!this.map)return!1;let n=this.map.consumer(),o=n.originalPositionFor({column:t,line:e});if(!o.source)return!1;let l;typeof r=="number"&&(l=n.originalPositionFor({column:i,line:r}));let a;ts(o.source)?a=Ct(o.source):a=new URL(o.source,this.map.consumer().sourceRoot||Ct(this.map.mapFile));let u={column:o.column,endColumn:l&&l.column,endLine:l&&l.line,line:o.line,url:a.toString()};if(a.protocol==="file:")if(Ii)u.file=Ii(a);else throw new Error("file: protocol is not available in this PostCSS build");let c=n.sourceContentFor(o.source);return c&&(u.source=c),u}toJSON(){let e={};for(let t of["hasBOM","css","file","id"])this[t]!=null&&(e[t]=this[t]);return this.map&&(e.map={...this.map},e.map.consumerCache&&(e.map.consumerCache=void 0)),e}get from(){return this.file||this.id}},cr=Zt;Zt.default=Zt;xr&&xr.registerInput&&xr.registerInput(Zt);var{SourceMapConsumer:jn,SourceMapGenerator:Pt}=ie,{dirname:$t,relative:Yn,resolve:Hn,sep:Zn}=ie,{pathToFileURL:Mi}=ie,pc=cr,mc=!!(jn&&Pt),gc=!!($t&&Hn&&Yn&&Zn),yc=class{constructor(e,t,r,i){this.stringify=e,this.mapOpts=r.map||{},this.root=t,this.opts=r,this.css=i,this.originalCSS=i,this.usesFileUrls=!this.mapOpts.from&&this.mapOpts.absolute,this.memoizedFileURLs=new Map,this.memoizedPaths=new Map,this.memoizedURLs=new Map}addAnnotation(){let e;this.isInline()?e="data:application/json;base64,"+this.toBase64(this.map.toString()):typeof this.mapOpts.annotation=="string"?e=this.mapOpts.annotation:typeof this.mapOpts.annotation=="function"?e=this.mapOpts.annotation(this.opts.to,this.root):e=this.outputFile()+".map";let t=` +`;this.css.includes(`\r +`)&&(t=`\r +`),this.css+=t+"/*# sourceMappingURL="+e+" */"}applyPrevMaps(){for(let e of this.previous()){let t=this.toUrl(this.path(e.file)),r=e.root||$t(e.file),i;this.mapOpts.sourcesContent===!1?(i=new jn(e.text),i.sourcesContent&&(i.sourcesContent=null)):i=e.consumer(),this.map.applySourceMap(i,t,this.toUrl(this.path(r)))}}clearAnnotation(){if(this.mapOpts.annotation!==!1)if(this.root){let e;for(let t=this.root.nodes.length-1;t>=0;t--)e=this.root.nodes[t],e.type==="comment"&&e.text.indexOf("# sourceMappingURL=")===0&&this.root.removeChild(t)}else this.css&&(this.css=this.css.replace(/\n*?\/\*#[\S\s]*?\*\/$/gm,""))}generate(){if(this.clearAnnotation(),gc&&mc&&this.isMap())return this.generateMap();{let e="";return this.stringify(this.root,t=>{e+=t}),[e]}}generateMap(){if(this.root)this.generateString();else if(this.previous().length===1){let e=this.previous()[0].consumer();e.file=this.outputFile(),this.map=Pt.fromSourceMap(e,{ignoreInvalidMapping:!0})}else this.map=new Pt({file:this.outputFile(),ignoreInvalidMapping:!0}),this.map.addMapping({generated:{column:0,line:1},original:{column:0,line:1},source:this.opts.from?this.toUrl(this.path(this.opts.from)):""});return this.isSourcesContent()&&this.setSourcesContent(),this.root&&this.previous().length>0&&this.applyPrevMaps(),this.isAnnotation()&&this.addAnnotation(),this.isInline()?[this.css]:[this.css,this.map]}generateString(){this.css="",this.map=new Pt({file:this.outputFile(),ignoreInvalidMapping:!0});let e=1,t=1,r="",i={generated:{column:0,line:0},original:{column:0,line:0},source:""},n,o;this.stringify(this.root,(l,a,u)=>{if(this.css+=l,a&&u!=="end"&&(i.generated.line=e,i.generated.column=t-1,a.source&&a.source.start?(i.source=this.sourcePath(a),i.original.line=a.source.start.line,i.original.column=a.source.start.column-1,this.map.addMapping(i)):(i.source=r,i.original.line=1,i.original.column=0,this.map.addMapping(i))),n=l.match(/\n/g),n?(e+=n.length,o=l.lastIndexOf(` +`),t=l.length-o):t+=l.length,a&&u!=="start"){let c=a.parent||{raws:{}};(!(a.type==="decl"||a.type==="atrule"&&!a.nodes)||a!==c.last||c.raws.semicolon)&&(a.source&&a.source.end?(i.source=this.sourcePath(a),i.original.line=a.source.end.line,i.original.column=a.source.end.column-1,i.generated.line=e,i.generated.column=t-2,this.map.addMapping(i)):(i.source=r,i.original.line=1,i.original.column=0,i.generated.line=e,i.generated.column=t-1,this.map.addMapping(i)))}})}isAnnotation(){return this.isInline()?!0:typeof this.mapOpts.annotation<"u"?this.mapOpts.annotation:this.previous().length?this.previous().some(e=>e.annotation):!0}isInline(){if(typeof this.mapOpts.inline<"u")return this.mapOpts.inline;let e=this.mapOpts.annotation;return typeof e<"u"&&e!==!0?!1:this.previous().length?this.previous().some(t=>t.inline):!0}isMap(){return typeof this.opts.map<"u"?!!this.opts.map:this.previous().length>0}isSourcesContent(){return typeof this.mapOpts.sourcesContent<"u"?this.mapOpts.sourcesContent:this.previous().length?this.previous().some(e=>e.withContent()):!0}outputFile(){return this.opts.to?this.path(this.opts.to):this.opts.from?this.path(this.opts.from):"to.css"}path(e){if(this.mapOpts.absolute||e.charCodeAt(0)===60||/^\w+:\/\//.test(e))return e;let t=this.memoizedPaths.get(e);if(t)return t;let r=this.opts.to?$t(this.opts.to):".";typeof this.mapOpts.annotation=="string"&&(r=$t(Hn(r,this.mapOpts.annotation)));let i=Yn(r,e);return this.memoizedPaths.set(e,i),i}previous(){if(!this.previousMaps)if(this.previousMaps=[],this.root)this.root.walk(e=>{if(e.source&&e.source.input.map){let t=e.source.input.map;this.previousMaps.includes(t)||this.previousMaps.push(t)}});else{let e=new pc(this.originalCSS,this.opts);e.map&&this.previousMaps.push(e.map)}return this.previousMaps}setSourcesContent(){let e={};if(this.root)this.root.walk(t=>{if(t.source){let r=t.source.input.from;if(r&&!e[r]){e[r]=!0;let i=this.usesFileUrls?this.toFileUrl(r):this.toUrl(this.path(r));this.map.setSourceContent(i,t.source.input.css)}}});else if(this.css){let t=this.opts.from?this.toUrl(this.path(this.opts.from)):"";this.map.setSourceContent(t,this.css)}}sourcePath(e){return this.mapOpts.from?this.toUrl(this.mapOpts.from):this.usesFileUrls?this.toFileUrl(e.source.input.from):this.toUrl(this.path(e.source.input.from))}toBase64(e){return Buffer?Buffer.from(e).toString("base64"):window.btoa(unescape(encodeURIComponent(e)))}toFileUrl(e){let t=this.memoizedFileURLs.get(e);if(t)return t;if(Mi){let r=Mi(e).toString();return this.memoizedFileURLs.set(e,r),r}else throw new Error("`map.absolute` option is not available in this PostCSS build")}toUrl(e){let t=this.memoizedURLs.get(e);if(t)return t;Zn==="\\"&&(e=e.replace(/\\/g,"/"));let r=encodeURI(e).replace(/[#?]/g,encodeURIComponent);return this.memoizedURLs.set(e,r),r}},Xn=yc,wc=lr,ss=class extends wc{constructor(e){super(e),this.type="comment"}},hr=ss;ss.default=ss;var{isClean:Jn,my:Kn}=nt,Qn=ur,qn=hr,bc=lr,eo,Ps,$s,to;function ro(s){return s.map(e=>(e.nodes&&(e.nodes=ro(e.nodes)),delete e.source,e))}function so(s){if(s[Jn]=!1,s.proxyOf.nodes)for(let e of s.proxyOf.nodes)so(e)}var he=class io extends bc{append(...e){for(let t of e){let r=this.normalize(t,this.last);for(let i of r)this.proxyOf.nodes.push(i)}return this.markDirty(),this}cleanRaws(e){if(super.cleanRaws(e),this.nodes)for(let t of this.nodes)t.cleanRaws(e)}each(e){if(!this.proxyOf.nodes)return;let t=this.getIterator(),r,i;for(;this.indexes[t]e[t](...r.map(i=>typeof i=="function"?(n,o)=>i(n.toProxy(),o):i)):t==="every"||t==="some"?r=>e[t]((i,...n)=>r(i.toProxy(),...n)):t==="root"?()=>e.root().toProxy():t==="nodes"?e.nodes.map(r=>r.toProxy()):t==="first"||t==="last"?e[t].toProxy():e[t]:e[t]},set(e,t,r){return e[t]===r||(e[t]=r,(t==="name"||t==="params"||t==="selector")&&e.markDirty()),!0}}}index(e){return typeof e=="number"?e:(e.proxyOf&&(e=e.proxyOf),this.proxyOf.nodes.indexOf(e))}insertAfter(e,t){let r=this.index(e),i=this.normalize(t,this.proxyOf.nodes[r]).reverse();r=this.index(e);for(let o of i)this.proxyOf.nodes.splice(r+1,0,o);let n;for(let o in this.indexes)n=this.indexes[o],r"u")e=[];else if(Array.isArray(e)){e=e.slice(0);for(let i of e)i.parent&&i.parent.removeChild(i,"ignore")}else if(e.type==="root"&&this.type!=="document"){e=e.nodes.slice(0);for(let i of e)i.parent&&i.parent.removeChild(i,"ignore")}else if(e.type)e=[e];else if(e.prop){if(typeof e.value>"u")throw new Error("Value field is missed in node creation");typeof e.value!="string"&&(e.value=String(e.value)),e=[new Qn(e)]}else if(e.selector)e=[new Ps(e)];else if(e.name)e=[new $s(e)];else if(e.text)e=[new qn(e)];else throw new Error("Unknown node type in node creation");return e.map(i=>(i[Kn]||io.rebuild(i),i=i.proxyOf,i.parent&&i.parent.removeChild(i),i[Jn]&&so(i),typeof i.raws.before>"u"&&t&&typeof t.raws.before<"u"&&(i.raws.before=t.raws.before.replace(/\S/g,"")),i.parent=this.proxyOf,i))}prepend(...e){e=e.reverse();for(let t of e){let r=this.normalize(t,this.first,"prepend").reverse();for(let i of r)this.proxyOf.nodes.unshift(i);for(let i in this.indexes)this.indexes[i]=this.indexes[i]+r.length}return this.markDirty(),this}push(e){return e.parent=this,this.proxyOf.nodes.push(e),this}removeAll(){for(let e of this.proxyOf.nodes)e.parent=void 0;return this.proxyOf.nodes=[],this.markDirty(),this}removeChild(e){e=this.index(e),this.proxyOf.nodes[e].parent=void 0,this.proxyOf.nodes.splice(e,1);let t;for(let r in this.indexes)t=this.indexes[r],t>=e&&(this.indexes[r]=t-1);return this.markDirty(),this}replaceValues(e,t,r){return r||(r=t,t={}),this.walkDecls(i=>{t.props&&!t.props.includes(i.prop)||t.fast&&!i.value.includes(t.fast)||(i.value=i.value.replace(e,r))}),this.markDirty(),this}some(e){return this.nodes.some(e)}walk(e){return this.each((t,r)=>{let i;try{i=e(t,r)}catch(n){throw t.addToError(n)}return i!==!1&&t.walk&&(i=t.walk(e)),i})}walkAtRules(e,t){return t?e instanceof RegExp?this.walk((r,i)=>{if(r.type==="atrule"&&e.test(r.name))return t(r,i)}):this.walk((r,i)=>{if(r.type==="atrule"&&r.name===e)return t(r,i)}):(t=e,this.walk((r,i)=>{if(r.type==="atrule")return t(r,i)}))}walkComments(e){return this.walk((t,r)=>{if(t.type==="comment")return e(t,r)})}walkDecls(e,t){return t?e instanceof RegExp?this.walk((r,i)=>{if(r.type==="decl"&&e.test(r.prop))return t(r,i)}):this.walk((r,i)=>{if(r.type==="decl"&&r.prop===e)return t(r,i)}):(t=e,this.walk((r,i)=>{if(r.type==="decl")return t(r,i)}))}walkRules(e,t){return t?e instanceof RegExp?this.walk((r,i)=>{if(r.type==="rule"&&e.test(r.selector))return t(r,i)}):this.walk((r,i)=>{if(r.type==="rule"&&r.selector===e)return t(r,i)}):(t=e,this.walk((r,i)=>{if(r.type==="rule")return t(r,i)}))}get first(){if(this.proxyOf.nodes)return this.proxyOf.nodes[0]}get last(){if(this.proxyOf.nodes)return this.proxyOf.nodes[this.proxyOf.nodes.length-1]}};he.registerParse=s=>{eo=s};he.registerRule=s=>{Ps=s};he.registerAtRule=s=>{$s=s};he.registerRoot=s=>{to=s};var Ce=he;he.default=he;he.rebuild=s=>{s.type==="atrule"?Object.setPrototypeOf(s,$s.prototype):s.type==="rule"?Object.setPrototypeOf(s,Ps.prototype):s.type==="decl"?Object.setPrototypeOf(s,Qn.prototype):s.type==="comment"?Object.setPrototypeOf(s,qn.prototype):s.type==="root"&&Object.setPrototypeOf(s,to.prototype),s[Kn]=!0,s.nodes&&s.nodes.forEach(e=>{he.rebuild(e)})};var Sc=Ce,no,oo,Qe=class extends Sc{constructor(e){super({type:"document",...e}),this.nodes||(this.nodes=[])}toResult(e={}){return new no(new oo,this,e).stringify()}};Qe.registerLazyResult=s=>{no=s};Qe.registerProcessor=s=>{oo=s};var Ls=Qe;Qe.default=Qe;var Ni={},ao=function(e){Ni[e]||(Ni[e]=!0,typeof console<"u"&&console.warn&&console.warn(e))},is=class{constructor(e,t={}){if(this.type="warning",this.text=e,t.node&&t.node.source){let r=t.node.rangeBy(t);this.line=r.start.line,this.column=r.start.column,this.endLine=r.end.line,this.endColumn=r.end.column}for(let r in t)this[r]=t[r]}toString(){return this.node?this.node.error(this.text,{index:this.index,plugin:this.plugin,word:this.word}).message:this.plugin?this.plugin+": "+this.text:this.text}},lo=is;is.default=is;var vc=lo,ns=class{constructor(e,t,r){this.processor=e,this.messages=[],this.root=t,this.opts=r,this.css=void 0,this.map=void 0}toString(){return this.css}warn(e,t={}){t.plugin||this.lastPlugin&&this.lastPlugin.postcssPlugin&&(t.plugin=this.lastPlugin.postcssPlugin);let r=new vc(e,t);return this.messages.push(r),r}warnings(){return this.messages.filter(e=>e.type==="warning")}get content(){return this.css}},Fs=ns;ns.default=ns;var Nr=39,Ri=34,It=92,Ai=47,Et=10,We=32,xt=12,Mt=9,Nt=13,Cc=91,Ic=93,Ec=40,xc=41,Mc=123,Nc=125,Rc=59,Ac=42,Oc=58,Dc=64,Rt=/[\t\n\f\r "#'()/;[\\\]{}]/g,At=/[\t\n\f\r !"#'():;@[\\\]{}]|\/(?=\*)/g,Tc=/.[\r\n"'(/\\]/,Oi=/[\da-f]/i,_c=function(e,t={}){let r=e.css.valueOf(),i=t.ignoreErrors,n,o,l,a,u,c,h,m,g,p,d=r.length,f=0,b=[],S=[];function y(){return f}function v($){throw e.error("Unclosed "+$,f)}function O(){return S.length===0&&f>=d}function T($){if(S.length)return S.pop();if(f>=d)return;let z=$?$.ignoreUnclosed:!1;switch(n=r.charCodeAt(f),n){case Et:case We:case Mt:case Nt:case xt:{o=f;do o+=1,n=r.charCodeAt(o);while(n===We||n===Et||n===Mt||n===Nt||n===xt);p=["space",r.slice(f,o)],f=o-1;break}case Cc:case Ic:case Mc:case Nc:case Oc:case Rc:case xc:{let Y=String.fromCharCode(n);p=[Y,Y,f];break}case Ec:{if(m=b.length?b.pop()[1]:"",g=r.charCodeAt(f+1),m==="url"&&g!==Nr&&g!==Ri&&g!==We&&g!==Et&&g!==Mt&&g!==xt&&g!==Nt){o=f;do{if(c=!1,o=r.indexOf(")",o+1),o===-1)if(i||z){o=f;break}else v("bracket");for(h=o;r.charCodeAt(h-1)===It;)h-=1,c=!c}while(c);p=["brackets",r.slice(f,o+1),f,o],f=o}else o=r.indexOf(")",f+1),a=r.slice(f,o+1),o===-1||Tc.test(a)?p=["(","(",f]:(p=["brackets",a,f,o],f=o);break}case Nr:case Ri:{l=n===Nr?"'":'"',o=f;do{if(c=!1,o=r.indexOf(l,o+1),o===-1)if(i||z){o=f+1;break}else v("string");for(h=o;r.charCodeAt(h-1)===It;)h-=1,c=!c}while(c);p=["string",r.slice(f,o+1),f,o],f=o;break}case Dc:{Rt.lastIndex=f+1,Rt.test(r),Rt.lastIndex===0?o=r.length-1:o=Rt.lastIndex-2,p=["at-word",r.slice(f,o+1),f,o],f=o;break}case It:{for(o=f,u=!0;r.charCodeAt(o+1)===It;)o+=1,u=!u;if(n=r.charCodeAt(o+1),u&&n!==Ai&&n!==We&&n!==Et&&n!==Mt&&n!==Nt&&n!==xt&&(o+=1,Oi.test(r.charAt(o)))){for(;Oi.test(r.charAt(o+1));)o+=1;r.charCodeAt(o+1)===We&&(o+=1)}p=["word",r.slice(f,o+1),f,o],f=o;break}default:{n===Ai&&r.charCodeAt(f+1)===Ac?(o=r.indexOf("*/",f+2)+1,o===0&&(i||z?o=r.length:v("comment")),p=["comment",r.slice(f,o+1),f,o],f=o):(At.lastIndex=f+1,At.test(r),At.lastIndex===0?o=r.length-1:o=At.lastIndex-2,p=["word",r.slice(f,o+1),f,o],b.push(p),f=o);break}}return f++,p}function V($){S.push($)}return{back:V,endOfFile:O,nextToken:T,position:y}},uo=Ce,Xt=class extends uo{constructor(e){super(e),this.type="atrule"}append(...e){return this.proxyOf.nodes||(this.nodes=[]),super.append(...e)}prepend(...e){return this.proxyOf.nodes||(this.nodes=[]),super.prepend(...e)}},Us=Xt;Xt.default=Xt;uo.registerAtRule(Xt);var co=Ce,ho,fo,_e=class extends co{constructor(e){super(e),this.type="root",this.nodes||(this.nodes=[])}normalize(e,t,r){let i=super.normalize(e);if(t){if(r==="prepend")this.nodes.length>1?t.raws.before=this.nodes[1].raws.before:delete t.raws.before;else if(this.first!==t)for(let n of i)n.raws.before=t.raws.before}return i}removeChild(e,t){let r=this.index(e);return!t&&r===0&&this.nodes.length>1&&(this.nodes[1].raws.before=this.nodes[r].raws.before),super.removeChild(e)}toResult(e={}){return new ho(new fo,this,e).stringify()}};_e.registerLazyResult=s=>{ho=s};_e.registerProcessor=s=>{fo=s};var ot=_e;_e.default=_e;co.registerRoot(_e);var qe={comma(s){return qe.split(s,[","],!0)},space(s){let e=[" ",` +`," "];return qe.split(s,e)},split(s,e,t){let r=[],i="",n=!1,o=0,l=!1,a="",u=!1;for(let c of s)u?u=!1:c==="\\"?u=!0:l?c===a&&(l=!1):c==='"'||c==="'"?(l=!0,a=c):c==="("?o+=1:c===")"?o>0&&(o-=1):o===0&&e.includes(c)&&(n=!0),n?(i!==""&&r.push(i.trim()),i="",n=!1):i+=c;return(t||i!=="")&&r.push(i.trim()),r}},po=qe;qe.default=qe;var mo=Ce,kc=po,Jt=class extends mo{constructor(e){super(e),this.type="rule",this.nodes||(this.nodes=[])}get selectors(){return kc.comma(this.selector)}set selectors(e){let t=this.selector?this.selector.match(/,\s*/):null,r=t?t[0]:","+this.raw("between","beforeOpen");this.selector=e.join(r)}},Bs=Jt;Jt.default=Jt;mo.registerRule(Jt);var Pc=ur,$c=_c,Lc=hr,Fc=Us,Uc=ot,Di=Bs,Ti={empty:!0,space:!0};function Bc(s){for(let e=s.length-1;e>=0;e--){let t=s[e],r=t[3]||t[2];if(r)return r}}var Wc=class{constructor(e){this.input=e,this.root=new Uc,this.current=this.root,this.spaces="",this.semicolon=!1,this.createTokenizer(),this.root.source={input:e,start:{column:1,line:1,offset:0}}}atrule(e){let t=new Fc;t.name=e[1].slice(1),t.name===""&&this.unnamedAtrule(t,e),this.init(t,e[2]);let r,i,n,o=!1,l=!1,a=[],u=[];for(;!this.tokenizer.endOfFile();){if(e=this.tokenizer.nextToken(),r=e[0],r==="("||r==="["?u.push(r==="("?")":"]"):r==="{"&&u.length>0?u.push("}"):r===u[u.length-1]&&u.pop(),u.length===0)if(r===";"){t.source.end=this.getPosition(e[2]),t.source.end.offset++,this.semicolon=!0;break}else if(r==="{"){l=!0;break}else if(r==="}"){if(a.length>0){for(n=a.length-1,i=a[n];i&&i[0]==="space";)i=a[--n];i&&(t.source.end=this.getPosition(i[3]||i[2]),t.source.end.offset++)}this.end(e);break}else a.push(e);else a.push(e);if(this.tokenizer.endOfFile()){o=!0;break}}t.raws.between=this.spacesAndCommentsFromEnd(a),a.length?(t.raws.afterName=this.spacesAndCommentsFromStart(a),this.raw(t,"params",a),o&&(e=a[a.length-1],t.source.end=this.getPosition(e[3]||e[2]),t.source.end.offset++,this.spaces=t.raws.between,t.raws.between="")):(t.raws.afterName="",t.params=""),l&&(t.nodes=[],this.current=t)}checkMissedSemicolon(e){let t=this.colon(e);if(t===!1)return;let r=0,i;for(let n=t-1;n>=0&&(i=e[n],!(i[0]!=="space"&&(r+=1,r===2)));n--);throw this.input.error("Missed semicolon",i[0]==="word"?i[3]+1:i[2])}colon(e){let t=0,r,i,n;for(let[o,l]of e.entries()){if(r=l,i=r[0],i==="("&&(t+=1),i===")"&&(t-=1),t===0&&i===":")if(!n)this.doubleColon(r);else{if(n[0]==="word"&&n[1]==="progid")continue;return o}n=r}return!1}comment(e){let t=new Lc;this.init(t,e[2]),t.source.end=this.getPosition(e[3]||e[2]),t.source.end.offset++;let r=e[1].slice(2,-2);if(/^\s*$/.test(r))t.text="",t.raws.left=r,t.raws.right="";else{let i=r.match(/^(\s*)([^]*\S)(\s*)$/);t.text=i[2],t.raws.left=i[1],t.raws.right=i[3]}}createTokenizer(){this.tokenizer=$c(this.input)}decl(e,t){let r=new Pc;this.init(r,e[0][2]);let i=e[e.length-1];for(i[0]===";"&&(this.semicolon=!0,e.pop()),r.source.end=this.getPosition(i[3]||i[2]||Bc(e)),r.source.end.offset++;e[0][0]!=="word";)e.length===1&&this.unknownWord(e),r.raws.before+=e.shift()[1];for(r.source.start=this.getPosition(e[0][2]),r.prop="";e.length;){let u=e[0][0];if(u===":"||u==="space"||u==="comment")break;r.prop+=e.shift()[1]}r.raws.between="";let n;for(;e.length;)if(n=e.shift(),n[0]===":"){r.raws.between+=n[1];break}else n[0]==="word"&&/\w/.test(n[1])&&this.unknownWord([n]),r.raws.between+=n[1];(r.prop[0]==="_"||r.prop[0]==="*")&&(r.raws.before+=r.prop[0],r.prop=r.prop.slice(1));let o=[],l;for(;e.length&&(l=e[0][0],!(l!=="space"&&l!=="comment"));)o.push(e.shift());this.precheckMissedSemicolon(e);for(let u=e.length-1;u>=0;u--){if(n=e[u],n[1].toLowerCase()==="!important"){r.important=!0;let c=this.stringFrom(e,u);c=this.spacesFromEnd(e)+c,c!==" !important"&&(r.raws.important=c);break}else if(n[1].toLowerCase()==="important"){let c=e.slice(0),h="";for(let m=u;m>0;m--){let g=c[m][0];if(h.trim().indexOf("!")===0&&g!=="space")break;h=c.pop()[1]+h}h.trim().indexOf("!")===0&&(r.important=!0,r.raws.important=h,e=c)}if(n[0]!=="space"&&n[0]!=="comment")break}e.some(u=>u[0]!=="space"&&u[0]!=="comment")&&(r.raws.between+=o.map(u=>u[1]).join(""),o=[]),this.raw(r,"value",o.concat(e),t),r.value.includes(":")&&!t&&this.checkMissedSemicolon(e)}doubleColon(e){throw this.input.error("Double colon",{offset:e[2]},{offset:e[2]+e[1].length})}emptyRule(e){let t=new Di;this.init(t,e[2]),t.selector="",t.raws.between="",this.current=t}end(e){this.current.nodes&&this.current.nodes.length&&(this.current.raws.semicolon=this.semicolon),this.semicolon=!1,this.current.raws.after=(this.current.raws.after||"")+this.spaces,this.spaces="",this.current.parent?(this.current.source.end=this.getPosition(e[2]),this.current.source.end.offset++,this.current=this.current.parent):this.unexpectedClose(e)}endFile(){this.current.parent&&this.unclosedBlock(),this.current.nodes&&this.current.nodes.length&&(this.current.raws.semicolon=this.semicolon),this.current.raws.after=(this.current.raws.after||"")+this.spaces,this.root.source.end=this.getPosition(this.tokenizer.position())}freeSemicolon(e){if(this.spaces+=e[1],this.current.nodes){let t=this.current.nodes[this.current.nodes.length-1];t&&t.type==="rule"&&!t.raws.ownSemicolon&&(t.raws.ownSemicolon=this.spaces,this.spaces="")}}getPosition(e){let t=this.input.fromOffset(e);return{column:t.col,line:t.line,offset:e}}init(e,t){this.current.push(e),e.source={input:this.input,start:this.getPosition(t)},e.raws.before=this.spaces,this.spaces="",e.type!=="comment"&&(this.semicolon=!1)}other(e){let t=!1,r=null,i=!1,n=null,o=[],l=e[1].startsWith("--"),a=[],u=e;for(;u;){if(r=u[0],a.push(u),r==="("||r==="[")n||(n=u),o.push(r==="("?")":"]");else if(l&&i&&r==="{")n||(n=u),o.push("}");else if(o.length===0)if(r===";")if(i){this.decl(a,l);return}else break;else if(r==="{"){this.rule(a);return}else if(r==="}"){this.tokenizer.back(a.pop()),t=!0;break}else r===":"&&(i=!0);else r===o[o.length-1]&&(o.pop(),o.length===0&&(n=null));u=this.tokenizer.nextToken()}if(this.tokenizer.endOfFile()&&(t=!0),o.length>0&&this.unclosedBracket(n),t&&i){if(!l)for(;a.length&&(u=a[a.length-1][0],!(u!=="space"&&u!=="comment"));)this.tokenizer.back(a.pop());this.decl(a,l)}else this.unknownWord(a)}parse(){let e;for(;!this.tokenizer.endOfFile();)switch(e=this.tokenizer.nextToken(),e[0]){case"space":this.spaces+=e[1];break;case";":this.freeSemicolon(e);break;case"}":this.end(e);break;case"comment":this.comment(e);break;case"at-word":this.atrule(e);break;case"{":this.emptyRule(e);break;default:this.other(e);break}this.endFile()}precheckMissedSemicolon(){}raw(e,t,r,i){let n,o,l=r.length,a="",u=!0,c,h;for(let m=0;mg+p[1],"");e.raws[t]={raw:m,value:a}}e[t]=a}rule(e){e.pop();let t=new Di;this.init(t,e[0][2]),t.raws.between=this.spacesAndCommentsFromEnd(e),this.raw(t,"selector",e),this.current=t}spacesAndCommentsFromEnd(e){let t,r="";for(;e.length&&(t=e[e.length-1][0],!(t!=="space"&&t!=="comment"));)r=e.pop()[1]+r;return r}spacesAndCommentsFromStart(e){let t,r="";for(;e.length&&(t=e[0][0],!(t!=="space"&&t!=="comment"));)r+=e.shift()[1];return r}spacesFromEnd(e){let t,r="";for(;e.length&&(t=e[e.length-1][0],t==="space");)r=e.pop()[1]+r;return r}stringFrom(e,t){let r="";for(let i=t;ios(e)),s}var as={},Pe=class yo{constructor(e,t,r){this.stringified=!1,this.processed=!1;let i;if(typeof t=="object"&&t!==null&&(t.type==="root"||t.type==="document"))i=os(t);else if(t instanceof yo||t instanceof _i)i=os(t.root),t.map&&(typeof r.map>"u"&&(r.map={}),r.map.inline||(r.map.inline=!1),r.map.prev=t.map);else{let n=Qc;r.syntax&&(n=r.syntax.parse),r.parser&&(n=r.parser),n.parse&&(n=n.parse);try{i=n(t,r)}catch(o){this.processed=!0,this.error=o}i&&!i[Yc]&&Xc.rebuild(i)}this.result=new _i(e,i,r),this.helpers={...as,postcss:as,result:this.result},this.plugins=this.processor.plugins.map(n=>typeof n=="object"&&n.prepare?{...n,...n.prepare(this.result)}:n)}async(){return this.error?Promise.reject(this.error):this.processed?Promise.resolve(this.result):(this.processing||(this.processing=this.runAsync()),this.processing)}catch(e){return this.async().catch(e)}finally(e){return this.async().then(e,e)}getAsyncError(){throw new Error("Use process(css).then(cb) to work with async plugins")}handleError(e,t){let r=this.result.lastPlugin;try{if(t&&t.addToError(e),this.error=e,e.name==="CssSyntaxError"&&!e.plugin)e.plugin=r.postcssPlugin,e.setMessage();else if(r.postcssVersion&&process.env.NODE_ENV!=="production"){let i=r.postcssPlugin,n=r.postcssVersion,o=this.result.processor.version,l=n.split("."),a=o.split(".");(l[0]!==a[0]||parseInt(l[1])>parseInt(a[1]))&&console.error("Unknown error from PostCSS plugin. Your current PostCSS version is "+o+", but "+i+" uses "+n+". Perhaps this is the source of the error below.")}}catch(i){console&&console.error&&console.error(i)}return e}prepareVisitors(){this.listeners={};let e=(t,r,i)=>{this.listeners[r]||(this.listeners[r]=[]),this.listeners[r].push([t,i])};for(let t of this.plugins)if(typeof t=="object")for(let r in t){if(!th[r]&&/^[A-Z]/.test(r))throw new Error(`Unknown event ${r} in ${t.postcssPlugin}. Try to update PostCSS (${this.processor.version} now).`);if(!rh[r])if(typeof t[r]=="object")for(let i in t[r])i==="*"?e(t,r,t[r][i]):e(t,r+"-"+i.toLowerCase(),t[r][i]);else typeof t[r]=="function"&&e(t,r,t[r])}this.hasListener=Object.keys(this.listeners).length>0}async runAsync(){this.plugin=0;for(let e=0;e0;){let r=this.visitTick(t);if(ze(r))try{await r}catch(i){let n=t[t.length-1].node;throw this.handleError(i,n)}}}if(this.listeners.OnceExit)for(let[t,r]of this.listeners.OnceExit){this.result.lastPlugin=t;try{if(e.type==="document"){let i=e.nodes.map(n=>r(n,this.helpers));await Promise.all(i)}else await r(e,this.helpers)}catch(i){throw this.handleError(i)}}}return this.processed=!0,this.stringify()}runOnRoot(e){this.result.lastPlugin=e;try{if(typeof e=="object"&&e.Once){if(this.result.root.type==="document"){let t=this.result.root.nodes.map(r=>e.Once(r,this.helpers));return ze(t[0])?Promise.all(t):t}return e.Once(this.result.root,this.helpers)}else if(typeof e=="function")return e(this.result.root,this.result)}catch(t){throw this.handleError(t)}}stringify(){if(this.error)throw this.error;if(this.stringified)return this.result;this.stringified=!0,this.sync();let e=this.result.opts,t=Zc;e.syntax&&(t=e.syntax.stringify),e.stringifier&&(t=e.stringifier),t.stringify&&(t=t.stringify);let i=new Hc(t,this.result.root,this.result.opts).generate();return this.result.css=i[0],this.result.map=i[1],this.result}sync(){if(this.error)throw this.error;if(this.processed)return this.result;if(this.processed=!0,this.processing)throw this.getAsyncError();for(let e of this.plugins){let t=this.runOnRoot(e);if(ze(t))throw this.getAsyncError()}if(this.prepareVisitors(),this.hasListener){let e=this.result.root;for(;!e[le];)e[le]=!0,this.walkSync(e);if(this.listeners.OnceExit)if(e.type==="document")for(let t of e.nodes)this.visitSync(this.listeners.OnceExit,t);else this.visitSync(this.listeners.OnceExit,e)}return this.result}then(e,t){return process.env.NODE_ENV!=="production"&&("from"in this.opts||Kc("Without `from` option PostCSS could generate wrong source map and will not find Browserslist config. Set it to CSS file path or to `undefined` to prevent this warning.")),this.async().then(e,t)}toString(){return this.css}visitSync(e,t){for(let[r,i]of e){this.result.lastPlugin=r;let n;try{n=i(t,this.helpers)}catch(o){throw this.handleError(o,t.proxyOf)}if(t.type!=="root"&&t.type!=="document"&&!t.parent)return!0;if(ze(n))throw this.getAsyncError()}}visitTick(e){let t=e[e.length-1],{node:r,visitors:i}=t;if(r.type!=="root"&&r.type!=="document"&&!r.parent){e.pop();return}if(i.length>0&&t.visitorIndex{i[le]||this.walkSync(i)});else{let i=this.listeners[r];if(i&&this.visitSync(i,e.toProxy()))return}}warnings(){return this.sync().warnings()}get content(){return this.stringify().content}get css(){return this.stringify().css}get map(){return this.stringify().map}get messages(){return this.sync().messages}get opts(){return this.result.opts}get processor(){return this.result.processor}get root(){return this.sync().root}get[Symbol.toStringTag](){return"LazyResult"}};Pe.registerPostcss=s=>{as=s};var wo=Pe;Pe.default=Pe;qc.registerLazyResult(Pe);Jc.registerLazyResult(Pe);var sh=Xn,ih=ar,nh=ao,oh=Ws,ah=Fs,ls=class{constructor(e,t,r){t=t.toString(),this.stringified=!1,this._processor=e,this._css=t,this._opts=r,this._map=void 0;let i,n=ih;this.result=new ah(this._processor,i,this._opts),this.result.css=t;let o=this;Object.defineProperty(this.result,"root",{get(){return o.root}});let l=new sh(n,i,this._opts,t);if(l.isMap()){let[a,u]=l.generate();a&&(this.result.css=a),u&&(this.result.map=u)}else l.clearAnnotation(),this.result.css=l.css}async(){return this.error?Promise.reject(this.error):Promise.resolve(this.result)}catch(e){return this.async().catch(e)}finally(e){return this.async().then(e,e)}sync(){if(this.error)throw this.error;return this.result}then(e,t){return process.env.NODE_ENV!=="production"&&("from"in this._opts||nh("Without `from` option PostCSS could generate wrong source map and will not find Browserslist config. Set it to CSS file path or to `undefined` to prevent this warning.")),this.async().then(e,t)}toString(){return this._css}warnings(){return[]}get content(){return this.result.css}get css(){return this.result.css}get map(){return this.result.map}get messages(){return[]}get opts(){return this.result.opts}get processor(){return this.result.processor}get root(){if(this._root)return this._root;let e,t=oh;try{e=t(this._css,this._opts)}catch(r){this.error=r}if(this.error)throw this.error;return this._root=e,e}get[Symbol.toStringTag](){return"NoWorkResult"}},lh=ls;ls.default=ls;var uh=lh,ch=wo,hh=Ls,fh=ot,et=class{constructor(e=[]){this.version="8.4.38",this.plugins=this.normalize(e)}normalize(e){let t=[];for(let r of e)if(r.postcss===!0?r=r():r.postcss&&(r=r.postcss),typeof r=="object"&&Array.isArray(r.plugins))t=t.concat(r.plugins);else if(typeof r=="object"&&r.postcssPlugin)t.push(r);else if(typeof r=="function")t.push(r);else if(typeof r=="object"&&(r.parse||r.stringify)){if(process.env.NODE_ENV!=="production")throw new Error("PostCSS syntaxes cannot be used as plugins. Instead, please use one of the syntax/parser/stringifier options as outlined in your PostCSS runner documentation.")}else throw new Error(r+" is not a PostCSS plugin");return t}process(e,t={}){return!this.plugins.length&&!t.parser&&!t.stringifier&&!t.syntax?new uh(this,e,t):new ch(this,e,t)}use(e){return this.plugins=this.plugins.concat(this.normalize([e])),this}},dh=et;et.default=et;fh.registerProcessor(et);hh.registerProcessor(et);var ph=ur,mh=Gn,gh=hr,yh=Us,wh=cr,bh=ot,Sh=Bs;function tt(s,e){if(Array.isArray(s))return s.map(i=>tt(i));let{inputs:t,...r}=s;if(t){e=[];for(let i of t){let n={...i,__proto__:wh.prototype};n.map&&(n.map={...n.map,__proto__:mh.prototype}),e.push(n)}}if(r.nodes&&(r.nodes=s.nodes.map(i=>tt(i,e))),r.source){let{inputId:i,...n}=r.source;r.source=n,i!=null&&(r.source.input=e[i])}if(r.type==="root")return new bh(r);if(r.type==="decl")return new ph(r);if(r.type==="rule")return new Sh(r);if(r.type==="comment")return new gh(r);if(r.type==="atrule")return new yh(r);throw new Error("Unknown node type: "+s.type)}var vh=tt;tt.default=tt;var Ch=ks,bo=ur,Ih=wo,Eh=Ce,zs=dh,xh=ar,Mh=vh,So=Ls,Nh=lo,vo=hr,Co=Us,Rh=Fs,Ah=cr,Oh=Ws,Dh=po,Io=Bs,Eo=ot,Th=lr;function A(...s){return s.length===1&&Array.isArray(s[0])&&(s=s[0]),new zs(s)}A.plugin=function(e,t){let r=!1;function i(...o){console&&console.warn&&!r&&(r=!0,console.warn(e+`: postcss.plugin was deprecated. Migration guide: +https://evilmartians.com/chronicles/postcss-8-plugin-migration`),process.env.LANG&&process.env.LANG.startsWith("cn")&&console.warn(e+`: \u91CC\u9762 postcss.plugin \u88AB\u5F03\u7528. \u8FC1\u79FB\u6307\u5357: +https://www.w3ctech.com/topic/2226`));let l=t(...o);return l.postcssPlugin=e,l.postcssVersion=new zs().version,l}let n;return Object.defineProperty(i,"postcss",{get(){return n||(n=i()),n}}),i.process=function(o,l,a){return A([i(a)]).process(o,l)},i};A.stringify=xh;A.parse=Oh;A.fromJSON=Mh;A.list=Dh;A.comment=s=>new vo(s);A.atRule=s=>new Co(s);A.decl=s=>new bo(s);A.rule=s=>new Io(s);A.root=s=>new Eo(s);A.document=s=>new So(s);A.CssSyntaxError=Ch;A.Declaration=bo;A.Container=Eh;A.Processor=zs;A.Document=So;A.Comment=vo;A.Warning=Nh;A.AtRule=Co;A.Result=Rh;A.Input=Ah;A.Rule=Io;A.Root=Eo;A.Node=Th;Ih.registerPostcss(A);var _h=A;A.default=A;var F=Vu(_h);F.stringify;F.fromJSON;F.plugin;F.parse;F.list;F.document;F.comment;F.atRule;F.rule;F.decl;F.root;F.CssSyntaxError;F.Declaration;F.Container;F.Processor;F.Document;F.Comment;F.Warning;F.AtRule;F.Result;F.Input;F.Rule;F.Root;F.Node;var us=class s{constructor(...e){ee(this,"parentElement",null),ee(this,"parentNode",null),ee(this,"ownerDocument"),ee(this,"firstChild",null),ee(this,"lastChild",null),ee(this,"previousSibling",null),ee(this,"nextSibling",null),ee(this,"ELEMENT_NODE",1),ee(this,"TEXT_NODE",3),ee(this,"nodeType"),ee(this,"nodeName"),ee(this,"RRNodeType")}get childNodes(){let e=[],t=this.firstChild;for(;t;)e.push(t),t=t.nextSibling;return e}contains(e){if(e instanceof s){if(e.ownerDocument!==this.ownerDocument)return!1;if(e===this)return!0}else return!1;for(;e.parentNode;){if(e.parentNode===this)return!0;e=e.parentNode}return!1}appendChild(e){throw new Error("RRDomException: Failed to execute 'appendChild' on 'RRNode': This RRNode type does not support this method.")}insertBefore(e,t){throw new Error("RRDomException: Failed to execute 'insertBefore' on 'RRNode': This RRNode type does not support this method.")}removeChild(e){throw new Error("RRDomException: Failed to execute 'removeChild' on 'RRNode': This RRNode type does not support this method.")}toString(){return"RRNode"}};var Pi={Node:["childNodes","parentNode","parentElement","textContent","ownerDocument"],ShadowRoot:["host","styleSheets"],Element:["shadowRoot","querySelector","querySelectorAll"],MutationObserver:[]},$i={Node:["contains","getRootNode"],ShadowRoot:["getSelection"],Element:[],MutationObserver:["constructor"]},Ot={},kh=()=>!!globalThis.Zone;function Vs(s){if(Ot[s])return Ot[s];let e=globalThis[s],t=e.prototype,r=s in Pi?Pi[s]:void 0,i=!!(r&&r.every(l=>{var a,u;return!!((u=(a=Object.getOwnPropertyDescriptor(t,l))==null?void 0:a.get)!=null&&u.toString().includes("[native code]"))})),n=s in $i?$i[s]:void 0,o=!!(n&&n.every(l=>{var a;return typeof t[l]=="function"&&((a=t[l])==null?void 0:a.toString().includes("[native code]"))}));if(i&&o&&!kh())return Ot[s]=e.prototype,e.prototype;try{let l=document.createElement("iframe");document.body.appendChild(l);let a=l.contentWindow;if(!a)return e.prototype;let u=a[s].prototype;return document.body.removeChild(l),u?Ot[s]=u:t}catch{return t}}var Rr={};function de(s,e,t){var r;let i=`${s}.${String(t)}`;if(Rr[i])return Rr[i].call(e);let n=Vs(s),o=(r=Object.getOwnPropertyDescriptor(n,t))==null?void 0:r.get;return o?(Rr[i]=o,o.call(e)):e[t]}var Ar={};function xo(s,e,t){let r=`${s}.${String(t)}`;if(Ar[r])return Ar[r].bind(e);let n=Vs(s)[t];return typeof n!="function"?e[t]:(Ar[r]=n,n.bind(e))}function Ph(s){return de("Node",s,"ownerDocument")}function $h(s){return de("Node",s,"childNodes")}function Lh(s){return de("Node",s,"parentNode")}function Fh(s){return de("Node",s,"parentElement")}function Uh(s){return de("Node",s,"textContent")}function Bh(s,e){return xo("Node",s,"contains")(e)}function Wh(s){return xo("Node",s,"getRootNode")()}function zh(s){return!s||!("host"in s)?null:de("ShadowRoot",s,"host")}function Vh(s){return s.styleSheets}function Gh(s){return!s||!("shadowRoot"in s)?null:de("Element",s,"shadowRoot")}function jh(s,e){return de("Element",s,"querySelector")(e)}function Yh(s,e){return de("Element",s,"querySelectorAll")(e)}function Mo(){return Vs("MutationObserver").constructor}function Ie(s,e,t){try{if(!(e in s))return()=>{};let r=s[e],i=t(r);return typeof i=="function"&&(i.prototype=i.prototype||{},Object.defineProperties(i,{__rrweb_original__:{enumerable:!1,value:r}})),s[e]=i,()=>{s[e]=r}}catch{return()=>{}}}var C={ownerDocument:Ph,childNodes:$h,parentNode:Lh,parentElement:Fh,textContent:Uh,contains:Bh,getRootNode:Wh,host:zh,styleSheets:Vh,shadowRoot:Gh,querySelector:jh,querySelectorAll:Yh,mutationObserver:Mo,patch:Ie};function Z(s,e,t=document){let r={capture:!0,passive:!0};return t.addEventListener(s,e,r),()=>t.removeEventListener(s,e,r)}var xe=`Please stop import mirror directly. Instead of that,\r +now you can use replayer.getMirror() to access the mirror instance of a replayer,\r +or you can use record.mirror to access the mirror instance during recording.`,Li={map:{},getId(){return console.error(xe),-1},getNode(){return console.error(xe),null},removeNodeFromMap(){console.error(xe)},has(){return console.error(xe),!1},reset(){console.error(xe)}};typeof window<"u"&&window.Proxy&&window.Reflect&&(Li=new Proxy(Li,{get(s,e,t){return e==="map"&&console.error(xe),Reflect.get(s,e,t)}}));function rt(s,e,t={}){let r=null,i=0;return function(...n){let o=Date.now();!i&&t.leading===!1&&(i=o);let l=e-(o-i),a=this;l<=0||l>e?(r&&(clearTimeout(r),r=null),i=o,s.apply(a,n)):!r&&t.trailing!==!1&&(r=setTimeout(()=>{i=t.leading===!1?0:Date.now(),r=null,s.apply(a,n)},l))}}function fr(s,e,t,r,i=window){let n=i.Object.getOwnPropertyDescriptor(s,e);return i.Object.defineProperty(s,e,r?t:{set(o){setTimeout(()=>{t.set.call(this,o)},0),n&&n.set&&n.set.call(this,o)}}),()=>fr(s,e,n||{},!0)}var Qt=Date.now;/[1-9][0-9]{12}/.test(Date.now().toString())||(Qt=()=>new Date().getTime());function No(s){var e,t,r,i;let n=s.document;return{left:n.scrollingElement?n.scrollingElement.scrollLeft:s.pageXOffset!==void 0?s.pageXOffset:n.documentElement.scrollLeft||n?.body&&((e=C.parentElement(n.body))==null?void 0:e.scrollLeft)||((t=n?.body)==null?void 0:t.scrollLeft)||0,top:n.scrollingElement?n.scrollingElement.scrollTop:s.pageYOffset!==void 0?s.pageYOffset:n?.documentElement.scrollTop||n?.body&&((r=C.parentElement(n.body))==null?void 0:r.scrollTop)||((i=n?.body)==null?void 0:i.scrollTop)||0}}function Ro(){return window.innerHeight||document.documentElement&&document.documentElement.clientHeight||document.body&&document.body.clientHeight}function Ao(){return window.innerWidth||document.documentElement&&document.documentElement.clientWidth||document.body&&document.body.clientWidth}function Oo(s){return s?s.nodeType===s.ELEMENT_NODE?s:C.parentElement(s):null}function X(s,e,t,r){if(!s)return!1;let i=Oo(s);if(!i)return!1;try{if(typeof e=="string"){if(i.classList.contains(e)||r&&i.closest("."+e)!==null)return!0}else if(Vt(i,e,r))return!0}catch{}return!!(t&&(i.matches(t)||r&&i.closest(t)!==null))}function Hh(s,e){return e.getId(s)!==-1}function Or(s,e,t){return s.tagName==="TITLE"&&t.headTitleMutations?!0:e.getId(s)===He}function Do(s,e){if(Ge(s))return!1;let t=e.getId(s);if(!e.has(t))return!0;let r=C.parentNode(s);return r&&r.nodeType===s.DOCUMENT_NODE?!1:r?Do(r,e):!0}function cs(s){return!!s.changedTouches}function Zh(s=window){"NodeList"in s&&!s.NodeList.prototype.forEach&&(s.NodeList.prototype.forEach=Array.prototype.forEach),"DOMTokenList"in s&&!s.DOMTokenList.prototype.forEach&&(s.DOMTokenList.prototype.forEach=Array.prototype.forEach)}function To(s,e){return!!(s.nodeName==="IFRAME"&&e.getMeta(s))}function _o(s,e){return!!(s.nodeName==="LINK"&&s.nodeType===s.ELEMENT_NODE&&s.getAttribute&&s.getAttribute("rel")==="stylesheet"&&e.getMeta(s))}function hs(s){return s?s instanceof us&&"shadowRoot"in s?!!s.shadowRoot:!!C.shadowRoot(s):!1}var fs=class{constructor(){w(this,"id",1),w(this,"styleIDMap",new WeakMap),w(this,"idStyleMap",new Map)}getId(e){return this.styleIDMap.get(e)??-1}has(e){return this.styleIDMap.has(e)}add(e,t){if(this.has(e))return this.getId(e);let r;return t===void 0?r=this.id++:r=t,this.styleIDMap.set(e,r),this.idStyleMap.set(r,e),r}getStyle(e){return this.idStyleMap.get(e)||null}reset(){this.styleIDMap=new WeakMap,this.idStyleMap=new Map,this.id=1}generateId(){return this.id++}};function ko(s){var e;let t=null;return"getRootNode"in s&&((e=C.getRootNode(s))==null?void 0:e.nodeType)===Node.DOCUMENT_FRAGMENT_NODE&&C.host(C.getRootNode(s))&&(t=C.host(C.getRootNode(s))),t}function Xh(s){let e=s,t;for(;t=ko(e);)e=t;return e}function Jh(s){let e=C.ownerDocument(s);if(!e)return!1;let t=Xh(s);return C.contains(e,t)}function Po(s){let e=C.ownerDocument(s);return e?C.contains(e,s)||Jh(s):!1}var x=(s=>(s[s.DomContentLoaded=0]="DomContentLoaded",s[s.Load=1]="Load",s[s.FullSnapshot=2]="FullSnapshot",s[s.IncrementalSnapshot=3]="IncrementalSnapshot",s[s.Meta=4]="Meta",s[s.Custom=5]="Custom",s[s.Plugin=6]="Plugin",s))(x||{}),I=(s=>(s[s.Mutation=0]="Mutation",s[s.MouseMove=1]="MouseMove",s[s.MouseInteraction=2]="MouseInteraction",s[s.Scroll=3]="Scroll",s[s.ViewportResize=4]="ViewportResize",s[s.Input=5]="Input",s[s.TouchMove=6]="TouchMove",s[s.MediaInteraction=7]="MediaInteraction",s[s.StyleSheetRule=8]="StyleSheetRule",s[s.CanvasMutation=9]="CanvasMutation",s[s.Font=10]="Font",s[s.Log=11]="Log",s[s.Drag=12]="Drag",s[s.StyleDeclaration=13]="StyleDeclaration",s[s.Selection=14]="Selection",s[s.AdoptedStyleSheet=15]="AdoptedStyleSheet",s[s.CustomElement=16]="CustomElement",s))(I||{}),J=(s=>(s[s.MouseUp=0]="MouseUp",s[s.MouseDown=1]="MouseDown",s[s.Click=2]="Click",s[s.ContextMenu=3]="ContextMenu",s[s.DblClick=4]="DblClick",s[s.Focus=5]="Focus",s[s.Blur=6]="Blur",s[s.TouchStart=7]="TouchStart",s[s.TouchMove_Departed=8]="TouchMove_Departed",s[s.TouchEnd=9]="TouchEnd",s[s.TouchCancel=10]="TouchCancel",s))(J||{}),ue=(s=>(s[s.Mouse=0]="Mouse",s[s.Pen=1]="Pen",s[s.Touch=2]="Touch",s))(ue||{}),$e=(s=>(s[s["2D"]=0]="2D",s[s.WebGL=1]="WebGL",s[s.WebGL2=2]="WebGL2",s))($e||{}),Me=(s=>(s[s.Play=0]="Play",s[s.Pause=1]="Pause",s[s.Seeked=2]="Seeked",s[s.VolumeChange=3]="VolumeChange",s[s.RateChange=4]="RateChange",s))(Me||{});var $o=(s=>(s[s.Document=0]="Document",s[s.DocumentType=1]="DocumentType",s[s.Element=2]="Element",s[s.Text=3]="Text",s[s.CDATA=4]="CDATA",s[s.Comment=5]="Comment",s))($o||{});function Fi(s){return"__ln"in s}var ds=class{constructor(){w(this,"length",0),w(this,"head",null),w(this,"tail",null)}get(e){if(e>=this.length)throw new Error("Position outside of list range");let t=this.head;for(let r=0;r`${s}@${e}`,ps=class{constructor(){w(this,"frozen",!1),w(this,"locked",!1),w(this,"texts",[]),w(this,"attributes",[]),w(this,"attributeMap",new WeakMap),w(this,"removes",[]),w(this,"mapRemoves",[]),w(this,"movedMap",{}),w(this,"addedSet",new Set),w(this,"movedSet",new Set),w(this,"droppedSet",new Set),w(this,"removesSubTreeCache",new Set),w(this,"mutationCb"),w(this,"blockClass"),w(this,"blockSelector"),w(this,"maskTextClass"),w(this,"maskTextSelector"),w(this,"inlineStylesheet"),w(this,"maskInputOptions"),w(this,"maskTextFn"),w(this,"maskInputFn"),w(this,"keepIframeSrcFn"),w(this,"recordCanvas"),w(this,"inlineImages"),w(this,"slimDOMOptions"),w(this,"dataURLOptions"),w(this,"doc"),w(this,"mirror"),w(this,"iframeManager"),w(this,"stylesheetManager"),w(this,"shadowDomManager"),w(this,"canvasManager"),w(this,"processedNodeManager"),w(this,"unattachedDoc"),w(this,"processMutations",e=>{e.forEach(this.processMutation),this.emit()}),w(this,"emit",()=>{if(this.frozen||this.locked)return;let e=[],t=new Set,r=new ds,i=a=>{let u=a,c=He;for(;c===He;)u=u&&u.nextSibling,c=u&&this.mirror.getId(u);return c},n=a=>{let u=C.parentNode(a);if(!u||!Po(a))return;let c=!1;if(a.nodeType===Node.TEXT_NODE){let p=u.tagName;if(p==="TEXTAREA")return;p==="STYLE"&&this.addedSet.has(u)&&(c=!0)}let h=Ge(u)?this.mirror.getId(ko(a)):this.mirror.getId(u),m=i(a);if(h===-1||m===-1)return r.addNode(a);let g=Re(a,{doc:this.doc,mirror:this.mirror,blockClass:this.blockClass,blockSelector:this.blockSelector,maskTextClass:this.maskTextClass,maskTextSelector:this.maskTextSelector,skipChild:!0,newlyAddedElement:!0,inlineStylesheet:this.inlineStylesheet,maskInputOptions:this.maskInputOptions,maskTextFn:this.maskTextFn,maskInputFn:this.maskInputFn,slimDOMOptions:this.slimDOMOptions,dataURLOptions:this.dataURLOptions,recordCanvas:this.recordCanvas,inlineImages:this.inlineImages,onSerialize:p=>{To(p,this.mirror)&&this.iframeManager.addIframe(p),_o(p,this.mirror)&&this.stylesheetManager.trackLinkElement(p),hs(a)&&this.shadowDomManager.addShadowRoot(C.shadowRoot(a),this.doc)},onIframeLoad:(p,d)=>{this.iframeManager.attachIframe(p,d),this.shadowDomManager.observeAttachShadow(p)},onStylesheetLoad:(p,d)=>{this.stylesheetManager.attachLinkElement(p,d)},cssCaptured:c});g&&(e.push({parentId:h,nextId:m,node:g}),t.add(g.id))};for(;this.mapRemoves.length;)this.mirror.removeNodeFromMap(this.mapRemoves.shift());for(let a of this.movedSet)Bi(this.removesSubTreeCache,a,this.mirror)&&!this.movedSet.has(C.parentNode(a))||n(a);for(let a of this.addedSet)!Wi(this.droppedSet,a)&&!Bi(this.removesSubTreeCache,a,this.mirror)||Wi(this.movedSet,a)?n(a):this.droppedSet.add(a);let o=null;for(;r.length;){let a=null;if(o){let u=this.mirror.getId(C.parentNode(o.value)),c=i(o.value);u!==-1&&c!==-1&&(a=o)}if(!a){let u=r.tail;for(;u;){let c=u;if(u=u.previous,c){let h=this.mirror.getId(C.parentNode(c.value));if(i(c.value)===-1)continue;if(h!==-1){a=c;break}else{let g=c.value,p=C.parentNode(g);if(p&&p.nodeType===Node.DOCUMENT_FRAGMENT_NODE){let d=C.host(p);if(this.mirror.getId(d)!==-1){a=c;break}}}}}}if(!a){for(;r.head;)r.removeNode(r.head.value);break}o=a.previous,r.removeNode(a.value),n(a.value)}let l={texts:this.texts.map(a=>{let u=a.node,c=C.parentNode(u);return c&&c.tagName==="TEXTAREA"&&this.genTextAreaValueMutation(c),{id:this.mirror.getId(u),value:a.value}}).filter(a=>!t.has(a.id)).filter(a=>this.mirror.has(a.id)),attributes:this.attributes.map(a=>{let{attributes:u}=a;if(typeof u.style=="string"){let c=JSON.stringify(a.styleDiff),h=JSON.stringify(a._unchangedStyles);c.length!t.has(a.id)).filter(a=>this.mirror.has(a.id)),removes:this.removes,adds:e};!l.texts.length&&!l.attributes.length&&!l.removes.length&&!l.adds.length||(this.texts=[],this.attributes=[],this.attributeMap=new WeakMap,this.removes=[],this.addedSet=new Set,this.movedSet=new Set,this.droppedSet=new Set,this.removesSubTreeCache=new Set,this.movedMap={},this.mutationCb(l))}),w(this,"genTextAreaValueMutation",e=>{let t=this.attributeMap.get(e);t||(t={node:e,attributes:{},styleDiff:{},_unchangedStyles:{}},this.attributes.push(t),this.attributeMap.set(e,t));let r=Array.from(C.childNodes(e),i=>C.textContent(i)||"").join("");t.attributes.value=Bt({element:e,maskInputOptions:this.maskInputOptions,tagName:e.tagName,type:Wt(e),value:r,maskInputFn:this.maskInputFn})}),w(this,"processMutation",e=>{if(!Or(e.target,this.mirror,this.slimDOMOptions))switch(e.type){case"characterData":{let t=C.textContent(e.target);!X(e.target,this.blockClass,this.blockSelector,!1)&&t!==e.oldValue&&this.texts.push({value:tn(e.target,this.maskTextClass,this.maskTextSelector,!0)&&t?this.maskTextFn?this.maskTextFn(t,Oo(e.target)):t.replace(/[\S]/g,"*"):t,node:e.target});break}case"attributes":{let t=e.target,r=e.attributeName,i=e.target.getAttribute(r);if(r==="value"){let o=Wt(t);i=Bt({element:t,maskInputOptions:this.maskInputOptions,tagName:t.tagName,type:o,value:i,maskInputFn:this.maskInputFn})}if(X(e.target,this.blockClass,this.blockSelector,!1)||i===e.oldValue)return;let n=this.attributeMap.get(e.target);if(t.tagName==="IFRAME"&&r==="src"&&!this.keepIframeSrcFn(i))if(!t.contentDocument)r="rr_src";else return;if(n||(n={node:e.target,attributes:{},styleDiff:{},_unchangedStyles:{}},this.attributes.push(n),this.attributeMap.set(e.target,n)),r==="type"&&t.tagName==="INPUT"&&(e.oldValue||"").toLowerCase()==="password"&&t.setAttribute("data-rr-is-password","true"),!en(t.tagName,r))if(n.attributes[r]=qi(this.doc,Se(t.tagName),Se(r),i),r==="style"){if(!this.unattachedDoc)try{this.unattachedDoc=document.implementation.createHTMLDocument()}catch{this.unattachedDoc=this.doc}let o=this.unattachedDoc.createElement("span");e.oldValue&&o.setAttribute("style",e.oldValue);for(let l of Array.from(t.style)){let a=t.style.getPropertyValue(l),u=t.style.getPropertyPriority(l);a!==o.style.getPropertyValue(l)||u!==o.style.getPropertyPriority(l)?u===""?n.styleDiff[l]=a:n.styleDiff[l]=[a,u]:n._unchangedStyles[l]=[a,u]}for(let l of Array.from(o.style))t.style.getPropertyValue(l)===""&&(n.styleDiff[l]=!1)}else r==="open"&&t.tagName==="DIALOG"&&(t.matches("dialog:modal")?n.attributes.rr_open_mode="modal":n.attributes.rr_open_mode="non-modal");break}case"childList":{if(X(e.target,this.blockClass,this.blockSelector,!0))return;if(e.target.tagName==="TEXTAREA"){this.genTextAreaValueMutation(e.target);return}e.addedNodes.forEach(t=>this.genAdds(t,e.target)),e.removedNodes.forEach(t=>{let r=this.mirror.getId(t),i=Ge(e.target)?this.mirror.getId(C.host(e.target)):this.mirror.getId(e.target);X(e.target,this.blockClass,this.blockSelector,!1)||Or(t,this.mirror,this.slimDOMOptions)||!Hh(t,this.mirror)||(this.addedSet.has(t)?(ms(this.addedSet,t),this.droppedSet.add(t)):this.addedSet.has(e.target)&&r===-1||Do(e.target,this.mirror)||(this.movedSet.has(t)&&this.movedMap[Ui(r,i)]?ms(this.movedSet,t):(this.removes.push({parentId:i,id:r,isShadow:Ge(e.target)&&je(e.target)?!0:void 0}),Kh(t,this.removesSubTreeCache))),this.mapRemoves.push(t))});break}}}),w(this,"genAdds",(e,t)=>{if(!this.processedNodeManager.inOtherBuffer(e,this)&&!(this.addedSet.has(e)||this.movedSet.has(e))){if(this.mirror.hasNode(e)){if(Or(e,this.mirror,this.slimDOMOptions))return;this.movedSet.add(e);let r=null;t&&this.mirror.hasNode(t)&&(r=this.mirror.getId(t)),r&&r!==-1&&(this.movedMap[Ui(this.mirror.getId(e),r)]=!0)}else this.addedSet.add(e),this.droppedSet.delete(e);X(e,this.blockClass,this.blockSelector,!1)||(C.childNodes(e).forEach(r=>this.genAdds(r)),hs(e)&&C.childNodes(C.shadowRoot(e)).forEach(r=>{this.processedNodeManager.add(r,this),this.genAdds(r,e)}))}})}init(e){["mutationCb","blockClass","blockSelector","maskTextClass","maskTextSelector","inlineStylesheet","maskInputOptions","maskTextFn","maskInputFn","keepIframeSrcFn","recordCanvas","inlineImages","slimDOMOptions","dataURLOptions","doc","mirror","iframeManager","stylesheetManager","shadowDomManager","canvasManager","processedNodeManager"].forEach(t=>{this[t]=e[t]})}freeze(){this.frozen=!0,this.canvasManager.freeze()}unfreeze(){this.frozen=!1,this.canvasManager.unfreeze(),this.emit()}isFrozen(){return this.frozen}lock(){this.locked=!0,this.canvasManager.lock()}unlock(){this.locked=!1,this.canvasManager.unlock(),this.emit()}reset(){this.shadowDomManager.reset(),this.canvasManager.reset()}};function ms(s,e){s.delete(e),C.childNodes(e).forEach(t=>ms(s,t))}function Kh(s,e){let t=[s];for(;t.length;){let r=t.pop();e.has(r)||(e.add(r),C.childNodes(r).forEach(i=>t.push(i)))}}function Bi(s,e,t){return s.size===0?!1:Qh(s,e)}function Qh(s,e,t){let r=C.parentNode(e);return r?s.has(r):!1}function Wi(s,e){return s.size===0?!1:Lo(s,e)}function Lo(s,e){let t=C.parentNode(e);return t?s.has(t)?!0:Lo(s,t):!1}var Ye;function qh(s){Ye=s}function ef(){Ye=void 0}var E=s=>Ye?(...t)=>{try{return s(...t)}catch(r){if(Ye&&Ye(r)===!0)return;throw r}}:s,be=[];function at(s){try{if("composedPath"in s){let e=s.composedPath();if(e.length)return e[0]}else if("path"in s&&s.path.length)return s.path[0]}catch{}return s&&s.target}function Fo(s,e){let t=new ps;be.push(t),t.init(s);let r=new(Mo())(E(t.processMutations.bind(t)));return r.observe(e,{attributes:!0,attributeOldValue:!0,characterData:!0,characterDataOldValue:!0,childList:!0,subtree:!0}),r}function tf({mousemoveCb:s,sampling:e,doc:t,mirror:r}){if(e.mousemove===!1)return()=>{};let i=typeof e.mousemove=="number"?e.mousemove:50,n=typeof e.mousemoveCallback=="number"?e.mousemoveCallback:500,o=[],l,a=rt(E(h=>{let m=Date.now()-l;s(o.map(g=>(g.timeOffset-=m,g)),h),o=[],l=null}),n),u=E(rt(E(h=>{let m=at(h),{clientX:g,clientY:p}=cs(h)?h.changedTouches[0]:h;l||(l=Qt()),o.push({x:g,y:p,id:r.getId(m),timeOffset:Qt()-l}),a(typeof DragEvent<"u"&&h instanceof DragEvent?I.Drag:h instanceof MouseEvent?I.MouseMove:I.TouchMove)}),i,{trailing:!1})),c=[Z("mousemove",u,t),Z("touchmove",u,t),Z("drag",u,t)];return E(()=>{c.forEach(h=>h())})}function rf({mouseInteractionCb:s,doc:e,mirror:t,blockClass:r,blockSelector:i,sampling:n}){if(n.mouseInteraction===!1)return()=>{};let o=n.mouseInteraction===!0||n.mouseInteraction===void 0?{}:n.mouseInteraction,l=[],a=null,u=c=>h=>{let m=at(h);if(X(m,r,i,!0))return;let g=null,p=c;if("pointerType"in h){switch(h.pointerType){case"mouse":g=ue.Mouse;break;case"touch":g=ue.Touch;break;case"pen":g=ue.Pen;break}g===ue.Touch?J[c]===J.MouseDown?p="TouchStart":J[c]===J.MouseUp&&(p="TouchEnd"):ue.Pen}else cs(h)&&(g=ue.Touch);g!==null?(a=g,(p.startsWith("Touch")&&g===ue.Touch||p.startsWith("Mouse")&&g===ue.Mouse)&&(g=null)):J[c]===J.Click&&(g=a,a=null);let d=cs(h)?h.changedTouches[0]:h;if(!d)return;let f=t.getId(m),{clientX:b,clientY:S}=d;E(s)({type:J[p],id:f,x:b,y:S,...g!==null&&{pointerType:g}})};return Object.keys(J).filter(c=>Number.isNaN(Number(c))&&!c.endsWith("_Departed")&&o[c]!==!1).forEach(c=>{let h=Se(c),m=u(c);if(window.PointerEvent)switch(J[c]){case J.MouseDown:case J.MouseUp:h=h.replace("mouse","pointer");break;case J.TouchStart:case J.TouchEnd:return}l.push(Z(h,m,e))}),E(()=>{l.forEach(c=>c())})}function Uo({scrollCb:s,doc:e,mirror:t,blockClass:r,blockSelector:i,sampling:n}){let o=E(rt(E(l=>{let a=at(l);if(!a||X(a,r,i,!0))return;let u=t.getId(a);if(a===e&&e.defaultView){let c=No(e.defaultView);s({id:u,x:c.left,y:c.top})}else s({id:u,x:a.scrollLeft,y:a.scrollTop})}),n.scroll||100));return Z("scroll",o,e)}function sf({viewportResizeCb:s},{win:e}){let t=-1,r=-1,i=E(rt(E(()=>{let n=Ro(),o=Ao();(t!==n||r!==o)&&(s({width:Number(o),height:Number(n)}),t=n,r=o)}),200));return Z("resize",i,e)}var nf=["INPUT","TEXTAREA","SELECT"],zi=new WeakMap;function of({inputCb:s,doc:e,mirror:t,blockClass:r,blockSelector:i,ignoreClass:n,ignoreSelector:o,maskInputOptions:l,maskInputFn:a,sampling:u,userTriggeredOnInput:c}){function h(S){let y=at(S),v=S.isTrusted,O=y&&y.tagName;if(y&&O==="OPTION"&&(y=C.parentElement(y)),!y||!O||nf.indexOf(O)<0||X(y,r,i,!0)||y.classList.contains(n)||o&&y.matches(o))return;let T=y.value,V=!1,$=Wt(y)||"";$==="radio"||$==="checkbox"?V=y.checked:(l[O.toLowerCase()]||l[$])&&(T=Bt({element:y,maskInputOptions:l,tagName:O,type:$,value:T,maskInputFn:a})),m(y,c?{text:T,isChecked:V,userTriggered:v}:{text:T,isChecked:V});let z=y.name;$==="radio"&&z&&V&&e.querySelectorAll(`input[type="radio"][name="${z}"]`).forEach(Y=>{if(Y!==y){let Q=Y.value;m(Y,c?{text:Q,isChecked:!V,userTriggered:!1}:{text:Q,isChecked:!V})}})}function m(S,y){let v=zi.get(S);if(!v||v.text!==y.text||v.isChecked!==y.isChecked){zi.set(S,y);let O=t.getId(S);E(s)({...y,id:O})}}let p=(u.input==="last"?["change"]:["input","change"]).map(S=>Z(S,E(h),e)),d=e.defaultView;if(!d)return()=>{p.forEach(S=>S())};let f=d.Object.getOwnPropertyDescriptor(d.HTMLInputElement.prototype,"value"),b=[[d.HTMLInputElement.prototype,"value"],[d.HTMLInputElement.prototype,"checked"],[d.HTMLSelectElement.prototype,"value"],[d.HTMLTextAreaElement.prototype,"value"],[d.HTMLSelectElement.prototype,"selectedIndex"],[d.HTMLOptionElement.prototype,"selected"]];return f&&f.set&&p.push(...b.map(S=>fr(S[0],S[1],{set(){E(h)({target:this,isTrusted:!1})}},!1,d))),E(()=>{p.forEach(S=>S())})}function qt(s){let e=[];function t(r,i){if(Dt("CSSGroupingRule")&&r.parentRule instanceof CSSGroupingRule||Dt("CSSMediaRule")&&r.parentRule instanceof CSSMediaRule||Dt("CSSSupportsRule")&&r.parentRule instanceof CSSSupportsRule||Dt("CSSConditionRule")&&r.parentRule instanceof CSSConditionRule){let o=Array.from(r.parentRule.cssRules).indexOf(r);i.unshift(o)}else if(r.parentStyleSheet){let o=Array.from(r.parentStyleSheet.cssRules).indexOf(r);i.unshift(o)}return i}return t(s,e)}function me(s,e,t){let r,i;return s?(s.ownerNode?r=e.getId(s.ownerNode):i=t.getId(s),{styleId:i,id:r}):{}}function af({styleSheetRuleCb:s,mirror:e,stylesheetManager:t},{win:r}){if(!r.CSSStyleSheet||!r.CSSStyleSheet.prototype)return()=>{};let i=r.CSSStyleSheet.prototype.insertRule;r.CSSStyleSheet.prototype.insertRule=new Proxy(i,{apply:E((c,h,m)=>{let[g,p]=m,{id:d,styleId:f}=me(h,e,t.styleMirror);return(d&&d!==-1||f&&f!==-1)&&s({id:d,styleId:f,adds:[{rule:g,index:p}]}),c.apply(h,m)})}),r.CSSStyleSheet.prototype.addRule=function(c,h,m=this.cssRules.length){let g=`${c} { ${h} }`;return r.CSSStyleSheet.prototype.insertRule.apply(this,[g,m])};let n=r.CSSStyleSheet.prototype.deleteRule;r.CSSStyleSheet.prototype.deleteRule=new Proxy(n,{apply:E((c,h,m)=>{let[g]=m,{id:p,styleId:d}=me(h,e,t.styleMirror);return(p&&p!==-1||d&&d!==-1)&&s({id:p,styleId:d,removes:[{index:g}]}),c.apply(h,m)})}),r.CSSStyleSheet.prototype.removeRule=function(c){return r.CSSStyleSheet.prototype.deleteRule.apply(this,[c])};let o;r.CSSStyleSheet.prototype.replace&&(o=r.CSSStyleSheet.prototype.replace,r.CSSStyleSheet.prototype.replace=new Proxy(o,{apply:E((c,h,m)=>{let[g]=m,{id:p,styleId:d}=me(h,e,t.styleMirror);return(p&&p!==-1||d&&d!==-1)&&s({id:p,styleId:d,replace:g}),c.apply(h,m)})}));let l;r.CSSStyleSheet.prototype.replaceSync&&(l=r.CSSStyleSheet.prototype.replaceSync,r.CSSStyleSheet.prototype.replaceSync=new Proxy(l,{apply:E((c,h,m)=>{let[g]=m,{id:p,styleId:d}=me(h,e,t.styleMirror);return(p&&p!==-1||d&&d!==-1)&&s({id:p,styleId:d,replaceSync:g}),c.apply(h,m)})}));let a={};Tt("CSSGroupingRule")?a.CSSGroupingRule=r.CSSGroupingRule:(Tt("CSSMediaRule")&&(a.CSSMediaRule=r.CSSMediaRule),Tt("CSSConditionRule")&&(a.CSSConditionRule=r.CSSConditionRule),Tt("CSSSupportsRule")&&(a.CSSSupportsRule=r.CSSSupportsRule));let u={};return Object.entries(a).forEach(([c,h])=>{u[c]={insertRule:h.prototype.insertRule,deleteRule:h.prototype.deleteRule},h.prototype.insertRule=new Proxy(u[c].insertRule,{apply:E((m,g,p)=>{let[d,f]=p,{id:b,styleId:S}=me(g.parentStyleSheet,e,t.styleMirror);return(b&&b!==-1||S&&S!==-1)&&s({id:b,styleId:S,adds:[{rule:d,index:[...qt(g),f||0]}]}),m.apply(g,p)})}),h.prototype.deleteRule=new Proxy(u[c].deleteRule,{apply:E((m,g,p)=>{let[d]=p,{id:f,styleId:b}=me(g.parentStyleSheet,e,t.styleMirror);return(f&&f!==-1||b&&b!==-1)&&s({id:f,styleId:b,removes:[{index:[...qt(g),d]}]}),m.apply(g,p)})})}),E(()=>{r.CSSStyleSheet.prototype.insertRule=i,r.CSSStyleSheet.prototype.deleteRule=n,o&&(r.CSSStyleSheet.prototype.replace=o),l&&(r.CSSStyleSheet.prototype.replaceSync=l),Object.entries(a).forEach(([c,h])=>{h.prototype.insertRule=u[c].insertRule,h.prototype.deleteRule=u[c].deleteRule})})}function Bo({mirror:s,stylesheetManager:e},t){var r,i,n;let o=null;t.nodeName==="#document"?o=s.getId(t):o=s.getId(C.host(t));let l=t.nodeName==="#document"?(r=t.defaultView)==null?void 0:r.Document:(n=(i=t.ownerDocument)==null?void 0:i.defaultView)==null?void 0:n.ShadowRoot,a=l?.prototype?Object.getOwnPropertyDescriptor(l?.prototype,"adoptedStyleSheets"):void 0;return o===null||o===-1||!l||!a?()=>{}:(Object.defineProperty(t,"adoptedStyleSheets",{configurable:a.configurable,enumerable:a.enumerable,get(){var u;return(u=a.get)==null?void 0:u.call(this)},set(u){var c;let h=(c=a.set)==null?void 0:c.call(this,u);if(o!==null&&o!==-1)try{e.adoptStyleSheets(u,o)}catch{}return h}}),E(()=>{Object.defineProperty(t,"adoptedStyleSheets",{configurable:a.configurable,enumerable:a.enumerable,get:a.get,set:a.set})}))}function lf({styleDeclarationCb:s,mirror:e,ignoreCSSAttributes:t,stylesheetManager:r},{win:i}){let n=i.CSSStyleDeclaration.prototype.setProperty;i.CSSStyleDeclaration.prototype.setProperty=new Proxy(n,{apply:E((l,a,u)=>{var c;let[h,m,g]=u;if(t.has(h))return n.apply(a,[h,m,g]);let{id:p,styleId:d}=me((c=a.parentRule)==null?void 0:c.parentStyleSheet,e,r.styleMirror);return(p&&p!==-1||d&&d!==-1)&&s({id:p,styleId:d,set:{property:h,value:m,priority:g},index:qt(a.parentRule)}),l.apply(a,u)})});let o=i.CSSStyleDeclaration.prototype.removeProperty;return i.CSSStyleDeclaration.prototype.removeProperty=new Proxy(o,{apply:E((l,a,u)=>{var c;let[h]=u;if(t.has(h))return o.apply(a,[h]);let{id:m,styleId:g}=me((c=a.parentRule)==null?void 0:c.parentStyleSheet,e,r.styleMirror);return(m&&m!==-1||g&&g!==-1)&&s({id:m,styleId:g,remove:{property:h},index:qt(a.parentRule)}),l.apply(a,u)})}),E(()=>{i.CSSStyleDeclaration.prototype.setProperty=n,i.CSSStyleDeclaration.prototype.removeProperty=o})}function uf({mediaInteractionCb:s,blockClass:e,blockSelector:t,mirror:r,sampling:i,doc:n}){let o=E(a=>rt(E(u=>{let c=at(u);if(!c||X(c,e,t,!0))return;let{currentTime:h,volume:m,muted:g,playbackRate:p,loop:d}=c;s({type:a,id:r.getId(c),currentTime:h,volume:m,muted:g,playbackRate:p,loop:d})}),i.media||500)),l=[Z("play",o(Me.Play),n),Z("pause",o(Me.Pause),n),Z("seeked",o(Me.Seeked),n),Z("volumechange",o(Me.VolumeChange),n),Z("ratechange",o(Me.RateChange),n)];return E(()=>{l.forEach(a=>a())})}function cf({fontCb:s,doc:e}){let t=e.defaultView;if(!t)return()=>{};let r=[],i=new WeakMap,n=t.FontFace;t.FontFace=function(a,u,c){let h=new n(a,u,c);return i.set(h,{family:a,buffer:typeof u!="string",descriptors:c,fontSource:typeof u=="string"?u:JSON.stringify(Array.from(new Uint8Array(u)))}),h};let o=Ie(e.fonts,"add",function(l){return function(a){return setTimeout(E(()=>{let u=i.get(a);u&&(s(u),i.delete(a))}),0),l.apply(this,[a])}});return r.push(()=>{t.FontFace=n}),r.push(o),E(()=>{r.forEach(l=>l())})}function hf(s){let{doc:e,mirror:t,blockClass:r,blockSelector:i,selectionCb:n}=s,o=!0,l=E(()=>{let a=e.getSelection();if(!a||o&&a?.isCollapsed)return;o=a.isCollapsed||!1;let u=[],c=a.rangeCount||0;for(let h=0;h{}:Ie(t.customElements,"define",function(i){return function(n,o,l){try{e({define:{name:n}})}catch{console.warn(`Custom element callback failed for ${n}`)}return i.apply(this,[n,o,l])}})}function df(s,e){let{mutationCb:t,mousemoveCb:r,mouseInteractionCb:i,scrollCb:n,viewportResizeCb:o,inputCb:l,mediaInteractionCb:a,styleSheetRuleCb:u,styleDeclarationCb:c,canvasMutationCb:h,fontCb:m,selectionCb:g,customElementCb:p}=s;s.mutationCb=(...d)=>{e.mutation&&e.mutation(...d),t(...d)},s.mousemoveCb=(...d)=>{e.mousemove&&e.mousemove(...d),r(...d)},s.mouseInteractionCb=(...d)=>{e.mouseInteraction&&e.mouseInteraction(...d),i(...d)},s.scrollCb=(...d)=>{e.scroll&&e.scroll(...d),n(...d)},s.viewportResizeCb=(...d)=>{e.viewportResize&&e.viewportResize(...d),o(...d)},s.inputCb=(...d)=>{e.input&&e.input(...d),l(...d)},s.mediaInteractionCb=(...d)=>{e.mediaInteaction&&e.mediaInteaction(...d),a(...d)},s.styleSheetRuleCb=(...d)=>{e.styleSheetRule&&e.styleSheetRule(...d),u(...d)},s.styleDeclarationCb=(...d)=>{e.styleDeclaration&&e.styleDeclaration(...d),c(...d)},s.canvasMutationCb=(...d)=>{e.canvasMutation&&e.canvasMutation(...d),h(...d)},s.fontCb=(...d)=>{e.font&&e.font(...d),m(...d)},s.selectionCb=(...d)=>{e.selection&&e.selection(...d),g(...d)},s.customElementCb=(...d)=>{e.customElement&&e.customElement(...d),p(...d)}}function pf(s,e={}){let t=s.doc.defaultView;if(!t)return()=>{};df(s,e);let r;s.recordDOM&&(r=Fo(s,s.doc));let i=tf(s),n=rf(s),o=Uo(s),l=sf(s,{win:t}),a=of(s),u=uf(s),c=()=>{},h=()=>{},m=()=>{},g=()=>{};s.recordDOM&&(c=af(s,{win:t}),h=Bo(s,s.doc),m=lf(s,{win:t}),s.collectFonts&&(g=cf(s)));let p=hf(s),d=ff(s),f=[];for(let b of s.plugins)f.push(b.observer(b.callback,t,b.options));return E(()=>{be.forEach(b=>b.reset()),r?.disconnect(),i(),n(),o(),l(),a(),u(),c(),h(),m(),g(),p(),d(),f.forEach(b=>b())})}function Dt(s){return typeof window[s]<"u"}function Tt(s){return!!(typeof window[s]<"u"&&window[s].prototype&&"insertRule"in window[s].prototype&&"deleteRule"in window[s].prototype)}var er=class{constructor(e){w(this,"iframeIdToRemoteIdMap",new WeakMap),w(this,"iframeRemoteIdToIdMap",new WeakMap),this.generateIdFn=e}getId(e,t,r,i){let n=r||this.getIdToRemoteIdMap(e),o=i||this.getRemoteIdToIdMap(e),l=n.get(t);return l||(l=this.generateIdFn(),n.set(t,l),o.set(l,t)),l}getIds(e,t){let r=this.getIdToRemoteIdMap(e),i=this.getRemoteIdToIdMap(e);return t.map(n=>this.getId(e,n,r,i))}getRemoteId(e,t,r){let i=r||this.getRemoteIdToIdMap(e);if(typeof t!="number")return t;let n=i.get(t);return n||-1}getRemoteIds(e,t){let r=this.getRemoteIdToIdMap(e);return t.map(i=>this.getRemoteId(e,i,r))}reset(e){if(!e){this.iframeIdToRemoteIdMap=new WeakMap,this.iframeRemoteIdToIdMap=new WeakMap;return}this.iframeIdToRemoteIdMap.delete(e),this.iframeRemoteIdToIdMap.delete(e)}getIdToRemoteIdMap(e){let t=this.iframeIdToRemoteIdMap.get(e);return t||(t=new Map,this.iframeIdToRemoteIdMap.set(e,t)),t}getRemoteIdToIdMap(e){let t=this.iframeRemoteIdToIdMap.get(e);return t||(t=new Map,this.iframeRemoteIdToIdMap.set(e,t)),t}},gs=class{constructor(e){w(this,"iframes",new WeakMap),w(this,"crossOriginIframeMap",new WeakMap),w(this,"crossOriginIframeMirror",new er(Qi)),w(this,"crossOriginIframeStyleMirror"),w(this,"crossOriginIframeRootIdMap",new WeakMap),w(this,"mirror"),w(this,"mutationCb"),w(this,"wrappedEmit"),w(this,"loadListener"),w(this,"stylesheetManager"),w(this,"recordCrossOriginIframes"),this.mutationCb=e.mutationCb,this.wrappedEmit=e.wrappedEmit,this.stylesheetManager=e.stylesheetManager,this.recordCrossOriginIframes=e.recordCrossOriginIframes,this.crossOriginIframeStyleMirror=new er(this.stylesheetManager.styleMirror.generateId.bind(this.stylesheetManager.styleMirror)),this.mirror=e.mirror,this.recordCrossOriginIframes&&window.addEventListener("message",this.handleMessage.bind(this))}addIframe(e){this.iframes.set(e,!0),e.contentWindow&&this.crossOriginIframeMap.set(e.contentWindow,e)}addLoadListener(e){this.loadListener=e}attachIframe(e,t){var r,i;this.mutationCb({adds:[{parentId:this.mirror.getId(e),nextId:null,node:t}],removes:[],texts:[],attributes:[],isAttachIframe:!0}),this.recordCrossOriginIframes&&((r=e.contentWindow)==null||r.addEventListener("message",this.handleMessage.bind(this))),(i=this.loadListener)==null||i.call(this,e),e.contentDocument&&e.contentDocument.adoptedStyleSheets&&e.contentDocument.adoptedStyleSheets.length>0&&this.stylesheetManager.adoptStyleSheets(e.contentDocument.adoptedStyleSheets,this.mirror.getId(e.contentDocument))}handleMessage(e){let t=e;if(t.data.type!=="rrweb"||t.origin!==t.data.origin||!e.source)return;let i=this.crossOriginIframeMap.get(e.source);if(!i)return;let n=this.transformCrossOriginEvent(i,t.data.event);n&&this.wrappedEmit(n,t.data.isCheckout)}transformCrossOriginEvent(e,t){var r;switch(t.type){case x.FullSnapshot:{this.crossOriginIframeMirror.reset(e),this.crossOriginIframeStyleMirror.reset(e),this.replaceIdOnNode(t.data.node,e);let i=t.data.node.id;return this.crossOriginIframeRootIdMap.set(e,i),this.patchRootIdOnNode(t.data.node,i),{timestamp:t.timestamp,type:x.IncrementalSnapshot,data:{source:I.Mutation,adds:[{parentId:this.mirror.getId(e),nextId:null,node:t.data.node}],removes:[],texts:[],attributes:[],isAttachIframe:!0}}}case x.Meta:case x.Load:case x.DomContentLoaded:return!1;case x.Plugin:return t;case x.Custom:return this.replaceIds(t.data.payload,e,["id","parentId","previousId","nextId"]),t;case x.IncrementalSnapshot:switch(t.data.source){case I.Mutation:return t.data.adds.forEach(i=>{this.replaceIds(i,e,["parentId","nextId","previousId"]),this.replaceIdOnNode(i.node,e);let n=this.crossOriginIframeRootIdMap.get(e);n&&this.patchRootIdOnNode(i.node,n)}),t.data.removes.forEach(i=>{this.replaceIds(i,e,["parentId","id"])}),t.data.attributes.forEach(i=>{this.replaceIds(i,e,["id"])}),t.data.texts.forEach(i=>{this.replaceIds(i,e,["id"])}),t;case I.Drag:case I.TouchMove:case I.MouseMove:return t.data.positions.forEach(i=>{this.replaceIds(i,e,["id"])}),t;case I.ViewportResize:return!1;case I.MediaInteraction:case I.MouseInteraction:case I.Scroll:case I.CanvasMutation:case I.Input:return this.replaceIds(t.data,e,["id"]),t;case I.StyleSheetRule:case I.StyleDeclaration:return this.replaceIds(t.data,e,["id"]),this.replaceStyleIds(t.data,e,["styleId"]),t;case I.Font:return t;case I.Selection:return t.data.ranges.forEach(i=>{this.replaceIds(i,e,["start","end"])}),t;case I.AdoptedStyleSheet:return this.replaceIds(t.data,e,["id"]),this.replaceStyleIds(t.data,e,["styleIds"]),(r=t.data.styles)==null||r.forEach(i=>{this.replaceStyleIds(i,e,["styleId"])}),t}}return!1}replace(e,t,r,i){for(let n of i)!Array.isArray(t[n])&&typeof t[n]!="number"||(Array.isArray(t[n])?t[n]=e.getIds(r,t[n]):t[n]=e.getId(r,t[n]));return t}replaceIds(e,t,r){return this.replace(this.crossOriginIframeMirror,e,t,r)}replaceStyleIds(e,t,r){return this.replace(this.crossOriginIframeStyleMirror,e,t,r)}replaceIdOnNode(e,t){this.replaceIds(e,t,["id","rootId"]),"childNodes"in e&&e.childNodes.forEach(r=>{this.replaceIdOnNode(r,t)})}patchRootIdOnNode(e,t){e.type!==$o.Document&&!e.rootId&&(e.rootId=t),"childNodes"in e&&e.childNodes.forEach(r=>{this.patchRootIdOnNode(r,t)})}},ys=class{constructor(e){w(this,"shadowDoms",new WeakSet),w(this,"mutationCb"),w(this,"scrollCb"),w(this,"bypassOptions"),w(this,"mirror"),w(this,"restoreHandlers",[]),this.mutationCb=e.mutationCb,this.scrollCb=e.scrollCb,this.bypassOptions=e.bypassOptions,this.mirror=e.mirror,this.init()}init(){this.reset(),this.patchAttachShadow(Element,document)}addShadowRoot(e,t){if(!je(e)||this.shadowDoms.has(e))return;this.shadowDoms.add(e);let r=Fo({...this.bypassOptions,doc:t,mutationCb:this.mutationCb,mirror:this.mirror,shadowDomManager:this},e);this.restoreHandlers.push(()=>r.disconnect()),this.restoreHandlers.push(Uo({...this.bypassOptions,scrollCb:this.scrollCb,doc:e,mirror:this.mirror})),setTimeout(()=>{e.adoptedStyleSheets&&e.adoptedStyleSheets.length>0&&this.bypassOptions.stylesheetManager.adoptStyleSheets(e.adoptedStyleSheets,this.mirror.getId(C.host(e))),this.restoreHandlers.push(Bo({mirror:this.mirror,stylesheetManager:this.bypassOptions.stylesheetManager},e))},0)}observeAttachShadow(e){!e.contentWindow||!e.contentDocument||this.patchAttachShadow(e.contentWindow.Element,e.contentDocument)}patchAttachShadow(e,t){let r=this;this.restoreHandlers.push(Ie(e.prototype,"attachShadow",function(i){return function(n){let o=i.call(this,n),l=C.shadowRoot(this);return l&&Po(this)&&r.addShadowRoot(l,t),o}}))}reset(){this.restoreHandlers.forEach(e=>{try{e()}catch{}}),this.restoreHandlers=[],this.shadowDoms=new WeakSet}},Ae="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",mf=typeof Uint8Array>"u"?[]:new Uint8Array(256);for(Ve=0;Ve>2],i+=Ae[(e[t]&3)<<4|e[t+1]>>4],i+=Ae[(e[t+1]&15)<<2|e[t+2]>>6],i+=Ae[e[t+2]&63];return r%3===2?i=i.substring(0,i.length-1)+"=":r%3===1&&(i=i.substring(0,i.length-2)+"=="),i};var Vi=new Map;function yf(s,e){let t=Vi.get(s);return t||(t=new Map,Vi.set(s,t)),t.has(e)||t.set(e,[]),t.get(e)}var Wo=(s,e,t)=>{if(!s||!(Vo(s,e)||typeof s=="object"))return;let r=s.constructor.name,i=yf(t,r),n=i.indexOf(s);return n===-1&&(n=i.length,i.push(s)),n};function Lt(s,e,t){if(s instanceof Array)return s.map(r=>Lt(r,e,t));if(s===null)return s;if(s instanceof Float32Array||s instanceof Float64Array||s instanceof Int32Array||s instanceof Uint32Array||s instanceof Uint8Array||s instanceof Uint16Array||s instanceof Int16Array||s instanceof Int8Array||s instanceof Uint8ClampedArray)return{rr_type:s.constructor.name,args:[Object.values(s)]};if(s instanceof ArrayBuffer){let r=s.constructor.name,i=gf(s);return{rr_type:r,base64:i}}else{if(s instanceof DataView)return{rr_type:s.constructor.name,args:[Lt(s.buffer,e,t),s.byteOffset,s.byteLength]};if(s instanceof HTMLImageElement){let r=s.constructor.name,{src:i}=s;return{rr_type:r,src:i}}else if(s instanceof HTMLCanvasElement){let r="HTMLImageElement",i=s.toDataURL();return{rr_type:r,src:i}}else{if(s instanceof ImageData)return{rr_type:s.constructor.name,args:[Lt(s.data,e,t),s.width,s.height]};if(Vo(s,e)||typeof s=="object"){let r=s.constructor.name,i=Wo(s,e,t);return{rr_type:r,index:i}}}}return s}var zo=(s,e,t)=>s.map(r=>Lt(r,e,t)),Vo=(s,e)=>!!["WebGLActiveInfo","WebGLBuffer","WebGLFramebuffer","WebGLProgram","WebGLRenderbuffer","WebGLShader","WebGLShaderPrecisionFormat","WebGLTexture","WebGLUniformLocation","WebGLVertexArrayObject","WebGLVertexArrayObjectOES"].filter(i=>typeof e[i]=="function").find(i=>s instanceof e[i]);function wf(s,e,t,r){let i=[],n=Object.getOwnPropertyNames(e.CanvasRenderingContext2D.prototype);for(let o of n)try{if(typeof e.CanvasRenderingContext2D.prototype[o]!="function")continue;let l=Ie(e.CanvasRenderingContext2D.prototype,o,function(a){return function(...u){return X(this.canvas,t,r,!0)||setTimeout(()=>{let c=zo(u,e,this);s(this.canvas,{type:$e["2D"],property:o,args:c})},0),a.apply(this,u)}});i.push(l)}catch{let l=fr(e.CanvasRenderingContext2D.prototype,o,{set(a){s(this.canvas,{type:$e["2D"],property:o,args:[a],setter:!0})}});i.push(l)}return()=>{i.forEach(o=>o())}}function bf(s){return s==="experimental-webgl"?"webgl":s}function Gi(s,e,t,r){let i=[];try{let n=Ie(s.HTMLCanvasElement.prototype,"getContext",function(o){return function(l,...a){if(!X(this,e,t,!0)){let u=bf(l);if("__context"in this||(this.__context=u),r&&["webgl","webgl2"].includes(u))if(a[0]&&typeof a[0]=="object"){let c=a[0];c.preserveDrawingBuffer||(c.preserveDrawingBuffer=!0)}else a.splice(0,1,{preserveDrawingBuffer:!0})}return o.apply(this,[l,...a])}});i.push(n)}catch{console.error("failed to patch HTMLCanvasElement.prototype.getContext")}return()=>{i.forEach(n=>n())}}function ji(s,e,t,r,i,n){let o=[],l=Object.getOwnPropertyNames(s);for(let a of l)if(!["isContextLost","canvas","drawingBufferWidth","drawingBufferHeight"].includes(a))try{if(typeof s[a]!="function")continue;let u=Ie(s,a,function(c){return function(...h){let m=c.apply(this,h);if(Wo(m,n,this),"tagName"in this.canvas&&!X(this.canvas,r,i,!0)){let g=zo(h,n,this),p={type:e,property:a,args:g};t(this.canvas,p)}return m}});o.push(u)}catch{let u=fr(s,a,{set(c){t(this.canvas,{type:e,property:a,args:[c],setter:!0})}});o.push(u)}return o}function Sf(s,e,t,r){let i=[];return i.push(...ji(e.WebGLRenderingContext.prototype,$e.WebGL,s,t,r,e)),typeof e.WebGL2RenderingContext<"u"&&i.push(...ji(e.WebGL2RenderingContext.prototype,$e.WebGL2,s,t,r,e)),()=>{i.forEach(n=>n())}}var Go="KGZ1bmN0aW9uKCkgewogICJ1c2Ugc3RyaWN0IjsKICB2YXIgY2hhcnMgPSAiQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0NTY3ODkrLyI7CiAgdmFyIGxvb2t1cCA9IHR5cGVvZiBVaW50OEFycmF5ID09PSAidW5kZWZpbmVkIiA/IFtdIDogbmV3IFVpbnQ4QXJyYXkoMjU2KTsKICBmb3IgKHZhciBpID0gMDsgaSA8IGNoYXJzLmxlbmd0aDsgaSsrKSB7CiAgICBsb29rdXBbY2hhcnMuY2hhckNvZGVBdChpKV0gPSBpOwogIH0KICB2YXIgZW5jb2RlID0gZnVuY3Rpb24oYXJyYXlidWZmZXIpIHsKICAgIHZhciBieXRlcyA9IG5ldyBVaW50OEFycmF5KGFycmF5YnVmZmVyKSwgaTIsIGxlbiA9IGJ5dGVzLmxlbmd0aCwgYmFzZTY0ID0gIiI7CiAgICBmb3IgKGkyID0gMDsgaTIgPCBsZW47IGkyICs9IDMpIHsKICAgICAgYmFzZTY0ICs9IGNoYXJzW2J5dGVzW2kyXSA+PiAyXTsKICAgICAgYmFzZTY0ICs9IGNoYXJzWyhieXRlc1tpMl0gJiAzKSA8PCA0IHwgYnl0ZXNbaTIgKyAxXSA+PiA0XTsKICAgICAgYmFzZTY0ICs9IGNoYXJzWyhieXRlc1tpMiArIDFdICYgMTUpIDw8IDIgfCBieXRlc1tpMiArIDJdID4+IDZdOwogICAgICBiYXNlNjQgKz0gY2hhcnNbYnl0ZXNbaTIgKyAyXSAmIDYzXTsKICAgIH0KICAgIGlmIChsZW4gJSAzID09PSAyKSB7CiAgICAgIGJhc2U2NCA9IGJhc2U2NC5zdWJzdHJpbmcoMCwgYmFzZTY0Lmxlbmd0aCAtIDEpICsgIj0iOwogICAgfSBlbHNlIGlmIChsZW4gJSAzID09PSAxKSB7CiAgICAgIGJhc2U2NCA9IGJhc2U2NC5zdWJzdHJpbmcoMCwgYmFzZTY0Lmxlbmd0aCAtIDIpICsgIj09IjsKICAgIH0KICAgIHJldHVybiBiYXNlNjQ7CiAgfTsKICBjb25zdCBsYXN0QmxvYk1hcCA9IC8qIEBfX1BVUkVfXyAqLyBuZXcgTWFwKCk7CiAgY29uc3QgdHJhbnNwYXJlbnRCbG9iTWFwID0gLyogQF9fUFVSRV9fICovIG5ldyBNYXAoKTsKICBhc3luYyBmdW5jdGlvbiBnZXRUcmFuc3BhcmVudEJsb2JGb3Iod2lkdGgsIGhlaWdodCwgZGF0YVVSTE9wdGlvbnMpIHsKICAgIGNvbnN0IGlkID0gYCR7d2lkdGh9LSR7aGVpZ2h0fWA7CiAgICBpZiAoIk9mZnNjcmVlbkNhbnZhcyIgaW4gZ2xvYmFsVGhpcykgewogICAgICBpZiAodHJhbnNwYXJlbnRCbG9iTWFwLmhhcyhpZCkpIHJldHVybiB0cmFuc3BhcmVudEJsb2JNYXAuZ2V0KGlkKTsKICAgICAgY29uc3Qgb2Zmc2NyZWVuID0gbmV3IE9mZnNjcmVlbkNhbnZhcyh3aWR0aCwgaGVpZ2h0KTsKICAgICAgb2Zmc2NyZWVuLmdldENvbnRleHQoIjJkIik7CiAgICAgIGNvbnN0IGJsb2IgPSBhd2FpdCBvZmZzY3JlZW4uY29udmVydFRvQmxvYihkYXRhVVJMT3B0aW9ucyk7CiAgICAgIGNvbnN0IGFycmF5QnVmZmVyID0gYXdhaXQgYmxvYi5hcnJheUJ1ZmZlcigpOwogICAgICBjb25zdCBiYXNlNjQgPSBlbmNvZGUoYXJyYXlCdWZmZXIpOwogICAgICB0cmFuc3BhcmVudEJsb2JNYXAuc2V0KGlkLCBiYXNlNjQpOwogICAgICByZXR1cm4gYmFzZTY0OwogICAgfSBlbHNlIHsKICAgICAgcmV0dXJuICIiOwogICAgfQogIH0KICBjb25zdCB3b3JrZXIgPSBzZWxmOwogIHdvcmtlci5vbm1lc3NhZ2UgPSBhc3luYyBmdW5jdGlvbihlKSB7CiAgICBpZiAoIk9mZnNjcmVlbkNhbnZhcyIgaW4gZ2xvYmFsVGhpcykgewogICAgICBjb25zdCB7IGlkLCBiaXRtYXAsIHdpZHRoLCBoZWlnaHQsIGRhdGFVUkxPcHRpb25zIH0gPSBlLmRhdGE7CiAgICAgIGNvbnN0IHRyYW5zcGFyZW50QmFzZTY0ID0gZ2V0VHJhbnNwYXJlbnRCbG9iRm9yKAogICAgICAgIHdpZHRoLAogICAgICAgIGhlaWdodCwKICAgICAgICBkYXRhVVJMT3B0aW9ucwogICAgICApOwogICAgICBjb25zdCBvZmZzY3JlZW4gPSBuZXcgT2Zmc2NyZWVuQ2FudmFzKHdpZHRoLCBoZWlnaHQpOwogICAgICBjb25zdCBjdHggPSBvZmZzY3JlZW4uZ2V0Q29udGV4dCgiMmQiKTsKICAgICAgY3R4LmRyYXdJbWFnZShiaXRtYXAsIDAsIDApOwogICAgICBiaXRtYXAuY2xvc2UoKTsKICAgICAgY29uc3QgYmxvYiA9IGF3YWl0IG9mZnNjcmVlbi5jb252ZXJ0VG9CbG9iKGRhdGFVUkxPcHRpb25zKTsKICAgICAgY29uc3QgdHlwZSA9IGJsb2IudHlwZTsKICAgICAgY29uc3QgYXJyYXlCdWZmZXIgPSBhd2FpdCBibG9iLmFycmF5QnVmZmVyKCk7CiAgICAgIGNvbnN0IGJhc2U2NCA9IGVuY29kZShhcnJheUJ1ZmZlcik7CiAgICAgIGlmICghbGFzdEJsb2JNYXAuaGFzKGlkKSAmJiBhd2FpdCB0cmFuc3BhcmVudEJhc2U2NCA9PT0gYmFzZTY0KSB7CiAgICAgICAgbGFzdEJsb2JNYXAuc2V0KGlkLCBiYXNlNjQpOwogICAgICAgIHJldHVybiB3b3JrZXIucG9zdE1lc3NhZ2UoeyBpZCB9KTsKICAgICAgfQogICAgICBpZiAobGFzdEJsb2JNYXAuZ2V0KGlkKSA9PT0gYmFzZTY0KSByZXR1cm4gd29ya2VyLnBvc3RNZXNzYWdlKHsgaWQgfSk7CiAgICAgIHdvcmtlci5wb3N0TWVzc2FnZSh7CiAgICAgICAgaWQsCiAgICAgICAgdHlwZSwKICAgICAgICBiYXNlNjQsCiAgICAgICAgd2lkdGgsCiAgICAgICAgaGVpZ2h0CiAgICAgIH0pOwogICAgICBsYXN0QmxvYk1hcC5zZXQoaWQsIGJhc2U2NCk7CiAgICB9IGVsc2UgewogICAgICByZXR1cm4gd29ya2VyLnBvc3RNZXNzYWdlKHsgaWQ6IGUuZGF0YS5pZCB9KTsKICAgIH0KICB9Owp9KSgpOwovLyMgc291cmNlTWFwcGluZ1VSTD1pbWFnZS1iaXRtYXAtZGF0YS11cmwtd29ya2VyLUlKcEM3Z19iLmpzLm1hcAo=",vf=s=>Uint8Array.from(atob(s),e=>e.charCodeAt(0)),Yi=typeof window<"u"&&window.Blob&&new Blob([vf(Go)],{type:"text/javascript;charset=utf-8"});function Cf(s){let e;try{if(e=Yi&&(window.URL||window.webkitURL).createObjectURL(Yi),!e)throw"";let t=new Worker(e,{name:s?.name});return t.addEventListener("error",()=>{(window.URL||window.webkitURL).revokeObjectURL(e)}),t}catch{return new Worker("data:text/javascript;base64,"+Go,{name:s?.name})}finally{e&&(window.URL||window.webkitURL).revokeObjectURL(e)}}var ws=class{constructor(e){w(this,"pendingCanvasMutations",new Map),w(this,"rafStamps",{latestId:0,invokeId:null}),w(this,"mirror"),w(this,"mutationCb"),w(this,"resetObservers"),w(this,"frozen",!1),w(this,"locked",!1),w(this,"processMutation",(a,u)=>{(this.rafStamps.invokeId&&this.rafStamps.latestId!==this.rafStamps.invokeId||!this.rafStamps.invokeId)&&(this.rafStamps.invokeId=this.rafStamps.latestId),this.pendingCanvasMutations.has(a)||this.pendingCanvasMutations.set(a,[]),this.pendingCanvasMutations.get(a).push(u)});let{sampling:t="all",win:r,blockClass:i,blockSelector:n,recordCanvas:o,dataURLOptions:l}=e;this.mutationCb=e.mutationCb,this.mirror=e.mirror,o&&t==="all"&&this.initCanvasMutationObserver(r,i,n),o&&typeof t=="number"&&this.initCanvasFPSObserver(t,r,i,n,{dataURLOptions:l})}reset(){this.pendingCanvasMutations.clear(),this.resetObservers&&this.resetObservers()}freeze(){this.frozen=!0}unfreeze(){this.frozen=!1}lock(){this.locked=!0}unlock(){this.locked=!1}initCanvasFPSObserver(e,t,r,i,n){let o=Gi(t,r,i,!0),l=new Map,a=new Cf;a.onmessage=p=>{let{id:d}=p.data;if(l.set(d,!1),!("base64"in p.data))return;let{base64:f,type:b,width:S,height:y}=p.data;this.mutationCb({id:d,type:$e["2D"],commands:[{property:"clearRect",args:[0,0,S,y]},{property:"drawImage",args:[{rr_type:"ImageBitmap",args:[{rr_type:"Blob",data:[{rr_type:"ArrayBuffer",base64:f}],type:b}]},0,0]}]})};let u=1e3/e,c=0,h,m=()=>{let p=[];return t.document.querySelectorAll("canvas").forEach(d=>{X(d,r,i,!0)||p.push(d)}),p},g=p=>{if(c&&p-c{var f;let b=this.mirror.getId(d);if(l.get(b)||d.width===0||d.height===0)return;if(l.set(b,!0),["webgl","webgl2"].includes(d.__context)){let y=d.getContext(d.__context);((f=y?.getContextAttributes())==null?void 0:f.preserveDrawingBuffer)===!1&&y.clear(y.COLOR_BUFFER_BIT)}let S=await createImageBitmap(d);a.postMessage({id:b,bitmap:S,width:d.width,height:d.height,dataURLOptions:n.dataURLOptions},[S])}),h=requestAnimationFrame(g)};h=requestAnimationFrame(g),this.resetObservers=()=>{o(),cancelAnimationFrame(h)}}initCanvasMutationObserver(e,t,r){this.startRAFTimestamping(),this.startPendingCanvasMutationFlusher();let i=Gi(e,t,r,!1),n=wf(this.processMutation.bind(this),e,t,r),o=Sf(this.processMutation.bind(this),e,t,r);this.resetObservers=()=>{i(),n(),o()}}startPendingCanvasMutationFlusher(){requestAnimationFrame(()=>this.flushPendingCanvasMutations())}startRAFTimestamping(){let e=t=>{this.rafStamps.latestId=t,requestAnimationFrame(e)};requestAnimationFrame(e)}flushPendingCanvasMutations(){this.pendingCanvasMutations.forEach((e,t)=>{let r=this.mirror.getId(t);this.flushPendingCanvasMutationFor(t,r)}),requestAnimationFrame(()=>this.flushPendingCanvasMutations())}flushPendingCanvasMutationFor(e,t){if(this.frozen||this.locked)return;let r=this.pendingCanvasMutations.get(e);if(!r||t===-1)return;let i=r.map(o=>{let{type:l,...a}=o;return a}),{type:n}=r[0];this.mutationCb({id:t,type:n,commands:i}),this.pendingCanvasMutations.delete(e)}},bs=class{constructor(e){w(this,"trackedLinkElements",new WeakSet),w(this,"mutationCb"),w(this,"adoptedStyleSheetCb"),w(this,"styleMirror",new fs),this.mutationCb=e.mutationCb,this.adoptedStyleSheetCb=e.adoptedStyleSheetCb}attachLinkElement(e,t){"_cssText"in t.attributes&&this.mutationCb({adds:[],removes:[],texts:[],attributes:[{id:t.id,attributes:t.attributes}]}),this.trackLinkElement(e)}trackLinkElement(e){this.trackedLinkElements.has(e)||(this.trackedLinkElements.add(e),this.trackStylesheetInLinkElement(e))}adoptStyleSheets(e,t){if(e.length===0)return;let r={id:t,styleIds:[]},i=[];for(let n of e){let o;this.styleMirror.has(n)?o=this.styleMirror.getId(n):(o=this.styleMirror.add(n),i.push({styleId:o,rules:Array.from(n.rules||CSSRule,(l,a)=>({rule:Ji(l,n.href),index:a}))})),r.styleIds.push(o)}i.length>0&&(r.styles=i),this.adoptedStyleSheetCb(r)}reset(){this.styleMirror.reset(),this.trackedLinkElements=new WeakSet}trackStylesheetInLinkElement(e){}},Ss=class{constructor(){w(this,"nodeMap",new WeakMap),w(this,"active",!1)}inOtherBuffer(e,t){let r=this.nodeMap.get(e);return r&&Array.from(r).some(i=>i!==t)}add(e,t){this.active||(this.active=!0,requestAnimationFrame(()=>{this.nodeMap=new WeakMap,this.active=!1})),this.nodeMap.set(e,(this.nodeMap.get(e)||new Set).add(t))}destroy(){}},W,Ft,Dr,tr=!1;try{if(Array.from([1],s=>s*2)[0]!==2){let s=document.createElement("iframe");document.body.appendChild(s),Array.from=((Zs=s.contentWindow)==null?void 0:Zs.Array.from)||Array.from,document.body.removeChild(s)}}catch(s){console.debug("Unable to override Array.from",s)}var re=Ea();function pe(s={}){let{emit:e,checkoutEveryNms:t,checkoutEveryNth:r,blockClass:i="rr-block",blockSelector:n=null,ignoreClass:o="rr-ignore",ignoreSelector:l=null,maskTextClass:a="rr-mask",maskTextSelector:u=null,inlineStylesheet:c=!0,maskAllInputs:h,maskInputOptions:m,slimDOMOptions:g,maskInputFn:p,maskTextFn:d,hooks:f,packFn:b,sampling:S={},dataURLOptions:y={},mousemoveWait:v,recordDOM:O=!0,recordCanvas:T=!1,recordCrossOriginIframes:V=!1,recordAfter:$=s.recordAfter==="DOMContentLoaded"?s.recordAfter:"load",userTriggeredOnInput:z=!1,collectFonts:Y=!1,inlineImages:Q=!1,plugins:ne,keepIframeSrcFn:M=()=>!1,ignoreCSSAttributes:Le=new Set([]),errorHandler:Fe}=s;qh(Fe);let G=V?window.parent===window:!0,q=!1;if(!G)try{window.parent.document&&(q=!1)}catch{q=!0}if(G&&!e)throw new Error("emit function is required");if(!G&&!q)return()=>{};v!==void 0&&S.mousemove===void 0&&(S.mousemove=v),re.reset();let ge=h===!0?{color:!0,date:!0,"datetime-local":!0,email:!0,month:!0,number:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0,textarea:!0,select:!0,password:!0}:m!==void 0?m:{password:!0},oe=rn(g);Zh();let Gs,pr=0,js=N=>{for(let te of ne||[])te.eventProcessor&&(N=te.eventProcessor(N));return b&&!q&&(N=b(N)),N};W=(N,te)=>{var U;let B=N;if(B.timestamp=Qt(),(U=be[0])!=null&&U.isFrozen()&&B.type!==x.FullSnapshot&&!(B.type===x.IncrementalSnapshot&&B.data.source===I.Mutation)&&be.forEach(K=>K.unfreeze()),G)e?.(js(B),te);else if(q){let K={type:"rrweb",event:js(B),origin:window.location.origin,isCheckout:te};window.parent.postMessage(K,"*")}if(B.type===x.FullSnapshot)Gs=B,pr=0;else if(B.type===x.IncrementalSnapshot){if(B.data.source===I.Mutation&&B.data.isAttachIframe)return;pr++;let K=r&&pr>=r,D=t&&B.timestamp-Gs.timestamp>t;(K||D)&&Ft(!0)}};let lt=N=>{W({type:x.IncrementalSnapshot,data:{source:I.Mutation,...N}})},Ys=N=>W({type:x.IncrementalSnapshot,data:{source:I.Scroll,...N}}),Hs=N=>W({type:x.IncrementalSnapshot,data:{source:I.CanvasMutation,...N}}),Ho=N=>W({type:x.IncrementalSnapshot,data:{source:I.AdoptedStyleSheet,...N}}),ye=new bs({mutationCb:lt,adoptedStyleSheetCb:Ho}),we=new gs({mirror:re,mutationCb:lt,stylesheetManager:ye,recordCrossOriginIframes:V,wrappedEmit:W});for(let N of ne||[])N.getMirror&&N.getMirror({nodeMirror:re,crossOriginIframeMirror:we.crossOriginIframeMirror,crossOriginIframeStyleMirror:we.crossOriginIframeStyleMirror});let mr=new Ss;Dr=new ws({recordCanvas:T,mutationCb:Hs,win:window,blockClass:i,blockSelector:n,mirror:re,sampling:S.canvas,dataURLOptions:y});let ut=new ys({mutationCb:lt,scrollCb:Ys,bypassOptions:{blockClass:i,blockSelector:n,maskTextClass:a,maskTextSelector:u,inlineStylesheet:c,maskInputOptions:ge,dataURLOptions:y,maskTextFn:d,maskInputFn:p,recordCanvas:T,inlineImages:Q,sampling:S,slimDOMOptions:oe,iframeManager:we,stylesheetManager:ye,canvasManager:Dr,keepIframeSrcFn:M,processedNodeManager:mr},mirror:re});Ft=(N=!1)=>{if(!O)return;W({type:x.Meta,data:{href:window.location.href,width:Ao(),height:Ro()}},N),ye.reset(),ut.init(),be.forEach(U=>U.lock());let te=Za(document,{mirror:re,blockClass:i,blockSelector:n,maskTextClass:a,maskTextSelector:u,inlineStylesheet:c,maskAllInputs:ge,maskTextFn:d,maskInputFn:p,slimDOM:oe,dataURLOptions:y,recordCanvas:T,inlineImages:Q,onSerialize:U=>{To(U,re)&&we.addIframe(U),_o(U,re)&&ye.trackLinkElement(U),hs(U)&&ut.addShadowRoot(C.shadowRoot(U),document)},onIframeLoad:(U,B)=>{we.attachIframe(U,B),ut.observeAttachShadow(U)},onStylesheetLoad:(U,B)=>{ye.attachLinkElement(U,B)},keepIframeSrcFn:M});if(!te)return console.warn("Failed to snapshot the document");W({type:x.FullSnapshot,data:{node:te,initialOffset:No(window)}},N),be.forEach(U=>U.unlock()),document.adoptedStyleSheets&&document.adoptedStyleSheets.length>0&&ye.adoptStyleSheets(document.adoptedStyleSheets,re.getId(document))};try{let N=[],te=B=>{var K;return E(pf)({mutationCb:lt,mousemoveCb:(D,gr)=>W({type:x.IncrementalSnapshot,data:{source:gr,positions:D}}),mouseInteractionCb:D=>W({type:x.IncrementalSnapshot,data:{source:I.MouseInteraction,...D}}),scrollCb:Ys,viewportResizeCb:D=>W({type:x.IncrementalSnapshot,data:{source:I.ViewportResize,...D}}),inputCb:D=>W({type:x.IncrementalSnapshot,data:{source:I.Input,...D}}),mediaInteractionCb:D=>W({type:x.IncrementalSnapshot,data:{source:I.MediaInteraction,...D}}),styleSheetRuleCb:D=>W({type:x.IncrementalSnapshot,data:{source:I.StyleSheetRule,...D}}),styleDeclarationCb:D=>W({type:x.IncrementalSnapshot,data:{source:I.StyleDeclaration,...D}}),canvasMutationCb:Hs,fontCb:D=>W({type:x.IncrementalSnapshot,data:{source:I.Font,...D}}),selectionCb:D=>{W({type:x.IncrementalSnapshot,data:{source:I.Selection,...D}})},customElementCb:D=>{W({type:x.IncrementalSnapshot,data:{source:I.CustomElement,...D}})},blockClass:i,ignoreClass:o,ignoreSelector:l,maskTextClass:a,maskTextSelector:u,maskInputOptions:ge,inlineStylesheet:c,sampling:S,recordDOM:O,recordCanvas:T,inlineImages:Q,userTriggeredOnInput:z,collectFonts:Y,doc:B,maskInputFn:p,maskTextFn:d,keepIframeSrcFn:M,blockSelector:n,slimDOMOptions:oe,dataURLOptions:y,mirror:re,iframeManager:we,stylesheetManager:ye,shadowDomManager:ut,processedNodeManager:mr,canvasManager:Dr,ignoreCSSAttributes:Le,plugins:((K=ne?.filter(D=>D.observer))==null?void 0:K.map(D=>({observer:D.observer,options:D.options,callback:gr=>W({type:x.Plugin,data:{plugin:D.name,payload:gr}})})))||[]},f)};we.addLoadListener(B=>{try{N.push(te(B.contentDocument))}catch(K){console.warn(K)}});let U=()=>{Ft(),N.push(te(document)),tr=!0};return["interactive","complete"].includes(document.readyState)?U():(N.push(Z("DOMContentLoaded",()=>{W({type:x.DomContentLoaded,data:{}}),$==="DOMContentLoaded"&&U()})),N.push(Z("load",()=>{W({type:x.Load,data:{}}),$==="load"&&U()},window))),()=>{N.forEach(B=>{try{B()}catch(K){String(K).toLowerCase().includes("cross-origin")||console.warn(K)}}),mr.destroy(),tr=!1,ef()}}catch(N){console.warn(N)}}pe.addCustomEvent=(s,e)=>{if(!tr)throw new Error("please add custom event after start recording");W({type:x.Custom,data:{tag:s,payload:e}})};pe.freezePage=()=>{be.forEach(s=>s.freeze())};pe.takeFullSnapshot=s=>{if(!tr)throw new Error("please take full snapshot after start recording");Ft(s)};pe.mirror=re;var Hi;(function(s){s[s.NotStarted=0]="NotStarted",s[s.Running=1]="Running",s[s.Stopped=2]="Stopped"})(Hi||(Hi={}));var id=5*1e3;var{addCustomEvent:nd}=pe,{freezePage:od}=pe,{takeFullSnapshot:ad}=pe;var dr=null;function jo(s,e){if(typeof document>"u"||typeof window>"u")return;let t=s.maxEventsPerChunk??200,r=s.flushIntervalMs??1e4,i=s.maxPayloadBytes??1048576,n=[],o=0,l=null;function a(g){if(n.length===0)return;let p=n[0].timestamp,d=n[n.length-1].timestamp,f=JSON.stringify(n);f.length>i,e({chunk_index:o,events_count:n.length,is_full_snapshot:g,started_at:new Date(p).toISOString(),ended_at:new Date(d).toISOString(),payload:f}),o+=1,n=[]}function u(g){let p=g||n.some(d=>d.type===2);n.length>=t?a(p):g&&n.length>0&&a(!0)}let c=pe({emit(g,p){n.push(g),u(!!p)},checkoutEveryNms:r,maskAllInputs:s.maskAllInputs??!0,maskTextSelector:s.maskTextSelector??"[data-openpanel-replay-mask]",blockSelector:s.blockSelector??"[data-openpanel-replay-block]",blockClass:s.blockClass,ignoreSelector:s.ignoreSelector});l=setInterval(()=>{if(n.length>0){let g=n.some(p=>p.type===2);a(g)}},r);function h(){if(document.visibilityState==="hidden"&&n.length>0){let g=n.some(p=>p.type===2);a(g)}}function m(){if(n.length>0){let g=n.some(p=>p.type===2);a(g)}}document.addEventListener("visibilitychange",h),window.addEventListener("pagehide",m),dr=()=>{l&&(clearInterval(l),l=null),document.removeEventListener("visibilitychange",h),window.removeEventListener("pagehide",m),c?.(),dr=null}}function Yo(){dr&&dr()}return qo(If);})(); +/*! Bundled license information: + +rrweb/dist/rrweb.js: + (*! ***************************************************************************** + Copyright (c) Microsoft Corporation. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + ***************************************************************************** *) +*/ diff --git a/apps/public/public/op1.js b/apps/public/public/op1.js index b052cec1..91b21902 100644 --- a/apps/public/public/op1.js +++ b/apps/public/public/op1.js @@ -1 +1 @@ -"use strict";(()=>{var u=class{constructor(e){this.baseUrl=e.baseUrl,this.headers={"Content-Type":"application/json",...e.defaultHeaders},this.maxRetries=e.maxRetries??3,this.initialRetryDelay=e.initialRetryDelay??500}async resolveHeaders(){let e={};for(let[i,t]of Object.entries(this.headers)){let s=await t;s!==null&&(e[i]=s)}return e}addHeader(e,i){this.headers[e]=i}async post(e,i,t,s){try{let n=await fetch(e,{method:"POST",headers:await this.resolveHeaders(),body:i?JSON.stringify(i??{}):void 0,keepalive:!0,...t});if(n.status===401)return null;if(n.status!==200&&n.status!==202)throw new Error(`HTTP error! status: ${n.status}`);let r=await n.text();return r?JSON.parse(r):null}catch(n){if(ssetTimeout(a,r)),this.post(e,i,t,s+1)}return console.error("Max retries reached:",n),null}}async fetch(e,i,t={}){let s=`${this.baseUrl}${e}`;return this.post(s,i,t,0)}},l=class{constructor(e){this.options=e,this.queue=[];let i={"openpanel-client-id":e.clientId};e.clientSecret&&(i["openpanel-client-secret"]=e.clientSecret),i["openpanel-sdk-name"]=e.sdk||"node",i["openpanel-sdk-version"]=e.sdkVersion||"1.0.3",this.api=new u({baseUrl:e.apiUrl||"https://api.openpanel.dev",defaultHeaders:i})}init(){}ready(){this.options.waitForProfile=!1,this.flush()}async send(e){return this.options.disabled||this.options.filter&&!this.options.filter(e)?Promise.resolve():this.options.waitForProfile&&!this.profileId?(this.queue.push(e),Promise.resolve()):this.api.fetch("/track",e)}setGlobalProperties(e){this.global={...this.global,...e}}async track(e,i){return this.log("track event",e,i),this.send({type:"track",payload:{name:e,profileId:i?.profileId??this.profileId,properties:{...this.global??{},...i??{}}}})}async identify(e){if(this.log("identify user",e),e.profileId&&(this.profileId=e.profileId,this.flush()),Object.keys(e).length>1)return this.send({type:"identify",payload:{...e,properties:{...this.global,...e.properties}}})}async alias(e){}async increment(e){return this.send({type:"increment",payload:e})}async decrement(e){return this.send({type:"decrement",payload:e})}async revenue(e,i){let t=i?.deviceId;return delete i?.deviceId,this.track("revenue",{...i??{},...t?{__deviceId:t}:{},__revenue:e})}async fetchDeviceId(){return(await this.api.fetch("/track/device-id",void 0,{method:"GET",keepalive:!1}))?.deviceId??""}clear(){this.profileId=void 0}flush(){this.queue.forEach(e=>{this.send({...e,payload:{...e.payload,profileId:e.payload.profileId??this.profileId}})}),this.queue=[]}log(...e){this.options.debug&&console.log("[OpenPanel.dev]",...e)}};function h(e){return e.replace(/([-_][a-z])/gi,i=>i.toUpperCase().replace("-","").replace("_",""))}var p=class extends l{constructor(t){super({sdk:"web",sdkVersion:"1.0.6",...t});this.options=t;this.lastPath="";this.pendingRevenues=[];if(!this.isServer()){try{let s=sessionStorage.getItem("openpanel-pending-revenues");if(s){let n=JSON.parse(s);Array.isArray(n)&&(this.pendingRevenues=n)}}catch{this.pendingRevenues=[]}this.setGlobalProperties({__referrer:document.referrer}),this.options.trackScreenViews&&(this.trackScreenViews(),setTimeout(()=>this.screenView(),0)),this.options.trackOutgoingLinks&&this.trackOutgoingLinks(),this.options.trackAttributes&&this.trackAttributes()}}debounce(t,s){clearTimeout(this.debounceTimer),this.debounceTimer=setTimeout(t,s)}isServer(){return typeof document>"u"}trackOutgoingLinks(){this.isServer()||document.addEventListener("click",t=>{let s=t.target,n=s.closest("a");if(n&&s){let r=n.getAttribute("href");if(r?.startsWith("http"))try{let a=new URL(r),o=window.location.hostname;a.hostname!==o&&super.track("link_out",{href:r,text:n.innerText||n.getAttribute("title")||s.getAttribute("alt")||s.getAttribute("title")})}catch{}}})}trackScreenViews(){if(this.isServer())return;let t=history.pushState;history.pushState=function(...a){let o=t.apply(this,a);return window.dispatchEvent(new Event("pushstate")),window.dispatchEvent(new Event("locationchange")),o};let s=history.replaceState;history.replaceState=function(...a){let o=s.apply(this,a);return window.dispatchEvent(new Event("replacestate")),window.dispatchEvent(new Event("locationchange")),o},window.addEventListener("popstate",()=>{window.dispatchEvent(new Event("locationchange"))});let n=()=>this.debounce(()=>this.screenView(),50);this.options.trackHashChanges?window.addEventListener("hashchange",n):window.addEventListener("locationchange",n)}trackAttributes(){this.isServer()||document.addEventListener("click",t=>{let s=t.target,n=s.closest("button"),r=s.closest("a"),a=n?.getAttribute("data-track")?n:r?.getAttribute("data-track")?r:null;if(a){let o={};for(let c of a.attributes)c.name.startsWith("data-")&&c.name!=="data-track"&&(o[h(c.name.replace(/^data-/,""))]=c.value);let d=a.getAttribute("data-track");d&&super.track(d,o)}})}screenView(t,s){if(this.isServer())return;let n,r;typeof t=="string"?(n=t,r=s):(n=window.location.href,r=t),this.lastPath!==n&&(this.lastPath=n,super.track("screen_view",{...r??{},__path:n,__title:document.title}))}async flushRevenue(){let t=this.pendingRevenues.map(s=>super.revenue(s.amount,s.properties));await Promise.all(t),this.clearRevenue()}clearRevenue(){if(this.pendingRevenues=[],!this.isServer())try{sessionStorage.removeItem("openpanel-pending-revenues")}catch{}}pendingRevenue(t,s){if(this.pendingRevenues.push({amount:t,properties:s}),!this.isServer())try{sessionStorage.setItem("openpanel-pending-revenues",JSON.stringify(this.pendingRevenues))}catch{}}};(e=>{if(e.op){let i=e.op.q||[],t=new p(i.shift()[1]);i.forEach(n=>{n[0]in t&&t[n[0]](...n.slice(1))});let s=new Proxy((n,...r)=>{let a=t[n]?t[n].bind(t):void 0;typeof a=="function"?a(...r):console.warn(`OpenPanel: ${n} is not a function`)},{get(n,r){if(r==="q")return;let a=t[r];return typeof a=="function"?a.bind(t):a}});e.op=s,e.openpanel=t}})(window);})(); +"use strict";(()=>{var g=Object.create;var h=Object.defineProperty;var m=Object.getOwnPropertyDescriptor;var v=Object.getOwnPropertyNames;var P=Object.getPrototypeOf,k=Object.prototype.hasOwnProperty;var b=(a,t)=>()=>(t||a((t={exports:{}}).exports,t),t.exports);var R=(a,t,e,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of v(t))!k.call(a,n)&&n!==e&&h(a,n,{get:()=>t[n],enumerable:!(s=m(t,n))||s.enumerable});return a};var w=(a,t,e)=>(e=a!=null?g(P(a)):{},R(t||!a||!a.__esModule?h(e,"default",{value:a,enumerable:!0}):e,a));var f=b((A,y)=>{"use strict";y.exports={}});var c=class{constructor(t){this.baseUrl=t.baseUrl,this.headers={"Content-Type":"application/json",...t.defaultHeaders},this.maxRetries=t.maxRetries??3,this.initialRetryDelay=t.initialRetryDelay??500}async resolveHeaders(){let t={};for(let[e,s]of Object.entries(this.headers)){let n=await s;n!==null&&(t[e]=n)}return t}addHeader(t,e){this.headers[t]=e}async post(t,e,s,n){try{let r=await fetch(t,{method:"POST",headers:await this.resolveHeaders(),body:e?JSON.stringify(e??{}):void 0,keepalive:!0,...s});if(r.status===401)return null;if(r.status!==200&&r.status!==202)throw new Error(`HTTP error! status: ${r.status}`);let i=await r.text();return i?JSON.parse(i):null}catch(r){if(nsetTimeout(o,i)),this.post(t,e,s,n+1)}return console.error("Max retries reached:",r),null}}async fetch(t,e,s={}){let n=`${this.baseUrl}${t}`;return this.post(n,e,s,0)}};var l=class{constructor(t){this.options=t;this.queue=[];let e={"openpanel-client-id":t.clientId};t.clientSecret&&(e["openpanel-client-secret"]=t.clientSecret),e["openpanel-sdk-name"]=t.sdk||"node",e["openpanel-sdk-version"]=t.sdkVersion||process.env.SDK_VERSION,this.api=new c({baseUrl:t.apiUrl||"https://api.openpanel.dev",defaultHeaders:e})}init(){}ready(){this.options.waitForProfile=!1,this.flush()}shouldQueue(t){return!!(t.type==="replay"&&!this.sessionId||this.options.waitForProfile&&!this.profileId)}async send(t){if(this.options.disabled||this.options.filter&&!this.options.filter(t))return Promise.resolve();if(this.shouldQueue(t))return this.queue.push(t),Promise.resolve();let e=await this.api.fetch("/track",t);this.deviceId=e?.deviceId;let s=!!this.sessionId;return this.sessionId=e?.sessionId,!s&&this.sessionId&&this.flush(),e}setGlobalProperties(t){this.global={...this.global,...t}}async track(t,e){return this.log("track event",t,e),this.send({type:"track",payload:{name:t,profileId:e?.profileId??this.profileId,properties:{...this.global??{},...e??{}}}})}async identify(t){if(this.log("identify user",t),t.profileId&&(this.profileId=t.profileId,this.flush()),Object.keys(t).length>1)return this.send({type:"identify",payload:{...t,properties:{...this.global,...t.properties}}})}async alias(t){}async increment(t){return this.send({type:"increment",payload:t})}async decrement(t){return this.send({type:"decrement",payload:t})}async revenue(t,e){let s=e?.deviceId;return delete e?.deviceId,this.track("revenue",{...e??{},...s?{__deviceId:s}:{},__revenue:t})}getDeviceId(){return this.deviceId??""}getSessionId(){return this.sessionId??""}async fetchDeviceId(){return Promise.resolve(this.deviceId??"")}clear(){this.profileId=void 0,this.deviceId=void 0,this.sessionId=void 0}flush(){let t=[];for(let e of this.queue){if(this.shouldQueue(e)){t.push(e);continue}let s=e.type==="replay"?e.payload:{...e.payload,profileId:"profileId"in e.payload?e.payload.profileId??this.profileId:this.profileId};this.send({...e,payload:s})}this.queue=t}log(...t){this.options.debug&&console.log("[OpenPanel.dev]",...t)}};function I(a){return a.replace(/([-_][a-z])/gi,t=>t.toUpperCase().replace("-","").replace("_",""))}var p=class extends l{constructor(e){super({sdk:"web",sdkVersion:"1.0.9",...e});this.options=e;this.lastPath="";this.pendingRevenues=[];if(!this.isServer()){try{let s=sessionStorage.getItem("openpanel-pending-revenues");if(s){let n=JSON.parse(s);Array.isArray(n)&&(this.pendingRevenues=n)}}catch{this.pendingRevenues=[]}if(this.setGlobalProperties({__referrer:document.referrer}),this.options.trackScreenViews&&(this.trackScreenViews(),setTimeout(()=>this.screenView(),0)),this.options.trackOutgoingLinks&&this.trackOutgoingLinks(),this.options.trackAttributes&&this.trackAttributes(),this.options.sessionReplay?.enabled){let s=this.options.sessionReplay.sampleRate??1;Math.random(){r&&r.startReplayRecorder(this.options.sessionReplay,i=>{this.send({type:"replay",payload:{...i}})})})}}}async loadReplayModule(){try{{let e=this.options.sessionReplay?.scriptUrl??"https://openpanel.dev/op1-replay.js";return window.__openpanel_replay?window.__openpanel_replay:new Promise(s=>{let n=document.createElement("script");n.src=e,n.onload=()=>{s(window.__openpanel_replay??null)},n.onerror=()=>{console.warn("[OpenPanel] Failed to load replay script from",e),s(null)},document.head.appendChild(n)})}return await Promise.resolve().then(()=>w(f(),1))}catch(e){return console.warn("[OpenPanel] Failed to load replay module",e),null}}debounce(e,s){clearTimeout(this.debounceTimer),this.debounceTimer=setTimeout(e,s)}isServer(){return typeof document>"u"}trackOutgoingLinks(){this.isServer()||document.addEventListener("click",e=>{let s=e.target,n=s.closest("a");if(n&&s){let r=n.getAttribute("href");if(r?.startsWith("http"))try{let i=new URL(r),o=window.location.hostname;i.hostname!==o&&super.track("link_out",{href:r,text:n.innerText||n.getAttribute("title")||s.getAttribute("alt")||s.getAttribute("title")})}catch{}}})}trackScreenViews(){if(this.isServer())return;let e=history.pushState;history.pushState=function(...i){let o=e.apply(this,i);return window.dispatchEvent(new Event("pushstate")),window.dispatchEvent(new Event("locationchange")),o};let s=history.replaceState;history.replaceState=function(...i){let o=s.apply(this,i);return window.dispatchEvent(new Event("replacestate")),window.dispatchEvent(new Event("locationchange")),o},window.addEventListener("popstate",()=>{window.dispatchEvent(new Event("locationchange"))});let n=()=>this.debounce(()=>this.screenView(),50);this.options.trackHashChanges?window.addEventListener("hashchange",n):window.addEventListener("locationchange",n)}trackAttributes(){this.isServer()||document.addEventListener("click",e=>{let s=e.target,n=s.closest("button"),r=s.closest("a"),i=n?.getAttribute("data-track")?n:r?.getAttribute("data-track")?r:null;if(i){let o={};for(let d of i.attributes)d.name.startsWith("data-")&&d.name!=="data-track"&&(o[I(d.name.replace(/^data-/,""))]=d.value);let u=i.getAttribute("data-track");u&&super.track(u,o)}})}screenView(e,s){if(this.isServer())return;let n,r;typeof e=="string"?(n=e,r=s):(n=window.location.href,r=e),this.lastPath!==n&&(this.lastPath=n,super.track("screen_view",{...r??{},__path:n,__title:document.title}))}async flushRevenue(){let e=this.pendingRevenues.map(s=>super.revenue(s.amount,s.properties));await Promise.all(e),this.clearRevenue()}clearRevenue(){if(this.pendingRevenues=[],!this.isServer())try{sessionStorage.removeItem("openpanel-pending-revenues")}catch{}}pendingRevenue(e,s){if(this.pendingRevenues.push({amount:e,properties:s}),!this.isServer())try{sessionStorage.setItem("openpanel-pending-revenues",JSON.stringify(this.pendingRevenues))}catch{}}};(a=>{if(a.op){let t=a.op.q||[],e=new p(t.shift()[1]);t.forEach(n=>{n[0]in e&&e[n[0]](...n.slice(1))});let s=new Proxy((n,...r)=>{let i=e[n]?e[n].bind(e):void 0;typeof i=="function"?i(...r):console.warn(`OpenPanel: ${n} is not a function`)},{get(n,r){if(r==="q")return;let i=e[r];return typeof i=="function"?i.bind(e):i}});a.op=s,a.openpanel=e}})(window);})(); diff --git a/apps/public/src/app/layout.tsx b/apps/public/src/app/layout.tsx index 8c86b308..d6fe60df 100644 --- a/apps/public/src/app/layout.tsx +++ b/apps/public/src/app/layout.tsx @@ -1,9 +1,9 @@ -import { TooltipProvider } from '@/components/ui/tooltip'; -import { getRootMetadata } from '@/lib/metadata'; -import { cn } from '@/lib/utils'; import { RootProvider } from 'fumadocs-ui/provider/next'; import type { Metadata, Viewport } from 'next'; import { Geist, Geist_Mono } from 'next/font/google'; +import { TooltipProvider } from '@/components/ui/tooltip'; +import { getRootMetadata } from '@/lib/metadata'; +import { cn } from '@/lib/utils'; import './global.css'; import { OpenPanelComponent } from '@openpanel/nextjs'; @@ -31,22 +31,20 @@ export const metadata: Metadata = getRootMetadata(); export default function Layout({ children }: { children: React.ReactNode }) { return ( - + {children} {process.env.NEXT_PUBLIC_OP_CLIENT_ID && ( )} diff --git a/apps/start/package.json b/apps/start/package.json index f28035da..c496ae4d 100644 --- a/apps/start/package.json +++ b/apps/start/package.json @@ -38,9 +38,10 @@ "@openpanel/integrations": "workspace:^", "@openpanel/json": "workspace:*", "@openpanel/payments": "workspace:*", + "@openpanel/sdk": "^1.0.8", "@openpanel/sdk-info": "workspace:^", "@openpanel/validation": "workspace:^", - "@openpanel/web": "^1.0.1", + "@openpanel/web": "^1.0.12", "@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-alert-dialog": "^1.1.15", "@radix-ui/react-aspect-ratio": "^1.1.7", @@ -141,6 +142,7 @@ "remark-math": "^6.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", + "rrweb-player": "2.0.0-alpha.20", "short-unique-id": "^5.0.3", "slugify": "^1.6.6", "sonner": "^1.4.0", diff --git a/apps/start/src/components/events/table/columns.tsx b/apps/start/src/components/events/table/columns.tsx index 4e58244e..203d4f16 100644 --- a/apps/start/src/components/events/table/columns.tsx +++ b/apps/start/src/components/events/table/columns.tsx @@ -89,7 +89,7 @@ export function useColumns() { projectId: row.original.projectId, }); }} - className="font-medium" + className="font-medium hover:underline" > {renderName()} @@ -144,10 +144,21 @@ export function useColumns() { { accessorKey: 'sessionId', header: 'Session ID', - size: 320, + size: 100, meta: { hidden: true, }, + cell({ row }) { + const { sessionId } = row.original; + return ( + + {sessionId.slice(0,6)} + + ); + }, }, { accessorKey: 'deviceId', diff --git a/apps/start/src/components/sessions/replay/browser-chrome.tsx b/apps/start/src/components/sessions/replay/browser-chrome.tsx new file mode 100644 index 00000000..dfb7d38c --- /dev/null +++ b/apps/start/src/components/sessions/replay/browser-chrome.tsx @@ -0,0 +1,42 @@ +import { cn } from '@/utils/cn'; +import type { ReactNode } from 'react'; + +export function BrowserChrome({ + url, + children, + right, + controls = ( +
+
+
+
+
+ ), + className, +}: { + url?: ReactNode; + children: ReactNode; + right?: ReactNode; + controls?: ReactNode; + className?: string; +}) { + return ( +
+
+ {controls} + {url !== false && ( +
+ {url} +
+ )} + {right} +
+ {children} +
+ ); +} diff --git a/apps/start/src/components/sessions/replay/index.tsx b/apps/start/src/components/sessions/replay/index.tsx new file mode 100644 index 00000000..70fec16d --- /dev/null +++ b/apps/start/src/components/sessions/replay/index.tsx @@ -0,0 +1,242 @@ +import type { IServiceEvent } from '@openpanel/db'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { Maximize2, Minimize2 } from 'lucide-react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { BrowserChrome } from './browser-chrome'; +import { ReplayTime } from './replay-controls'; +import { ReplayTimeline } from './replay-timeline'; +import { getEventOffsetMs } from './replay-utils'; +import { + ReplayProvider, + useCurrentTime, + useReplayContext, +} from '@/components/sessions/replay/replay-context'; +import { ReplayEventFeed } from '@/components/sessions/replay/replay-event-feed'; +import { ReplayPlayer } from '@/components/sessions/replay/replay-player'; +import { useTRPC } from '@/integrations/trpc/react'; + +function BrowserUrlBar({ events }: { events: IServiceEvent[] }) { + const { startTime } = useReplayContext(); + const currentTime = useCurrentTime(250); + + const currentUrl = useMemo(() => { + if (startTime == null || !events.length) { + return ''; + } + + const withOffset = events + .map((ev) => ({ + event: ev, + offsetMs: getEventOffsetMs(ev, startTime), + })) + .filter(({ offsetMs }) => offsetMs >= -10_000 && offsetMs <= currentTime) + .sort((a, b) => a.offsetMs - b.offsetMs); + + const latest = withOffset.at(-1); + if (!latest) { + return ''; + } + + const { origin = '', path = '/' } = latest.event; + return `${origin}${path}`; + }, [events, currentTime, startTime]); + + return {currentUrl}; +} + +/** + * Feeds remaining chunks into the player after it's ready. + * Receives already-fetched chunks from the initial batch, then pages + * through the rest using replayChunksFrom. + */ +function ReplayChunkLoader({ + sessionId, + projectId, + fromIndex, +}: { + sessionId: string; + projectId: string; + fromIndex: number; +}) { + const trpc = useTRPC(); + const queryClient = useQueryClient(); + const { addEvent, refreshDuration } = useReplayContext(); + + useEffect(() => { + function recursive(fromIndex: number) { + queryClient + .fetchQuery( + trpc.session.replayChunksFrom.queryOptions({ + sessionId, + projectId, + fromIndex, + }) + ) + .then((res) => { + res.data.forEach((row) => { + row?.events?.forEach((event) => { + addEvent(event); + }); + }); + refreshDuration(); + if (res.hasMore) { + recursive(fromIndex + res.data.length); + } + }) + .catch(() => { + // chunk loading failed — replay may be incomplete + }); + } + + recursive(fromIndex); + }, []); + + return null; +} + +function FullscreenButton({ + containerRef, +}: { + containerRef: React.RefObject; +}) { + const [isFullscreen, setIsFullscreen] = useState(false); + + useEffect(() => { + const onChange = () => setIsFullscreen(!!document.fullscreenElement); + document.addEventListener('fullscreenchange', onChange); + return () => document.removeEventListener('fullscreenchange', onChange); + }, []); + + const toggle = useCallback(() => { + if (!containerRef.current) { + return; + } + if (document.fullscreenElement) { + document.exitFullscreen(); + } else { + containerRef.current.requestFullscreen(); + } + }, [containerRef]); + + return ( + + ); +} + +function ReplayContent({ + sessionId, + projectId, +}: { + sessionId: string; + projectId: string; +}) { + const trpc = useTRPC(); + const containerRef = useRef(null); + + const { data: eventsData } = useQuery( + trpc.event.events.queryOptions({ + projectId, + sessionId, + filters: [], + columnVisibility: {}, + }) + ); + + // Fetch first batch of chunks (includes chunk 0 for player init + more) + const { data: firstBatch, isLoading: replayLoading } = useQuery( + trpc.session.replayChunksFrom.queryOptions({ + sessionId, + projectId, + fromIndex: 0, + }) + ); + + const events = eventsData?.data ?? []; + const playerEvents = + firstBatch?.data.flatMap((row) => row?.events ?? []) ?? []; + const hasMore = firstBatch?.hasMore ?? false; + const hasReplay = playerEvents.length !== 0; + + function renderReplay() { + if (replayLoading) { + return ( +
+
+
Loading session replay
+
+ ); + } + if (hasReplay) { + return ; + } + return ( +
+ No replay data available for this session. +
+ ); + } + + return ( + +
+
+ + {hasReplay && } + +
+ } + url={ + hasReplay ? ( + + ) : ( + about:blank + ) + } + > + {renderReplay()} + {hasReplay && } + +
+
+
+ +
+
+
+ {hasReplay && hasMore && ( + + )} + + ); +} + +export function ReplayShell({ + sessionId, + projectId, +}: { + sessionId: string; + projectId: string; +}) { + return ; +} diff --git a/apps/start/src/components/sessions/replay/replay-context.tsx b/apps/start/src/components/sessions/replay/replay-context.tsx new file mode 100644 index 00000000..d60b5776 --- /dev/null +++ b/apps/start/src/components/sessions/replay/replay-context.tsx @@ -0,0 +1,205 @@ +import { + type ReactNode, + createContext, + useCallback, + useContext, + useEffect, + useRef, + useState, +} from 'react'; + +export interface ReplayPlayerInstance { + play: () => void; + pause: () => void; + toggle: () => void; + goto: (timeOffset: number, play?: boolean) => void; + setSpeed: (speed: number) => void; + getMetaData: () => { startTime: number; endTime: number; totalTime: number }; + getReplayer: () => { getCurrentTime: () => number }; + addEvent: (event: { type: number; data: unknown; timestamp: number }) => void; + addEventListener: (event: string, handler: (e: { payload: unknown }) => void) => void; + $set?: (props: Record) => void; + $destroy?: () => void; +} + +type CurrentTimeListener = (t: number) => void; + +interface ReplayContextValue { + // High-frequency value — read via ref, not state. Use subscribeToCurrentTime + // or useCurrentTime() to get updates without causing 60fps re-renders. + currentTimeRef: React.MutableRefObject; + subscribeToCurrentTime: (fn: CurrentTimeListener) => () => void; + // Low-frequency state (safe to consume directly) + isPlaying: boolean; + duration: number; + startTime: number | null; + isReady: boolean; + // Playback controls + play: () => void; + pause: () => void; + toggle: () => void; + seek: (timeMs: number) => void; + setSpeed: (speed: number) => void; + // Lazy chunk loading + addEvent: (event: { type: number; data: unknown; timestamp: number }) => void; + refreshDuration: () => void; + // Called by ReplayPlayer to register/unregister the rrweb instance + onPlayerReady: (player: ReplayPlayerInstance, playerStartTime: number) => void; + onPlayerDestroy: () => void; + // State setters exposed so ReplayPlayer can wire rrweb event listeners + setCurrentTime: (t: number) => void; + setIsPlaying: (p: boolean) => void; + setDuration: (d: number) => void; +} + +const ReplayContext = createContext(null); + +const SPEED_OPTIONS = [0.5, 1, 2, 4, 8] as const; + +export function useReplayContext() { + const ctx = useContext(ReplayContext); + if (!ctx) { + throw new Error('useReplayContext must be used within ReplayProvider'); + } + return ctx; +} + +/** + * Subscribe to currentTime updates at a throttled rate. + * intervalMs=0 means every tick (use for the progress bar DOM writes). + * intervalMs=250 means 4 updates/second (use for text displays). + */ +export function useCurrentTime(intervalMs = 0): number { + const { currentTimeRef, subscribeToCurrentTime } = useReplayContext(); + const [time, setTime] = useState(currentTimeRef.current); + const lastUpdateRef = useRef(0); + + useEffect(() => { + return subscribeToCurrentTime((t) => { + if (intervalMs === 0) { + setTime(t); + return; + } + const now = performance.now(); + if (now - lastUpdateRef.current >= intervalMs) { + lastUpdateRef.current = now; + setTime(t); + } + }); + }, [subscribeToCurrentTime, intervalMs]); + + return time; +} + +export function ReplayProvider({ children }: { children: ReactNode }) { + const playerRef = useRef(null); + const isPlayingRef = useRef(false); + const currentTimeRef = useRef(0); + const listenersRef = useRef>(new Set()); + + const [isPlaying, setIsPlaying] = useState(false); + const [duration, setDuration] = useState(0); + const [startTime, setStartTime] = useState(null); + const [isReady, setIsReady] = useState(false); + + const setIsPlayingWithRef = useCallback((playing: boolean) => { + isPlayingRef.current = playing; + setIsPlaying(playing); + }, []); + + const subscribeToCurrentTime = useCallback((fn: CurrentTimeListener) => { + listenersRef.current.add(fn); + return () => { + listenersRef.current.delete(fn); + }; + }, []); + + // Called by ReplayPlayer on every ui-update-current-time tick. + // Updates the ref and notifies subscribers — no React state update here. + const setCurrentTime = useCallback((t: number) => { + currentTimeRef.current = t; + for (const fn of listenersRef.current) { + fn(t); + } + }, []); + + const onPlayerReady = useCallback( + (player: ReplayPlayerInstance, playerStartTime: number) => { + playerRef.current = player; + setStartTime(playerStartTime); + currentTimeRef.current = 0; + setIsPlayingWithRef(false); + setIsReady(true); + }, + [setIsPlayingWithRef], + ); + + const onPlayerDestroy = useCallback(() => { + playerRef.current = null; + setIsReady(false); + currentTimeRef.current = 0; + setDuration(0); + setStartTime(null); + setIsPlayingWithRef(false); + }, [setIsPlayingWithRef]); + + const play = useCallback(() => { + playerRef.current?.play(); + }, []); + + const pause = useCallback(() => { + playerRef.current?.pause(); + }, []); + + const toggle = useCallback(() => { + playerRef.current?.toggle(); + }, []); + + const seek = useCallback((timeMs: number) => { + playerRef.current?.goto(timeMs, isPlayingRef.current); + }, []); + + const setSpeed = useCallback((s: number) => { + if (!SPEED_OPTIONS.includes(s as (typeof SPEED_OPTIONS)[number])) return; + playerRef.current?.setSpeed(s); + }, []); + + const addEvent = useCallback( + (event: { type: number; data: unknown; timestamp: number }) => { + playerRef.current?.addEvent(event); + }, + [], + ); + + const refreshDuration = useCallback(() => { + const total = playerRef.current?.getMetaData().totalTime ?? 0; + if (total > 0) setDuration(total); + }, []); + + const value: ReplayContextValue = { + currentTimeRef, + subscribeToCurrentTime, + isPlaying, + duration, + startTime, + isReady, + play, + pause, + toggle, + seek, + setSpeed, + addEvent, + refreshDuration, + onPlayerReady, + onPlayerDestroy, + setCurrentTime, + setIsPlaying: setIsPlayingWithRef, + setDuration, + }; + + return ( + {children} + ); +} + +export { SPEED_OPTIONS }; diff --git a/apps/start/src/components/sessions/replay/replay-controls.tsx b/apps/start/src/components/sessions/replay/replay-controls.tsx new file mode 100644 index 00000000..c6a6e1c2 --- /dev/null +++ b/apps/start/src/components/sessions/replay/replay-controls.tsx @@ -0,0 +1,33 @@ +import { useCurrentTime, useReplayContext } from '@/components/sessions/replay/replay-context'; +import { Button } from '@/components/ui/button'; +import { Pause, Play } from 'lucide-react'; +import { formatDuration } from './replay-utils'; + +export function ReplayTime() { + const { duration } = useReplayContext(); + const currentTime = useCurrentTime(250); + + return ( + + {formatDuration(currentTime)} / {formatDuration(duration)} + + ); +} + +export function ReplayPlayPauseButton() { + const { isPlaying, isReady, toggle } = useReplayContext(); + + if (!isReady) return null; + + return ( + + ); +} diff --git a/apps/start/src/components/sessions/replay/replay-event-feed.tsx b/apps/start/src/components/sessions/replay/replay-event-feed.tsx new file mode 100644 index 00000000..03fcf4da --- /dev/null +++ b/apps/start/src/components/sessions/replay/replay-event-feed.tsx @@ -0,0 +1,107 @@ +import { useCurrentTime, useReplayContext } from '@/components/sessions/replay/replay-context'; +import { ReplayEventItem } from '@/components/sessions/replay/replay-event-item'; +import { ScrollArea } from '@/components/ui/scroll-area'; +import type { IServiceEvent } from '@openpanel/db'; +import { useEffect, useMemo, useRef } from 'react'; +import { BrowserChrome } from './browser-chrome'; +import { getEventOffsetMs } from './replay-utils'; + +type EventWithOffset = { event: IServiceEvent; offsetMs: number }; + +export function ReplayEventFeed({ events, replayLoading }: { events: IServiceEvent[]; replayLoading: boolean }) { + const { startTime, isReady, seek } = useReplayContext(); + const currentTime = useCurrentTime(100); + const viewportRef = useRef(null); + const prevCountRef = useRef(0); + + // Pre-sort events by offset once when events/startTime changes. + // This is the expensive part — done once, not on every tick. + const sortedEvents = useMemo(() => { + if (startTime == null || !isReady) return []; + return events + .map((ev) => ({ event: ev, offsetMs: getEventOffsetMs(ev, startTime) })) + .filter(({ offsetMs }) => offsetMs >= -10_000) + .sort((a, b) => a.offsetMs - b.offsetMs); + }, [events, startTime, isReady]); + + // Binary search to find how many events are visible at currentTime. + // O(log n) instead of O(n) filter on every tick. + const visibleCount = useMemo(() => { + let lo = 0; + let hi = sortedEvents.length; + while (lo < hi) { + const mid = (lo + hi) >>> 1; + if ((sortedEvents[mid]?.offsetMs ?? 0) <= currentTime) { + lo = mid + 1; + } else { + hi = mid; + } + } + return lo; + }, [sortedEvents, currentTime]); + + const visibleEvents = sortedEvents.slice(0, visibleCount); + const currentEventId = visibleEvents[visibleCount - 1]?.event.id ?? null; + + useEffect(() => { + const viewport = viewportRef.current; + if (!viewport || visibleEvents.length === 0) return; + + const isNewItem = visibleEvents.length > prevCountRef.current; + prevCountRef.current = visibleEvents.length; + + requestAnimationFrame(() => { + viewport.scrollTo({ + top: viewport.scrollHeight, + behavior: isNewItem ? 'smooth' : 'instant', + }); + }); + }, [visibleEvents.length]); + + return ( + Timeline} + className="h-full" + > + +
+ {visibleEvents.map(({ event, offsetMs }) => ( +
+ seek(Math.max(0, offsetMs))} + /> +
+ ))} + {!replayLoading && visibleEvents.length === 0 && ( +
+ Events will appear as the replay plays. +
+ )} + {replayLoading && + Array.from({ length: 5 }).map((_, i) => ( +
+
+
+
+
+
+
+ ))} + +
+ + + ); +} diff --git a/apps/start/src/components/sessions/replay/replay-event-item.tsx b/apps/start/src/components/sessions/replay/replay-event-item.tsx new file mode 100644 index 00000000..28a0102b --- /dev/null +++ b/apps/start/src/components/sessions/replay/replay-event-item.tsx @@ -0,0 +1,51 @@ +import { EventIcon } from '@/components/events/event-icon'; +import { cn } from '@/lib/utils'; +import type { IServiceEvent } from '@openpanel/db'; + +function formatTime(date: Date | string): string { + const d = date instanceof Date ? date : new Date(date); + const h = d.getHours().toString().padStart(2, '0'); + const m = d.getMinutes().toString().padStart(2, '0'); + const s = d.getSeconds().toString().padStart(2, '0'); + return `${h}:${m}:${s}`; +} + +export function ReplayEventItem({ + event, + isCurrent, + onClick, +}: { + event: IServiceEvent; + isCurrent: boolean; + onClick: () => void; +}) { + const displayName = + event.name === 'screen_view' && event.path + ? event.path + : event.name.replace(/_/g, ' '); + + return ( + + ); +} diff --git a/apps/start/src/components/sessions/replay/replay-player.tsx b/apps/start/src/components/sessions/replay/replay-player.tsx new file mode 100644 index 00000000..d80159cf --- /dev/null +++ b/apps/start/src/components/sessions/replay/replay-player.tsx @@ -0,0 +1,184 @@ +import { useReplayContext } from '@/components/sessions/replay/replay-context'; +import type { ReplayPlayerInstance } from '@/components/sessions/replay/replay-context'; +import { useEffect, useMemo, useRef, useState } from 'react'; + +import 'rrweb-player/dist/style.css'; + +/** rrweb meta event (type 4) carries the recorded viewport size */ +function getRecordedDimensions( + events: Array<{ type: number; data: unknown }>, +): { width: number; height: number } | null { + const meta = events.find((e) => e.type === 4); + if ( + meta && + typeof meta.data === 'object' && + meta.data !== null && + 'width' in meta.data && + 'height' in meta.data + ) { + const { width, height } = meta.data as { width: number; height: number }; + if (width > 0 && height > 0) return { width, height }; + } + return null; +} + +function calcDimensions( + containerWidth: number, + aspectRatio: number, +): { width: number; height: number } { + const maxHeight = window.innerHeight * 0.7; + const height = Math.min(Math.round(containerWidth / aspectRatio), maxHeight); + const width = Math.min(containerWidth, Math.round(height * aspectRatio)); + return { width, height }; +} + +export function ReplayPlayer({ + events, +}: { + events: Array<{ type: number; data: unknown; timestamp: number }>; +}) { + const containerRef = useRef(null); + const playerRef = useRef(null); + const { + onPlayerReady, + onPlayerDestroy, + setCurrentTime, + setIsPlaying, + setDuration, + } = useReplayContext(); + const [importError, setImportError] = useState(false); + + const recordedDimensions = useMemo( + () => getRecordedDimensions(events), + [events], + ); + + useEffect(() => { + if (!events.length || !containerRef.current) return; + + // Clear any previous player DOM + containerRef.current.innerHTML = ''; + + let mounted = true; + let player: ReplayPlayerInstance | null = null; + let handleVisibilityChange: (() => void) | null = null; + + const aspectRatio = recordedDimensions + ? recordedDimensions.width / recordedDimensions.height + : 16 / 9; + + const { width, height } = calcDimensions( + containerRef.current.offsetWidth, + aspectRatio, + ); + + import('rrweb-player') + .then((module) => { + if (!containerRef.current || !mounted) return; + + const PlayerConstructor = module.default; + player = new PlayerConstructor({ + target: containerRef.current, + props: { + events, + width, + height, + autoPlay: false, + showController: false, + speedOption: [0.5, 1, 2, 4, 8], + UNSAFE_replayCanvas: true, + skipInactive: false, + }, + }) as ReplayPlayerInstance; + + playerRef.current = player; + + // Track play state from replayer (getMetaData() does not expose isPlaying) + let playingState = false; + + // Wire rrweb's built-in event emitter — no RAF loop needed. + // Note: rrweb-player does NOT emit ui-update-duration; duration is + // read from getMetaData() on init and after each addEvent batch. + player.addEventListener('ui-update-current-time', (e) => { + const t = e.payload as number; + setCurrentTime(t); + }); + + player.addEventListener('ui-update-player-state', (e) => { + const playing = e.payload === 'playing'; + playingState = playing; + setIsPlaying(playing); + }); + + // Pause on tab hide; resume on show (prevents timer drift). + // getMetaData() does not expose isPlaying, so we use playingState + // kept in sync by ui-update-player-state above. + let wasPlaying = false; + handleVisibilityChange = () => { + if (!player) return; + if (document.hidden) { + wasPlaying = playingState; + if (wasPlaying) player.pause(); + } else { + if (wasPlaying) { + player.play(); + wasPlaying = false; + } + } + }; + document.addEventListener('visibilitychange', handleVisibilityChange); + + // Notify context — marks isReady = true and sets initial duration + const meta = player.getMetaData(); + if (meta.totalTime > 0) setDuration(meta.totalTime); + onPlayerReady(player, meta.startTime); + }) + .catch(() => { + if (mounted) setImportError(true); + }); + + const onWindowResize = () => { + if (!containerRef.current || !mounted || !playerRef.current?.$set) return; + const { width: w, height: h } = calcDimensions( + containerRef.current.offsetWidth, + aspectRatio, + ); + playerRef.current.$set({ width: w, height: h }); + }; + window.addEventListener('resize', onWindowResize); + + return () => { + mounted = false; + window.removeEventListener('resize', onWindowResize); + if (handleVisibilityChange) { + document.removeEventListener('visibilitychange', handleVisibilityChange); + } + if (player) { + player.pause(); + } + if (containerRef.current) { + containerRef.current.innerHTML = ''; + } + playerRef.current = null; + onPlayerDestroy(); + }; + }, [events, recordedDimensions, onPlayerReady, onPlayerDestroy, setCurrentTime, setIsPlaying, setDuration]); + + if (importError) { + return ( +
+ Failed to load replay player. +
+ ); + } + + return ( +
+
+
+ ); +} diff --git a/apps/start/src/components/sessions/replay/replay-timeline.tsx b/apps/start/src/components/sessions/replay/replay-timeline.tsx new file mode 100644 index 00000000..33a9a3f5 --- /dev/null +++ b/apps/start/src/components/sessions/replay/replay-timeline.tsx @@ -0,0 +1,261 @@ +import { useCurrentTime, useReplayContext } from '@/components/sessions/replay/replay-context'; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from '@/components/ui/tooltip'; +import type { IServiceEvent } from '@openpanel/db'; +import { AnimatePresence, motion } from 'framer-motion'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; + +import { EventIcon } from '@/components/events/event-icon'; +import { cn } from '@/lib/utils'; +import { ReplayPlayPauseButton } from './replay-controls'; +import { formatDuration, getEventOffsetMs } from './replay-utils'; + +export function ReplayTimeline({ events }: { events: IServiceEvent[] }) { + const { currentTimeRef, duration, startTime, isReady, seek, subscribeToCurrentTime } = + useReplayContext(); + // currentTime as React state is only needed for keyboard seeks (low frequency). + // The progress bar and thumb are updated directly via DOM refs to avoid re-renders. + const currentTime = useCurrentTime(250); + const trackRef = useRef(null); + const progressBarRef = useRef(null); + const thumbRef = useRef(null); + const [isDragging, setIsDragging] = useState(false); + const [hoverInfo, setHoverInfo] = useState<{ + pct: number; + timeMs: number; + } | null>(null); + const dragCleanupRef = useRef<(() => void) | null>(null); + const rafDragRef = useRef(null); + + // Clean up any in-progress drag listeners when the component unmounts + useEffect(() => { + return () => { + dragCleanupRef.current?.(); + }; + }, []); + + // Update progress bar and thumb directly via DOM on every tick — no React re-render. + useEffect(() => { + if (duration <= 0) return; + return subscribeToCurrentTime((t) => { + const pct = Math.max(0, Math.min(100, (t / duration) * 100)); + if (progressBarRef.current) { + progressBarRef.current.style.width = `${pct}%`; + } + if (thumbRef.current) { + thumbRef.current.style.left = `calc(${pct}% - 8px)`; + } + }); + }, [subscribeToCurrentTime, duration]); + + const getTimeFromClientX = useCallback( + (clientX: number) => { + if (!trackRef.current || duration <= 0) return null; + const rect = trackRef.current.getBoundingClientRect(); + if (rect.width <= 0 || !Number.isFinite(rect.width)) { + return null; + } + const x = clientX - rect.left; + const pct = Math.max(0, Math.min(1, x / rect.width)); + return { pct, timeMs: pct * duration }; + }, + [duration], + ); + + const handleTrackMouseMove = useCallback( + (e: React.MouseEvent) => { + if ((e.target as HTMLElement).closest('[data-timeline-event]')) { + setHoverInfo(null); + return; + } + const info = getTimeFromClientX(e.clientX); + if (info) setHoverInfo(info); + }, + [getTimeFromClientX], + ); + + const handleTrackMouseLeave = useCallback(() => { + if (!isDragging) setHoverInfo(null); + }, [isDragging]); + + const handleTrackMouseDown = useCallback( + (e: React.MouseEvent) => { + // Only handle direct clicks on the track, not on child elements like the thumb + if ( + e.target !== trackRef.current && + !(e.target as HTMLElement).closest('.replay-track-bg') + ) + return; + const info = getTimeFromClientX(e.clientX); + if (info) seek(info.timeMs); + }, + [getTimeFromClientX, seek], + ); + + const eventsWithOffset = useMemo( + () => + events + .map((ev) => ({ + event: ev, + offsetMs: startTime != null ? getEventOffsetMs(ev, startTime) : 0, + })) + .filter(({ offsetMs }) => offsetMs >= 0 && offsetMs <= duration), + [events, startTime, duration], + ); + + // Group events that are within 24px of each other on the track. + // We need the track width for pixel math — use a stable ref-based calculation. + const groupedEvents = useMemo(() => { + if (!eventsWithOffset.length || duration <= 0) return []; + + // Sort by offsetMs so we sweep left-to-right + const sorted = [...eventsWithOffset].sort((a, b) => a.offsetMs - b.offsetMs); + + // 24px in ms — recalculated from container width; fall back to 2% of duration + const trackWidth = trackRef.current?.offsetWidth ?? 600; + const thresholdMs = (24 / trackWidth) * duration; + + const groups: { items: typeof sorted; pct: number }[] = []; + for (const item of sorted) { + const last = groups[groups.length - 1]; + const lastPct = last ? (last.items[last.items.length - 1]!.offsetMs / duration) * 100 : -Infinity; + const thisPct = (item.offsetMs / duration) * 100; + + if (last && item.offsetMs - last.items[last.items.length - 1]!.offsetMs <= thresholdMs) { + last.items.push(item); + // Anchor the group at its first item's position + } else { + groups.push({ items: [item], pct: thisPct }); + } + // keep pct pointing at the first item (already set on push) + void lastPct; + } + + return groups; + }, [eventsWithOffset, duration]); + + if (!isReady || duration <= 0) return null; + + const progressPct = Math.max(0, Math.min(100, (currentTimeRef.current / duration) * 100)); + + return ( + +
+ +
+
{ + const step = 5000; + if (e.key === 'ArrowLeft') { + e.preventDefault(); + seek(Math.max(0, currentTime - step)); + } else if (e.key === 'ArrowRight') { + e.preventDefault(); + seek(Math.min(duration, currentTime + step)); + } + }} + > +
+
+
+
+ {/* Hover timestamp tooltip */} + + {hoverInfo && ( + + {/* Vertical line */} +
+ {/* Timestamp badge */} + + {formatDuration(hoverInfo.timeMs)} + + + )} + + {groupedEvents.map((group) => { + const first = group.items[0]!; + const isGroup = group.items.length > 1; + return ( + + + + + + {group.items.map(({ event: ev, offsetMs }) => ( +
+ + + {ev.name === 'screen_view' ? ev.path : ev.name} + + + {formatDuration(offsetMs)} + +
+ ))} +
+
+ ); + })} +
+
+
+ + ); +} diff --git a/apps/start/src/components/sessions/replay/replay-utils.ts b/apps/start/src/components/sessions/replay/replay-utils.ts new file mode 100644 index 00000000..415ebae2 --- /dev/null +++ b/apps/start/src/components/sessions/replay/replay-utils.ts @@ -0,0 +1,20 @@ +import type { IServiceEvent } from '@openpanel/db'; + +export function getEventOffsetMs( + event: IServiceEvent, + startTime: number, +): number { + const t = + typeof event.createdAt === 'object' && event.createdAt instanceof Date + ? event.createdAt.getTime() + : new Date(event.createdAt).getTime(); + return t - startTime; +} + +/** Format a duration in milliseconds as M:SS */ +export function formatDuration(ms: number): string { + const totalSeconds = Math.max(0, Math.floor(ms / 1000)); + const m = Math.floor(totalSeconds / 60); + const s = totalSeconds % 60; + return `${m}:${s.toString().padStart(2, '0')}`; +} diff --git a/apps/start/src/components/sessions/table/columns.tsx b/apps/start/src/components/sessions/table/columns.tsx index e5a3e0be..cd267afe 100644 --- a/apps/start/src/components/sessions/table/columns.tsx +++ b/apps/start/src/components/sessions/table/columns.tsx @@ -1,13 +1,12 @@ -import { ProjectLink } from '@/components/links'; -import { SerieIcon } from '@/components/report-chart/common/serie-icon'; -import { formatDateTime, formatTimeAgoOrDateTime } from '@/utils/date'; -import type { ColumnDef } from '@tanstack/react-table'; - -import { ColumnCreatedAt } from '@/components/column-created-at'; -import { ProfileAvatar } from '@/components/profiles/profile-avatar'; -import { getProfileName } from '@/utils/getters'; import { round } from '@openpanel/common'; import type { IServiceSession } from '@openpanel/db'; +import type { ColumnDef } from '@tanstack/react-table'; +import { Video } from 'lucide-react'; +import { ColumnCreatedAt } from '@/components/column-created-at'; +import { ProjectLink } from '@/components/links'; +import { ProfileAvatar } from '@/components/profiles/profile-avatar'; +import { SerieIcon } from '@/components/report-chart/common/serie-icon'; +import { getProfileName } from '@/utils/getters'; function formatDuration(milliseconds: number): string { const seconds = milliseconds / 1000; @@ -44,13 +43,25 @@ export function useColumns() { cell: ({ row }) => { const session = row.original; return ( - - {session.id.slice(0, 8)}... - +
+ + {session.id.slice(0, 8)}... + + {session.hasReplay && ( + + + )} +
); }, }, @@ -63,8 +74,8 @@ export function useColumns() { if (session.profile) { return ( {getProfileName(session.profile)} @@ -73,8 +84,8 @@ export function useColumns() { } return ( {session.profileId} diff --git a/apps/start/src/components/ui/scroll-area.tsx b/apps/start/src/components/ui/scroll-area.tsx index d737b1dc..538653c1 100644 --- a/apps/start/src/components/ui/scroll-area.tsx +++ b/apps/start/src/components/ui/scroll-area.tsx @@ -48,7 +48,7 @@ function ScrollArea({ > {children} diff --git a/apps/start/src/hooks/use-page-tabs.ts b/apps/start/src/hooks/use-page-tabs.ts index b76b9506..c6b7a564 100644 --- a/apps/start/src/hooks/use-page-tabs.ts +++ b/apps/start/src/hooks/use-page-tabs.ts @@ -2,7 +2,8 @@ import { useLocation } from '@tanstack/react-router'; export function usePageTabs(tabs: { id: string; label: string }[]) { const location = useLocation(); - const tab = location.pathname.split('/').pop(); + const segments = location.pathname.split('/').filter(Boolean); + const tab = segments[segments.length - 1]; if (!tab) { return { diff --git a/apps/start/src/modals/event-details.tsx b/apps/start/src/modals/event-details.tsx index 6a97f8b6..dc344fc0 100644 --- a/apps/start/src/modals/event-details.tsx +++ b/apps/start/src/modals/event-details.tsx @@ -1,22 +1,3 @@ -import { ReportChartShortcut } from '@/components/report-chart/shortcut'; -import { - useEventQueryFilters, - useEventQueryNamesFilter, -} from '@/hooks/use-event-query-filters'; - -import { ProjectLink } from '@/components/links'; -import { - WidgetButtons, - WidgetHead, -} from '@/components/overview/overview-widget'; -import { SerieIcon } from '@/components/report-chart/common/serie-icon'; -import { Button } from '@/components/ui/button'; -import { FieldValue, KeyValueGrid } from '@/components/ui/key-value-grid'; -import { Widget, WidgetBody } from '@/components/widget'; -import { fancyMinutes } from '@/hooks/use-numer-formatter'; -import { useTRPC } from '@/integrations/trpc/react'; -import { cn } from '@/utils/cn'; -import { getProfileName } from '@/utils/getters'; import type { IClickhouseEvent, IServiceEvent } from '@openpanel/db'; import { useSuspenseQuery } from '@tanstack/react-query'; import { FilterIcon, XIcon } from 'lucide-react'; @@ -24,6 +5,24 @@ import { omit } from 'ramda'; import { Suspense, useState } from 'react'; import { popModal } from '.'; import { ModalContent } from './Modal/Container'; +import { ProjectLink } from '@/components/links'; +import { + WidgetButtons, + WidgetHead, +} from '@/components/overview/overview-widget'; +import { SerieIcon } from '@/components/report-chart/common/serie-icon'; +import { ReportChartShortcut } from '@/components/report-chart/shortcut'; +import { Button } from '@/components/ui/button'; +import { FieldValue, KeyValueGrid } from '@/components/ui/key-value-grid'; +import { Widget, WidgetBody } from '@/components/widget'; +import { + useEventQueryFilters, + useEventQueryNamesFilter, +} from '@/hooks/use-event-query-filters'; +import { fancyMinutes } from '@/hooks/use-numer-formatter'; +import { useTRPC } from '@/integrations/trpc/react'; +import { cn } from '@/utils/cn'; +import { getProfileName } from '@/utils/getters'; interface Props { id: string; @@ -55,7 +54,7 @@ const filterable: Partial> = export default function EventDetails(props: Props) { return ( - + }> @@ -84,7 +83,7 @@ function EventDetailsContent({ id, createdAt, projectId }: Props) { id, projectId, createdAt, - }), + }) ); const { event, session } = query.data; @@ -158,7 +157,7 @@ function EventDetailsContent({ id, createdAt, projectId }: Props) { event, }); } - }, + } ); } @@ -209,7 +208,7 @@ function EventDetailsContent({ id, createdAt, projectId }: Props) { > */} -
@@ -218,10 +217,10 @@ function EventDetailsContent({ id, createdAt, projectId }: Props) { {Object.entries(TABS).map(([, tab]) => ( @@ -231,29 +230,29 @@ function EventDetailsContent({ id, createdAt, projectId }: Props) { {profile && ( popModal()} + className="card col gap-2 p-4 py-2 hover:bg-def-100" href={`/profiles/${encodeURIComponent(profile.id)}`} - className="card p-4 py-2 col gap-2 hover:bg-def-100" + onClick={() => popModal()} > -
-
+
+
{profile.avatar && ( )} -
+
{getProfileName(profile, false)}
-
-
+
+
-
+
{event.referrerName || event.referrer}
@@ -276,16 +275,16 @@ function EventDetailsContent({ id, createdAt, projectId }: Props) { { + popModal(); + setFilter(`properties.${item.name}`, item.value as any); + }} renderValue={(item) => (
{String(item.value)}
)} - onItemClick={(item) => { - popModal(); - setFilter(`properties.${item.name}`, item.value as any); - }} /> )} @@ -296,25 +295,6 @@ function EventDetailsContent({ id, createdAt, projectId }: Props) { { - const isFilterable = item.value && (filterable as any)[item.name]; - if (isFilterable) { - return ( -
- - -
- ); - } - - return ( - - ); - }} onItemClick={(item) => { const isFilterable = item.value && (filterable as any)[item.name]; if (isFilterable) { @@ -322,26 +302,45 @@ function EventDetailsContent({ id, createdAt, projectId }: Props) { setFilter(item.name as keyof IServiceEvent, item.value); } }} + renderValue={(item) => { + const isFilterable = item.value && (filterable as any)[item.name]; + if (isFilterable) { + return ( +
+ + +
+ ); + } + + return ( + + ); + }} />
All events for {event.name}
-
+
-
-
-
+
+
+
-
-
+
+
{/* Profile skeleton */} -
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
+
-
+
{/* Properties skeleton */}
-
+
{Array.from({ length: 3 }).map((_, i) => (
-
-
+
+
))}
@@ -419,16 +418,16 @@ function EventDetailsSkeleton() { {/* Information skeleton */}
-
+
{Array.from({ length: 6 }).map((_, i) => (
-
-
+
+
))}
@@ -437,11 +436,11 @@ function EventDetailsSkeleton() { {/* Chart skeleton */}
-
-
+
+
-
+
diff --git a/apps/start/src/routeTree.gen.ts b/apps/start/src/routeTree.gen.ts index 77a0ef9a..5b7eba9a 100644 --- a/apps/start/src/routeTree.gen.ts +++ b/apps/start/src/routeTree.gen.ts @@ -83,6 +83,7 @@ import { Route as AppOrganizationIdProjectIdEventsTabsStatsRouteImport } from '. import { Route as AppOrganizationIdProjectIdEventsTabsEventsRouteImport } from './routes/_app.$organizationId.$projectId.events._tabs.events' import { Route as AppOrganizationIdProjectIdEventsTabsConversionsRouteImport } from './routes/_app.$organizationId.$projectId.events._tabs.conversions' import { Route as AppOrganizationIdProjectIdProfilesProfileIdTabsIndexRouteImport } from './routes/_app.$organizationId.$projectId.profiles.$profileId._tabs.index' +import { Route as AppOrganizationIdProjectIdProfilesProfileIdTabsSessionsRouteImport } from './routes/_app.$organizationId.$projectId.profiles.$profileId._tabs.sessions' import { Route as AppOrganizationIdProjectIdProfilesProfileIdTabsEventsRouteImport } from './routes/_app.$organizationId.$projectId.profiles.$profileId._tabs.events' const AppOrganizationIdProfileRouteImport = createFileRoute( @@ -557,6 +558,12 @@ const AppOrganizationIdProjectIdProfilesProfileIdTabsIndexRoute = path: '/', getParentRoute: () => AppOrganizationIdProjectIdProfilesProfileIdTabsRoute, } as any) +const AppOrganizationIdProjectIdProfilesProfileIdTabsSessionsRoute = + AppOrganizationIdProjectIdProfilesProfileIdTabsSessionsRouteImport.update({ + id: '/sessions', + path: '/sessions', + getParentRoute: () => AppOrganizationIdProjectIdProfilesProfileIdTabsRoute, + } as any) const AppOrganizationIdProjectIdProfilesProfileIdTabsEventsRoute = AppOrganizationIdProjectIdProfilesProfileIdTabsEventsRouteImport.update({ id: '/events', @@ -633,6 +640,7 @@ export interface FileRoutesByFullPath { '/$organizationId/$projectId/profiles/': typeof AppOrganizationIdProjectIdProfilesTabsIndexRoute '/$organizationId/$projectId/settings/': typeof AppOrganizationIdProjectIdSettingsTabsIndexRoute '/$organizationId/$projectId/profiles/$profileId/events': typeof AppOrganizationIdProjectIdProfilesProfileIdTabsEventsRoute + '/$organizationId/$projectId/profiles/$profileId/sessions': typeof AppOrganizationIdProjectIdProfilesProfileIdTabsSessionsRoute '/$organizationId/$projectId/profiles/$profileId/': typeof AppOrganizationIdProjectIdProfilesProfileIdTabsIndexRoute } export interface FileRoutesByTo { @@ -695,6 +703,7 @@ export interface FileRoutesByTo { '/$organizationId/$projectId/settings/imports': typeof AppOrganizationIdProjectIdSettingsTabsImportsRoute '/$organizationId/$projectId/settings/widgets': typeof AppOrganizationIdProjectIdSettingsTabsWidgetsRoute '/$organizationId/$projectId/profiles/$profileId/events': typeof AppOrganizationIdProjectIdProfilesProfileIdTabsEventsRoute + '/$organizationId/$projectId/profiles/$profileId/sessions': typeof AppOrganizationIdProjectIdProfilesProfileIdTabsSessionsRoute } export interface FileRoutesById { __root__: typeof rootRouteImport @@ -778,6 +787,7 @@ export interface FileRoutesById { '/_app/$organizationId/$projectId/profiles/_tabs/': typeof AppOrganizationIdProjectIdProfilesTabsIndexRoute '/_app/$organizationId/$projectId/settings/_tabs/': typeof AppOrganizationIdProjectIdSettingsTabsIndexRoute '/_app/$organizationId/$projectId/profiles/$profileId/_tabs/events': typeof AppOrganizationIdProjectIdProfilesProfileIdTabsEventsRoute + '/_app/$organizationId/$projectId/profiles/$profileId/_tabs/sessions': typeof AppOrganizationIdProjectIdProfilesProfileIdTabsSessionsRoute '/_app/$organizationId/$projectId/profiles/$profileId/_tabs/': typeof AppOrganizationIdProjectIdProfilesProfileIdTabsIndexRoute } export interface FileRouteTypes { @@ -851,6 +861,7 @@ export interface FileRouteTypes { | '/$organizationId/$projectId/profiles/' | '/$organizationId/$projectId/settings/' | '/$organizationId/$projectId/profiles/$profileId/events' + | '/$organizationId/$projectId/profiles/$profileId/sessions' | '/$organizationId/$projectId/profiles/$profileId/' fileRoutesByTo: FileRoutesByTo to: @@ -913,6 +924,7 @@ export interface FileRouteTypes { | '/$organizationId/$projectId/settings/imports' | '/$organizationId/$projectId/settings/widgets' | '/$organizationId/$projectId/profiles/$profileId/events' + | '/$organizationId/$projectId/profiles/$profileId/sessions' id: | '__root__' | '/' @@ -995,6 +1007,7 @@ export interface FileRouteTypes { | '/_app/$organizationId/$projectId/profiles/_tabs/' | '/_app/$organizationId/$projectId/settings/_tabs/' | '/_app/$organizationId/$projectId/profiles/$profileId/_tabs/events' + | '/_app/$organizationId/$projectId/profiles/$profileId/_tabs/sessions' | '/_app/$organizationId/$projectId/profiles/$profileId/_tabs/' fileRoutesById: FileRoutesById } @@ -1578,6 +1591,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AppOrganizationIdProjectIdProfilesProfileIdTabsIndexRouteImport parentRoute: typeof AppOrganizationIdProjectIdProfilesProfileIdTabsRoute } + '/_app/$organizationId/$projectId/profiles/$profileId/_tabs/sessions': { + id: '/_app/$organizationId/$projectId/profiles/$profileId/_tabs/sessions' + path: '/sessions' + fullPath: '/$organizationId/$projectId/profiles/$profileId/sessions' + preLoaderRoute: typeof AppOrganizationIdProjectIdProfilesProfileIdTabsSessionsRouteImport + parentRoute: typeof AppOrganizationIdProjectIdProfilesProfileIdTabsRoute + } '/_app/$organizationId/$projectId/profiles/$profileId/_tabs/events': { id: '/_app/$organizationId/$projectId/profiles/$profileId/_tabs/events' path: '/events' @@ -1689,6 +1709,7 @@ const AppOrganizationIdProjectIdProfilesTabsRouteWithChildren = interface AppOrganizationIdProjectIdProfilesProfileIdTabsRouteChildren { AppOrganizationIdProjectIdProfilesProfileIdTabsEventsRoute: typeof AppOrganizationIdProjectIdProfilesProfileIdTabsEventsRoute + AppOrganizationIdProjectIdProfilesProfileIdTabsSessionsRoute: typeof AppOrganizationIdProjectIdProfilesProfileIdTabsSessionsRoute AppOrganizationIdProjectIdProfilesProfileIdTabsIndexRoute: typeof AppOrganizationIdProjectIdProfilesProfileIdTabsIndexRoute } @@ -1696,6 +1717,8 @@ const AppOrganizationIdProjectIdProfilesProfileIdTabsRouteChildren: AppOrganizat { AppOrganizationIdProjectIdProfilesProfileIdTabsEventsRoute: AppOrganizationIdProjectIdProfilesProfileIdTabsEventsRoute, + AppOrganizationIdProjectIdProfilesProfileIdTabsSessionsRoute: + AppOrganizationIdProjectIdProfilesProfileIdTabsSessionsRoute, AppOrganizationIdProjectIdProfilesProfileIdTabsIndexRoute: AppOrganizationIdProjectIdProfilesProfileIdTabsIndexRoute, } diff --git a/apps/start/src/routes/_app.$organizationId.$projectId.profiles.$profileId._tabs.sessions.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.profiles.$profileId._tabs.sessions.tsx new file mode 100644 index 00000000..4645e872 --- /dev/null +++ b/apps/start/src/routes/_app.$organizationId.$projectId.profiles.$profileId._tabs.sessions.tsx @@ -0,0 +1,33 @@ +import { SessionsTable } from '@/components/sessions/table'; +import { useSearchQueryState } from '@/hooks/use-search-query-state'; +import { useTRPC } from '@/integrations/trpc/react'; +import { useInfiniteQuery } from '@tanstack/react-query'; +import { createFileRoute } from '@tanstack/react-router'; + +export const Route = createFileRoute( + '/_app/$organizationId/$projectId/profiles/$profileId/_tabs/sessions', +)({ + component: Component, +}); + +function Component() { + const { projectId, profileId } = Route.useParams(); + const trpc = useTRPC(); + const { debouncedSearch } = useSearchQueryState(); + + const query = useInfiniteQuery( + trpc.session.list.infiniteQueryOptions( + { + projectId, + profileId, + take: 50, + search: debouncedSearch, + }, + { + getNextPageParam: (lastPage) => lastPage.meta.next, + }, + ), + ); + + return ; +} diff --git a/apps/start/src/routes/_app.$organizationId.$projectId.profiles.$profileId._tabs.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.profiles.$profileId._tabs.tsx index 24f2a563..315c80c3 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId.profiles.$profileId._tabs.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.profiles.$profileId._tabs.tsx @@ -43,6 +43,7 @@ function Component() { label: 'Overview', }, { id: 'events', label: 'Events' }, + { id: 'sessions', label: 'Sessions' }, ]); const handleTabChange = (tabId: string) => { diff --git a/apps/start/src/routes/_app.$organizationId.$projectId.sessions_.$sessionId.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.sessions_.$sessionId.tsx index f031dc89..32b420db 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId.sessions_.$sessionId.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.sessions_.$sessionId.tsx @@ -1,21 +1,28 @@ -import { EventsTable } from '@/components/events/table'; +import type { IServiceEvent, IServiceSession } from '@openpanel/db'; +import { useSuspenseQuery } from '@tanstack/react-query'; +import { createFileRoute, Link } from '@tanstack/react-router'; +import { EventIcon } from '@/components/events/event-icon'; import FullPageLoadingState from '@/components/full-page-loading-state'; import { PageContainer } from '@/components/page-container'; import { PageHeader } from '@/components/page-header'; +import { ProfileAvatar } from '@/components/profiles/profile-avatar'; import { SerieIcon } from '@/components/report-chart/common/serie-icon'; -import { useReadColumnVisibility } from '@/components/ui/data-table/data-table-hooks'; +import { ReplayShell } from '@/components/sessions/replay'; +import { KeyValueGrid } from '@/components/ui/key-value-grid'; import { - useEventQueryFilters, - useEventQueryNamesFilter, -} from '@/hooks/use-event-query-filters'; + Widget, + WidgetBody, + WidgetHead, + WidgetTitle, +} from '@/components/widget'; +import { useNumber } from '@/hooks/use-numer-formatter'; import { useTRPC } from '@/integrations/trpc/react'; +import { formatDateTime } from '@/utils/date'; +import { getProfileName } from '@/utils/getters'; import { createProjectTitle } from '@/utils/title'; -import { useInfiniteQuery, useSuspenseQuery } from '@tanstack/react-query'; -import { createFileRoute } from '@tanstack/react-router'; -import { parseAsIsoDateTime, useQueryState } from 'nuqs'; export const Route = createFileRoute( - '/_app/$organizationId/$projectId/sessions_/$sessionId', + '/_app/$organizationId/$projectId/sessions_/$sessionId' )({ component: Component, loader: async ({ context, params }) => { @@ -24,66 +31,148 @@ export const Route = createFileRoute( context.trpc.session.byId.queryOptions({ sessionId: params.sessionId, projectId: params.projectId, - }), + }) + ), + context.queryClient.prefetchQuery( + context.trpc.event.events.queryOptions({ + projectId: params.projectId, + sessionId: params.sessionId, + filters: [], + columnVisibility: {}, + }) ), ]); }, - head: () => { - return { - meta: [ - { - title: createProjectTitle('Sessions'), - }, - ], - }; - }, + head: () => ({ + meta: [{ title: createProjectTitle('Session') }], + }), pendingComponent: FullPageLoadingState, }); -function Component() { - const { projectId, sessionId } = Route.useParams(); - const trpc = useTRPC(); +function sessionToFakeEvent(session: IServiceSession): IServiceEvent { + return { + ...session, + name: 'screen_view', + sessionId: session.id, + properties: {}, + path: session.exitPath, + origin: session.exitOrigin, + importedAt: undefined, + meta: undefined, + sdkName: undefined, + sdkVersion: undefined, + profile: undefined, + }; +} - const LIMIT = 50; +function VisitedRoutes({ paths }: { paths: string[] }) { + const counted = paths.reduce>((acc, p) => { + acc[p] = (acc[p] ?? 0) + 1; + return acc; + }, {}); + const sorted = Object.entries(counted).sort((a, b) => b[1] - a[1]); + const max = sorted[0]?.[1] ?? 1; - const { data: session } = useSuspenseQuery( - trpc.session.byId.queryOptions({ - sessionId, - projectId, - }), - ); - - const [filters] = useEventQueryFilters(); - const [startDate] = useQueryState('startDate', parseAsIsoDateTime); - const [endDate] = useQueryState('endDate', parseAsIsoDateTime); - const [eventNames] = useEventQueryNamesFilter(); - const columnVisibility = useReadColumnVisibility('events'); - const query = useInfiniteQuery( - trpc.event.events.infiniteQueryOptions( - { - projectId, - sessionId, - filters, - events: eventNames, - startDate: startDate || undefined, - endDate: endDate || undefined, - columnVisibility: columnVisibility ?? {}, - }, - { - enabled: columnVisibility !== null, - getNextPageParam: (lastPage) => lastPage.meta.next, - }, - ), - ); + if (sorted.length === 0) { + return null; + } return ( - - -
+ + + Visited pages + +
+ {sorted.map(([path, count]) => ( +
+
+
+ {path} + {count} +
+
+ ))} +
+ + ); +} + +function EventDistribution({ events }: { events: IServiceEvent[] }) { + const counted = events.reduce>((acc, e) => { + acc[e.name] = (acc[e.name] ?? 0) + 1; + return acc; + }, {}); + const sorted = Object.entries(counted).sort((a, b) => b[1] - a[1]); + const max = sorted[0]?.[1] ?? 1; + + if (sorted.length === 0) { + return null; + } + + return ( + + + Event distribution + +
+ {sorted.map(([name, count]) => ( +
+
+
+ {name.replace(/_/g, ' ')} + {count} +
+
+ ))} +
+ + ); +} + +function Component() { + const { projectId, sessionId, organizationId } = Route.useParams(); + const trpc = useTRPC(); + const number = useNumber(); + + const { data: session } = useSuspenseQuery( + trpc.session.byId.queryOptions({ sessionId, projectId }) + ); + + const { data: eventsData } = useSuspenseQuery( + trpc.event.events.queryOptions({ + projectId, + sessionId, + filters: [], + columnVisibility: {}, + }) + ); + + const events = eventsData?.data ?? []; + + const isIdentified = + session.profileId && session.profileId !== session.deviceId; + + const { data: profile } = useSuspenseQuery( + trpc.profile.byId.queryOptions({ + profileId: session.profileId, + projectId, + }) + ); + + const fakeEvent = sessionToFakeEvent(session); + + return ( + + +
{session.country && ( -
+
{session.country} @@ -92,32 +181,195 @@ function Component() {
)} {session.device && ( -
+
{session.device}
)} {session.os && ( -
+
{session.os}
)} {session.model && ( -
+
{session.model}
)} {session.browser && ( -
+
{session.browser}
)}
- + + {session.hasReplay && ( + + )} + +
+ {/* Left column */} +
+ {/* Session info */} + + + Session info + + 0 + ? [{ name: 'revenue', value: `$${session.revenue}` }] + : []), + { name: 'country', value: session.country, event: fakeEvent }, + ...(session.city + ? [{ name: 'city', value: session.city, event: fakeEvent }] + : []), + ...(session.os + ? [{ name: 'os', value: session.os, event: fakeEvent }] + : []), + ...(session.browser + ? [ + { + name: 'browser', + value: session.browser, + event: fakeEvent, + }, + ] + : []), + ...(session.device + ? [ + { + name: 'device', + value: session.device, + event: fakeEvent, + }, + ] + : []), + ...(session.brand + ? [{ name: 'brand', value: session.brand, event: fakeEvent }] + : []), + ...(session.model + ? [{ name: 'model', value: session.model, event: fakeEvent }] + : []), + ]} + /> + + + {/* Profile card */} + {isIdentified && profile && ( + + + Profile + + + + +
+ + {getProfileName(profile, false) ?? session.profileId} + + {profile.email && ( + + {profile.email} + + )} +
+ +
+
+ )} + + {/* Visited pages */} + e.name === 'screen_view' && e.path) + .map((e) => e.path)} + /> + + {/* Event distribution */} + +
+ + {/* Right column */} +
+ {/* Events list */} + + + Events + +
+ {events.map((event) => ( +
+ +
+ + {event.name === 'screen_view' && event.path + ? event.path + : event.name.replace(/_/g, ' ')} + +
+ + {formatDateTime(event.createdAt)} + +
+ ))} + {events.length === 0 && ( +
+ No events found +
+ )} +
+
+
+
); } diff --git a/apps/start/src/types/rrweb-player.d.ts b/apps/start/src/types/rrweb-player.d.ts new file mode 100644 index 00000000..2cae9bf9 --- /dev/null +++ b/apps/start/src/types/rrweb-player.d.ts @@ -0,0 +1,47 @@ +declare module 'rrweb-player' { + interface RrwebPlayerProps { + events: Array<{ type: number; data: unknown; timestamp: number }>; + width?: number; + height?: number; + autoPlay?: boolean; + showController?: boolean; + speedOption?: number[]; + UNSAFE_replayCanvas?: boolean; + skipInactive?: boolean; + } + + interface RrwebPlayerOptions { + target: HTMLElement; + props: RrwebPlayerProps; + } + + interface RrwebReplayer { + getCurrentTime: () => number; + } + + interface RrwebPlayerMetaData { + startTime: number; + endTime: number; + totalTime: number; + } + + interface RrwebPlayerInstance { + play: () => void; + pause: () => void; + toggle: () => void; + goto: (timeOffset: number, play?: boolean) => void; + setSpeed: (speed: number) => void; + getMetaData: () => RrwebPlayerMetaData; + getReplayer: () => RrwebReplayer; + addEvent: (event: { type: number; data: unknown; timestamp: number }) => void; + addEventListener?: ( + event: string, + handler: (...args: unknown[]) => void, + ) => void; + $set?: (props: Partial) => void; + $destroy?: () => void; + } + + const rrwebPlayer: new (options: RrwebPlayerOptions) => RrwebPlayerInstance; + export default rrwebPlayer; +} diff --git a/apps/start/src/utils/op.ts b/apps/start/src/utils/op.ts index 0ba0fa95..745f712e 100644 --- a/apps/start/src/utils/op.ts +++ b/apps/start/src/utils/op.ts @@ -2,19 +2,14 @@ import { OpenPanel } from '@openpanel/web'; const clientId = import.meta.env.VITE_OP_CLIENT_ID; -const createOpInstance = () => { - if (!clientId || clientId === 'undefined') { - return new Proxy({} as OpenPanel, { - get: () => () => {}, - }); - } - - return new OpenPanel({ - clientId, - trackScreenViews: true, - trackOutgoingLinks: true, - trackAttributes: true, - }); -}; - -export const op = createOpInstance(); +export const op = new OpenPanel({ + clientId, + disabled: clientId === 'undefined' || !clientId, + // apiUrl: 'http://localhost:3333', + trackScreenViews: true, + trackOutgoingLinks: true, + trackAttributes: true, + // sessionReplay: { + // enabled: true, + // } +}); diff --git a/apps/worker/src/boot-cron.ts b/apps/worker/src/boot-cron.ts index 4a564e0d..106cec07 100644 --- a/apps/worker/src/boot-cron.ts +++ b/apps/worker/src/boot-cron.ts @@ -63,6 +63,11 @@ export async function bootCron() { type: 'flushProfileBackfill', pattern: 1000 * 30, }, + { + name: 'flush', + type: 'flushReplay', + pattern: 1000 * 10, + }, { name: 'insightsDaily', type: 'insightsDaily', diff --git a/apps/worker/src/jobs/cron.ts b/apps/worker/src/jobs/cron.ts index 2735f565..8d69afec 100644 --- a/apps/worker/src/jobs/cron.ts +++ b/apps/worker/src/jobs/cron.ts @@ -1,6 +1,6 @@ import type { Job } from 'bullmq'; -import { eventBuffer, profileBackfillBuffer, profileBuffer, sessionBuffer } from '@openpanel/db'; +import { eventBuffer, profileBackfillBuffer, profileBuffer, replayBuffer, sessionBuffer } from '@openpanel/db'; import type { CronQueuePayload } from '@openpanel/queue'; import { jobdeleteProjects } from './cron.delete-projects'; @@ -26,6 +26,9 @@ export async function cronJob(job: Job) { case 'flushProfileBackfill': { return await profileBackfillBuffer.tryFlush(); } + case 'flushReplay': { + return await replayBuffer.tryFlush(); + } case 'ping': { return await ping(); } diff --git a/apps/worker/src/jobs/events.incoming-event.ts b/apps/worker/src/jobs/events.incoming-event.ts index 73f6c000..a0af841f 100644 --- a/apps/worker/src/jobs/events.incoming-event.ts +++ b/apps/worker/src/jobs/events.incoming-event.ts @@ -15,7 +15,6 @@ import { import type { ILogger } from '@openpanel/logger'; import type { EventsQueuePayloadIncomingEvent } from '@openpanel/queue'; import * as R from 'ramda'; -import { v4 as uuid } from 'uuid'; import { logger as baseLogger } from '@/utils/logger'; import { createSessionEndJob, getSessionEnd } from '@/utils/session-handler'; @@ -53,10 +52,9 @@ async function createEventAndNotify( logger.info('Creating event', { event: payload }); const [event] = await Promise.all([ createEvent(payload), - checkNotificationRulesForEvent(payload).catch(() => {}), + checkNotificationRulesForEvent(payload).catch(() => null), ]); - console.log('Event created:', event); return event; } @@ -87,6 +85,8 @@ export async function incomingEvent( projectId, currentDeviceId, previousDeviceId, + deviceId, + sessionId, uaInfo: _uaInfo, } = jobPayload; const properties = body.properties ?? {}; @@ -157,7 +157,6 @@ export async function incomingEvent( : undefined, } as const; - console.log('HERE?'); // if timestamp is from the past we dont want to create a new session if (uaInfo.isServer || isTimestampFromThePast) { const session = profileId @@ -167,8 +166,6 @@ export async function incomingEvent( }) : null; - console.log('Server?'); - const payload = { ...baseEvent, deviceId: session?.device_id ?? '', @@ -194,31 +191,31 @@ export async function incomingEvent( return createEventAndNotify(payload as IServiceEvent, logger, projectId); } - console.log('not?'); + const sessionEnd = await getSessionEnd({ projectId, currentDeviceId, previousDeviceId, + deviceId, profileId, }); - console.log('Server?'); - const lastScreenView = sessionEnd + const activeSession = sessionEnd ? await sessionBuffer.getExistingSession({ sessionId: sessionEnd.sessionId, }) : null; const payload: IServiceCreateEventPayload = merge(baseEvent, { - deviceId: sessionEnd?.deviceId ?? currentDeviceId, - sessionId: sessionEnd?.sessionId ?? uuid(), + deviceId: sessionEnd?.deviceId ?? deviceId, + sessionId: sessionEnd?.sessionId ?? sessionId, referrer: sessionEnd?.referrer ?? baseEvent.referrer, referrerName: sessionEnd?.referrerName ?? baseEvent.referrerName, referrerType: sessionEnd?.referrerType ?? baseEvent.referrerType, // if the path is not set, use the last screen view path - path: baseEvent.path || lastScreenView?.exit_path || '', - origin: baseEvent.origin || lastScreenView?.exit_origin || '', + path: baseEvent.path || activeSession?.exit_path || '', + origin: baseEvent.origin || activeSession?.exit_origin || '', } as Partial) as IServiceCreateEventPayload; - console.log('SessionEnd?', sessionEnd); + if (!sessionEnd) { logger.info('Creating session start event', { event: payload }); await createEventAndNotify( diff --git a/apps/worker/src/jobs/events.incoming-events.test.ts b/apps/worker/src/jobs/events.incoming-events.test.ts index aef05511..b5e5226b 100644 --- a/apps/worker/src/jobs/events.incoming-events.test.ts +++ b/apps/worker/src/jobs/events.incoming-events.test.ts @@ -32,6 +32,8 @@ const SESSION_TIMEOUT = 30 * 60 * 1000; const projectId = 'test-project'; const currentDeviceId = 'device-123'; const previousDeviceId = 'device-456'; +// Valid UUID used when creating a new session in tests +const newSessionId = 'a1b2c3d4-e5f6-4789-a012-345678901234'; const geo = { country: 'US', city: 'New York', @@ -67,7 +69,7 @@ describe('incomingEvent', () => { vi.clearAllMocks(); }); - it.only('should create a session start and an event', async () => { + it('should create a session start and an event', async () => { const spySessionsQueueAdd = vi.spyOn(sessionsQueue, 'add'); const timestamp = new Date(); // Mock job data @@ -90,12 +92,15 @@ describe('incomingEvent', () => { projectId, currentDeviceId, previousDeviceId, + deviceId: currentDeviceId, + sessionId: newSessionId, }; const event = { name: 'test_event', deviceId: currentDeviceId, profileId: '', sessionId: expect.stringMatching( + // biome-ignore lint/performance/useTopLevelRegex: test /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i ), projectId, @@ -182,6 +187,8 @@ describe('incomingEvent', () => { projectId, currentDeviceId, previousDeviceId, + deviceId: currentDeviceId, + sessionId: 'session-123', }; const changeDelay = vi.fn(); @@ -263,6 +270,8 @@ describe('incomingEvent', () => { projectId, currentDeviceId: '', previousDeviceId: '', + deviceId: '', + sessionId: '', uaInfo: uaInfoServer, }; @@ -367,6 +376,8 @@ describe('incomingEvent', () => { projectId, currentDeviceId: '', previousDeviceId: '', + deviceId: '', + sessionId: '', uaInfo: uaInfoServer, }; diff --git a/apps/worker/src/metrics.ts b/apps/worker/src/metrics.ts index 14cb5333..eaea78d2 100644 --- a/apps/worker/src/metrics.ts +++ b/apps/worker/src/metrics.ts @@ -4,6 +4,7 @@ import { botBuffer, eventBuffer, profileBuffer, + replayBuffer, sessionBuffer, } from '@openpanel/db'; import { cronQueue, eventsGroupQueues, sessionsQueue } from '@openpanel/queue'; @@ -124,3 +125,14 @@ register.registerMetric( }, }), ); + +register.registerMetric( + new client.Gauge({ + name: `buffer_${replayBuffer.name}_count`, + help: 'Number of unprocessed replay chunks', + async collect() { + const metric = await replayBuffer.getBufferSize(); + this.set(metric); + }, + }), +); diff --git a/apps/worker/src/utils/session-handler.ts b/apps/worker/src/utils/session-handler.ts index 59b6edad..dcf427fe 100644 --- a/apps/worker/src/utils/session-handler.ts +++ b/apps/worker/src/utils/session-handler.ts @@ -39,17 +39,20 @@ export async function getSessionEnd({ projectId, currentDeviceId, previousDeviceId, + deviceId, profileId, }: { projectId: string; currentDeviceId: string; previousDeviceId: string; + deviceId: string; profileId: string; }) { const sessionEnd = await getSessionEndJob({ projectId, currentDeviceId, previousDeviceId, + deviceId, }); if (sessionEnd) { @@ -81,6 +84,7 @@ export async function getSessionEndJob(args: { projectId: string; currentDeviceId: string; previousDeviceId: string; + deviceId: string; retryCount?: number; }): Promise<{ deviceId: string; @@ -130,20 +134,31 @@ export async function getSessionEndJob(args: { return null; } - // Check current device job - const currentJob = await sessionsQueue.getJob( - getSessionEndJobId(args.projectId, args.currentDeviceId), - ); - if (currentJob) { - return await handleJobStates(currentJob, args.currentDeviceId); + // TODO: Remove this when migrated to deviceId + if (args.currentDeviceId && args.previousDeviceId) { + // Check current device job + const currentJob = await sessionsQueue.getJob( + getSessionEndJobId(args.projectId, args.currentDeviceId), + ); + if (currentJob) { + return await handleJobStates(currentJob, args.currentDeviceId); + } + + // Check previous device job + const previousJob = await sessionsQueue.getJob( + getSessionEndJobId(args.projectId, args.previousDeviceId), + ); + if (previousJob) { + return await handleJobStates(previousJob, args.previousDeviceId); + } } - // Check previous device job - const previousJob = await sessionsQueue.getJob( - getSessionEndJobId(args.projectId, args.previousDeviceId), + // Check current device job + const currentJob = await sessionsQueue.getJob( + getSessionEndJobId(args.projectId, args.deviceId), ); - if (previousJob) { - return await handleJobStates(previousJob, args.previousDeviceId); + if (currentJob) { + return await handleJobStates(currentJob, args.deviceId); } // Create session diff --git a/packages/common/src/id.ts b/packages/common/src/id.ts index 7b9462be..06d963d9 100644 --- a/packages/common/src/id.ts +++ b/packages/common/src/id.ts @@ -4,6 +4,6 @@ export function shortId() { return nanoid(4); } -export function generateId() { - return nanoid(8); +export function generateId(prefix?: string, length?: number) { + return prefix ? `${prefix}_${nanoid(length ?? 8)}` : nanoid(length ?? 8); } diff --git a/packages/db/code-migrations/10-add-session-replay.ts b/packages/db/code-migrations/10-add-session-replay.ts new file mode 100644 index 00000000..21a1ceb7 --- /dev/null +++ b/packages/db/code-migrations/10-add-session-replay.ts @@ -0,0 +1,60 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { TABLE_NAMES } from '../src/clickhouse/client'; +import { + addColumns, + createTable, + modifyTTL, + runClickhouseMigrationCommands, +} from '../src/clickhouse/migration'; +import { getIsCluster } from './helpers'; + +export async function up() { + const isClustered = getIsCluster(); + + const sqls: string[] = [ + ...createTable({ + name: TABLE_NAMES.session_replay_chunks, + columns: [ + '`project_id` String CODEC(ZSTD(3))', + '`session_id` String CODEC(ZSTD(3))', + '`chunk_index` UInt16', + '`started_at` DateTime64(3) CODEC(DoubleDelta, ZSTD(3))', + '`ended_at` DateTime64(3) CODEC(DoubleDelta, ZSTD(3))', + '`events_count` UInt16', + '`is_full_snapshot` Bool', + '`payload` String CODEC(ZSTD(6))', + ], + orderBy: ['project_id', 'session_id', 'chunk_index'], + partitionBy: 'toYYYYMM(started_at)', + settings: { + index_granularity: 8192, + }, + distributionHash: 'cityHash64(project_id, session_id)', + replicatedVersion: '1', + isClustered, + }), + modifyTTL({ + tableName: TABLE_NAMES.session_replay_chunks, + isClustered, + ttl: 'started_at + INTERVAL 30 DAY', + }), + ]; + + fs.writeFileSync( + path.join(__filename.replace('.ts', '.sql')), + sqls + .map((sql) => + sql + .trim() + .replace(/;$/, '') + .replace(/\n{2,}/g, '\n') + .concat(';'), + ) + .join('\n\n---\n\n'), + ); + + if (!process.argv.includes('--dry')) { + await runClickhouseMigrationCommands(sqls); + } +} diff --git a/packages/db/code-migrations/migrate.ts b/packages/db/code-migrations/migrate.ts index 3f4a5eac..1598f060 100644 --- a/packages/db/code-migrations/migrate.ts +++ b/packages/db/code-migrations/migrate.ts @@ -19,12 +19,19 @@ async function migrate() { const migration = args.filter((arg) => !arg.startsWith('--'))[0]; const migrationsDir = path.join(__dirname, '..', 'code-migrations'); - const migrations = fs.readdirSync(migrationsDir).filter((file) => { - const version = file.split('-')[0]; - return ( - !Number.isNaN(Number.parseInt(version ?? '')) && file.endsWith('.ts') - ); - }); + const migrations = fs + .readdirSync(migrationsDir) + .filter((file) => { + const version = file.split('-')[0]; + return ( + !Number.isNaN(Number.parseInt(version ?? '')) && file.endsWith('.ts') + ); + }) + .sort((a, b) => { + const aVersion = Number.parseInt(a.split('-')[0]!); + const bVersion = Number.parseInt(b.split('-')[0]!); + return aVersion - bVersion; + }); const finishedMigrations = await db.codeMigration.findMany(); diff --git a/packages/db/src/buffers/index.ts b/packages/db/src/buffers/index.ts index 5bafb88f..1d7b1baf 100644 --- a/packages/db/src/buffers/index.ts +++ b/packages/db/src/buffers/index.ts @@ -2,6 +2,7 @@ import { BotBuffer as BotBufferRedis } from './bot-buffer'; import { EventBuffer as EventBufferRedis } from './event-buffer'; import { ProfileBackfillBuffer } from './profile-backfill-buffer'; import { ProfileBuffer as ProfileBufferRedis } from './profile-buffer'; +import { ReplayBuffer } from './replay-buffer'; import { SessionBuffer } from './session-buffer'; export const eventBuffer = new EventBufferRedis(); @@ -9,5 +10,7 @@ export const profileBuffer = new ProfileBufferRedis(); export const botBuffer = new BotBufferRedis(); export const sessionBuffer = new SessionBuffer(); export const profileBackfillBuffer = new ProfileBackfillBuffer(); +export const replayBuffer = new ReplayBuffer(); export type { ProfileBackfillEntry } from './profile-backfill-buffer'; +export type { IClickhouseSessionReplayChunk } from './replay-buffer'; diff --git a/packages/db/src/buffers/replay-buffer.ts b/packages/db/src/buffers/replay-buffer.ts new file mode 100644 index 00000000..587985f3 --- /dev/null +++ b/packages/db/src/buffers/replay-buffer.ts @@ -0,0 +1,92 @@ +import { getSafeJson } from '@openpanel/json'; +import { getRedisCache } from '@openpanel/redis'; +import { TABLE_NAMES, ch } from '../clickhouse/client'; +import { BaseBuffer } from './base-buffer'; + +export interface IClickhouseSessionReplayChunk { + project_id: string; + session_id: string; + chunk_index: number; + started_at: string; + ended_at: string; + events_count: number; + is_full_snapshot: boolean; + payload: string; +} + +export class ReplayBuffer extends BaseBuffer { + private batchSize = process.env.REPLAY_BUFFER_BATCH_SIZE + ? Number.parseInt(process.env.REPLAY_BUFFER_BATCH_SIZE, 10) + : 500; + private chunkSize = process.env.REPLAY_BUFFER_CHUNK_SIZE + ? Number.parseInt(process.env.REPLAY_BUFFER_CHUNK_SIZE, 10) + : 500; + + private readonly redisKey = 'replay-buffer'; + + constructor() { + super({ + name: 'replay', + onFlush: async () => { + await this.processBuffer(); + }, + }); + } + + async add(chunk: IClickhouseSessionReplayChunk) { + try { + const redis = getRedisCache(); + const result = await redis + .multi() + .rpush(this.redisKey, JSON.stringify(chunk)) + .incr(this.bufferCounterKey) + .llen(this.redisKey) + .exec(); + + const bufferLength = (result?.[2]?.[1] as number) ?? 0; + if (bufferLength >= this.batchSize) { + await this.tryFlush(); + } + } catch (error) { + this.logger.error('Failed to add replay chunk to buffer', { error }); + } + } + + async processBuffer() { + const redis = getRedisCache(); + try { + const items = await redis.lrange(this.redisKey, 0, this.batchSize - 1); + + if (items.length === 0) { + return; + } + + const chunks = items + .map((item) => getSafeJson(item)) + .filter((item): item is IClickhouseSessionReplayChunk => item != null); + + for (const chunk of this.chunks(chunks, this.chunkSize)) { + await ch.insert({ + table: TABLE_NAMES.session_replay_chunks, + values: chunk, + format: 'JSONEachRow', + }); + } + + await redis + .multi() + .ltrim(this.redisKey, items.length, -1) + .decrby(this.bufferCounterKey, items.length) + .exec(); + + this.logger.debug('Processed replay chunks', { count: items.length }); + } catch (error) { + this.logger.error('Failed to process replay buffer', { error }); + } + } + + async getBufferSize() { + const redis = getRedisCache(); + return this.getBufferSizeWithCounter(() => redis.llen(this.redisKey)); + } +} diff --git a/packages/db/src/buffers/session-buffer.ts b/packages/db/src/buffers/session-buffer.ts index fad5ee09..35939bd1 100644 --- a/packages/db/src/buffers/session-buffer.ts +++ b/packages/db/src/buffers/session-buffer.ts @@ -1,8 +1,7 @@ -import { type Redis, getRedisCache } from '@openpanel/redis'; - import { getSafeJson } from '@openpanel/json'; +import { getRedisCache, type Redis } from '@openpanel/redis'; import { assocPath, clone } from 'ramda'; -import { TABLE_NAMES, ch } from '../clickhouse/client'; +import { ch, TABLE_NAMES } from '../clickhouse/client'; import type { IClickhouseEvent } from '../services/event.service'; import type { IClickhouseSession } from '../services/session.service'; import { BaseBuffer } from './base-buffer'; @@ -35,14 +34,14 @@ export class SessionBuffer extends BaseBuffer { | { projectId: string; profileId: string; - }, + } ) { let hit: string | null = null; if ('sessionId' in options) { hit = await this.redis.get(`session:${options.sessionId}`); } else { hit = await this.redis.get( - `session:${options.projectId}:${options.profileId}`, + `session:${options.projectId}:${options.profileId}` ); } @@ -54,7 +53,7 @@ export class SessionBuffer extends BaseBuffer { } async getSession( - event: IClickhouseEvent, + event: IClickhouseEvent ): Promise<[IClickhouseSession] | [IClickhouseSession, IClickhouseSession]> { const existingSession = await this.getExistingSession({ sessionId: event.session_id, @@ -186,14 +185,14 @@ export class SessionBuffer extends BaseBuffer { `session:${newSession.id}`, JSON.stringify(newSession), 'EX', - 60 * 60, + 60 * 60 ); if (newSession.profile_id) { multi.set( `session:${newSession.project_id}:${newSession.profile_id}`, JSON.stringify(newSession), 'EX', - 60 * 60, + 60 * 60 ); } for (const session of sessions) { @@ -220,10 +219,12 @@ export class SessionBuffer extends BaseBuffer { const events = await this.redis.lrange( this.redisKey, 0, - this.batchSize - 1, + this.batchSize - 1 ); - if (events.length === 0) return; + if (events.length === 0) { + return; + } const sessions = events .map((e) => getSafeJson(e)) @@ -258,7 +259,7 @@ export class SessionBuffer extends BaseBuffer { } } - async getBufferSize() { + getBufferSize() { return this.getBufferSizeWithCounter(() => this.redis.llen(this.redisKey)); } } diff --git a/packages/db/src/clickhouse/client.ts b/packages/db/src/clickhouse/client.ts index 244d15a2..bcba30be 100644 --- a/packages/db/src/clickhouse/client.ts +++ b/packages/db/src/clickhouse/client.ts @@ -59,6 +59,7 @@ export const TABLE_NAMES = { cohort_events_mv: 'cohort_events_mv', sessions: 'sessions', events_imports: 'events_imports', + session_replay_chunks: 'session_replay_chunks', }; /** diff --git a/packages/db/src/services/session.service.ts b/packages/db/src/services/session.service.ts index 0d39460a..c7681a91 100644 --- a/packages/db/src/services/session.service.ts +++ b/packages/db/src/services/session.service.ts @@ -1,19 +1,21 @@ +import { getSafeJson } from '@openpanel/json'; import { cacheable } from '@openpanel/redis'; import type { IChartEventFilter } from '@openpanel/validation'; import sqlstring from 'sqlstring'; import { - TABLE_NAMES, ch, chQuery, + convertClickhouseDateToJs, formatClickhouseDate, + TABLE_NAMES, } from '../clickhouse/client'; import { clix } from '../clickhouse/query-builder'; import { createSqlBuilder } from '../sql-builder'; import { getEventFiltersWhereClause } from './chart.service'; import { getOrganizationByProjectIdCached } from './organization.service'; -import { type IServiceProfile, getProfilesCached } from './profile.service'; +import { getProfilesCached, type IServiceProfile } from './profile.service'; -export type IClickhouseSession = { +export interface IClickhouseSession { id: string; profile_id: string; event_count: number; @@ -52,7 +54,9 @@ export type IClickhouseSession = { revenue: number; sign: 1 | 0; version: number; -}; + // Dynamically added + has_replay?: boolean; +} export interface IServiceSession { id: string; @@ -91,6 +95,7 @@ export interface IServiceSession { utmTerm: string; revenue: number; profile?: IServiceProfile; + hasReplay?: boolean; } export interface GetSessionListOptions { @@ -114,8 +119,8 @@ export function transformSession(session: IClickhouseSession): IServiceSession { entryOrigin: session.entry_origin, exitPath: session.exit_path, exitOrigin: session.exit_origin, - createdAt: new Date(session.created_at), - endedAt: new Date(session.ended_at), + createdAt: convertClickhouseDateToJs(session.created_at), + endedAt: convertClickhouseDateToJs(session.ended_at), referrer: session.referrer, referrerName: session.referrer_name, referrerType: session.referrer_type, @@ -142,19 +147,18 @@ export function transformSession(session: IClickhouseSession): IServiceSession { utmTerm: session.utm_term, revenue: session.revenue, profile: undefined, + hasReplay: session.has_replay, }; } -type Direction = 'initial' | 'next' | 'prev'; - -type PageInfo = { +interface PageInfo { next?: Cursor; // use last row -}; +} -type Cursor = { +interface Cursor { createdAt: string; // ISO 8601 with ms id: string; -}; +} export async function getSessionList({ cursor, @@ -176,8 +180,9 @@ export async function getSessionList({ sb.where.range = `created_at BETWEEN toDateTime('${formatClickhouseDate(startDate)}') AND toDateTime('${formatClickhouseDate(endDate)}')`; } - if (profileId) + if (profileId) { sb.where.profileId = `profile_id = ${sqlstring.escape(profileId)}`; + } if (search) { const s = sqlstring.escape(`%${search}%`); sb.where.search = `(entry_path ILIKE ${s} OR exit_path ILIKE ${s} OR referrer ILIKE ${s} OR referrer_name ILIKE ${s})`; @@ -191,13 +196,11 @@ export async function getSessionList({ const dateIntervalInDays = organization?.subscriptionPeriodEventsLimit && organization?.subscriptionPeriodEventsLimit > 1_000_000 - ? 1 + ? 2 : 360; if (cursor) { const cAt = sqlstring.escape(cursor.createdAt); - // TODO: remove id from cursor - const cId = sqlstring.escape(cursor.id); sb.where.cursor = `created_at < toDateTime64(${cAt}, 3)`; sb.where.cursorWindow = `created_at >= toDateTime64(${cAt}, 3) - INTERVAL ${dateIntervalInDays} DAY`; sb.orderBy.created_at = 'created_at DESC'; @@ -235,10 +238,14 @@ export async function getSessionList({ sb.select[column] = column; }); + sb.select.has_replay = `toBool(src.session_id != '') as hasReplay`; + sb.joins.has_replay = `LEFT JOIN (SELECT DISTINCT session_id FROM ${TABLE_NAMES.session_replay_chunks} WHERE project_id = ${sqlstring.escape(projectId)} AND started_at > now() - INTERVAL ${dateIntervalInDays} DAY) AS src ON src.session_id = id`; + const sql = getSql(); const data = await chQuery< IClickhouseSession & { latestCreatedAt: string; + hasReplay: boolean; } >(sql); @@ -321,23 +328,79 @@ export async function getSessionsCount({ export const getSessionsCountCached = cacheable(getSessionsCount, 60 * 10); +export interface ISessionReplayChunkMeta { + chunk_index: number; + started_at: string; + ended_at: string; + events_count: number; + is_full_snapshot: boolean; +} + +const REPLAY_CHUNKS_PAGE_SIZE = 40; + +export async function getSessionReplayChunksFrom( + sessionId: string, + projectId: string, + fromIndex: number +) { + const rows = await chQuery<{ chunk_index: number; payload: string }>( + `SELECT chunk_index, payload + FROM ${TABLE_NAMES.session_replay_chunks} + WHERE session_id = ${sqlstring.escape(sessionId)} + AND project_id = ${sqlstring.escape(projectId)} + ORDER BY started_at, ended_at, chunk_index + LIMIT ${REPLAY_CHUNKS_PAGE_SIZE + 1} + OFFSET ${fromIndex}` + ); + + return { + data: rows + .slice(0, REPLAY_CHUNKS_PAGE_SIZE) + .map((row, index) => { + const events = getSafeJson< + { type: number; data: unknown; timestamp: number }[] + >(row.payload); + if (!events) { + return null; + } + return { chunkIndex: index + fromIndex, events }; + }) + .filter(Boolean), + hasMore: rows.length > REPLAY_CHUNKS_PAGE_SIZE, + }; +} + class SessionService { constructor(private client: typeof ch) {} async byId(sessionId: string, projectId: string) { - const result = await clix(this.client) - .select(['*']) - .from(TABLE_NAMES.sessions) - .where('id', '=', sessionId) - .where('project_id', '=', projectId) - .where('sign', '=', 1) - .execute(); + const [sessionRows, hasReplayRows] = await Promise.all([ + clix(this.client) + .select(['*']) + .from(TABLE_NAMES.sessions, true) + .where('id', '=', sessionId) + .where('project_id', '=', projectId) + .where('sign', '=', 1) + .execute(), + chQuery<{ n: number }>( + `SELECT 1 AS n + FROM ${TABLE_NAMES.session_replay_chunks} + WHERE session_id = ${sqlstring.escape(sessionId)} + AND project_id = ${sqlstring.escape(projectId)} + LIMIT 1` + ), + ]); - if (!result[0]) { + if (!sessionRows[0]) { throw new Error('Session not found'); } - return transformSession(result[0]); + const session = transformSession(sessionRows[0]); + + return { + ...session, + hasReplay: hasReplayRows.length > 0, + }; } } diff --git a/packages/queue/src/queues.ts b/packages/queue/src/queues.ts index 37ee7024..c4737db3 100644 --- a/packages/queue/src/queues.ts +++ b/packages/queue/src/queues.ts @@ -65,8 +65,10 @@ export interface EventsQueuePayloadIncomingEvent { latitude: number | undefined; }; headers: Record; - currentDeviceId: string; - previousDeviceId: string; + currentDeviceId: string; // TODO: Remove + previousDeviceId: string; // TODO: Remove + deviceId: string; + sessionId: string; }; } export interface EventsQueuePayloadCreateEvent { @@ -123,12 +125,17 @@ export type CronQueuePayloadFlushProfileBackfill = { type: 'flushProfileBackfill'; payload: undefined; }; +export type CronQueuePayloadFlushReplay = { + type: 'flushReplay'; + payload: undefined; +}; export type CronQueuePayload = | CronQueuePayloadSalt | CronQueuePayloadFlushEvents | CronQueuePayloadFlushSessions | CronQueuePayloadFlushProfiles | CronQueuePayloadFlushProfileBackfill + | CronQueuePayloadFlushReplay | CronQueuePayloadPing | CronQueuePayloadProject | CronQueuePayloadInsightsDaily diff --git a/packages/sdks/astro/src/OpenPanelComponent.astro b/packages/sdks/astro/src/OpenPanelComponent.astro index 6e95c6af..20248aa0 100644 --- a/packages/sdks/astro/src/OpenPanelComponent.astro +++ b/packages/sdks/astro/src/OpenPanelComponent.astro @@ -4,12 +4,14 @@ import { getInitSnippet } from '@openpanel/web'; type Props = Omit & { profileId?: string; + /** @deprecated Use `scriptUrl` instead. */ cdnUrl?: string; + scriptUrl?: string; filter?: string; globalProperties?: Record; }; -const { profileId, cdnUrl, globalProperties, ...options } = Astro.props; +const { profileId, cdnUrl, scriptUrl, globalProperties, ...options } = Astro.props; const CDN_URL = 'https://openpanel.dev/op1.js'; @@ -60,5 +62,5 @@ ${methods .join('\n')}`; --- -