From 2925fe028531367d84b8db11296d9985b056e386 Mon Sep 17 00:00:00 2001 From: "Andrew @ Conduit" Date: Tue, 26 May 2026 13:57:20 -0500 Subject: [PATCH 1/2] feat: integrate Sentry for error tracking and telemetry - Added Sentry integration in the renderer process for capturing errors and diagnostics. - Implemented a diagnostics overlay (SentryDiag) to verify Sentry configuration and send test events. - Enhanced telemetry reporting by including machine ID and additional context in crash reports. - Updated IPC handlers to support new telemetry features and error reporting from the renderer. - Introduced a script for analyzing Crashpad/Breakpad minidump files to facilitate debugging. - Configured Vite to upload source maps to Sentry during CI builds. --- .github/workflows/release.yml | 19 + package-lock.json | 1636 ++++++++++++++++-- package.json | 1 + packages/desktop/package.json | 5 +- packages/desktop/src/main.ts | 107 +- packages/desktop/src/main/ipc.ts | 32 +- packages/desktop/src/preload.ts | 15 + packages/desktop/src/renderer.ts | 38 +- packages/desktop/src/renderer/SentryDiag.tsx | 151 ++ packages/desktop/src/renderer/env.d.ts | 7 + packages/desktop/vite.main.config.ts | 41 +- packages/desktop/vite.renderer.config.ts | 14 +- scripts/analyze-dump.mjs | 352 ++++ vite.renderer.config.ts | 19 +- 14 files changed, 2253 insertions(+), 184 deletions(-) create mode 100644 packages/desktop/src/renderer/SentryDiag.tsx create mode 100644 scripts/analyze-dump.mjs diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e0412f3..0d54fe9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -64,6 +64,9 @@ jobs: APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} NODE_ENV: production + SENTRY_DSN: ${{ secrets.SENTRY_DSN }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} - name: Rename macOS artifacts run: | @@ -75,6 +78,16 @@ jobs: mv "$DMG" "packages/desktop/out/make/OpenConduit-mac-arm64-${VERSION}.dmg" fi + - name: Upload dSYMs to Sentry + continue-on-error: true + run: | + npx sentry-cli debug-files upload \ + --org openconduit \ + --project ${{ secrets.SENTRY_PROJECT }} \ + packages/desktop/out/make + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + - name: Clean up keychain if: always() run: | @@ -116,6 +129,9 @@ jobs: run: npm run make -- --platform win32 --arch ${{ matrix.arch }} env: NODE_ENV: production + SENTRY_DSN: ${{ secrets.SENTRY_DSN }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} - name: Rename Windows artifacts shell: pwsh @@ -165,6 +181,9 @@ jobs: run: npm run make -- --platform linux env: NODE_ENV: production + SENTRY_DSN: ${{ secrets.SENTRY_DSN }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} - name: Upload Linux artifacts uses: actions/upload-artifact@v4 diff --git a/package-lock.json b/package-lock.json index edbb07e..6a94961 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "packages/desktop" ], "devDependencies": { + "@sentry/vite-plugin": "^5.3.0", "husky": "^9.1.7" } }, @@ -2358,6 +2359,108 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@fastify/otel": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@fastify/otel/-/otel-0.18.0.tgz", + "integrity": "sha512-3TASCATfw+ctICSb4ymrv7iCm0qJ0N9CarB+CZ7zIJ7KqNbwI5JjyDL1/sxoC0ccTO1Zyd1iQ+oqncPg5FJXaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.28.0", + "minimatch": "^10.2.4" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0" + } + }, + "node_modules/@fastify/otel/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@fastify/otel/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@fastify/otel/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@fastify/otel/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@fastify/otel/node_modules/import-in-the-middle": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-2.0.6.tgz", + "integrity": "sha512-3vZV3jX0XRFW3EJDTwzWoZa+RH1b8eTTx6YOCjglrLyPuepwoBti1k3L2dKwdCUrnVEfc5CuRuGstaC/uQJJaw==", + "license": "Apache-2.0", + "dependencies": { + "acorn": "^8.15.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^2.2.0", + "module-details-from-path": "^1.0.4" + } + }, + "node_modules/@fastify/otel/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", @@ -3403,9 +3506,9 @@ } }, "node_modules/@openconduit/core": { - "version": "2.0.0-beta.2", - "resolved": "https://npm.pkg.github.com/download/@openconduit/core/2.0.0-beta.2/b96d1bf92e0ea98a94aca13ac0c2e9cedfb5770f", - "integrity": "sha512-4V286mZFYD0EnLSoN6eBTW14CCSVrqoIBtErFkg7WQe4qmtGcCo+2WY7coKXAb0F1SFW4VULDoIqDkvvTiPRYA==", + "version": "2.0.0-beta.4", + "resolved": "https://npm.pkg.github.com/download/@openconduit/core/2.0.0-beta.4/31033231730fb338709180d4b9410acecc38c317", + "integrity": "sha512-PRnujI2euxvUkuauv2VaaASw3NXgAY5JojbxZrVf4b/rhlqvNZ+U3YSouyaY3QuqL/tURs71LkDRHTo24RK/+A==", "license": "AGPL-3.0", "dependencies": { "@codemirror/commands": "^6.10.3", @@ -3425,128 +3528,648 @@ "react": "^19.0.0" } }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "license": "BSD-3-Clause" + "node_modules/@opentelemetry/api": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", + "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "license": "BSD-3-Clause" + "node_modules/@opentelemetry/api-logs": { + "version": "0.214.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.214.0.tgz", + "integrity": "sha512-40lSJeqYO8Uz2Yj7u94/SJWE/wONa7rmMKjI1ZcIjgf3MHNHv1OZUCrCETGuaRF62d5pQD1wKIW+L4lmSMTzZA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", - "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", - "license": "BSD-3-Clause" + "node_modules/@opentelemetry/core": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.1.tgz", + "integrity": "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", - "license": "BSD-3-Clause" + "node_modules/@opentelemetry/instrumentation": { + "version": "0.214.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.214.0.tgz", + "integrity": "sha512-MHqEX5Dk59cqVah5LiARMACku7jXSVk9iVDWOea4x3cr7VfdByeDCURK6o1lntT1JS/Tsovw01UJrBhN3/uC5w==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.214.0", + "import-in-the-middle": "^3.0.0", + "require-in-the-middle": "^8.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "license": "BSD-3-Clause", + "node_modules/@opentelemetry/instrumentation-amqplib": { + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.61.0.tgz", + "integrity": "sha512-mCKoyTGfRNisge4br0NpOFSy2Z1NnEW8hbCJdUDdJFHrPqVzc4IIBPA/vX0U+LUcQqrQvJX+HMIU0dbDRe0i0Q==", + "license": "Apache-2.0", "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.214.0", + "@opentelemetry/semantic-conventions": "^1.33.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "license": "BSD-3-Clause" + "node_modules/@opentelemetry/instrumentation-connect": { + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.57.0.tgz", + "integrity": "sha512-FMEBChnI4FLN5TE9DHwfH7QpNir1JzXno1uz/TAucVdLCyrG0jTrKIcNHt/i30A0M2AunNBCkcd8Ei26dIPKdg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.214.0", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@types/connect": "3.4.38" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.1.tgz", - "integrity": "sha512-mnzgDV26ueAvk7rsbt9L7bE0SuAoqyuys/sMMrmVcN5x9VsxpcG3rqAUSgDyLp0UZlmNfIbQ4fHfCtreVBk8Ew==", - "license": "BSD-3-Clause" + "node_modules/@opentelemetry/instrumentation-dataloader": { + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.31.0.tgz", + "integrity": "sha512-f654tZFQXS5YeLDNb9KySrwtg7SnqZN119FauD7acBoTzuLduaiGTNz88ixcVSOOMGZ+EjJu/RFtx5klObC95g==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.214.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "license": "BSD-3-Clause" + "node_modules/@opentelemetry/instrumentation-fs": { + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.33.0.tgz", + "integrity": "sha512-sCZWXGalQ01wr3tAhSR9ucqFJ0phidpAle6/17HVjD6gN8FLmZMK/8sKxdXYHy3PbnlV1P4zeiSVFNKpbFMNLA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.214.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "license": "BSD-3-Clause" + "node_modules/@opentelemetry/instrumentation-generic-pool": { + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.57.0.tgz", + "integrity": "sha512-orhmlaK+ZIW9hKU+nHTbXrCSXZcH83AescTqmpamHRobRmYSQwRbD0a1odc0yAzuzOtxYiHiXAnpnIpaSSY7Ow==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.214.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", - "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", - "license": "BSD-3-Clause" + "node_modules/@opentelemetry/instrumentation-graphql": { + "version": "0.62.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.62.0.tgz", + "integrity": "sha512-3YNuLVPUxafXkH1jBAbGsKNsP3XVzcFDhCDCE3OqBwCwShlqQbLMRMFh1T/d5jaVZiGVmSsfof+ICKD2iOV8xg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.214.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.27", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", - "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", - "dev": true, - "license": "MIT" + "node_modules/@opentelemetry/instrumentation-hapi": { + "version": "0.60.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.60.0.tgz", + "integrity": "sha512-aNljZKYrEa7obLAxd1bCEDxF7kzCLGXTuTJZ8lMR9rIVEjmuKBXN1gfqpm/OB//Zc2zP4iIve1jBp7sr3mQV6w==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.214.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.60.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz", - "integrity": "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "node_modules/@opentelemetry/instrumentation-http": { + "version": "0.214.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.214.0.tgz", + "integrity": "sha512-FlkDhZDRjDJDcO2LcSCtjRpkal1NJ8y0fBqBhTvfAR3JSYY2jAIj1kSS5IjmEBt4c3aWv+u/lqLuoCDrrKCSKg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.6.1", + "@opentelemetry/instrumentation": "0.214.0", + "@opentelemetry/semantic-conventions": "^1.29.0", + "forwarded-parse": "2.1.2" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.60.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz", - "integrity": "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "node_modules/@opentelemetry/instrumentation-http/node_modules/@opentelemetry/core": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.1.tgz", + "integrity": "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.60.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz", - "integrity": "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "node_modules/@opentelemetry/instrumentation-ioredis": { + "version": "0.62.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.62.0.tgz", + "integrity": "sha512-ZYt//zcPve8qklaZX+5Z4MkU7UpEkFRrxsf2cnaKYBitqDnsCN69CPAuuMOX6NYdW2rG9sFy7V/QWtBlP5XiNQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.214.0", + "@opentelemetry/redis-common": "^0.38.2", + "@opentelemetry/semantic-conventions": "^1.33.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.60.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz", - "integrity": "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", + "node_modules/@opentelemetry/instrumentation-kafkajs": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.23.0.tgz", + "integrity": "sha512-4K+nVo+zI+aDz0Z85SObwbdixIbzS9moIuKJaYsdlzcHYnKOPtB7ya8r8Ezivy/GVIBHiKJVq4tv+BEkgOMLaQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.214.0", + "@opentelemetry/semantic-conventions": "^1.30.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-knex": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.58.0.tgz", + "integrity": "sha512-Hc/o8fSsaWxZ8r1Yw4rNDLwTpUopTf4X32y4W6UhlHmW8Wizz8wfhgOKIelSeqFVTKBBPIDUOsQWuIMxBmu8Bw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.214.0", + "@opentelemetry/semantic-conventions": "^1.33.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-koa": { + "version": "0.62.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.62.0.tgz", + "integrity": "sha512-uVip0VuGUQXZ+vFxkKxAUNq8qNl+VFlyHDh/U6IQ8COOEDfbEchdaHnpFrMYF3psZRUuoSIgb7xOeXj00RdwDA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.214.0", + "@opentelemetry/semantic-conventions": "^1.36.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0" + } + }, + "node_modules/@opentelemetry/instrumentation-lru-memoizer": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.58.0.tgz", + "integrity": "sha512-6grM3TdMyHzlGY1cUA+mwoPueB1F3dYKgKtZIH6jOFXqfHAByyLTc+6PFjGM9tKh52CFBJaDwodNlL/Td39z7Q==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.214.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mongodb": { + "version": "0.67.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.67.0.tgz", + "integrity": "sha512-1WJp5N1lYfHq2IhECOTewFs5Tf2NfUOwQRqs/rZdXKTezArMlucxgzAaqcgp3A3YREXopXTpXHsxZTGHjNhMdQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.214.0", + "@opentelemetry/semantic-conventions": "^1.33.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mongoose": { + "version": "0.60.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.60.0.tgz", + "integrity": "sha512-8BahAZpKsOoc+lrZGb7Ofn4g3z8qtp5IxDfvAVpKXsEheQN7ONMH5djT5ihy6yf8yyeQJGS0gXFfpEAEeEHqQg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.214.0", + "@opentelemetry/semantic-conventions": "^1.33.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mysql": { + "version": "0.60.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.60.0.tgz", + "integrity": "sha512-08pO8GFPEIz2zquKDGteBZDNmwketdgH8hTe9rVYgW9kCJXq1Psj3wPQGx+VaX4ZJKCfPeoLMYup9+cxHvZyVQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.214.0", + "@opentelemetry/semantic-conventions": "^1.33.0", + "@types/mysql": "2.15.27" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mysql2": { + "version": "0.60.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.60.0.tgz", + "integrity": "sha512-m/5d3bxQALllCzezYDk/6vajh0tj5OijMMvOZGr+qN1NMXm1dzMNwyJ0gNZW7Fo3YFRyj/jJMxIw+W7d525dlw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.214.0", + "@opentelemetry/semantic-conventions": "^1.33.0", + "@opentelemetry/sql-common": "^0.41.2" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-pg": { + "version": "0.66.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.66.0.tgz", + "integrity": "sha512-KxfLGXBb7k2ueaPJfq2GXBDXBly8P+SpR/4Mj410hhNgmQF3sCqwXvUBQxZQkDAmsdBAoenM+yV1LhtsMRamcA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.214.0", + "@opentelemetry/semantic-conventions": "^1.34.0", + "@opentelemetry/sql-common": "^0.41.2", + "@types/pg": "8.15.6", + "@types/pg-pool": "2.0.7" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-redis": { + "version": "0.62.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.62.0.tgz", + "integrity": "sha512-y3pPpot7WzR/8JtHcYlTYsyY8g+pbFhAqbwAuG5bLPnR6v6pt1rQc0DpH0OlGP/9CZbWBP+Zhwp9yFoygf/ZXQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.214.0", + "@opentelemetry/redis-common": "^0.38.2", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-tedious": { + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.33.0.tgz", + "integrity": "sha512-Q6WQwAD01MMTub31GlejoiFACYNw26J426wyjvU7by7fDIr2nZXNW4vhTGs7i7F0TnXBO3xN688g1tdUgYwJ5w==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.214.0", + "@opentelemetry/semantic-conventions": "^1.33.0", + "@types/tedious": "^4.0.14" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/redis-common": { + "version": "0.38.3", + "resolved": "https://registry.npmjs.org/@opentelemetry/redis-common/-/redis-common-0.38.3.tgz", + "integrity": "sha512-VCghU1JYs/4gP6Gqf/xro9MEsZ7LrMv2uONVsaESKL38ZOB9BqnI98FfS23wjMnHlpuE+TTaWSoAVNpTwYXzjw==", + "license": "Apache-2.0", + "engines": { + "node": "^18.19.0 || >=20.6.0" + } + }, + "node_modules/@opentelemetry/resources": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", + "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.7.1.tgz", + "integrity": "sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.41.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.41.1.tgz", + "integrity": "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/sql-common": { + "version": "0.41.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/sql-common/-/sql-common-0.41.2.tgz", + "integrity": "sha512-4mhWm3Z8z+i508zQJ7r6Xi7y4mmoJpdvH0fZPFRkWrdp5fq7hhZ2HhYokEOLkfqSMgPR4Z9EyB3DBkbKGOqZiQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0" + } + }, + "node_modules/@prisma/instrumentation": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@prisma/instrumentation/-/instrumentation-7.6.0.tgz", + "integrity": "sha512-ZPW2gRiwpPzEfgeZgaekhqXrbW+Y2RJKHVqUmlhZhKzRNCcvR6DykzylDrynpArKKRQtLxoZy36fK7U0p3pdgQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.207.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.8" + } + }, + "node_modules/@prisma/instrumentation/node_modules/@opentelemetry/api-logs": { + "version": "0.207.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.207.0.tgz", + "integrity": "sha512-lAb0jQRVyleQQGiuuvCOTDVspc14nx6XJjP4FspJ1sNARo3Regq4ZZbrc3rN4b1TYSuUCvgH+UXUPug4SLOqEQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@prisma/instrumentation/node_modules/@opentelemetry/instrumentation": { + "version": "0.207.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.207.0.tgz", + "integrity": "sha512-y6eeli9+TLKnznrR8AZlQMSJT7wILpXH+6EYq5Vf/4Ao+huI7EedxQHwRgVUOMLFbe7VFDvHJrX9/f4lcwnJsA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.207.0", + "import-in-the-middle": "^2.0.0", + "require-in-the-middle": "^8.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@prisma/instrumentation/node_modules/import-in-the-middle": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-2.0.6.tgz", + "integrity": "sha512-3vZV3jX0XRFW3EJDTwzWoZa+RH1b8eTTx6YOCjglrLyPuepwoBti1k3L2dKwdCUrnVEfc5CuRuGstaC/uQJJaw==", + "license": "Apache-2.0", + "dependencies": { + "acorn": "^8.15.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^2.2.0", + "module-details-from-path": "^1.0.4" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.1.tgz", + "integrity": "sha512-mnzgDV26ueAvk7rsbt9L7bE0SuAoqyuys/sMMrmVcN5x9VsxpcG3rqAUSgDyLp0UZlmNfIbQ4fHfCtreVBk8Ew==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", + "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", + "license": "BSD-3-Clause" + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz", + "integrity": "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz", + "integrity": "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz", + "integrity": "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz", + "integrity": "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -3834,63 +4457,565 @@ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.4.tgz", "integrity": "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==", "cpu": [ - "arm64" + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz", + "integrity": "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz", + "integrity": "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.4.tgz", + "integrity": "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sentry-internal/browser-utils": { + "version": "10.50.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-10.50.0.tgz", + "integrity": "sha512-42bxyRTxnCmYlWnvz4CxikuQNanw8UNma2WJrtxJ0f1MAJV2GhQGSHDLnA+lvFlmiz6qct3pfen/NXGyOTegTA==", + "license": "MIT", + "dependencies": { + "@sentry/core": "10.50.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry-internal/feedback": { + "version": "10.50.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-10.50.0.tgz", + "integrity": "sha512-0k9XZF0wn86f77mIO2U3gNNyDZooy139CnEanRzHinrN106vVzvBZ6TUEQoHtoO1fqQxr+nWWVrqV/PXUqk47w==", + "license": "MIT", + "dependencies": { + "@sentry/core": "10.50.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry-internal/replay": { + "version": "10.50.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-10.50.0.tgz", + "integrity": "sha512-51FYNfnvVLAWw1rrEWPFfwHuMRb9mkVCFGA4J9/un7SpeGBsQDziGB0Di4fsCxI7+EdSBpfLHPF0csKtCCw0oQ==", + "license": "MIT", + "dependencies": { + "@sentry-internal/browser-utils": "10.50.0", + "@sentry/core": "10.50.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry-internal/replay-canvas": { + "version": "10.50.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-10.50.0.tgz", + "integrity": "sha512-jx6RKBmcJSWdI92qDGS/sBv1w+7Cww879Z/moX7bw7ipHa/Ts3iDcB3rgZwvhmi17U+mvYsbJeL2DXkPo3TjPw==", + "license": "MIT", + "dependencies": { + "@sentry-internal/replay": "10.50.0", + "@sentry/core": "10.50.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/babel-plugin-component-annotate": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-5.3.0.tgz", + "integrity": "sha512-p4q8gn8wcFqZGP/s2MnJCAAd8fTikaU6A0mM97RDHQgStcrYiaS0Sc5zUNfb1V+UOLPuvdEdL6MwyxfzjYJQTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@sentry/browser": { + "version": "10.50.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.50.0.tgz", + "integrity": "sha512-1f6rAvET6myiTaSeYqvaaBwvq1LfxqWjAPIoAW/NVC9bPMkeEcuvgDajHrnZMrBeWoJ81NMyoLkyX+iOc7MoFA==", + "license": "MIT", + "dependencies": { + "@sentry-internal/browser-utils": "10.50.0", + "@sentry-internal/feedback": "10.50.0", + "@sentry-internal/replay": "10.50.0", + "@sentry-internal/replay-canvas": "10.50.0", + "@sentry/core": "10.50.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/bundler-plugin-core": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@sentry/bundler-plugin-core/-/bundler-plugin-core-5.3.0.tgz", + "integrity": "sha512-L5T60sWdAI3qWwdg3Ptwek/0TY59PERrxyqp4XMUkroayQvGd9r5dIW9Q1kSeXX9iJ442nXbFZKAOyCKV4Z13Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.18.5", + "@sentry/babel-plugin-component-annotate": "5.3.0", + "@sentry/cli": "^2.58.5", + "dotenv": "^16.3.1", + "find-up": "^5.0.0", + "glob": "^13.0.6", + "magic-string": "~0.30.8" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@sentry/bundler-plugin-core/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@sentry/bundler-plugin-core/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@sentry/bundler-plugin-core/node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@sentry/bundler-plugin-core/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@sentry/bundler-plugin-core/node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/@sentry/cli": { + "version": "2.58.6", + "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.58.6.tgz", + "integrity": "sha512-baBcNPLLfUi9WuL+Tpri9BFaAdvugZIKelC5X0tt0Zdy+K0K+PCVSrnNmwMWU/HyaF/SEv6b6UHnXIdqanBlcg==", + "dev": true, + "hasInstallScript": true, + "license": "FSL-1.1-MIT", + "dependencies": { + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.7", + "progress": "^2.0.3", + "proxy-from-env": "^1.1.0", + "which": "^2.0.2" + }, + "bin": { + "sentry-cli": "bin/sentry-cli" + }, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@sentry/cli-darwin": "2.58.6", + "@sentry/cli-linux-arm": "2.58.6", + "@sentry/cli-linux-arm64": "2.58.6", + "@sentry/cli-linux-i686": "2.58.6", + "@sentry/cli-linux-x64": "2.58.6", + "@sentry/cli-win32-arm64": "2.58.6", + "@sentry/cli-win32-i686": "2.58.6", + "@sentry/cli-win32-x64": "2.58.6" + } + }, + "node_modules/@sentry/cli-darwin": { + "version": "2.58.6", + "resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.58.6.tgz", + "integrity": "sha512-udAVvcyfNa0R+95GvPz/+43/N3TC0TYKdkQ7D7jhPSzbcMc7l2fxRNN5yB3UpCA5fWFnW4toeaqwDBhb/Wh3LA==", + "dev": true, + "license": "FSL-1.1-MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-linux-arm": { + "version": "2.58.6", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.58.6.tgz", + "integrity": "sha512-pD0LAt5PcUzAinBwvDqc66x9+2CabHEv486yP0gRjWO7SakbaxmfVq/EXd8VLq/Tzi39LAu422UYK1lpW3MILw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "FSL-1.1-MIT", + "optional": true, + "os": [ + "linux", + "freebsd", + "android" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-linux-arm64": { + "version": "2.58.6", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.58.6.tgz", + "integrity": "sha512-q8mEcNNmeXMy5i+jWT30TVpH7LcP4HD21CD5XRSPAd/a912HF6EpK0ybf/1USO14WOhoXbAGi9txwaWabSe33g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "FSL-1.1-MIT", + "optional": true, + "os": [ + "linux", + "freebsd", + "android" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-linux-i686": { + "version": "2.58.6", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.58.6.tgz", + "integrity": "sha512-q8vNJi1eOV/4vxAFWBsEwLHoSYapaZHIf4j76KJGJXFKTkEbsjCOOsKbwUIBTQQhRgV4DFWh3ryfsPS/que4Kg==", + "cpu": [ + "x86", + "ia32" + ], + "dev": true, + "license": "FSL-1.1-MIT", + "optional": true, + "os": [ + "linux", + "freebsd", + "android" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-linux-x64": { + "version": "2.58.6", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.58.6.tgz", + "integrity": "sha512-DZu956Mhi3ZRjTBe1WdbGV46ldVbA8d2rgp/fh51GsI25zjBHah4wZnPTSzpc+YqxU6pJpg579B/r3jrIK530Q==", + "cpu": [ + "x64" ], "dev": true, - "license": "MIT", + "license": "FSL-1.1-MIT", "optional": true, "os": [ - "win32" - ] + "linux", + "freebsd", + "android" + ], + "engines": { + "node": ">=10" + } }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.60.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz", - "integrity": "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==", + "node_modules/@sentry/cli-win32-arm64": { + "version": "2.58.6", + "resolved": "https://registry.npmjs.org/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.58.6.tgz", + "integrity": "sha512-nj0Ff/kmAB73EPDhR8B4O9r+NUHK5GkPCkGWC+kXVemqAJWL5jcJ5KdxG0l/S0z6RoEoltID8/43/B+TaMlT7A==", "cpu": [ - "ia32" + "arm64" ], "dev": true, - "license": "MIT", + "license": "FSL-1.1-MIT", "optional": true, "os": [ "win32" - ] + ], + "engines": { + "node": ">=10" + } }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.60.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz", - "integrity": "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==", + "node_modules/@sentry/cli-win32-i686": { + "version": "2.58.6", + "resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.58.6.tgz", + "integrity": "sha512-WNZiDzPbgsEMQWq4avsQ391v/xWKJDIWWWo9GYl+N/w5qcYKkoDW7wQG7T9FasI6ENn68phChTOAPXXxbfAdOg==", "cpu": [ - "x64" + "x86", + "ia32" ], "dev": true, - "license": "MIT", + "license": "FSL-1.1-MIT", "optional": true, "os": [ "win32" - ] + ], + "engines": { + "node": ">=10" + } }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.60.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.4.tgz", - "integrity": "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==", + "node_modules/@sentry/cli-win32-x64": { + "version": "2.58.6", + "resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.58.6.tgz", + "integrity": "sha512-R35WJ17oF4D2eqI1DR2sQQqr0fjRTt5xoP16WrTu91XM2lndRMFsnjh+/GttbxapLCBNlrjzia99MJ0PZHZpgA==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", + "license": "FSL-1.1-MIT", "optional": true, "os": [ "win32" - ] + ], + "engines": { + "node": ">=10" + } }, - "node_modules/@rtsao/scc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", - "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "node_modules/@sentry/core": { + "version": "10.50.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.50.0.tgz", + "integrity": "sha512-J4A+vzUO3adl0TkFCjaN1+4miamrjHiEIYuLHiuu1lmAjq5WIVw32ObvAh4yMwNtxyaEMosTrrh5M6f12XSJFg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/electron": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@sentry/electron/-/electron-7.13.0.tgz", + "integrity": "sha512-zW/1c9fKafCZsvhRRp9mIDH9bSvzBiIUwoN087zDDHc0vatVsqqai8nUk0bUewwYZY4Inia7r5w+kPWi8LXZzg==", + "license": "MIT", + "dependencies": { + "@sentry/browser": "10.50.0", + "@sentry/core": "10.50.0", + "@sentry/node": "10.50.0" + }, + "peerDependencies": { + "@sentry/node-native": "10.50.0" + }, + "peerDependenciesMeta": { + "@sentry/node-native": { + "optional": true + } + } + }, + "node_modules/@sentry/node": { + "version": "10.50.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-10.50.0.tgz", + "integrity": "sha512-TvwzFQu8MGKzMQ2/tqxcNzFA8UG2kKTB+GDmA4uOzx3+GT849YZRRSJzEXCmYhk1teVd2fbmgqyYY2nyLF5a+Q==", + "license": "MIT", + "dependencies": { + "@fastify/otel": "0.18.0", + "@opentelemetry/api": "^1.9.1", + "@opentelemetry/core": "^2.6.1", + "@opentelemetry/instrumentation": "^0.214.0", + "@opentelemetry/instrumentation-amqplib": "0.61.0", + "@opentelemetry/instrumentation-connect": "0.57.0", + "@opentelemetry/instrumentation-dataloader": "0.31.0", + "@opentelemetry/instrumentation-fs": "0.33.0", + "@opentelemetry/instrumentation-generic-pool": "0.57.0", + "@opentelemetry/instrumentation-graphql": "0.62.0", + "@opentelemetry/instrumentation-hapi": "0.60.0", + "@opentelemetry/instrumentation-http": "0.214.0", + "@opentelemetry/instrumentation-ioredis": "0.62.0", + "@opentelemetry/instrumentation-kafkajs": "0.23.0", + "@opentelemetry/instrumentation-knex": "0.58.0", + "@opentelemetry/instrumentation-koa": "0.62.0", + "@opentelemetry/instrumentation-lru-memoizer": "0.58.0", + "@opentelemetry/instrumentation-mongodb": "0.67.0", + "@opentelemetry/instrumentation-mongoose": "0.60.0", + "@opentelemetry/instrumentation-mysql": "0.60.0", + "@opentelemetry/instrumentation-mysql2": "0.60.0", + "@opentelemetry/instrumentation-pg": "0.66.0", + "@opentelemetry/instrumentation-redis": "0.62.0", + "@opentelemetry/instrumentation-tedious": "0.33.0", + "@opentelemetry/sdk-trace-base": "^2.6.1", + "@opentelemetry/semantic-conventions": "^1.40.0", + "@prisma/instrumentation": "7.6.0", + "@sentry/core": "10.50.0", + "@sentry/node-core": "10.50.0", + "@sentry/opentelemetry": "10.50.0", + "import-in-the-middle": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/node-core": { + "version": "10.50.0", + "resolved": "https://registry.npmjs.org/@sentry/node-core/-/node-core-10.50.0.tgz", + "integrity": "sha512-Eb1BYf4Lc7ZYmdX3acKP6SgyGikrBA370gbGHaWI5jRu7G7vig8sIu1ghPmY5AlvqBPOetado7GniXr6fAXbTw==", + "license": "MIT", + "dependencies": { + "@sentry/core": "10.50.0", + "@sentry/opentelemetry": "10.50.0", + "import-in-the-middle": "^3.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/core": "^1.30.1 || ^2.1.0", + "@opentelemetry/exporter-trace-otlp-http": ">=0.57.0 <1", + "@opentelemetry/instrumentation": ">=0.57.1 <1", + "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0", + "@opentelemetry/semantic-conventions": "^1.39.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@opentelemetry/core": { + "optional": true + }, + "@opentelemetry/exporter-trace-otlp-http": { + "optional": true + }, + "@opentelemetry/instrumentation": { + "optional": true + }, + "@opentelemetry/sdk-trace-base": { + "optional": true + }, + "@opentelemetry/semantic-conventions": { + "optional": true + } + } + }, + "node_modules/@sentry/opentelemetry": { + "version": "10.50.0", + "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-10.50.0.tgz", + "integrity": "sha512-axn3pgDPveGdaMUC0abMCmFN7ux2pA5ebPufCef4lMIsyg7BBQvaEJ+vE19wjstMaBCAJGsdZlL3eeP2rtgRMw==", + "license": "MIT", + "dependencies": { + "@sentry/core": "10.50.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/core": "^1.30.1 || ^2.1.0", + "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0", + "@opentelemetry/semantic-conventions": "^1.39.0" + } + }, + "node_modules/@sentry/rollup-plugin": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@sentry/rollup-plugin/-/rollup-plugin-5.3.0.tgz", + "integrity": "sha512-hgPGPYdQJ/G1cGYOxAb7d4z3V+/k/E5/P/5TFPEEBLuIbFFk+JG0CISUDJdzXJjO382Lb99PBJuXGbueBmO79w==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@sentry/bundler-plugin-core": "5.3.0", + "magic-string": "~0.30.8" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "rollup": ">=3.2.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@sentry/vite-plugin": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@sentry/vite-plugin/-/vite-plugin-5.3.0.tgz", + "integrity": "sha512-qcoSzo4n2MulVQ70UUPLq6dTleb2a2HwL2wuwvAgWhPChrYTuk6A6mDg6aQb9fairPAwFPiU9PzOANpoDJcz1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sentry/bundler-plugin-core": "5.3.0", + "@sentry/rollup-plugin": "5.3.0" + }, + "engines": { + "node": ">= 18" + } }, "node_modules/@sindresorhus/is": { "version": "4.6.0", @@ -4541,6 +5666,15 @@ "@types/responselike": "^1.0.0" } }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/d3": { "version": "7.4.3", "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", @@ -4936,6 +6070,15 @@ "@types/node": "*" } }, + "node_modules/@types/mysql": { + "version": "2.15.27", + "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.27.tgz", + "integrity": "sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { "version": "25.8.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.8.0.tgz", @@ -4954,6 +6097,26 @@ "@types/node": "*" } }, + "node_modules/@types/pg": { + "version": "8.15.6", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.15.6.tgz", + "integrity": "sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, + "node_modules/@types/pg-pool": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/pg-pool/-/pg-pool-2.0.7.tgz", + "integrity": "sha512-U4CwmGVQcbEuqpyju8/ptOKg6gEC+Tqsvj2xS9o1g71bUh8twxnC6ZL5rZKCsGN0iyH0CwgUyc9VR5owNQF9Ng==", + "license": "MIT", + "dependencies": { + "@types/pg": "*" + } + }, "node_modules/@types/react": { "version": "19.2.14", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", @@ -4996,6 +6159,15 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/tedious": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@types/tedious/-/tedious-4.0.14.tgz", + "integrity": "sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -5504,7 +6676,6 @@ "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -5513,6 +6684,15 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^8" + } + }, "node_modules/acorn-import-phases": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", @@ -6510,6 +7690,12 @@ "node": ">=6.0" } }, + "node_modules/cjs-module-lexer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", + "license": "MIT" + }, "node_modules/clean-git-ref": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/clean-git-ref/-/clean-git-ref-2.0.1.tgz", @@ -7804,6 +8990,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/ds-store": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/ds-store/-/ds-store-0.1.6.tgz", @@ -9776,6 +10975,12 @@ "node": ">= 0.6" } }, + "node_modules/forwarded-parse": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.2.tgz", + "integrity": "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==", + "license": "MIT" + }, "node_modules/fraction.js": { "version": "5.3.4", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", @@ -10752,6 +11957,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/import-in-the-middle": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-3.0.1.tgz", + "integrity": "sha512-pYkiyXVL2Mf3pozdlDGV6NAObxQx13Ae8knZk1UJRJ6uRW/ZRmTGHlQYtrsSl7ubuE5F8CD1z+s1n4RHNuTtuA==", + "license": "Apache-2.0", + "dependencies": { + "acorn": "^8.15.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^2.2.0", + "module-details-from-path": "^1.0.4" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/import-meta-resolve": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", @@ -13529,6 +14749,12 @@ "node": ">=10" } }, + "node_modules/module-details-from-path": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", + "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -14367,6 +15593,43 @@ "dev": true, "license": "MIT" }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.5.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.0.tgz", + "integrity": "sha512-5YgH9UJd7wVb9hIouI2adWpgqrrICkt070Dnj8EUY1+B4B2P9eRLPAkAAo6NICA7CEhOIeBHl46u9zSNpNu7zA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/path-scurry/node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/path-to-regexp": { "version": "8.4.2", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", @@ -14441,6 +15704,37 @@ "dev": true, "license": "MIT" }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.14.0.tgz", + "integrity": "sha512-n5taZ1kO3s9ngDTVxsEznOqCyToTgz0FLuPq0B33COy5pPpuWJpY3/2oRBVETuOgzdqRXfWpM9HIhp2LBBT1BA==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -14556,6 +15850,45 @@ "dev": true, "license": "MIT" }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/postject": { "version": "1.0.0-alpha.6", "resolved": "https://registry.npmjs.org/postject/-/postject-1.0.0-alpha.6.tgz", @@ -14705,6 +16038,13 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true, + "license": "MIT" + }, "node_modules/pump": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", @@ -15171,6 +16511,19 @@ "node": ">=0.10.0" } }, + "node_modules/require-in-the-middle": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-8.0.1.tgz", + "integrity": "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "module-details-from-path": "^1.0.3" + }, + "engines": { + "node": ">=9.3.0 || >=8.10.0 <9.0.0" + } + }, "node_modules/resedit": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/resedit/-/resedit-2.0.3.tgz", @@ -17706,9 +19059,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, "license": "MIT", - "optional": true, "engines": { "node": ">=0.4" } @@ -17908,7 +19259,8 @@ "@aws-sdk/client-bedrock-runtime": "^3.1053.0", "@google/genai": "^2.3.0", "@modelcontextprotocol/sdk": "^1.29.0", - "@openconduit/core": "^2.0.0-beta.2", + "@openconduit/core": "^2.0.0-beta.4", + "@sentry/electron": "^7.13.0", "@types/pdf-parse": "^1.1.5", "electron-squirrel-startup": "^1.0.1", "electron-store": "^11.0.2", diff --git a/package.json b/package.json index 6bf5e93..981ee1b 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "prepare": "husky" }, "devDependencies": { + "@sentry/vite-plugin": "^5.3.0", "husky": "^9.1.7" }, "version": "1.2.1" diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 7962230..98fc46e 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,7 +1,7 @@ { "name": "openconduit", "productName": "OpenConduit", - "version": "2.0.0-beta.3", + "version": "2.0.0-beta.4", "description": "Cross-platform desktop AI chat client — OpenAI, Anthropic, LM Studio, and MCP tool servers", "main": ".vite/build/main.js", "private": true, @@ -58,7 +58,8 @@ "@aws-sdk/client-bedrock-runtime": "^3.1053.0", "@google/genai": "^2.3.0", "@modelcontextprotocol/sdk": "^1.29.0", - "@openconduit/core": "^2.0.0-beta.2", + "@openconduit/core": "^2.0.0-beta.4", + "@sentry/electron": "^7.13.0", "@types/pdf-parse": "^1.1.5", "electron-squirrel-startup": "^1.0.1", "electron-store": "^11.0.2", diff --git a/packages/desktop/src/main.ts b/packages/desktop/src/main.ts index e87a448..980122d 100644 --- a/packages/desktop/src/main.ts +++ b/packages/desktop/src/main.ts @@ -1,17 +1,55 @@ -import { app, BrowserWindow, session, autoUpdater, crashReporter } from 'electron'; +import { app, BrowserWindow, session, autoUpdater } from 'electron'; +import * as Sentry from '@sentry/electron/main'; +import { sentryMinidumpIntegration } from '@sentry/electron/main'; import path from 'node:path'; import { pathToFileURL } from 'node:url'; import started from 'electron-squirrel-startup'; import { updateElectronApp, UpdateSourceType } from 'update-electron-app'; import { registerIpcHandlers, fireTelemetrySessionStart, fireTelemetryCrash } from './main/ipc'; -import { getSettings } from './main/store/settings'; +import { getSettings, getMachineId } from './main/store/settings'; import { destroyBrowserWindow } from './main/webtools/browser'; if (started) app.quit(); +// Initialise Sentry before `app.ready` as required by @sentry/electron. +// The minidump (Crashpad) integration is excluded here and added explicitly +// inside app.on('ready') — calling crashReporter.start() before the framework +// is fully initialised conflicts with Chromium's exception handler on Apple Silicon. +Sentry.init({ + dsn: process.env.SENTRY_DSN || undefined, + release: `openconduit@${app.getVersion()}`, + environment: app.isPackaged ? 'production' : 'development', + // Exclude the minidump integration — it is registered after app.ready below. + integrations: (defaults) => defaults.filter((i) => i.name !== 'SentryMinidump'), + initialScope: { + tags: { + appVersion: app.getVersion(), + platform: process.platform, + arch: process.arch, + electronV: process.versions.electron, + nodeV: process.versions.node, + v8V: process.versions.v8, + }, + }, + // Respect the user's crash-reporting opt-out. beforeSend runs at event-send + // time (not at init time) so getSettings() is safe to call here — userData + // is always set before any event can be generated. + beforeSend(event) { + try { + if (getSettings().telemetry?.crashReports === false) return null; + } catch { /* store not ready yet — allow the event through */ } + return event; + }, +}); + // Pin userData to a stable name so it never moves when productName changes. app.setPath('userData', path.join(app.getPath('appData'), 'openconduit')); +// Identify this device in Sentry. getMachineId() requires userData to be set first +// (it reads from electron-store). The ID is a random UUID generated on first launch +// and is never tied to any personal information. +Sentry.setUser({ id: getMachineId() }); + // Register openconduit:// as a deep-link protocol (e.g. openconduit://join?roomId=xxx) app.setAsDefaultProtocolClient('openconduit'); @@ -83,6 +121,13 @@ const createWindow = () => { ); } + // Crash-loop guard: track recent renderer crash timestamps. + // If the renderer crashes 3+ times within 15 seconds, stop reloading and + // show a static error page so the user isn't stuck in an infinite loop. + const CRASH_WINDOW_MS = 15_000; + const CRASH_LOOP_THRESHOLD = 3; + const rendererCrashTimes: number[] = []; + // Reload automatically when the renderer crashes instead of staying white. // Skips clean exits (e.g. deliberate reload / navigation) to avoid loops. mainWindow.webContents.on('render-process-gone', (_event, details) => { @@ -96,6 +141,42 @@ const createWindow = () => { exitCode: details.exitCode, }); + const now = Date.now(); + rendererCrashTimes.push(now); + // Evict timestamps outside the window + while (rendererCrashTimes.length > 0 && rendererCrashTimes[0] < now - CRASH_WINDOW_MS) { + rendererCrashTimes.shift(); + } + + if (rendererCrashTimes.length >= CRASH_LOOP_THRESHOLD) { + // Crash loop detected — load a static fallback instead of reloading. + // The user can still quit or submit a bug report from this page. + if (!mainWindow.isDestroyed()) { + mainWindow.loadURL( + 'data:text/html,' + encodeURIComponent([ + '', + '', + '', + '
⚠️
', + '

OpenConduit crashed repeatedly

', + '

The app crashed ' + CRASH_LOOP_THRESHOLD + ' times in ' + (CRASH_WINDOW_MS / 1000) + ' seconds. ', + 'Please restart the application. If it keeps happening, use ', + 'Settings → Feedback to report the issue.

', + '', + '', + ].join('')), + ); + } + return; + } + setTimeout(() => { if (mainWindow.isDestroyed()) return; if (MAIN_WINDOW_VITE_DEV_SERVER_URL) { @@ -111,24 +192,10 @@ const createWindow = () => { }; app.on('ready', () => { - // Start Crashpad inside app.on('ready') so the Mach exception handler is - // registered after the framework is fully initialised. Calling it before - // app.ready on Apple Silicon can conflict with Chromium's own exception - // handling and trigger spurious CHECK failures in the renderer. - crashReporter.start({ - submitURL: '', - uploadToServer: false, - // Embed build/env info in every minidump so crash reports are self-contained - // even when the .dmp is inspected offline without a symbol server. - globalExtra: { - appVersion: app.getVersion(), - platform: process.platform, - arch: process.arch, - electronV: process.versions.electron, - nodeV: process.versions.node, - v8V: process.versions.v8, - }, - }); + // Register the Crashpad/minidump integration now that the framework is fully + // initialised. On Apple Silicon, crashReporter.start() must not be called + // before app.ready or it conflicts with Chromium's own exception handling. + Sentry.addIntegration(sentryMinidumpIntegration()); // In production the renderer loads via file://, so absolute paths like // /app-icon.png resolve to the filesystem root instead of the bundled asset // directory. Intercept those requests and redirect to the correct path. diff --git a/packages/desktop/src/main/ipc.ts b/packages/desktop/src/main/ipc.ts index 686d165..1182d49 100644 --- a/packages/desktop/src/main/ipc.ts +++ b/packages/desktop/src/main/ipc.ts @@ -60,7 +60,6 @@ import type { ClientEvent } from './collaboration/types'; const EXTENSION_SERVER_ID = '__extension__'; const TELEMETRY_PRIMARY = 'https://updates.openconduit.ai'; -const TELEMETRY_BACKUP = 'https://openconduit-release-api.chumchal-account.workers.dev'; // ─── Telemetry helpers ──────────────────────────────────────────────────────── // These are exported so main.ts can call them at boot and on crash. @@ -70,16 +69,12 @@ async function postTelemetry(payload: object): Promise { const ua = `openconduit/${app.getVersion()}`; const body = JSON.stringify(payload); const headers = { 'Content-Type': 'application/json', 'User-Agent': ua }; - // Try primary first; fall back to backup if it fails or times out. - for (const base of [TELEMETRY_PRIMARY, TELEMETRY_BACKUP]) { - try { - const res = await fetch(`${base}/telemetry`, { - method: 'POST', headers, body, - signal: AbortSignal.timeout(4000), - }); - if (res.ok || res.status < 500) return; // success or client error — don't retry - } catch { /* try backup */ } - } + try { + await fetch(`${TELEMETRY_PRIMARY}/telemetry`, { + method: 'POST', headers, body, + signal: AbortSignal.timeout(4000), + }); + } catch { /* silently swallow */ } } export async function fireTelemetrySessionStart(): Promise { @@ -88,6 +83,7 @@ export async function fireTelemetrySessionStart(): Promise { if (!settings.telemetry?.usageReports) return; await postTelemetry({ event: 'session_start', + machineId: getMachineId(), appVersion: app.getVersion(), platform: process.platform, arch: process.arch, @@ -135,8 +131,10 @@ export async function fireTelemetryCrash(error: Error, extra?: { crashDumpsDir?: if (!settings.telemetry?.crashReports) return; await postTelemetry({ event: 'crash', + machineId: getMachineId(), appVersion: app.getVersion(), platform: process.platform, + arch: process.arch, electronVersion: process.versions.electron, errorType: error.name, errorMessage: error.message.replace(/(?:\/[\w.-]+){2,}/g, '').slice(0, 300), @@ -1312,6 +1310,15 @@ export function registerIpcHandlers(): void { } catch { /* log write failure must never crash the app */ } }); + // Renderer-side JS errors (window.onerror, unhandledrejection, React boundary) + // arrive here so they can be persisted and included in crash telemetry. + ipcMain.on('renderer:error', (_e, { message, stack }: { message: string; stack?: string }) => { + const err = new Error(message); + err.name = 'RendererError'; + if (stack) err.stack = stack; + void fireTelemetryCrash(err); + }); + ipcMain.handle('log:open', async (): Promise => { await fs.mkdir(logsDir, { recursive: true }); await shell.openPath(logsDir); @@ -1403,6 +1410,8 @@ export function registerIpcHandlers(): void { }); // ─── Crash report: manual send ─────────────────────────────────────────── + ipcMain.handle('machine:get-id', (): string => getMachineId()); + ipcMain.handle('crash:has-stored', (): boolean => { return getStoredCrash() !== undefined; }); @@ -1412,6 +1421,7 @@ export function registerIpcHandlers(): void { if (!crash) return; await postTelemetry({ event: 'crash', + machineId: getMachineId(), appVersion: crash.appVersion, platform: crash.platform, electronVersion: crash.electronVersion, diff --git a/packages/desktop/src/preload.ts b/packages/desktop/src/preload.ts index d5aa1d5..34e388d 100644 --- a/packages/desktop/src/preload.ts +++ b/packages/desktop/src/preload.ts @@ -1,4 +1,7 @@ import { contextBridge, ipcRenderer, crashReporter } from 'electron'; +// Side-effect import: hooks up the Sentry IPC bridge so the renderer can +// forward events to the main-process Sentry instance (contextIsolation: true). +import '@sentry/electron/preload'; // ── Crash breadcrumbs ───────────────────────────────────────────────────────── // Intercept every IPC invoke so the last-called channel is written into the @@ -274,6 +277,12 @@ contextBridge.exposeInMainWorld('api', { ipcRenderer.invoke('crash:send-stored'), }, + machine: { + /** Returns the persistent anonymous machine ID used for telemetry deduplication. */ + getId: (): Promise => + ipcRenderer.invoke('machine:get-id'), + }, + diagnostics: { /** * Write an arbitrary key/value pair into the Crashpad extra-parameters @@ -284,6 +293,12 @@ contextBridge.exposeInMainWorld('api', { setParam: (key: string, value: string): void => { try { crashReporter.addExtraParameter(key.slice(0, 40), value.slice(0, 127)); } catch { /* non-fatal */ } }, + /** + * Report a renderer-side JS error to the main process so it can be + * persisted and included in crash telemetry. Fire-and-forget. + */ + reportError: (message: string, stack?: string): void => + ipcRenderer.send('renderer:error', { message, stack }), }, folder: { diff --git a/packages/desktop/src/renderer.ts b/packages/desktop/src/renderer.ts index 4fcc7cf..0de37e9 100644 --- a/packages/desktop/src/renderer.ts +++ b/packages/desktop/src/renderer.ts @@ -1,4 +1,5 @@ import './index.css'; +import * as Sentry from '@sentry/electron/renderer'; import { initService } from '@openconduit/core/services'; import type { AppService } from '@openconduit/core/services/appService'; import { debugConsole } from '@openconduit/core'; @@ -6,12 +7,20 @@ import type { DebugLevel, LogCategory } from '@openconduit/core'; import React from 'react'; import { createRoot } from 'react-dom/client'; import App from '@openconduit/core/App'; +import { SentryDiag } from './renderer/SentryDiag'; // ── Renderer lifecycle markers ─────────────────────────────────────────────── // Each call writes a key into the Crashpad extra-parameters table so the // renderer's progress is visible in .dmp / .ips files if V8 crashes. window.api.diagnostics.setParam('rendererState', 'preInit'); +// Initialise Sentry in the renderer — events are forwarded to the main process +// via the IPC bridge set up in preload.ts, so no CORS issues. +Sentry.init({ + dsn: __SENTRY_DSN__ || undefined, + release: `openconduit@${__APP_VERSION__}`, +}); + // Wire the Electron IPC bridge to the AppService interface. // This must run before React renders so stores can access the service. initService(window.api as AppService); @@ -33,12 +42,23 @@ function logRendererError(message: string, stack?: string) { } catch { /* log failure must never throw */ } } +// Throttle: send at most one renderer error to crash telemetry per 60 s. +let lastErrorReportMs = 0; +function reportRendererError(message: string, stack?: string) { + logRendererError(message, stack); + const now = Date.now(); + if (now - lastErrorReportMs > 60_000) { + lastErrorReportMs = now; + try { window.api.diagnostics.reportError(message, stack); } catch { /* non-fatal */ } + } +} + window.addEventListener('error', (e) => { - logRendererError(`Uncaught error: ${e.message}`, e.error?.stack); + reportRendererError(`Uncaught error: ${e.message}`, e.error?.stack); }); window.addEventListener('unhandledrejection', (e) => { const msg = e.reason instanceof Error ? e.reason.message : String(e.reason); - logRendererError(`Unhandled rejection: ${msg}`, e.reason?.stack); + reportRendererError(`Unhandled rejection: ${msg}`, e.reason?.stack); }); // ── React Error Boundary ────────────────────────────────────────────────────── @@ -54,10 +74,15 @@ class AppErrorBoundary extends React.Component< } componentDidCatch(error: Error, info: React.ErrorInfo) { - logRendererError( + reportRendererError( `React boundary caught: ${error.message}`, (error.stack ?? '') + '\n\nComponent stack:' + info.componentStack, ); + // Send to Sentry with React component stack as extra context. + Sentry.withScope((scope) => { + scope.setExtra('componentStack', info.componentStack); + Sentry.captureException(error); + }); } reload() { @@ -105,6 +130,11 @@ createRoot(document.getElementById('root')!).render( React.createElement( React.StrictMode, null, - React.createElement(AppErrorBoundary, null, React.createElement(App)), + React.createElement( + React.Fragment, + null, + React.createElement(AppErrorBoundary, null, React.createElement(App)), + React.createElement(SentryDiag), + ), ), ); diff --git a/packages/desktop/src/renderer/SentryDiag.tsx b/packages/desktop/src/renderer/SentryDiag.tsx new file mode 100644 index 0000000..3241198 --- /dev/null +++ b/packages/desktop/src/renderer/SentryDiag.tsx @@ -0,0 +1,151 @@ +/** + * SentryDiag — hidden diagnostics overlay for verifying Sentry integration. + * + * Toggle: Cmd+Shift+Option+D (macOS) / Ctrl+Alt+Shift+D (Windows/Linux) + * + * Shows: + * - Whether a DSN is configured (first/last chars only, never the full URL) + * - Whether the Sentry client initialised successfully + * - Whether the preload IPC bridge is present (window.__SENTRY_IPC__) + * - A button that fires a test captureMessage and returns the event ID + */ + +import React, { useEffect, useState, useCallback } from 'react'; +import * as Sentry from '@sentry/electron/renderer'; + +declare const __SENTRY_DSN__: string; + +function maskDsn(dsn: string): string { + try { + const url = new URL(dsn); + // Show scheme + first 6 chars of key + *** + host, hide project ID + const key = url.username; + return `${url.protocol}//${key.slice(0, 6)}***@${url.host}/***`; + } catch { + return dsn.slice(0, 12) + '***'; + } +} + +interface Row { label: string; ok: boolean; detail?: string } + +function StatusRow({ label, ok, detail }: Row) { + return ( +
+
+ {ok ? '✓' : '✗'} + {label} +
+ {detail && ( +
{detail}
+ )} +
+ ); +} + +export function SentryDiag() { + const [visible, setVisible] = useState(false); + const [testResult, setTestResult] = useState<{ id: string | null; ts: number } | null>(null); + const [sending, setSending] = useState(false); + + // Cmd+Shift+Option+D (mac) / Ctrl+Alt+Shift+D (win/linux) + // Use e.code (physical key) not e.key — Option+D on macOS produces '∂', not 'd'. + // Use navigator.userAgent — process.platform is not available in the renderer. + const isMac = navigator.userAgent.includes('Macintosh'); + const handleKey = useCallback((e: KeyboardEvent) => { + const mod = isMac ? e.metaKey : e.ctrlKey; + if (mod && e.shiftKey && e.altKey && e.code === 'KeyD') { + e.preventDefault(); + setVisible(v => !v); + setTestResult(null); + } + }, []); + + useEffect(() => { + window.addEventListener('keydown', handleKey); + return () => window.removeEventListener('keydown', handleKey); + }, [handleKey]); + + if (!visible) return null; + + const dsn = __SENTRY_DSN__; + const hasDsn = !!dsn; + const client = Sentry.getClient(); + const clientOk = !!client; + // The preload bridge writes window.__SENTRY_IPC__ when hookupIpc() runs + const bridgeOk = typeof (window as { __SENTRY_IPC__?: unknown }).__SENTRY_IPC__ === 'object'; + + const sendTest = async () => { + setSending(true); + setTestResult(null); + const id = Sentry.captureMessage('[OpenConduit] Sentry diagnostics test', 'info'); + // Give the IPC bridge ~2 s to forward the event to the main process + await new Promise(r => setTimeout(r, 2000)); + setTestResult({ id: id ?? null, ts: Date.now() }); + setSending(false); + }; + + return ( +
{ if (e.target === e.currentTarget) setVisible(false); }} + > +
+
+

Sentry Diagnostics

+ +
+ +
+ + + +
+ + + + {testResult && ( +
+ {testResult.id + ? <> +
Event sent ✓
+
+ ID: {testResult.id} +
+ + :
No event ID — check DSN and network.
+ } +
+ )} + +
+ {navigator.userAgent.includes('Macintosh') ? '⌘⇧⌥D' : 'Ctrl+Alt+Shift+D'} to toggle +
+
+
+ ); +} diff --git a/packages/desktop/src/renderer/env.d.ts b/packages/desktop/src/renderer/env.d.ts index e1e3a66..1d3feba 100644 --- a/packages/desktop/src/renderer/env.d.ts +++ b/packages/desktop/src/renderer/env.d.ts @@ -20,6 +20,7 @@ type UnsubFn = () => void; declare global { const __APP_VERSION__: string; + const __SENTRY_DSN__: string; interface Window { api: { chat: { @@ -150,6 +151,8 @@ declare global { diagnostics: { /** Write a key/value pair into the Crashpad minidump for crash diagnosis. */ setParam: (key: string, value: string) => void; + /** Report a renderer-side JS error to the main process for crash telemetry. */ + reportError: (message: string, stack?: string) => void; }; crash: { /** Returns true if a crash report has been stored and is available to send. */ @@ -157,6 +160,10 @@ declare global { /** Sends the stored crash report to telemetry and clears it. */ sendStored: () => Promise; }; + machine: { + /** Returns the persistent anonymous machine ID used for telemetry deduplication. */ + getId: () => Promise; + }; folder: { /** Opens a native directory picker; resolves to the selected path or null. */ pick: () => Promise; diff --git a/packages/desktop/vite.main.config.ts b/packages/desktop/vite.main.config.ts index 77b68cd..14163f4 100644 --- a/packages/desktop/vite.main.config.ts +++ b/packages/desktop/vite.main.config.ts @@ -1,15 +1,36 @@ -import { defineConfig } from 'vite'; +import { defineConfig, loadEnv } from 'vite'; import path from 'path'; +import { sentryVitePlugin } from '@sentry/vite-plugin'; // https://vitejs.dev/config -export default defineConfig({ +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, __dirname, ''); + + return { + define: { + // Injected at build time from the SENTRY_DSN env var (set as a GitHub + // Actions secret). Empty string in dev → Sentry is a no-op. + 'process.env.SENTRY_DSN': JSON.stringify(env.SENTRY_DSN ?? ''), + }, resolve: { alias: { '@main': path.resolve(__dirname, 'src/main'), '@shared': path.resolve(__dirname, 'src/shared'), }, }, + plugins: [ + // Upload main-process source maps to Sentry only in CI (when auth token is set). + ...(env.SENTRY_AUTH_TOKEN ? [sentryVitePlugin({ + org: 'openconduit', + project: env.SENTRY_PROJECT, + authToken: env.SENTRY_AUTH_TOKEN, + release: { name: process.env.npm_package_version }, + sourcemaps: { assets: '.vite/build/**' }, + telemetry: false, + })] : []), + ], build: { + sourcemap: true, // required for Sentry source map upload rollupOptions: { // Optional native modules used by ws / @google/genai — not needed at runtime. // @aws-sdk/* and @smithy/* were previously external but that breaks packaged @@ -26,7 +47,23 @@ export default defineConfig({ ], output: { inlineDynamicImports: true, + // pdfjs-dist (bundled via pdf-parse) has top-level `new DOMMatrix()` that runs + // at module evaluation time. Its own polyfill loads @napi-rs/canvas via the + // native `.node` binding, which fails in packaged Electron builds due to ABI + // mismatch — leaving globalThis.DOMMatrix undefined and crashing on startup. + // @napi-rs/canvas/geometry.js is pure JavaScript (no native binding) and + // always loads successfully. This banner runs before any module code in the + // bundle, guaranteeing DOMMatrix is defined before pdfjs is evaluated. + banner: [ + 'try {', + ' const _geo = require(\'@napi-rs/canvas/geometry\');', + ' if (!globalThis.DOMMatrix) globalThis.DOMMatrix = _geo.DOMMatrix;', + ' if (!globalThis.DOMRect) globalThis.DOMRect = _geo.DOMRect;', + ' if (!globalThis.DOMPoint) globalThis.DOMPoint = _geo.DOMPoint;', + '} catch (_e) { /* geometry.js unavailable — DOMMatrix may crash later */ }', + ].join('\n'), }, }, }, + }; }); diff --git a/packages/desktop/vite.renderer.config.ts b/packages/desktop/vite.renderer.config.ts index 71f6522..fc9272f 100644 --- a/packages/desktop/vite.renderer.config.ts +++ b/packages/desktop/vite.renderer.config.ts @@ -1,4 +1,4 @@ -import { defineConfig } from 'vite'; +import { defineConfig, loadEnv } from 'vite'; import react from '@vitejs/plugin-react'; import path from 'path'; import { createRequire } from 'module'; @@ -7,10 +7,19 @@ const require = createRequire(import.meta.url); const { version } = require('./package.json') as { version: string }; // https://vitejs.dev/config -export default defineConfig({ +export default defineConfig(({ mode }) => { + // Load .env from this package directory — empty prefix picks up all vars, + // not just VITE_-prefixed ones (e.g. SENTRY_DSN, which we don't want + // accidentally exposed to the browser via import.meta.env). + const env = loadEnv(mode, __dirname, ''); + + return { plugins: [react()], define: { __APP_VERSION__: JSON.stringify(version), + // Injected at build time from SENTRY_DSN in .env or the shell environment. + // Empty string → Sentry.init() receives undefined and no-ops. + __SENTRY_DSN__: JSON.stringify(env.SENTRY_DSN ?? ''), }, css: { postcss: './postcss.config.cjs', @@ -43,4 +52,5 @@ export default defineConfig({ '@openconduit/core/App', ], }, + }; }); diff --git a/scripts/analyze-dump.mjs b/scripts/analyze-dump.mjs new file mode 100644 index 0000000..02e3adf --- /dev/null +++ b/scripts/analyze-dump.mjs @@ -0,0 +1,352 @@ +#!/usr/bin/env node +/** + * analyze-dump.mjs + * + * Parses Crashpad/Breakpad Minidump (.dmp) files and prints a human-readable + * summary: architecture, OS version, exception code, faulting address, and + * the list of loaded modules. If `minidump_stackwalk` is on PATH (install + * via `brew install google-breakpad`) it also prints the full stack trace. + * + * Usage: + * node scripts/analyze-dump.mjs error_dump/*.dmp + * node scripts/analyze-dump.mjs path/to/file.dmp + */ +import { readFileSync } from 'node:fs'; +import { spawnSync } from 'node:child_process'; +import path from 'node:path'; + +const args = process.argv.slice(2); +if (args.length === 0) { + console.error('Usage: node scripts/analyze-dump.mjs [...]'); + process.exit(1); +} + +// ─── Lookup tables ──────────────────────────────────────────────────────────── + +const EXCEPTION_CODES = { + 0xC0000005: 'ACCESS_VIOLATION (segfault / bad pointer read-write)', + 0xC0000006: 'IN_PAGE_ERROR (page fault)', + 0xC0000017: 'NO_MEMORY / OOM', + 0xC000001D: 'ILLEGAL_INSTRUCTION (SIGILL)', + 0xC0000025: 'NONCONTINUABLE_EXCEPTION', + 0x80000001: 'GUARD_PAGE_VIOLATION', + 0x80000003: 'BREAKPOINT', + 0x80000004: 'SINGLE_STEP', + 0xC0000094: 'INTEGER_DIVIDE_BY_ZERO', + 0xC00000FD: 'STACK_OVERFLOW', + 0xC0000135: 'DLL_NOT_FOUND', + 0xC0000142: 'DLL_INIT_FAILED', + 0xC0000409: 'STACK_BUFFER_OVERRUN', + 0xC000C000: 'HEAP_CORRUPTION', + // POSIX signals as written by Crashpad on macOS/Linux + 0x00000004: 'SIGILL (illegal instruction)', + 0x00000006: 'SIGABRT (abort / V8 fatal error)', + 0x00000008: 'SIGFPE (floating-point exception)', + 0x0000000B: 'SIGSEGV (segmentation fault)', +}; + +const ARCH_NAMES = { + 0: 'x86 (32-bit)', + 5: 'ARM', + 6: 'IA-64', + 9: 'x86-64 (AMD64)', + 12: 'ARM64 (Apple Silicon)', + 0xffff: 'Unknown', +}; + +const OS_NAMES = { + 0: 'Win32s', + 1: 'Windows 9x', + 2: 'Windows NT', + 3: 'Windows CE', + 0x8000: 'Unix', + 0x8101: 'macOS', + 0x8102: 'iOS', + 0x8201: 'Linux', + 0x8202: 'Solaris', + 0x8203: 'Android', +}; + +// ─── Binary helpers ─────────────────────────────────────────────────────────── + +function hex(n, pad = 8) { + return '0x' + (n >>> 0).toString(16).toUpperCase().padStart(pad, '0'); +} + +/** Read a MINIDUMP_STRING (UINT32 byte-len + UTF-16LE chars) at rva. */ +function readMDString(buf, rva) { + if (!rva || rva + 4 > buf.length) return ''; + const byteLen = buf.readUInt32LE(rva); + if (!byteLen || rva + 4 + byteLen > buf.length) return ''; + return buf.subarray(rva + 4, rva + 4 + byteLen).toString('utf16le'); +} + +function readBigUInt64(buf, offset) { + const lo = buf.readUInt32LE(offset); + const hi = buf.readUInt32LE(offset + 4); + return (BigInt(hi) << 32n) | BigInt(lo); +} + +// ─── Stream parsers ─────────────────────────────────────────────────────────── + +/** SystemInfoStream (type 7) — architecture, OS version. */ +function parseSystemInfo(buf, rva) { + if (rva + 28 > buf.length) return null; + const arch = buf.readUInt16LE(rva + 0); + const numCpus = buf.readUInt8(rva + 6); + const major = buf.readUInt32LE(rva + 8); + const minor = buf.readUInt32LE(rva + 12); + const build = buf.readUInt32LE(rva + 16); + const platform = buf.readUInt32LE(rva + 20); + const csdRva = buf.readUInt32LE(rva + 24); + return { + arch: ARCH_NAMES[arch] ?? `Unknown (${arch})`, + cpuCount: numCpus, + os: OS_NAMES[platform] ?? `Unknown OS (${hex(platform)})`, + version: `${major}.${minor}.${build}`, + csd: readMDString(buf, csdRva), + platform, + }; +} + +/** ExceptionStream (type 6) — what actually crashed. */ +function parseException(buf, rva) { + if (rva + 48 > buf.length) return null; + // MINIDUMP_EXCEPTION_STREAM layout: + // +0 UINT32 ThreadId + // +4 UINT32 __alignment + // +8 MINIDUMP_EXCEPTION: + // +8 UINT32 ExceptionCode + // +12 UINT32 ExceptionFlags + // +16 UINT64 ExceptionRecord (linked) + // +24 UINT64 ExceptionAddress + // +32 UINT32 NumberParameters + // +36 UINT32 __unusedAlignment + // +40 UINT64[15] ExceptionInformation + const threadId = buf.readUInt32LE(rva + 0); + const code = buf.readUInt32LE(rva + 8); + const flags = buf.readUInt32LE(rva + 12); + const address = readBigUInt64(buf, rva + 24); + const numParams = buf.readUInt32LE(rva + 32); + const params = []; + for (let i = 0; i < Math.min(numParams, 15) && rva + 40 + (i + 1) * 8 <= buf.length; i++) { + params.push(readBigUInt64(buf, rva + 40 + i * 8)); + } + return { + threadId, + code, + flags, + address, + params, + codeDesc: EXCEPTION_CODES[code] ?? EXCEPTION_CODES[code >>> 0] ?? 'Unknown exception code', + }; +} + +/** MiscInfoStream (type 15) — process ID. */ +function parseMiscInfo(buf, rva) { + if (rva + 12 > buf.length) return null; + const flags = buf.readUInt32LE(rva + 4); + const pid = (flags & 0x1) ? buf.readUInt32LE(rva + 8) : 0; + return { pid }; +} + +/** + * ModuleListStream (type 4) — loaded modules. + * MINIDUMP_MODULE size = 108 bytes: + * +0 UINT64 BaseOfImage + * +8 UINT32 SizeOfImage + * +12 UINT32 CheckSum + * +16 UINT32 TimeDateStamp + * +20 UINT32 ModuleNameRva + * +24 VS_FIXEDFILEINFO (52 bytes) + * +76 MINIDUMP_LOCATION_DESCRIPTOR CvRecord (8 bytes) + * +84 MINIDUMP_LOCATION_DESCRIPTOR MiscRecord (8 bytes) + * +92 UINT64 Reserved0 + * +100 UINT64 Reserved1 + */ +function parseModuleList(buf, rva) { + if (rva + 4 > buf.length) return []; + const count = buf.readUInt32LE(rva); + const MODULE_SIZE = 108; + const modules = []; + for (let i = 0; i < count; i++) { + const base = rva + 4 + i * MODULE_SIZE; + if (base + MODULE_SIZE > buf.length) break; + const imageBase = readBigUInt64(buf, base + 0); + const imageSize = buf.readUInt32LE(base + 8); + const nameRva = buf.readUInt32LE(base + 20); + const name = readMDString(buf, nameRva); + modules.push({ base: imageBase, size: imageSize, name }); + } + return modules; +} + +// ─── Core analysis ──────────────────────────────────────────────────────────── + +function analyzeDump(filePath) { + const buf = readFileSync(filePath); + + if (buf.length < 32 || buf.subarray(0, 4).toString('ascii') !== 'MDMP') { + console.error(` ✗ Not a valid Minidump file`); + return; + } + + const numStreams = buf.readUInt32LE(8); + const dirRva = buf.readUInt32LE(12); + const ts = new Date(buf.readUInt32LE(20) * 1000); + + console.log(` File: ${path.basename(filePath)} (${(buf.length / 1024).toFixed(0)} KB)`); + console.log(` Timestamp: ${ts.toISOString()}`); + + // Walk stream directory (each entry = 12 bytes: type, size, rva) + const streams = {}; + for (let i = 0; i < numStreams; i++) { + const entry = dirRva + i * 12; + if (entry + 12 > buf.length) break; + const type = buf.readUInt32LE(entry + 0); + streams[type] = { + size: buf.readUInt32LE(entry + 4), + rva: buf.readUInt32LE(entry + 8), + }; + } + + // ── System info ──────────────────────────────────────────────────────────── + if (streams[7]) { + const info = parseSystemInfo(buf, streams[7].rva); + if (info) { + const osStr = info.os === 'macOS' + ? `macOS (Darwin ${info.version})${info.csd ? ' ' + info.csd : ''}` + : `${info.os} ${info.version}${info.csd ? ' ' + info.csd : ''}`; + console.log(` Arch: ${info.arch} (${info.cpuCount} CPU${info.cpuCount !== 1 ? 's' : ''})`); + console.log(` OS: ${osStr}`); + } + } + + // ── Exception ────────────────────────────────────────────────────────────── + if (streams[6]) { + const ex = parseException(buf, streams[6].rva); + if (ex) { + console.log(''); + console.log(' ┌─ Exception ───────────────────────────────────────────────┐'); + console.log(` │ Code: ${hex(ex.code, 8)} → ${ex.codeDesc}`); + console.log(` │ Address: 0x${ex.address.toString(16).toUpperCase().padStart(16, '0')}`); + console.log(` │ Thread ID: ${ex.threadId}`); + if (ex.code === 0xC0000005 && ex.params.length >= 2) { + // ACCESS_VIOLATION: param[0] = 0=read / 1=write / 8=DEP, param[1] = bad address + const ops = { 0n: 'read from', 1n: 'write to', 8n: 'execute at' }; + const op = ops[ex.params[0]] ?? `access (${ex.params[0]})`; + console.log(` │ → Tried to ${op} 0x${ex.params[1].toString(16).toUpperCase().padStart(16, '0')}`); + } + console.log(' └───────────────────────────────────────────────────────────┘'); + } + } else { + console.log(' (no ExceptionStream found)'); + } + + // ── PID ──────────────────────────────────────────────────────────────────── + if (streams[15]) { + const misc = parseMiscInfo(buf, streams[15].rva); + if (misc?.pid) console.log(` PID: ${misc.pid}`); + } + + // ── Modules ──────────────────────────────────────────────────────────────── + if (streams[4]) { + const modules = parseModuleList(buf, streams[4].rva); + if (modules.length > 0) { + console.log(''); + console.log(' ── Loaded Modules (app / runtime) ─────────────────────────'); + // Show app-relevant modules first; fall back to first 25 if nothing matches + const highlight = modules.filter(m => + /electron|openconduit|node|v8|libc\+\+|libsystem_c|libdyld|dyld|chrome|ffmpeg/i.test(m.name) + ); + const toShow = highlight.length > 0 ? highlight : modules.slice(0, 25); + for (const m of toShow) { + const shortName = m.name.split(/[\\/]/).pop() ?? m.name; + const baseStr = '0x' + m.base.toString(16).toUpperCase(); + const sizeStr = `(${(m.size / 1024).toFixed(0)} KB)`.padStart(10); + console.log(` ${baseStr.padEnd(20)} ${sizeStr} ${shortName}`); + } + if (modules.length > toShow.length) { + console.log(` … and ${modules.length - toShow.length} more modules`); + } + console.log(` Total: ${modules.length} modules`); + } + } +} + +// ─── Stack trace via lldb or minidump_stackwalk ─────────────────────────────── + +/** Try minidump_stackwalk (needs `brew install google-breakpad` *tools* build). */ +function tryStackwalk(filePath) { + for (const cmd of ['minidump_stackwalk', '/opt/homebrew/bin/minidump_stackwalk', '/usr/local/bin/minidump_stackwalk']) { + try { + const r = spawnSync(cmd, [filePath], { encoding: 'utf-8', timeout: 20_000, maxBuffer: 4 * 1024 * 1024 }); + if (r.stdout || r.status === 0) return { tool: 'minidump_stackwalk', output: r.stdout || r.stderr || '' }; + } catch { /* not available */ } + } + return null; +} + +/** + * Use lldb (ships with Xcode CLT) to load the minidump and run: + * bt all — all-threads backtrace + * register read — registers of crashing thread + */ +function tryLldb(filePath) { + const lldb = spawnSync('which', ['lldb'], { encoding: 'utf-8' }); + if (lldb.status !== 0) return null; + + // Feed commands via stdin so we don't need a TTY + const commands = [ + 'bt all', + 'register read', + 'quit', + ].join('\n'); + + const r = spawnSync('lldb', ['--core', filePath], { + input: commands, + encoding: 'utf-8', + timeout: 30_000, + maxBuffer: 8 * 1024 * 1024, + }); + + const out = (r.stdout || '') + (r.stderr || ''); + return out.trim() ? { tool: 'lldb', output: out } : null; +} + +// ─── Entry point ────────────────────────────────────────────────────────────── + +for (const arg of args) { + console.log(''); + console.log('═'.repeat(72)); + try { + analyzeDump(arg); + + const result = tryStackwalk(arg) ?? tryLldb(arg); + if (result) { + console.log(''); + console.log(` ── Stack trace (${result.tool}) ${'─'.repeat(Math.max(0, 47 - result.tool.length))}`); + const lines = result.output.split('\n'); + for (const line of lines.slice(0, 150)) { + console.log(' ' + line); + } + if (lines.length > 150) { + console.log(` … (${lines.length - 150} more lines)`); + } + if (result.tool === 'lldb') { + console.log(''); + console.log(' ╌ Frames show only addresses because Electron Framework is stripped.'); + console.log(' ╌ To get symbols, download:'); + console.log(' ╌ https://github.com/electron/electron/releases/download/v/electron-v-darwin-arm64-dsym.zip'); + console.log(' ╌ Extract the .dSYM bundle next to the app and re-run.'); + } + } else { + console.log(''); + console.log(' ╌ No stack-trace tool found. Install Xcode CLT for lldb:'); + console.log(' ╌ xcode-select --install'); + } + } catch (err) { + console.error(` Error reading file: ${err.message}`); + } +} +console.log(''); diff --git a/vite.renderer.config.ts b/vite.renderer.config.ts index b17066b..cc01ee2 100644 --- a/vite.renderer.config.ts +++ b/vite.renderer.config.ts @@ -2,15 +2,32 @@ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import path from 'path'; import { createRequire } from 'module'; +import { sentryVitePlugin } from '@sentry/vite-plugin'; const require = createRequire(import.meta.url); const { version } = require('./package.json') as { version: string }; // https://vitejs.dev/config export default defineConfig({ - plugins: [react()], + plugins: [ + react(), + // Upload renderer source maps to Sentry only in CI (when auth token is set). + ...(process.env.SENTRY_AUTH_TOKEN ? [sentryVitePlugin({ + org: process.env.SENTRY_ORG, + project: process.env.SENTRY_PROJECT, + authToken: process.env.SENTRY_AUTH_TOKEN, + release: { name: version }, + sourcemaps: { assets: '.vite/renderer/**' }, + telemetry: false, + })] : []), + ], define: { __APP_VERSION__: JSON.stringify(version), + // Injected at build time from the SENTRY_DSN env var. Empty string → no-op. + __SENTRY_DSN__: JSON.stringify(process.env.SENTRY_DSN ?? ''), + }, + build: { + sourcemap: true, // required for Sentry source map upload }, css: { postcss: './postcss.config.cjs', From e19aae575c2588d3a17be3a6d91587d73e7d4598 Mon Sep 17 00:00:00 2001 From: "Andrew @ Conduit" Date: Tue, 26 May 2026 13:57:45 -0500 Subject: [PATCH 2/2] feat: update openconduit version to 2.0.0-beta.4 in package-lock.json --- package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 6a94961..80f46c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19251,7 +19251,7 @@ }, "packages/desktop": { "name": "openconduit", - "version": "2.0.0-beta.3", + "version": "2.0.0-beta.4", "license": "AGPL-3.0-only", "dependencies": { "@anthropic-ai/foundry-sdk": "^0.2.3",