From b090edb56df233c8f6ceb816ee70b016a07540ea Mon Sep 17 00:00:00 2001 From: Dmytro-Fedoryshyn Date: Tue, 7 Jul 2026 19:15:38 +0300 Subject: [PATCH 01/15] Add Sentry integration --- package-lock.json | 433 +++++++++++++++++++++++++++++++++++++ package.json | 2 + src/main/index.ts | 3 + src/main/instrument.ts | 20 ++ src/renderer/index.tsx | 8 +- src/renderer/instrument.ts | 16 ++ vite.config.ts | 17 +- 7 files changed, 492 insertions(+), 7 deletions(-) create mode 100644 src/main/instrument.ts create mode 100644 src/renderer/instrument.ts diff --git a/package-lock.json b/package-lock.json index 9c9a1a8..6e9ba22 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,8 @@ "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-toggle": "^1.1.10", "@radix-ui/react-toggle-group": "^1.1.11", + "@sentry/node": "^10.64.0", + "@sentry/react": "^10.64.0", "async-mutex": "^0.5.0", "class-variance-authority": "^0.7.0", "lucide-react": "^0.503.0", @@ -65,6 +67,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/@babel/code-frame": { "version": "7.29.0", "dev": true, @@ -1414,6 +1459,119 @@ "node": ">= 8" } }, + "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.127.0", "devOptional": true, @@ -2308,6 +2466,203 @@ "dev": 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/@sindresorhus/merge-streams": { "version": "4.0.0", "dev": true, @@ -3023,6 +3378,15 @@ "node": ">=8" } }, + "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/async-mutex": { "version": "0.5.0", "license": "MIT", @@ -3215,6 +3579,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", "license": "Apache-2.0", @@ -3480,6 +3850,12 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "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/es6-error": { "version": "4.1.1", "license": "MIT" @@ -4082,6 +4458,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/import-meta-resolve": { "version": "4.2.0", "dev": true, @@ -4593,6 +4983,15 @@ "node": ">= 8" } }, + "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/micromatch": { "version": "4.0.8", "dev": true, @@ -4619,6 +5018,12 @@ "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", "dev": true, @@ -5073,6 +5478,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/resolve-from": { "version": "4.0.0", "dev": true, @@ -5168,6 +5586,12 @@ "version": "0.27.0", "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.7.4", "license": "ISC", @@ -5298,6 +5722,15 @@ "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, + "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", "devOptional": true, diff --git a/package.json b/package.json index 0279a17..2bb59c2 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,8 @@ "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-toggle": "^1.1.10", "@radix-ui/react-toggle-group": "^1.1.11", + "@sentry/node": "^10.64.0", + "@sentry/react": "^10.64.0", "async-mutex": "^0.5.0", "class-variance-authority": "^0.7.0", "lucide-react": "^0.503.0", diff --git a/src/main/index.ts b/src/main/index.ts index 163c4ed..7c70d60 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,5 +1,8 @@ +import './instrument'; +import * as Sentry from '@sentry/node'; import { Application } from './Application'; void new Application().initialize().catch((err: unknown) => { console.error('[Application] Failed to initialize:', err); + Sentry.captureException(err); }); diff --git a/src/main/instrument.ts b/src/main/instrument.ts new file mode 100644 index 0000000..ac0e96c --- /dev/null +++ b/src/main/instrument.ts @@ -0,0 +1,20 @@ +import * as Sentry from '@sentry/node'; +import packageJson from '../../package.json'; + +const SENTRY_DSN = 'https://98438641f3fb15aa82986c5e12065c84@o4511693799227392.ingest.de.sentry.io/4511693822099536'; + +Sentry.init({ + dsn: SENTRY_DSN, + environment: process.env.NODE_ENV ?? 'development', + release: `${packageJson.name}@${packageJson.version}`, + defaultIntegrations: false, + integrations: [ + Sentry.onUncaughtExceptionIntegration(), + Sentry.onUnhandledRejectionIntegration(), + ], +}); +Sentry.setTag('process', 'main'); +Sentry.captureMessage('app.launch', 'info'); + +Sentry.startSession(); +Sentry.captureSession(); diff --git a/src/renderer/index.tsx b/src/renderer/index.tsx index c75753d..672df40 100644 --- a/src/renderer/index.tsx +++ b/src/renderer/index.tsx @@ -1,5 +1,7 @@ +import './instrument'; import React from 'react'; import ReactDOM from 'react-dom/client'; +import * as Sentry from '@sentry/react'; import { AboutApp } from './about/AboutApp'; import { startTranscriptionRuntime } from './recording/transcription/TranscriptionRuntime'; import './index.css'; @@ -53,7 +55,11 @@ if (rendererWindowKind === 'background') { const SelectedApp = rendererWindowKind === null ? null : APP_BY_WINDOW[rendererWindowKind]; const appElement = SelectedApp === null ? <> : React.createElement(SelectedApp); - ReactDOM.createRoot(rootElement).render( + ReactDOM.createRoot(rootElement, { + onCaughtError: Sentry.reactErrorHandler(), + onRecoverableError: Sentry.reactErrorHandler(), + onUncaughtError: Sentry.reactErrorHandler(), + }).render( {appElement} , diff --git a/src/renderer/instrument.ts b/src/renderer/instrument.ts new file mode 100644 index 0000000..47d4a30 --- /dev/null +++ b/src/renderer/instrument.ts @@ -0,0 +1,16 @@ +import * as Sentry from '@sentry/react'; +import packageJson from '../../package.json'; + +const SENTRY_DSN = 'https://98438641f3fb15aa82986c5e12065c84@o4511693799227392.ingest.de.sentry.io/4511693822099536'; +const RENDERER_INTEGRATIONS = Sentry.getDefaultIntegrations({}).filter( + (integration) => integration.name !== 'BrowserSession', +); + +Sentry.init({ + dsn: SENTRY_DSN, + environment: import.meta.env.MODE, + release: `${packageJson.name}@${packageJson.version}`, + defaultIntegrations: false, + integrations: RENDERER_INTEGRATIONS, +}); +Sentry.setTag('process', 'renderer'); diff --git a/vite.config.ts b/vite.config.ts index 5ed209f..6a857d1 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,7 +1,17 @@ +import { builtinModules } from "node:module" import path from "path" import react from "@vitejs/plugin-react" import { defineConfig, type UserConfig } from "vite" +const mainProcessExternals = [ + "mobrowser", + "import-in-the-middle", + "module-details-from-path", + "require-in-the-middle", + ...builtinModules, + ...builtinModules.map((name) => `node:${name}`), +] + export default defineConfig(({ mode }) => { if (mode === "main") { return defineMainConfig() @@ -26,12 +36,7 @@ function defineMainConfig(): UserConfig { fileName: () => "index.js", }, rollupOptions: { - external: [ - "mobrowser", - "node:crypto", - "node:fs", - "node:path", - ], + external: mainProcessExternals, }, }, resolve: { From b2f0f48aaa32df2533efcec06a4ae44a2f82995f Mon Sep 17 00:00:00 2001 From: Dmytro-Fedoryshyn Date: Tue, 7 Jul 2026 19:52:28 +0300 Subject: [PATCH 02/15] Flush startup main failures --- src/main/index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/index.ts b/src/main/index.ts index 7c70d60..03c78c7 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -2,7 +2,10 @@ import './instrument'; import * as Sentry from '@sentry/node'; import { Application } from './Application'; -void new Application().initialize().catch((err: unknown) => { +const SENTRY_FLUSH_TIMEOUT_MS = 2000; + +void new Application().initialize().catch(async (err: unknown) => { console.error('[Application] Failed to initialize:', err); Sentry.captureException(err); + await Sentry.flush(SENTRY_FLUSH_TIMEOUT_MS); }); From 9c267f64577ed9a8acd26feb100da2adc7ab32a7 Mon Sep 17 00:00:00 2001 From: Dmytro-Fedoryshyn Date: Wed, 8 Jul 2026 11:18:02 +0300 Subject: [PATCH 03/15] Fix renderer Sentry environment tagging --- src/renderer/instrument.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/instrument.ts b/src/renderer/instrument.ts index 47d4a30..46a3d02 100644 --- a/src/renderer/instrument.ts +++ b/src/renderer/instrument.ts @@ -8,7 +8,7 @@ const RENDERER_INTEGRATIONS = Sentry.getDefaultIntegrations({}).filter( Sentry.init({ dsn: SENTRY_DSN, - environment: import.meta.env.MODE, + environment: import.meta.env.DEV ? 'development' : 'production', release: `${packageJson.name}@${packageJson.version}`, defaultIntegrations: false, integrations: RENDERER_INTEGRATIONS, From d77406a533325a4c7f451d13b04020c951d9a3f8 Mon Sep 17 00:00:00 2001 From: Dmytro-Fedoryshyn Date: Wed, 8 Jul 2026 12:21:32 +0300 Subject: [PATCH 04/15] Fix main Sentry environment tagging --- src/main/instrument.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/instrument.ts b/src/main/instrument.ts index ac0e96c..57fdbe2 100644 --- a/src/main/instrument.ts +++ b/src/main/instrument.ts @@ -1,3 +1,4 @@ +import { app } from '@mobrowser/api'; import * as Sentry from '@sentry/node'; import packageJson from '../../package.json'; @@ -5,7 +6,7 @@ const SENTRY_DSN = 'https://98438641f3fb15aa82986c5e12065c84@o4511693799227392.i Sentry.init({ dsn: SENTRY_DSN, - environment: process.env.NODE_ENV ?? 'development', + environment: app.packaged ? 'production' : 'development', release: `${packageJson.name}@${packageJson.version}`, defaultIntegrations: false, integrations: [ From 56c0ac3c4af1be0c237516b0bf29388c29d78b99 Mon Sep 17 00:00:00 2001 From: Dmytro-Fedoryshyn Date: Wed, 8 Jul 2026 12:50:44 +0300 Subject: [PATCH 05/15] Fix review issues --- src/main/instrument.ts | 4 ++-- src/renderer/instrument.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/instrument.ts b/src/main/instrument.ts index 57fdbe2..7f351b1 100644 --- a/src/main/instrument.ts +++ b/src/main/instrument.ts @@ -1,13 +1,13 @@ import { app } from '@mobrowser/api'; import * as Sentry from '@sentry/node'; -import packageJson from '../../package.json'; +import { name, version } from '../../package.json'; const SENTRY_DSN = 'https://98438641f3fb15aa82986c5e12065c84@o4511693799227392.ingest.de.sentry.io/4511693822099536'; Sentry.init({ dsn: SENTRY_DSN, environment: app.packaged ? 'production' : 'development', - release: `${packageJson.name}@${packageJson.version}`, + release: `${name}@${version}`, defaultIntegrations: false, integrations: [ Sentry.onUncaughtExceptionIntegration(), diff --git a/src/renderer/instrument.ts b/src/renderer/instrument.ts index 46a3d02..b4a3e4b 100644 --- a/src/renderer/instrument.ts +++ b/src/renderer/instrument.ts @@ -1,5 +1,5 @@ import * as Sentry from '@sentry/react'; -import packageJson from '../../package.json'; +import { name, version } from '../../package.json'; const SENTRY_DSN = 'https://98438641f3fb15aa82986c5e12065c84@o4511693799227392.ingest.de.sentry.io/4511693822099536'; const RENDERER_INTEGRATIONS = Sentry.getDefaultIntegrations({}).filter( @@ -9,7 +9,7 @@ const RENDERER_INTEGRATIONS = Sentry.getDefaultIntegrations({}).filter( Sentry.init({ dsn: SENTRY_DSN, environment: import.meta.env.DEV ? 'development' : 'production', - release: `${packageJson.name}@${packageJson.version}`, + release: `${name}@${version}`, defaultIntegrations: false, integrations: RENDERER_INTEGRATIONS, }); From 0e9423c8b3b84164a30dfb8b3e819277d07890e5 Mon Sep 17 00:00:00 2001 From: Dmytro-Fedoryshyn Date: Wed, 8 Jul 2026 13:17:51 +0300 Subject: [PATCH 06/15] Fix review issues --- .gitignore | 3 +++ src/main/index.ts | 2 +- src/main/instrument.ts | 21 --------------------- src/main/sentry.ts | 24 ++++++++++++++++++++++++ src/renderer/index.tsx | 2 +- src/renderer/instrument.ts | 16 ---------------- src/renderer/sentry.ts | 21 +++++++++++++++++++++ vite.config.ts | 15 +++++++++++++++ 8 files changed, 65 insertions(+), 39 deletions(-) delete mode 100644 src/main/instrument.ts create mode 100644 src/main/sentry.ts delete mode 100644 src/renderer/instrument.ts create mode 100644 src/renderer/sentry.ts diff --git a/.gitignore b/.gitignore index e65b59a..b306dd7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ # Node dependencies. node_modules/ +# Local environment. +.env + # Build output. build/ dist/ diff --git a/src/main/index.ts b/src/main/index.ts index 03c78c7..f019f8f 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,4 +1,4 @@ -import './instrument'; +import './sentry'; import * as Sentry from '@sentry/node'; import { Application } from './Application'; diff --git a/src/main/instrument.ts b/src/main/instrument.ts deleted file mode 100644 index 7f351b1..0000000 --- a/src/main/instrument.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { app } from '@mobrowser/api'; -import * as Sentry from '@sentry/node'; -import { name, version } from '../../package.json'; - -const SENTRY_DSN = 'https://98438641f3fb15aa82986c5e12065c84@o4511693799227392.ingest.de.sentry.io/4511693822099536'; - -Sentry.init({ - dsn: SENTRY_DSN, - environment: app.packaged ? 'production' : 'development', - release: `${name}@${version}`, - defaultIntegrations: false, - integrations: [ - Sentry.onUncaughtExceptionIntegration(), - Sentry.onUnhandledRejectionIntegration(), - ], -}); -Sentry.setTag('process', 'main'); -Sentry.captureMessage('app.launch', 'info'); - -Sentry.startSession(); -Sentry.captureSession(); diff --git a/src/main/sentry.ts b/src/main/sentry.ts new file mode 100644 index 0000000..7b24786 --- /dev/null +++ b/src/main/sentry.ts @@ -0,0 +1,24 @@ +import { app } from '@mobrowser/api'; +import * as Sentry from '@sentry/node'; + +declare const __SENTRY_DSN__: string; + +const SENTRY_DSN = __SENTRY_DSN__; + +if (SENTRY_DSN !== '' && app.packaged) { + Sentry.init({ + dsn: SENTRY_DSN, + environment: 'production', + release: `${app.name}@${app.version}`, + defaultIntegrations: false, + integrations: [ + Sentry.onUncaughtExceptionIntegration(), + Sentry.onUnhandledRejectionIntegration(), + ], + }); + Sentry.setTag('process', 'main'); + Sentry.captureMessage('app.launch', 'info'); + + Sentry.startSession(); + Sentry.captureSession(); +} diff --git a/src/renderer/index.tsx b/src/renderer/index.tsx index 672df40..7ce6a02 100644 --- a/src/renderer/index.tsx +++ b/src/renderer/index.tsx @@ -1,4 +1,4 @@ -import './instrument'; +import './sentry'; import React from 'react'; import ReactDOM from 'react-dom/client'; import * as Sentry from '@sentry/react'; diff --git a/src/renderer/instrument.ts b/src/renderer/instrument.ts deleted file mode 100644 index b4a3e4b..0000000 --- a/src/renderer/instrument.ts +++ /dev/null @@ -1,16 +0,0 @@ -import * as Sentry from '@sentry/react'; -import { name, version } from '../../package.json'; - -const SENTRY_DSN = 'https://98438641f3fb15aa82986c5e12065c84@o4511693799227392.ingest.de.sentry.io/4511693822099536'; -const RENDERER_INTEGRATIONS = Sentry.getDefaultIntegrations({}).filter( - (integration) => integration.name !== 'BrowserSession', -); - -Sentry.init({ - dsn: SENTRY_DSN, - environment: import.meta.env.DEV ? 'development' : 'production', - release: `${name}@${version}`, - defaultIntegrations: false, - integrations: RENDERER_INTEGRATIONS, -}); -Sentry.setTag('process', 'renderer'); diff --git a/src/renderer/sentry.ts b/src/renderer/sentry.ts new file mode 100644 index 0000000..53d7262 --- /dev/null +++ b/src/renderer/sentry.ts @@ -0,0 +1,21 @@ +import * as Sentry from '@sentry/react'; + +declare const __SENTRY_DSN__: string; +declare const __SENTRY_RELEASE__: string; + +const SENTRY_DSN = __SENTRY_DSN__; + +if (SENTRY_DSN !== '' && !import.meta.env.DEV) { + const rendererIntegrations = Sentry.getDefaultIntegrations({}).filter( + (integration) => integration.name !== 'BrowserSession', + ); + + Sentry.init({ + dsn: SENTRY_DSN, + environment: 'production', + release: __SENTRY_RELEASE__, + defaultIntegrations: false, + integrations: rendererIntegrations, + }); + Sentry.setTag('process', 'renderer'); +} diff --git a/vite.config.ts b/vite.config.ts index 6a857d1..c7de8e0 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,6 +2,7 @@ import { builtinModules } from "node:module" import path from "path" import react from "@vitejs/plugin-react" import { defineConfig, type UserConfig } from "vite" +import mobrowserConfig from "./mobrowser.conf.json" with { type: "json" } const mainProcessExternals = [ "mobrowser", @@ -12,6 +13,18 @@ const mainProcessExternals = [ ...builtinModules.map((name) => `node:${name}`), ] +const appVersion = mobrowserConfig.app.version +const appRelease = [ + appVersion.major, + appVersion.minor, + appVersion.patch, +].join(".") + +const buildTimeDefines = { + __SENTRY_DSN__: JSON.stringify(process.env.SENTRY_DSN ?? ""), + __SENTRY_RELEASE__: JSON.stringify(`${mobrowserConfig.app.name}@${appRelease}`), +} + export default defineConfig(({ mode }) => { if (mode === "main") { return defineMainConfig() @@ -25,6 +38,7 @@ export default defineConfig(({ mode }) => { function defineMainConfig(): UserConfig { return { root: path.resolve(__dirname, "./src/main"), + define: buildTimeDefines, build: { target: "esnext", outDir: path.resolve(__dirname, "./out/main"), @@ -58,6 +72,7 @@ function defineMainConfig(): UserConfig { function defineRendererConfig(): UserConfig { return { root: path.resolve(__dirname, "./src/renderer"), + define: buildTimeDefines, plugins: [react()], build: { outDir: path.resolve(__dirname, "./out/renderer"), From 0ea3935d36def98ce0e46bf22624899f78ca8895 Mon Sep 17 00:00:00 2001 From: Dmytro-Fedoryshyn Date: Wed, 8 Jul 2026 13:31:28 +0300 Subject: [PATCH 07/15] Configure Sentry for production telemetry --- .github/workflows/build.yml | 2 ++ README.md | 3 +++ 2 files changed, 5 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 751a4cb..3c657bd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,6 +25,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/README.md b/README.md index 540962c..b20fa17 100644 --- a/README.md +++ b/README.md @@ -55,5 +55,8 @@ npm install npm run build ``` +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. + If you need to sign and notarize the application, see the [.github/workflows/build.yml](.github/workflows/build.yml) for a ready-to-use script that works on macOS and Windows. From e1d408b34b402a90db9d4575dc4d2a34ced33961 Mon Sep 17 00:00:00 2001 From: Dmytro-Fedoryshyn Date: Wed, 8 Jul 2026 13:57:05 +0300 Subject: [PATCH 08/15] Add comments --- src/main/sentry.ts | 1 + src/renderer/sentry.ts | 1 + vite.config.ts | 1 + 3 files changed, 3 insertions(+) diff --git a/src/main/sentry.ts b/src/main/sentry.ts index 7b24786..ec0dc83 100644 --- a/src/main/sentry.ts +++ b/src/main/sentry.ts @@ -17,6 +17,7 @@ if (SENTRY_DSN !== '' && app.packaged) { ], }); Sentry.setTag('process', 'main'); + // Send one info event per production start to avoid telemetry corruption. Sentry.captureMessage('app.launch', 'info'); Sentry.startSession(); diff --git a/src/renderer/sentry.ts b/src/renderer/sentry.ts index 53d7262..7c10f87 100644 --- a/src/renderer/sentry.ts +++ b/src/renderer/sentry.ts @@ -6,6 +6,7 @@ declare const __SENTRY_RELEASE__: string; const SENTRY_DSN = __SENTRY_DSN__; if (SENTRY_DSN !== '' && !import.meta.env.DEV) { + // Keep renderer windows from creating extra launch sessions. const rendererIntegrations = Sentry.getDefaultIntegrations({}).filter( (integration) => integration.name !== 'BrowserSession', ); diff --git a/vite.config.ts b/vite.config.ts index c7de8e0..56a46a6 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -6,6 +6,7 @@ import mobrowserConfig from "./mobrowser.conf.json" with { type: "json" } const mainProcessExternals = [ "mobrowser", + // Keep Sentry's runtime helpers and Node built-ins out of the main bundle. "import-in-the-middle", "module-details-from-path", "require-in-the-middle", From 8cd4a0ac660073f84ef5db5e8db6bc31b8687cdb Mon Sep 17 00:00:00 2001 From: Dmytro-Fedoryshyn Date: Wed, 8 Jul 2026 14:25:52 +0300 Subject: [PATCH 09/15] Refine production Sentry configuration --- src/main/sentry.ts | 2 +- src/renderer/sentry.ts | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/main/sentry.ts b/src/main/sentry.ts index ec0dc83..6967b8c 100644 --- a/src/main/sentry.ts +++ b/src/main/sentry.ts @@ -17,7 +17,7 @@ if (SENTRY_DSN !== '' && app.packaged) { ], }); Sentry.setTag('process', 'main'); - // Send one info event per production start to avoid telemetry corruption. + // Send only one event per production launch to avoid stats duplication. Sentry.captureMessage('app.launch', 'info'); Sentry.startSession(); diff --git a/src/renderer/sentry.ts b/src/renderer/sentry.ts index 7c10f87..62a7a3c 100644 --- a/src/renderer/sentry.ts +++ b/src/renderer/sentry.ts @@ -6,17 +6,10 @@ declare const __SENTRY_RELEASE__: string; const SENTRY_DSN = __SENTRY_DSN__; if (SENTRY_DSN !== '' && !import.meta.env.DEV) { - // Keep renderer windows from creating extra launch sessions. - const rendererIntegrations = Sentry.getDefaultIntegrations({}).filter( - (integration) => integration.name !== 'BrowserSession', - ); - Sentry.init({ dsn: SENTRY_DSN, environment: 'production', release: __SENTRY_RELEASE__, - defaultIntegrations: false, - integrations: rendererIntegrations, }); Sentry.setTag('process', 'renderer'); } From 46391d3852bd5e5a0e98528753731d076bdf5a97 Mon Sep 17 00:00:00 2001 From: Dmytro-Fedoryshyn Date: Wed, 8 Jul 2026 14:53:15 +0300 Subject: [PATCH 10/15] Use app metadata IPC to get renderer Sentry release info --- src/main/Application.ts | 4 +++- src/main/system/ApplicationMetadataService.ts | 5 ++++- src/renderer/index.tsx | 4 +++- src/renderer/proto/application_metadata.proto | 7 ++++++- src/renderer/sentry.ts | 12 +++++++++--- vite.config.ts | 16 +++------------- 6 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/main/Application.ts b/src/main/Application.ts index a1741c1..bf183ee 100644 --- a/src/main/Application.ts +++ b/src/main/Application.ts @@ -60,6 +60,9 @@ export class Application { */ async initialize(): Promise { await this.historyStore.initialize(); + // Register this before renderer windows are created, + // because renderer startup reads app metadata. + registerApplicationMetadataIpc(); this.recordingWorkerWindow.initialize(); this.mainMenu.create(); let initialWelcomeAutoShowHandled = false; @@ -80,7 +83,6 @@ export class Application { this.recordingController.handleShortcutTrigger(); }); - registerApplicationMetadataIpc(); registerDesktopIpc(); registerPermissionsIpc(this.permissions, () => { void this.readiness.recompute(); diff --git a/src/main/system/ApplicationMetadataService.ts b/src/main/system/ApplicationMetadataService.ts index 60ade26..2d9fe43 100644 --- a/src/main/system/ApplicationMetadataService.ts +++ b/src/main/system/ApplicationMetadataService.ts @@ -16,6 +16,9 @@ class ApplicationMetadataService implements ApplicationMetadataServiceInterface * Returns metadata from the application shell configuration. */ GetApplicationMetadata() { - return Promise.resolve({ version: app.version }); + return Promise.resolve({ + name: app.name, + version: app.version, + }); } } diff --git a/src/renderer/index.tsx b/src/renderer/index.tsx index 7ce6a02..288e12c 100644 --- a/src/renderer/index.tsx +++ b/src/renderer/index.tsx @@ -1,7 +1,7 @@ -import './sentry'; import React from 'react'; import ReactDOM from 'react-dom/client'; import * as Sentry from '@sentry/react'; +import { initializeSentry } from './sentry'; import { AboutApp } from './about/AboutApp'; import { startTranscriptionRuntime } from './recording/transcription/TranscriptionRuntime'; import './index.css'; @@ -41,6 +41,8 @@ document.documentElement.dataset.rendererWindow = rendererWindowKind ?? 'unknown document.body.dataset.rendererWindow = rendererWindowKind ?? 'unknown'; document.title = rendererWindowKind === null ? 'MōVoice' : TITLE_BY_WINDOW[rendererWindowKind]; +await initializeSentry(); + initTheme(); const rootElement = document.getElementById('root'); diff --git a/src/renderer/proto/application_metadata.proto b/src/renderer/proto/application_metadata.proto index b1b728e..4b0858a 100644 --- a/src/renderer/proto/application_metadata.proto +++ b/src/renderer/proto/application_metadata.proto @@ -6,10 +6,15 @@ import "google/protobuf/empty.proto"; * Application metadata exposed by the main process. */ message ApplicationMetadata { + /** + * Current application name. + */ + string name = 1; + /** * Current application version. */ - string version = 1; + string version = 2; } /** diff --git a/src/renderer/sentry.ts b/src/renderer/sentry.ts index 62a7a3c..f51eab2 100644 --- a/src/renderer/sentry.ts +++ b/src/renderer/sentry.ts @@ -1,15 +1,21 @@ import * as Sentry from '@sentry/react'; +import { ipc } from './gen/ipc'; declare const __SENTRY_DSN__: string; -declare const __SENTRY_RELEASE__: string; const SENTRY_DSN = __SENTRY_DSN__; -if (SENTRY_DSN !== '' && !import.meta.env.DEV) { +export async function initializeSentry(): Promise { + if (SENTRY_DSN === '' || import.meta.env.DEV) { + return; + } + + const metadata = await ipc.applicationMetadata.GetApplicationMetadata({}); + Sentry.init({ dsn: SENTRY_DSN, environment: 'production', - release: __SENTRY_RELEASE__, + release: `${metadata.name}@${metadata.version}`, }); Sentry.setTag('process', 'renderer'); } diff --git a/vite.config.ts b/vite.config.ts index 56a46a6..2019641 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,29 +1,19 @@ -import { builtinModules } from "node:module" import path from "path" import react from "@vitejs/plugin-react" import { defineConfig, type UserConfig } from "vite" -import mobrowserConfig from "./mobrowser.conf.json" with { type: "json" } const mainProcessExternals = [ "mobrowser", - // Keep Sentry's runtime helpers and Node built-ins out of the main bundle. "import-in-the-middle", "module-details-from-path", "require-in-the-middle", - ...builtinModules, - ...builtinModules.map((name) => `node:${name}`), + "node:crypto", + "node:fs", + "node:path", ] -const appVersion = mobrowserConfig.app.version -const appRelease = [ - appVersion.major, - appVersion.minor, - appVersion.patch, -].join(".") - const buildTimeDefines = { __SENTRY_DSN__: JSON.stringify(process.env.SENTRY_DSN ?? ""), - __SENTRY_RELEASE__: JSON.stringify(`${mobrowserConfig.app.name}@${appRelease}`), } export default defineConfig(({ mode }) => { From 7e6cf2830a2418ee61c587aaa7912a7b55a43644 Mon Sep 17 00:00:00 2001 From: Dmytro-Fedoryshyn Date: Wed, 8 Jul 2026 14:59:28 +0300 Subject: [PATCH 11/15] Simplify var usage --- src/main/sentry.ts | 4 +--- src/renderer/sentry.ts | 4 +--- vite.config.ts | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/main/sentry.ts b/src/main/sentry.ts index 6967b8c..cfc7af4 100644 --- a/src/main/sentry.ts +++ b/src/main/sentry.ts @@ -1,9 +1,7 @@ import { app } from '@mobrowser/api'; import * as Sentry from '@sentry/node'; -declare const __SENTRY_DSN__: string; - -const SENTRY_DSN = __SENTRY_DSN__; +declare const SENTRY_DSN: string; if (SENTRY_DSN !== '' && app.packaged) { Sentry.init({ diff --git a/src/renderer/sentry.ts b/src/renderer/sentry.ts index f51eab2..a763ce3 100644 --- a/src/renderer/sentry.ts +++ b/src/renderer/sentry.ts @@ -1,9 +1,7 @@ import * as Sentry from '@sentry/react'; import { ipc } from './gen/ipc'; -declare const __SENTRY_DSN__: string; - -const SENTRY_DSN = __SENTRY_DSN__; +declare const SENTRY_DSN: string; export async function initializeSentry(): Promise { if (SENTRY_DSN === '' || import.meta.env.DEV) { diff --git a/vite.config.ts b/vite.config.ts index 2019641..8c8312a 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -13,7 +13,7 @@ const mainProcessExternals = [ ] const buildTimeDefines = { - __SENTRY_DSN__: JSON.stringify(process.env.SENTRY_DSN ?? ""), + SENTRY_DSN: JSON.stringify(process.env.SENTRY_DSN ?? ""), } export default defineConfig(({ mode }) => { From 7e7051405fc3a8e3d143dc86027f0ff482c333a1 Mon Sep 17 00:00:00 2001 From: Dmytro-Fedoryshyn Date: Wed, 8 Jul 2026 15:08:55 +0300 Subject: [PATCH 12/15] Inline main process externals --- vite.config.ts | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/vite.config.ts b/vite.config.ts index 8c8312a..0f1c74b 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,16 +2,6 @@ import path from "path" import react from "@vitejs/plugin-react" import { defineConfig, type UserConfig } from "vite" -const mainProcessExternals = [ - "mobrowser", - "import-in-the-middle", - "module-details-from-path", - "require-in-the-middle", - "node:crypto", - "node:fs", - "node:path", -] - const buildTimeDefines = { SENTRY_DSN: JSON.stringify(process.env.SENTRY_DSN ?? ""), } @@ -41,7 +31,15 @@ function defineMainConfig(): UserConfig { fileName: () => "index.js", }, rollupOptions: { - external: mainProcessExternals, + external: [ + "mobrowser", + "import-in-the-middle", + "module-details-from-path", + "require-in-the-middle", + "node:crypto", + "node:fs", + "node:path", + ], }, }, resolve: { From 86f8d304f356d816ac40e6150df2cb1aa0e515fa Mon Sep 17 00:00:00 2001 From: Dmytro-Fedoryshyn Date: Wed, 8 Jul 2026 16:33:45 +0300 Subject: [PATCH 13/15] Externalize Node builtins for Sentry --- vite.config.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/vite.config.ts b/vite.config.ts index 0f1c74b..06ae7b0 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,3 +1,4 @@ +import { builtinModules } from "node:module" import path from "path" import react from "@vitejs/plugin-react" import { defineConfig, type UserConfig } from "vite" @@ -6,6 +7,11 @@ const buildTimeDefines = { SENTRY_DSN: JSON.stringify(process.env.SENTRY_DSN ?? ""), } +// Sentry needs real Node.js built-ins in the main process, not bundled browser stubs. +function getNodeBuiltinExternals(): string[] { + return builtinModules.flatMap((name) => [name, `node:${name}`]) +} + export default defineConfig(({ mode }) => { if (mode === "main") { return defineMainConfig() @@ -36,9 +42,7 @@ function defineMainConfig(): UserConfig { "import-in-the-middle", "module-details-from-path", "require-in-the-middle", - "node:crypto", - "node:fs", - "node:path", + ...getNodeBuiltinExternals(), ], }, }, From 714a16afdf1d8c1f4befe1038751a0105c49de5a Mon Sep 17 00:00:00 2001 From: Dmytro-Fedoryshyn Date: Wed, 8 Jul 2026 18:18:36 +0300 Subject: [PATCH 14/15] Externalize Node imports for Sentry --- vite.config.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/vite.config.ts b/vite.config.ts index 06ae7b0..9cc22f6 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,4 +1,3 @@ -import { builtinModules } from "node:module" import path from "path" import react from "@vitejs/plugin-react" import { defineConfig, type UserConfig } from "vite" @@ -7,11 +6,6 @@ const buildTimeDefines = { SENTRY_DSN: JSON.stringify(process.env.SENTRY_DSN ?? ""), } -// Sentry needs real Node.js built-ins in the main process, not bundled browser stubs. -function getNodeBuiltinExternals(): string[] { - return builtinModules.flatMap((name) => [name, `node:${name}`]) -} - export default defineConfig(({ mode }) => { if (mode === "main") { return defineMainConfig() @@ -42,7 +36,7 @@ function defineMainConfig(): UserConfig { "import-in-the-middle", "module-details-from-path", "require-in-the-middle", - ...getNodeBuiltinExternals(), + /^node:.*/, ], }, }, From 32ff6c92971bf6cec0c5244c5e6e9889048c17e3 Mon Sep 17 00:00:00 2001 From: Dmytro-Fedoryshyn Date: Wed, 8 Jul 2026 19:04:57 +0300 Subject: [PATCH 15/15] Improve Sentry main crash tracking --- src/main/index.ts | 6 ++---- src/main/sentry.ts | 20 ++++++++++++++++---- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index f019f8f..ce4d0fc 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,11 +1,9 @@ -import './sentry'; +import { flushSentry } from './sentry'; import * as Sentry from '@sentry/node'; import { Application } from './Application'; -const SENTRY_FLUSH_TIMEOUT_MS = 2000; - void new Application().initialize().catch(async (err: unknown) => { console.error('[Application] Failed to initialize:', err); Sentry.captureException(err); - await Sentry.flush(SENTRY_FLUSH_TIMEOUT_MS); + await flushSentry(); }); diff --git a/src/main/sentry.ts b/src/main/sentry.ts index cfc7af4..a4b3075 100644 --- a/src/main/sentry.ts +++ b/src/main/sentry.ts @@ -3,16 +3,14 @@ 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, - integrations: [ - Sentry.onUncaughtExceptionIntegration(), - Sentry.onUnhandledRejectionIntegration(), - ], }); Sentry.setTag('process', 'main'); // Send only one event per production launch to avoid stats duplication. @@ -20,4 +18,18 @@ if (SENTRY_DSN !== '' && app.packaged) { 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); }