diff --git a/next.config.js b/next.config.js index 3dd7ef1..2957172 100644 --- a/next.config.js +++ b/next.config.js @@ -1,4 +1,62 @@ +// @ts-check + +import withBundleAnalyzerConfig from "@next/bundle-analyzer"; +import { withSentryConfig } from "@sentry/nextjs"; + /** @type {import('next').NextConfig} */ -module.exports = { +const nextConfig = { reactStrictMode: true, + experimental: { + optimizePackageImports: [ + "@sentry/nextjs", + "@sentry/node", + "usehooks-ts", + "@saleor/app-sdk", + "@trpc/server", + "@trpc/client", + "@trpc/react-query", + "@trpc/next", + "jotai", + "@saleor/apps-shared", + ], + }, + /* + * Ignore opentelemetry warnings - https://github.com/open-telemetry/opentelemetry-js/issues/4173 + * Remove when https://github.com/open-telemetry/opentelemetry-js/pull/4660 is released + */ + /** @type {import('next').NextConfig['webpack']} */ + webpack: (config, { isServer }) => { + if (isServer) { + config.ignoreWarnings = [{ module: /opentelemetry/ }]; + } + return config; + }, }; + +const configWithSentry = withSentryConfig( + nextConfig, + { + org: process.env.SENTRY_ORG, + project: process.env.SENTRY_PROJECT, + silent: true, + }, + { + hideSourceMaps: true, + widenClientFileUpload: true, + disableLogger: true, + transpileClientSDK: true, + tunnelRoute: "/monitoring", + }, +); + +const withBundleAnalyzer = withBundleAnalyzerConfig({ + enabled: process.env.ANALYZE_BUNDLE === "true", +}); + +const isSentryPropertiesInEnvironment = + process.env.SENTRY_AUTH_TOKEN && process.env.SENTRY_PROJECT && process.env.SENTRY_ORG; + +const config = isSentryPropertiesInEnvironment ? configWithSentry : nextConfig; + +// @ts-expect-error bundle analyzer requires NextConfig when Sentry is returning NextConfigFunction | NextConfigObject +export default withBundleAnalyzer(config); diff --git a/package.json b/package.json index 5bab708..2e9e60d 100644 --- a/package.json +++ b/package.json @@ -18,9 +18,23 @@ "node": ">=18.17.0 <=20", "pnpm": ">=9" }, + "type": "module", "dependencies": { + "@next/bundle-analyzer": "14.2.5", + "@opentelemetry/api": "1.9.0", + "@opentelemetry/api-logs": "0.52.1", + "@opentelemetry/core": "1.25.1", + "@opentelemetry/exporter-logs-otlp-http": "0.52.1", + "@opentelemetry/exporter-trace-otlp-http": "0.52.1", + "@opentelemetry/instrumentation-http": "0.52.1", + "@opentelemetry/resources": "1.25.1", + "@opentelemetry/sdk-logs": "0.52.1", + "@opentelemetry/sdk-node": "0.52.1", + "@opentelemetry/sdk-trace-base": "1.25.1", + "@opentelemetry/semantic-conventions": "1.25.1", "@saleor/app-sdk": "0.50.0", "@saleor/macaw-ui": "1.0.0", + "@sentry/nextjs": "7.117.0", "@tanstack/react-query": "^4.35.3", "@trpc/client": "10.45.2", "@trpc/next": "10.45.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 138476c..aa164cb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,12 +8,51 @@ importers: .: dependencies: + '@next/bundle-analyzer': + specifier: 14.2.5 + version: 14.2.5 + '@opentelemetry/api': + specifier: 1.9.0 + version: 1.9.0 + '@opentelemetry/api-logs': + specifier: 0.52.1 + version: 0.52.1 + '@opentelemetry/core': + specifier: 1.25.1 + version: 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-logs-otlp-http': + specifier: 0.52.1 + version: 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-http': + specifier: 0.52.1 + version: 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-http': + specifier: 0.52.1 + version: 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': + specifier: 1.25.1 + version: 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': + specifier: 0.52.1 + version: 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-node': + specifier: 0.52.1 + version: 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': + specifier: 1.25.1 + version: 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': + specifier: 1.25.1 + version: 1.25.1 '@saleor/app-sdk': specifier: 0.50.0 - version: 0.50.0(graphql@16.8.1)(next@14.1.2(@babel/core@7.24.0)(@opentelemetry/api@1.8.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 0.50.0(graphql@16.8.1)(next@14.1.2(@babel/core@7.24.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@saleor/macaw-ui': specifier: 1.0.0 version: 1.0.0(@types/react-dom@18.2.4)(@types/react@18.2.6)(@vanilla-extract/css@1.14.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@sentry/nextjs': + specifier: 7.117.0 + version: 7.117.0(next@14.1.2(@babel/core@7.24.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0) '@tanstack/react-query': specifier: ^4.35.3 version: 4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -22,7 +61,7 @@ importers: version: 10.45.2(@trpc/server@10.45.2) '@trpc/next': specifier: 10.45.2 - version: 10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/react-query@10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/server@10.45.2)(next@14.1.2(@babel/core@7.24.0)(@opentelemetry/api@1.8.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/react-query@10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/server@10.45.2)(next@14.1.2(@babel/core@7.24.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@trpc/react-query': specifier: ^10.45.2 version: 10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -52,7 +91,7 @@ importers: version: 6.1.0(modern-errors@7.0.1) next: specifier: 14.1.2 - version: 14.1.2(@babel/core@7.24.0)(@opentelemetry/api@1.8.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 14.1.2(@babel/core@7.24.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) react: specifier: 18.2.0 version: 18.2.0 @@ -532,6 +571,10 @@ packages: peerDependencies: react: '>=16.8.0' + '@discoveryjs/json-ext@0.5.7': + resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} + engines: {node: '>=10.0.0'} + '@emotion/hash@0.9.1': resolution: {integrity: sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==} @@ -884,6 +927,15 @@ packages: peerDependencies: graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + '@grpc/grpc-js@1.11.1': + resolution: {integrity: sha512-gyt/WayZrVPH2w/UTLansS7F9Nwld472JxxaETamrM8HNlsa+jSLNyKAZmhxI2Me4c3mQHFiS1wWHDY1g1Kthw==} + engines: {node: '>=12.10.0'} + + '@grpc/proto-loader@0.7.13': + resolution: {integrity: sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==} + engines: {node: '>=6'} + hasBin: true + '@humanwhocodes/config-array@0.11.11': resolution: {integrity: sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==} engines: {node: '>=10.10.0'} @@ -917,6 +969,12 @@ packages: '@jridgewell/trace-mapping@0.3.19': resolution: {integrity: sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==} + '@js-sdsl/ordered-map@4.4.2': + resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} + + '@next/bundle-analyzer@14.2.5': + resolution: {integrity: sha512-BtBbI8VUnB7s4m9ut6CkeJ8Hyx+aq+86mbH+uAld7ZGG0/eH4+5hcPnkHKsQM/yj74iClazS0fninI8yZbIZWA==} + '@next/env@14.1.2': resolution: {integrity: sha512-U0iEG+JF86j6qyu330sfPgsMmDVH8vWVmzZadl+an5EU3o5HqdNytOpM+HsFpl58PmhGBTKx3UmM9c+eoLK0mA==} @@ -989,12 +1047,136 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@opentelemetry/api@1.8.0': - resolution: {integrity: sha512-I/s6F7yKUDdtMsoBWXJe8Qz40Tui5vsuKCWJEWVL+5q9sSWRzzx6v2KeNsOBEwd94j0eWkpWCH4yB6rZg9Mf0w==} + '@opentelemetry/api-logs@0.52.1': + resolution: {integrity: sha512-qnSqB2DQ9TPP96dl8cDubDvrUyWc0/sK81xHTK8eSUspzDM3bsewX903qclQFvVhgStjRWdC5bLb3kQqMkfV5A==} + engines: {node: '>=14'} + + '@opentelemetry/api@1.9.0': + resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} - '@opentelemetry/semantic-conventions@1.22.0': - resolution: {integrity: sha512-CAOgFOKLybd02uj/GhCdEeeBjOS0yeoDeo/CA7ASBSmenpZHAKGB3iDm/rv3BQLcabb/OprDEsSQ1y0P8A7Siw==} + '@opentelemetry/context-async-hooks@1.25.1': + resolution: {integrity: sha512-UW/ge9zjvAEmRWVapOP0qyCvPulWU6cQxGxDbWEFfGOj1VBBZAuOqTo3X6yWmDTD3Xe15ysCZChHncr2xFMIfQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@1.25.1': + resolution: {integrity: sha512-GeT/l6rBYWVQ4XArluLVB6WWQ8flHbdb6r2FCHC3smtdOAbrJBIv35tpV/yp9bmYUJf+xmZpu9DRTIeJVhFbEQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/exporter-logs-otlp-http@0.52.1': + resolution: {integrity: sha512-qKgywId2DbdowPZpOBXQKp0B8DfhfIArmSic15z13Nk/JAOccBUQdPwDjDnjsM5f0ckZFMVR2t/tijTUAqDZoA==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/exporter-trace-otlp-grpc@0.52.1': + resolution: {integrity: sha512-pVkSH20crBwMTqB3nIN4jpQKUEoB0Z94drIHpYyEqs7UBr+I0cpYyOR3bqjA/UasQUMROb3GX8ZX4/9cVRqGBQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/exporter-trace-otlp-http@0.52.1': + resolution: {integrity: sha512-05HcNizx0BxcFKKnS5rwOV+2GevLTVIRA0tRgWYyw4yCgR53Ic/xk83toYKts7kbzcI+dswInUg/4s8oyA+tqg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/exporter-trace-otlp-proto@0.52.1': + resolution: {integrity: sha512-pt6uX0noTQReHXNeEslQv7x311/F1gJzMnp1HD2qgypLRPbXDeMzzeTngRTUaUbP6hqWNtPxuLr4DEoZG+TcEQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/exporter-zipkin@1.25.1': + resolution: {integrity: sha512-RmOwSvkimg7ETwJbUOPTMhJm9A9bG1U8s7Zo3ajDh4zM7eYcycQ0dM7FbLD6NXWbI2yj7UY4q8BKinKYBQksyw==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/instrumentation-http@0.52.1': + resolution: {integrity: sha512-dG/aevWhaP+7OLv4BQQSEKMJv8GyeOp3Wxl31NHqE8xo9/fYMfEljiZphUHIfyg4gnZ9swMyWjfOQs5GUQe54Q==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation@0.52.1': + resolution: {integrity: sha512-uXJbYU/5/MBHjMp1FqrILLRuiJCs3Ofk0MeRDk8g1S1gD47U8X3JnSwcMO1rtRo1x1a7zKaQHaoYu49p/4eSKw==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-exporter-base@0.52.1': + resolution: {integrity: sha512-z175NXOtX5ihdlshtYBe5RpGeBoTXVCKPPLiQlD6FHvpM4Ch+p2B0yWKYSrBfLH24H9zjJiBdTrtD+hLlfnXEQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/otlp-grpc-exporter-base@0.52.1': + resolution: {integrity: sha512-zo/YrSDmKMjG+vPeA9aBBrsQM9Q/f2zo6N04WMB3yNldJRsgpRBeLLwvAt/Ba7dpehDLOEFBd1i2JCoaFtpCoQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/otlp-transformer@0.52.1': + resolution: {integrity: sha512-I88uCZSZZtVa0XniRqQWKbjAUm73I8tpEy/uJYPPYw5d7BRdVk0RfTBQw8kSUl01oVWEuqxLDa802222MYyWHg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/propagator-b3@1.25.1': + resolution: {integrity: sha512-p6HFscpjrv7//kE+7L+3Vn00VEDUJB0n6ZrjkTYHrJ58QZ8B3ajSJhRbCcY6guQ3PDjTbxWklyvIN2ojVbIb1A==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/propagator-jaeger@1.25.1': + resolution: {integrity: sha512-nBprRf0+jlgxks78G/xq72PipVK+4or9Ypntw0gVZYNTCSK8rg5SeaGV19tV920CMqBD/9UIOiFr23Li/Q8tiA==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/resources@1.25.1': + resolution: {integrity: sha512-pkZT+iFYIZsVn6+GzM0kSX+u3MSLCY9md+lIJOoKl/P+gJFfxJte/60Usdp8Ce4rOs8GduUpSPNe1ddGyDT1sQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/sdk-logs@0.52.1': + resolution: {integrity: sha512-MBYh+WcPPsN8YpRHRmK1Hsca9pVlyyKd4BxOC4SsgHACnl/bPp4Cri9hWhVm5+2tiQ9Zf4qSc1Jshw9tOLGWQA==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.4.0 <1.10.0' + + '@opentelemetry/sdk-metrics@1.25.1': + resolution: {integrity: sha512-9Mb7q5ioFL4E4dDrc4wC/A3NTHDat44v4I3p2pLPSxRvqUbDIQyMVr9uK+EU69+HWhlET1VaSrRzwdckWqY15Q==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-node@0.52.1': + resolution: {integrity: sha512-uEG+gtEr6eKd8CVWeKMhH2olcCHM9dEK68pe0qE0be32BcCRsvYURhHaD1Srngh1SQcnQzZ4TP324euxqtBOJA==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-trace-base@1.25.1': + resolution: {integrity: sha512-C8k4hnEbc5FamuZQ92nTOp8X/diCY56XUTnMiv9UTuJitCzaNNHAVsdm5+HLCdI8SLQsLWIrG38tddMxLVoftw==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/sdk-trace-node@1.25.1': + resolution: {integrity: sha512-nMcjFIKxnFqoez4gUmihdBrbpsEnAX/Xj16sGvZm+guceYE0NE00vLhpDVK6f3q8Q4VFI5xG8JjlXKMB/SkTTQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/semantic-conventions@1.25.1': + resolution: {integrity: sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==} engines: {node: '>=14'} '@parcel/watcher-android-arm64@2.3.0': @@ -1084,6 +1266,39 @@ packages: resolution: {integrity: sha512-VtaY4spKTdN5LjJ04im/d/joXuvLbQdgy5Z4DXF4MFZhQ+MTrejbNMkfZBp1Bs3O5+bFqnJgyGdPuZQflvIa5A==} engines: {node: '>=10.12.0'} + '@polka/url@1.0.0-next.25': + resolution: {integrity: sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==} + + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + + '@protobufjs/codegen@2.0.4': + resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + + '@protobufjs/eventemitter@1.1.0': + resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + + '@protobufjs/fetch@1.1.0': + resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/inquire@1.1.0': + resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.0': + resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + '@radix-ui/primitive@1.0.1': resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==} @@ -1470,6 +1685,24 @@ packages: '@repeaterjs/repeater@3.0.4': resolution: {integrity: sha512-AW8PKd6iX3vAZ0vA43nOUOnbq/X5ihgU+mSXXqunMkeQADGiqw/PY0JNeYtD5sr0PAy51YPgAPbDoeapv9r8WA==} + '@rollup/plugin-commonjs@24.0.0': + resolution: {integrity: sha512-0w0wyykzdyRRPHOb0cQt14mIBLujfAv6GgP6g8nvg/iBxEm112t3YPPq+Buqe2+imvElTka+bjNlJ/gB56TD8g==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^2.68.0||^3.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/pluginutils@5.1.0': + resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + '@rollup/rollup-android-arm-eabi@4.16.4': resolution: {integrity: sha512-GkhjAaQ8oUTOKE4g4gsZ0u8K/IHU1+2WQSgS1TwTcYvL+sjbaQjNHFXbOJ6kgqGHIO1DfUhI/Sphi9GkRT9K+Q==} cpu: [arm] @@ -1574,6 +1807,76 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + '@sentry-internal/feedback@7.117.0': + resolution: {integrity: sha512-4X+NnnY17W74TymgLFH7/KPTVYpEtoMMJh8HzVdCmHTOE6j32XKBeBMRaXBhmNYmEgovgyRKKf2KvtSfgw+V1Q==} + engines: {node: '>=12'} + + '@sentry-internal/replay-canvas@7.117.0': + resolution: {integrity: sha512-7hjIhwEcoosr+BIa0AyEssB5xwvvlzUpvD5fXu4scd3I3qfX8gdnofO96a8r+LrQm3bSj+eN+4TfKEtWb7bU5A==} + engines: {node: '>=12'} + + '@sentry-internal/tracing@7.117.0': + resolution: {integrity: sha512-fAIyijNvKBZNA12IcKo+dOYDRTNrzNsdzbm3DP37vJRKVQu19ucqP4Y6InvKokffDP2HZPzFPDoGXYuXkDhUZg==} + engines: {node: '>=8'} + + '@sentry/browser@7.117.0': + resolution: {integrity: sha512-29X9HlvDEKIaWp6XKlNPPSNND0U6P/ede5WA2nVHfs1zJLWdZ7/ijuMc0sH/CueEkqHe/7gt94hBcI7HOU/wSw==} + engines: {node: '>=8'} + + '@sentry/cli@1.77.3': + resolution: {integrity: sha512-c3eDqcDRmy4TFz2bFU5Y6QatlpoBPPa8cxBooaS4aMQpnIdLYPF1xhyyiW0LQlDUNc3rRjNF7oN5qKoaRoMTQQ==} + engines: {node: '>= 8'} + hasBin: true + + '@sentry/core@7.117.0': + resolution: {integrity: sha512-1XZ4/d/DEwnfM2zBMloXDwX+W7s76lGKQMgd8bwgPJZjjEztMJ7X0uopKAGwlQcjn242q+hsCBR6C+fSuI5kvg==} + engines: {node: '>=8'} + + '@sentry/integrations@7.117.0': + resolution: {integrity: sha512-U3suSZysmU9EiQqg0ga5CxveAyNbi9IVdsapMDq5EQGNcVDvheXtULs+BOc11WYP3Kw2yWB38VDqLepfc/Fg2g==} + engines: {node: '>=8'} + + '@sentry/nextjs@7.117.0': + resolution: {integrity: sha512-nS14Q2HSh6a0FZcSPAWL9kqecAYIn+11vRe8qcZKlStSg+SmWlOrvsG9/LuZtuZQI+IOtdVUMsxEM9p6LMMQ/w==} + engines: {node: '>=8'} + peerDependencies: + next: ^10.0.8 || ^11.0 || ^12.0 || ^13.0 || ^14.0 + react: 16.x || 17.x || 18.x + webpack: '>= 4.0.0' + peerDependenciesMeta: + webpack: + optional: true + + '@sentry/node@7.117.0': + resolution: {integrity: sha512-0MWXdT8dv1MtQGF0aeB8LQTBTJS1L1Vz24+wvdXroR3/52mPYrPWlzuc7+Ew/Dlqdlb5LKVIlkuDSRWj8UKpTQ==} + engines: {node: '>=8'} + + '@sentry/react@7.117.0': + resolution: {integrity: sha512-aK+yaEP2esBhaczGU96Y7wkqB4umSIlRAzobv7ER88EGHzZulRaocTpQO8HJJGDHm4D8rD+E893BHnghkoqp4Q==} + engines: {node: '>=8'} + peerDependencies: + react: 15.x || 16.x || 17.x || 18.x + + '@sentry/replay@7.117.0': + resolution: {integrity: sha512-V4DfU+x4UsA4BsufbQ8jHYa5H0q5PYUgso2X1PR31g1fpx7yiYguSmCfz1UryM6KkH92dfTnqXapDB44kXOqzQ==} + engines: {node: '>=12'} + + '@sentry/types@7.117.0': + resolution: {integrity: sha512-5dtdulcUttc3F0Te7ekZmpSp/ebt/CA71ELx0uyqVGjWsSAINwskFD77sdcjqvZWek//WjiYX1+GRKlpJ1QqsA==} + engines: {node: '>=8'} + + '@sentry/utils@7.117.0': + resolution: {integrity: sha512-KkcLY8643SGBiDyPvMQOubBkwVX5IPknMHInc7jYC8pDVncGp7C65Wi506bCNPpKCWspUd/0VDNWOOen51/qKA==} + engines: {node: '>=8'} + + '@sentry/vercel-edge@7.117.0': + resolution: {integrity: sha512-1fXvX9Xwg1jmGpY6Zurpye7dNsB9NIQXFbnltR11RQtnbkErMVbc4rJ2ivpeuvsDp+GVZODSPoeBiOiA8m3AWQ==} + engines: {node: '>=8'} + + '@sentry/webpack-plugin@1.21.0': + resolution: {integrity: sha512-x0PYIMWcsTauqxgl7vWUY6sANl+XGKtx7DCVnnY7aOIIlIna0jChTAPANTfA2QrK+VK+4I/4JxatCEZBnXh3Og==} + engines: {node: '>= 8'} + '@sinclair/typebox@0.27.8': resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} @@ -1669,6 +1972,9 @@ packages: '@types/scheduler@0.16.4': resolution: {integrity: sha512-2L9ifAGl7wmXwP4v3pN4p2FLhD0O1qsJpvKmNin5VA8+UvNVb447UDaAEV6UdrkA+m/Xs58U1RFps44x6TFsVQ==} + '@types/shimmer@1.2.0': + resolution: {integrity: sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==} + '@types/uuid@10.0.0': resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} @@ -1770,6 +2076,11 @@ packages: acorn-globals@7.0.1: resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==} + acorn-import-attributes@1.9.5: + resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} + peerDependencies: + acorn: ^8 + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -1922,6 +2233,9 @@ packages: brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + braces@3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} engines: {node: '>=8'} @@ -1985,6 +2299,10 @@ packages: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} + chalk@3.0.0: + resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} + engines: {node: '>=8'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -2004,6 +2322,9 @@ packages: check-error@1.0.3: resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + cjs-module-lexer@1.3.1: + resolution: {integrity: sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==} + clean-stack@2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} @@ -2058,10 +2379,17 @@ packages: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + common-tags@1.8.2: resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} engines: {node: '>=4.0.0'} + commondir@1.0.1: + resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + compute-scroll-into-view@2.0.4: resolution: {integrity: sha512-y/ZA3BGnxoM/QHHQ2Uy49CLtnWPbt4tTPpEEZiEmmiWBFKjej7nEyH8Ryz54jH0MLXflUYA3Er2zUxPSJu5R+g==} @@ -2144,6 +2472,15 @@ packages: supports-color: optional: true + debug@4.3.6: + resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decamelize@1.2.0: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} engines: {node: '>=0.10.0'} @@ -2241,6 +2578,9 @@ packages: resolution: {integrity: sha512-g/M9sqy3oHe477Ar4voQxWtaPIFw1jTdKZuomOjhCcBx9nHUNn0pu6NopuFFrTh/TRZIKEj+76vLWFu9BNKk+Q==} engines: {node: '>=4'} + duplexer@0.1.2: + resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} + electron-to-chromium@1.4.542: resolution: {integrity: sha512-6+cpa00G09N3sfh2joln4VUXHquWrOFx3FLZqiVQvl45+zS9DskDBTPvob+BhvFRmTBkyDSk0vvLMMRo/qc6mQ==} @@ -2434,6 +2774,9 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} @@ -2586,6 +2929,11 @@ packages: glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + deprecated: Glob versions prior to v9 are no longer supported + globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} @@ -2642,6 +2990,10 @@ packages: resolution: {integrity: sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + gzip-size@6.0.0: + resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==} + engines: {node: '>=10'} + has-bigints@1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} @@ -2675,10 +3027,16 @@ packages: header-case@2.0.4: resolution: {integrity: sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==} + hoist-non-react-statics@3.3.2: + resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + html-encoding-sniffer@3.0.0: resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==} engines: {node: '>=12'} + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + http-errors@2.0.0: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} @@ -2718,6 +3076,9 @@ packages: resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} engines: {node: '>= 4'} + immediate@3.0.6: + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + immutable@3.7.6: resolution: {integrity: sha512-AizQPcaofEtO11RZhPPHBOJRdo/20MKQF9mBLnVkBoyHi1/zXK8fzVdnEpSV9gxqtnh6Qomfp3F0xT5qP/vThw==} engines: {node: '>=0.8.0'} @@ -2730,6 +3091,9 @@ packages: resolution: {integrity: sha512-P9J71vT5nLlDeV8FHs5nNxaLbrpfAV5cF5srvbZfpwpcJoM/xZR3hiv+q+SAnuSmuGbXMWud063iIMx/V/EWZQ==} engines: {node: '>=12.2'} + import-in-the-middle@1.11.0: + resolution: {integrity: sha512-5DimNQGoe0pLUHbR9qK84iWaWjjbsxiqXnw6Qz64+azRgleqv9k2kTt5fw7QsOpmaGYtuxxursnPPsnTKEx10Q==} + imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -2844,9 +3208,16 @@ packages: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} + is-plain-object@5.0.0: + resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} + engines: {node: '>=0.10.0'} + is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-reference@1.2.1: + resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} + is-regex@1.1.4: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} @@ -3004,6 +3375,9 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + lie@3.1.1: + resolution: {integrity: sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==} + lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -3020,6 +3394,9 @@ packages: resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} engines: {node: '>=14'} + localforage@1.10.0: + resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==} + locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -3028,6 +3405,9 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} @@ -3045,6 +3425,9 @@ packages: resolution: {integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==} engines: {node: '>=10'} + long@5.2.3: + resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==} + loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -3065,6 +3448,10 @@ packages: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} + magic-string@0.27.0: + resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==} + engines: {node: '>=12'} + magic-string@0.30.10: resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} @@ -3122,9 +3509,17 @@ packages: resolution: {integrity: sha512-lIUdtK5hdofgCTu3aT0sOaHsYR37viUuIc0rwnnDXImbwFRcumyLMeZaM0t0I/fgxS6s6JMfu0rLD1Wz9pv1ng==} engines: {node: '>=10'} + minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + mlly@1.6.1: resolution: {integrity: sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==} @@ -3141,6 +3536,13 @@ packages: resolution: {integrity: sha512-UaWtjvt1LWLTmQq9vBcjVzVyaBS8PyN3YYF9Vzdf7dgaB9ySFBWbIH5nglEfLr1VVF7XrBrHrdp+Tnw7oITfTA==} engines: {node: '>=18.18.0'} + module-details-from-path@1.0.3: + resolution: {integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==} + + mrmime@2.0.0: + resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==} + engines: {node: '>=10'} + ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} @@ -3259,6 +3661,10 @@ packages: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} engines: {node: '>=12'} + opener@1.5.2: + resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} + hasBin: true + optionator@0.9.3: resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} engines: {node: '>= 0.8.0'} @@ -3394,12 +3800,23 @@ packages: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + promise@7.3.1: resolution: {integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==} prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + protobufjs@7.3.2: + resolution: {integrity: sha512-RXyHaACeqXeqAKGLDl68rQKbmObRsTIn4TYVUUug1KfS47YWCo5MacGITEryugIgZqORCvJWEk4l449POg5Txg==} + engines: {node: '>=12.0.0'} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + psl@1.9.0: resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} @@ -3521,6 +3938,10 @@ packages: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} + require-in-the-middle@7.4.0: + resolution: {integrity: sha512-X34iHADNbNDfr6OTStIAHWSAvvKQRYgLO6duASaVf7J2VA3lvmNYboAHOuLC2huav1IwgZJtyEcJCKVzFxOSMQ==} + engines: {node: '>=8.6.0'} + require-main-filename@2.0.0: resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} @@ -3542,6 +3963,10 @@ packages: resolution: {integrity: sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw==} hasBin: true + resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + hasBin: true + resolve@2.0.0-next.4: resolution: {integrity: sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==} hasBin: true @@ -3564,6 +3989,11 @@ packages: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} hasBin: true + rollup@2.78.0: + resolution: {integrity: sha512-4+YfbQC9QEVvKTanHhIAFVUFSRsezvQF8vFOJwtGfb9Bb+r014S+qryr9PSmw8x6sMnPkmFBGAvIFVQxvJxjtg==} + engines: {node: '>=10.0.0'} + hasBin: true + rollup@4.16.4: resolution: {integrity: sha512-kuaTJSUbz+Wsb2ATGvEknkI12XV40vIiHmLuFlejoo7HtDok/O5eDDD0UpCVY5bBX5U5RYo8wWP83H7ZsqVEnA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -3658,6 +4088,9 @@ packages: shell-quote@1.8.1: resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==} + shimmer@1.2.1: + resolution: {integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==} + side-channel@1.0.4: resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} @@ -3674,6 +4107,10 @@ packages: signedsource@1.0.0: resolution: {integrity: sha512-6+eerH9fEnNmi/hyM1DXcRK3pWdoMQtlkQ+ns0ntzunjKqp5i3sKCc80ym8Fib3iaYhdJUOPdhlJWj1tvge2Ww==} + sirv@2.0.4: + resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==} + engines: {node: '>= 10'} + slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -3703,6 +4140,10 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + stacktrace-parser@0.1.10: + resolution: {integrity: sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==} + engines: {node: '>=6'} + statuses@2.0.1: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} @@ -3827,6 +4268,10 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + tough-cookie@4.1.3: resolution: {integrity: sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==} engines: {node: '>=6'} @@ -3882,6 +4327,10 @@ packages: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} + type-fest@0.7.1: + resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==} + engines: {node: '>=8'} + typed-array-buffer@1.0.0: resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==} engines: {node: '>= 0.4'} @@ -4078,6 +4527,15 @@ packages: resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} engines: {node: '>=12'} + webpack-bundle-analyzer@4.10.1: + resolution: {integrity: sha512-s3P7pgexgT/HTUSYgxJyn28A+99mmLq4HsJepMPzu0R8ImJc52QNqaFYW1Z2z2uIb1/J3eYgaAWVpaC+v/1aAQ==} + engines: {node: '>= 10.13.0'} + hasBin: true + + webpack-sources@3.2.3: + resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} + engines: {node: '>=10.13.0'} + whatwg-encoding@2.0.0: resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==} engines: {node: '>=12'} @@ -4138,6 +4596,18 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@7.5.10: + resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + ws@8.13.0: resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==} engines: {node: '>=10.0.0'} @@ -4707,6 +5177,8 @@ snapshots: '@dessert-box/core': 0.2.0 react: 18.2.0 + '@discoveryjs/json-ext@0.5.7': {} + '@emotion/hash@0.9.1': {} '@esbuild/aix-ppc64@0.20.2': @@ -5258,6 +5730,18 @@ snapshots: dependencies: graphql: 16.8.1 + '@grpc/grpc-js@1.11.1': + dependencies: + '@grpc/proto-loader': 0.7.13 + '@js-sdsl/ordered-map': 4.4.2 + + '@grpc/proto-loader@0.7.13': + dependencies: + lodash.camelcase: 4.3.0 + long: 5.2.3 + protobufjs: 7.3.2 + yargs: 17.7.2 + '@humanwhocodes/config-array@0.11.11': dependencies: '@humanwhocodes/object-schema': 1.2.1 @@ -5291,6 +5775,15 @@ snapshots: '@jridgewell/resolve-uri': 3.1.1 '@jridgewell/sourcemap-codec': 1.4.15 + '@js-sdsl/ordered-map@4.4.2': {} + + '@next/bundle-analyzer@14.2.5': + dependencies: + webpack-bundle-analyzer: 4.10.1 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + '@next/env@14.1.2': {} '@next/eslint-plugin-next@13.1.2': @@ -5336,9 +5829,180 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.15.0 - '@opentelemetry/api@1.8.0': {} + '@opentelemetry/api-logs@0.52.1': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/api@1.9.0': {} + + '@opentelemetry/context-async-hooks@1.25.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/core@1.25.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.25.1 + + '@opentelemetry/exporter-logs-otlp-http@0.52.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.52.1 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.52.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-trace-otlp-grpc@0.52.1(@opentelemetry/api@1.9.0)': + dependencies: + '@grpc/grpc-js': 1.11.1 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-grpc-exporter-base': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.25.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-trace-otlp-http@0.52.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.25.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-trace-otlp-proto@0.52.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.25.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-zipkin@1.25.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.25.1 + + '@opentelemetry/instrumentation-http@0.52.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.25.1 + semver: 7.5.4 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation@0.52.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.52.1 + '@types/shimmer': 1.2.0 + import-in-the-middle: 1.11.0 + require-in-the-middle: 7.4.0 + semver: 7.5.4 + shimmer: 1.2.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/otlp-exporter-base@0.52.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.52.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/otlp-grpc-exporter-base@0.52.1(@opentelemetry/api@1.9.0)': + dependencies: + '@grpc/grpc-js': 1.11.1 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.52.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/otlp-transformer@0.52.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.52.1 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.25.1(@opentelemetry/api@1.9.0) + protobufjs: 7.3.2 + + '@opentelemetry/propagator-b3@1.25.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/propagator-jaeger@1.25.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/resources@1.25.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.25.1 + + '@opentelemetry/sdk-logs@0.52.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.52.1 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.25.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/sdk-metrics@1.25.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.25.1(@opentelemetry/api@1.9.0) + lodash.merge: 4.6.2 + + '@opentelemetry/sdk-node@0.52.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.52.1 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-grpc': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-http': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-proto': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-zipkin': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-node': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.25.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/sdk-trace-base@1.25.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.25.1 + + '@opentelemetry/sdk-trace-node@1.25.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/context-async-hooks': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/propagator-b3': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/propagator-jaeger': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.25.1(@opentelemetry/api@1.9.0) + semver: 7.5.4 - '@opentelemetry/semantic-conventions@1.22.0': {} + '@opentelemetry/semantic-conventions@1.25.1': {} '@parcel/watcher-android-arm64@2.3.0': optional: true @@ -5414,6 +6078,31 @@ snapshots: tslib: 2.6.2 webcrypto-core: 1.7.7 + '@polka/url@1.0.0-next.25': {} + + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.4': {} + + '@protobufjs/eventemitter@1.1.0': {} + + '@protobufjs/fetch@1.1.0': + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/inquire': 1.1.0 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/inquire@1.1.0': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.0': {} + '@radix-ui/primitive@1.0.1': dependencies: '@babel/runtime': 7.23.1 @@ -5842,6 +6531,25 @@ snapshots: '@repeaterjs/repeater@3.0.4': {} + '@rollup/plugin-commonjs@24.0.0(rollup@2.78.0)': + dependencies: + '@rollup/pluginutils': 5.1.0(rollup@2.78.0) + commondir: 1.0.1 + estree-walker: 2.0.2 + glob: 8.1.0 + is-reference: 1.2.1 + magic-string: 0.27.0 + optionalDependencies: + rollup: 2.78.0 + + '@rollup/pluginutils@5.1.0(rollup@2.78.0)': + dependencies: + '@types/estree': 1.0.5 + estree-walker: 2.0.2 + picomatch: 2.3.1 + optionalDependencies: + rollup: 2.78.0 + '@rollup/rollup-android-arm-eabi@4.16.4': optional: true @@ -5892,14 +6600,14 @@ snapshots: '@rushstack/eslint-patch@1.5.1': {} - '@saleor/app-sdk@0.50.0(graphql@16.8.1)(next@14.1.2(@babel/core@7.24.0)(@opentelemetry/api@1.8.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@saleor/app-sdk@0.50.0(graphql@16.8.1)(next@14.1.2(@babel/core@7.24.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: - '@opentelemetry/api': 1.8.0 - '@opentelemetry/semantic-conventions': 1.22.0 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.25.1 debug: 4.3.4 graphql: 16.8.1 jose: 4.14.4 - next: 14.1.2(@babel/core@7.24.0)(@opentelemetry/api@1.8.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + next: 14.1.2(@babel/core@7.24.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) raw-body: 2.5.2 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -5931,6 +6639,127 @@ snapshots: transitivePeerDependencies: - '@vanilla-extract/css' + '@sentry-internal/feedback@7.117.0': + dependencies: + '@sentry/core': 7.117.0 + '@sentry/types': 7.117.0 + '@sentry/utils': 7.117.0 + + '@sentry-internal/replay-canvas@7.117.0': + dependencies: + '@sentry/core': 7.117.0 + '@sentry/replay': 7.117.0 + '@sentry/types': 7.117.0 + '@sentry/utils': 7.117.0 + + '@sentry-internal/tracing@7.117.0': + dependencies: + '@sentry/core': 7.117.0 + '@sentry/types': 7.117.0 + '@sentry/utils': 7.117.0 + + '@sentry/browser@7.117.0': + dependencies: + '@sentry-internal/feedback': 7.117.0 + '@sentry-internal/replay-canvas': 7.117.0 + '@sentry-internal/tracing': 7.117.0 + '@sentry/core': 7.117.0 + '@sentry/integrations': 7.117.0 + '@sentry/replay': 7.117.0 + '@sentry/types': 7.117.0 + '@sentry/utils': 7.117.0 + + '@sentry/cli@1.77.3': + dependencies: + https-proxy-agent: 5.0.1 + mkdirp: 0.5.6 + node-fetch: 2.7.0 + progress: 2.0.3 + proxy-from-env: 1.1.0 + which: 2.0.2 + transitivePeerDependencies: + - encoding + - supports-color + + '@sentry/core@7.117.0': + dependencies: + '@sentry/types': 7.117.0 + '@sentry/utils': 7.117.0 + + '@sentry/integrations@7.117.0': + dependencies: + '@sentry/core': 7.117.0 + '@sentry/types': 7.117.0 + '@sentry/utils': 7.117.0 + localforage: 1.10.0 + + '@sentry/nextjs@7.117.0(next@14.1.2(@babel/core@7.24.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0)': + dependencies: + '@rollup/plugin-commonjs': 24.0.0(rollup@2.78.0) + '@sentry/core': 7.117.0 + '@sentry/integrations': 7.117.0 + '@sentry/node': 7.117.0 + '@sentry/react': 7.117.0(react@18.2.0) + '@sentry/types': 7.117.0 + '@sentry/utils': 7.117.0 + '@sentry/vercel-edge': 7.117.0 + '@sentry/webpack-plugin': 1.21.0 + chalk: 3.0.0 + next: 14.1.2(@babel/core@7.24.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + react: 18.2.0 + resolve: 1.22.8 + rollup: 2.78.0 + stacktrace-parser: 0.1.10 + transitivePeerDependencies: + - encoding + - supports-color + + '@sentry/node@7.117.0': + dependencies: + '@sentry-internal/tracing': 7.117.0 + '@sentry/core': 7.117.0 + '@sentry/integrations': 7.117.0 + '@sentry/types': 7.117.0 + '@sentry/utils': 7.117.0 + + '@sentry/react@7.117.0(react@18.2.0)': + dependencies: + '@sentry/browser': 7.117.0 + '@sentry/core': 7.117.0 + '@sentry/types': 7.117.0 + '@sentry/utils': 7.117.0 + hoist-non-react-statics: 3.3.2 + react: 18.2.0 + + '@sentry/replay@7.117.0': + dependencies: + '@sentry-internal/tracing': 7.117.0 + '@sentry/core': 7.117.0 + '@sentry/types': 7.117.0 + '@sentry/utils': 7.117.0 + + '@sentry/types@7.117.0': {} + + '@sentry/utils@7.117.0': + dependencies: + '@sentry/types': 7.117.0 + + '@sentry/vercel-edge@7.117.0': + dependencies: + '@sentry-internal/tracing': 7.117.0 + '@sentry/core': 7.117.0 + '@sentry/integrations': 7.117.0 + '@sentry/types': 7.117.0 + '@sentry/utils': 7.117.0 + + '@sentry/webpack-plugin@1.21.0': + dependencies: + '@sentry/cli': 1.77.3 + webpack-sources: 3.2.3 + transitivePeerDependencies: + - encoding + - supports-color + '@sinclair/typebox@0.27.8': {} '@swc/helpers@0.5.2': @@ -5953,13 +6782,13 @@ snapshots: dependencies: '@trpc/server': 10.45.2 - '@trpc/next@10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/react-query@10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/server@10.45.2)(next@14.1.2(@babel/core@7.24.0)(@opentelemetry/api@1.8.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@trpc/next@10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/react-query@10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/server@10.45.2)(next@14.1.2(@babel/core@7.24.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@tanstack/react-query': 4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@trpc/client': 10.45.2(@trpc/server@10.45.2) '@trpc/react-query': 10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@trpc/server': 10.45.2 - next: 14.1.2(@babel/core@7.24.0)(@opentelemetry/api@1.8.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + next: 14.1.2(@babel/core@7.24.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -6020,6 +6849,8 @@ snapshots: '@types/scheduler@0.16.4': {} + '@types/shimmer@1.2.0': {} + '@types/uuid@10.0.0': {} '@types/ws@8.5.6': @@ -6168,6 +6999,10 @@ snapshots: acorn: 8.11.3 acorn-walk: 8.3.2 + acorn-import-attributes@1.9.5(acorn@8.11.3): + dependencies: + acorn: 8.11.3 + acorn-jsx@5.3.2(acorn@8.10.0): dependencies: acorn: 8.10.0 @@ -6361,6 +7196,10 @@ snapshots: balanced-match: 1.0.2 concat-map: 0.0.1 + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + braces@3.0.2: dependencies: fill-range: 7.0.1 @@ -6436,6 +7275,11 @@ snapshots: escape-string-regexp: 1.0.5 supports-color: 5.5.0 + chalk@3.0.0: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -6488,6 +7332,8 @@ snapshots: dependencies: get-func-name: 2.0.2 + cjs-module-lexer@1.3.1: {} + clean-stack@2.2.0: {} cli-cursor@3.1.0: @@ -6537,8 +7383,12 @@ snapshots: dependencies: delayed-stream: 1.0.0 + commander@7.2.0: {} + common-tags@1.8.2: {} + commondir@1.0.1: {} + compute-scroll-into-view@2.0.4: {} concat-map@0.0.1: {} @@ -6614,6 +7464,10 @@ snapshots: dependencies: ms: 2.1.2 + debug@4.3.6: + dependencies: + ms: 2.1.2 + decamelize@1.2.0: {} decimal.js@10.4.3: {} @@ -6694,6 +7548,8 @@ snapshots: dset@3.1.2: {} + duplexer@0.1.2: {} + electron-to-chromium@1.4.542: {} electron-to-chromium@1.4.692: {} @@ -7047,6 +7903,8 @@ snapshots: estraverse@5.3.0: {} + estree-walker@2.0.2: {} + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.5 @@ -7229,6 +8087,14 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 + glob@8.1.0: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.6 + once: 1.4.0 + globals@11.12.0: {} globals@13.22.0: @@ -7295,6 +8161,10 @@ snapshots: graphql@16.8.1: {} + gzip-size@6.0.0: + dependencies: + duplexer: 0.1.2 + has-bigints@1.0.2: {} has-flag@3.0.0: {} @@ -7320,10 +8190,16 @@ snapshots: capital-case: 1.0.4 tslib: 2.6.2 + hoist-non-react-statics@3.3.2: + dependencies: + react-is: 16.13.1 + html-encoding-sniffer@3.0.0: dependencies: whatwg-encoding: 2.0.0 + html-escaper@2.0.2: {} + http-errors@2.0.0: dependencies: depd: 2.0.0 @@ -7375,6 +8251,8 @@ snapshots: ignore@5.2.4: {} + immediate@3.0.6: {} + immutable@3.7.6: {} import-fresh@3.3.0: @@ -7384,6 +8262,13 @@ snapshots: import-from@4.0.0: {} + import-in-the-middle@1.11.0: + dependencies: + acorn: 8.11.3 + acorn-import-attributes: 1.9.5(acorn@8.11.3) + cjs-module-lexer: 1.3.1 + module-details-from-path: 1.0.3 + imurmurhash@0.1.4: {} indent-string@4.0.0: {} @@ -7499,8 +8384,14 @@ snapshots: is-plain-obj@4.1.0: {} + is-plain-object@5.0.0: {} + is-potential-custom-element-name@1.0.1: {} + is-reference@1.2.1: + dependencies: + '@types/estree': 1.0.5 + is-regex@1.1.4: dependencies: call-bind: 1.0.2 @@ -7673,6 +8564,10 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + lie@3.1.1: + dependencies: + immediate: 3.0.6 + lines-and-columns@1.2.4: {} listr2@4.0.5: @@ -7691,6 +8586,10 @@ snapshots: mlly: 1.6.1 pkg-types: 1.1.0 + localforage@1.10.0: + dependencies: + lie: 3.1.1 + locate-path@5.0.0: dependencies: p-locate: 4.1.0 @@ -7699,6 +8598,8 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash.camelcase@4.3.0: {} + lodash.debounce@4.0.8: {} lodash.merge@4.6.2: {} @@ -7717,6 +8618,8 @@ snapshots: slice-ansi: 4.0.0 wrap-ansi: 6.2.0 + long@5.2.3: {} + loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 @@ -7741,6 +8644,10 @@ snapshots: dependencies: yallist: 4.0.0 + magic-string@0.27.0: + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + magic-string@0.30.10: dependencies: '@jridgewell/sourcemap-codec': 1.4.15 @@ -7789,8 +8696,16 @@ snapshots: dependencies: brace-expansion: 1.1.11 + minimatch@5.1.6: + dependencies: + brace-expansion: 2.0.1 + minimist@1.2.8: {} + mkdirp@0.5.6: + dependencies: + minimist: 1.2.8 + mlly@1.6.1: dependencies: acorn: 8.11.3 @@ -7818,6 +8733,10 @@ snapshots: set-error-props: 6.0.0 set-error-stack: 3.0.0 + module-details-from-path@1.0.3: {} + + mrmime@2.0.0: {} + ms@2.1.2: {} ms@2.1.3: {} @@ -7828,7 +8747,7 @@ snapshots: natural-compare@1.4.0: {} - next@14.1.2(@babel/core@7.24.0)(@opentelemetry/api@1.8.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + next@14.1.2(@babel/core@7.24.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: '@next/env': 14.1.2 '@swc/helpers': 0.5.2 @@ -7849,7 +8768,7 @@ snapshots: '@next/swc-win32-arm64-msvc': 14.1.2 '@next/swc-win32-ia32-msvc': 14.1.2 '@next/swc-win32-x64-msvc': 14.1.2 - '@opentelemetry/api': 1.8.0 + '@opentelemetry/api': 1.9.0 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros @@ -7943,6 +8862,8 @@ snapshots: dependencies: mimic-fn: 4.0.0 + opener@1.5.2: {} + optionator@0.9.3: dependencies: '@aashutoshrathi/word-wrap': 1.2.6 @@ -8084,6 +9005,8 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.3.0 + progress@2.0.3: {} + promise@7.3.1: dependencies: asap: 2.0.6 @@ -8094,6 +9017,23 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 + protobufjs@7.3.2: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/node': 18.11.18 + long: 5.2.3 + + proxy-from-env@1.1.0: {} + psl@1.9.0: {} punycode@1.4.1: {} @@ -8210,6 +9150,14 @@ snapshots: require-directory@2.1.1: {} + require-in-the-middle@7.4.0: + dependencies: + debug: 4.3.6 + module-details-from-path: 1.0.3 + resolve: 1.22.8 + transitivePeerDependencies: + - supports-color + require-main-filename@2.0.0: {} requires-port@1.0.0: {} @@ -8226,6 +9174,12 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + resolve@1.22.8: + dependencies: + is-core-module: 2.13.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + resolve@2.0.0-next.4: dependencies: is-core-module: 2.13.0 @@ -8250,6 +9204,10 @@ snapshots: dependencies: glob: 7.2.3 + rollup@2.78.0: + optionalDependencies: + fsevents: 2.3.3 + rollup@4.16.4: dependencies: '@types/estree': 1.0.5 @@ -8364,6 +9322,8 @@ snapshots: shell-quote@1.8.1: {} + shimmer@1.2.1: {} + side-channel@1.0.4: dependencies: call-bind: 1.0.2 @@ -8378,6 +9338,12 @@ snapshots: signedsource@1.0.0: {} + sirv@2.0.4: + dependencies: + '@polka/url': 1.0.0-next.25 + mrmime: 2.0.0 + totalist: 3.0.1 + slash@3.0.0: {} slice-ansi@3.0.0: @@ -8408,6 +9374,10 @@ snapshots: stackback@0.0.2: {} + stacktrace-parser@0.1.10: + dependencies: + type-fest: 0.7.1 + statuses@2.0.1: {} std-env@3.7.0: {} @@ -8521,6 +9491,8 @@ snapshots: toidentifier@1.0.1: {} + totalist@3.0.1: {} + tough-cookie@4.1.3: dependencies: psl: 1.9.0 @@ -8568,6 +9540,8 @@ snapshots: type-fest@0.21.3: {} + type-fest@0.7.1: {} + typed-array-buffer@1.0.0: dependencies: call-bind: 1.0.2 @@ -8771,6 +9745,27 @@ snapshots: webidl-conversions@7.0.0: {} + webpack-bundle-analyzer@4.10.1: + dependencies: + '@discoveryjs/json-ext': 0.5.7 + acorn: 8.11.3 + acorn-walk: 8.3.2 + commander: 7.2.0 + debounce: 1.2.1 + escape-string-regexp: 4.0.0 + gzip-size: 6.0.0 + html-escaper: 2.0.2 + is-plain-object: 5.0.0 + opener: 1.5.2 + picocolors: 1.0.0 + sirv: 2.0.4 + ws: 7.5.10 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + webpack-sources@3.2.3: {} + whatwg-encoding@2.0.0: dependencies: iconv-lite: 0.6.3 @@ -8857,6 +9852,8 @@ snapshots: wrappy@1.0.2: {} + ws@7.5.10: {} + ws@8.13.0: {} ws@8.14.2: {} diff --git a/sentry.client.config.ts b/sentry.client.config.ts new file mode 100644 index 0000000..a04bc5f --- /dev/null +++ b/sentry.client.config.ts @@ -0,0 +1,16 @@ +/* + * This file configures the initialization of Sentry on the browser. + * The config you add here will be used whenever a page is visited. + * https://docs.sentry.io/platforms/javascript/guides/nextjs/ + */ + +import * as Sentry from "@sentry/nextjs"; + +Sentry.init({ + dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, + enableTracing: false, + environment: process.env.ENV, + includeLocalVariables: true, + ignoreErrors: ["TRPCClientError"], + integrations: [], +}); diff --git a/sentry.edge.config.ts b/sentry.edge.config.ts new file mode 100644 index 0000000..789ed89 --- /dev/null +++ b/sentry.edge.config.ts @@ -0,0 +1,14 @@ +import * as Sentry from "@sentry/nextjs"; + +Sentry.init({ + dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, + enableTracing: false, + environment: process.env.ENV, + includeLocalVariables: true, + integrations: [ + Sentry.localVariablesIntegration({ + captureAllExceptions: true, + }), + Sentry.extraErrorDataIntegration(), + ], +}); diff --git a/sentry.server.config.ts b/sentry.server.config.ts new file mode 100644 index 0000000..789ed89 --- /dev/null +++ b/sentry.server.config.ts @@ -0,0 +1,14 @@ +import * as Sentry from "@sentry/nextjs"; + +Sentry.init({ + dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, + enableTracing: false, + environment: process.env.ENV, + includeLocalVariables: true, + integrations: [ + Sentry.localVariablesIntegration({ + captureAllExceptions: true, + }), + Sentry.extraErrorDataIntegration(), + ], +}); diff --git a/src/lib/logger/logger-console-transport.ts b/src/lib/logger/logger-console-transport.ts new file mode 100644 index 0000000..e907a98 --- /dev/null +++ b/src/lib/logger/logger-console-transport.ts @@ -0,0 +1,20 @@ +import { ILogObj, ILogObjMeta, Logger } from "tslog"; + +export const attachLoggerConsoleTransport = (logger: Logger) => { + logger.attachTransport((log) => { + const { + message, + attributes, + _meta: { date, name, parentNames }, + } = log as ILogObj & + ILogObjMeta & { + message: string; + attributes: Record; + }; + + const formattedName = `${(parentNames ?? []).join(":")}:${name}`; + const formattedDate = date.toISOString(); + + console.log(`\x1b[2m ${formattedDate} ${formattedName}\x1b[0m \t${message}`, attributes); + }); +}; diff --git a/src/lib/logger/logger-context.ts b/src/lib/logger/logger-context.ts new file mode 100644 index 0000000..746f7ae --- /dev/null +++ b/src/lib/logger/logger-context.ts @@ -0,0 +1,56 @@ +import { SALEOR_API_URL_HEADER, SALEOR_EVENT_HEADER } from "@saleor/app-sdk/const"; +import { AsyncLocalStorage } from "async_hooks"; +import { NextApiHandler, NextApiRequest, NextApiResponse } from "next"; + +export class LoggerContext { + private als = new AsyncLocalStorage>(); + private project_name = process.env.OTEL_SERVICE_NAME as string | undefined; + + getRawContext() { + const store = this.als.getStore(); + + if (!store) { + if (!process.env.CI && process.env.OTEL_ENABLED === "true") { + console.warn( + "You cant use LoggerContext outside of the wrapped scope. Will fallback to {}" + ); + } + + return {}; + } + + return store; + } + + async wrap(fn: (...args: unknown[]) => unknown, initialState = {}) { + return this.als.run( + { + ...initialState, + project_name: this.project_name, + }, + fn + ); + } + + set(key: string, value: string | number | Record | null) { + const store = this.getRawContext(); + + store[key] = value; + } +} + +export const wrapWithLoggerContext = (handler: NextApiHandler, loggerContext: LoggerContext) => { + return (req: NextApiRequest, res: NextApiResponse) => { + return loggerContext.wrap(() => { + const saleorApiUrl = req.headers[SALEOR_API_URL_HEADER] as string; + const saleorEvent = req.headers[SALEOR_EVENT_HEADER] as string; + const path = req.url as string; + + loggerContext.set("path", path); + loggerContext.set("saleorApiUrl", saleorApiUrl ?? null); + loggerContext.set("saleorEvent", saleorEvent ?? null); + + return handler(req, res); + }); + }; +}; diff --git a/src/lib/logger/logger-otel-transport.ts b/src/lib/logger/logger-otel-transport.ts new file mode 100644 index 0000000..771045d --- /dev/null +++ b/src/lib/logger/logger-otel-transport.ts @@ -0,0 +1,73 @@ +import { context } from "@opentelemetry/api"; +import { LogAttributes, logs } from "@opentelemetry/api-logs"; +import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions"; +import { ILogObj, Logger } from "tslog"; +import { LoggerContext } from "./logger-context"; + +export const attachLoggerOtelTransport = ( + logger: Logger, + appVersion: string, + loggerContext?: LoggerContext +) => { + logger.attachTransport((log) => { + const { message, attributes, _meta, ...inheritedAttributes } = log as ILogObj & { + message: string; + attributes: Record; + }; + + if (!message || !attributes) { + console.error("Logger is not configured properly. OTEL transport will not be attached."); + + return; + } + + /** + * Prune empty keys and serialize top-level arrays, because OTEL can't consume them + */ + const serializedAttributes = Object.entries({ + ...(loggerContext?.getRawContext() ?? {}), + ...attributes, + }).reduce((acc, [key, value]) => { + if (Array.isArray(value)) { + acc[key] = JSON.stringify(value); + } else { + // @ts-expect-error - Logger maps attribute as IMeta, but in the runtime Meta is only in log._meta field which is filtered out first + acc[key] = value; + } + + return acc; + }, {} as LogAttributes); + + /** + * Try to serialize Error. Modern-errors has plugin to serialize + * https://github.com/ehmicky/modern-errors-serialize + * + * It add "serialize" method that converts class to plain object, working for OTEL. + * + * This is not perfect, doesn't work for nested object. We probably need to introduce some abstraction + * on logger error? + */ + try { + const errorAttribute = serializedAttributes.error; + const ErrorConstructor = errorAttribute?.["constructor"]; + + // @ts-expect-error - ErrorConstructor is a class that could have serialize method. If not, safely throw and ignore + serializedAttributes.error = ErrorConstructor.serialize(serializedAttributes.error); + // @ts-expect-error - Additional mapping for Datadog + serializedAttributes.error.type = serializedAttributes.error.name; + } catch (e) {} + + logs.getLogger("app-logger-otel").emit({ + body: log._meta.name ? `[${log._meta.name}] ${message}` : message, + context: context.active(), + severityText: log._meta.logLevelName, + attributes: { + ...serializedAttributes, + [SemanticResourceAttributes.SERVICE_NAME]: process.env.OTEL_SERVICE_NAME, + [SemanticResourceAttributes.SERVICE_VERSION]: appVersion, + ["commit-sha"]: process.env.VERCEL_GIT_COMMIT_SHA, + [SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: process.env.ENV, + }, + }); + }); +}; diff --git a/src/lib/logger/logger-sentry-transport.ts b/src/lib/logger/logger-sentry-transport.ts new file mode 100644 index 0000000..ecbb0f9 --- /dev/null +++ b/src/lib/logger/logger-sentry-transport.ts @@ -0,0 +1,62 @@ +import { ILogObj, Logger } from "tslog"; +import * as Sentry from "@sentry/nextjs"; +import { SeverityLevel } from "@sentry/nextjs"; + +const loggerLevelToSentryLevel = (level: string): SeverityLevel => { + switch (level) { + case "fatal": + case "error": + return "error"; + case "warn": + return "warning"; + case "silly": + case "debug": + case "trace": + return "debug"; + case "info": + return "info"; + } + + return "debug"; +}; + +const levelToBreadcrumbType = (level: string) => { + switch (level) { + case "error": + case "fatal": + return "error"; + case "debug": + case "trace": + case "silly": + return "debug"; + case "info": + default: + return "default"; + } +}; + +export const attachLoggerSentryTransport = (logger: Logger) => { + logger.attachTransport((log) => { + const { message, attributes, _meta, ...inheritedAttributes } = log as ILogObj & { + message: string; + attributes: Record; + }; + + if (!message || !attributes) { + console.error("Logger is not configured properly. Sentry transport will not be attached."); + + return; + } + + logger.attachTransport((log) => { + Sentry?.addBreadcrumb?.({ + message: message, + type: levelToBreadcrumbType(log._meta.logLevelName), + level: loggerLevelToSentryLevel(log._meta.logLevelName), + // @ts-ignore - Sentry only allows number type, but ISOString is valid + timestamp: log._meta.date.toISOString(), + data: attributes, + }); + }); + }); +}; diff --git a/src/logger.ts b/src/lib/logger/logger.ts similarity index 69% rename from src/logger.ts rename to src/lib/logger/logger.ts index 129fdbd..2e0c5f0 100644 --- a/src/logger.ts +++ b/src/lib/logger/logger.ts @@ -1,24 +1,5 @@ import { ILogObj, ILogObjMeta, Logger } from "tslog"; -export const attachLoggerConsoleTransport = (logger: Logger) => { - logger.attachTransport((log) => { - const { - message, - attributes, - _meta: { date, name, parentNames }, - } = log as ILogObj & - ILogObjMeta & { - message: string; - attributes: Record; - }; - - const formattedName = `${(parentNames ?? []).join(":")}:${name}`; - const formattedDate = date.toISOString(); - - console.log(`\x1b[2m ${formattedDate} ${formattedName}\x1b[0m \t${message}`, attributes); - }); -}; - function isObject(item: unknown) { return typeof item === "object" && !Array.isArray(item) && item !== null; } @@ -72,9 +53,6 @@ export const logger = new Logger({ }, }); -// TODO: Remove me -attachLoggerConsoleTransport(logger); - export const createLogger = (name: string, params?: Record) => logger.getSubLogger( { diff --git a/src/lib/otel/get-attributes-from-request.ts b/src/lib/otel/get-attributes-from-request.ts new file mode 100644 index 0000000..0143569 --- /dev/null +++ b/src/lib/otel/get-attributes-from-request.ts @@ -0,0 +1,33 @@ +import { NextApiRequest } from "next"; +import { SemanticAttributes } from "@opentelemetry/semantic-conventions"; +import { SALEOR_API_URL_HEADER } from "@saleor/app-sdk/const"; + +const pruneEmptyKeys = (obj: Record): Record => { + const clonedObj = { ...obj }; + + Object.keys(clonedObj).forEach((key) => { + const value = obj[key]; + + if (value === undefined || value === null || value === "") { + obj[key] === undefined && delete obj[key]; + } + }); + + return clonedObj as Record; +}; + +export const getAttributesFromRequest = (request: NextApiRequest) => { + const attributes = { + [SemanticAttributes.FAAS_EXECUTION]: request.headers["x-vercel-proxy-signature-ts"] as string, + [SemanticAttributes.HTTP_USER_AGENT]: request.headers["user-agent"] as string, + [SemanticAttributes.HTTP_TARGET]: request.headers.referer as string, + [SemanticAttributes.NET_HOST_NAME]: request.headers.host as string, + [SemanticAttributes.HTTP_METHOD]: (request.method ?? "").toUpperCase(), + saleorApiUrl: request.headers[SALEOR_API_URL_HEADER] as string, + "url.path": request.url, + vercelRequestId: request.headers["x-vercel-id"], + requestId: request.headers["x-vercel-proxy-signature-ts"] as string, + } as const ; + + return pruneEmptyKeys(attributes); +}; diff --git a/src/lib/otel/instrumentation.ts b/src/lib/otel/instrumentation.ts new file mode 100644 index 0000000..916ab28 --- /dev/null +++ b/src/lib/otel/instrumentation.ts @@ -0,0 +1,82 @@ +import { DiagConsoleLogger, DiagLogLevel, SpanStatusCode, diag } from "@opentelemetry/api"; +import { W3CTraceContextPropagator } from "@opentelemetry/core"; +import { HttpInstrumentation } from "@opentelemetry/instrumentation-http"; +import { Resource } from "@opentelemetry/resources"; +import { NodeSDK } from "@opentelemetry/sdk-node"; +import { + SemanticAttributes, + SemanticResourceAttributes, +} from "@opentelemetry/semantic-conventions"; +import { type ClientRequest } from "node:http"; +import { otelLogsProcessor } from "./otel-logs-setup"; +import { batchSpanProcessor } from "./otel-traces-setup"; + +if (process.env.ENABLE_OTEL_RUNTIME_LOGS === "true") { + const getLogLevel = () => { + switch (process.env.OTEL_LOG_LEVEL) { + case "debug": + return DiagLogLevel.DEBUG; + case "error": + return DiagLogLevel.ERROR; + case "warn": + return DiagLogLevel.WARN; + case "verbose": + return DiagLogLevel.VERBOSE; + case "all": + return DiagLogLevel.ALL; + case "none": + return DiagLogLevel.NONE; + case "info": + default: + return DiagLogLevel.INFO; + } + }; + + diag.setLogger(new DiagConsoleLogger(), getLogLevel()); +} + +export const otelSdk = new NodeSDK({ + resource: new Resource({ + [SemanticResourceAttributes.SERVICE_NAME]: process.env.OTEL_SERVICE_NAME, + "commit-sha": process.env.VERCEL_GIT_COMMIT_SHA, + [SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: process.env.ENV, + }), + spanProcessor: batchSpanProcessor, + logRecordProcessor: otelLogsProcessor, + textMapPropagator: new W3CTraceContextPropagator(), + instrumentations: [ + new HttpInstrumentation({ + requireParentforIncomingSpans: true, + requireParentforOutgoingSpans: true, + /** + * HTTP spans are creates as entry spans/siblings, instead of children. + * TODO Fix this. + */ + applyCustomAttributesOnSpan: (span, req, response) => { + span.setAttribute(SemanticAttributes.HTTP_ROUTE, (req as ClientRequest)?.path); + span.setAttribute(SemanticAttributes.HTTP_HOST, (req as ClientRequest)?.host); + + if (response.statusCode) { + span.setAttribute(SemanticAttributes.HTTP_STATUS_CODE, response.statusCode); + } + + if (response.statusCode && response.statusCode >= 400) { + span.setStatus({ + code: SpanStatusCode.ERROR, + }); + } + + if (response.statusCode && response.statusCode >= 200 && response.statusCode < 400) { + span.setStatus({ + code: SpanStatusCode.OK, + }); + } + }, + + ignoreOutgoingUrls: [ + (url) => url.includes("ingest.sentry.io"), + (url) => url.includes("/v1/logs"), + ], + }), + ], +}); diff --git a/src/lib/otel/lib/observability-attributes.ts b/src/lib/otel/lib/observability-attributes.ts new file mode 100644 index 0000000..e4617bc --- /dev/null +++ b/src/lib/otel/lib/observability-attributes.ts @@ -0,0 +1,14 @@ +export const ObservabilityAttributes = { + SALEOR_API_URL: "saleorApiUrl", + SALEOR_VERSION: "saleorVersion", + CHANNEL_SLUG: "channelSlug", + TRANSACTION_ID: "transactionId", +} as const; + +export enum GraphQLAttributeNames { + OPERATION_TYPE = "graphql.operation.type", + OPERATION_NAME = "graphql.operation.name", + OPERATION_BODY = "graphql.operation.body", + OPERATION_KEY = "graphql.operation.key", + VARIABLES = "graphql.variables.", +} diff --git a/src/lib/otel/lib/race.ts b/src/lib/otel/lib/race.ts new file mode 100644 index 0000000..6e88f70 --- /dev/null +++ b/src/lib/otel/lib/race.ts @@ -0,0 +1,24 @@ +export function race({ + promise, + timeout, + error, +}: { + promise: Promise; + timeout: number; + error: Error; +}): Promise { + let timer: NodeJS.Timeout | null = null; + + return Promise.race([ + new Promise((res, rej) => { + timer = setTimeout(() => { + rej(error); + }, timeout); + }), + promise.finally(() => { + if (timer) { + clearTimeout(timer); + } + }), + ]); +} diff --git a/src/lib/otel/otel-exchange.ts b/src/lib/otel/otel-exchange.ts new file mode 100644 index 0000000..8bae1d9 --- /dev/null +++ b/src/lib/otel/otel-exchange.ts @@ -0,0 +1,77 @@ +import { type Span, SpanKind, SpanStatusCode, context } from "@opentelemetry/api"; +import { type CombinedError, type Operation, makeOperation, mapExchange } from "urql"; +import { getOtelTracer } from "./otel-tracer"; +import { GraphQLAttributeNames, ObservabilityAttributes } from "./lib/observability-attributes"; +import { addInputVariableAttributes, addRequestHeaderAttributes } from "./otel-graphql-utils"; +import { SemanticAttributes } from "@opentelemetry/semantic-conventions"; + +type Definition = { + name: { + value: string; + }; +}; + +interface ExtendedFetchOptions extends RequestInit { + headers: Record; +} + +type ExtendedOperationContext = Operation["context"] & { + span: Span; + fetchOptions?: ExtendedFetchOptions; +}; + +interface ExtendedOperation extends Operation { + context: ExtendedOperationContext; +} + +export const otelExchange = mapExchange({ + onOperation(operation: ExtendedOperation) { + const span = getOtelTracer().startSpan( + "graphql-request", + { + kind: SpanKind.CLIENT, + }, + context.active(), + ); + + span.setAttribute( + GraphQLAttributeNames.OPERATION_NAME, + `${(operation.query.definitions[0] as Definition).name.value ?? "unknown"}`, + ); + + span.setAttribute(GraphQLAttributeNames.OPERATION_TYPE, operation.kind); + + span.setAttribute( + GraphQLAttributeNames.OPERATION_BODY, + operation.query.loc?.source.body ?? "unknown", + ); + + span.setAttribute(GraphQLAttributeNames.OPERATION_KEY, operation.key); + + span.setAttribute(ObservabilityAttributes.SALEOR_API_URL, operation.context.url); + + span.setAttribute(SemanticAttributes.HTTP_URL, operation.context.url); + + addRequestHeaderAttributes(span, operation.context.fetchOptions?.headers); + if (operation.variables) { + addInputVariableAttributes(span, operation.variables); + } + + return makeOperation(operation.kind, operation, { + ...operation.context, + span, + }); + }, + // @ts-expect-error - small hack, we're extending `operation` with `span` + onResult({ error, operation }: { operation: ExtendedOperation; error?: CombinedError }) { + const span = operation.context.span; + + if (error) { + span.recordException(error); + } + + span.setStatus({ code: error ? SpanStatusCode.ERROR : SpanStatusCode.OK }); + + span.end(); + }, +}); diff --git a/src/lib/otel/otel-graphql-utils.ts b/src/lib/otel/otel-graphql-utils.ts new file mode 100644 index 0000000..37c94ab --- /dev/null +++ b/src/lib/otel/otel-graphql-utils.ts @@ -0,0 +1,43 @@ +import { type Span } from "@opentelemetry/api"; +import { GraphQLAttributeNames } from "./lib/observability-attributes"; + +export const addRequestHeaderAttributes = ( + span: Span, + headers?: Record, +) => { + if (!headers) return; + + Object.entries(headers).forEach(([key, value]) => { + if (key.toLowerCase().includes("authorization")) { + span.setAttribute(`http.request.header.${key}`, "(redacted)"); + + return; + } + + if (Array.isArray(value)) { + span.setAttribute(`http.request.header.${key}`, value.join(", ")); + } else { + span.setAttribute(`http.request.header.${key}`, String(value)); + } + }); +}; + +const addInputVariableAttribute = (span: Span, key: string, variable: any) => { + if (Array.isArray(variable)) { + variable.forEach((value, idx) => { + addInputVariableAttribute(span, `${key}.${idx}`, value); + }); + } else if (variable instanceof Object) { + Object.entries(variable).forEach(([nestedKey, value]) => { + addInputVariableAttribute(span, `${key}.${nestedKey}`, value); + }); + } else { + span.setAttribute(`${GraphQLAttributeNames.VARIABLES}${String(key)}`, variable); + } +}; + +export const addInputVariableAttributes = (span: Span, variableValues: { [key: string]: any }) => { + Object.entries(variableValues).forEach(([key, value]) => { + addInputVariableAttribute(span, key, value); + }); +}; diff --git a/src/lib/otel/otel-logs-setup.ts b/src/lib/otel/otel-logs-setup.ts new file mode 100644 index 0000000..7685cfb --- /dev/null +++ b/src/lib/otel/otel-logs-setup.ts @@ -0,0 +1,32 @@ +import { logs } from "@opentelemetry/api-logs"; +import { BatchLogRecordProcessor, LoggerProvider } from "@opentelemetry/sdk-logs"; +import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http"; +import { + detectResourcesSync, + envDetectorSync, + hostDetectorSync, + osDetectorSync, + processDetector, +} from "@opentelemetry/resources"; +import { sharedOtelConfig } from "./shared-config"; + +const batchLogRecordProcessor = new BatchLogRecordProcessor( + new OTLPLogExporter({ + headers: sharedOtelConfig.exporterHeaders, + }), + sharedOtelConfig.batchProcessorConfig, +); + +export const otelLogsProcessor = batchLogRecordProcessor; + +const detectedResource = detectResourcesSync({ + detectors: [envDetectorSync, hostDetectorSync, osDetectorSync, processDetector], +}); + +export const loggerProvider = new LoggerProvider({ + resource: detectedResource, + forceFlushTimeoutMillis: sharedOtelConfig.flushTimeout, +}); + +loggerProvider.addLogRecordProcessor(otelLogsProcessor); +logs.setGlobalLoggerProvider(loggerProvider); diff --git a/src/lib/otel/otel-tracer.ts b/src/lib/otel/otel-tracer.ts new file mode 100644 index 0000000..2cfbc01 --- /dev/null +++ b/src/lib/otel/otel-tracer.ts @@ -0,0 +1,5 @@ +import { trace } from "@opentelemetry/api"; + +const ROOT_TRACE_NAME = "app-api-handler"; + +export const getOtelTracer = () => trace.getTracer(ROOT_TRACE_NAME); diff --git a/src/lib/otel/otel-traces-setup.ts b/src/lib/otel/otel-traces-setup.ts new file mode 100644 index 0000000..dfd64d9 --- /dev/null +++ b/src/lib/otel/otel-traces-setup.ts @@ -0,0 +1,30 @@ +import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"; +import { BatchSpanProcessor, ReadableSpan } from "@opentelemetry/sdk-trace-base"; +import { sharedOtelConfig } from "./shared-config"; + +class CustomSpanProcessor extends BatchSpanProcessor { + onEnd(span: ReadableSpan): void { + /** + * Next.js has a bug and API functions don't propagate proper name. + * https://github.com/vercel/next.js/blob/faa44210340d2ef19da6252e40a9b3e66f214637/packages/next/src/server/base-server.ts#L821 + * + * Filter them out - they duplicate child span that have similar attributes. + * + * TODO: Verify with latest next.js versions + */ + const isBrokenNextSpan = + span.instrumentationLibrary.name === "next.js" && !span.attributes["next.route"]; + + if (!isBrokenNextSpan) { + super.onEnd(span); + } + } +} + +export const batchSpanProcessor = new CustomSpanProcessor( + new OTLPTraceExporter({ + headers: sharedOtelConfig.exporterHeaders, + timeoutMillis: sharedOtelConfig.flushTimeout, + }), + sharedOtelConfig.batchProcessorConfig, +); diff --git a/src/lib/otel/otel-wrapper.ts b/src/lib/otel/otel-wrapper.ts new file mode 100644 index 0000000..4605b37 --- /dev/null +++ b/src/lib/otel/otel-wrapper.ts @@ -0,0 +1,126 @@ +import { SpanKind, SpanStatusCode, type Span } from "@opentelemetry/api"; +import { SemanticAttributes } from "@opentelemetry/semantic-conventions"; +import { type NextApiHandler, type NextApiRequest, type NextApiResponse } from "next"; +import { otelSdk } from "./instrumentation"; + +import { race } from "./lib/race"; +import { getOtelTracer } from "./otel-tracer"; + +import { getAttributesFromRequest } from "./get-attributes-from-request"; +import { loggerProvider, otelLogsProcessor } from "./otel-logs-setup"; +import { batchSpanProcessor } from "./otel-traces-setup"; +import { sharedOtelConfig } from "./shared-config"; + +const tracer = getOtelTracer(); + +if (process.env.OTEL_ENABLED === "true" && process.env.OTEL_SERVICE_NAME) { + otelSdk.start(); +} + +const OTEL_FLUSH_TIMEOUT = sharedOtelConfig.flushTimeout; + +const flushOtel = async () => { + await race({ + promise: loggerProvider.forceFlush(), + error: new Error("Timeout flushing OTEL logs from provider"), + timeout: OTEL_FLUSH_TIMEOUT, + }); + + await race({ + promise: Promise.all([batchSpanProcessor.forceFlush(), otelLogsProcessor.forceFlush()]), + error: new Error("Timeout flushing OTEL items from processors"), + timeout: OTEL_FLUSH_TIMEOUT, + }); +}; + +/** + * TODO: Consider injecting into Next.js config, to automatically wrap routes and infer static route name from file name + */ +export const withOtel = (handler: NextApiHandler, staticRouteName: string): NextApiHandler => { + if (process.env.OTEL_ENABLED !== "true") { + return handler; + } + + return new Proxy(handler, { + apply: async ( + wrappingTarget, + thisArg, + args: [NextApiRequest | undefined, NextApiResponse | undefined] + ) => { + const [req, res] = args; + + if (!req || !res) { + console.warn("No request and/or response objects found, OTEL is not set-up"); + + // @ts-expect-error runtime check + return wrappingTarget.apply(thisArg, args); + } + + const attributesFromRequest = getAttributesFromRequest(req); + + return tracer.startActiveSpan( + `${attributesFromRequest[SemanticAttributes.HTTP_METHOD]} ${staticRouteName}`, + { + kind: SpanKind.SERVER, + attributes: attributesFromRequest, + }, + async (span) => { + span.setAttribute(SemanticAttributes.HTTP_ROUTE, staticRouteName); + + const originalResEnd = res.end; + + /** + * Override native res.end to flush OTEL traces before it ends + */ + // @ts-expect-error - this is a hack to get around Vercel freezing lambda's + res.end = async function (this: unknown, ...args: unknown[]) { + span.setAttribute(SemanticAttributes.HTTP_STATUS_CODE, res.statusCode); + span.end(); + + try { + await flushOtel(); + } catch (e) { + console.error("Failed to flush OTEL", { error: e }); + // noop - don't block return even if we loose traces + } + + // @ts-expect-error passthrough args to the original function + return originalResEnd.apply(this, args); + }; + + try { + // return await loggerContext.wrap(() => wrappingTarget.apply(thisArg, [req, res])); + + wrappingTarget.apply(thisArg, [req, res]); + } catch (error) { + span.setAttribute(SemanticAttributes.HTTP_STATUS_CODE, 500); + + setErrorOnSpan(error, span); + + span.end(); + + try { + await flushOtel(); + } catch (e) { + console.error("Failed to flush OTEL", { error: e }); + } + + /** + * Rethrow error so that Next.js (and other wrappers like Sentry) can handle it + */ + throw error; + } + } + ); + }, + }); +}; + +export function setErrorOnSpan(error: unknown, span: Span) { + span.setStatus({ code: SpanStatusCode.ERROR }); + + if (error instanceof Error) { + span.setAttribute("error.type", error.name); + span.recordException(error); + } +} diff --git a/src/lib/otel/shared-config.ts b/src/lib/otel/shared-config.ts new file mode 100644 index 0000000..f7054d6 --- /dev/null +++ b/src/lib/otel/shared-config.ts @@ -0,0 +1,27 @@ +import { BufferConfig } from "@opentelemetry/sdk-logs"; + +const FLUSH_TIMEOUT = 1_000; + +const batchProcessorConfig: BufferConfig = { + exportTimeoutMillis: FLUSH_TIMEOUT, + maxExportBatchSize: 1024, + maxQueueSize: 1024, + /** + * Long delay that will be for sure longer than the lambda timeout. + * Avoid mid-execution flushes, because we flush them manually in the end. + */ + scheduledDelayMillis: 2 * 5 * 60 * 1000, +}; + +export const sharedOtelConfig = { + flushTimeout: FLUSH_TIMEOUT, + batchProcessorConfig: batchProcessorConfig, + exporterHeaders: { + /** + * This is the token that is used to authenticate with the OTEL collector set up in the Saleor infrastructure. + * + * In case of forked usage, leave this field empty, but protecting collector is recommended. + */ + "x-alb-access-token": process.env.OTEL_ACCESS_TOKEN, + }, +} as const; diff --git a/src/logger-context.ts b/src/logger-context.ts new file mode 100644 index 0000000..965add8 --- /dev/null +++ b/src/logger-context.ts @@ -0,0 +1,6 @@ +import { LoggerContext } from "./lib/logger/logger-context"; + +/** + * Server-side only + */ +export const loggerContext = new LoggerContext(); diff --git a/src/pages/api/manifest.ts b/src/pages/api/manifest.ts index bb8ef6f..ab015c6 100644 --- a/src/pages/api/manifest.ts +++ b/src/pages/api/manifest.ts @@ -8,70 +8,79 @@ import { transactionProcessSessionWebhook } from "./webhooks/transaction-process import { transactionRefundRequestedWebhook } from "./webhooks/transaction-refund-requested"; import { transactionChargeRequestedWebhook } from "./webhooks/transaction-charge-requested"; import { transactionCancelationRequestedWebhook } from "./webhooks/transaction-cancel-requested"; +import { wrapWithLoggerContext } from "@/lib/logger/logger-context"; +import { withOtel } from "@/lib/otel/otel-wrapper"; +import { loggerContext } from "@/logger-context"; /** * App SDK helps with the valid Saleor App Manifest creation. Read more: * https://github.com/saleor/saleor-app-sdk/blob/main/docs/api-handlers.md#manifest-handler-factory */ -export default createManifestHandler({ - async manifestFactory({ appBaseUrl, request }) { - /** - * Allow to overwrite default app base url, to enable Docker support. - * - * See docs: https://docs.saleor.io/docs/3.x/developer/extending/apps/local-app-development - */ - const iframeBaseUrl = process.env.APP_IFRAME_BASE_URL ?? appBaseUrl; - const apiBaseURL = process.env.APP_API_BASE_URL ?? appBaseUrl; - - const manifest: AppManifest = { - name: "Dummy Payment App", - tokenTargetUrl: `${apiBaseURL}/api/register`, - appUrl: iframeBaseUrl, - /** - * Set permissions for app if needed - * https://docs.saleor.io/docs/3.x/developer/permissions - */ - permissions: [ +export default wrapWithLoggerContext( + withOtel( + createManifestHandler({ + async manifestFactory({ appBaseUrl, request }) { /** - * Add permission to allow "ORDER_CREATED" webhook registration. + * Allow to overwrite default app base url, to enable Docker support. * - * This can be removed + * See docs: https://docs.saleor.io/docs/3.x/developer/extending/apps/local-app-development */ - "MANAGE_CHECKOUTS", - "HANDLE_PAYMENTS", - "MANAGE_ORDERS", - ], - id: "saleor.io.dummy-payment-app", - version: packageJson.version, - /** - * Configure webhooks here. They will be created in Saleor during installation - * Read more - * https://docs.saleor.io/docs/3.x/developer/api-reference/webhooks/objects/webhook - * - * Easiest way to create webhook is to use app-sdk - * https://github.com/saleor/saleor-app-sdk/blob/main/docs/saleor-webhook.md - */ - webhooks: [ - paymentGatewayInitializeSessionWebhook.getWebhookManifest(apiBaseURL), - transactionInitializeSessionWebhook.getWebhookManifest(apiBaseURL), - transactionProcessSessionWebhook.getWebhookManifest(apiBaseURL), - transactionRefundRequestedWebhook.getWebhookManifest(apiBaseURL), - transactionChargeRequestedWebhook.getWebhookManifest(apiBaseURL), - transactionCancelationRequestedWebhook.getWebhookManifest(apiBaseURL), - ], - /** - * Optionally, extend Dashboard with custom UIs - * https://docs.saleor.io/docs/3.x/developer/extending/apps/extending-dashboard-with-apps - */ - extensions: [], - author: "Saleor Commerce", - brand: { - logo: { - default: `${apiBaseURL}/logo.png`, - }, - }, - }; + const iframeBaseUrl = process.env.APP_IFRAME_BASE_URL ?? appBaseUrl; + const apiBaseURL = process.env.APP_API_BASE_URL ?? appBaseUrl; - return manifest; - }, -}); + const manifest: AppManifest = { + name: "Dummy Payment App", + tokenTargetUrl: `${apiBaseURL}/api/register`, + appUrl: iframeBaseUrl, + /** + * Set permissions for app if needed + * https://docs.saleor.io/docs/3.x/developer/permissions + */ + permissions: [ + /** + * Add permission to allow "ORDER_CREATED" webhook registration. + * + * This can be removed + */ + "MANAGE_CHECKOUTS", + "HANDLE_PAYMENTS", + "MANAGE_ORDERS", + ], + id: "saleor.io.dummy-payment-app", + version: packageJson.version, + /** + * Configure webhooks here. They will be created in Saleor during installation + * Read more + * https://docs.saleor.io/docs/3.x/developer/api-reference/webhooks/objects/webhook + * + * Easiest way to create webhook is to use app-sdk + * https://github.com/saleor/saleor-app-sdk/blob/main/docs/saleor-webhook.md + */ + webhooks: [ + paymentGatewayInitializeSessionWebhook.getWebhookManifest(apiBaseURL), + transactionInitializeSessionWebhook.getWebhookManifest(apiBaseURL), + transactionProcessSessionWebhook.getWebhookManifest(apiBaseURL), + transactionRefundRequestedWebhook.getWebhookManifest(apiBaseURL), + transactionChargeRequestedWebhook.getWebhookManifest(apiBaseURL), + transactionCancelationRequestedWebhook.getWebhookManifest(apiBaseURL), + ], + /** + * Optionally, extend Dashboard with custom UIs + * https://docs.saleor.io/docs/3.x/developer/extending/apps/extending-dashboard-with-apps + */ + extensions: [], + author: "Saleor Commerce", + brand: { + logo: { + default: `${apiBaseURL}/logo.png`, + }, + }, + }; + + return manifest; + }, + }), + "/api/manifest" + ), + loggerContext +); diff --git a/src/pages/api/register.ts b/src/pages/api/register.ts index 382f71a..f97bf8b 100644 --- a/src/pages/api/register.ts +++ b/src/pages/api/register.ts @@ -1,6 +1,12 @@ import { createAppRegisterHandler } from "@saleor/app-sdk/handlers/next"; import { saleorApp } from "../../saleor-app"; +import { wrapWithLoggerContext } from "@/lib/logger/logger-context"; +import { withOtel } from "@/lib/otel/otel-wrapper"; +import { loggerContext } from "@/logger-context"; +import { createLogger } from "@/lib/logger/logger"; + +const logger = createLogger("createAppRegisterHandler"); const allowedUrlsPattern = process.env.ALLOWED_DOMAIN_PATTERN; @@ -8,17 +14,33 @@ const allowedUrlsPattern = process.env.ALLOWED_DOMAIN_PATTERN; * Required endpoint, called by Saleor to install app. * It will exchange tokens with app, so saleorApp.apl will contain token */ -export default createAppRegisterHandler({ - apl: saleorApp.apl, - allowedSaleorUrls: [ - (url) => { - if (allowedUrlsPattern) { - const regex = new RegExp(allowedUrlsPattern); +export default wrapWithLoggerContext( + withOtel( + createAppRegisterHandler({ + apl: saleorApp.apl, + /** + * Prohibit installation from Saleors other than specified by the regex. + * Regex source is ENV so if ENV is not set, all installations will be allowed. + */ + allowedSaleorUrls: [ + (url) => { + if (allowedUrlsPattern) { + const regex = new RegExp(allowedUrlsPattern); + + return regex.test(url); + } - return regex.test(url); - } + return true; + }, + ], + onAuthAplSaved: async (_req, context) => { + logger.info("Dummy payment app configuration set up successfully", { + saleorApiUrl: context.authData.saleorApiUrl, + }); + }, + }), + "/api/register" + ), - return true; - }, - ], -}); + loggerContext +); diff --git a/src/pages/api/webhooks/payment-gateway-initialize-session.ts b/src/pages/api/webhooks/payment-gateway-initialize-session.ts index ffe861e..46de2ff 100644 --- a/src/pages/api/webhooks/payment-gateway-initialize-session.ts +++ b/src/pages/api/webhooks/payment-gateway-initialize-session.ts @@ -1,16 +1,13 @@ import { SaleorSyncWebhook } from "@saleor/app-sdk/handlers/next"; -import { createClient } from "../../../lib/create-graphq-client"; -import { saleorApp } from "../../../saleor-app"; +import { saleorApp } from "@/saleor-app"; import { PaymentGatewayInitializeSessionDocument, PaymentGatewayInitializeSessionEventFragment, -} from "../../../../generated/graphql"; +} from "@/generated/graphql"; +import { wrapWithLoggerContext } from "@/lib/logger/logger-context"; +import { withOtel } from "@/lib/otel/otel-wrapper"; +import { loggerContext } from "@/logger-context"; -/** - * Create abstract Webhook. It decorates handler and performs security checks under the hood. - * - * paymentGatewayInitializeSessionWebhook.getWebhookManifest() must be called in api/manifest too! - */ export const paymentGatewayInitializeSessionWebhook = new SaleorSyncWebhook({ name: "Payment Gateway Initialize Session", @@ -20,50 +17,19 @@ export const paymentGatewayInitializeSessionWebhook = query: PaymentGatewayInitializeSessionDocument, }); -/** - * Export decorated Next.js handler, which adds extra context - */ -export default paymentGatewayInitializeSessionWebhook.createHandler((req, res, ctx) => { - const { - /** - * Access payload from Saleor - defined above - */ - payload, - /** - * Saleor event that triggers the webhook (here - ORDER_CREATED) - */ - event, - /** - * App's URL - */ - baseUrl, - /** - * Auth data (from APL) - contains token and saleorApiUrl that can be used to construct graphQL client - */ - authData, - } = ctx; - - /** - * Create GraphQL client to interact with Saleor API. - */ - const client = createClient(authData.saleorApiUrl, async () => ({ token: authData.token })); - - /** - * Now you can fetch additional data using urql. - * https://formidable.com/open-source/urql/docs/api/core/#clientquery - */ - - // const data = await client.query().toPromise() - - /** - * Inform Saleor that webhook was delivered properly. - */ - return res.status(200).json({ - data: { - ok: true, - }, - }); -}); +export default wrapWithLoggerContext( + withOtel( + paymentGatewayInitializeSessionWebhook.createHandler((req, res, ctx) => { + return res.status(200).json({ + data: { + ok: true, + }, + }); + }), + "/api/webhooks/payment-gateway-initialize-session" + ), + loggerContext +); /** * Disable body parser for this endpoint, so signature can be verified diff --git a/src/pages/api/webhooks/transaction-cancel-requested.ts b/src/pages/api/webhooks/transaction-cancel-requested.ts index c6a4cfa..7f365ea 100644 --- a/src/pages/api/webhooks/transaction-cancel-requested.ts +++ b/src/pages/api/webhooks/transaction-cancel-requested.ts @@ -1,18 +1,21 @@ import { SaleorSyncWebhook } from "@saleor/app-sdk/handlers/next"; import { v7 as uuidv7 } from "uuid"; -import { saleorApp } from "../../../saleor-app"; +import { saleorApp } from "@/saleor-app"; import { TransactionCancelRequestedDocument, TransactionCancelRequestedEventFragment, -} from "../../../../generated/graphql"; -import { createLogger } from "../../../logger"; +} from "@/generated/graphql"; import { CancelationRequestedResponse, cancelationRequestedInputSchema, -} from "../../../modules/validation/cancel-webhook"; -import { getZodErrorMessage } from "../../../lib/zod-error"; -import { getTransactionActions } from "../../../lib/transaction-actions"; +} from "@/modules/validation/cancel-webhook"; +import { getZodErrorMessage } from "@/lib/zod-error"; +import { getTransactionActions } from "@/lib/transaction-actions"; import { AppUrlGenerator } from "@/modules/url/app-url-generator"; +import { createLogger } from "@/lib/logger/logger"; +import { wrapWithLoggerContext } from "@/lib/logger/logger-context"; +import { withOtel } from "@/lib/otel/otel-wrapper"; +import { loggerContext } from "@/logger-context"; export const transactionCancelationRequestedWebhook = new SaleorSyncWebhook({ @@ -23,48 +26,56 @@ export const transactionCancelationRequestedWebhook = query: TransactionCancelRequestedDocument, }); -export default transactionCancelationRequestedWebhook.createHandler((req, res, ctx) => { - const logger = createLogger("transaction-cancelation-requested"); - const { payload } = ctx; +export default wrapWithLoggerContext( + withOtel( + transactionCancelationRequestedWebhook.createHandler((req, res, ctx) => { + const logger = createLogger("transaction-cancelation-requested"); + const { payload } = ctx; - logger.debug("Received webhook", { payload }); + logger.debug("Received webhook", { payload }); - const payloadResult = cancelationRequestedInputSchema.safeParse(payload); + const payloadResult = cancelationRequestedInputSchema.safeParse(payload); - if (payloadResult.error) { - logger.warn("Data received from Saleor didn't pass validation", { error: payloadResult.error }); + if (payloadResult.error) { + logger.warn("Data received from Saleor didn't pass validation", { + error: payloadResult.error, + }); - const failureResponse: CancelationRequestedResponse = { - pspReference: uuidv7(), - result: "CANCEL_FAILURE", - message: getZodErrorMessage(payloadResult.error), - actions: getTransactionActions("CANCEL_FAILURE"), - amount: 0, - }; + const failureResponse: CancelationRequestedResponse = { + pspReference: uuidv7(), + result: "CANCEL_FAILURE", + message: getZodErrorMessage(payloadResult.error), + actions: getTransactionActions("CANCEL_FAILURE"), + amount: 0, + }; - logger.info("Returning error response from Saleor", { response: failureResponse }); + logger.info("Returning error response from Saleor", { response: failureResponse }); - return res.status(200).json(failureResponse); - } + return res.status(200).json(failureResponse); + } - const parsedPayload = payloadResult.data; - const amount = parsedPayload.transaction.authorizedAmount.amount; - const urlGenerator = new AppUrlGenerator(ctx.authData); + const parsedPayload = payloadResult.data; + const amount = parsedPayload.transaction.authorizedAmount.amount; + const urlGenerator = new AppUrlGenerator(ctx.authData); - const successResponse: CancelationRequestedResponse = { - pspReference: uuidv7(), - // TODO: Add result customization - result: "CANCEL_SUCCESS", - message: "Great success!", - actions: getTransactionActions("CANCEL_SUCCESS"), - amount, - externalUrl: urlGenerator.getTransactionDetailsUrl(parsedPayload.transaction.id), - }; + const successResponse: CancelationRequestedResponse = { + pspReference: uuidv7(), + // TODO: Add result customization + result: "CANCEL_SUCCESS", + message: "Great success!", + actions: getTransactionActions("CANCEL_SUCCESS"), + amount, + externalUrl: urlGenerator.getTransactionDetailsUrl(parsedPayload.transaction.id), + }; - logger.info("Returning response to Saleor", { response: successResponse }); + logger.info("Returning response to Saleor", { response: successResponse }); - return res.status(200).json(successResponse); -}); + return res.status(200).json(successResponse); + }), + "/api/webhooks/transaction-cancel-requested" + ), + loggerContext +); /** * Disable body parser for this endpoint, so signature can be verified diff --git a/src/pages/api/webhooks/transaction-charge-requested.ts b/src/pages/api/webhooks/transaction-charge-requested.ts index 92a15fa..94a7e3e 100644 --- a/src/pages/api/webhooks/transaction-charge-requested.ts +++ b/src/pages/api/webhooks/transaction-charge-requested.ts @@ -1,18 +1,21 @@ import { SaleorSyncWebhook } from "@saleor/app-sdk/handlers/next"; import { v7 as uuidv7 } from "uuid"; -import { saleorApp } from "../../../saleor-app"; +import { saleorApp } from "@/saleor-app"; import { TransactionChargeRequestedDocument, TransactionChargeRequestedEventFragment, -} from "../../../../generated/graphql"; -import { createLogger } from "../../../logger"; +} from "@/generated/graphql"; +import { createLogger } from "@/lib/logger/logger"; import { ChargeRequestedResponse, chargeRequestedInputSchema, -} from "../../../modules/validation/charge-webhook"; -import { getZodErrorMessage } from "../../../lib/zod-error"; -import { getTransactionActions } from "../../../lib/transaction-actions"; +} from "@/modules/validation/charge-webhook"; +import { getZodErrorMessage } from "@/lib/zod-error"; +import { getTransactionActions } from "@/lib/transaction-actions"; import { AppUrlGenerator } from "@/modules/url/app-url-generator"; +import { wrapWithLoggerContext } from "@/lib/logger/logger-context"; +import { withOtel } from "@/lib/otel/otel-wrapper"; +import { loggerContext } from "@/logger-context"; export const transactionChargeRequestedWebhook = new SaleorSyncWebhook({ @@ -23,48 +26,56 @@ export const transactionChargeRequestedWebhook = query: TransactionChargeRequestedDocument, }); -export default transactionChargeRequestedWebhook.createHandler((req, res, ctx) => { - const logger = createLogger("transaction-charge-requested"); - const { payload } = ctx; - const { amount } = payload.action; +export default wrapWithLoggerContext( + withOtel( + transactionChargeRequestedWebhook.createHandler((req, res, ctx) => { + const logger = createLogger("transaction-charge-requested"); + const { payload } = ctx; + const { amount } = payload.action; - logger.debug("Received webhook", { payload }); + logger.debug("Received webhook", { payload }); - const payloadResult = chargeRequestedInputSchema.safeParse(payload); + const payloadResult = chargeRequestedInputSchema.safeParse(payload); - if (payloadResult.error) { - logger.warn("Data received from Saleor didn't pass validation", { error: payloadResult.error }); + if (payloadResult.error) { + logger.warn("Data received from Saleor didn't pass validation", { + error: payloadResult.error, + }); - const failureResponse: ChargeRequestedResponse = { - pspReference: uuidv7(), - result: "CHARGE_FAILURE", - message: getZodErrorMessage(payloadResult.error), - actions: getTransactionActions("CHARGE_FAILURE"), - amount, - }; + const failureResponse: ChargeRequestedResponse = { + pspReference: uuidv7(), + result: "CHARGE_FAILURE", + message: getZodErrorMessage(payloadResult.error), + actions: getTransactionActions("CHARGE_FAILURE"), + amount, + }; - logger.info("Returning error response from Saleor", { response: failureResponse }); + logger.info("Returning error response from Saleor", { response: failureResponse }); - return res.status(200).json(failureResponse); - } + return res.status(200).json(failureResponse); + } - const parsedPayload = payloadResult.data; - const urlGenerator = new AppUrlGenerator(ctx.authData); + const parsedPayload = payloadResult.data; + const urlGenerator = new AppUrlGenerator(ctx.authData); - const successResponse: ChargeRequestedResponse = { - pspReference: uuidv7(), - // TODO: Add result customization - result: "CHARGE_SUCCESS", - message: "Great success!", - actions: getTransactionActions("CHARGE_SUCCESS"), - amount, - externalUrl: urlGenerator.getTransactionDetailsUrl(parsedPayload.transaction.id), - }; + const successResponse: ChargeRequestedResponse = { + pspReference: uuidv7(), + // TODO: Add result customization + result: "CHARGE_SUCCESS", + message: "Great success!", + actions: getTransactionActions("CHARGE_SUCCESS"), + amount, + externalUrl: urlGenerator.getTransactionDetailsUrl(parsedPayload.transaction.id), + }; - logger.info("Returning response to Saleor", { response: successResponse }); + logger.info("Returning response to Saleor", { response: successResponse }); - return res.status(200).json(successResponse); -}); + return res.status(200).json(successResponse); + }), + "/api/webhooks/transaction-charge-requested" + ), + loggerContext +); /** * Disable body parser for this endpoint, so signature can be verified diff --git a/src/pages/api/webhooks/transaction-initialize-session.ts b/src/pages/api/webhooks/transaction-initialize-session.ts index 06ce6e0..806b38f 100644 --- a/src/pages/api/webhooks/transaction-initialize-session.ts +++ b/src/pages/api/webhooks/transaction-initialize-session.ts @@ -1,17 +1,20 @@ import { SaleorSyncWebhook } from "@saleor/app-sdk/handlers/next"; -import { saleorApp } from "../../../saleor-app"; +import { saleorApp } from "@/saleor-app"; import { TransactionEventTypeEnum, TransactionFlowStrategyEnum, TransactionInitializeSessionDocument, TransactionInitializeSessionEventFragment, -} from "../../../../generated/graphql"; +} from "@/generated/graphql"; import { v7 as uuidv7 } from "uuid"; -import { getTransactionActions } from "../../../lib/transaction-actions"; -import { createLogger } from "../../../logger"; -import { getZodErrorMessage } from "../../../lib/zod-error"; -import { dataSchema, ResponseType } from "../../../modules/validation/sync-transaction"; +import { getTransactionActions } from "@/lib/transaction-actions"; +import { createLogger } from "@/lib/logger/logger"; +import { getZodErrorMessage } from "@/lib/zod-error"; +import { dataSchema, ResponseType } from "@/modules/validation/sync-transaction"; import { AppUrlGenerator } from "@/modules/url/app-url-generator"; +import { wrapWithLoggerContext } from "@/lib/logger/logger-context"; +import { withOtel } from "@/lib/otel/otel-wrapper"; +import { loggerContext } from "@/logger-context"; export const transactionInitializeSessionWebhook = new SaleorSyncWebhook({ @@ -22,57 +25,63 @@ export const transactionInitializeSessionWebhook = query: TransactionInitializeSessionDocument, }); -export default transactionInitializeSessionWebhook.createHandler((req, res, ctx) => { - const logger = createLogger("transaction-initialize-session"); - const { payload } = ctx; - const { actionType, amount } = payload.action; +export default wrapWithLoggerContext( + withOtel( + transactionInitializeSessionWebhook.createHandler((req, res, ctx) => { + const logger = createLogger("transaction-initialize-session"); + const { payload } = ctx; + const { actionType, amount } = payload.action; - logger.debug("Received webhook", { payload }); + logger.debug("Received webhook", { payload }); - const rawEventData = payload.data; - const dataResult = dataSchema.safeParse(rawEventData); + const rawEventData = payload.data; + const dataResult = dataSchema.safeParse(rawEventData); - if (dataResult.error) { - logger.warn("Invalid data field received in notification", { error: dataResult.error }); + if (dataResult.error) { + logger.warn("Invalid data field received in notification", { error: dataResult.error }); - const errorResponse: ResponseType = { - pspReference: uuidv7(), - result: - actionType === TransactionFlowStrategyEnum.Charge - ? "CHARGE_FAILURE" - : "AUTHORIZATION_FAILURE", - message: getZodErrorMessage(dataResult.error), - amount, - actions: [], - data: { - exception: true, - }, - }; + const errorResponse: ResponseType = { + pspReference: uuidv7(), + result: + actionType === TransactionFlowStrategyEnum.Charge + ? "CHARGE_FAILURE" + : "AUTHORIZATION_FAILURE", + message: getZodErrorMessage(dataResult.error), + amount, + actions: [], + data: { + exception: true, + }, + }; - logger.info("Returning error response to Saleor", { response: errorResponse }); + logger.info("Returning error response to Saleor", { response: errorResponse }); - return res.status(200).json(errorResponse); - } + return res.status(200).json(errorResponse); + } - const data = dataResult.data; + const data = dataResult.data; - logger.info("Parsed data field from notification", { data }); + logger.info("Parsed data field from notification", { data }); - const urlGenerator = new AppUrlGenerator(ctx.authData); + const urlGenerator = new AppUrlGenerator(ctx.authData); - const successResponse: ResponseType = { - pspReference: data.event.includePspReference ? uuidv7() : undefined, - result: data.event.type, - message: "Great success!", - actions: getTransactionActions(data.event.type as TransactionEventTypeEnum), - amount, - externalUrl: urlGenerator.getTransactionDetailsUrl(payload.transaction.id), - }; + const successResponse: ResponseType = { + pspReference: data.event.includePspReference ? uuidv7() : undefined, + result: data.event.type, + message: "Great success!", + actions: getTransactionActions(data.event.type as TransactionEventTypeEnum), + amount, + externalUrl: urlGenerator.getTransactionDetailsUrl(payload.transaction.id), + }; - logger.info("Returning response to Saleor", { response: successResponse }); + logger.info("Returning response to Saleor", { response: successResponse }); - return res.status(200).json(successResponse); -}); + return res.status(200).json(successResponse); + }), + "/api/webhooks/transaction-initialize-session" + ), + loggerContext +); /** * Disable body parser for this endpoint, so signature can be verified diff --git a/src/pages/api/webhooks/transaction-process-session.ts b/src/pages/api/webhooks/transaction-process-session.ts index a446bdd..62dbcc8 100644 --- a/src/pages/api/webhooks/transaction-process-session.ts +++ b/src/pages/api/webhooks/transaction-process-session.ts @@ -1,18 +1,20 @@ import { SaleorSyncWebhook } from "@saleor/app-sdk/handlers/next"; import { v7 as uuidv7 } from "uuid"; -import { saleorApp } from "../../../saleor-app"; +import { saleorApp } from "@/saleor-app"; import { TransactionEventTypeEnum, TransactionFlowStrategyEnum, TransactionProcessSessionDocument, TransactionProcessSessionEventFragment, -} from "../../../../generated/graphql"; -import { createLogger } from "../../../logger"; -import { dataSchema, ResponseType } from "../../../modules/validation/sync-transaction"; -import { getZodErrorMessage } from "../../../lib/zod-error"; -import { getTransactionActions } from "../../../lib/transaction-actions"; +} from "@/generated/graphql"; +import { createLogger } from "@/lib/logger/logger"; +import { dataSchema, ResponseType } from "@/modules/validation/sync-transaction"; +import { getZodErrorMessage } from "@/lib/zod-error"; +import { getTransactionActions } from "@/lib/transaction-actions"; import { AppUrlGenerator } from "@/modules/url/app-url-generator"; -import { TransactionPspFinder } from "@/modules/transaction/transaction-psp-finder"; +import { wrapWithLoggerContext } from "@/lib/logger/logger-context"; +import { withOtel } from "@/lib/otel/otel-wrapper"; +import { loggerContext } from "@/logger-context"; export const transactionProcessSessionWebhook = new SaleorSyncWebhook({ @@ -23,57 +25,63 @@ export const transactionProcessSessionWebhook = query: TransactionProcessSessionDocument, }); -export default transactionProcessSessionWebhook.createHandler((req, res, ctx) => { - const logger = createLogger("transaction-process-session"); - const { payload } = ctx; - const { actionType, amount } = payload.action; +export default wrapWithLoggerContext( + withOtel( + transactionProcessSessionWebhook.createHandler((req, res, ctx) => { + const logger = createLogger("transaction-process-session"); + const { payload } = ctx; + const { actionType, amount } = payload.action; - logger.debug("Received webhook", { payload }); + logger.debug("Received webhook", { payload }); - const rawEventData = payload.data; - const dataResult = dataSchema.safeParse(rawEventData); + const rawEventData = payload.data; + const dataResult = dataSchema.safeParse(rawEventData); - if (dataResult.error) { - logger.warn("Invalid data field received in notification", { error: dataResult.error }); + if (dataResult.error) { + logger.warn("Invalid data field received in notification", { error: dataResult.error }); - const errorResponse: ResponseType = { - pspReference: uuidv7(), - result: - actionType === TransactionFlowStrategyEnum.Charge - ? "CHARGE_FAILURE" - : "AUTHORIZATION_FAILURE", - message: getZodErrorMessage(dataResult.error), - amount, - actions: [], - data: { - exception: true, - }, - }; + const errorResponse: ResponseType = { + pspReference: uuidv7(), + result: + actionType === TransactionFlowStrategyEnum.Charge + ? "CHARGE_FAILURE" + : "AUTHORIZATION_FAILURE", + message: getZodErrorMessage(dataResult.error), + amount, + actions: [], + data: { + exception: true, + }, + }; - logger.info("Returning error response to Saleor", { response: errorResponse }); + logger.info("Returning error response to Saleor", { response: errorResponse }); - return res.status(200).json(errorResponse); - } + return res.status(200).json(errorResponse); + } - const data = dataResult.data; + const data = dataResult.data; - logger.info("Parsed data field from notification", { data }); + logger.info("Parsed data field from notification", { data }); - const urlGenerator = new AppUrlGenerator(ctx.authData); + const urlGenerator = new AppUrlGenerator(ctx.authData); - const successResponse: ResponseType = { - pspReference: data.event.includePspReference ? uuidv7() : undefined, - result: data.event.type, - message: "Great success!", - actions: getTransactionActions(data.event.type as TransactionEventTypeEnum), - amount, - externalUrl: urlGenerator.getTransactionDetailsUrl(payload.transaction.id), - }; + const successResponse: ResponseType = { + pspReference: data.event.includePspReference ? uuidv7() : undefined, + result: data.event.type, + message: "Great success!", + actions: getTransactionActions(data.event.type as TransactionEventTypeEnum), + amount, + externalUrl: urlGenerator.getTransactionDetailsUrl(payload.transaction.id), + }; - logger.info("Returning response to Saleor", { response: successResponse }); + logger.info("Returning response to Saleor", { response: successResponse }); - return res.status(200).json(successResponse); -}); + return res.status(200).json(successResponse); + }), + "/api/webhooks/transaction-process-session" + ), + loggerContext +); /** * Disable body parser for this endpoint, so signature can be verified diff --git a/src/pages/api/webhooks/transaction-refund-requested.ts b/src/pages/api/webhooks/transaction-refund-requested.ts index 4749fb5..2792f47 100644 --- a/src/pages/api/webhooks/transaction-refund-requested.ts +++ b/src/pages/api/webhooks/transaction-refund-requested.ts @@ -1,19 +1,22 @@ import { SaleorSyncWebhook } from "@saleor/app-sdk/handlers/next"; import { v7 as uuidv7 } from "uuid"; -import { saleorApp } from "../../../saleor-app"; +import { saleorApp } from "@/saleor-app"; import { TransactionRefundRequestedDocument, TransactionRefundRequestedEventFragment, -} from "../../../../generated/graphql"; -import { createLogger } from "../../../logger"; +} from "@/generated/graphql"; +import { createLogger } from "@/lib/logger/logger"; import { RefundRequestedResponse, refundRequestedInputSchema, -} from "../../../modules/validation/refund-webhook"; -import { TransactionRefundChecker } from "../../../modules/transaction/transaction-refund-checker"; -import { getZodErrorMessage } from "../../../lib/zod-error"; -import { getTransactionActions } from "../../../lib/transaction-actions"; +} from "@/modules/validation/refund-webhook"; +import { TransactionRefundChecker } from "@/modules/transaction/transaction-refund-checker"; +import { getZodErrorMessage } from "@/lib/zod-error"; +import { getTransactionActions } from "@/lib/transaction-actions"; import { AppUrlGenerator } from "@/modules/url/app-url-generator"; +import { wrapWithLoggerContext } from "@/lib/logger/logger-context"; +import { withOtel } from "@/lib/otel/otel-wrapper"; +import { loggerContext } from "@/logger-context"; export const transactionRefundRequestedWebhook = new SaleorSyncWebhook({ @@ -24,55 +27,64 @@ export const transactionRefundRequestedWebhook = query: TransactionRefundRequestedDocument, }); -export default transactionRefundRequestedWebhook.createHandler((req, res, ctx) => { - const logger = createLogger("transaction-refund-requested"); - const { payload } = ctx; - const { amount } = payload.action; +export default wrapWithLoggerContext( + withOtel( + transactionRefundRequestedWebhook.createHandler((req, res, ctx) => { + const logger = createLogger("transaction-refund-requested"); + const { payload } = ctx; + const { amount } = payload.action; - const transactionRefundChecker = new TransactionRefundChecker(); + const transactionRefundChecker = new TransactionRefundChecker(); - logger.debug("Received webhook", { payload }); + logger.debug("Received webhook", { payload }); - const payloadResult = refundRequestedInputSchema.safeParse(payload); + const payloadResult = refundRequestedInputSchema.safeParse(payload); - if (payloadResult.error) { - logger.warn("Data received from Saleor didn't pass validation", { error: payloadResult.error }); + if (payloadResult.error) { + logger.warn("Data received from Saleor didn't pass validation", { + error: payloadResult.error, + }); - const failureResponse: RefundRequestedResponse = { - pspReference: uuidv7(), - result: "REFUND_FAILURE", - message: getZodErrorMessage(payloadResult.error), - actions: getTransactionActions("REFUND_FAILURE"), - amount, - }; + const failureResponse: RefundRequestedResponse = { + pspReference: uuidv7(), + result: "REFUND_FAILURE", + message: getZodErrorMessage(payloadResult.error), + actions: getTransactionActions("REFUND_FAILURE"), + amount, + }; - logger.info("Returning error response from Saleor", { response: failureResponse }); + logger.info("Returning error response from Saleor", { response: failureResponse }); - return res.status(200).json(failureResponse); - } + return res.status(200).json(failureResponse); + } - const parsedPayload = payloadResult.data; - const urlGenerator = new AppUrlGenerator(ctx.authData); + const parsedPayload = payloadResult.data; + const urlGenerator = new AppUrlGenerator(ctx.authData); - const successResponse: RefundRequestedResponse = { - pspReference: uuidv7(), - // TODO: Add result customization - result: "REFUND_SUCCESS", - message: "Great success!", - actions: transactionRefundChecker.checkIfAnotherRefundIsPossible( - amount, - payload.transaction?.chargedAmount - ) - ? ["REFUND"] - : [], - amount, - externalUrl: urlGenerator.getTransactionDetailsUrl(parsedPayload.transaction.id), - }; + const successResponse: RefundRequestedResponse = { + pspReference: uuidv7(), + // TODO: Add result customization + result: "REFUND_SUCCESS", + message: "Great success!", + actions: transactionRefundChecker.checkIfAnotherRefundIsPossible( + amount, + payload.transaction?.chargedAmount + ) + ? ["REFUND"] + : [], + amount, + externalUrl: urlGenerator.getTransactionDetailsUrl(parsedPayload.transaction.id), + }; - logger.info("Returning response to Saleor", { response: successResponse }); + logger.info("Returning response to Saleor", { response: successResponse }); - return res.status(200).json(successResponse); -}); + return res.status(200).json(successResponse); + }), + + "/api/wb/transaction-refund-requested" + ), + loggerContext +); /** * Disable body parser for this endpoint, so signature can be verified diff --git a/src/server/routers/transaction-reporter.router.ts b/src/server/routers/transaction-reporter.router.ts index c4f2f93..222a87e 100644 --- a/src/server/routers/transaction-reporter.router.ts +++ b/src/server/routers/transaction-reporter.router.ts @@ -9,7 +9,7 @@ import { import { v7 as uuidv7 } from "uuid"; import { getTransactionActions } from "@/lib/transaction-actions"; import { TRPCError } from "@trpc/server"; -import { createLogger } from "@/logger"; +import { createLogger } from "@/lib/logger/logger"; import { AppUrlGenerator } from "@/modules/url/app-url-generator"; export const transactionReporterRouter = router({