diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index eee097753523..419b77ce086d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -379,7 +379,7 @@ jobs: - name: Pack run: yarn build:tarball - name: Archive artifacts - uses: actions/upload-artifact@v3.1.1 + uses: actions/upload-artifact@v3.1.2 with: name: ${{ github.sha }} path: | @@ -833,7 +833,7 @@ jobs: GITHUB_TOKEN: ${{ github.token }} - name: Upload results - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v3.1.2 if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository with: name: ${{ steps.process.outputs.artifactName }} diff --git a/.github/workflows/gitflow-sync-develop.yml b/.github/workflows/gitflow-sync-develop.yml index be9621362623..54fe0e5234b5 100644 --- a/.github/workflows/gitflow-sync-develop.yml +++ b/.github/workflows/gitflow-sync-develop.yml @@ -32,6 +32,7 @@ jobs: destination_branch: ${{ env.DEV_BRANCH }} pr_title: '[Gitflow] Merge ${{ github.ref_name }} into ${{ env.DEV_BRANCH }}' pr_body: 'Merge ${{ github.ref_name }} branch into ${{ env.DEV_BRANCH }}' + pr_label: 'Dev: Gitflow' # https://github.com/marketplace/actions/enable-pull-request-automerge - name: Enable automerge for PR diff --git a/.github/workflows/gitflow-sync-master.yml b/.github/workflows/gitflow-sync-master.yml index f8faee895f6e..9dbd6ca050e5 100644 --- a/.github/workflows/gitflow-sync-master.yml +++ b/.github/workflows/gitflow-sync-master.yml @@ -34,6 +34,7 @@ jobs: destination_branch: ${{ env.MAIN_BRANCH }} pr_title: '[Gitflow] Merge ${{ github.ref_name }} into ${{ env.MAIN_BRANCH }}' pr_body: 'Merge ${{ github.ref_name }} branch into ${{ env.MAIN_BRANCH }}' + pr_label: 'Dev: Gitflow' # https://github.com/marketplace/actions/enable-pull-request-automerge - name: Enable automerge for PR diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fd846ab02fa..eac9eade421c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,29 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +## 7.35.0 + +Session Replay is deprecating privacy options in favor of a more streamlined API. Please see the [Replay migration guide](https://github.com/getsentry/sentry-javascript/blob/master/packages/replay/MIGRATION.md) for further information. +Additionally, the following configuration options will no longer be configurable: `slimDOMOptions`, `recordCanvas`, `inlineStylesheet`, `collectFonts`, `inlineImages`. + +- feat(browser): Track if cdn or npm bundle (#6976) +- feat(core): Add aria label to breadcrumb attributes (#6955) +- feat(core): Add Offline Transport wrapper (#6884) +- feat(loader): Add SENTRY_SDK_SOURCE to track loader stats (#6985) +- feat(loader): Sync loader with Sentry template (#7001) +- feat(replay): Deprecate privacy options in favor of a new API, remove some recording options (#6645) +- feat(replay): Move sample rate tags into event context (#6659) +- fix(nextjs): Add isomorphic versions of `ErrorBoundary`, `withErrorBoundary` and `showReportDialog` (#6987) +- fix(nextjs): Don't modify require calls in wrapping loader (#6979) +- fix(nextjs): Don't share I/O resources in between requests (#6980) +- fix(nextjs): Inject client config into `_app` instead of `main` (#7009) +- fix(nextjs): Use Proxies to wrap to preserve static methods (#7002) +- fix(replay): Catch style mutation handling & null events in rrweb (#7010) +- fix(replay): Handle compression failures more robustly (#6988) +- fix(replay): Only call `scope.getLastBreadcrumb` if available (#6969) +- fix(utils): Account for null prototype during normalization (#6925) +- ref(replay): Log warning if sample rates are all undefined (#6959) + ## 7.34.0 This release adds automatic injection of the Next.js SDK into serverside `app` directory bundles, allowing users to call the Sentry SDK in server components. diff --git a/MIGRATION.md b/MIGRATION.md index 9aeca9e1d3d8..55987041e698 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,5 +1,10 @@ # Deprecations in 7.x +## Replay options changed (since 7.35.0) - #6645 + +Some options for replay have been depracted in favor of new APIs. +See [Replay Migration docs](./packages/replay/MIGRATION.md#upgrading-replay-from-7340-to-7350) for details. + ## Renaming of Next.js wrapper methods (since 7.31.0) - #6790 We updated the names of the functions to wrap data fetchers and API routes to better reflect what they are doing. diff --git a/packages/browser/src/loader.js b/packages/browser/src/loader.js index a914137f6f95..48029e7aec46 100644 --- a/packages/browser/src/loader.js +++ b/packages/browser/src/loader.js @@ -61,7 +61,7 @@ var _currentScriptTag = _document.scripts[0]; var _newScriptTag = _document.createElement(_script); _newScriptTag.src = _sdkBundleUrl; - _newScriptTag.setAttribute('crossorigin', 'anonymous'); + _newScriptTag.crossOrigin = 'anonymous'; // Once our SDK is loaded _newScriptTag.addEventListener('load', function() { diff --git a/packages/nextjs/src/client/wrapAppGetInitialPropsWithSentry.ts b/packages/nextjs/src/client/wrapAppGetInitialPropsWithSentry.ts index 205650d41dac..41a24d57c5c2 100644 --- a/packages/nextjs/src/client/wrapAppGetInitialPropsWithSentry.ts +++ b/packages/nextjs/src/client/wrapAppGetInitialPropsWithSentry.ts @@ -7,9 +7,11 @@ type AppGetInitialProps = typeof App['getInitialProps']; * so we are consistent with the serverside implementation. */ export function wrapAppGetInitialPropsWithSentry(origAppGetInitialProps: AppGetInitialProps): AppGetInitialProps { - return async function (this: unknown, ...args: Parameters): ReturnType { - return await origAppGetInitialProps.apply(this, args); - }; + return new Proxy(origAppGetInitialProps, { + apply: async (wrappingTarget, thisArg, args: Parameters) => { + return await wrappingTarget.apply(thisArg, args); + }, + }); } /** diff --git a/packages/nextjs/src/client/wrapDocumentGetInitialPropsWithSentry.ts b/packages/nextjs/src/client/wrapDocumentGetInitialPropsWithSentry.ts index c68c2a266df1..0af40a1f3f84 100644 --- a/packages/nextjs/src/client/wrapDocumentGetInitialPropsWithSentry.ts +++ b/packages/nextjs/src/client/wrapDocumentGetInitialPropsWithSentry.ts @@ -9,12 +9,11 @@ type DocumentGetInitialProps = typeof Document.getInitialProps; export function wrapDocumentGetInitialPropsWithSentry( origDocumentGetInitialProps: DocumentGetInitialProps, ): DocumentGetInitialProps { - return async function ( - this: unknown, - ...args: Parameters - ): ReturnType { - return await origDocumentGetInitialProps.apply(this, args); - }; + return new Proxy(origDocumentGetInitialProps, { + apply: async (wrappingTarget, thisArg, args: Parameters) => { + return await wrappingTarget.apply(thisArg, args); + }, + }); } /** diff --git a/packages/nextjs/src/client/wrapErrorGetInitialPropsWithSentry.ts b/packages/nextjs/src/client/wrapErrorGetInitialPropsWithSentry.ts index e018fb47246d..605efa58eff9 100644 --- a/packages/nextjs/src/client/wrapErrorGetInitialPropsWithSentry.ts +++ b/packages/nextjs/src/client/wrapErrorGetInitialPropsWithSentry.ts @@ -10,9 +10,11 @@ type ErrorGetInitialProps = (context: NextPageContext) => Promise; export function wrapErrorGetInitialPropsWithSentry( origErrorGetInitialProps: ErrorGetInitialProps, ): ErrorGetInitialProps { - return async function (this: unknown, ...args: Parameters): ReturnType { - return await origErrorGetInitialProps.apply(this, args); - }; + return new Proxy(origErrorGetInitialProps, { + apply: async (wrappingTarget, thisArg, args: Parameters) => { + return await wrappingTarget.apply(thisArg, args); + }, + }); } /** diff --git a/packages/nextjs/src/client/wrapGetInitialPropsWithSentry.ts b/packages/nextjs/src/client/wrapGetInitialPropsWithSentry.ts index f29561a4f333..1fbbd8707063 100644 --- a/packages/nextjs/src/client/wrapGetInitialPropsWithSentry.ts +++ b/packages/nextjs/src/client/wrapGetInitialPropsWithSentry.ts @@ -7,9 +7,11 @@ type GetInitialProps = Required['getInitialProps']; * so we are consistent with the serverside implementation. */ export function wrapGetInitialPropsWithSentry(origGetInitialProps: GetInitialProps): GetInitialProps { - return async function (this: unknown, ...args: Parameters): Promise> { - return origGetInitialProps.apply(this, args); - }; + return new Proxy(origGetInitialProps, { + apply: async (wrappingTarget, thisArg, args: Parameters) => { + return await wrappingTarget.apply(thisArg, args); + }, + }); } /** diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index dcb37e935ff4..918d98f74cb4 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -473,8 +473,13 @@ function shouldAddSentryToEntryPoint( return entryPointName.startsWith('pages/'); } else if (runtime === 'browser') { return ( - entryPointName === 'main' || // entrypoint for `/pages` pages - entryPointName === 'main-app' // entrypoint for `/app` pages + // entrypoint for `/pages` pages - this is included on all clientside pages + // It's important that we inject the SDK into this file and not into 'main' because in 'main' + // some important Next.js code (like the setup code for getCongig()) is located and some users + // may need this code inside their Sentry configs + entryPointName === 'pages/_app' || + // entrypoint for `/app` pages + entryPointName === 'main-app' ); } else { // User-specified pages to skip. (Note: For ease of use, `excludeServerRoutes` is specified in terms of routes, diff --git a/packages/nextjs/src/edge/wrapApiHandlerWithSentry.ts b/packages/nextjs/src/edge/wrapApiHandlerWithSentry.ts index 7e87c6a5e607..ef228abc40e9 100644 --- a/packages/nextjs/src/edge/wrapApiHandlerWithSentry.ts +++ b/packages/nextjs/src/edge/wrapApiHandlerWithSentry.ts @@ -10,22 +10,24 @@ export function wrapApiHandlerWithSentry( handler: H, parameterizedRoute: string, ): (...params: Parameters) => Promise> { - return async function (this: unknown, ...args: Parameters): Promise> { - const req = args[0]; + return new Proxy(handler, { + apply: async (wrappingTarget, thisArg, args: Parameters) => { + const req = args[0]; - const activeSpan = !!getCurrentHub().getScope()?.getSpan(); + const activeSpan = !!getCurrentHub().getScope()?.getSpan(); - const wrappedHandler = withEdgeWrapping(handler, { - spanDescription: - activeSpan || !(req instanceof Request) - ? `handler (${parameterizedRoute})` - : `${req.method} ${parameterizedRoute}`, - spanOp: activeSpan ? 'function' : 'http.server', - mechanismFunctionName: 'wrapApiHandlerWithSentry', - }); + const wrappedHandler = withEdgeWrapping(wrappingTarget, { + spanDescription: + activeSpan || !(req instanceof Request) + ? `handler (${parameterizedRoute})` + : `${req.method} ${parameterizedRoute}`, + spanOp: activeSpan ? 'function' : 'http.server', + mechanismFunctionName: 'wrapApiHandlerWithSentry', + }); - return await wrappedHandler.apply(this, args); - }; + return await wrappedHandler.apply(thisArg, args); + }, + }); } /** diff --git a/packages/nextjs/src/edge/wrapMiddlewareWithSentry.ts b/packages/nextjs/src/edge/wrapMiddlewareWithSentry.ts index cb535c41b28d..18c16f1a4198 100644 --- a/packages/nextjs/src/edge/wrapMiddlewareWithSentry.ts +++ b/packages/nextjs/src/edge/wrapMiddlewareWithSentry.ts @@ -10,9 +10,13 @@ import { withEdgeWrapping } from './utils/edgeWrapperUtils'; export function wrapMiddlewareWithSentry( middleware: H, ): (...params: Parameters) => Promise> { - return withEdgeWrapping(middleware, { - spanDescription: 'middleware', - spanOp: 'middleware.nextjs', - mechanismFunctionName: 'withSentryMiddleware', + return new Proxy(middleware, { + apply: async (wrappingTarget, thisArg, args: Parameters) => { + return withEdgeWrapping(wrappingTarget, { + spanDescription: 'middleware', + spanOp: 'middleware.nextjs', + mechanismFunctionName: 'withSentryMiddleware', + }).apply(thisArg, args); + }, }); } diff --git a/packages/nextjs/src/server/utils/nextLogger.ts b/packages/nextjs/src/server/utils/nextLogger.ts deleted file mode 100644 index 14d701d6edb7..000000000000 --- a/packages/nextjs/src/server/utils/nextLogger.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* eslint-disable no-console */ -import * as chalk from 'chalk'; - -// This is nextjs's own logging formatting, vendored since it's not exported. See -// https://github.com/vercel/next.js/blob/c3ceeb03abb1b262032bd96457e224497d3bbcef/packages/next/build/output/log.ts#L3-L11 -// and -// https://github.com/vercel/next.js/blob/de7aa2d6e486c40b8be95a1327639cbed75a8782/packages/next/lib/eslint/runLintCheck.ts#L321-L323. - -const prefixes = { - wait: `${chalk.cyan('wait')} -`, - error: `${chalk.red('error')} -`, - warn: `${chalk.yellow('warn')} -`, - ready: `${chalk.green('ready')} -`, - info: `${chalk.cyan('info')} -`, - event: `${chalk.magenta('event')} -`, - trace: `${chalk.magenta('trace')} -`, -}; - -export const formatAsCode = (str: string): string => chalk.bold.cyan(str); - -export const nextLogger: { - [key: string]: (...message: unknown[]) => void; -} = { - wait: (...message) => console.log(prefixes.wait, ...message), - error: (...message) => console.error(prefixes.error, ...message), - warn: (...message) => console.warn(prefixes.warn, ...message), - ready: (...message) => console.log(prefixes.ready, ...message), - info: (...message) => console.log(prefixes.info, ...message), - event: (...message) => console.log(prefixes.event, ...message), - trace: (...message) => console.log(prefixes.trace, ...message), -}; diff --git a/packages/nextjs/src/server/wrapApiHandlerWithSentry.ts b/packages/nextjs/src/server/wrapApiHandlerWithSentry.ts index 897b362cc550..6ba3d1851acf 100644 --- a/packages/nextjs/src/server/wrapApiHandlerWithSentry.ts +++ b/packages/nextjs/src/server/wrapApiHandlerWithSentry.ts @@ -11,8 +11,7 @@ import { } from '@sentry/utils'; import * as domain from 'domain'; -import type { AugmentedNextApiRequest, AugmentedNextApiResponse, NextApiHandler, WrappedNextApiHandler } from './types'; -import { formatAsCode, nextLogger } from './utils/nextLogger'; +import type { AugmentedNextApiRequest, AugmentedNextApiResponse, NextApiHandler } from './types'; import { platformSupportsStreaming } from './utils/platformSupportsStreaming'; import { autoEndTransactionOnResponseEnd, finishTransaction, flushQueue } from './utils/responseEnd'; @@ -20,35 +19,18 @@ import { autoEndTransactionOnResponseEnd, finishTransaction, flushQueue } from ' * Wrap the given API route handler for tracing and error capturing. Thin wrapper around `withSentry`, which only * applies it if it hasn't already been applied. * - * @param maybeWrappedHandler The handler exported from the user's API page route file, which may or may not already be + * @param apiHandler The handler exported from the user's API page route file, which may or may not already be * wrapped with `withSentry` * @param parameterizedRoute The page's route, passed in via the proxy loader * @returns The wrapped handler */ -export function wrapApiHandlerWithSentry( - maybeWrappedHandler: NextApiHandler | WrappedNextApiHandler, - parameterizedRoute: string, -): WrappedNextApiHandler { - // Log a warning if the user is still manually wrapping their route in `withSentry`. Doesn't work in cases where - // there's been an intermediate wrapper (like `withSentryAPI(someOtherWrapper(withSentry(handler)))`) but should catch - // most cases. Only runs once per route. (Note: Such double-wrapping isn't harmful, but we'll eventually deprecate and remove `withSentry`, so - // best to get people to stop using it.) - if (maybeWrappedHandler.name === 'sentryWrappedHandler') { - const [_sentryNextjs_, _autoWrapOption_, _withSentry_, _route_] = [ - '@sentry/nextjs', - 'autoInstrumentServerFunctions', - 'withSentry', - parameterizedRoute, - ].map(phrase => formatAsCode(phrase)); - - nextLogger.info( - `${_sentryNextjs_} is running with the ${_autoWrapOption_} flag set, which means API routes no longer need to ` + - `be manually wrapped with ${_withSentry_}. Detected manual wrapping in ${_route_}.`, - ); - } - - // eslint-disable-next-line deprecation/deprecation - return withSentry(maybeWrappedHandler, parameterizedRoute); +export function wrapApiHandlerWithSentry(apiHandler: NextApiHandler, parameterizedRoute: string): NextApiHandler { + return new Proxy(apiHandler, { + apply: async (wrappingTarget, thisArg, args: Parameters) => { + // eslint-disable-next-line deprecation/deprecation + return withSentry(wrappingTarget, parameterizedRoute).apply(thisArg, args); + }, + }); } /** @@ -59,175 +41,178 @@ export const withSentryAPI = wrapApiHandlerWithSentry; /** * Legacy function for manually wrapping API route handlers, now used as the innards of `wrapApiHandlerWithSentry`. * - * @param origHandler The user's original API route handler + * @param apiHandler The user's original API route handler * @param parameterizedRoute The route whose handler is being wrapped. Meant for internal use only. * @returns A wrapped version of the handler * * @deprecated Use `wrapApiWithSentry()` instead */ -export function withSentry(origHandler: NextApiHandler, parameterizedRoute?: string): WrappedNextApiHandler { - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - return async function sentryWrappedHandler(req: AugmentedNextApiRequest, res: AugmentedNextApiResponse) { - // We're now auto-wrapping API route handlers using `wrapApiHandlerWithSentry` (which uses `withSentry` under the hood), but - // users still may have their routes manually wrapped with `withSentry`. This check makes `sentryWrappedHandler` - // idempotent so that those cases don't break anything. - if (req.__withSentry_applied__) { - return origHandler(req, res); - } - req.__withSentry_applied__ = true; - - // use a domain in order to prevent scope bleed between requests - const local = domain.create(); - local.add(req); - local.add(res); - - // `local.bind` causes everything to run inside a domain, just like `local.run` does, but it also lets the callback - // return a value. In our case, all any of the codepaths return is a promise of `void`, but nextjs still counts on - // getting that before it will finish the response. - // eslint-disable-next-line complexity - const boundHandler = local.bind(async () => { - let transaction: Transaction | undefined; - const hub = getCurrentHub(); - const currentScope = hub.getScope(); - const options = hub.getClient()?.getOptions(); - - if (currentScope) { - currentScope.setSDKProcessingMetadata({ request: req }); - - if (hasTracingEnabled(options) && options?.instrumenter === 'sentry') { - // If there is a trace header set, extract the data from it (parentSpanId, traceId, and sampling decision) - let traceparentData; - if (req.headers && isString(req.headers['sentry-trace'])) { - traceparentData = extractTraceparentData(req.headers['sentry-trace']); - __DEBUG_BUILD__ && logger.log(`[Tracing] Continuing trace ${traceparentData?.traceId}.`); - } +export function withSentry(apiHandler: NextApiHandler, parameterizedRoute?: string): NextApiHandler { + return new Proxy(apiHandler, { + apply: async (wrappingTarget, thisArg, args: [AugmentedNextApiRequest, AugmentedNextApiResponse]) => { + const [req, res] = args; + + // We're now auto-wrapping API route handlers using `wrapApiHandlerWithSentry` (which uses `withSentry` under the hood), but + // users still may have their routes manually wrapped with `withSentry`. This check makes `sentryWrappedHandler` + // idempotent so that those cases don't break anything. + if (req.__withSentry_applied__) { + return wrappingTarget.apply(thisArg, args); + } + req.__withSentry_applied__ = true; + + // use a domain in order to prevent scope bleed between requests + const local = domain.create(); + local.add(req); + local.add(res); + + // `local.bind` causes everything to run inside a domain, just like `local.run` does, but it also lets the callback + // return a value. In our case, all any of the codepaths return is a promise of `void`, but nextjs still counts on + // getting that before it will finish the response. + // eslint-disable-next-line complexity + const boundHandler = local.bind(async () => { + let transaction: Transaction | undefined; + const hub = getCurrentHub(); + const currentScope = hub.getScope(); + const options = hub.getClient()?.getOptions(); - const baggageHeader = req.headers && req.headers.baggage; - const dynamicSamplingContext = baggageHeaderToDynamicSamplingContext(baggageHeader); - - // prefer the parameterized route, if we have it (which we will if we've auto-wrapped the route handler) - let reqPath = parameterizedRoute; - - // If not, fake it by just replacing parameter values with their names, hoping that none of them match either - // each other or any hard-coded parts of the path - if (!reqPath) { - const url = `${req.url}`; - // pull off query string, if any - reqPath = stripUrlQueryAndFragment(url); - // Replace with placeholder - if (req.query) { - for (const [key, value] of Object.entries(req.query)) { - reqPath = reqPath.replace(`${value}`, `[${key}]`); - } + if (currentScope) { + currentScope.setSDKProcessingMetadata({ request: req }); + + if (hasTracingEnabled(options) && options?.instrumenter === 'sentry') { + // If there is a trace header set, extract the data from it (parentSpanId, traceId, and sampling decision) + let traceparentData; + if (req.headers && isString(req.headers['sentry-trace'])) { + traceparentData = extractTraceparentData(req.headers['sentry-trace']); + __DEBUG_BUILD__ && logger.log(`[Tracing] Continuing trace ${traceparentData?.traceId}.`); } - } - const reqMethod = `${(req.method || 'GET').toUpperCase()} `; - - transaction = startTransaction( - { - name: `${reqMethod}${reqPath}`, - op: 'http.server', - ...traceparentData, - metadata: { - dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext, - source: 'route', - request: req, - }, - }, - // extra context passed to the `tracesSampler` - { request: req }, - ); - currentScope.setSpan(transaction); - if (platformSupportsStreaming() && !origHandler.__sentry_test_doesnt_support_streaming__) { - autoEndTransactionOnResponseEnd(transaction, res); - } else { - // If we're not on a platform that supports streaming, we're blocking res.end() until the queue is flushed. - // res.json() and res.send() will implicitly call res.end(), so it is enough to wrap res.end(). - - // eslint-disable-next-line @typescript-eslint/unbound-method - const origResEnd = res.end; - res.end = async function (this: unknown, ...args: unknown[]) { - if (transaction) { - await finishTransaction(transaction, res); - await flushQueue(); + const baggageHeader = req.headers && req.headers.baggage; + const dynamicSamplingContext = baggageHeaderToDynamicSamplingContext(baggageHeader); + + // prefer the parameterized route, if we have it (which we will if we've auto-wrapped the route handler) + let reqPath = parameterizedRoute; + + // If not, fake it by just replacing parameter values with their names, hoping that none of them match either + // each other or any hard-coded parts of the path + if (!reqPath) { + const url = `${req.url}`; + // pull off query string, if any + reqPath = stripUrlQueryAndFragment(url); + // Replace with placeholder + if (req.query) { + for (const [key, value] of Object.entries(req.query)) { + reqPath = reqPath.replace(`${value}`, `[${key}]`); + } } + } - origResEnd.apply(this, args); - }; + const reqMethod = `${(req.method || 'GET').toUpperCase()} `; + + transaction = startTransaction( + { + name: `${reqMethod}${reqPath}`, + op: 'http.server', + ...traceparentData, + metadata: { + dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext, + source: 'route', + request: req, + }, + }, + // extra context passed to the `tracesSampler` + { request: req }, + ); + currentScope.setSpan(transaction); + if (platformSupportsStreaming() && !wrappingTarget.__sentry_test_doesnt_support_streaming__) { + autoEndTransactionOnResponseEnd(transaction, res); + } else { + // If we're not on a platform that supports streaming, we're blocking res.end() until the queue is flushed. + // res.json() and res.send() will implicitly call res.end(), so it is enough to wrap res.end(). + + // eslint-disable-next-line @typescript-eslint/unbound-method + const origResEnd = res.end; + res.end = async function (this: unknown, ...args: unknown[]) { + if (transaction) { + await finishTransaction(transaction, res); + await flushQueue(); + } + + origResEnd.apply(this, args); + }; + } } } - } - try { - const handlerResult = await origHandler(req, res); - - if ( - process.env.NODE_ENV === 'development' && - !process.env.SENTRY_IGNORE_API_RESOLUTION_ERROR && - !res.finished - // This can only happen (not always) when the user is using `withSentry` manually, which we're deprecating. - // Warning suppression on Next.JS is only necessary in that case. - ) { - // eslint-disable-next-line no-console - console.warn( - `[sentry] If Next.js logs a warning "API resolved without sending a response", it's a false positive, which may happen when you use \`withSentry\` manually to wrap your routes. - To suppress this warning, set \`SENTRY_IGNORE_API_RESOLUTION_ERROR\` to 1 in your env. - To suppress the nextjs warning, use the \`externalResolver\` API route option (see https://nextjs.org/docs/api-routes/api-middlewares#custom-config for details).`, - ); - } - - return handlerResult; - } catch (e) { - // In case we have a primitive, wrap it in the equivalent wrapper class (string -> String, etc.) so that we can - // store a seen flag on it. (Because of the one-way-on-Vercel-one-way-off-of-Vercel approach we've been forced - // to take, it can happen that the same thrown object gets caught in two different ways, and flagging it is a - // way to prevent it from actually being reported twice.) - const objectifiedErr = objectify(e); + try { + const handlerResult = await wrappingTarget.apply(thisArg, args); + + if ( + process.env.NODE_ENV === 'development' && + !process.env.SENTRY_IGNORE_API_RESOLUTION_ERROR && + !res.finished + // This can only happen (not always) when the user is using `withSentry` manually, which we're deprecating. + // Warning suppression on Next.JS is only necessary in that case. + ) { + // eslint-disable-next-line no-console + console.warn( + `[sentry] If Next.js logs a warning "API resolved without sending a response", it's a false positive, which may happen when you use \`withSentry\` manually to wrap your routes. + To suppress this warning, set \`SENTRY_IGNORE_API_RESOLUTION_ERROR\` to 1 in your env. + To suppress the nextjs warning, use the \`externalResolver\` API route option (see https://nextjs.org/docs/api-routes/api-middlewares#custom-config for details).`, + ); + } - if (currentScope) { - currentScope.addEventProcessor(event => { - addExceptionMechanism(event, { - type: 'instrument', - handled: true, - data: { - wrapped_handler: origHandler.name, - function: 'withSentry', - }, + return handlerResult; + } catch (e) { + // In case we have a primitive, wrap it in the equivalent wrapper class (string -> String, etc.) so that we can + // store a seen flag on it. (Because of the one-way-on-Vercel-one-way-off-of-Vercel approach we've been forced + // to take, it can happen that the same thrown object gets caught in two different ways, and flagging it is a + // way to prevent it from actually being reported twice.) + const objectifiedErr = objectify(e); + + if (currentScope) { + currentScope.addEventProcessor(event => { + addExceptionMechanism(event, { + type: 'instrument', + handled: true, + data: { + wrapped_handler: wrappingTarget.name, + function: 'withSentry', + }, + }); + return event; }); - return event; - }); - captureException(objectifiedErr); - } + captureException(objectifiedErr); + } - // Because we're going to finish and send the transaction before passing the error onto nextjs, it won't yet - // have had a chance to set the status to 500, so unless we do it ourselves now, we'll incorrectly report that - // the transaction was error-free - res.statusCode = 500; - res.statusMessage = 'Internal Server Error'; - - // Make sure we have a chance to finish the transaction and flush events to Sentry before the handler errors - // out. (Apps which are deployed on Vercel run their API routes in lambdas, and those lambdas will shut down the - // moment they detect an error, so it's important to get this done before rethrowing the error. Apps not - // deployed serverlessly will run into this cleanup code again in `res.end(), but the transaction will already - // be finished and the queue will already be empty, so effectively it'll just no-op.) - if (platformSupportsStreaming() && !origHandler.__sentry_test_doesnt_support_streaming__) { - void finishTransaction(transaction, res); - } else { - await finishTransaction(transaction, res); - await flushQueue(); - } + // Because we're going to finish and send the transaction before passing the error onto nextjs, it won't yet + // have had a chance to set the status to 500, so unless we do it ourselves now, we'll incorrectly report that + // the transaction was error-free + res.statusCode = 500; + res.statusMessage = 'Internal Server Error'; + + // Make sure we have a chance to finish the transaction and flush events to Sentry before the handler errors + // out. (Apps which are deployed on Vercel run their API routes in lambdas, and those lambdas will shut down the + // moment they detect an error, so it's important to get this done before rethrowing the error. Apps not + // deployed serverlessly will run into this cleanup code again in `res.end(), but the transaction will already + // be finished and the queue will already be empty, so effectively it'll just no-op.) + if (platformSupportsStreaming() && !wrappingTarget.__sentry_test_doesnt_support_streaming__) { + void finishTransaction(transaction, res); + } else { + await finishTransaction(transaction, res); + await flushQueue(); + } - // We rethrow here so that nextjs can do with the error whatever it would normally do. (Sometimes "whatever it - // would normally do" is to allow the error to bubble up to the global handlers - another reason we need to mark - // the error as already having been captured.) - throw objectifiedErr; - } - }); + // We rethrow here so that nextjs can do with the error whatever it would normally do. (Sometimes "whatever it + // would normally do" is to allow the error to bubble up to the global handlers - another reason we need to mark + // the error as already having been captured.) + throw objectifiedErr; + } + }); - // Since API route handlers are all async, nextjs always awaits the return value (meaning it's fine for us to return - // a promise here rather than a real result, and it saves us the overhead of an `await` call.) - return boundHandler(); - }; + // Since API route handlers are all async, nextjs always awaits the return value (meaning it's fine for us to return + // a promise here rather than a real result, and it saves us the overhead of an `await` call.) + return boundHandler(); + }, + }); } diff --git a/packages/nextjs/src/server/wrapAppGetInitialPropsWithSentry.ts b/packages/nextjs/src/server/wrapAppGetInitialPropsWithSentry.ts index 09b9d7070e9f..953f75142b32 100644 --- a/packages/nextjs/src/server/wrapAppGetInitialPropsWithSentry.ts +++ b/packages/nextjs/src/server/wrapAppGetInitialPropsWithSentry.ts @@ -21,58 +21,60 @@ type AppGetInitialProps = typeof App['getInitialProps']; * @returns A wrapped version of the function */ export function wrapAppGetInitialPropsWithSentry(origAppGetInitialProps: AppGetInitialProps): AppGetInitialProps { - return async function (this: unknown, ...args: Parameters): ReturnType { - if (isBuild()) { - return origAppGetInitialProps.apply(this, args); - } + return new Proxy(origAppGetInitialProps, { + apply: async (wrappingTarget, thisArg, args: Parameters) => { + if (isBuild()) { + return wrappingTarget.apply(thisArg, args); + } - const [context] = args; - const { req, res } = context.ctx; + const [context] = args; + const { req, res } = context.ctx; - const errorWrappedAppGetInitialProps = withErrorInstrumentation(origAppGetInitialProps); - const options = getCurrentHub().getClient()?.getOptions(); + const errorWrappedAppGetInitialProps = withErrorInstrumentation(wrappingTarget); + const options = getCurrentHub().getClient()?.getOptions(); - // Generally we can assume that `req` and `res` are always defined on the server: - // https://nextjs.org/docs/api-reference/data-fetching/get-initial-props#context-object - // This does not seem to be the case in dev mode. Because we have no clean way of associating the the data fetcher - // span with each other when there are no req or res objects, we simply do not trace them at all here. - if (hasTracingEnabled() && req && res && options?.instrumenter === 'sentry') { - const tracedGetInitialProps = withTracedServerSideDataFetcher(errorWrappedAppGetInitialProps, req, res, { - dataFetcherRouteName: '/_app', - requestedRouteName: context.ctx.pathname, - dataFetchingMethodName: 'getInitialProps', - }); + // Generally we can assume that `req` and `res` are always defined on the server: + // https://nextjs.org/docs/api-reference/data-fetching/get-initial-props#context-object + // This does not seem to be the case in dev mode. Because we have no clean way of associating the the data fetcher + // span with each other when there are no req or res objects, we simply do not trace them at all here. + if (hasTracingEnabled() && req && res && options?.instrumenter === 'sentry') { + const tracedGetInitialProps = withTracedServerSideDataFetcher(errorWrappedAppGetInitialProps, req, res, { + dataFetcherRouteName: '/_app', + requestedRouteName: context.ctx.pathname, + dataFetchingMethodName: 'getInitialProps', + }); - const appGetInitialProps: { - pageProps: { - _sentryTraceData?: string; - _sentryBaggage?: string; - }; - } = await tracedGetInitialProps.apply(this, args); + const appGetInitialProps: { + pageProps: { + _sentryTraceData?: string; + _sentryBaggage?: string; + }; + } = await tracedGetInitialProps.apply(thisArg, args); - const requestTransaction = getTransactionFromRequest(req); + const requestTransaction = getTransactionFromRequest(req); - // Per definition, `pageProps` is not optional, however an increased amount of users doesn't seem to call - // `App.getInitialProps(appContext)` in their custom `_app` pages which is required as per - // https://nextjs.org/docs/advanced-features/custom-app - resulting in missing `pageProps`. - // For this reason, we just handle the case where `pageProps` doesn't exist explicitly. - if (!appGetInitialProps.pageProps) { - appGetInitialProps.pageProps = {}; - } + // Per definition, `pageProps` is not optional, however an increased amount of users doesn't seem to call + // `App.getInitialProps(appContext)` in their custom `_app` pages which is required as per + // https://nextjs.org/docs/advanced-features/custom-app - resulting in missing `pageProps`. + // For this reason, we just handle the case where `pageProps` doesn't exist explicitly. + if (!appGetInitialProps.pageProps) { + appGetInitialProps.pageProps = {}; + } - if (requestTransaction) { - appGetInitialProps.pageProps._sentryTraceData = requestTransaction.toTraceparent(); + if (requestTransaction) { + appGetInitialProps.pageProps._sentryTraceData = requestTransaction.toTraceparent(); - const dynamicSamplingContext = requestTransaction.getDynamicSamplingContext(); - appGetInitialProps.pageProps._sentryBaggage = - dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext); - } + const dynamicSamplingContext = requestTransaction.getDynamicSamplingContext(); + appGetInitialProps.pageProps._sentryBaggage = + dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext); + } - return appGetInitialProps; - } else { - return errorWrappedAppGetInitialProps.apply(this, args); - } - }; + return appGetInitialProps; + } else { + return errorWrappedAppGetInitialProps.apply(thisArg, args); + } + }, + }); } /** diff --git a/packages/nextjs/src/server/wrapDocumentGetInitialPropsWithSentry.ts b/packages/nextjs/src/server/wrapDocumentGetInitialPropsWithSentry.ts index 614516af1e47..a3d1aa46eaea 100644 --- a/packages/nextjs/src/server/wrapDocumentGetInitialPropsWithSentry.ts +++ b/packages/nextjs/src/server/wrapDocumentGetInitialPropsWithSentry.ts @@ -18,36 +18,35 @@ type DocumentGetInitialProps = typeof Document.getInitialProps; export function wrapDocumentGetInitialPropsWithSentry( origDocumentGetInitialProps: DocumentGetInitialProps, ): DocumentGetInitialProps { - return async function ( - this: unknown, - ...args: Parameters - ): ReturnType { - if (isBuild()) { - return origDocumentGetInitialProps.apply(this, args); - } - - const [context] = args; - const { req, res } = context; - - const errorWrappedGetInitialProps = withErrorInstrumentation(origDocumentGetInitialProps); - const options = getCurrentHub().getClient()?.getOptions(); - - // Generally we can assume that `req` and `res` are always defined on the server: - // https://nextjs.org/docs/api-reference/data-fetching/get-initial-props#context-object - // This does not seem to be the case in dev mode. Because we have no clean way of associating the the data fetcher - // span with each other when there are no req or res objects, we simply do not trace them at all here. - if (hasTracingEnabled() && req && res && options?.instrumenter === 'sentry') { - const tracedGetInitialProps = withTracedServerSideDataFetcher(errorWrappedGetInitialProps, req, res, { - dataFetcherRouteName: '/_document', - requestedRouteName: context.pathname, - dataFetchingMethodName: 'getInitialProps', - }); - - return await tracedGetInitialProps.apply(this, args); - } else { - return errorWrappedGetInitialProps.apply(this, args); - } - }; + return new Proxy(origDocumentGetInitialProps, { + apply: async (wrappingTarget, thisArg, args: Parameters) => { + if (isBuild()) { + return wrappingTarget.apply(thisArg, args); + } + + const [context] = args; + const { req, res } = context; + + const errorWrappedGetInitialProps = withErrorInstrumentation(wrappingTarget); + const options = getCurrentHub().getClient()?.getOptions(); + + // Generally we can assume that `req` and `res` are always defined on the server: + // https://nextjs.org/docs/api-reference/data-fetching/get-initial-props#context-object + // This does not seem to be the case in dev mode. Because we have no clean way of associating the the data fetcher + // span with each other when there are no req or res objects, we simply do not trace them at all here. + if (hasTracingEnabled() && req && res && options?.instrumenter === 'sentry') { + const tracedGetInitialProps = withTracedServerSideDataFetcher(errorWrappedGetInitialProps, req, res, { + dataFetcherRouteName: '/_document', + requestedRouteName: context.pathname, + dataFetchingMethodName: 'getInitialProps', + }); + + return await tracedGetInitialProps.apply(thisArg, args); + } else { + return errorWrappedGetInitialProps.apply(thisArg, args); + } + }, + }); } /** diff --git a/packages/nextjs/src/server/wrapErrorGetInitialPropsWithSentry.ts b/packages/nextjs/src/server/wrapErrorGetInitialPropsWithSentry.ts index 5c1987b26416..bf01ec8e4e84 100644 --- a/packages/nextjs/src/server/wrapErrorGetInitialPropsWithSentry.ts +++ b/packages/nextjs/src/server/wrapErrorGetInitialPropsWithSentry.ts @@ -24,46 +24,48 @@ type ErrorGetInitialProps = (context: NextPageContext) => Promise; export function wrapErrorGetInitialPropsWithSentry( origErrorGetInitialProps: ErrorGetInitialProps, ): ErrorGetInitialProps { - return async function (this: unknown, ...args: Parameters): ReturnType { - if (isBuild()) { - return origErrorGetInitialProps.apply(this, args); - } + return new Proxy(origErrorGetInitialProps, { + apply: async (wrappingTarget, thisArg, args: Parameters) => { + if (isBuild()) { + return wrappingTarget.apply(thisArg, args); + } - const [context] = args; - const { req, res } = context; + const [context] = args; + const { req, res } = context; - const errorWrappedGetInitialProps = withErrorInstrumentation(origErrorGetInitialProps); - const options = getCurrentHub().getClient()?.getOptions(); + const errorWrappedGetInitialProps = withErrorInstrumentation(wrappingTarget); + const options = getCurrentHub().getClient()?.getOptions(); - // Generally we can assume that `req` and `res` are always defined on the server: - // https://nextjs.org/docs/api-reference/data-fetching/get-initial-props#context-object - // This does not seem to be the case in dev mode. Because we have no clean way of associating the the data fetcher - // span with each other when there are no req or res objects, we simply do not trace them at all here. - if (hasTracingEnabled() && req && res && options?.instrumenter === 'sentry') { - const tracedGetInitialProps = withTracedServerSideDataFetcher(errorWrappedGetInitialProps, req, res, { - dataFetcherRouteName: '/_error', - requestedRouteName: context.pathname, - dataFetchingMethodName: 'getInitialProps', - }); + // Generally we can assume that `req` and `res` are always defined on the server: + // https://nextjs.org/docs/api-reference/data-fetching/get-initial-props#context-object + // This does not seem to be the case in dev mode. Because we have no clean way of associating the the data fetcher + // span with each other when there are no req or res objects, we simply do not trace them at all here. + if (hasTracingEnabled() && req && res && options?.instrumenter === 'sentry') { + const tracedGetInitialProps = withTracedServerSideDataFetcher(errorWrappedGetInitialProps, req, res, { + dataFetcherRouteName: '/_error', + requestedRouteName: context.pathname, + dataFetchingMethodName: 'getInitialProps', + }); - const errorGetInitialProps: ErrorProps & { - _sentryTraceData?: string; - _sentryBaggage?: string; - } = await tracedGetInitialProps.apply(this, args); + const errorGetInitialProps: ErrorProps & { + _sentryTraceData?: string; + _sentryBaggage?: string; + } = await tracedGetInitialProps.apply(thisArg, args); - const requestTransaction = getTransactionFromRequest(req); - if (requestTransaction) { - errorGetInitialProps._sentryTraceData = requestTransaction.toTraceparent(); + const requestTransaction = getTransactionFromRequest(req); + if (requestTransaction) { + errorGetInitialProps._sentryTraceData = requestTransaction.toTraceparent(); - const dynamicSamplingContext = requestTransaction.getDynamicSamplingContext(); - errorGetInitialProps._sentryBaggage = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext); - } + const dynamicSamplingContext = requestTransaction.getDynamicSamplingContext(); + errorGetInitialProps._sentryBaggage = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext); + } - return errorGetInitialProps; - } else { - return errorWrappedGetInitialProps.apply(this, args); - } - }; + return errorGetInitialProps; + } else { + return errorWrappedGetInitialProps.apply(thisArg, args); + } + }, + }); } /** diff --git a/packages/nextjs/src/server/wrapGetInitialPropsWithSentry.ts b/packages/nextjs/src/server/wrapGetInitialPropsWithSentry.ts index 72521f10c4f2..c180dfe3b9be 100644 --- a/packages/nextjs/src/server/wrapGetInitialPropsWithSentry.ts +++ b/packages/nextjs/src/server/wrapGetInitialPropsWithSentry.ts @@ -20,46 +20,48 @@ type GetInitialProps = Required['getInitialProps']; * @returns A wrapped version of the function */ export function wrapGetInitialPropsWithSentry(origGetInitialProps: GetInitialProps): GetInitialProps { - return async function (this: unknown, ...args: Parameters): Promise> { - if (isBuild()) { - return origGetInitialProps.apply(this, args); - } + return new Proxy(origGetInitialProps, { + apply: async (wrappingTarget, thisArg, args: Parameters) => { + if (isBuild()) { + return wrappingTarget.apply(thisArg, args); + } - const [context] = args; - const { req, res } = context; + const [context] = args; + const { req, res } = context; - const errorWrappedGetInitialProps = withErrorInstrumentation(origGetInitialProps); - const options = getCurrentHub().getClient()?.getOptions(); + const errorWrappedGetInitialProps = withErrorInstrumentation(wrappingTarget); + const options = getCurrentHub().getClient()?.getOptions(); - // Generally we can assume that `req` and `res` are always defined on the server: - // https://nextjs.org/docs/api-reference/data-fetching/get-initial-props#context-object - // This does not seem to be the case in dev mode. Because we have no clean way of associating the the data fetcher - // span with each other when there are no req or res objects, we simply do not trace them at all here. - if (hasTracingEnabled() && req && res && options?.instrumenter === 'sentry') { - const tracedGetInitialProps = withTracedServerSideDataFetcher(errorWrappedGetInitialProps, req, res, { - dataFetcherRouteName: context.pathname, - requestedRouteName: context.pathname, - dataFetchingMethodName: 'getInitialProps', - }); + // Generally we can assume that `req` and `res` are always defined on the server: + // https://nextjs.org/docs/api-reference/data-fetching/get-initial-props#context-object + // This does not seem to be the case in dev mode. Because we have no clean way of associating the the data fetcher + // span with each other when there are no req or res objects, we simply do not trace them at all here. + if (hasTracingEnabled() && req && res && options?.instrumenter === 'sentry') { + const tracedGetInitialProps = withTracedServerSideDataFetcher(errorWrappedGetInitialProps, req, res, { + dataFetcherRouteName: context.pathname, + requestedRouteName: context.pathname, + dataFetchingMethodName: 'getInitialProps', + }); - const initialProps: { - _sentryTraceData?: string; - _sentryBaggage?: string; - } = await tracedGetInitialProps.apply(this, args); + const initialProps: { + _sentryTraceData?: string; + _sentryBaggage?: string; + } = await tracedGetInitialProps.apply(thisArg, args); - const requestTransaction = getTransactionFromRequest(req); - if (requestTransaction) { - initialProps._sentryTraceData = requestTransaction.toTraceparent(); + const requestTransaction = getTransactionFromRequest(req); + if (requestTransaction) { + initialProps._sentryTraceData = requestTransaction.toTraceparent(); - const dynamicSamplingContext = requestTransaction.getDynamicSamplingContext(); - initialProps._sentryBaggage = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext); - } + const dynamicSamplingContext = requestTransaction.getDynamicSamplingContext(); + initialProps._sentryBaggage = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext); + } - return initialProps; - } else { - return errorWrappedGetInitialProps.apply(this, args); - } - }; + return initialProps; + } else { + return errorWrappedGetInitialProps.apply(thisArg, args); + } + }, + }); } /** diff --git a/packages/nextjs/src/server/wrapGetServerSidePropsWithSentry.ts b/packages/nextjs/src/server/wrapGetServerSidePropsWithSentry.ts index 691b890e4723..e305a72686d5 100644 --- a/packages/nextjs/src/server/wrapGetServerSidePropsWithSentry.ts +++ b/packages/nextjs/src/server/wrapGetServerSidePropsWithSentry.ts @@ -21,43 +21,45 @@ export function wrapGetServerSidePropsWithSentry( origGetServerSideProps: GetServerSideProps, parameterizedRoute: string, ): GetServerSideProps { - return async function (this: unknown, ...args: Parameters): ReturnType { - if (isBuild()) { - return origGetServerSideProps.apply(this, args); - } + return new Proxy(origGetServerSideProps, { + apply: async (wrappingTarget, thisArg, args: Parameters) => { + if (isBuild()) { + return wrappingTarget.apply(thisArg, args); + } - const [context] = args; - const { req, res } = context; + const [context] = args; + const { req, res } = context; - const errorWrappedGetServerSideProps = withErrorInstrumentation(origGetServerSideProps); - const options = getCurrentHub().getClient()?.getOptions(); + const errorWrappedGetServerSideProps = withErrorInstrumentation(wrappingTarget); + const options = getCurrentHub().getClient()?.getOptions(); - if (hasTracingEnabled() && options?.instrumenter === 'sentry') { - const tracedGetServerSideProps = withTracedServerSideDataFetcher(errorWrappedGetServerSideProps, req, res, { - dataFetcherRouteName: parameterizedRoute, - requestedRouteName: parameterizedRoute, - dataFetchingMethodName: 'getServerSideProps', - }); + if (hasTracingEnabled() && options?.instrumenter === 'sentry') { + const tracedGetServerSideProps = withTracedServerSideDataFetcher(errorWrappedGetServerSideProps, req, res, { + dataFetcherRouteName: parameterizedRoute, + requestedRouteName: parameterizedRoute, + dataFetchingMethodName: 'getServerSideProps', + }); - const serverSideProps = await (tracedGetServerSideProps.apply(this, args) as ReturnType< - typeof tracedGetServerSideProps - >); + const serverSideProps = await (tracedGetServerSideProps.apply(thisArg, args) as ReturnType< + typeof tracedGetServerSideProps + >); - if ('props' in serverSideProps) { - const requestTransaction = getTransactionFromRequest(req); - if (requestTransaction) { - serverSideProps.props._sentryTraceData = requestTransaction.toTraceparent(); + if ('props' in serverSideProps) { + const requestTransaction = getTransactionFromRequest(req); + if (requestTransaction) { + serverSideProps.props._sentryTraceData = requestTransaction.toTraceparent(); - const dynamicSamplingContext = requestTransaction.getDynamicSamplingContext(); - serverSideProps.props._sentryBaggage = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext); + const dynamicSamplingContext = requestTransaction.getDynamicSamplingContext(); + serverSideProps.props._sentryBaggage = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext); + } } - } - return serverSideProps; - } else { - return errorWrappedGetServerSideProps.apply(this, args); - } - }; + return serverSideProps; + } else { + return errorWrappedGetServerSideProps.apply(thisArg, args); + } + }, + }); } /** diff --git a/packages/nextjs/src/server/wrapGetStaticPropsWithSentry.ts b/packages/nextjs/src/server/wrapGetStaticPropsWithSentry.ts index 380ea38949fc..e21978580ea6 100644 --- a/packages/nextjs/src/server/wrapGetStaticPropsWithSentry.ts +++ b/packages/nextjs/src/server/wrapGetStaticPropsWithSentry.ts @@ -15,28 +15,28 @@ type Props = { [key: string]: unknown }; * @returns A wrapped version of the function */ export function wrapGetStaticPropsWithSentry( - origGetStaticProps: GetStaticProps, + origGetStaticPropsa: GetStaticProps, parameterizedRoute: string, ): GetStaticProps { - return async function ( - ...getStaticPropsArguments: Parameters> - ): ReturnType> { - if (isBuild()) { - return origGetStaticProps(...getStaticPropsArguments); - } + return new Proxy(origGetStaticPropsa, { + apply: async (wrappingTarget, thisArg, args: Parameters>) => { + if (isBuild()) { + return wrappingTarget.apply(thisArg, args); + } - const errorWrappedGetStaticProps = withErrorInstrumentation(origGetStaticProps); - const options = getCurrentHub().getClient()?.getOptions(); + const errorWrappedGetStaticProps = withErrorInstrumentation(wrappingTarget); + const options = getCurrentHub().getClient()?.getOptions(); - if (hasTracingEnabled() && options?.instrumenter === 'sentry') { - return callDataFetcherTraced(errorWrappedGetStaticProps, getStaticPropsArguments, { - parameterizedRoute, - dataFetchingMethodName: 'getStaticProps', - }); - } + if (hasTracingEnabled() && options?.instrumenter === 'sentry') { + return callDataFetcherTraced(errorWrappedGetStaticProps, args, { + parameterizedRoute, + dataFetchingMethodName: 'getStaticProps', + }); + } - return errorWrappedGetStaticProps(...getStaticPropsArguments); - }; + return errorWrappedGetStaticProps.apply(thisArg, args); + }, + }); } /** diff --git a/packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts b/packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts index db27f13df67f..01f89bed1077 100644 --- a/packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts +++ b/packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts @@ -139,7 +139,7 @@ describe('constructWebpackConfigFunction()', () => { ); }); - it('injects user config file into `_app` in server bundle but not in client bundle', async () => { + it('injects user config file into `_app` in server bundle and in the client bundle', async () => { const finalServerWebpackConfig = await materializeFinalWebpackConfig({ exportedNextConfig, incomingWebpackConfig: serverWebpackConfig, @@ -158,7 +158,7 @@ describe('constructWebpackConfigFunction()', () => { ); expect(finalClientWebpackConfig.entry).toEqual( expect.objectContaining({ - 'pages/_app': expect.not.arrayContaining([clientConfigFilePath]), + 'pages/_app': expect.arrayContaining([clientConfigFilePath]), }), ); }); @@ -233,9 +233,9 @@ describe('constructWebpackConfigFunction()', () => { }); expect(finalWebpackConfig.entry).toEqual({ - main: ['./sentry.client.config.js', './src/index.ts'], + main: './src/index.ts', // only _app has config file injected - 'pages/_app': 'next-client-pages-loader?page=%2F_app', + 'pages/_app': ['./sentry.client.config.js', 'next-client-pages-loader?page=%2F_app'], 'pages/_error': 'next-client-pages-loader?page=%2F_error', 'pages/sniffTour': ['./node_modules/smellOVision/index.js', 'private-next-pages/sniffTour.js'], 'pages/simulator/leaderboard': { diff --git a/packages/nextjs/test/config/withSentry.test.ts b/packages/nextjs/test/config/withSentry.test.ts index e622df357a61..dfc06c9bcf7e 100644 --- a/packages/nextjs/test/config/withSentry.test.ts +++ b/packages/nextjs/test/config/withSentry.test.ts @@ -1,10 +1,10 @@ import * as hub from '@sentry/core'; import * as Sentry from '@sentry/node'; import type { Client, ClientOptions } from '@sentry/types'; -import type { NextApiHandler, NextApiRequest, NextApiResponse } from 'next'; +import type { NextApiRequest, NextApiResponse } from 'next'; import { withSentry } from '../../src/server'; -import type { AugmentedNextApiResponse, WrappedNextApiHandler } from '../../src/server/types'; +import type { AugmentedNextApiResponse, NextApiHandler } from '../../src/server/types'; const FLUSH_DURATION = 200; @@ -23,7 +23,7 @@ async function sleep(ms: number): Promise { * @param req * @param res */ -async function callWrappedHandler(wrappedHandler: WrappedNextApiHandler, req: NextApiRequest, res: NextApiResponse) { +async function callWrappedHandler(wrappedHandler: NextApiHandler, req: NextApiRequest, res: NextApiResponse) { await wrappedHandler(req, res); // we know we need to wait at least this long for `flush()` to finish diff --git a/packages/replay/.eslintrc.js b/packages/replay/.eslintrc.js index bb645aac9ed1..a0d9dc46f1f8 100644 --- a/packages/replay/.eslintrc.js +++ b/packages/replay/.eslintrc.js @@ -13,6 +13,10 @@ module.exports = { // TODO: figure out if we need a worker-specific tsconfig project: ['tsconfig.worker.json'], }, + rules: { + // We cannot use backticks, as that conflicts with the stringified worker + 'prefer-template': 'off', + }, }, { files: ['src/worker/**/*.js'], diff --git a/packages/replay/MIGRATION.md b/packages/replay/MIGRATION.md index 2c738e6e9acf..c196a735058a 100644 --- a/packages/replay/MIGRATION.md +++ b/packages/replay/MIGRATION.md @@ -1,3 +1,23 @@ +# Upgrading Replay from 7.34.0 to 7.35.0 - #6645 + +This release will remove the ability to change the default rrweb recording options (outside of privacy options). The following are the new configuration values all replays will use: +`slimDOMOptions: 'all'` - Removes `script`, comments, `favicon`, whitespace in `head`, and a few `meta` tags in `head` +`recordCanvas: false` - This option did not do anything as playback of recorded canvas means we would have to remove the playback sandbox (which is a security concern). +`inlineStylesheet: true` - Inlines styles into the recording itself instead of attempting to fetch it remotely. This means that styles in the replay will reflect the styles at the time of recording and not the current styles of the remote stylesheet. +`collectFonts: true` - Attempts to load custom fonts. +`inlineImages: false` - Does not inline images to recording and instead loads the asset remotely. During playback, images may not load due to CORS (add sentry.io as an origin). + +Additionally, we have streamlined the privacy options. The following table lists the deprecated value, and what it is replaced by: + +| deprecated key | replaced by | description | +| ---------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | +| maskInputOptions | mask | Use CSS selectors in `mask` in order to mask all inputs of a certain type. For example, `input[type="address"]` | +| blockSelector | block | The selector(s) can be moved directly in the `block` array. | +| blockClass | block | Convert the class name to a CSS selector and add to `block` array. For example, `first-name` becomes `.first-name`. Regexes can be moved as-is. | +| maskClass | mask | Convert the class name to a CSS selector and add to `mask` array. For example, `first-name` becomes `.first-name`. Regexes can be moved as-is. | +| maskSelector | mask | The selector(s) can be moved directly in the `mask` array. | +| ignoreClass | ignore | Convert the class name to a CSS selector and add to `ignore` array. For example, `first-name` becomes `.first-name`. Regexes can be moved as-is. | + # Upgrading Replay from 7.31.0 to 7.32.0 In 7.32.0, we have removed the default values for the replay sample rates. diff --git a/packages/replay/package.json b/packages/replay/package.json index cc9e9f0a51e9..011fbe9e0bcf 100644 --- a/packages/replay/package.json +++ b/packages/replay/package.json @@ -46,7 +46,7 @@ "homepage": "https://docs.sentry.io/platforms/javascript/session-replay/", "devDependencies": { "@babel/core": "^7.17.5", - "@sentry-internal/rrweb": "1.100.1", + "@sentry-internal/rrweb": "1.100.2", "@types/pako": "^2.0.0", "jsdom-worker": "^0.2.1", "pako": "^2.0.4", diff --git a/packages/replay/src/eventBuffer/EventBufferArray.ts b/packages/replay/src/eventBuffer/EventBufferArray.ts index 59eda189e60c..3ec54a526b9d 100644 --- a/packages/replay/src/eventBuffer/EventBufferArray.ts +++ b/packages/replay/src/eventBuffer/EventBufferArray.ts @@ -5,38 +5,31 @@ import type { AddEventResult, EventBuffer, RecordingEvent } from '../types'; * Used as fallback if the compression worker cannot be loaded or is disabled. */ export class EventBufferArray implements EventBuffer { - private _events: RecordingEvent[]; + /** All the events that are buffered to be sent. */ + public events: RecordingEvent[]; public constructor() { - this._events = []; + this.events = []; } /** @inheritdoc */ - public get pendingLength(): number { - return this._events.length; - } - - /** - * Returns the raw events that are buffered. In `EventBufferArray`, this is the - * same as `this._events`. - */ - public get pendingEvents(): RecordingEvent[] { - return this._events; + public get hasEvents(): boolean { + return this.events.length > 0; } /** @inheritdoc */ public destroy(): void { - this._events = []; + this.events = []; } /** @inheritdoc */ public async addEvent(event: RecordingEvent, isCheckout?: boolean): Promise { if (isCheckout) { - this._events = [event]; + this.events = [event]; return; } - this._events.push(event); + this.events.push(event); return; } @@ -46,8 +39,8 @@ export class EventBufferArray implements EventBuffer { // Make a copy of the events array reference and immediately clear the // events member so that we do not lose new events while uploading // attachment. - const eventsRet = this._events; - this._events = []; + const eventsRet = this.events; + this.events = []; resolve(JSON.stringify(eventsRet)); }); } diff --git a/packages/replay/src/eventBuffer/EventBufferCompressionWorker.ts b/packages/replay/src/eventBuffer/EventBufferCompressionWorker.ts index 66cb92db849b..3d532b68df28 100644 --- a/packages/replay/src/eventBuffer/EventBufferCompressionWorker.ts +++ b/packages/replay/src/eventBuffer/EventBufferCompressionWorker.ts @@ -1,43 +1,21 @@ import type { ReplayRecordingData } from '@sentry/types'; -import { logger } from '@sentry/utils'; -import type { AddEventResult, EventBuffer, RecordingEvent, WorkerRequest, WorkerResponse } from '../types'; +import type { AddEventResult, EventBuffer, RecordingEvent } from '../types'; +import { WorkerHandler } from './WorkerHandler'; /** * Event buffer that uses a web worker to compress events. * Exported only for testing. */ export class EventBufferCompressionWorker implements EventBuffer { - /** - * Keeps track of the list of events since the last flush that have not been compressed. - * For example, page is reloaded and a flush attempt is made, but - * `finish()` (and thus the flush), does not complete. - */ - public _pendingEvents: RecordingEvent[] = []; + /** @inheritdoc */ + public hasEvents: boolean; - private _worker: Worker; - private _eventBufferItemLength: number = 0; - private _id: number = 0; - private _ensureReadyPromise?: Promise; + private _worker: WorkerHandler; public constructor(worker: Worker) { - this._worker = worker; - } - - /** - * The number of raw events that are buffered. This may not be the same as - * the number of events that have been compresed in the worker because - * `addEvent` is async. - */ - public get pendingLength(): number { - return this._eventBufferItemLength; - } - - /** - * Returns a list of the raw recording events that are being compressed. - */ - public get pendingEvents(): RecordingEvent[] { - return this._pendingEvents; + this._worker = new WorkerHandler(worker); + this.hasEvents = false; } /** @@ -45,42 +23,14 @@ export class EventBufferCompressionWorker implements EventBuffer { * This will either resolve when the worker is ready, or reject if an error occured. */ public ensureReady(): Promise { - // Ensure we only check once - if (this._ensureReadyPromise) { - return this._ensureReadyPromise; - } - - this._ensureReadyPromise = new Promise((resolve, reject) => { - this._worker.addEventListener( - 'message', - ({ data }: MessageEvent) => { - if ((data as WorkerResponse).success) { - resolve(); - } else { - reject(); - } - }, - { once: true }, - ); - - this._worker.addEventListener( - 'error', - error => { - reject(error); - }, - { once: true }, - ); - }); - - return this._ensureReadyPromise; + return this._worker.ensureReady(); } /** * Destroy the event buffer. */ public destroy(): void { - __DEBUG_BUILD__ && logger.log('[Replay] Destroying compression worker'); - this._worker.terminate(); + this._worker.destroy(); } /** @@ -89,19 +39,12 @@ export class EventBufferCompressionWorker implements EventBuffer { * Returns true if event was successfuly received and processed by worker. */ public async addEvent(event: RecordingEvent, isCheckout?: boolean): Promise { + this.hasEvents = true; + if (isCheckout) { // This event is a checkout, make sure worker buffer is cleared before // proceeding. - await this._postMessage({ - id: this._getAndIncrementId(), - method: 'init', - args: [], - }); - } - - // Don't store checkout events in `_pendingEvents` because they are too large - if (!isCheckout) { - this._pendingEvents.push(event); + await this._clear(); } return this._sendEventToWorker(event); @@ -110,97 +53,30 @@ export class EventBufferCompressionWorker implements EventBuffer { /** * Finish the event buffer and return the compressed data. */ - public async finish(): Promise { - try { - return await this._finishRequest(this._getAndIncrementId()); - } catch (error) { - __DEBUG_BUILD__ && logger.error('[Replay] Error when trying to compress events', error); - // fall back to uncompressed - const events = this.pendingEvents; - return JSON.stringify(events); - } - } - - /** - * Post message to worker and wait for response before resolving promise. - */ - private _postMessage({ id, method, args }: WorkerRequest): Promise { - return new Promise((resolve, reject) => { - const listener = ({ data }: MessageEvent): void => { - const response = data as WorkerResponse; - if (response.method !== method) { - return; - } - - // There can be multiple listeners for a single method, the id ensures - // that the response matches the caller. - if (response.id !== id) { - return; - } - - // At this point, we'll always want to remove listener regardless of result status - this._worker.removeEventListener('message', listener); - - if (!response.success) { - // TODO: Do some error handling, not sure what - __DEBUG_BUILD__ && logger.error('[Replay]', response.response); - - reject(new Error('Error in compression worker')); - return; - } - - resolve(response.response as T); - }; - - let stringifiedArgs; - try { - stringifiedArgs = JSON.stringify(args); - } catch (err) { - __DEBUG_BUILD__ && logger.error('[Replay] Error when trying to stringify args', err); - stringifiedArgs = '[]'; - } - - // Note: we can't use `once` option because it's possible it needs to - // listen to multiple messages - this._worker.addEventListener('message', listener); - this._worker.postMessage({ id, method, args: stringifiedArgs }); - }); + public finish(): Promise { + return this._finishRequest(); } /** * Send the event to the worker. */ - private async _sendEventToWorker(event: RecordingEvent): Promise { - const promise = this._postMessage({ - id: this._getAndIncrementId(), - method: 'addEvent', - args: [event], - }); - - // XXX: See note in `get length()` - this._eventBufferItemLength++; - - return promise; + private _sendEventToWorker(event: RecordingEvent): Promise { + return this._worker.postMessage('addEvent', JSON.stringify(event)); } /** * Finish the request and return the compressed data from the worker. */ - private async _finishRequest(id: number): Promise { - const promise = this._postMessage({ id, method: 'finish', args: [] }); - - // XXX: See note in `get length()` - this._eventBufferItemLength = 0; - - await promise; + private async _finishRequest(): Promise { + const response = await this._worker.postMessage('finish'); - this._pendingEvents = []; + this.hasEvents = false; - return promise; + return response; } - /** Get the current ID and increment it for the next call. */ - private _getAndIncrementId(): number { - return this._id++; + /** Clear any pending events from the worker. */ + private _clear(): Promise { + return this._worker.postMessage('clear'); } } diff --git a/packages/replay/src/eventBuffer/EventBufferProxy.ts b/packages/replay/src/eventBuffer/EventBufferProxy.ts index f438cd5a6ef9..24c5fa85f3a1 100644 --- a/packages/replay/src/eventBuffer/EventBufferProxy.ts +++ b/packages/replay/src/eventBuffer/EventBufferProxy.ts @@ -21,19 +21,12 @@ export class EventBufferProxy implements EventBuffer { this._compression = new EventBufferCompressionWorker(worker); this._used = this._fallback; - this._ensureWorkerIsLoadedPromise = this._ensureWorkerIsLoaded().catch(() => { - // Ignore errors here - }); + this._ensureWorkerIsLoadedPromise = this._ensureWorkerIsLoaded(); } /** @inheritDoc */ - public get pendingLength(): number { - return this._used.pendingLength; - } - - /** @inheritDoc */ - public get pendingEvents(): RecordingEvent[] { - return this._used.pendingEvents; + public get hasEvents(): boolean { + return this._used.hasEvents; } /** @inheritDoc */ @@ -75,18 +68,28 @@ export class EventBufferProxy implements EventBuffer { return; } - // Compression worker is ready, we can use it // Now we need to switch over the array buffer to the compression worker + await this._switchToCompressionWorker(); + } + + /** Switch the used buffer to the compression worker. */ + private async _switchToCompressionWorker(): Promise { + const { events } = this._fallback; + const addEventPromises: Promise[] = []; - for (const event of this._fallback.pendingEvents) { + for (const event of events) { addEventPromises.push(this._compression.addEvent(event)); } - // We switch over to the compression buffer immediately - any further events will be added + // We switch over to the new buffer immediately - any further events will be added // after the previously buffered ones this._used = this._compression; // Wait for original events to be re-added before resolving - await Promise.all(addEventPromises); + try { + await Promise.all(addEventPromises); + } catch (error) { + __DEBUG_BUILD__ && logger.warn('[Replay] Failed to add events when switching buffers.', error); + } } } diff --git a/packages/replay/src/eventBuffer/WorkerHandler.ts b/packages/replay/src/eventBuffer/WorkerHandler.ts new file mode 100644 index 000000000000..711b21bd3380 --- /dev/null +++ b/packages/replay/src/eventBuffer/WorkerHandler.ts @@ -0,0 +1,106 @@ +import { logger } from '@sentry/utils'; + +import type { WorkerRequest, WorkerResponse } from '../types'; + +/** + * Event buffer that uses a web worker to compress events. + * Exported only for testing. + */ +export class WorkerHandler { + private _worker: Worker; + private _id: number; + private _ensureReadyPromise?: Promise; + + public constructor(worker: Worker) { + this._worker = worker; + this._id = 0; + } + + /** + * Ensure the worker is ready (or not). + * This will either resolve when the worker is ready, or reject if an error occured. + */ + public ensureReady(): Promise { + // Ensure we only check once + if (this._ensureReadyPromise) { + return this._ensureReadyPromise; + } + + this._ensureReadyPromise = new Promise((resolve, reject) => { + this._worker.addEventListener( + 'message', + ({ data }: MessageEvent) => { + if ((data as WorkerResponse).success) { + resolve(); + } else { + reject(); + } + }, + { once: true }, + ); + + this._worker.addEventListener( + 'error', + error => { + reject(error); + }, + { once: true }, + ); + }); + + return this._ensureReadyPromise; + } + + /** + * Destroy the worker. + */ + public destroy(): void { + __DEBUG_BUILD__ && logger.log('[Replay] Destroying compression worker'); + this._worker.terminate(); + } + + /** + * Post message to worker and wait for response before resolving promise. + */ + public postMessage(method: WorkerRequest['method'], arg?: WorkerRequest['arg']): Promise { + const id = this._getAndIncrementId(); + + return new Promise((resolve, reject) => { + const listener = ({ data }: MessageEvent): void => { + const response = data as WorkerResponse; + if (response.method !== method) { + return; + } + + // There can be multiple listeners for a single method, the id ensures + // that the response matches the caller. + if (response.id !== id) { + return; + } + + // At this point, we'll always want to remove listener regardless of result status + this._worker.removeEventListener('message', listener); + + if (!response.success) { + // TODO: Do some error handling, not sure what + __DEBUG_BUILD__ && logger.error('[Replay]', response.response); + + reject(new Error('Error in compression worker')); + return; + } + + resolve(response.response as T); + }; + + // Note: we can't use `once` option because it's possible it needs to + // listen to multiple messages + this._worker.addEventListener('message', listener); + this._worker.postMessage({ id, method, arg }); + }); + } + + /** Get the current ID and increment it for the next call. */ + private _getAndIncrementId(): number { + return this._id++; + } +} diff --git a/packages/replay/src/replay.ts b/packages/replay/src/replay.ts index 2c648e13b786..ba55e339a611 100644 --- a/packages/replay/src/replay.ts +++ b/packages/replay/src/replay.ts @@ -772,7 +772,7 @@ export class ReplayContainer implements ReplayContainerInterface { await this._addPerformanceEntries(); // Check eventBuffer again, as it could have been stopped in the meanwhile - if (!this.eventBuffer || !this.eventBuffer.pendingLength) { + if (!this.eventBuffer || !this.eventBuffer.hasEvents) { return; } diff --git a/packages/replay/src/types.ts b/packages/replay/src/types.ts index a16b3547e0ec..b9e96d7186b1 100644 --- a/packages/replay/src/types.ts +++ b/packages/replay/src/types.ts @@ -23,8 +23,8 @@ export interface SendReplayData { */ export interface WorkerRequest { id: number; - method: string; - args: unknown[]; + method: 'clear' | 'addEvent' | 'finish'; + arg?: string; } // PerformancePaintTiming and PerformanceNavigationTiming are only available with TS 4.4 and newer @@ -261,14 +261,9 @@ export interface Session { export interface EventBuffer { /** - * The number of raw events that are buffered - */ - readonly pendingLength: number; - - /** - * The raw events that are buffered. + * If any events have been added to the buffer. */ - readonly pendingEvents: RecordingEvent[]; + readonly hasEvents: boolean; /** * Destroy the event buffer. diff --git a/packages/replay/src/util/addEvent.ts b/packages/replay/src/util/addEvent.ts index 2b957823bb0f..cb66d0c7822c 100644 --- a/packages/replay/src/util/addEvent.ts +++ b/packages/replay/src/util/addEvent.ts @@ -1,3 +1,5 @@ +import { logger } from '@sentry/utils'; + import { SESSION_IDLE_DURATION } from '../constants'; import type { AddEventResult, RecordingEvent, ReplayContainer } from '../types'; @@ -39,5 +41,10 @@ export async function addEvent( replay.getContext().earliestEvent = timestampInMs; } - return replay.eventBuffer.addEvent(event, isCheckout); + try { + return await replay.eventBuffer.addEvent(event, isCheckout); + } catch (error) { + __DEBUG_BUILD__ && logger.error(error); + replay.stop(); + } } diff --git a/packages/replay/src/worker/worker.js b/packages/replay/src/worker/worker.js index 34abe6a4f742..d034d5002634 100644 --- a/packages/replay/src/worker/worker.js +++ b/packages/replay/src/worker/worker.js @@ -1,2 +1,2 @@ export default `/*! pako 2.1.0 https://github.com/nodeca/pako @license (MIT AND Zlib) */ -function t(t){let e=t.length;for(;--e>=0;)t[e]=0}const e=new Uint8Array([0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0]),a=new Uint8Array([0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13]),i=new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7]),n=new Uint8Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),s=new Array(576);t(s);const r=new Array(60);t(r);const o=new Array(512);t(o);const l=new Array(256);t(l);const h=new Array(29);t(h);const d=new Array(30);function _(t,e,a,i,n){this.static_tree=t,this.extra_bits=e,this.extra_base=a,this.elems=i,this.max_length=n,this.has_stree=t&&t.length}let f,c,u;function w(t,e){this.dyn_tree=t,this.max_code=0,this.stat_desc=e}t(d);const m=t=>t<256?o[t]:o[256+(t>>>7)],b=(t,e)=>{t.pending_buf[t.pending++]=255&e,t.pending_buf[t.pending++]=e>>>8&255},g=(t,e,a)=>{t.bi_valid>16-a?(t.bi_buf|=e<>16-t.bi_valid,t.bi_valid+=a-16):(t.bi_buf|=e<{g(t,a[2*e],a[2*e+1])},k=(t,e)=>{let a=0;do{a|=1&t,t>>>=1,a<<=1}while(--e>0);return a>>>1},v=(t,e,a)=>{const i=new Array(16);let n,s,r=0;for(n=1;n<=15;n++)r=r+a[n-1]<<1,i[n]=r;for(s=0;s<=e;s++){let e=t[2*s+1];0!==e&&(t[2*s]=k(i[e]++,e))}},y=t=>{let e;for(e=0;e<286;e++)t.dyn_ltree[2*e]=0;for(e=0;e<30;e++)t.dyn_dtree[2*e]=0;for(e=0;e<19;e++)t.bl_tree[2*e]=0;t.dyn_ltree[512]=1,t.opt_len=t.static_len=0,t.sym_next=t.matches=0},x=t=>{t.bi_valid>8?b(t,t.bi_buf):t.bi_valid>0&&(t.pending_buf[t.pending++]=t.bi_buf),t.bi_buf=0,t.bi_valid=0},z=(t,e,a,i)=>{const n=2*e,s=2*a;return t[n]{const i=t.heap[a];let n=a<<1;for(;n<=t.heap_len&&(n{let s,r,o,_,f=0;if(0!==t.sym_next)do{s=255&t.pending_buf[t.sym_buf+f++],s+=(255&t.pending_buf[t.sym_buf+f++])<<8,r=t.pending_buf[t.sym_buf+f++],0===s?p(t,r,i):(o=l[r],p(t,o+256+1,i),_=e[o],0!==_&&(r-=h[o],g(t,r,_)),s--,o=m(s),p(t,o,n),_=a[o],0!==_&&(s-=d[o],g(t,s,_)))}while(f{const a=e.dyn_tree,i=e.stat_desc.static_tree,n=e.stat_desc.has_stree,s=e.stat_desc.elems;let r,o,l,h=-1;for(t.heap_len=0,t.heap_max=573,r=0;r>1;r>=1;r--)A(t,a,r);l=s;do{r=t.heap[1],t.heap[1]=t.heap[t.heap_len--],A(t,a,1),o=t.heap[1],t.heap[--t.heap_max]=r,t.heap[--t.heap_max]=o,a[2*l]=a[2*r]+a[2*o],t.depth[l]=(t.depth[r]>=t.depth[o]?t.depth[r]:t.depth[o])+1,a[2*r+1]=a[2*o+1]=l,t.heap[1]=l++,A(t,a,1)}while(t.heap_len>=2);t.heap[--t.heap_max]=t.heap[1],((t,e)=>{const a=e.dyn_tree,i=e.max_code,n=e.stat_desc.static_tree,s=e.stat_desc.has_stree,r=e.stat_desc.extra_bits,o=e.stat_desc.extra_base,l=e.stat_desc.max_length;let h,d,_,f,c,u,w=0;for(f=0;f<=15;f++)t.bl_count[f]=0;for(a[2*t.heap[t.heap_max]+1]=0,h=t.heap_max+1;h<573;h++)d=t.heap[h],f=a[2*a[2*d+1]+1]+1,f>l&&(f=l,w++),a[2*d+1]=f,d>i||(t.bl_count[f]++,c=0,d>=o&&(c=r[d-o]),u=a[2*d],t.opt_len+=u*(f+c),s&&(t.static_len+=u*(n[2*d+1]+c)));if(0!==w){do{for(f=l-1;0===t.bl_count[f];)f--;t.bl_count[f]--,t.bl_count[f+1]+=2,t.bl_count[l]--,w-=2}while(w>0);for(f=l;0!==f;f--)for(d=t.bl_count[f];0!==d;)_=t.heap[--h],_>i||(a[2*_+1]!==f&&(t.opt_len+=(f-a[2*_+1])*a[2*_],a[2*_+1]=f),d--)}})(t,e),v(a,h,t.bl_count)},Z=(t,e,a)=>{let i,n,s=-1,r=e[1],o=0,l=7,h=4;for(0===r&&(l=138,h=3),e[2*(a+1)+1]=65535,i=0;i<=a;i++)n=r,r=e[2*(i+1)+1],++o{let i,n,s=-1,r=e[1],o=0,l=7,h=4;for(0===r&&(l=138,h=3),i=0;i<=a;i++)if(n=r,r=e[2*(i+1)+1],!(++o{g(t,0+(i?1:0),3),x(t),b(t,a),b(t,~a),a&&t.pending_buf.set(t.window.subarray(e,e+a),t.pending),t.pending+=a};var O=(t,e,a,i)=>{let o,l,h=0;t.level>0?(2===t.strm.data_type&&(t.strm.data_type=(t=>{let e,a=4093624447;for(e=0;e<=31;e++,a>>>=1)if(1&a&&0!==t.dyn_ltree[2*e])return 0;if(0!==t.dyn_ltree[18]||0!==t.dyn_ltree[20]||0!==t.dyn_ltree[26])return 1;for(e=32;e<256;e++)if(0!==t.dyn_ltree[2*e])return 1;return 0})(t)),R(t,t.l_desc),R(t,t.d_desc),h=(t=>{let e;for(Z(t,t.dyn_ltree,t.l_desc.max_code),Z(t,t.dyn_dtree,t.d_desc.max_code),R(t,t.bl_desc),e=18;e>=3&&0===t.bl_tree[2*n[e]+1];e--);return t.opt_len+=3*(e+1)+5+5+4,e})(t),o=t.opt_len+3+7>>>3,l=t.static_len+3+7>>>3,l<=o&&(o=l)):o=l=a+5,a+4<=o&&-1!==e?D(t,e,a,i):4===t.strategy||l===o?(g(t,2+(i?1:0),3),E(t,s,r)):(g(t,4+(i?1:0),3),((t,e,a,i)=>{let s;for(g(t,e-257,5),g(t,a-1,5),g(t,i-4,4),s=0;s{U||((()=>{let t,n,w,m,b;const g=new Array(16);for(w=0,m=0;m<28;m++)for(h[m]=w,t=0;t<1<>=7;m<30;m++)for(d[m]=b<<7,t=0;t<1<(t.pending_buf[t.sym_buf+t.sym_next++]=e,t.pending_buf[t.sym_buf+t.sym_next++]=e>>8,t.pending_buf[t.sym_buf+t.sym_next++]=a,0===e?t.dyn_ltree[2*a]++:(t.matches++,e--,t.dyn_ltree[2*(l[a]+256+1)]++,t.dyn_dtree[2*m(e)]++),t.sym_next===t.sym_end),_tr_align:t=>{g(t,2,3),p(t,256,s),(t=>{16===t.bi_valid?(b(t,t.bi_buf),t.bi_buf=0,t.bi_valid=0):t.bi_valid>=8&&(t.pending_buf[t.pending++]=255&t.bi_buf,t.bi_buf>>=8,t.bi_valid-=8)})(t)}};var N=(t,e,a,i)=>{let n=65535&t|0,s=t>>>16&65535|0,r=0;for(;0!==a;){r=a>2e3?2e3:a,a-=r;do{n=n+e[i++]|0,s=s+n|0}while(--r);n%=65521,s%=65521}return n|s<<16|0};const F=new Uint32Array((()=>{let t,e=[];for(var a=0;a<256;a++){t=a;for(var i=0;i<8;i++)t=1&t?3988292384^t>>>1:t>>>1;e[a]=t}return e})());var L=(t,e,a,i)=>{const n=F,s=i+a;t^=-1;for(let a=i;a>>8^n[255&(t^e[a])];return-1^t},I={2:"need dictionary",1:"stream end",0:"","-1":"file error","-2":"stream error","-3":"data error","-4":"insufficient memory","-5":"buffer error","-6":"incompatible version"},B={Z_NO_FLUSH:0,Z_PARTIAL_FLUSH:1,Z_SYNC_FLUSH:2,Z_FULL_FLUSH:3,Z_FINISH:4,Z_BLOCK:5,Z_TREES:6,Z_OK:0,Z_STREAM_END:1,Z_NEED_DICT:2,Z_ERRNO:-1,Z_STREAM_ERROR:-2,Z_DATA_ERROR:-3,Z_MEM_ERROR:-4,Z_BUF_ERROR:-5,Z_NO_COMPRESSION:0,Z_BEST_SPEED:1,Z_BEST_COMPRESSION:9,Z_DEFAULT_COMPRESSION:-1,Z_FILTERED:1,Z_HUFFMAN_ONLY:2,Z_RLE:3,Z_FIXED:4,Z_DEFAULT_STRATEGY:0,Z_BINARY:0,Z_TEXT:1,Z_UNKNOWN:2,Z_DEFLATED:8};const{_tr_init:C,_tr_stored_block:H,_tr_flush_block:M,_tr_tally:j,_tr_align:K}=T,{Z_NO_FLUSH:P,Z_PARTIAL_FLUSH:Y,Z_FULL_FLUSH:G,Z_FINISH:X,Z_BLOCK:J,Z_OK:W,Z_STREAM_END:q,Z_STREAM_ERROR:Q,Z_DATA_ERROR:V,Z_BUF_ERROR:$,Z_DEFAULT_COMPRESSION:tt,Z_FILTERED:et,Z_HUFFMAN_ONLY:at,Z_RLE:it,Z_FIXED:nt,Z_DEFAULT_STRATEGY:st,Z_UNKNOWN:rt,Z_DEFLATED:ot}=B,lt=(t,e)=>(t.msg=I[e],e),ht=t=>2*t-(t>4?9:0),dt=t=>{let e=t.length;for(;--e>=0;)t[e]=0},_t=t=>{let e,a,i,n=t.w_size;e=t.hash_size,i=e;do{a=t.head[--i],t.head[i]=a>=n?a-n:0}while(--e);e=n,i=e;do{a=t.prev[--i],t.prev[i]=a>=n?a-n:0}while(--e)};let ft=(t,e,a)=>(e<{const e=t.state;let a=e.pending;a>t.avail_out&&(a=t.avail_out),0!==a&&(t.output.set(e.pending_buf.subarray(e.pending_out,e.pending_out+a),t.next_out),t.next_out+=a,e.pending_out+=a,t.total_out+=a,t.avail_out-=a,e.pending-=a,0===e.pending&&(e.pending_out=0))},ut=(t,e)=>{M(t,t.block_start>=0?t.block_start:-1,t.strstart-t.block_start,e),t.block_start=t.strstart,ct(t.strm)},wt=(t,e)=>{t.pending_buf[t.pending++]=e},mt=(t,e)=>{t.pending_buf[t.pending++]=e>>>8&255,t.pending_buf[t.pending++]=255&e},bt=(t,e,a,i)=>{let n=t.avail_in;return n>i&&(n=i),0===n?0:(t.avail_in-=n,e.set(t.input.subarray(t.next_in,t.next_in+n),a),1===t.state.wrap?t.adler=N(t.adler,e,n,a):2===t.state.wrap&&(t.adler=L(t.adler,e,n,a)),t.next_in+=n,t.total_in+=n,n)},gt=(t,e)=>{let a,i,n=t.max_chain_length,s=t.strstart,r=t.prev_length,o=t.nice_match;const l=t.strstart>t.w_size-262?t.strstart-(t.w_size-262):0,h=t.window,d=t.w_mask,_=t.prev,f=t.strstart+258;let c=h[s+r-1],u=h[s+r];t.prev_length>=t.good_match&&(n>>=2),o>t.lookahead&&(o=t.lookahead);do{if(a=e,h[a+r]===u&&h[a+r-1]===c&&h[a]===h[s]&&h[++a]===h[s+1]){s+=2,a++;do{}while(h[++s]===h[++a]&&h[++s]===h[++a]&&h[++s]===h[++a]&&h[++s]===h[++a]&&h[++s]===h[++a]&&h[++s]===h[++a]&&h[++s]===h[++a]&&h[++s]===h[++a]&&sr){if(t.match_start=e,r=i,i>=o)break;c=h[s+r-1],u=h[s+r]}}}while((e=_[e&d])>l&&0!=--n);return r<=t.lookahead?r:t.lookahead},pt=t=>{const e=t.w_size;let a,i,n;do{if(i=t.window_size-t.lookahead-t.strstart,t.strstart>=e+(e-262)&&(t.window.set(t.window.subarray(e,e+e-i),0),t.match_start-=e,t.strstart-=e,t.block_start-=e,t.insert>t.strstart&&(t.insert=t.strstart),_t(t),i+=e),0===t.strm.avail_in)break;if(a=bt(t.strm,t.window,t.strstart+t.lookahead,i),t.lookahead+=a,t.lookahead+t.insert>=3)for(n=t.strstart-t.insert,t.ins_h=t.window[n],t.ins_h=ft(t,t.ins_h,t.window[n+1]);t.insert&&(t.ins_h=ft(t,t.ins_h,t.window[n+3-1]),t.prev[n&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=n,n++,t.insert--,!(t.lookahead+t.insert<3)););}while(t.lookahead<262&&0!==t.strm.avail_in)},kt=(t,e)=>{let a,i,n,s=t.pending_buf_size-5>t.w_size?t.w_size:t.pending_buf_size-5,r=0,o=t.strm.avail_in;do{if(a=65535,n=t.bi_valid+42>>3,t.strm.avail_outi+t.strm.avail_in&&(a=i+t.strm.avail_in),a>n&&(a=n),a>8,t.pending_buf[t.pending-2]=~a,t.pending_buf[t.pending-1]=~a>>8,ct(t.strm),i&&(i>a&&(i=a),t.strm.output.set(t.window.subarray(t.block_start,t.block_start+i),t.strm.next_out),t.strm.next_out+=i,t.strm.avail_out-=i,t.strm.total_out+=i,t.block_start+=i,a-=i),a&&(bt(t.strm,t.strm.output,t.strm.next_out,a),t.strm.next_out+=a,t.strm.avail_out-=a,t.strm.total_out+=a)}while(0===r);return o-=t.strm.avail_in,o&&(o>=t.w_size?(t.matches=2,t.window.set(t.strm.input.subarray(t.strm.next_in-t.w_size,t.strm.next_in),0),t.strstart=t.w_size,t.insert=t.strstart):(t.window_size-t.strstart<=o&&(t.strstart-=t.w_size,t.window.set(t.window.subarray(t.w_size,t.w_size+t.strstart),0),t.matches<2&&t.matches++,t.insert>t.strstart&&(t.insert=t.strstart)),t.window.set(t.strm.input.subarray(t.strm.next_in-o,t.strm.next_in),t.strstart),t.strstart+=o,t.insert+=o>t.w_size-t.insert?t.w_size-t.insert:o),t.block_start=t.strstart),t.high_watern&&t.block_start>=t.w_size&&(t.block_start-=t.w_size,t.strstart-=t.w_size,t.window.set(t.window.subarray(t.w_size,t.w_size+t.strstart),0),t.matches<2&&t.matches++,n+=t.w_size,t.insert>t.strstart&&(t.insert=t.strstart)),n>t.strm.avail_in&&(n=t.strm.avail_in),n&&(bt(t.strm,t.window,t.strstart,n),t.strstart+=n,t.insert+=n>t.w_size-t.insert?t.w_size-t.insert:n),t.high_water>3,n=t.pending_buf_size-n>65535?65535:t.pending_buf_size-n,s=n>t.w_size?t.w_size:n,i=t.strstart-t.block_start,(i>=s||(i||e===X)&&e!==P&&0===t.strm.avail_in&&i<=n)&&(a=i>n?n:i,r=e===X&&0===t.strm.avail_in&&a===i?1:0,H(t,t.block_start,a,r),t.block_start+=a,ct(t.strm)),r?3:1)},vt=(t,e)=>{let a,i;for(;;){if(t.lookahead<262){if(pt(t),t.lookahead<262&&e===P)return 1;if(0===t.lookahead)break}if(a=0,t.lookahead>=3&&(t.ins_h=ft(t,t.ins_h,t.window[t.strstart+3-1]),a=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart),0!==a&&t.strstart-a<=t.w_size-262&&(t.match_length=gt(t,a)),t.match_length>=3)if(i=j(t,t.strstart-t.match_start,t.match_length-3),t.lookahead-=t.match_length,t.match_length<=t.max_lazy_match&&t.lookahead>=3){t.match_length--;do{t.strstart++,t.ins_h=ft(t,t.ins_h,t.window[t.strstart+3-1]),a=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart}while(0!=--t.match_length);t.strstart++}else t.strstart+=t.match_length,t.match_length=0,t.ins_h=t.window[t.strstart],t.ins_h=ft(t,t.ins_h,t.window[t.strstart+1]);else i=j(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++;if(i&&(ut(t,!1),0===t.strm.avail_out))return 1}return t.insert=t.strstart<2?t.strstart:2,e===X?(ut(t,!0),0===t.strm.avail_out?3:4):t.sym_next&&(ut(t,!1),0===t.strm.avail_out)?1:2},yt=(t,e)=>{let a,i,n;for(;;){if(t.lookahead<262){if(pt(t),t.lookahead<262&&e===P)return 1;if(0===t.lookahead)break}if(a=0,t.lookahead>=3&&(t.ins_h=ft(t,t.ins_h,t.window[t.strstart+3-1]),a=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart),t.prev_length=t.match_length,t.prev_match=t.match_start,t.match_length=2,0!==a&&t.prev_length4096)&&(t.match_length=2)),t.prev_length>=3&&t.match_length<=t.prev_length){n=t.strstart+t.lookahead-3,i=j(t,t.strstart-1-t.prev_match,t.prev_length-3),t.lookahead-=t.prev_length-1,t.prev_length-=2;do{++t.strstart<=n&&(t.ins_h=ft(t,t.ins_h,t.window[t.strstart+3-1]),a=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart)}while(0!=--t.prev_length);if(t.match_available=0,t.match_length=2,t.strstart++,i&&(ut(t,!1),0===t.strm.avail_out))return 1}else if(t.match_available){if(i=j(t,0,t.window[t.strstart-1]),i&&ut(t,!1),t.strstart++,t.lookahead--,0===t.strm.avail_out)return 1}else t.match_available=1,t.strstart++,t.lookahead--}return t.match_available&&(i=j(t,0,t.window[t.strstart-1]),t.match_available=0),t.insert=t.strstart<2?t.strstart:2,e===X?(ut(t,!0),0===t.strm.avail_out?3:4):t.sym_next&&(ut(t,!1),0===t.strm.avail_out)?1:2};function xt(t,e,a,i,n){this.good_length=t,this.max_lazy=e,this.nice_length=a,this.max_chain=i,this.func=n}const zt=[new xt(0,0,0,0,kt),new xt(4,4,8,4,vt),new xt(4,5,16,8,vt),new xt(4,6,32,32,vt),new xt(4,4,16,16,yt),new xt(8,16,32,32,yt),new xt(8,16,128,128,yt),new xt(8,32,128,256,yt),new xt(32,128,258,1024,yt),new xt(32,258,258,4096,yt)];function At(){this.strm=null,this.status=0,this.pending_buf=null,this.pending_buf_size=0,this.pending_out=0,this.pending=0,this.wrap=0,this.gzhead=null,this.gzindex=0,this.method=ot,this.last_flush=-1,this.w_size=0,this.w_bits=0,this.w_mask=0,this.window=null,this.window_size=0,this.prev=null,this.head=null,this.ins_h=0,this.hash_size=0,this.hash_bits=0,this.hash_mask=0,this.hash_shift=0,this.block_start=0,this.match_length=0,this.prev_match=0,this.match_available=0,this.strstart=0,this.match_start=0,this.lookahead=0,this.prev_length=0,this.max_chain_length=0,this.max_lazy_match=0,this.level=0,this.strategy=0,this.good_match=0,this.nice_match=0,this.dyn_ltree=new Uint16Array(1146),this.dyn_dtree=new Uint16Array(122),this.bl_tree=new Uint16Array(78),dt(this.dyn_ltree),dt(this.dyn_dtree),dt(this.bl_tree),this.l_desc=null,this.d_desc=null,this.bl_desc=null,this.bl_count=new Uint16Array(16),this.heap=new Uint16Array(573),dt(this.heap),this.heap_len=0,this.heap_max=0,this.depth=new Uint16Array(573),dt(this.depth),this.sym_buf=0,this.lit_bufsize=0,this.sym_next=0,this.sym_end=0,this.opt_len=0,this.static_len=0,this.matches=0,this.insert=0,this.bi_buf=0,this.bi_valid=0}const Et=t=>{if(!t)return 1;const e=t.state;return!e||e.strm!==t||42!==e.status&&57!==e.status&&69!==e.status&&73!==e.status&&91!==e.status&&103!==e.status&&113!==e.status&&666!==e.status?1:0},Rt=t=>{if(Et(t))return lt(t,Q);t.total_in=t.total_out=0,t.data_type=rt;const e=t.state;return e.pending=0,e.pending_out=0,e.wrap<0&&(e.wrap=-e.wrap),e.status=2===e.wrap?57:e.wrap?42:113,t.adler=2===e.wrap?0:1,e.last_flush=-2,C(e),W},Zt=t=>{const e=Rt(t);var a;return e===W&&((a=t.state).window_size=2*a.w_size,dt(a.head),a.max_lazy_match=zt[a.level].max_lazy,a.good_match=zt[a.level].good_length,a.nice_match=zt[a.level].nice_length,a.max_chain_length=zt[a.level].max_chain,a.strstart=0,a.block_start=0,a.lookahead=0,a.insert=0,a.match_length=a.prev_length=2,a.match_available=0,a.ins_h=0),e},St=(t,e,a,i,n,s)=>{if(!t)return Q;let r=1;if(e===tt&&(e=6),i<0?(r=0,i=-i):i>15&&(r=2,i-=16),n<1||n>9||a!==ot||i<8||i>15||e<0||e>9||s<0||s>nt||8===i&&1!==r)return lt(t,Q);8===i&&(i=9);const o=new At;return t.state=o,o.strm=t,o.status=42,o.wrap=r,o.gzhead=null,o.w_bits=i,o.w_size=1<St(t,e,ot,15,8,st),deflateInit2:St,deflateReset:Zt,deflateResetKeep:Rt,deflateSetHeader:(t,e)=>Et(t)||2!==t.state.wrap?Q:(t.state.gzhead=e,W),deflate:(t,e)=>{if(Et(t)||e>J||e<0)return t?lt(t,Q):Q;const a=t.state;if(!t.output||0!==t.avail_in&&!t.input||666===a.status&&e!==X)return lt(t,0===t.avail_out?$:Q);const i=a.last_flush;if(a.last_flush=e,0!==a.pending){if(ct(t),0===t.avail_out)return a.last_flush=-1,W}else if(0===t.avail_in&&ht(e)<=ht(i)&&e!==X)return lt(t,$);if(666===a.status&&0!==t.avail_in)return lt(t,$);if(42===a.status&&0===a.wrap&&(a.status=113),42===a.status){let e=ot+(a.w_bits-8<<4)<<8,i=-1;if(i=a.strategy>=at||a.level<2?0:a.level<6?1:6===a.level?2:3,e|=i<<6,0!==a.strstart&&(e|=32),e+=31-e%31,mt(a,e),0!==a.strstart&&(mt(a,t.adler>>>16),mt(a,65535&t.adler)),t.adler=1,a.status=113,ct(t),0!==a.pending)return a.last_flush=-1,W}if(57===a.status)if(t.adler=0,wt(a,31),wt(a,139),wt(a,8),a.gzhead)wt(a,(a.gzhead.text?1:0)+(a.gzhead.hcrc?2:0)+(a.gzhead.extra?4:0)+(a.gzhead.name?8:0)+(a.gzhead.comment?16:0)),wt(a,255&a.gzhead.time),wt(a,a.gzhead.time>>8&255),wt(a,a.gzhead.time>>16&255),wt(a,a.gzhead.time>>24&255),wt(a,9===a.level?2:a.strategy>=at||a.level<2?4:0),wt(a,255&a.gzhead.os),a.gzhead.extra&&a.gzhead.extra.length&&(wt(a,255&a.gzhead.extra.length),wt(a,a.gzhead.extra.length>>8&255)),a.gzhead.hcrc&&(t.adler=L(t.adler,a.pending_buf,a.pending,0)),a.gzindex=0,a.status=69;else if(wt(a,0),wt(a,0),wt(a,0),wt(a,0),wt(a,0),wt(a,9===a.level?2:a.strategy>=at||a.level<2?4:0),wt(a,3),a.status=113,ct(t),0!==a.pending)return a.last_flush=-1,W;if(69===a.status){if(a.gzhead.extra){let e=a.pending,i=(65535&a.gzhead.extra.length)-a.gzindex;for(;a.pending+i>a.pending_buf_size;){let n=a.pending_buf_size-a.pending;if(a.pending_buf.set(a.gzhead.extra.subarray(a.gzindex,a.gzindex+n),a.pending),a.pending=a.pending_buf_size,a.gzhead.hcrc&&a.pending>e&&(t.adler=L(t.adler,a.pending_buf,a.pending-e,e)),a.gzindex+=n,ct(t),0!==a.pending)return a.last_flush=-1,W;e=0,i-=n}let n=new Uint8Array(a.gzhead.extra);a.pending_buf.set(n.subarray(a.gzindex,a.gzindex+i),a.pending),a.pending+=i,a.gzhead.hcrc&&a.pending>e&&(t.adler=L(t.adler,a.pending_buf,a.pending-e,e)),a.gzindex=0}a.status=73}if(73===a.status){if(a.gzhead.name){let e,i=a.pending;do{if(a.pending===a.pending_buf_size){if(a.gzhead.hcrc&&a.pending>i&&(t.adler=L(t.adler,a.pending_buf,a.pending-i,i)),ct(t),0!==a.pending)return a.last_flush=-1,W;i=0}e=a.gzindexi&&(t.adler=L(t.adler,a.pending_buf,a.pending-i,i)),a.gzindex=0}a.status=91}if(91===a.status){if(a.gzhead.comment){let e,i=a.pending;do{if(a.pending===a.pending_buf_size){if(a.gzhead.hcrc&&a.pending>i&&(t.adler=L(t.adler,a.pending_buf,a.pending-i,i)),ct(t),0!==a.pending)return a.last_flush=-1,W;i=0}e=a.gzindexi&&(t.adler=L(t.adler,a.pending_buf,a.pending-i,i))}a.status=103}if(103===a.status){if(a.gzhead.hcrc){if(a.pending+2>a.pending_buf_size&&(ct(t),0!==a.pending))return a.last_flush=-1,W;wt(a,255&t.adler),wt(a,t.adler>>8&255),t.adler=0}if(a.status=113,ct(t),0!==a.pending)return a.last_flush=-1,W}if(0!==t.avail_in||0!==a.lookahead||e!==P&&666!==a.status){let i=0===a.level?kt(a,e):a.strategy===at?((t,e)=>{let a;for(;;){if(0===t.lookahead&&(pt(t),0===t.lookahead)){if(e===P)return 1;break}if(t.match_length=0,a=j(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++,a&&(ut(t,!1),0===t.strm.avail_out))return 1}return t.insert=0,e===X?(ut(t,!0),0===t.strm.avail_out?3:4):t.sym_next&&(ut(t,!1),0===t.strm.avail_out)?1:2})(a,e):a.strategy===it?((t,e)=>{let a,i,n,s;const r=t.window;for(;;){if(t.lookahead<=258){if(pt(t),t.lookahead<=258&&e===P)return 1;if(0===t.lookahead)break}if(t.match_length=0,t.lookahead>=3&&t.strstart>0&&(n=t.strstart-1,i=r[n],i===r[++n]&&i===r[++n]&&i===r[++n])){s=t.strstart+258;do{}while(i===r[++n]&&i===r[++n]&&i===r[++n]&&i===r[++n]&&i===r[++n]&&i===r[++n]&&i===r[++n]&&i===r[++n]&&nt.lookahead&&(t.match_length=t.lookahead)}if(t.match_length>=3?(a=j(t,1,t.match_length-3),t.lookahead-=t.match_length,t.strstart+=t.match_length,t.match_length=0):(a=j(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++),a&&(ut(t,!1),0===t.strm.avail_out))return 1}return t.insert=0,e===X?(ut(t,!0),0===t.strm.avail_out?3:4):t.sym_next&&(ut(t,!1),0===t.strm.avail_out)?1:2})(a,e):zt[a.level].func(a,e);if(3!==i&&4!==i||(a.status=666),1===i||3===i)return 0===t.avail_out&&(a.last_flush=-1),W;if(2===i&&(e===Y?K(a):e!==J&&(H(a,0,0,!1),e===G&&(dt(a.head),0===a.lookahead&&(a.strstart=0,a.block_start=0,a.insert=0))),ct(t),0===t.avail_out))return a.last_flush=-1,W}return e!==X?W:a.wrap<=0?q:(2===a.wrap?(wt(a,255&t.adler),wt(a,t.adler>>8&255),wt(a,t.adler>>16&255),wt(a,t.adler>>24&255),wt(a,255&t.total_in),wt(a,t.total_in>>8&255),wt(a,t.total_in>>16&255),wt(a,t.total_in>>24&255)):(mt(a,t.adler>>>16),mt(a,65535&t.adler)),ct(t),a.wrap>0&&(a.wrap=-a.wrap),0!==a.pending?W:q)},deflateEnd:t=>{if(Et(t))return Q;const e=t.state.status;return t.state=null,113===e?lt(t,V):W},deflateSetDictionary:(t,e)=>{let a=e.length;if(Et(t))return Q;const i=t.state,n=i.wrap;if(2===n||1===n&&42!==i.status||i.lookahead)return Q;if(1===n&&(t.adler=N(t.adler,e,a,0)),i.wrap=0,a>=i.w_size){0===n&&(dt(i.head),i.strstart=0,i.block_start=0,i.insert=0);let t=new Uint8Array(i.w_size);t.set(e.subarray(a-i.w_size,a),0),e=t,a=i.w_size}const s=t.avail_in,r=t.next_in,o=t.input;for(t.avail_in=a,t.next_in=0,t.input=e,pt(i);i.lookahead>=3;){let t=i.strstart,e=i.lookahead-2;do{i.ins_h=ft(i,i.ins_h,i.window[t+3-1]),i.prev[t&i.w_mask]=i.head[i.ins_h],i.head[i.ins_h]=t,t++}while(--e);i.strstart=t,i.lookahead=2,pt(i)}return i.strstart+=i.lookahead,i.block_start=i.strstart,i.insert=i.lookahead,i.lookahead=0,i.match_length=i.prev_length=2,i.match_available=0,t.next_in=r,t.input=o,t.avail_in=s,i.wrap=n,W},deflateInfo:"pako deflate (from Nodeca project)"};const Dt=(t,e)=>Object.prototype.hasOwnProperty.call(t,e);var Ot=function(t){const e=Array.prototype.slice.call(arguments,1);for(;e.length;){const a=e.shift();if(a){if("object"!=typeof a)throw new TypeError(a+"must be non-object");for(const e in a)Dt(a,e)&&(t[e]=a[e])}}return t},Tt=t=>{let e=0;for(let a=0,i=t.length;a=252?6:t>=248?5:t>=240?4:t>=224?3:t>=192?2:1;Ft[254]=Ft[254]=1;var Lt=t=>{if("function"==typeof TextEncoder&&TextEncoder.prototype.encode)return(new TextEncoder).encode(t);let e,a,i,n,s,r=t.length,o=0;for(n=0;n>>6,e[s++]=128|63&a):a<65536?(e[s++]=224|a>>>12,e[s++]=128|a>>>6&63,e[s++]=128|63&a):(e[s++]=240|a>>>18,e[s++]=128|a>>>12&63,e[s++]=128|a>>>6&63,e[s++]=128|63&a);return e},It=(t,e)=>{const a=e||t.length;if("function"==typeof TextDecoder&&TextDecoder.prototype.decode)return(new TextDecoder).decode(t.subarray(0,e));let i,n;const s=new Array(2*a);for(n=0,i=0;i4)s[n++]=65533,i+=r-1;else{for(e&=2===r?31:3===r?15:7;r>1&&i1?s[n++]=65533:e<65536?s[n++]=e:(e-=65536,s[n++]=55296|e>>10&1023,s[n++]=56320|1023&e)}}return((t,e)=>{if(e<65534&&t.subarray&&Nt)return String.fromCharCode.apply(null,t.length===e?t:t.subarray(0,e));let a="";for(let i=0;i{(e=e||t.length)>t.length&&(e=t.length);let a=e-1;for(;a>=0&&128==(192&t[a]);)a--;return a<0||0===a?e:a+Ft[t[a]]>e?a:e};var Ct=function(){this.input=null,this.next_in=0,this.avail_in=0,this.total_in=0,this.output=null,this.next_out=0,this.avail_out=0,this.total_out=0,this.msg="",this.state=null,this.data_type=2,this.adler=0};const Ht=Object.prototype.toString,{Z_NO_FLUSH:Mt,Z_SYNC_FLUSH:jt,Z_FULL_FLUSH:Kt,Z_FINISH:Pt,Z_OK:Yt,Z_STREAM_END:Gt,Z_DEFAULT_COMPRESSION:Xt,Z_DEFAULT_STRATEGY:Jt,Z_DEFLATED:Wt}=B;function qt(t){this.options=Ot({level:Xt,method:Wt,chunkSize:16384,windowBits:15,memLevel:8,strategy:Jt},t||{});let e=this.options;e.raw&&e.windowBits>0?e.windowBits=-e.windowBits:e.gzip&&e.windowBits>0&&e.windowBits<16&&(e.windowBits+=16),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new Ct,this.strm.avail_out=0;let a=Ut.deflateInit2(this.strm,e.level,e.method,e.windowBits,e.memLevel,e.strategy);if(a!==Yt)throw new Error(I[a]);if(e.header&&Ut.deflateSetHeader(this.strm,e.header),e.dictionary){let t;if(t="string"==typeof e.dictionary?Lt(e.dictionary):"[object ArrayBuffer]"===Ht.call(e.dictionary)?new Uint8Array(e.dictionary):e.dictionary,a=Ut.deflateSetDictionary(this.strm,t),a!==Yt)throw new Error(I[a]);this._dict_set=!0}}function Qt(t,e){const a=new qt(e);if(a.push(t,!0),a.err)throw a.msg||I[a.err];return a.result}qt.prototype.push=function(t,e){const a=this.strm,i=this.options.chunkSize;let n,s;if(this.ended)return!1;for(s=e===~~e?e:!0===e?Pt:Mt,"string"==typeof t?a.input=Lt(t):"[object ArrayBuffer]"===Ht.call(t)?a.input=new Uint8Array(t):a.input=t,a.next_in=0,a.avail_in=a.input.length;;)if(0===a.avail_out&&(a.output=new Uint8Array(i),a.next_out=0,a.avail_out=i),(s===jt||s===Kt)&&a.avail_out<=6)this.onData(a.output.subarray(0,a.next_out)),a.avail_out=0;else{if(n=Ut.deflate(a,s),n===Gt)return a.next_out>0&&this.onData(a.output.subarray(0,a.next_out)),n=Ut.deflateEnd(this.strm),this.onEnd(n),this.ended=!0,n===Yt;if(0!==a.avail_out){if(s>0&&a.next_out>0)this.onData(a.output.subarray(0,a.next_out)),a.avail_out=0;else if(0===a.avail_in)break}else this.onData(a.output)}return!0},qt.prototype.onData=function(t){this.chunks.push(t)},qt.prototype.onEnd=function(t){t===Yt&&(this.result=Tt(this.chunks)),this.chunks=[],this.err=t,this.msg=this.strm.msg};var Vt={Deflate:qt,deflate:Qt,deflateRaw:function(t,e){return(e=e||{}).raw=!0,Qt(t,e)},gzip:function(t,e){return(e=e||{}).gzip=!0,Qt(t,e)},constants:B};var $t=function(t,e){let a,i,n,s,r,o,l,h,d,_,f,c,u,w,m,b,g,p,k,v,y,x,z,A;const E=t.state;a=t.next_in,z=t.input,i=a+(t.avail_in-5),n=t.next_out,A=t.output,s=n-(e-t.avail_out),r=n+(t.avail_out-257),o=E.dmax,l=E.wsize,h=E.whave,d=E.wnext,_=E.window,f=E.hold,c=E.bits,u=E.lencode,w=E.distcode,m=(1<>>24,f>>>=p,c-=p,p=g>>>16&255,0===p)A[n++]=65535&g;else{if(!(16&p)){if(0==(64&p)){g=u[(65535&g)+(f&(1<>>=p,c-=p),c<15&&(f+=z[a++]<>>24,f>>>=p,c-=p,p=g>>>16&255,!(16&p)){if(0==(64&p)){g=w[(65535&g)+(f&(1<o){t.msg="invalid distance too far back",E.mode=16209;break t}if(f>>>=p,c-=p,p=n-s,v>p){if(p=v-p,p>h&&E.sane){t.msg="invalid distance too far back",E.mode=16209;break t}if(y=0,x=_,0===d){if(y+=l-p,p2;)A[n++]=x[y++],A[n++]=x[y++],A[n++]=x[y++],k-=3;k&&(A[n++]=x[y++],k>1&&(A[n++]=x[y++]))}else{y=n-v;do{A[n++]=A[y++],A[n++]=A[y++],A[n++]=A[y++],k-=3}while(k>2);k&&(A[n++]=A[y++],k>1&&(A[n++]=A[y++]))}break}}break}}while(a>3,a-=k,c-=k<<3,f&=(1<{const l=o.bits;let h,d,_,f,c,u,w=0,m=0,b=0,g=0,p=0,k=0,v=0,y=0,x=0,z=0,A=null;const E=new Uint16Array(16),R=new Uint16Array(16);let Z,S,U,D=null;for(w=0;w<=15;w++)E[w]=0;for(m=0;m=1&&0===E[g];g--);if(p>g&&(p=g),0===g)return n[s++]=20971520,n[s++]=20971520,o.bits=1,0;for(b=1;b0&&(0===t||1!==g))return-1;for(R[1]=0,w=1;w<15;w++)R[w+1]=R[w]+E[w];for(m=0;m852||2===t&&x>592)return 1;for(;;){Z=w-v,r[m]+1=u?(S=D[r[m]-u],U=A[r[m]-u]):(S=96,U=0),h=1<>v)+d]=Z<<24|S<<16|U|0}while(0!==d);for(h=1<>=1;if(0!==h?(z&=h-1,z+=h):z=0,m++,0==--E[w]){if(w===g)break;w=e[a+r[m]]}if(w>p&&(z&f)!==_){for(0===v&&(v=p),c+=b,k=w-v,y=1<852||2===t&&x>592)return 1;_=z&f,n[_]=p<<24|k<<16|c-s|0}}return 0!==z&&(n[c+z]=w-v<<24|64<<16|0),o.bits=p,0};const{Z_FINISH:se,Z_BLOCK:re,Z_TREES:oe,Z_OK:le,Z_STREAM_END:he,Z_NEED_DICT:de,Z_STREAM_ERROR:_e,Z_DATA_ERROR:fe,Z_MEM_ERROR:ce,Z_BUF_ERROR:ue,Z_DEFLATED:we}=B,me=16209,be=t=>(t>>>24&255)+(t>>>8&65280)+((65280&t)<<8)+((255&t)<<24);function ge(){this.strm=null,this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new Uint16Array(320),this.work=new Uint16Array(288),this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}const pe=t=>{if(!t)return 1;const e=t.state;return!e||e.strm!==t||e.mode<16180||e.mode>16211?1:0},ke=t=>{if(pe(t))return _e;const e=t.state;return t.total_in=t.total_out=e.total=0,t.msg="",e.wrap&&(t.adler=1&e.wrap),e.mode=16180,e.last=0,e.havedict=0,e.flags=-1,e.dmax=32768,e.head=null,e.hold=0,e.bits=0,e.lencode=e.lendyn=new Int32Array(852),e.distcode=e.distdyn=new Int32Array(592),e.sane=1,e.back=-1,le},ve=t=>{if(pe(t))return _e;const e=t.state;return e.wsize=0,e.whave=0,e.wnext=0,ke(t)},ye=(t,e)=>{let a;if(pe(t))return _e;const i=t.state;return e<0?(a=0,e=-e):(a=5+(e>>4),e<48&&(e&=15)),e&&(e<8||e>15)?_e:(null!==i.window&&i.wbits!==e&&(i.window=null),i.wrap=a,i.wbits=e,ve(t))},xe=(t,e)=>{if(!t)return _e;const a=new ge;t.state=a,a.strm=t,a.window=null,a.mode=16180;const i=ye(t,e);return i!==le&&(t.state=null),i};let ze,Ae,Ee=!0;const Re=t=>{if(Ee){ze=new Int32Array(512),Ae=new Int32Array(32);let e=0;for(;e<144;)t.lens[e++]=8;for(;e<256;)t.lens[e++]=9;for(;e<280;)t.lens[e++]=7;for(;e<288;)t.lens[e++]=8;for(ne(1,t.lens,0,288,ze,0,t.work,{bits:9}),e=0;e<32;)t.lens[e++]=5;ne(2,t.lens,0,32,Ae,0,t.work,{bits:5}),Ee=!1}t.lencode=ze,t.lenbits=9,t.distcode=Ae,t.distbits=5},Ze=(t,e,a,i)=>{let n;const s=t.state;return null===s.window&&(s.wsize=1<=s.wsize?(s.window.set(e.subarray(a-s.wsize,a),0),s.wnext=0,s.whave=s.wsize):(n=s.wsize-s.wnext,n>i&&(n=i),s.window.set(e.subarray(a-i,a-i+n),s.wnext),(i-=n)?(s.window.set(e.subarray(a-i,a),0),s.wnext=i,s.whave=s.wsize):(s.wnext+=n,s.wnext===s.wsize&&(s.wnext=0),s.whavexe(t,15),inflateInit2:xe,inflate:(t,e)=>{let a,i,n,s,r,o,l,h,d,_,f,c,u,w,m,b,g,p,k,v,y,x,z=0;const A=new Uint8Array(4);let E,R;const Z=new Uint8Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]);if(pe(t)||!t.output||!t.input&&0!==t.avail_in)return _e;a=t.state,16191===a.mode&&(a.mode=16192),r=t.next_out,n=t.output,l=t.avail_out,s=t.next_in,i=t.input,o=t.avail_in,h=a.hold,d=a.bits,_=o,f=l,x=le;t:for(;;)switch(a.mode){case 16180:if(0===a.wrap){a.mode=16192;break}for(;d<16;){if(0===o)break t;o--,h+=i[s++]<>>8&255,a.check=L(a.check,A,2,0),h=0,d=0,a.mode=16181;break}if(a.head&&(a.head.done=!1),!(1&a.wrap)||(((255&h)<<8)+(h>>8))%31){t.msg="incorrect header check",a.mode=me;break}if((15&h)!==we){t.msg="unknown compression method",a.mode=me;break}if(h>>>=4,d-=4,y=8+(15&h),0===a.wbits&&(a.wbits=y),y>15||y>a.wbits){t.msg="invalid window size",a.mode=me;break}a.dmax=1<>8&1),512&a.flags&&4&a.wrap&&(A[0]=255&h,A[1]=h>>>8&255,a.check=L(a.check,A,2,0)),h=0,d=0,a.mode=16182;case 16182:for(;d<32;){if(0===o)break t;o--,h+=i[s++]<>>8&255,A[2]=h>>>16&255,A[3]=h>>>24&255,a.check=L(a.check,A,4,0)),h=0,d=0,a.mode=16183;case 16183:for(;d<16;){if(0===o)break t;o--,h+=i[s++]<>8),512&a.flags&&4&a.wrap&&(A[0]=255&h,A[1]=h>>>8&255,a.check=L(a.check,A,2,0)),h=0,d=0,a.mode=16184;case 16184:if(1024&a.flags){for(;d<16;){if(0===o)break t;o--,h+=i[s++]<>>8&255,a.check=L(a.check,A,2,0)),h=0,d=0}else a.head&&(a.head.extra=null);a.mode=16185;case 16185:if(1024&a.flags&&(c=a.length,c>o&&(c=o),c&&(a.head&&(y=a.head.extra_len-a.length,a.head.extra||(a.head.extra=new Uint8Array(a.head.extra_len)),a.head.extra.set(i.subarray(s,s+c),y)),512&a.flags&&4&a.wrap&&(a.check=L(a.check,i,c,s)),o-=c,s+=c,a.length-=c),a.length))break t;a.length=0,a.mode=16186;case 16186:if(2048&a.flags){if(0===o)break t;c=0;do{y=i[s+c++],a.head&&y&&a.length<65536&&(a.head.name+=String.fromCharCode(y))}while(y&&c>9&1,a.head.done=!0),t.adler=a.check=0,a.mode=16191;break;case 16189:for(;d<32;){if(0===o)break t;o--,h+=i[s++]<>>=7&d,d-=7&d,a.mode=16206;break}for(;d<3;){if(0===o)break t;o--,h+=i[s++]<>>=1,d-=1,3&h){case 0:a.mode=16193;break;case 1:if(Re(a),a.mode=16199,e===oe){h>>>=2,d-=2;break t}break;case 2:a.mode=16196;break;case 3:t.msg="invalid block type",a.mode=me}h>>>=2,d-=2;break;case 16193:for(h>>>=7&d,d-=7&d;d<32;){if(0===o)break t;o--,h+=i[s++]<>>16^65535)){t.msg="invalid stored block lengths",a.mode=me;break}if(a.length=65535&h,h=0,d=0,a.mode=16194,e===oe)break t;case 16194:a.mode=16195;case 16195:if(c=a.length,c){if(c>o&&(c=o),c>l&&(c=l),0===c)break t;n.set(i.subarray(s,s+c),r),o-=c,s+=c,l-=c,r+=c,a.length-=c;break}a.mode=16191;break;case 16196:for(;d<14;){if(0===o)break t;o--,h+=i[s++]<>>=5,d-=5,a.ndist=1+(31&h),h>>>=5,d-=5,a.ncode=4+(15&h),h>>>=4,d-=4,a.nlen>286||a.ndist>30){t.msg="too many length or distance symbols",a.mode=me;break}a.have=0,a.mode=16197;case 16197:for(;a.have>>=3,d-=3}for(;a.have<19;)a.lens[Z[a.have++]]=0;if(a.lencode=a.lendyn,a.lenbits=7,E={bits:a.lenbits},x=ne(0,a.lens,0,19,a.lencode,0,a.work,E),a.lenbits=E.bits,x){t.msg="invalid code lengths set",a.mode=me;break}a.have=0,a.mode=16198;case 16198:for(;a.have>>24,b=z>>>16&255,g=65535&z,!(m<=d);){if(0===o)break t;o--,h+=i[s++]<>>=m,d-=m,a.lens[a.have++]=g;else{if(16===g){for(R=m+2;d>>=m,d-=m,0===a.have){t.msg="invalid bit length repeat",a.mode=me;break}y=a.lens[a.have-1],c=3+(3&h),h>>>=2,d-=2}else if(17===g){for(R=m+3;d>>=m,d-=m,y=0,c=3+(7&h),h>>>=3,d-=3}else{for(R=m+7;d>>=m,d-=m,y=0,c=11+(127&h),h>>>=7,d-=7}if(a.have+c>a.nlen+a.ndist){t.msg="invalid bit length repeat",a.mode=me;break}for(;c--;)a.lens[a.have++]=y}}if(a.mode===me)break;if(0===a.lens[256]){t.msg="invalid code -- missing end-of-block",a.mode=me;break}if(a.lenbits=9,E={bits:a.lenbits},x=ne(1,a.lens,0,a.nlen,a.lencode,0,a.work,E),a.lenbits=E.bits,x){t.msg="invalid literal/lengths set",a.mode=me;break}if(a.distbits=6,a.distcode=a.distdyn,E={bits:a.distbits},x=ne(2,a.lens,a.nlen,a.ndist,a.distcode,0,a.work,E),a.distbits=E.bits,x){t.msg="invalid distances set",a.mode=me;break}if(a.mode=16199,e===oe)break t;case 16199:a.mode=16200;case 16200:if(o>=6&&l>=258){t.next_out=r,t.avail_out=l,t.next_in=s,t.avail_in=o,a.hold=h,a.bits=d,$t(t,f),r=t.next_out,n=t.output,l=t.avail_out,s=t.next_in,i=t.input,o=t.avail_in,h=a.hold,d=a.bits,16191===a.mode&&(a.back=-1);break}for(a.back=0;z=a.lencode[h&(1<>>24,b=z>>>16&255,g=65535&z,!(m<=d);){if(0===o)break t;o--,h+=i[s++]<>p)],m=z>>>24,b=z>>>16&255,g=65535&z,!(p+m<=d);){if(0===o)break t;o--,h+=i[s++]<>>=p,d-=p,a.back+=p}if(h>>>=m,d-=m,a.back+=m,a.length=g,0===b){a.mode=16205;break}if(32&b){a.back=-1,a.mode=16191;break}if(64&b){t.msg="invalid literal/length code",a.mode=me;break}a.extra=15&b,a.mode=16201;case 16201:if(a.extra){for(R=a.extra;d>>=a.extra,d-=a.extra,a.back+=a.extra}a.was=a.length,a.mode=16202;case 16202:for(;z=a.distcode[h&(1<>>24,b=z>>>16&255,g=65535&z,!(m<=d);){if(0===o)break t;o--,h+=i[s++]<>p)],m=z>>>24,b=z>>>16&255,g=65535&z,!(p+m<=d);){if(0===o)break t;o--,h+=i[s++]<>>=p,d-=p,a.back+=p}if(h>>>=m,d-=m,a.back+=m,64&b){t.msg="invalid distance code",a.mode=me;break}a.offset=g,a.extra=15&b,a.mode=16203;case 16203:if(a.extra){for(R=a.extra;d>>=a.extra,d-=a.extra,a.back+=a.extra}if(a.offset>a.dmax){t.msg="invalid distance too far back",a.mode=me;break}a.mode=16204;case 16204:if(0===l)break t;if(c=f-l,a.offset>c){if(c=a.offset-c,c>a.whave&&a.sane){t.msg="invalid distance too far back",a.mode=me;break}c>a.wnext?(c-=a.wnext,u=a.wsize-c):u=a.wnext-c,c>a.length&&(c=a.length),w=a.window}else w=n,u=r-a.offset,c=a.length;c>l&&(c=l),l-=c,a.length-=c;do{n[r++]=w[u++]}while(--c);0===a.length&&(a.mode=16200);break;case 16205:if(0===l)break t;n[r++]=a.length,l--,a.mode=16200;break;case 16206:if(a.wrap){for(;d<32;){if(0===o)break t;o--,h|=i[s++]<{if(pe(t))return _e;let e=t.state;return e.window&&(e.window=null),t.state=null,le},inflateGetHeader:(t,e)=>{if(pe(t))return _e;const a=t.state;return 0==(2&a.wrap)?_e:(a.head=e,e.done=!1,le)},inflateSetDictionary:(t,e)=>{const a=e.length;let i,n,s;return pe(t)?_e:(i=t.state,0!==i.wrap&&16190!==i.mode?_e:16190===i.mode&&(n=1,n=N(n,e,a,0),n!==i.check)?fe:(s=Ze(t,e,a,a),s?(i.mode=16210,ce):(i.havedict=1,le)))},inflateInfo:"pako inflate (from Nodeca project)"};var Ue=function(){this.text=0,this.time=0,this.xflags=0,this.os=0,this.extra=null,this.extra_len=0,this.name="",this.comment="",this.hcrc=0,this.done=!1};const De=Object.prototype.toString,{Z_NO_FLUSH:Oe,Z_FINISH:Te,Z_OK:Ne,Z_STREAM_END:Fe,Z_NEED_DICT:Le,Z_STREAM_ERROR:Ie,Z_DATA_ERROR:Be,Z_MEM_ERROR:Ce}=B;function He(t){this.options=Ot({chunkSize:65536,windowBits:15,to:""},t||{});const e=this.options;e.raw&&e.windowBits>=0&&e.windowBits<16&&(e.windowBits=-e.windowBits,0===e.windowBits&&(e.windowBits=-15)),!(e.windowBits>=0&&e.windowBits<16)||t&&t.windowBits||(e.windowBits+=32),e.windowBits>15&&e.windowBits<48&&0==(15&e.windowBits)&&(e.windowBits|=15),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new Ct,this.strm.avail_out=0;let a=Se.inflateInit2(this.strm,e.windowBits);if(a!==Ne)throw new Error(I[a]);if(this.header=new Ue,Se.inflateGetHeader(this.strm,this.header),e.dictionary&&("string"==typeof e.dictionary?e.dictionary=Lt(e.dictionary):"[object ArrayBuffer]"===De.call(e.dictionary)&&(e.dictionary=new Uint8Array(e.dictionary)),e.raw&&(a=Se.inflateSetDictionary(this.strm,e.dictionary),a!==Ne)))throw new Error(I[a])}He.prototype.push=function(t,e){const a=this.strm,i=this.options.chunkSize,n=this.options.dictionary;let s,r,o;if(this.ended)return!1;for(r=e===~~e?e:!0===e?Te:Oe,"[object ArrayBuffer]"===De.call(t)?a.input=new Uint8Array(t):a.input=t,a.next_in=0,a.avail_in=a.input.length;;){for(0===a.avail_out&&(a.output=new Uint8Array(i),a.next_out=0,a.avail_out=i),s=Se.inflate(a,r),s===Le&&n&&(s=Se.inflateSetDictionary(a,n),s===Ne?s=Se.inflate(a,r):s===Be&&(s=Le));a.avail_in>0&&s===Fe&&a.state.wrap>0&&0!==t[a.next_in];)Se.inflateReset(a),s=Se.inflate(a,r);switch(s){case Ie:case Be:case Le:case Ce:return this.onEnd(s),this.ended=!0,!1}if(o=a.avail_out,a.next_out&&(0===a.avail_out||s===Fe))if("string"===this.options.to){let t=Bt(a.output,a.next_out),e=a.next_out-t,n=It(a.output,t);a.next_out=e,a.avail_out=i-e,e&&a.output.set(a.output.subarray(t,t+e),0),this.onData(n)}else this.onData(a.output.length===a.next_out?a.output:a.output.subarray(0,a.next_out));if(s!==Ne||0!==o){if(s===Fe)return s=Se.inflateEnd(this.strm),this.onEnd(s),this.ended=!0,!0;if(0===a.avail_in)break}}return!0},He.prototype.onData=function(t){this.chunks.push(t)},He.prototype.onEnd=function(t){t===Ne&&("string"===this.options.to?this.result=this.chunks.join(""):this.result=Tt(this.chunks)),this.chunks=[],this.err=t,this.msg=this.strm.msg};const{Deflate:Me,deflate:je,deflateRaw:Ke,gzip:Pe}=Vt;var Ye=Me,Ge=B;const Xe=new class{constructor(){this.added=0,this.init()}init(){this.added=0,this.deflate=new Ye,this.deflate.push("[",Ge.Z_NO_FLUSH)}addEvent(t){if(!t)throw new Error("Adding invalid event");const e=this.added>0?",":"";this.deflate.push(e+JSON.stringify(t),Ge.Z_SYNC_FLUSH),this.added++}finish(){if(this.deflate.push("]",Ge.Z_FINISH),this.deflate.err)throw this.deflate.err;const t=this.deflate.result;return this.init(),t}},Je={init:()=>(Xe.init(),""),addEvent:t=>Xe.addEvent(t),finish:()=>Xe.finish()};addEventListener("message",(function(t){const e=t.data.method,a=t.data.id,[i]=t.data.args?JSON.parse(t.data.args):[];if(e in Je&&"function"==typeof Je[e])try{const t=Je[e](i);postMessage({id:a,method:e,success:!0,response:t})}catch(t){postMessage({id:a,method:e,success:!1,response:t.message}),console.error(t)}})),postMessage({id:void 0,method:"init",success:!0,response:void 0});`; +function t(t){let e=t.length;for(;--e>=0;)t[e]=0}const e=new Uint8Array([0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0]),a=new Uint8Array([0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13]),i=new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7]),n=new Uint8Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),s=new Array(576);t(s);const r=new Array(60);t(r);const o=new Array(512);t(o);const l=new Array(256);t(l);const h=new Array(29);t(h);const d=new Array(30);function _(t,e,a,i,n){this.static_tree=t,this.extra_bits=e,this.extra_base=a,this.elems=i,this.max_length=n,this.has_stree=t&&t.length}let f,c,u;function w(t,e){this.dyn_tree=t,this.max_code=0,this.stat_desc=e}t(d);const m=t=>t<256?o[t]:o[256+(t>>>7)],b=(t,e)=>{t.pending_buf[t.pending++]=255&e,t.pending_buf[t.pending++]=e>>>8&255},g=(t,e,a)=>{t.bi_valid>16-a?(t.bi_buf|=e<>16-t.bi_valid,t.bi_valid+=a-16):(t.bi_buf|=e<{g(t,a[2*e],a[2*e+1])},k=(t,e)=>{let a=0;do{a|=1&t,t>>>=1,a<<=1}while(--e>0);return a>>>1},v=(t,e,a)=>{const i=new Array(16);let n,s,r=0;for(n=1;n<=15;n++)r=r+a[n-1]<<1,i[n]=r;for(s=0;s<=e;s++){let e=t[2*s+1];0!==e&&(t[2*s]=k(i[e]++,e))}},y=t=>{let e;for(e=0;e<286;e++)t.dyn_ltree[2*e]=0;for(e=0;e<30;e++)t.dyn_dtree[2*e]=0;for(e=0;e<19;e++)t.bl_tree[2*e]=0;t.dyn_ltree[512]=1,t.opt_len=t.static_len=0,t.sym_next=t.matches=0},x=t=>{t.bi_valid>8?b(t,t.bi_buf):t.bi_valid>0&&(t.pending_buf[t.pending++]=t.bi_buf),t.bi_buf=0,t.bi_valid=0},z=(t,e,a,i)=>{const n=2*e,s=2*a;return t[n]{const i=t.heap[a];let n=a<<1;for(;n<=t.heap_len&&(n{let s,r,o,_,f=0;if(0!==t.sym_next)do{s=255&t.pending_buf[t.sym_buf+f++],s+=(255&t.pending_buf[t.sym_buf+f++])<<8,r=t.pending_buf[t.sym_buf+f++],0===s?p(t,r,i):(o=l[r],p(t,o+256+1,i),_=e[o],0!==_&&(r-=h[o],g(t,r,_)),s--,o=m(s),p(t,o,n),_=a[o],0!==_&&(s-=d[o],g(t,s,_)))}while(f{const a=e.dyn_tree,i=e.stat_desc.static_tree,n=e.stat_desc.has_stree,s=e.stat_desc.elems;let r,o,l,h=-1;for(t.heap_len=0,t.heap_max=573,r=0;r>1;r>=1;r--)A(t,a,r);l=s;do{r=t.heap[1],t.heap[1]=t.heap[t.heap_len--],A(t,a,1),o=t.heap[1],t.heap[--t.heap_max]=r,t.heap[--t.heap_max]=o,a[2*l]=a[2*r]+a[2*o],t.depth[l]=(t.depth[r]>=t.depth[o]?t.depth[r]:t.depth[o])+1,a[2*r+1]=a[2*o+1]=l,t.heap[1]=l++,A(t,a,1)}while(t.heap_len>=2);t.heap[--t.heap_max]=t.heap[1],((t,e)=>{const a=e.dyn_tree,i=e.max_code,n=e.stat_desc.static_tree,s=e.stat_desc.has_stree,r=e.stat_desc.extra_bits,o=e.stat_desc.extra_base,l=e.stat_desc.max_length;let h,d,_,f,c,u,w=0;for(f=0;f<=15;f++)t.bl_count[f]=0;for(a[2*t.heap[t.heap_max]+1]=0,h=t.heap_max+1;h<573;h++)d=t.heap[h],f=a[2*a[2*d+1]+1]+1,f>l&&(f=l,w++),a[2*d+1]=f,d>i||(t.bl_count[f]++,c=0,d>=o&&(c=r[d-o]),u=a[2*d],t.opt_len+=u*(f+c),s&&(t.static_len+=u*(n[2*d+1]+c)));if(0!==w){do{for(f=l-1;0===t.bl_count[f];)f--;t.bl_count[f]--,t.bl_count[f+1]+=2,t.bl_count[l]--,w-=2}while(w>0);for(f=l;0!==f;f--)for(d=t.bl_count[f];0!==d;)_=t.heap[--h],_>i||(a[2*_+1]!==f&&(t.opt_len+=(f-a[2*_+1])*a[2*_],a[2*_+1]=f),d--)}})(t,e),v(a,h,t.bl_count)},Z=(t,e,a)=>{let i,n,s=-1,r=e[1],o=0,l=7,h=4;for(0===r&&(l=138,h=3),e[2*(a+1)+1]=65535,i=0;i<=a;i++)n=r,r=e[2*(i+1)+1],++o{let i,n,s=-1,r=e[1],o=0,l=7,h=4;for(0===r&&(l=138,h=3),i=0;i<=a;i++)if(n=r,r=e[2*(i+1)+1],!(++o{g(t,0+(i?1:0),3),x(t),b(t,a),b(t,~a),a&&t.pending_buf.set(t.window.subarray(e,e+a),t.pending),t.pending+=a};var T=(t,e,a,i)=>{let o,l,h=0;t.level>0?(2===t.strm.data_type&&(t.strm.data_type=(t=>{let e,a=4093624447;for(e=0;e<=31;e++,a>>>=1)if(1&a&&0!==t.dyn_ltree[2*e])return 0;if(0!==t.dyn_ltree[18]||0!==t.dyn_ltree[20]||0!==t.dyn_ltree[26])return 1;for(e=32;e<256;e++)if(0!==t.dyn_ltree[2*e])return 1;return 0})(t)),R(t,t.l_desc),R(t,t.d_desc),h=(t=>{let e;for(Z(t,t.dyn_ltree,t.l_desc.max_code),Z(t,t.dyn_dtree,t.d_desc.max_code),R(t,t.bl_desc),e=18;e>=3&&0===t.bl_tree[2*n[e]+1];e--);return t.opt_len+=3*(e+1)+5+5+4,e})(t),o=t.opt_len+3+7>>>3,l=t.static_len+3+7>>>3,l<=o&&(o=l)):o=l=a+5,a+4<=o&&-1!==e?D(t,e,a,i):4===t.strategy||l===o?(g(t,2+(i?1:0),3),E(t,s,r)):(g(t,4+(i?1:0),3),((t,e,a,i)=>{let s;for(g(t,e-257,5),g(t,a-1,5),g(t,i-4,4),s=0;s{S||((()=>{let t,n,w,m,b;const g=new Array(16);for(w=0,m=0;m<28;m++)for(h[m]=w,t=0;t<1<>=7;m<30;m++)for(d[m]=b<<7,t=0;t<1<(t.pending_buf[t.sym_buf+t.sym_next++]=e,t.pending_buf[t.sym_buf+t.sym_next++]=e>>8,t.pending_buf[t.sym_buf+t.sym_next++]=a,0===e?t.dyn_ltree[2*a]++:(t.matches++,e--,t.dyn_ltree[2*(l[a]+256+1)]++,t.dyn_dtree[2*m(e)]++),t.sym_next===t.sym_end),_tr_align:t=>{g(t,2,3),p(t,256,s),(t=>{16===t.bi_valid?(b(t,t.bi_buf),t.bi_buf=0,t.bi_valid=0):t.bi_valid>=8&&(t.pending_buf[t.pending++]=255&t.bi_buf,t.bi_buf>>=8,t.bi_valid-=8)})(t)}};var F=(t,e,a,i)=>{let n=65535&t|0,s=t>>>16&65535|0,r=0;for(;0!==a;){r=a>2e3?2e3:a,a-=r;do{n=n+e[i++]|0,s=s+n|0}while(--r);n%=65521,s%=65521}return n|s<<16|0};const L=new Uint32Array((()=>{let t,e=[];for(var a=0;a<256;a++){t=a;for(var i=0;i<8;i++)t=1&t?3988292384^t>>>1:t>>>1;e[a]=t}return e})());var N=(t,e,a,i)=>{const n=L,s=i+a;t^=-1;for(let a=i;a>>8^n[255&(t^e[a])];return-1^t},I={2:"need dictionary",1:"stream end",0:"","-1":"file error","-2":"stream error","-3":"data error","-4":"insufficient memory","-5":"buffer error","-6":"incompatible version"},B={Z_NO_FLUSH:0,Z_PARTIAL_FLUSH:1,Z_SYNC_FLUSH:2,Z_FULL_FLUSH:3,Z_FINISH:4,Z_BLOCK:5,Z_TREES:6,Z_OK:0,Z_STREAM_END:1,Z_NEED_DICT:2,Z_ERRNO:-1,Z_STREAM_ERROR:-2,Z_DATA_ERROR:-3,Z_MEM_ERROR:-4,Z_BUF_ERROR:-5,Z_NO_COMPRESSION:0,Z_BEST_SPEED:1,Z_BEST_COMPRESSION:9,Z_DEFAULT_COMPRESSION:-1,Z_FILTERED:1,Z_HUFFMAN_ONLY:2,Z_RLE:3,Z_FIXED:4,Z_DEFAULT_STRATEGY:0,Z_BINARY:0,Z_TEXT:1,Z_UNKNOWN:2,Z_DEFLATED:8};const{_tr_init:C,_tr_stored_block:H,_tr_flush_block:M,_tr_tally:j,_tr_align:K}=O,{Z_NO_FLUSH:P,Z_PARTIAL_FLUSH:Y,Z_FULL_FLUSH:G,Z_FINISH:X,Z_BLOCK:W,Z_OK:q,Z_STREAM_END:J,Z_STREAM_ERROR:Q,Z_DATA_ERROR:V,Z_BUF_ERROR:$,Z_DEFAULT_COMPRESSION:tt,Z_FILTERED:et,Z_HUFFMAN_ONLY:at,Z_RLE:it,Z_FIXED:nt,Z_DEFAULT_STRATEGY:st,Z_UNKNOWN:rt,Z_DEFLATED:ot}=B,lt=(t,e)=>(t.msg=I[e],e),ht=t=>2*t-(t>4?9:0),dt=t=>{let e=t.length;for(;--e>=0;)t[e]=0},_t=t=>{let e,a,i,n=t.w_size;e=t.hash_size,i=e;do{a=t.head[--i],t.head[i]=a>=n?a-n:0}while(--e);e=n,i=e;do{a=t.prev[--i],t.prev[i]=a>=n?a-n:0}while(--e)};let ft=(t,e,a)=>(e<{const e=t.state;let a=e.pending;a>t.avail_out&&(a=t.avail_out),0!==a&&(t.output.set(e.pending_buf.subarray(e.pending_out,e.pending_out+a),t.next_out),t.next_out+=a,e.pending_out+=a,t.total_out+=a,t.avail_out-=a,e.pending-=a,0===e.pending&&(e.pending_out=0))},ut=(t,e)=>{M(t,t.block_start>=0?t.block_start:-1,t.strstart-t.block_start,e),t.block_start=t.strstart,ct(t.strm)},wt=(t,e)=>{t.pending_buf[t.pending++]=e},mt=(t,e)=>{t.pending_buf[t.pending++]=e>>>8&255,t.pending_buf[t.pending++]=255&e},bt=(t,e,a,i)=>{let n=t.avail_in;return n>i&&(n=i),0===n?0:(t.avail_in-=n,e.set(t.input.subarray(t.next_in,t.next_in+n),a),1===t.state.wrap?t.adler=F(t.adler,e,n,a):2===t.state.wrap&&(t.adler=N(t.adler,e,n,a)),t.next_in+=n,t.total_in+=n,n)},gt=(t,e)=>{let a,i,n=t.max_chain_length,s=t.strstart,r=t.prev_length,o=t.nice_match;const l=t.strstart>t.w_size-262?t.strstart-(t.w_size-262):0,h=t.window,d=t.w_mask,_=t.prev,f=t.strstart+258;let c=h[s+r-1],u=h[s+r];t.prev_length>=t.good_match&&(n>>=2),o>t.lookahead&&(o=t.lookahead);do{if(a=e,h[a+r]===u&&h[a+r-1]===c&&h[a]===h[s]&&h[++a]===h[s+1]){s+=2,a++;do{}while(h[++s]===h[++a]&&h[++s]===h[++a]&&h[++s]===h[++a]&&h[++s]===h[++a]&&h[++s]===h[++a]&&h[++s]===h[++a]&&h[++s]===h[++a]&&h[++s]===h[++a]&&sr){if(t.match_start=e,r=i,i>=o)break;c=h[s+r-1],u=h[s+r]}}}while((e=_[e&d])>l&&0!=--n);return r<=t.lookahead?r:t.lookahead},pt=t=>{const e=t.w_size;let a,i,n;do{if(i=t.window_size-t.lookahead-t.strstart,t.strstart>=e+(e-262)&&(t.window.set(t.window.subarray(e,e+e-i),0),t.match_start-=e,t.strstart-=e,t.block_start-=e,t.insert>t.strstart&&(t.insert=t.strstart),_t(t),i+=e),0===t.strm.avail_in)break;if(a=bt(t.strm,t.window,t.strstart+t.lookahead,i),t.lookahead+=a,t.lookahead+t.insert>=3)for(n=t.strstart-t.insert,t.ins_h=t.window[n],t.ins_h=ft(t,t.ins_h,t.window[n+1]);t.insert&&(t.ins_h=ft(t,t.ins_h,t.window[n+3-1]),t.prev[n&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=n,n++,t.insert--,!(t.lookahead+t.insert<3)););}while(t.lookahead<262&&0!==t.strm.avail_in)},kt=(t,e)=>{let a,i,n,s=t.pending_buf_size-5>t.w_size?t.w_size:t.pending_buf_size-5,r=0,o=t.strm.avail_in;do{if(a=65535,n=t.bi_valid+42>>3,t.strm.avail_outi+t.strm.avail_in&&(a=i+t.strm.avail_in),a>n&&(a=n),a>8,t.pending_buf[t.pending-2]=~a,t.pending_buf[t.pending-1]=~a>>8,ct(t.strm),i&&(i>a&&(i=a),t.strm.output.set(t.window.subarray(t.block_start,t.block_start+i),t.strm.next_out),t.strm.next_out+=i,t.strm.avail_out-=i,t.strm.total_out+=i,t.block_start+=i,a-=i),a&&(bt(t.strm,t.strm.output,t.strm.next_out,a),t.strm.next_out+=a,t.strm.avail_out-=a,t.strm.total_out+=a)}while(0===r);return o-=t.strm.avail_in,o&&(o>=t.w_size?(t.matches=2,t.window.set(t.strm.input.subarray(t.strm.next_in-t.w_size,t.strm.next_in),0),t.strstart=t.w_size,t.insert=t.strstart):(t.window_size-t.strstart<=o&&(t.strstart-=t.w_size,t.window.set(t.window.subarray(t.w_size,t.w_size+t.strstart),0),t.matches<2&&t.matches++,t.insert>t.strstart&&(t.insert=t.strstart)),t.window.set(t.strm.input.subarray(t.strm.next_in-o,t.strm.next_in),t.strstart),t.strstart+=o,t.insert+=o>t.w_size-t.insert?t.w_size-t.insert:o),t.block_start=t.strstart),t.high_watern&&t.block_start>=t.w_size&&(t.block_start-=t.w_size,t.strstart-=t.w_size,t.window.set(t.window.subarray(t.w_size,t.w_size+t.strstart),0),t.matches<2&&t.matches++,n+=t.w_size,t.insert>t.strstart&&(t.insert=t.strstart)),n>t.strm.avail_in&&(n=t.strm.avail_in),n&&(bt(t.strm,t.window,t.strstart,n),t.strstart+=n,t.insert+=n>t.w_size-t.insert?t.w_size-t.insert:n),t.high_water>3,n=t.pending_buf_size-n>65535?65535:t.pending_buf_size-n,s=n>t.w_size?t.w_size:n,i=t.strstart-t.block_start,(i>=s||(i||e===X)&&e!==P&&0===t.strm.avail_in&&i<=n)&&(a=i>n?n:i,r=e===X&&0===t.strm.avail_in&&a===i?1:0,H(t,t.block_start,a,r),t.block_start+=a,ct(t.strm)),r?3:1)},vt=(t,e)=>{let a,i;for(;;){if(t.lookahead<262){if(pt(t),t.lookahead<262&&e===P)return 1;if(0===t.lookahead)break}if(a=0,t.lookahead>=3&&(t.ins_h=ft(t,t.ins_h,t.window[t.strstart+3-1]),a=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart),0!==a&&t.strstart-a<=t.w_size-262&&(t.match_length=gt(t,a)),t.match_length>=3)if(i=j(t,t.strstart-t.match_start,t.match_length-3),t.lookahead-=t.match_length,t.match_length<=t.max_lazy_match&&t.lookahead>=3){t.match_length--;do{t.strstart++,t.ins_h=ft(t,t.ins_h,t.window[t.strstart+3-1]),a=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart}while(0!=--t.match_length);t.strstart++}else t.strstart+=t.match_length,t.match_length=0,t.ins_h=t.window[t.strstart],t.ins_h=ft(t,t.ins_h,t.window[t.strstart+1]);else i=j(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++;if(i&&(ut(t,!1),0===t.strm.avail_out))return 1}return t.insert=t.strstart<2?t.strstart:2,e===X?(ut(t,!0),0===t.strm.avail_out?3:4):t.sym_next&&(ut(t,!1),0===t.strm.avail_out)?1:2},yt=(t,e)=>{let a,i,n;for(;;){if(t.lookahead<262){if(pt(t),t.lookahead<262&&e===P)return 1;if(0===t.lookahead)break}if(a=0,t.lookahead>=3&&(t.ins_h=ft(t,t.ins_h,t.window[t.strstart+3-1]),a=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart),t.prev_length=t.match_length,t.prev_match=t.match_start,t.match_length=2,0!==a&&t.prev_length4096)&&(t.match_length=2)),t.prev_length>=3&&t.match_length<=t.prev_length){n=t.strstart+t.lookahead-3,i=j(t,t.strstart-1-t.prev_match,t.prev_length-3),t.lookahead-=t.prev_length-1,t.prev_length-=2;do{++t.strstart<=n&&(t.ins_h=ft(t,t.ins_h,t.window[t.strstart+3-1]),a=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart)}while(0!=--t.prev_length);if(t.match_available=0,t.match_length=2,t.strstart++,i&&(ut(t,!1),0===t.strm.avail_out))return 1}else if(t.match_available){if(i=j(t,0,t.window[t.strstart-1]),i&&ut(t,!1),t.strstart++,t.lookahead--,0===t.strm.avail_out)return 1}else t.match_available=1,t.strstart++,t.lookahead--}return t.match_available&&(i=j(t,0,t.window[t.strstart-1]),t.match_available=0),t.insert=t.strstart<2?t.strstart:2,e===X?(ut(t,!0),0===t.strm.avail_out?3:4):t.sym_next&&(ut(t,!1),0===t.strm.avail_out)?1:2};function xt(t,e,a,i,n){this.good_length=t,this.max_lazy=e,this.nice_length=a,this.max_chain=i,this.func=n}const zt=[new xt(0,0,0,0,kt),new xt(4,4,8,4,vt),new xt(4,5,16,8,vt),new xt(4,6,32,32,vt),new xt(4,4,16,16,yt),new xt(8,16,32,32,yt),new xt(8,16,128,128,yt),new xt(8,32,128,256,yt),new xt(32,128,258,1024,yt),new xt(32,258,258,4096,yt)];function At(){this.strm=null,this.status=0,this.pending_buf=null,this.pending_buf_size=0,this.pending_out=0,this.pending=0,this.wrap=0,this.gzhead=null,this.gzindex=0,this.method=ot,this.last_flush=-1,this.w_size=0,this.w_bits=0,this.w_mask=0,this.window=null,this.window_size=0,this.prev=null,this.head=null,this.ins_h=0,this.hash_size=0,this.hash_bits=0,this.hash_mask=0,this.hash_shift=0,this.block_start=0,this.match_length=0,this.prev_match=0,this.match_available=0,this.strstart=0,this.match_start=0,this.lookahead=0,this.prev_length=0,this.max_chain_length=0,this.max_lazy_match=0,this.level=0,this.strategy=0,this.good_match=0,this.nice_match=0,this.dyn_ltree=new Uint16Array(1146),this.dyn_dtree=new Uint16Array(122),this.bl_tree=new Uint16Array(78),dt(this.dyn_ltree),dt(this.dyn_dtree),dt(this.bl_tree),this.l_desc=null,this.d_desc=null,this.bl_desc=null,this.bl_count=new Uint16Array(16),this.heap=new Uint16Array(573),dt(this.heap),this.heap_len=0,this.heap_max=0,this.depth=new Uint16Array(573),dt(this.depth),this.sym_buf=0,this.lit_bufsize=0,this.sym_next=0,this.sym_end=0,this.opt_len=0,this.static_len=0,this.matches=0,this.insert=0,this.bi_buf=0,this.bi_valid=0}const Et=t=>{if(!t)return 1;const e=t.state;return!e||e.strm!==t||42!==e.status&&57!==e.status&&69!==e.status&&73!==e.status&&91!==e.status&&103!==e.status&&113!==e.status&&666!==e.status?1:0},Rt=t=>{if(Et(t))return lt(t,Q);t.total_in=t.total_out=0,t.data_type=rt;const e=t.state;return e.pending=0,e.pending_out=0,e.wrap<0&&(e.wrap=-e.wrap),e.status=2===e.wrap?57:e.wrap?42:113,t.adler=2===e.wrap?0:1,e.last_flush=-2,C(e),q},Zt=t=>{const e=Rt(t);var a;return e===q&&((a=t.state).window_size=2*a.w_size,dt(a.head),a.max_lazy_match=zt[a.level].max_lazy,a.good_match=zt[a.level].good_length,a.nice_match=zt[a.level].nice_length,a.max_chain_length=zt[a.level].max_chain,a.strstart=0,a.block_start=0,a.lookahead=0,a.insert=0,a.match_length=a.prev_length=2,a.match_available=0,a.ins_h=0),e},Ut=(t,e,a,i,n,s)=>{if(!t)return Q;let r=1;if(e===tt&&(e=6),i<0?(r=0,i=-i):i>15&&(r=2,i-=16),n<1||n>9||a!==ot||i<8||i>15||e<0||e>9||s<0||s>nt||8===i&&1!==r)return lt(t,Q);8===i&&(i=9);const o=new At;return t.state=o,o.strm=t,o.status=42,o.wrap=r,o.gzhead=null,o.w_bits=i,o.w_size=1<Ut(t,e,ot,15,8,st),deflateInit2:Ut,deflateReset:Zt,deflateResetKeep:Rt,deflateSetHeader:(t,e)=>Et(t)||2!==t.state.wrap?Q:(t.state.gzhead=e,q),deflate:(t,e)=>{if(Et(t)||e>W||e<0)return t?lt(t,Q):Q;const a=t.state;if(!t.output||0!==t.avail_in&&!t.input||666===a.status&&e!==X)return lt(t,0===t.avail_out?$:Q);const i=a.last_flush;if(a.last_flush=e,0!==a.pending){if(ct(t),0===t.avail_out)return a.last_flush=-1,q}else if(0===t.avail_in&&ht(e)<=ht(i)&&e!==X)return lt(t,$);if(666===a.status&&0!==t.avail_in)return lt(t,$);if(42===a.status&&0===a.wrap&&(a.status=113),42===a.status){let e=ot+(a.w_bits-8<<4)<<8,i=-1;if(i=a.strategy>=at||a.level<2?0:a.level<6?1:6===a.level?2:3,e|=i<<6,0!==a.strstart&&(e|=32),e+=31-e%31,mt(a,e),0!==a.strstart&&(mt(a,t.adler>>>16),mt(a,65535&t.adler)),t.adler=1,a.status=113,ct(t),0!==a.pending)return a.last_flush=-1,q}if(57===a.status)if(t.adler=0,wt(a,31),wt(a,139),wt(a,8),a.gzhead)wt(a,(a.gzhead.text?1:0)+(a.gzhead.hcrc?2:0)+(a.gzhead.extra?4:0)+(a.gzhead.name?8:0)+(a.gzhead.comment?16:0)),wt(a,255&a.gzhead.time),wt(a,a.gzhead.time>>8&255),wt(a,a.gzhead.time>>16&255),wt(a,a.gzhead.time>>24&255),wt(a,9===a.level?2:a.strategy>=at||a.level<2?4:0),wt(a,255&a.gzhead.os),a.gzhead.extra&&a.gzhead.extra.length&&(wt(a,255&a.gzhead.extra.length),wt(a,a.gzhead.extra.length>>8&255)),a.gzhead.hcrc&&(t.adler=N(t.adler,a.pending_buf,a.pending,0)),a.gzindex=0,a.status=69;else if(wt(a,0),wt(a,0),wt(a,0),wt(a,0),wt(a,0),wt(a,9===a.level?2:a.strategy>=at||a.level<2?4:0),wt(a,3),a.status=113,ct(t),0!==a.pending)return a.last_flush=-1,q;if(69===a.status){if(a.gzhead.extra){let e=a.pending,i=(65535&a.gzhead.extra.length)-a.gzindex;for(;a.pending+i>a.pending_buf_size;){let n=a.pending_buf_size-a.pending;if(a.pending_buf.set(a.gzhead.extra.subarray(a.gzindex,a.gzindex+n),a.pending),a.pending=a.pending_buf_size,a.gzhead.hcrc&&a.pending>e&&(t.adler=N(t.adler,a.pending_buf,a.pending-e,e)),a.gzindex+=n,ct(t),0!==a.pending)return a.last_flush=-1,q;e=0,i-=n}let n=new Uint8Array(a.gzhead.extra);a.pending_buf.set(n.subarray(a.gzindex,a.gzindex+i),a.pending),a.pending+=i,a.gzhead.hcrc&&a.pending>e&&(t.adler=N(t.adler,a.pending_buf,a.pending-e,e)),a.gzindex=0}a.status=73}if(73===a.status){if(a.gzhead.name){let e,i=a.pending;do{if(a.pending===a.pending_buf_size){if(a.gzhead.hcrc&&a.pending>i&&(t.adler=N(t.adler,a.pending_buf,a.pending-i,i)),ct(t),0!==a.pending)return a.last_flush=-1,q;i=0}e=a.gzindexi&&(t.adler=N(t.adler,a.pending_buf,a.pending-i,i)),a.gzindex=0}a.status=91}if(91===a.status){if(a.gzhead.comment){let e,i=a.pending;do{if(a.pending===a.pending_buf_size){if(a.gzhead.hcrc&&a.pending>i&&(t.adler=N(t.adler,a.pending_buf,a.pending-i,i)),ct(t),0!==a.pending)return a.last_flush=-1,q;i=0}e=a.gzindexi&&(t.adler=N(t.adler,a.pending_buf,a.pending-i,i))}a.status=103}if(103===a.status){if(a.gzhead.hcrc){if(a.pending+2>a.pending_buf_size&&(ct(t),0!==a.pending))return a.last_flush=-1,q;wt(a,255&t.adler),wt(a,t.adler>>8&255),t.adler=0}if(a.status=113,ct(t),0!==a.pending)return a.last_flush=-1,q}if(0!==t.avail_in||0!==a.lookahead||e!==P&&666!==a.status){let i=0===a.level?kt(a,e):a.strategy===at?((t,e)=>{let a;for(;;){if(0===t.lookahead&&(pt(t),0===t.lookahead)){if(e===P)return 1;break}if(t.match_length=0,a=j(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++,a&&(ut(t,!1),0===t.strm.avail_out))return 1}return t.insert=0,e===X?(ut(t,!0),0===t.strm.avail_out?3:4):t.sym_next&&(ut(t,!1),0===t.strm.avail_out)?1:2})(a,e):a.strategy===it?((t,e)=>{let a,i,n,s;const r=t.window;for(;;){if(t.lookahead<=258){if(pt(t),t.lookahead<=258&&e===P)return 1;if(0===t.lookahead)break}if(t.match_length=0,t.lookahead>=3&&t.strstart>0&&(n=t.strstart-1,i=r[n],i===r[++n]&&i===r[++n]&&i===r[++n])){s=t.strstart+258;do{}while(i===r[++n]&&i===r[++n]&&i===r[++n]&&i===r[++n]&&i===r[++n]&&i===r[++n]&&i===r[++n]&&i===r[++n]&&nt.lookahead&&(t.match_length=t.lookahead)}if(t.match_length>=3?(a=j(t,1,t.match_length-3),t.lookahead-=t.match_length,t.strstart+=t.match_length,t.match_length=0):(a=j(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++),a&&(ut(t,!1),0===t.strm.avail_out))return 1}return t.insert=0,e===X?(ut(t,!0),0===t.strm.avail_out?3:4):t.sym_next&&(ut(t,!1),0===t.strm.avail_out)?1:2})(a,e):zt[a.level].func(a,e);if(3!==i&&4!==i||(a.status=666),1===i||3===i)return 0===t.avail_out&&(a.last_flush=-1),q;if(2===i&&(e===Y?K(a):e!==W&&(H(a,0,0,!1),e===G&&(dt(a.head),0===a.lookahead&&(a.strstart=0,a.block_start=0,a.insert=0))),ct(t),0===t.avail_out))return a.last_flush=-1,q}return e!==X?q:a.wrap<=0?J:(2===a.wrap?(wt(a,255&t.adler),wt(a,t.adler>>8&255),wt(a,t.adler>>16&255),wt(a,t.adler>>24&255),wt(a,255&t.total_in),wt(a,t.total_in>>8&255),wt(a,t.total_in>>16&255),wt(a,t.total_in>>24&255)):(mt(a,t.adler>>>16),mt(a,65535&t.adler)),ct(t),a.wrap>0&&(a.wrap=-a.wrap),0!==a.pending?q:J)},deflateEnd:t=>{if(Et(t))return Q;const e=t.state.status;return t.state=null,113===e?lt(t,V):q},deflateSetDictionary:(t,e)=>{let a=e.length;if(Et(t))return Q;const i=t.state,n=i.wrap;if(2===n||1===n&&42!==i.status||i.lookahead)return Q;if(1===n&&(t.adler=F(t.adler,e,a,0)),i.wrap=0,a>=i.w_size){0===n&&(dt(i.head),i.strstart=0,i.block_start=0,i.insert=0);let t=new Uint8Array(i.w_size);t.set(e.subarray(a-i.w_size,a),0),e=t,a=i.w_size}const s=t.avail_in,r=t.next_in,o=t.input;for(t.avail_in=a,t.next_in=0,t.input=e,pt(i);i.lookahead>=3;){let t=i.strstart,e=i.lookahead-2;do{i.ins_h=ft(i,i.ins_h,i.window[t+3-1]),i.prev[t&i.w_mask]=i.head[i.ins_h],i.head[i.ins_h]=t,t++}while(--e);i.strstart=t,i.lookahead=2,pt(i)}return i.strstart+=i.lookahead,i.block_start=i.strstart,i.insert=i.lookahead,i.lookahead=0,i.match_length=i.prev_length=2,i.match_available=0,t.next_in=r,t.input=o,t.avail_in=s,i.wrap=n,q},deflateInfo:"pako deflate (from Nodeca project)"};const Dt=(t,e)=>Object.prototype.hasOwnProperty.call(t,e);var Tt=function(t){const e=Array.prototype.slice.call(arguments,1);for(;e.length;){const a=e.shift();if(a){if("object"!=typeof a)throw new TypeError(a+"must be non-object");for(const e in a)Dt(a,e)&&(t[e]=a[e])}}return t},Ot=t=>{let e=0;for(let a=0,i=t.length;a=252?6:t>=248?5:t>=240?4:t>=224?3:t>=192?2:1;Lt[254]=Lt[254]=1;var Nt=t=>{if("function"==typeof TextEncoder&&TextEncoder.prototype.encode)return(new TextEncoder).encode(t);let e,a,i,n,s,r=t.length,o=0;for(n=0;n>>6,e[s++]=128|63&a):a<65536?(e[s++]=224|a>>>12,e[s++]=128|a>>>6&63,e[s++]=128|63&a):(e[s++]=240|a>>>18,e[s++]=128|a>>>12&63,e[s++]=128|a>>>6&63,e[s++]=128|63&a);return e},It=(t,e)=>{const a=e||t.length;if("function"==typeof TextDecoder&&TextDecoder.prototype.decode)return(new TextDecoder).decode(t.subarray(0,e));let i,n;const s=new Array(2*a);for(n=0,i=0;i4)s[n++]=65533,i+=r-1;else{for(e&=2===r?31:3===r?15:7;r>1&&i1?s[n++]=65533:e<65536?s[n++]=e:(e-=65536,s[n++]=55296|e>>10&1023,s[n++]=56320|1023&e)}}return((t,e)=>{if(e<65534&&t.subarray&&Ft)return String.fromCharCode.apply(null,t.length===e?t:t.subarray(0,e));let a="";for(let i=0;i{(e=e||t.length)>t.length&&(e=t.length);let a=e-1;for(;a>=0&&128==(192&t[a]);)a--;return a<0||0===a?e:a+Lt[t[a]]>e?a:e};var Ct=function(){this.input=null,this.next_in=0,this.avail_in=0,this.total_in=0,this.output=null,this.next_out=0,this.avail_out=0,this.total_out=0,this.msg="",this.state=null,this.data_type=2,this.adler=0};const Ht=Object.prototype.toString,{Z_NO_FLUSH:Mt,Z_SYNC_FLUSH:jt,Z_FULL_FLUSH:Kt,Z_FINISH:Pt,Z_OK:Yt,Z_STREAM_END:Gt,Z_DEFAULT_COMPRESSION:Xt,Z_DEFAULT_STRATEGY:Wt,Z_DEFLATED:qt}=B;function Jt(t){this.options=Tt({level:Xt,method:qt,chunkSize:16384,windowBits:15,memLevel:8,strategy:Wt},t||{});let e=this.options;e.raw&&e.windowBits>0?e.windowBits=-e.windowBits:e.gzip&&e.windowBits>0&&e.windowBits<16&&(e.windowBits+=16),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new Ct,this.strm.avail_out=0;let a=St.deflateInit2(this.strm,e.level,e.method,e.windowBits,e.memLevel,e.strategy);if(a!==Yt)throw new Error(I[a]);if(e.header&&St.deflateSetHeader(this.strm,e.header),e.dictionary){let t;if(t="string"==typeof e.dictionary?Nt(e.dictionary):"[object ArrayBuffer]"===Ht.call(e.dictionary)?new Uint8Array(e.dictionary):e.dictionary,a=St.deflateSetDictionary(this.strm,t),a!==Yt)throw new Error(I[a]);this._dict_set=!0}}function Qt(t,e){const a=new Jt(e);if(a.push(t,!0),a.err)throw a.msg||I[a.err];return a.result}Jt.prototype.push=function(t,e){const a=this.strm,i=this.options.chunkSize;let n,s;if(this.ended)return!1;for(s=e===~~e?e:!0===e?Pt:Mt,"string"==typeof t?a.input=Nt(t):"[object ArrayBuffer]"===Ht.call(t)?a.input=new Uint8Array(t):a.input=t,a.next_in=0,a.avail_in=a.input.length;;)if(0===a.avail_out&&(a.output=new Uint8Array(i),a.next_out=0,a.avail_out=i),(s===jt||s===Kt)&&a.avail_out<=6)this.onData(a.output.subarray(0,a.next_out)),a.avail_out=0;else{if(n=St.deflate(a,s),n===Gt)return a.next_out>0&&this.onData(a.output.subarray(0,a.next_out)),n=St.deflateEnd(this.strm),this.onEnd(n),this.ended=!0,n===Yt;if(0!==a.avail_out){if(s>0&&a.next_out>0)this.onData(a.output.subarray(0,a.next_out)),a.avail_out=0;else if(0===a.avail_in)break}else this.onData(a.output)}return!0},Jt.prototype.onData=function(t){this.chunks.push(t)},Jt.prototype.onEnd=function(t){t===Yt&&(this.result=Ot(this.chunks)),this.chunks=[],this.err=t,this.msg=this.strm.msg};var Vt={Deflate:Jt,deflate:Qt,deflateRaw:function(t,e){return(e=e||{}).raw=!0,Qt(t,e)},gzip:function(t,e){return(e=e||{}).gzip=!0,Qt(t,e)},constants:B};var $t=function(t,e){let a,i,n,s,r,o,l,h,d,_,f,c,u,w,m,b,g,p,k,v,y,x,z,A;const E=t.state;a=t.next_in,z=t.input,i=a+(t.avail_in-5),n=t.next_out,A=t.output,s=n-(e-t.avail_out),r=n+(t.avail_out-257),o=E.dmax,l=E.wsize,h=E.whave,d=E.wnext,_=E.window,f=E.hold,c=E.bits,u=E.lencode,w=E.distcode,m=(1<>>24,f>>>=p,c-=p,p=g>>>16&255,0===p)A[n++]=65535&g;else{if(!(16&p)){if(0==(64&p)){g=u[(65535&g)+(f&(1<>>=p,c-=p),c<15&&(f+=z[a++]<>>24,f>>>=p,c-=p,p=g>>>16&255,!(16&p)){if(0==(64&p)){g=w[(65535&g)+(f&(1<o){t.msg="invalid distance too far back",E.mode=16209;break t}if(f>>>=p,c-=p,p=n-s,v>p){if(p=v-p,p>h&&E.sane){t.msg="invalid distance too far back",E.mode=16209;break t}if(y=0,x=_,0===d){if(y+=l-p,p2;)A[n++]=x[y++],A[n++]=x[y++],A[n++]=x[y++],k-=3;k&&(A[n++]=x[y++],k>1&&(A[n++]=x[y++]))}else{y=n-v;do{A[n++]=A[y++],A[n++]=A[y++],A[n++]=A[y++],k-=3}while(k>2);k&&(A[n++]=A[y++],k>1&&(A[n++]=A[y++]))}break}}break}}while(a>3,a-=k,c-=k<<3,f&=(1<{const l=o.bits;let h,d,_,f,c,u,w=0,m=0,b=0,g=0,p=0,k=0,v=0,y=0,x=0,z=0,A=null;const E=new Uint16Array(16),R=new Uint16Array(16);let Z,U,S,D=null;for(w=0;w<=15;w++)E[w]=0;for(m=0;m=1&&0===E[g];g--);if(p>g&&(p=g),0===g)return n[s++]=20971520,n[s++]=20971520,o.bits=1,0;for(b=1;b0&&(0===t||1!==g))return-1;for(R[1]=0,w=1;w<15;w++)R[w+1]=R[w]+E[w];for(m=0;m852||2===t&&x>592)return 1;for(;;){Z=w-v,r[m]+1=u?(U=D[r[m]-u],S=A[r[m]-u]):(U=96,S=0),h=1<>v)+d]=Z<<24|U<<16|S|0}while(0!==d);for(h=1<>=1;if(0!==h?(z&=h-1,z+=h):z=0,m++,0==--E[w]){if(w===g)break;w=e[a+r[m]]}if(w>p&&(z&f)!==_){for(0===v&&(v=p),c+=b,k=w-v,y=1<852||2===t&&x>592)return 1;_=z&f,n[_]=p<<24|k<<16|c-s|0}}return 0!==z&&(n[c+z]=w-v<<24|64<<16|0),o.bits=p,0};const{Z_FINISH:se,Z_BLOCK:re,Z_TREES:oe,Z_OK:le,Z_STREAM_END:he,Z_NEED_DICT:de,Z_STREAM_ERROR:_e,Z_DATA_ERROR:fe,Z_MEM_ERROR:ce,Z_BUF_ERROR:ue,Z_DEFLATED:we}=B,me=16209,be=t=>(t>>>24&255)+(t>>>8&65280)+((65280&t)<<8)+((255&t)<<24);function ge(){this.strm=null,this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new Uint16Array(320),this.work=new Uint16Array(288),this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}const pe=t=>{if(!t)return 1;const e=t.state;return!e||e.strm!==t||e.mode<16180||e.mode>16211?1:0},ke=t=>{if(pe(t))return _e;const e=t.state;return t.total_in=t.total_out=e.total=0,t.msg="",e.wrap&&(t.adler=1&e.wrap),e.mode=16180,e.last=0,e.havedict=0,e.flags=-1,e.dmax=32768,e.head=null,e.hold=0,e.bits=0,e.lencode=e.lendyn=new Int32Array(852),e.distcode=e.distdyn=new Int32Array(592),e.sane=1,e.back=-1,le},ve=t=>{if(pe(t))return _e;const e=t.state;return e.wsize=0,e.whave=0,e.wnext=0,ke(t)},ye=(t,e)=>{let a;if(pe(t))return _e;const i=t.state;return e<0?(a=0,e=-e):(a=5+(e>>4),e<48&&(e&=15)),e&&(e<8||e>15)?_e:(null!==i.window&&i.wbits!==e&&(i.window=null),i.wrap=a,i.wbits=e,ve(t))},xe=(t,e)=>{if(!t)return _e;const a=new ge;t.state=a,a.strm=t,a.window=null,a.mode=16180;const i=ye(t,e);return i!==le&&(t.state=null),i};let ze,Ae,Ee=!0;const Re=t=>{if(Ee){ze=new Int32Array(512),Ae=new Int32Array(32);let e=0;for(;e<144;)t.lens[e++]=8;for(;e<256;)t.lens[e++]=9;for(;e<280;)t.lens[e++]=7;for(;e<288;)t.lens[e++]=8;for(ne(1,t.lens,0,288,ze,0,t.work,{bits:9}),e=0;e<32;)t.lens[e++]=5;ne(2,t.lens,0,32,Ae,0,t.work,{bits:5}),Ee=!1}t.lencode=ze,t.lenbits=9,t.distcode=Ae,t.distbits=5},Ze=(t,e,a,i)=>{let n;const s=t.state;return null===s.window&&(s.wsize=1<=s.wsize?(s.window.set(e.subarray(a-s.wsize,a),0),s.wnext=0,s.whave=s.wsize):(n=s.wsize-s.wnext,n>i&&(n=i),s.window.set(e.subarray(a-i,a-i+n),s.wnext),(i-=n)?(s.window.set(e.subarray(a-i,a),0),s.wnext=i,s.whave=s.wsize):(s.wnext+=n,s.wnext===s.wsize&&(s.wnext=0),s.whavexe(t,15),inflateInit2:xe,inflate:(t,e)=>{let a,i,n,s,r,o,l,h,d,_,f,c,u,w,m,b,g,p,k,v,y,x,z=0;const A=new Uint8Array(4);let E,R;const Z=new Uint8Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]);if(pe(t)||!t.output||!t.input&&0!==t.avail_in)return _e;a=t.state,16191===a.mode&&(a.mode=16192),r=t.next_out,n=t.output,l=t.avail_out,s=t.next_in,i=t.input,o=t.avail_in,h=a.hold,d=a.bits,_=o,f=l,x=le;t:for(;;)switch(a.mode){case 16180:if(0===a.wrap){a.mode=16192;break}for(;d<16;){if(0===o)break t;o--,h+=i[s++]<>>8&255,a.check=N(a.check,A,2,0),h=0,d=0,a.mode=16181;break}if(a.head&&(a.head.done=!1),!(1&a.wrap)||(((255&h)<<8)+(h>>8))%31){t.msg="incorrect header check",a.mode=me;break}if((15&h)!==we){t.msg="unknown compression method",a.mode=me;break}if(h>>>=4,d-=4,y=8+(15&h),0===a.wbits&&(a.wbits=y),y>15||y>a.wbits){t.msg="invalid window size",a.mode=me;break}a.dmax=1<>8&1),512&a.flags&&4&a.wrap&&(A[0]=255&h,A[1]=h>>>8&255,a.check=N(a.check,A,2,0)),h=0,d=0,a.mode=16182;case 16182:for(;d<32;){if(0===o)break t;o--,h+=i[s++]<>>8&255,A[2]=h>>>16&255,A[3]=h>>>24&255,a.check=N(a.check,A,4,0)),h=0,d=0,a.mode=16183;case 16183:for(;d<16;){if(0===o)break t;o--,h+=i[s++]<>8),512&a.flags&&4&a.wrap&&(A[0]=255&h,A[1]=h>>>8&255,a.check=N(a.check,A,2,0)),h=0,d=0,a.mode=16184;case 16184:if(1024&a.flags){for(;d<16;){if(0===o)break t;o--,h+=i[s++]<>>8&255,a.check=N(a.check,A,2,0)),h=0,d=0}else a.head&&(a.head.extra=null);a.mode=16185;case 16185:if(1024&a.flags&&(c=a.length,c>o&&(c=o),c&&(a.head&&(y=a.head.extra_len-a.length,a.head.extra||(a.head.extra=new Uint8Array(a.head.extra_len)),a.head.extra.set(i.subarray(s,s+c),y)),512&a.flags&&4&a.wrap&&(a.check=N(a.check,i,c,s)),o-=c,s+=c,a.length-=c),a.length))break t;a.length=0,a.mode=16186;case 16186:if(2048&a.flags){if(0===o)break t;c=0;do{y=i[s+c++],a.head&&y&&a.length<65536&&(a.head.name+=String.fromCharCode(y))}while(y&&c>9&1,a.head.done=!0),t.adler=a.check=0,a.mode=16191;break;case 16189:for(;d<32;){if(0===o)break t;o--,h+=i[s++]<>>=7&d,d-=7&d,a.mode=16206;break}for(;d<3;){if(0===o)break t;o--,h+=i[s++]<>>=1,d-=1,3&h){case 0:a.mode=16193;break;case 1:if(Re(a),a.mode=16199,e===oe){h>>>=2,d-=2;break t}break;case 2:a.mode=16196;break;case 3:t.msg="invalid block type",a.mode=me}h>>>=2,d-=2;break;case 16193:for(h>>>=7&d,d-=7&d;d<32;){if(0===o)break t;o--,h+=i[s++]<>>16^65535)){t.msg="invalid stored block lengths",a.mode=me;break}if(a.length=65535&h,h=0,d=0,a.mode=16194,e===oe)break t;case 16194:a.mode=16195;case 16195:if(c=a.length,c){if(c>o&&(c=o),c>l&&(c=l),0===c)break t;n.set(i.subarray(s,s+c),r),o-=c,s+=c,l-=c,r+=c,a.length-=c;break}a.mode=16191;break;case 16196:for(;d<14;){if(0===o)break t;o--,h+=i[s++]<>>=5,d-=5,a.ndist=1+(31&h),h>>>=5,d-=5,a.ncode=4+(15&h),h>>>=4,d-=4,a.nlen>286||a.ndist>30){t.msg="too many length or distance symbols",a.mode=me;break}a.have=0,a.mode=16197;case 16197:for(;a.have>>=3,d-=3}for(;a.have<19;)a.lens[Z[a.have++]]=0;if(a.lencode=a.lendyn,a.lenbits=7,E={bits:a.lenbits},x=ne(0,a.lens,0,19,a.lencode,0,a.work,E),a.lenbits=E.bits,x){t.msg="invalid code lengths set",a.mode=me;break}a.have=0,a.mode=16198;case 16198:for(;a.have>>24,b=z>>>16&255,g=65535&z,!(m<=d);){if(0===o)break t;o--,h+=i[s++]<>>=m,d-=m,a.lens[a.have++]=g;else{if(16===g){for(R=m+2;d>>=m,d-=m,0===a.have){t.msg="invalid bit length repeat",a.mode=me;break}y=a.lens[a.have-1],c=3+(3&h),h>>>=2,d-=2}else if(17===g){for(R=m+3;d>>=m,d-=m,y=0,c=3+(7&h),h>>>=3,d-=3}else{for(R=m+7;d>>=m,d-=m,y=0,c=11+(127&h),h>>>=7,d-=7}if(a.have+c>a.nlen+a.ndist){t.msg="invalid bit length repeat",a.mode=me;break}for(;c--;)a.lens[a.have++]=y}}if(a.mode===me)break;if(0===a.lens[256]){t.msg="invalid code -- missing end-of-block",a.mode=me;break}if(a.lenbits=9,E={bits:a.lenbits},x=ne(1,a.lens,0,a.nlen,a.lencode,0,a.work,E),a.lenbits=E.bits,x){t.msg="invalid literal/lengths set",a.mode=me;break}if(a.distbits=6,a.distcode=a.distdyn,E={bits:a.distbits},x=ne(2,a.lens,a.nlen,a.ndist,a.distcode,0,a.work,E),a.distbits=E.bits,x){t.msg="invalid distances set",a.mode=me;break}if(a.mode=16199,e===oe)break t;case 16199:a.mode=16200;case 16200:if(o>=6&&l>=258){t.next_out=r,t.avail_out=l,t.next_in=s,t.avail_in=o,a.hold=h,a.bits=d,$t(t,f),r=t.next_out,n=t.output,l=t.avail_out,s=t.next_in,i=t.input,o=t.avail_in,h=a.hold,d=a.bits,16191===a.mode&&(a.back=-1);break}for(a.back=0;z=a.lencode[h&(1<>>24,b=z>>>16&255,g=65535&z,!(m<=d);){if(0===o)break t;o--,h+=i[s++]<>p)],m=z>>>24,b=z>>>16&255,g=65535&z,!(p+m<=d);){if(0===o)break t;o--,h+=i[s++]<>>=p,d-=p,a.back+=p}if(h>>>=m,d-=m,a.back+=m,a.length=g,0===b){a.mode=16205;break}if(32&b){a.back=-1,a.mode=16191;break}if(64&b){t.msg="invalid literal/length code",a.mode=me;break}a.extra=15&b,a.mode=16201;case 16201:if(a.extra){for(R=a.extra;d>>=a.extra,d-=a.extra,a.back+=a.extra}a.was=a.length,a.mode=16202;case 16202:for(;z=a.distcode[h&(1<>>24,b=z>>>16&255,g=65535&z,!(m<=d);){if(0===o)break t;o--,h+=i[s++]<>p)],m=z>>>24,b=z>>>16&255,g=65535&z,!(p+m<=d);){if(0===o)break t;o--,h+=i[s++]<>>=p,d-=p,a.back+=p}if(h>>>=m,d-=m,a.back+=m,64&b){t.msg="invalid distance code",a.mode=me;break}a.offset=g,a.extra=15&b,a.mode=16203;case 16203:if(a.extra){for(R=a.extra;d>>=a.extra,d-=a.extra,a.back+=a.extra}if(a.offset>a.dmax){t.msg="invalid distance too far back",a.mode=me;break}a.mode=16204;case 16204:if(0===l)break t;if(c=f-l,a.offset>c){if(c=a.offset-c,c>a.whave&&a.sane){t.msg="invalid distance too far back",a.mode=me;break}c>a.wnext?(c-=a.wnext,u=a.wsize-c):u=a.wnext-c,c>a.length&&(c=a.length),w=a.window}else w=n,u=r-a.offset,c=a.length;c>l&&(c=l),l-=c,a.length-=c;do{n[r++]=w[u++]}while(--c);0===a.length&&(a.mode=16200);break;case 16205:if(0===l)break t;n[r++]=a.length,l--,a.mode=16200;break;case 16206:if(a.wrap){for(;d<32;){if(0===o)break t;o--,h|=i[s++]<{if(pe(t))return _e;let e=t.state;return e.window&&(e.window=null),t.state=null,le},inflateGetHeader:(t,e)=>{if(pe(t))return _e;const a=t.state;return 0==(2&a.wrap)?_e:(a.head=e,e.done=!1,le)},inflateSetDictionary:(t,e)=>{const a=e.length;let i,n,s;return pe(t)?_e:(i=t.state,0!==i.wrap&&16190!==i.mode?_e:16190===i.mode&&(n=1,n=F(n,e,a,0),n!==i.check)?fe:(s=Ze(t,e,a,a),s?(i.mode=16210,ce):(i.havedict=1,le)))},inflateInfo:"pako inflate (from Nodeca project)"};var Se=function(){this.text=0,this.time=0,this.xflags=0,this.os=0,this.extra=null,this.extra_len=0,this.name="",this.comment="",this.hcrc=0,this.done=!1};const De=Object.prototype.toString,{Z_NO_FLUSH:Te,Z_FINISH:Oe,Z_OK:Fe,Z_STREAM_END:Le,Z_NEED_DICT:Ne,Z_STREAM_ERROR:Ie,Z_DATA_ERROR:Be,Z_MEM_ERROR:Ce}=B;function He(t){this.options=Tt({chunkSize:65536,windowBits:15,to:""},t||{});const e=this.options;e.raw&&e.windowBits>=0&&e.windowBits<16&&(e.windowBits=-e.windowBits,0===e.windowBits&&(e.windowBits=-15)),!(e.windowBits>=0&&e.windowBits<16)||t&&t.windowBits||(e.windowBits+=32),e.windowBits>15&&e.windowBits<48&&0==(15&e.windowBits)&&(e.windowBits|=15),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new Ct,this.strm.avail_out=0;let a=Ue.inflateInit2(this.strm,e.windowBits);if(a!==Fe)throw new Error(I[a]);if(this.header=new Se,Ue.inflateGetHeader(this.strm,this.header),e.dictionary&&("string"==typeof e.dictionary?e.dictionary=Nt(e.dictionary):"[object ArrayBuffer]"===De.call(e.dictionary)&&(e.dictionary=new Uint8Array(e.dictionary)),e.raw&&(a=Ue.inflateSetDictionary(this.strm,e.dictionary),a!==Fe)))throw new Error(I[a])}He.prototype.push=function(t,e){const a=this.strm,i=this.options.chunkSize,n=this.options.dictionary;let s,r,o;if(this.ended)return!1;for(r=e===~~e?e:!0===e?Oe:Te,"[object ArrayBuffer]"===De.call(t)?a.input=new Uint8Array(t):a.input=t,a.next_in=0,a.avail_in=a.input.length;;){for(0===a.avail_out&&(a.output=new Uint8Array(i),a.next_out=0,a.avail_out=i),s=Ue.inflate(a,r),s===Ne&&n&&(s=Ue.inflateSetDictionary(a,n),s===Fe?s=Ue.inflate(a,r):s===Be&&(s=Ne));a.avail_in>0&&s===Le&&a.state.wrap>0&&0!==t[a.next_in];)Ue.inflateReset(a),s=Ue.inflate(a,r);switch(s){case Ie:case Be:case Ne:case Ce:return this.onEnd(s),this.ended=!0,!1}if(o=a.avail_out,a.next_out&&(0===a.avail_out||s===Le))if("string"===this.options.to){let t=Bt(a.output,a.next_out),e=a.next_out-t,n=It(a.output,t);a.next_out=e,a.avail_out=i-e,e&&a.output.set(a.output.subarray(t,t+e),0),this.onData(n)}else this.onData(a.output.length===a.next_out?a.output:a.output.subarray(0,a.next_out));if(s!==Fe||0!==o){if(s===Le)return s=Ue.inflateEnd(this.strm),this.onEnd(s),this.ended=!0,!0;if(0===a.avail_in)break}}return!0},He.prototype.onData=function(t){this.chunks.push(t)},He.prototype.onEnd=function(t){t===Fe&&("string"===this.options.to?this.result=this.chunks.join(""):this.result=Ot(this.chunks)),this.chunks=[],this.err=t,this.msg=this.strm.msg};const{Deflate:Me,deflate:je,deflateRaw:Ke,gzip:Pe}=Vt;var Ye=Me,Ge=B;const Xe=new class{constructor(){this._init()}clear(){this._init()}addEvent(t){if(!t)throw new Error("Adding invalid event");const e=this._hasEvents?",":"";this.deflate.push(e+t,Ge.Z_SYNC_FLUSH),this._hasEvents=!0}finish(){if(this.deflate.push("]",Ge.Z_FINISH),this.deflate.err)throw this.deflate.err;const t=this.deflate.result;return this._init(),t}_init(){this._hasEvents=!1,this.deflate=new Ye,this.deflate.push("[",Ge.Z_NO_FLUSH)}},We={clear:()=>(Xe.clear(),""),addEvent:t=>Xe.addEvent(t),finish:()=>Xe.finish()};addEventListener("message",(function(t){const e=t.data.method,a=t.data.id,i=t.data.arg;if(e in We&&"function"==typeof We[e])try{const t=We[e](i);postMessage({id:a,method:e,success:!0,response:t})}catch(t){postMessage({id:a,method:e,success:!1,response:t.message}),console.error(t)}})),postMessage({id:void 0,method:"init",success:!0,response:void 0});`; diff --git a/packages/replay/test/integration/rateLimiting.test.ts b/packages/replay/test/integration/rateLimiting.test.ts index 379efb9b430d..ad8f810604b2 100644 --- a/packages/replay/test/integration/rateLimiting.test.ts +++ b/packages/replay/test/integration/rateLimiting.test.ts @@ -168,7 +168,7 @@ describe('Integration | rate-limiting behaviour', () => { expect(replay).toHaveLastSentReplay({ recordingData: JSON.stringify([TEST_EVENT3]) }); // events array should be empty - expect(replay.eventBuffer?.pendingLength).toBe(0); + expect(replay.eventBuffer?.hasEvents).toBe(false); }, ); @@ -253,7 +253,7 @@ describe('Integration | rate-limiting behaviour', () => { expect(replay).toHaveLastSentReplay({ recordingData: JSON.stringify([TEST_EVENT3]) }); // events array should be empty - expect(replay.eventBuffer?.pendingLength).toBe(0); + expect(replay.eventBuffer?.hasEvents).toBe(false); }); it("doesn't do anything, if a rate limit is hit and recording is already paused", async () => { diff --git a/packages/replay/test/integration/sendReplayEvent.test.ts b/packages/replay/test/integration/sendReplayEvent.test.ts index 2a9130e7bb45..3263e1a09772 100644 --- a/packages/replay/test/integration/sendReplayEvent.test.ts +++ b/packages/replay/test/integration/sendReplayEvent.test.ts @@ -106,7 +106,7 @@ describe('Integration | sendReplayEvent', () => { expect(replay.session?.segmentId).toBe(1); // events array should be empty - expect(replay.eventBuffer?.pendingLength).toBe(0); + expect(replay.eventBuffer?.hasEvents).toBe(false); }); it('update last activity when user clicks mouse', async () => { @@ -145,7 +145,7 @@ describe('Integration | sendReplayEvent', () => { expect(replay.session?.segmentId).toBe(1); // events array should be empty - expect(replay.eventBuffer?.pendingLength).toBe(0); + expect(replay.eventBuffer?.hasEvents).toBe(false); }); it('uploads a replay event if maxFlushDelay is set 15 seconds have elapsed since the last replay upload', async () => { @@ -173,7 +173,7 @@ describe('Integration | sendReplayEvent', () => { expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP); expect(replay.session?.segmentId).toBe(1); // events array should be empty - expect(replay.eventBuffer?.pendingLength).toBe(0); + expect(replay.eventBuffer?.hasEvents).toBe(false); // Let's make sure it continues to work mockTransportSend.mockClear(); @@ -218,7 +218,7 @@ describe('Integration | sendReplayEvent', () => { // Session's last activity should not be updated expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP); // events array should be empty - expect(replay.eventBuffer?.pendingLength).toBe(0); + expect(replay.eventBuffer?.hasEvents).toBe(false); }); it('uploads a replay event when document becomes hidden', async () => { @@ -246,7 +246,7 @@ describe('Integration | sendReplayEvent', () => { // visibilitystate as user being active expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP); // events array should be empty - expect(replay.eventBuffer?.pendingLength).toBe(0); + expect(replay.eventBuffer?.hasEvents).toBe(false); }); it('uploads a dom breadcrumb 5 seconds after listener receives an event', async () => { diff --git a/packages/replay/test/integration/stop.test.ts b/packages/replay/test/integration/stop.test.ts index 651a21f7e5bc..7e091bc7b49b 100644 --- a/packages/replay/test/integration/stop.test.ts +++ b/packages/replay/test/integration/stop.test.ts @@ -128,17 +128,17 @@ describe('Integration | stop', () => { it('does not buffer events when stopped', async function () { WINDOW.dispatchEvent(new Event('blur')); - expect(replay.eventBuffer?.pendingLength).toBe(1); + expect(replay.eventBuffer?.hasEvents).toBe(true); // stop replays integration.stop(); - expect(replay.eventBuffer?.pendingLength).toBe(undefined); + expect(replay.eventBuffer).toBe(null); WINDOW.dispatchEvent(new Event('blur')); await new Promise(process.nextTick); - expect(replay.eventBuffer?.pendingLength).toBe(undefined); + expect(replay.eventBuffer).toBe(null); expect(replay).not.toHaveLastSentReplay(); }); diff --git a/packages/replay/test/unit/eventBuffer.test.ts b/packages/replay/test/unit/eventBuffer.test.ts deleted file mode 100644 index b9c96b225cc3..000000000000 --- a/packages/replay/test/unit/eventBuffer.test.ts +++ /dev/null @@ -1,237 +0,0 @@ -import 'jsdom-worker'; - -import pako from 'pako'; - -import { EventBufferProxy } from '../../src/eventBuffer/EventBufferProxy'; -import { createEventBuffer } from './../../src/eventBuffer'; -import { BASE_TIMESTAMP } from './../index'; - -const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 3 }; -describe('Unit | eventBuffer', () => { - describe('EventBufferArray', () => { - it('adds events to normal event buffer', async function () { - const buffer = createEventBuffer({ useCompression: false }); - - buffer.addEvent(TEST_EVENT); - buffer.addEvent(TEST_EVENT); - - const result = await buffer.finish(); - - expect(result).toEqual(JSON.stringify([TEST_EVENT, TEST_EVENT])); - }); - - it('adds checkout event to normal event buffer', async function () { - const buffer = createEventBuffer({ useCompression: false }); - - buffer.addEvent(TEST_EVENT); - buffer.addEvent(TEST_EVENT); - buffer.addEvent(TEST_EVENT); - - buffer.addEvent(TEST_EVENT, true); - const result = await buffer.finish(); - - expect(result).toEqual(JSON.stringify([TEST_EVENT])); - }); - - it('calling `finish()` multiple times does not result in duplicated events', async function () { - const buffer = createEventBuffer({ useCompression: false }); - - buffer.addEvent(TEST_EVENT); - - const promise1 = buffer.finish(); - const promise2 = buffer.finish(); - - const result1 = (await promise1) as Uint8Array; - const result2 = (await promise2) as Uint8Array; - - expect(result1).toEqual(JSON.stringify([TEST_EVENT])); - expect(result2).toEqual(JSON.stringify([])); - }); - }); - - describe('EventBufferCompressionWorker', () => { - it('adds events to event buffer with compression worker', async function () { - const buffer = createEventBuffer({ - useCompression: true, - }) as EventBufferProxy; - - expect(buffer).toBeInstanceOf(EventBufferProxy); - - // Ensure worker is ready - await buffer.ensureWorkerIsLoaded(); - - buffer.addEvent(TEST_EVENT); - buffer.addEvent(TEST_EVENT); - - expect(buffer.pendingEvents).toEqual([TEST_EVENT, TEST_EVENT]); - - const result = await buffer.finish(); - expect(result).toBeInstanceOf(Uint8Array); - const restored = pako.inflate(result as Uint8Array, { to: 'string' }); - - expect(restored).toEqual(JSON.stringify([TEST_EVENT, TEST_EVENT])); - }); - - it('adds checkout events to event buffer with compression worker', async function () { - const buffer = createEventBuffer({ - useCompression: true, - }) as EventBufferProxy; - - expect(buffer).toBeInstanceOf(EventBufferProxy); - - // Ensure worker is ready - await buffer.ensureWorkerIsLoaded(); - - await buffer.addEvent(TEST_EVENT); - await buffer.addEvent(TEST_EVENT); - - // This should clear previous buffer - await buffer.addEvent({ ...TEST_EVENT, type: 2 }, true); - - const result = await buffer.finish(); - expect(result).toBeInstanceOf(Uint8Array); - const restored = pako.inflate(result as Uint8Array, { to: 'string' }); - - expect(restored).toEqual(JSON.stringify([{ ...TEST_EVENT, type: 2 }])); - }); - - it('calling `finish()` multiple times does not result in duplicated events', async function () { - const buffer = createEventBuffer({ - useCompression: true, - }) as EventBufferProxy; - - expect(buffer).toBeInstanceOf(EventBufferProxy); - - // Ensure worker is ready - await buffer.ensureWorkerIsLoaded(); - - buffer.addEvent(TEST_EVENT); - - const promise1 = buffer.finish(); - const promise2 = buffer.finish(); - - const result1 = (await promise1) as Uint8Array; - const result2 = (await promise2) as Uint8Array; - const restored1 = pako.inflate(result1, { to: 'string' }); - const restored2 = pako.inflate(result2, { to: 'string' }); - - expect(restored1).toEqual(JSON.stringify([TEST_EVENT])); - expect(restored2).toEqual(JSON.stringify([])); - }); - - it('calling `finish()` multiple times, with events in between, does not result in duplicated or dropped events', async function () { - const buffer = createEventBuffer({ - useCompression: true, - }) as EventBufferProxy; - - expect(buffer).toBeInstanceOf(EventBufferProxy); - - // Ensure worker is ready - await buffer.ensureWorkerIsLoaded(); - - buffer.addEvent(TEST_EVENT); - - const promise1 = buffer.finish(); - await new Promise(process.nextTick); - - buffer.addEvent({ ...TEST_EVENT, type: 5 }); - const promise2 = buffer.finish(); - - const result1 = (await promise1) as Uint8Array; - const result2 = (await promise2) as Uint8Array; - - const restored1 = pako.inflate(result1, { to: 'string' }); - const restored2 = pako.inflate(result2, { to: 'string' }); - - expect(restored1).toEqual(JSON.stringify([TEST_EVENT])); - expect(restored2).toEqual(JSON.stringify([{ ...TEST_EVENT, type: 5 }])); - }); - - it('handles an error when compressing the payload', async function () { - const buffer = createEventBuffer({ - useCompression: true, - }) as EventBufferProxy; - - expect(buffer).toBeInstanceOf(EventBufferProxy); - - // Ensure worker is ready - await buffer.ensureWorkerIsLoaded(); - - buffer.addEvent(TEST_EVENT); - buffer.addEvent(TEST_EVENT); - - // @ts-ignore Mock this private so it triggers an error - const postMessageSpy = jest.spyOn(buffer._compression, '_postMessage').mockImplementation(() => { - return Promise.reject('test worker error'); - }); - - const result = await buffer.finish(); - - expect(postMessageSpy).toHaveBeenCalledTimes(1); - - expect(result).toEqual(JSON.stringify([TEST_EVENT, TEST_EVENT])); - }); - }); - - describe('EventBufferProxy fallback', () => { - let consoleErrorSpy: jest.SpyInstance; - - beforeEach(() => { - // Avoid logging errors to console - consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); - }); - - afterEach(() => { - consoleErrorSpy.mockRestore(); - }); - - it('waits for the worker to be loaded when calling finish', async function () { - const buffer = createEventBuffer({ - useCompression: true, - }) as EventBufferProxy; - - expect(buffer).toBeInstanceOf(EventBufferProxy); - - buffer.addEvent(TEST_EVENT); - buffer.addEvent(TEST_EVENT); - - expect(buffer.pendingEvents).toEqual([TEST_EVENT, TEST_EVENT]); - - const result = await buffer.finish(); - expect(result).toBeInstanceOf(Uint8Array); - const restored = pako.inflate(result as Uint8Array, { to: 'string' }); - expect(restored).toEqual(JSON.stringify([TEST_EVENT, TEST_EVENT])); - }); - - it('keeps using simple buffer if worker cannot be loaded', async function () { - const workerString = 'window.triggerBlaError();'; - const workerBlob = new Blob([workerString]); - const workerUrl = URL.createObjectURL(workerBlob); - const worker = new Worker(workerUrl); - const buffer = new EventBufferProxy(worker); - - buffer.addEvent(TEST_EVENT); - buffer.addEvent(TEST_EVENT); - - expect(buffer.pendingEvents).toEqual([TEST_EVENT, TEST_EVENT]); - - // Finish before the worker is loaded - const result = await buffer.finish(); - expect(typeof result).toBe('string'); - expect(result).toEqual(JSON.stringify([TEST_EVENT, TEST_EVENT])); - - // Now actually finish loading the worker - which triggers an error - await buffer.ensureWorkerIsLoaded(); - - buffer.addEvent(TEST_EVENT); - buffer.addEvent(TEST_EVENT); - buffer.addEvent(TEST_EVENT); - - expect(buffer.pendingEvents).toEqual([TEST_EVENT, TEST_EVENT, TEST_EVENT]); - - const result2 = await buffer.finish(); - expect(typeof result2).toBe('string'); - expect(result2).toEqual(JSON.stringify([TEST_EVENT, TEST_EVENT, TEST_EVENT])); - }); - }); -}); diff --git a/packages/replay/test/unit/eventBuffer/EventBufferArray.test.ts b/packages/replay/test/unit/eventBuffer/EventBufferArray.test.ts new file mode 100644 index 000000000000..36d0b5aa0e00 --- /dev/null +++ b/packages/replay/test/unit/eventBuffer/EventBufferArray.test.ts @@ -0,0 +1,45 @@ +import { createEventBuffer } from './../../../src/eventBuffer'; +import { BASE_TIMESTAMP } from './../../index'; + +const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 3 }; + +describe('Unit | eventBuffer | EventBufferArray', () => { + it('adds events to normal event buffer', async function () { + const buffer = createEventBuffer({ useCompression: false }); + + buffer.addEvent(TEST_EVENT); + buffer.addEvent(TEST_EVENT); + + const result = await buffer.finish(); + + expect(result).toEqual(JSON.stringify([TEST_EVENT, TEST_EVENT])); + }); + + it('adds checkout event to normal event buffer', async function () { + const buffer = createEventBuffer({ useCompression: false }); + + buffer.addEvent(TEST_EVENT); + buffer.addEvent(TEST_EVENT); + buffer.addEvent(TEST_EVENT); + + buffer.addEvent(TEST_EVENT, true); + const result = await buffer.finish(); + + expect(result).toEqual(JSON.stringify([TEST_EVENT])); + }); + + it('calling `finish()` multiple times does not result in duplicated events', async function () { + const buffer = createEventBuffer({ useCompression: false }); + + buffer.addEvent(TEST_EVENT); + + const promise1 = buffer.finish(); + const promise2 = buffer.finish(); + + const result1 = (await promise1) as Uint8Array; + const result2 = (await promise2) as Uint8Array; + + expect(result1).toEqual(JSON.stringify([TEST_EVENT])); + expect(result2).toEqual(JSON.stringify([])); + }); +}); diff --git a/packages/replay/test/unit/eventBuffer/EventBufferCompressionWorker.test.ts b/packages/replay/test/unit/eventBuffer/EventBufferCompressionWorker.test.ts new file mode 100644 index 000000000000..1e22716b553a --- /dev/null +++ b/packages/replay/test/unit/eventBuffer/EventBufferCompressionWorker.test.ts @@ -0,0 +1,148 @@ +import 'jsdom-worker'; + +import pako from 'pako'; + +import { BASE_TIMESTAMP } from '../..'; +import { EventBufferProxy } from '../../../src/eventBuffer/EventBufferProxy'; +import { createEventBuffer } from './../../../src/eventBuffer'; + +const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 3 }; + +describe('Unit | eventBuffer | EventBufferCompressionWorker', () => { + it('adds events to event buffer with compression worker', async function () { + const buffer = createEventBuffer({ + useCompression: true, + }) as EventBufferProxy; + + expect(buffer).toBeInstanceOf(EventBufferProxy); + + // Ensure worker is ready + await buffer.ensureWorkerIsLoaded(); + + buffer.addEvent(TEST_EVENT); + buffer.addEvent(TEST_EVENT); + + const result = await buffer.finish(); + expect(result).toBeInstanceOf(Uint8Array); + const restored = pako.inflate(result as Uint8Array, { to: 'string' }); + + expect(restored).toEqual(JSON.stringify([TEST_EVENT, TEST_EVENT])); + }); + + it('adds checkout events to event buffer with compression worker', async function () { + const buffer = createEventBuffer({ + useCompression: true, + }) as EventBufferProxy; + + expect(buffer).toBeInstanceOf(EventBufferProxy); + + // Ensure worker is ready + await buffer.ensureWorkerIsLoaded(); + + await buffer.addEvent(TEST_EVENT); + await buffer.addEvent(TEST_EVENT); + + // This should clear previous buffer + await buffer.addEvent({ ...TEST_EVENT, type: 2 }, true); + + const result = await buffer.finish(); + expect(result).toBeInstanceOf(Uint8Array); + const restored = pako.inflate(result as Uint8Array, { to: 'string' }); + + expect(restored).toEqual(JSON.stringify([{ ...TEST_EVENT, type: 2 }])); + }); + + it('calling `finish()` multiple times does not result in duplicated events', async function () { + const buffer = createEventBuffer({ + useCompression: true, + }) as EventBufferProxy; + + expect(buffer).toBeInstanceOf(EventBufferProxy); + + // Ensure worker is ready + await buffer.ensureWorkerIsLoaded(); + + buffer.addEvent(TEST_EVENT); + + const promise1 = buffer.finish(); + const promise2 = buffer.finish(); + + const result1 = (await promise1) as Uint8Array; + const result2 = (await promise2) as Uint8Array; + const restored1 = pako.inflate(result1, { to: 'string' }); + const restored2 = pako.inflate(result2, { to: 'string' }); + + expect(restored1).toEqual(JSON.stringify([TEST_EVENT])); + expect(restored2).toEqual(JSON.stringify([])); + }); + + it('calling `finish()` multiple times, with events in between, does not result in duplicated or dropped events', async function () { + const buffer = createEventBuffer({ + useCompression: true, + }) as EventBufferProxy; + + expect(buffer).toBeInstanceOf(EventBufferProxy); + + // Ensure worker is ready + await buffer.ensureWorkerIsLoaded(); + + buffer.addEvent(TEST_EVENT); + + const promise1 = buffer.finish(); + await new Promise(process.nextTick); + + buffer.addEvent({ ...TEST_EVENT, type: 5 }); + const promise2 = buffer.finish(); + + const result1 = (await promise1) as Uint8Array; + const result2 = (await promise2) as Uint8Array; + + const restored1 = pako.inflate(result1, { to: 'string' }); + const restored2 = pako.inflate(result2, { to: 'string' }); + + expect(restored1).toEqual(JSON.stringify([TEST_EVENT])); + expect(restored2).toEqual(JSON.stringify([{ ...TEST_EVENT, type: 5 }])); + }); + + it('handles an error when compressing the payload', async function () { + const buffer = createEventBuffer({ + useCompression: true, + }) as EventBufferProxy; + + expect(buffer).toBeInstanceOf(EventBufferProxy); + + // Ensure worker is ready + await buffer.ensureWorkerIsLoaded(); + + await buffer.addEvent(TEST_EVENT); + await buffer.addEvent(TEST_EVENT); + + // @ts-ignore Mock this private so it triggers an error + jest.spyOn(buffer._compression._worker, 'postMessage').mockImplementationOnce(() => { + return Promise.reject('test worker error'); + }); + + await expect(() => buffer.finish()).rejects.toBeDefined(); + }); + + it('handles an error when adding an event', async function () { + const buffer = createEventBuffer({ + useCompression: true, + }) as EventBufferProxy; + + expect(buffer).toBeInstanceOf(EventBufferProxy); + + // Ensure worker is ready + await buffer.ensureWorkerIsLoaded(); + + await buffer.addEvent({ data: { o: 1 }, timestamp: BASE_TIMESTAMP, type: 3 }, true); + await buffer.addEvent({ data: { o: 2 }, timestamp: BASE_TIMESTAMP, type: 3 }); + + // @ts-ignore Mock this private so it triggers an error + jest.spyOn(buffer._compression._worker, 'postMessage').mockImplementationOnce(() => { + return Promise.reject('test worker error'); + }); + + await expect(() => buffer.addEvent({ data: { o: 3 }, timestamp: BASE_TIMESTAMP, type: 3 })).rejects.toBeDefined(); + }); +}); diff --git a/packages/replay/test/unit/eventBuffer/EventBufferProxy.test.ts b/packages/replay/test/unit/eventBuffer/EventBufferProxy.test.ts new file mode 100644 index 000000000000..c4a98f3c446e --- /dev/null +++ b/packages/replay/test/unit/eventBuffer/EventBufferProxy.test.ts @@ -0,0 +1,65 @@ +import 'jsdom-worker'; + +import pako from 'pako'; + +import { BASE_TIMESTAMP } from '../..'; +import { EventBufferProxy } from '../../../src/eventBuffer/EventBufferProxy'; +import { createEventBuffer } from './../../../src/eventBuffer'; + +const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 3 }; + +describe('Unit | eventBuffer | EventBufferProxy', () => { + let consoleErrorSpy: jest.SpyInstance; + + beforeEach(() => { + // Avoid logging errors to console + consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + }); + + afterEach(() => { + consoleErrorSpy.mockRestore(); + }); + + it('waits for the worker to be loaded when calling finish', async function () { + const buffer = createEventBuffer({ + useCompression: true, + }) as EventBufferProxy; + + expect(buffer).toBeInstanceOf(EventBufferProxy); + + buffer.addEvent(TEST_EVENT); + buffer.addEvent(TEST_EVENT); + + const result = await buffer.finish(); + expect(result).toBeInstanceOf(Uint8Array); + const restored = pako.inflate(result as Uint8Array, { to: 'string' }); + expect(restored).toEqual(JSON.stringify([TEST_EVENT, TEST_EVENT])); + }); + + it('keeps using simple buffer if worker cannot be loaded', async function () { + const workerString = 'window.triggerBlaError();'; + const workerBlob = new Blob([workerString]); + const workerUrl = URL.createObjectURL(workerBlob); + const worker = new Worker(workerUrl); + const buffer = new EventBufferProxy(worker); + + buffer.addEvent(TEST_EVENT); + buffer.addEvent(TEST_EVENT); + + // Finish before the worker is loaded + const result = await buffer.finish(); + expect(typeof result).toBe('string'); + expect(result).toEqual(JSON.stringify([TEST_EVENT, TEST_EVENT])); + + // Now actually finish loading the worker - which triggers an error + await buffer.ensureWorkerIsLoaded(); + + buffer.addEvent(TEST_EVENT); + buffer.addEvent(TEST_EVENT); + buffer.addEvent(TEST_EVENT); + + const result2 = await buffer.finish(); + expect(typeof result2).toBe('string'); + expect(result2).toEqual(JSON.stringify([TEST_EVENT, TEST_EVENT, TEST_EVENT])); + }); +}); diff --git a/packages/replay/test/unit/util/addEvent.test.ts b/packages/replay/test/unit/util/addEvent.test.ts new file mode 100644 index 000000000000..6cc2e6b6ffdf --- /dev/null +++ b/packages/replay/test/unit/util/addEvent.test.ts @@ -0,0 +1,32 @@ +import 'jsdom-worker'; + +import { BASE_TIMESTAMP } from '../..'; +import type { EventBufferProxy } from '../../../src/eventBuffer/EventBufferProxy'; +import { addEvent } from '../../../src/util/addEvent'; +import { setupReplayContainer } from '../../utils/setupReplayContainer'; +import { useFakeTimers } from '../../utils/use-fake-timers'; + +useFakeTimers(); + +describe('Unit | util | addEvent', () => { + it('stops when encountering a compression error', async function () { + jest.setSystemTime(BASE_TIMESTAMP); + + const replay = setupReplayContainer({ + options: { + useCompression: true, + }, + }); + + await (replay.eventBuffer as EventBufferProxy).ensureWorkerIsLoaded(); + + // @ts-ignore Mock this private so it triggers an error + jest.spyOn(replay.eventBuffer._compression._worker, 'postMessage').mockImplementationOnce(() => { + return Promise.reject('test worker error'); + }); + + await addEvent(replay, { data: {}, timestamp: BASE_TIMESTAMP + 10, type: 2 }); + + expect(replay.isEnabled()).toEqual(false); + }); +}); diff --git a/packages/replay/test/unit/worker/Compressor.test.ts b/packages/replay/test/unit/worker/Compressor.test.ts index e8de4bd2f94a..e701ce2d79ff 100644 --- a/packages/replay/test/unit/worker/Compressor.test.ts +++ b/packages/replay/test/unit/worker/Compressor.test.ts @@ -17,7 +17,7 @@ describe('Unit | worker | Compressor', () => { }, ]; - events.forEach(event => compressor.addEvent(event)); + events.forEach(event => compressor.addEvent(JSON.stringify(event))); const compressed = compressor.finish(); diff --git a/packages/replay/test/utils/setupReplayContainer.ts b/packages/replay/test/utils/setupReplayContainer.ts new file mode 100644 index 000000000000..fd6a5baa40e0 --- /dev/null +++ b/packages/replay/test/utils/setupReplayContainer.ts @@ -0,0 +1,38 @@ +import { SESSION_IDLE_DURATION } from '../../src/constants'; +import { createEventBuffer } from '../../src/eventBuffer'; +import { ReplayContainer } from '../../src/replay'; +import type { RecordingOptions, ReplayPluginOptions } from '../../src/types'; +import { clearSession } from './clearSession'; + +export function setupReplayContainer({ + options, + recordingOptions, +}: { options?: Partial; recordingOptions?: Partial } = {}): ReplayContainer { + const replay = new ReplayContainer({ + options: { + flushMinDelay: 100, + flushMaxDelay: 100, + stickySession: false, + sessionSampleRate: 0, + errorSampleRate: 1, + useCompression: false, + maskAllText: true, + blockAllMedia: true, + _experiments: {}, + ...options, + }, + recordingOptions: { + ...recordingOptions, + }, + }); + + clearSession(replay); + replay['_setInitialState'](); + replay['_loadAndCheckSession'](SESSION_IDLE_DURATION); + replay['_isEnabled'] = true; + replay.eventBuffer = createEventBuffer({ + useCompression: options?.useCompression || false, + }); + + return replay; +} diff --git a/packages/replay/worker/src/Compressor.ts b/packages/replay/worker/src/Compressor.ts index a70eb51422ad..5eb6f5fe4953 100644 --- a/packages/replay/worker/src/Compressor.ts +++ b/packages/replay/worker/src/Compressor.ts @@ -7,37 +7,31 @@ export class Compressor { public deflate: Deflate; /** - * Number of added events + * If any events have been added. */ - public added: number = 0; + private _hasEvents: boolean; public constructor() { - this.init(); + this._init(); } - public init(): void { - this.added = 0; - this.deflate = new Deflate(); - - // Fake an array by adding a `[` - this.deflate.push('[', constants.Z_NO_FLUSH); - - return; + public clear(): void { + this._init(); } - public addEvent(data: Record): void { + public addEvent(data: string): void { if (!data) { throw new Error('Adding invalid event'); } // If the event is not the first event, we need to prefix it with a `,` so // that we end up with a list of events - const prefix = this.added > 0 ? ',' : ''; + const prefix = this._hasEvents ? ',' : ''; // TODO: We may want Z_SYNC_FLUSH or Z_FULL_FLUSH (not sure the difference) // Using NO_FLUSH here for now as we can create many attachments that our // web UI will get API rate limited. - this.deflate.push(prefix + JSON.stringify(data), constants.Z_SYNC_FLUSH); + this.deflate.push(prefix + data, constants.Z_SYNC_FLUSH); - this.added++; + this._hasEvents = true; } public finish(): Uint8Array { @@ -52,8 +46,16 @@ export class Compressor { // result const result = this.deflate.result; - this.init(); + this._init(); return result; } + + private _init(): void { + this._hasEvents = false; + this.deflate = new Deflate(); + + // Fake an array by adding a `[` + this.deflate.push('[', constants.Z_NO_FLUSH); + } } diff --git a/packages/replay/worker/src/handleMessage.ts b/packages/replay/worker/src/handleMessage.ts index 0dd9e871c972..95fcb1256f11 100644 --- a/packages/replay/worker/src/handleMessage.ts +++ b/packages/replay/worker/src/handleMessage.ts @@ -4,18 +4,18 @@ import { Compressor } from './Compressor'; const compressor = new Compressor(); interface Handlers { - init: () => void; - addEvent: (data: Record) => void; + clear: () => void; + addEvent: (data: string) => void; finish: () => void; } const handlers: Handlers = { - init: () => { - compressor.init(); + clear: () => { + compressor.clear(); return ''; }, - addEvent: (data: Record) => { + addEvent: (data: string) => { return compressor.addEvent(data); }, @@ -27,7 +27,7 @@ const handlers: Handlers = { export function handleMessage(e: MessageEvent): void { const method = e.data.method as string; const id = e.data.id as number; - const [data] = e.data.args ? JSON.parse(e.data.args) : []; + const data = e.data.arg as string; // @ts-ignore this syntax is actually fine if (method in handlers && typeof handlers[method] === 'function') { diff --git a/yarn.lock b/yarn.lock index be9e39c8a41d..b49a1981ca0e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3753,17 +3753,17 @@ semver "7.3.2" semver-intersect "1.4.0" -"@sentry-internal/rrweb-snapshot@1.100.1": - version "1.100.1" - resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb-snapshot/-/rrweb-snapshot-1.100.1.tgz#ee6bc41658a3915223790e19a6c1e12115bf5094" - integrity sha512-xg1FIDWOppxwXaZoz5vGR1s/YFMgxUAXHB+O8x8U+4r+3DdrIdm1YBKt5EoqbK5TGxrCvEIPGcpp5f1NlL20wg== +"@sentry-internal/rrweb-snapshot@1.100.2": + version "1.100.2" + resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb-snapshot/-/rrweb-snapshot-1.100.2.tgz#982b239d67872da5eb31d7d466815d4a05680450" + integrity sha512-6EvrPBPTlSD21lhDX20gxN3hnU4xj5hMLO5K4F0B1kPNVKXs1Mtf1nbpHYoIyU/67fZKmRuw/qlCjZykecXXBQ== -"@sentry-internal/rrweb@1.100.1": - version "1.100.1" - resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb/-/rrweb-1.100.1.tgz#1dcfd3f8fe45567efb936e8279268736e8946ace" - integrity sha512-CKRgPmWxwKYltY0RChDpbBrSwzkusUJ7CCdiNTV7cF+pIuC2VTifjAT75nflFzasB36KDoq8ZcdlBtKjFHLjMA== +"@sentry-internal/rrweb@1.100.2": + version "1.100.2" + resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb/-/rrweb-1.100.2.tgz#3403161925d0e4778d58033ba93766b6d261736a" + integrity sha512-MtB2fpmc7XHCwS6JdjAEJsKO/0C/VmFxI1I4o+qIjWHOEpOF1nBYZ/RSE1BadEwgGjWlU8Hp0eWUBvSBDTF8sg== dependencies: - "@sentry-internal/rrweb-snapshot" "1.100.1" + "@sentry-internal/rrweb-snapshot" "1.100.2" "@types/css-font-loading-module" "0.0.7" "@xstate/fsm" "^1.4.0" base64-arraybuffer "^1.0.1"