From e71350412505921fd9e0db2b089467b9196c7305 Mon Sep 17 00:00:00 2001 From: Dmytro-Fedoryshyn Date: Wed, 8 Jul 2026 20:11:12 +0300 Subject: [PATCH 1/7] Add Sentry integration --- .github/workflows/release.yml | 2 + .gitignore | 5 + README.md | 3 + package-lock.json | 483 ++++++++++++++++++++++++++++++---- package.json | 2 + src/main/application.ts | 6 + src/main/index.ts | 1 + src/main/sentry.ts | 35 +++ src/renderer/main.tsx | 10 +- src/renderer/proto/app.proto | 7 + src/renderer/sentry.ts | 20 ++ vite.config.ts | 26 +- 12 files changed, 540 insertions(+), 60 deletions(-) create mode 100644 src/main/sentry.ts create mode 100644 src/renderer/sentry.ts diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 08d8201..41bcc63 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,6 +29,8 @@ jobs: MACOS_APPLE_ID: ${{ secrets.MACOS_APPLE_ID }} # App-specific password generated at appleid.apple.com for notarization. MACOS_APPLE_PASSWORD: ${{ secrets.MACOS_APPLE_PASSWORD }} + # Sentry project DSN used for production telemetry. + SENTRY_DSN: ${{ secrets.SENTRY_DSN }} steps: - uses: actions/checkout@v6 diff --git a/.gitignore b/.gitignore index 388d213..b042ecc 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,11 @@ out/ # Generated code. gen/ +# Local environment. +.env +.env.* +!.env.example + # macOS metadata. .DS_Store diff --git a/README.md b/README.md index 9b70fad..b81d38f 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,9 @@ npm run build Builds a macOS app and `.dmg`. Signing and notarization need Apple credentials from the environment; without them the build still produces an unsigned `.dmg`. +Production Sentry telemetry is included only when `SENTRY_DSN` is set during the build. +Release builds read it from the `SENTRY_DSN` GitHub Actions secret. + ## Project layout - **`src/main/`** — app lifecycle, window, tray, metrics, and process services; owns privileged work and the typed IPC. diff --git a/package-lock.json b/package-lock.json index d34802a..e436fe8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,8 @@ "@mobrowser/cli": "2.8.0", "@mobrowser/native": "2.8.0", "@radix-ui/react-dropdown-menu": "^2.0.6", + "@sentry/node": "^10.64.0", + "@sentry/react": "^10.64.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "lucide-react": "^0.503.0", @@ -60,6 +62,49 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@apm-js-collab/code-transformer": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@apm-js-collab/code-transformer/-/code-transformer-0.15.0.tgz", + "integrity": "sha512-XmXYVs8CzJ1Aj79noVbn2weUO/XWtRyURpGqx7aU7DOXlUQhR0WKOQNF0okh7PCeY37vxf7kU3v57OAkEPm3ww==", + "license": "Apache-2.0", + "dependencies": { + "@types/estree": "^1.0.8", + "astring": "^1.9.0", + "esquery": "^1.7.0", + "meriyah": "^6.1.4", + "semifies": "^1.0.0", + "source-map": "^0.6.0" + }, + "bin": { + "code-transformer": "cli.js" + } + }, + "node_modules/@apm-js-collab/code-transformer-bundler-plugins": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@apm-js-collab/code-transformer-bundler-plugins/-/code-transformer-bundler-plugins-0.5.0.tgz", + "integrity": "sha512-YxLBY5nGlurL7QeJLq6e5g0ouBpAp0pwgyA/5rHXEXwhiPLn9ZHbT+Y2LlP90GT872cSocfjWRYu/fnpuBudNQ==", + "license": "MIT", + "dependencies": { + "@apm-js-collab/code-transformer": "^0.15.0", + "es-module-lexer": "^2.1.0", + "magic-string": "^0.30.21", + "module-details-from-path": "^1.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@apm-js-collab/tracing-hooks": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@apm-js-collab/tracing-hooks/-/tracing-hooks-0.10.1.tgz", + "integrity": "sha512-w2OWXR7FWrKqSziuE9+QclaZrStxO/8+OwbXM635s/zs0Eez1Qo3ivSPdB2WsaPY/iznKTytONPx/PitD7IXcA==", + "license": "Apache-2.0", + "dependencies": { + "@apm-js-collab/code-transformer": "^0.15.0", + "debug": "^4.4.1", + "module-details-from-path": "^1.0.4" + } + }, "node_modules/@bufbuild/protobuf": { "version": "2.12.0", "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.12.0.tgz", @@ -450,7 +495,6 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { @@ -561,6 +605,119 @@ "@emnapi/runtime": "^1.7.1" } }, + "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/@opentelemetry/api-logs": { + "version": "0.220.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.220.0.tgz", + "integrity": "sha512-CmVa4ImJ+ynfrPMNaAXHET6Bhb44SwzmfyVJFq9ni2jgXJR/l7C6gfVFddNmHP+ZOkP9cf4f9DBe68qVLTHc9w==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.9.0.tgz", + "integrity": "sha512-m2nckMT80NnmjTYSPjJQObBJ+8dgkoajEOUbznL8AHZ3T3yHRk2P7gI1PhEBc1+lOnrYE9UWrWHqJDsmqjmNbw==", + "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/@opentelemetry/instrumentation": { + "version": "0.220.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.220.0.tgz", + "integrity": "sha512-xQx3E2WxP1mDvKzxLxX+CTCtNLa560YJZ3087qYHerl2YmiKpv7AH+dAy7vmx+eVrZ5BwhfWUAVoKOoxCNHcpw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.220.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/@opentelemetry/resources": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.9.0.tgz", + "integrity": "sha512-jyA5MBLQ+Dkl3+JsZkUoUvL7yHvU64kLsvpXKarWm6347Sl1t1bXFTFykUePNpT5WH5pm9a2Qtt03iIYQhZ1Fg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.9.0", + "@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": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace/-/sdk-trace-2.9.0.tgz", + "integrity": "sha512-sGA19HvtrrSKYsseHphluH6j3p6Xa3fqc7c7y8f/7mYWejc1lyDFcpSdD1kYa50HCLUeEo4zA5bW0pniaPszuw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.9.0", + "@opentelemetry/resources": "2.9.0", + "@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.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.9.0.tgz", + "integrity": "sha512-cp9zmTl62R8PJrpvFcmc8N2JQU/xfa0S+61q511Nji+QxCfZ8Ifvg7H27G8cANe4crg4RTrWsVvanHiXjSp6ag==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.9.0", + "@opentelemetry/resources": "2.9.0", + "@opentelemetry/sdk-trace": "2.9.0", + "@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.42.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.42.0.tgz", + "integrity": "sha512-icc5xCzndZfhuJMy5oqk5AvloWquR7jtae74qzpkKkhGp8BivK+oCcEXgGnjCdTfp8hA44l+w8gE8yYJbocJJw==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, "node_modules/@oxc-project/types": { "version": "0.132.0", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.132.0.tgz", @@ -1227,9 +1384,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1246,9 +1400,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1265,9 +1416,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1284,9 +1432,6 @@ "cpu": [ "s390x" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1303,9 +1448,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1322,9 +1464,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1407,6 +1546,203 @@ "devOptional": true, "license": "MIT" }, + "node_modules/@sentry/browser": { + "version": "10.64.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.64.0.tgz", + "integrity": "sha512-CHZ7+79evh8knBtVHjW/XqV3mRaWs2TdjX7i55zpFe9vcNngQHV++0ss8ird/xfMY1mfCDtbyAN+Z6XY2o5aJA==", + "license": "MIT", + "dependencies": { + "@sentry/browser-utils": "10.64.0", + "@sentry/core": "10.64.0", + "@sentry/feedback": "10.64.0", + "@sentry/replay": "10.64.0", + "@sentry/replay-canvas": "10.64.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/browser-utils": { + "version": "10.64.0", + "resolved": "https://registry.npmjs.org/@sentry/browser-utils/-/browser-utils-10.64.0.tgz", + "integrity": "sha512-qXvUpsZdE0+6SovhDZvjN4JkqQEpzeYsrOF5g63wMsqJWWv2vW/pQZIlJs0F6C0t4q5AHZL4fl5rNkvpVqFdKA==", + "license": "MIT", + "dependencies": { + "@sentry/core": "10.64.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/conventions": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@sentry/conventions/-/conventions-0.15.1.tgz", + "integrity": "sha512-ZLP8bRdMON3prWE2tJyImuYscCxdcJeIPIhrOs/rgyFm3C1nCh1B6gdvPj3AZ5zW08oSFFCsq7T+tYEW3h8MNA==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@sentry/core": { + "version": "10.64.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.64.0.tgz", + "integrity": "sha512-HjojJcXD1l2qZ1AXje2s0XY/nYsaUt00wsM1HMBImA8vAClyPisFE/CC0/UD6pEvsGFhVgi8Dcxo7EN41uyeFw==", + "license": "MIT", + "dependencies": { + "@sentry/conventions": "^0.15.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/feedback": { + "version": "10.64.0", + "resolved": "https://registry.npmjs.org/@sentry/feedback/-/feedback-10.64.0.tgz", + "integrity": "sha512-YrmKwRoAto+nwo26DoAhnpNmhkrVkuQFIAXqlTD8ipERjhcSNUYJAkAB+nH4w1KIgm9QQZE1sTq3iNQdPl1XMQ==", + "license": "MIT", + "dependencies": { + "@sentry/core": "10.64.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/node": { + "version": "10.64.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-10.64.0.tgz", + "integrity": "sha512-rhNZ3CTqwdTQHo9Zd+NsoLyW69VIzbMcGMRX9y8W1A6runT4YH/MDhmT0U9oWfNZKN8ngqR/gfVMTnlYVQliCA==", + "license": "MIT", + "dependencies": { + "@opentelemetry/api": "^1.9.1", + "@opentelemetry/instrumentation": "^0.220.0", + "@opentelemetry/sdk-trace-base": "^2.9.0", + "@sentry/conventions": "^0.15.1", + "@sentry/core": "10.64.0", + "@sentry/node-core": "10.64.0", + "@sentry/opentelemetry": "10.64.0", + "@sentry/server-utils": "10.64.0", + "import-in-the-middle": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/node-core": { + "version": "10.64.0", + "resolved": "https://registry.npmjs.org/@sentry/node-core/-/node-core-10.64.0.tgz", + "integrity": "sha512-dcma5uEWl0SXywY4vzrlOwuz4NBPyPhrzqL1NjlU6Di/OVbyYAZJPCwsR8XYDz73PGc5sU3qKSRoXWX56Ip0wA==", + "license": "MIT", + "dependencies": { + "@sentry/conventions": "^0.15.1", + "@sentry/core": "10.64.0", + "@sentry/opentelemetry": "10.64.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" + }, + "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 + } + } + }, + "node_modules/@sentry/opentelemetry": { + "version": "10.64.0", + "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-10.64.0.tgz", + "integrity": "sha512-hXw8dwSSA9/9r3VGy0PO2SJp7g5/yH2+7Bak60EkOT21AT1BwFnVEsVQyZb+UHjG7UWne/jDbwdWPSUm/rtovw==", + "license": "MIT", + "dependencies": { + "@sentry/conventions": "^0.15.1", + "@sentry/core": "10.64.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" + } + }, + "node_modules/@sentry/react": { + "version": "10.64.0", + "resolved": "https://registry.npmjs.org/@sentry/react/-/react-10.64.0.tgz", + "integrity": "sha512-oShEKcosBKdm0kgan8suFb6Jj8poccEyDz2qiflonq/5/hUDyzdVeKfFzf0b1MmdjWSn9k3hfrLNFfPNCuEt7Q==", + "license": "MIT", + "dependencies": { + "@sentry/browser": "10.64.0", + "@sentry/core": "10.64.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^16.14.0 || 17.x || 18.x || 19.x" + } + }, + "node_modules/@sentry/replay": { + "version": "10.64.0", + "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-10.64.0.tgz", + "integrity": "sha512-q8nke2GDSwEiu1zYoctDDvnSNTjHWoVAKo2JXTleD0nwOO6IlAgUYmsm3qEQiSW7BVla9W1TRbW6ChFAUXYZbw==", + "license": "MIT", + "dependencies": { + "@sentry/browser-utils": "10.64.0", + "@sentry/core": "10.64.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/replay-canvas": { + "version": "10.64.0", + "resolved": "https://registry.npmjs.org/@sentry/replay-canvas/-/replay-canvas-10.64.0.tgz", + "integrity": "sha512-EioBwcnnhtPiXzTZJTbZKaMEg3qNM5YZlX1VanoXomPATqfJD62cb/M27zhgiWXXJ7e8/NGVHvwNDYGrqsN39g==", + "license": "MIT", + "dependencies": { + "@sentry/core": "10.64.0", + "@sentry/replay": "10.64.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/server-utils": { + "version": "10.64.0", + "resolved": "https://registry.npmjs.org/@sentry/server-utils/-/server-utils-10.64.0.tgz", + "integrity": "sha512-d568Jhn3WBfm07dsdPsLh0q+qRj71xZEDZFsA33kizD0UuSCR8axtc2YW6aKdPhtuqgm9tHjSX35Q9vK/XmujA==", + "license": "MIT", + "dependencies": { + "@apm-js-collab/code-transformer": "^0.15.0", + "@apm-js-collab/code-transformer-bundler-plugins": "^0.5.0", + "@apm-js-collab/tracing-hooks": "^0.10.1", + "@sentry/conventions": "^0.15.1", + "@sentry/core": "10.64.0", + "magic-string": "~0.30.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", @@ -1581,9 +1917,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1601,9 +1934,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1621,9 +1951,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1641,9 +1968,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1763,7 +2087,6 @@ "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "dev": true, "license": "MIT" }, "node_modules/@types/json-schema": { @@ -2269,6 +2592,15 @@ "node": ">=12" } }, + "node_modules/astring": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", + "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", + "license": "MIT", + "bin": { + "astring": "bin/astring" + } + }, "node_modules/b4a": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", @@ -2447,6 +2779,12 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "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/class-variance-authority": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", @@ -2528,7 +2866,6 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2603,10 +2940,9 @@ } }, "node_modules/es-module-lexer": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", - "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", - "dev": true, + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.0.tgz", + "integrity": "sha512-KLdwQm2NvGLDkQDCGvmiQrhkd0JbMzXthwQAUgWjQuQdBLFa3eiBP5arXZyA+f8x+x7OXgud6bq2rxjGtHV2tw==", "license": "MIT" }, "node_modules/escape-string-regexp": { @@ -2814,7 +3150,6 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", - "dev": true, "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" @@ -2840,7 +3175,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -3074,6 +3408,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/import-in-the-middle": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-3.3.1.tgz", + "integrity": "sha512-0rymlHSFLwZ0ixx8DaQkoIyZojJPY2a0K2nEYslhKJ6jIYO/m0IcCb7iQsFPmS7WmKwISZiIrv5Icstrw/CmqA==", + "license": "Apache-2.0", + "dependencies": { + "cjs-module-lexer": "^2.2.0", + "es-module-lexer": "^2.2.0", + "module-details-from-path": "^1.0.4" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -3319,9 +3667,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -3342,9 +3687,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -3365,9 +3707,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -3388,9 +3727,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -3480,12 +3816,20 @@ "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/meriyah": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/meriyah/-/meriyah-6.1.4.tgz", + "integrity": "sha512-Sz8FzjzI0kN13GK/6MVEsVzMZEPvOhnmmI1lU5+/1cGOiK3QUahntrNNtdVeihrO7t9JpoH75iMNXg6R6uWflQ==", + "license": "ISC", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/minimatch": { "version": "10.2.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", @@ -3502,11 +3846,16 @@ "url": "https://github.com/sponsors/isaacs" } }, + "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", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/nanoid": { @@ -3798,6 +4147,19 @@ } } }, + "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/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -3848,6 +4210,12 @@ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "license": "MIT" }, + "node_modules/semifies": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semifies/-/semifies-1.0.0.tgz", + "integrity": "sha512-xXR3KGeoxTNWPD4aBvL5NUpMTT7WMANr3EWnaS190QVkY52lqqcVRD7Q05UVbBhiWDGWMlJEUam9m7uFFGVScw==", + "license": "Apache-2.0" + }, "node_modules/semver": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", @@ -3891,6 +4259,15 @@ "dev": true, "license": "ISC" }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", diff --git a/package.json b/package.json index 6ea105c..ca182d7 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,8 @@ "@mobrowser/cli": "2.8.0", "@mobrowser/native": "2.8.0", "@radix-ui/react-dropdown-menu": "^2.0.6", + "@sentry/node": "^10.64.0", + "@sentry/react": "^10.64.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "lucide-react": "^0.503.0", diff --git a/src/main/application.ts b/src/main/application.ts index 0524c11..917aab6 100644 --- a/src/main/application.ts +++ b/src/main/application.ts @@ -128,6 +128,12 @@ export class Application { private registerAppService(): void { const window = this.window; ipc.registerService(AppServiceDescriptor, { + async GetApplicationMetadata() { + return { + name: app.name, + version: app.version, + }; + }, async SetAlwaysOnTop(request: SetAlwaysOnTopRequest) { window.setAlwaysOnTop(request.alwaysOnTop); return {}; diff --git a/src/main/index.ts b/src/main/index.ts index 6b8495c..e9f5243 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,3 +1,4 @@ +import "./sentry"; import { Application } from "./application"; new Application().initialize(); diff --git a/src/main/sentry.ts b/src/main/sentry.ts new file mode 100644 index 0000000..cc667e5 --- /dev/null +++ b/src/main/sentry.ts @@ -0,0 +1,35 @@ +import { app } from "@mobrowser/api"; +import * as Sentry from "@sentry/node"; + +declare const SENTRY_DSN: string; + +const SENTRY_FLUSH_TIMEOUT_MS = 2000; + +if (SENTRY_DSN !== "" && app.packaged) { + Sentry.init({ + dsn: SENTRY_DSN, + environment: "production", + release: `${app.name}@${app.version}`, + defaultIntegrations: false, + }); + Sentry.setTag("process", "main"); + // Send only one event per production launch to avoid stats duplication. + Sentry.captureMessage("app.launch", "info"); + + Sentry.startSession(); + Sentry.captureSession(); + + process.once("uncaughtException", captureFatalError); + process.once("unhandledRejection", captureFatalError); +} + +function captureFatalError(error: unknown): void { + Sentry.captureException(error); + void flushSentry().finally(() => { + process.exit(1); + }); +} + +export function flushSentry(): Promise { + return Sentry.flush(SENTRY_FLUSH_TIMEOUT_MS); +} diff --git a/src/renderer/main.tsx b/src/renderer/main.tsx index df43aa2..9d06a9a 100644 --- a/src/renderer/main.tsx +++ b/src/renderer/main.tsx @@ -1,8 +1,12 @@ import React from "react"; import ReactDOM from "react-dom/client"; +import * as Sentry from "@sentry/react"; import "@fontsource-variable/sora/index.css"; import "./index.css"; import App from "./App.tsx"; +import { initializeSentry } from "./sentry"; + +await initializeSentry(); /* * Block trackpad pinch-to-zoom on the page. @@ -17,7 +21,11 @@ window.addEventListener( { passive: false }, ); -ReactDOM.createRoot(document.getElementById("root")!).render( +ReactDOM.createRoot(document.getElementById("root")!, { + onCaughtError: Sentry.reactErrorHandler(), + onRecoverableError: Sentry.reactErrorHandler(), + onUncaughtError: Sentry.reactErrorHandler(), +}).render( , diff --git a/src/renderer/proto/app.proto b/src/renderer/proto/app.proto index ca22008..fcf5b1e 100644 --- a/src/renderer/proto/app.proto +++ b/src/renderer/proto/app.proto @@ -2,6 +2,11 @@ syntax = "proto3"; import "google/protobuf/empty.proto"; +message ApplicationMetadata { + string name = 1; + string version = 2; +} + message SetAlwaysOnTopRequest { bool always_on_top = 1; } @@ -32,6 +37,8 @@ message CopyTextRequest { // App-level renderer-to-main commands. service AppService { + rpc GetApplicationMetadata(google.protobuf.Empty) returns (ApplicationMetadata); + rpc SetAlwaysOnTop(SetAlwaysOnTopRequest) returns (google.protobuf.Empty); // Reports the selected top-level view so main can gate per-view background diff --git a/src/renderer/sentry.ts b/src/renderer/sentry.ts new file mode 100644 index 0000000..ee39380 --- /dev/null +++ b/src/renderer/sentry.ts @@ -0,0 +1,20 @@ +import * as Sentry from "@sentry/react"; +import { ipc } from "./gen/ipc"; + +declare const SENTRY_DSN: string; +declare const SENTRY_ENABLED: boolean; + +export async function initializeSentry(): Promise { + if (SENTRY_DSN === "" || !SENTRY_ENABLED) { + return; + } + + const metadata = await ipc.app.GetApplicationMetadata({}); + + Sentry.init({ + dsn: SENTRY_DSN, + environment: "production", + release: `${metadata.name}@${metadata.version}`, + }); + Sentry.setTag("process", "renderer"); +} diff --git a/vite.config.ts b/vite.config.ts index 03a40e9..044524d 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,24 +2,34 @@ import path from "path"; import react from "@vitejs/plugin-react"; import { defineConfig, type UserConfig } from "vite"; -export default defineConfig(({ mode }) => { +export default defineConfig(({ command, mode }) => { + const buildTimeDefines = createBuildTimeDefines(command === "build"); + if (mode === "main") { - return defineMainConfig(); + return defineMainConfig(buildTimeDefines); } if (mode === "renderer") { - return defineRendererConfig(); + return defineRendererConfig(buildTimeDefines); } throw new Error(`Unsupported Vite config mode: ${mode}`); }); +function createBuildTimeDefines(sentryEnabled: boolean): Record { + return { + SENTRY_DSN: JSON.stringify(process.env.SENTRY_DSN ?? ""), + SENTRY_ENABLED: JSON.stringify(sentryEnabled), + }; +} + // No build sourcemaps: `mobrowser build` output is the distributed app, and // shipping .map files would hand the original TypeScript to anyone who opens // the bundle - defeating the framework's source protection. Dev debugging // goes through the dev server, which has its own transient maps. -function defineMainConfig(): UserConfig { +function defineMainConfig(buildTimeDefines: Record): UserConfig { return { root: path.resolve(__dirname, "./src/main"), + define: buildTimeDefines, build: { target: "esnext", outDir: path.resolve(__dirname, "./out/main"), @@ -32,12 +42,15 @@ function defineMainConfig(): UserConfig { rollupOptions: { external: [ "mobrowser", - // Externalize all Node.js built-in modules + "import-in-the-middle", + "module-details-from-path", + "require-in-the-middle", /^node:.*/, ], }, }, resolve: { + conditions: ["node"], alias: { "@": path.resolve(__dirname, "./src/main"), }, @@ -46,9 +59,10 @@ function defineMainConfig(): UserConfig { } -function defineRendererConfig(): UserConfig { +function defineRendererConfig(buildTimeDefines: Record): UserConfig { return { root: path.resolve(__dirname, "./src/renderer"), + define: buildTimeDefines, plugins: [react()], build: { outDir: path.resolve(__dirname, "./out/renderer"), From 705c60f813cdc98e1b95330882b5323df11d15a1 Mon Sep 17 00:00:00 2001 From: Dmytro-Fedoryshyn Date: Thu, 9 Jul 2026 12:51:36 +0300 Subject: [PATCH 2/7] Prevent Sentry failures from blocking UI startup --- src/renderer/main.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/renderer/main.tsx b/src/renderer/main.tsx index 9d06a9a..782b484 100644 --- a/src/renderer/main.tsx +++ b/src/renderer/main.tsx @@ -6,7 +6,9 @@ import "./index.css"; import App from "./App.tsx"; import { initializeSentry } from "./sentry"; -await initializeSentry(); +void initializeSentry().catch((error: unknown) => { + console.warn("Sentry init failed", error); +}); /* * Block trackpad pinch-to-zoom on the page. From f6cba7369536f518f652f7f7d37daca56cafc085 Mon Sep 17 00:00:00 2001 From: Dmytro-Fedoryshyn Date: Thu, 9 Jul 2026 13:02:39 +0300 Subject: [PATCH 3/7] Avoid embedding Sentry DSN outside builds --- vite.config.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vite.config.ts b/vite.config.ts index 044524d..ccdaa04 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -15,8 +15,10 @@ export default defineConfig(({ command, mode }) => { }); function createBuildTimeDefines(sentryEnabled: boolean): Record { + const sentryDsn = sentryEnabled ? (process.env.SENTRY_DSN ?? "") : ""; + return { - SENTRY_DSN: JSON.stringify(process.env.SENTRY_DSN ?? ""), + SENTRY_DSN: JSON.stringify(sentryDsn), SENTRY_ENABLED: JSON.stringify(sentryEnabled), }; } From f938ec0891963648e742a5aee95b6f9be7737fa6 Mon Sep 17 00:00:00 2001 From: Dmytro-Fedoryshyn Date: Thu, 9 Jul 2026 13:04:38 +0300 Subject: [PATCH 4/7] Clean .gitignore --- .gitignore | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitignore b/.gitignore index b042ecc..9a41973 100644 --- a/.gitignore +++ b/.gitignore @@ -11,8 +11,6 @@ gen/ # Local environment. .env -.env.* -!.env.example # macOS metadata. .DS_Store From c3ac948e21eb306a6db94dbbc386c2fe093bf833 Mon Sep 17 00:00:00 2001 From: Dmytro-Fedoryshyn Date: Thu, 9 Jul 2026 13:05:19 +0300 Subject: [PATCH 5/7] Restore the comment --- vite.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/vite.config.ts b/vite.config.ts index ccdaa04..62f89fa 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -47,6 +47,7 @@ function defineMainConfig(buildTimeDefines: Record): UserConfig "import-in-the-middle", "module-details-from-path", "require-in-the-middle", + // Externalize all Node.js built-in modules /^node:.*/, ], }, From f24e50e9d63a626d25aebcb973e03c733274fc4b Mon Sep 17 00:00:00 2001 From: Dmytro-Fedoryshyn Date: Thu, 9 Jul 2026 13:05:36 +0300 Subject: [PATCH 6/7] Fix formatting --- vite.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vite.config.ts b/vite.config.ts index 62f89fa..1699384 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -47,7 +47,7 @@ function defineMainConfig(buildTimeDefines: Record): UserConfig "import-in-the-middle", "module-details-from-path", "require-in-the-middle", - // Externalize all Node.js built-in modules + // Externalize all Node.js built-in modules /^node:.*/, ], }, From 48d85c7162e9d18d83a62343c026dd2dcb01cb7d Mon Sep 17 00:00:00 2001 From: Dmytro-Fedoryshyn Date: Thu, 9 Jul 2026 13:48:06 +0300 Subject: [PATCH 7/7] Clean up lock file --- package-lock.json | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/package-lock.json b/package-lock.json index e436fe8..82bed12 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1384,6 +1384,9 @@ "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1400,6 +1403,9 @@ "cpu": [ "arm64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1416,6 +1422,9 @@ "cpu": [ "ppc64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1432,6 +1441,9 @@ "cpu": [ "s390x" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1448,6 +1460,9 @@ "cpu": [ "x64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1464,6 +1479,9 @@ "cpu": [ "x64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1917,6 +1935,9 @@ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1934,6 +1955,9 @@ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1951,6 +1975,9 @@ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1968,6 +1995,9 @@ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -3667,6 +3697,9 @@ "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -3687,6 +3720,9 @@ "cpu": [ "arm64" ], + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -3707,6 +3743,9 @@ "cpu": [ "x64" ], + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -3727,6 +3766,9 @@ "cpu": [ "x64" ], + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [