diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3c3b54fb..352708ae 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,6 +25,7 @@ jobs: with: node-version: ${{ matrix.node-version }} - run: npm ci + - run: node packages/react-native/scripts/generate-agent-version.cjs --check - run: npm run build - run: npm run lint - run: npm run format:check diff --git a/package-lock.json b/package-lock.json index 2791087e..993cb2c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -65,7 +65,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", @@ -80,7 +80,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -90,7 +90,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.29.7", @@ -119,7 +119,7 @@ }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.1", - "dev": true, + "devOptional": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -154,7 +154,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.29.7", @@ -184,7 +184,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.29.7", @@ -199,7 +199,7 @@ }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { "version": "6.3.1", - "dev": true, + "devOptional": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -282,7 +282,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -306,7 +306,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.29.7", @@ -320,7 +320,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.29.7", @@ -411,7 +411,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -421,7 +421,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -431,7 +431,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -456,7 +456,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/template": "^7.29.7", @@ -470,7 +470,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/types": "^7.29.7" @@ -2102,7 +2102,7 @@ }, "node_modules/@babel/runtime": { "version": "7.25.6", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" @@ -2115,7 +2115,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.29.7", @@ -2130,7 +2130,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.29.7", @@ -2149,7 +2149,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.29.7", @@ -3289,7 +3289,6 @@ }, "node_modules/@jest/schemas": { "version": "29.6.3", - "dev": true, "license": "MIT", "dependencies": { "@sinclair/typebox": "^0.27.8" @@ -3366,7 +3365,7 @@ }, "node_modules/@jest/types": { "version": "29.6.3", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jest/schemas": "^29.6.3", @@ -3394,7 +3393,7 @@ "version": "2.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -4307,7 +4306,6 @@ }, "node_modules/@sinclair/typebox": { "version": "0.27.8", - "dev": true, "license": "MIT" }, "node_modules/@sindresorhus/is": { @@ -4590,12 +4588,12 @@ }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@types/istanbul-lib-report": { "version": "3.0.3", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@types/istanbul-lib-coverage": "*" @@ -4603,7 +4601,7 @@ }, "node_modules/@types/istanbul-reports": { "version": "3.0.4", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@types/istanbul-lib-report": "*" @@ -4847,7 +4845,7 @@ }, "node_modules/@types/yargs": { "version": "17.0.33", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@types/yargs-parser": "*" @@ -4855,7 +4853,7 @@ }, "node_modules/@types/yargs-parser": { "version": "21.0.3", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@types/yauzl": { @@ -7116,7 +7114,6 @@ }, "node_modules/asap": { "version": "2.0.6", - "dev": true, "license": "MIT" }, "node_modules/asn1.js": { @@ -7693,7 +7690,7 @@ }, "node_modules/braces": { "version": "3.0.3", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -7849,7 +7846,7 @@ }, "node_modules/bser": { "version": "2.1.1", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "node-int64": "^0.4.0" @@ -8475,7 +8472,7 @@ }, "node_modules/ci-info": { "version": "3.9.0", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -8550,7 +8547,7 @@ }, "node_modules/cliui": { "version": "8.0.1", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -8563,12 +8560,12 @@ }, "node_modules/cliui/node_modules/emoji-regex": { "version": "8.0.0", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/cliui/node_modules/string-width": { "version": "4.2.3", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -8581,7 +8578,7 @@ }, "node_modules/cliui/node_modules/wrap-ansi": { "version": "7.0.0", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -8934,7 +8931,7 @@ "version": "3.7.0", "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "debug": "2.6.9", @@ -8950,7 +8947,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ms": "2.0.0" @@ -8960,7 +8957,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/consola": { @@ -9029,7 +9026,7 @@ }, "node_modules/convert-source-map": { "version": "2.0.0", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/cookie": { @@ -10901,7 +10898,7 @@ }, "node_modules/ee-first": { "version": "1.1.1", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/ejs": { @@ -10993,7 +10990,7 @@ }, "node_modules/encodeurl": { "version": "1.0.2", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -11094,7 +11091,7 @@ "version": "2.1.4", "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "stackframe": "^1.3.4" @@ -11661,7 +11658,7 @@ }, "node_modules/escape-html": { "version": "1.0.3", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/escape-string-regexp": { @@ -12685,7 +12682,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0" }, "node_modules/express": { @@ -12993,7 +12990,7 @@ }, "node_modules/fb-watchman": { "version": "2.0.2", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "bser": "2.1.1" @@ -13104,7 +13101,7 @@ }, "node_modules/fill-range": { "version": "7.1.1", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -13127,7 +13124,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "debug": "2.6.9", @@ -13146,7 +13143,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ms": "2.0.0" @@ -13156,7 +13153,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/find-cache-dir": { @@ -13355,7 +13352,7 @@ "version": "0.0.6", "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz", "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/flush-write-stream": { @@ -13611,7 +13608,7 @@ }, "node_modules/gensync": { "version": "1.0.0-beta.2", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -13619,7 +13616,7 @@ }, "node_modules/get-caller-file": { "version": "2.0.5", - "dev": true, + "devOptional": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -14317,7 +14314,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz", "integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "queue": "6.0.2" @@ -14411,7 +14408,7 @@ }, "node_modules/inherits": { "version": "2.0.4", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/internal-slot": { @@ -14439,7 +14436,7 @@ "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "loose-envify": "^1.0.0" @@ -14781,7 +14778,7 @@ }, "node_modules/is-number": { "version": "7.0.0", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -15450,7 +15447,7 @@ }, "node_modules/jest-get-type": { "version": "29.6.3", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -15707,7 +15704,7 @@ }, "node_modules/jest-util": { "version": "29.7.0", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", @@ -15723,7 +15720,7 @@ }, "node_modules/jest-validate": { "version": "29.7.0", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", @@ -15739,7 +15736,7 @@ }, "node_modules/jest-validate/node_modules/camelcase": { "version": "6.3.0", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=10" @@ -15768,7 +15765,7 @@ }, "node_modules/jest-worker": { "version": "29.7.0", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -15782,7 +15779,7 @@ }, "node_modules/jest-worker/node_modules/has-flag": { "version": "4.0.0", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -15790,7 +15787,7 @@ }, "node_modules/jest-worker/node_modules/supports-color": { "version": "8.1.1", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -15837,7 +15834,7 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz", "integrity": "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==", - "dev": true, + "devOptional": true, "license": "0BSD" }, "node_modules/jsdom": { @@ -15888,7 +15885,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, + "devOptional": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -15925,7 +15922,7 @@ }, "node_modules/json5": { "version": "2.2.3", - "dev": true, + "devOptional": true, "license": "MIT", "bin": { "json5": "lib/cli.js" @@ -16034,7 +16031,7 @@ }, "node_modules/leven": { "version": "3.1.0", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6" @@ -16208,7 +16205,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/lodash.union": { @@ -16286,7 +16283,7 @@ }, "node_modules/lru-cache": { "version": "5.1.1", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "yallist": "^3.0.2" @@ -16331,7 +16328,7 @@ }, "node_modules/makeerror": { "version": "1.0.12", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause", "dependencies": { "tmpl": "1.0.5" @@ -16455,7 +16452,7 @@ }, "node_modules/micromatch": { "version": "4.0.8", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -17059,7 +17056,7 @@ }, "node_modules/node-int64": { "version": "0.4.0", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/node-libs-browser": { @@ -17189,7 +17186,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/nwsapi": { @@ -17388,7 +17385,7 @@ }, "node_modules/on-finished": { "version": "2.3.0", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ee-first": "1.1.1" @@ -17747,7 +17744,7 @@ }, "node_modules/parseurl": { "version": "1.3.3", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -17909,7 +17906,7 @@ }, "node_modules/picomatch": { "version": "2.3.1", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -18112,7 +18109,6 @@ }, "node_modules/pretty-format": { "version": "29.7.0", - "dev": true, "license": "MIT", "dependencies": { "@jest/schemas": "^29.6.3", @@ -18125,7 +18121,6 @@ }, "node_modules/pretty-format/node_modules/ansi-styles": { "version": "5.2.0", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -18172,7 +18167,6 @@ }, "node_modules/promise": { "version": "8.3.0", - "dev": true, "license": "MIT", "dependencies": { "asap": "~2.0.6" @@ -18434,7 +18428,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "inherits": "~2.0.3" @@ -18580,7 +18574,6 @@ }, "node_modules/react-is": { "version": "18.3.1", - "dev": true, "license": "MIT" }, "node_modules/react-is-18": { @@ -19292,7 +19285,7 @@ }, "node_modules/regenerator-runtime": { "version": "0.14.1", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/regex-not": { @@ -19386,7 +19379,7 @@ }, "node_modules/require-directory": { "version": "2.1.1", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -20636,7 +20629,7 @@ "version": "1.3.4", "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/stacktrace-parser": { @@ -20697,7 +20690,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -21297,7 +21290,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/through": { @@ -21337,7 +21330,7 @@ }, "node_modules/tmpl": { "version": "1.0.5", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause" }, "node_modules/to-arraybuffer": { @@ -21388,7 +21381,7 @@ }, "node_modules/to-regex-range": { "version": "5.0.1", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -22070,7 +22063,7 @@ }, "node_modules/unpipe": { "version": "1.0.0", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -22280,7 +22273,7 @@ }, "node_modules/utils-merge": { "version": "1.0.1", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.4.0" @@ -22381,7 +22374,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/vm-browserify": { @@ -22419,7 +22412,7 @@ }, "node_modules/walker": { "version": "1.0.8", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "makeerror": "1.0.12" @@ -23852,7 +23845,7 @@ }, "node_modules/y18n": { "version": "5.0.8", - "dev": true, + "devOptional": true, "license": "ISC", "engines": { "node": ">=10" @@ -23860,14 +23853,14 @@ }, "node_modules/yallist": { "version": "3.1.1", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/yaml": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", - "dev": true, + "devOptional": true, "license": "ISC", "bin": { "yaml": "bin.mjs" @@ -23883,7 +23876,7 @@ "version": "17.7.3", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -23900,7 +23893,7 @@ }, "node_modules/yargs-parser": { "version": "21.1.1", - "dev": true, + "devOptional": true, "license": "ISC", "engines": { "node": ">=12" @@ -23956,12 +23949,12 @@ }, "node_modules/yargs/node_modules/emoji-regex": { "version": "8.0.0", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/yargs/node_modules/string-width": { "version": "4.2.3", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -24094,7 +24087,7 @@ "version": "0.6.2", "license": "MIT", "dependencies": { - "@backtrace/sdk-core": "0.8.3" + "@backtrace/sdk-core": "0.8.3 || 0.8.4-rc" }, "devDependencies": { "@reduxjs/toolkit": "^1.9.5", @@ -24302,7 +24295,7 @@ "version": "0.8.3", "license": "MIT", "dependencies": { - "@backtrace/sdk-core": "^0.8.3", + "@backtrace/sdk-core": "^0.8.3 || 0.8.4-rc", "form-data": "^4.0.0" }, "devDependencies": { @@ -24387,10 +24380,12 @@ }, "packages/react-native": { "name": "@backtrace/react-native", - "version": "0.2.2", + "version": "0.2.3-rc", "license": "MIT", "dependencies": { - "@backtrace/sdk-core": "^0.8.3", + "@backtrace/sdk-core": "0.8.4-rc", + "pretty-format": "^29.7.0", + "promise": "^8.3.0", "web-streams-polyfill": "^4.3.0" }, "devDependencies": { @@ -24412,8 +24407,18 @@ "node": ">= 16.0.0" }, "peerDependencies": { + "@backtrace/sourcemap-tools": "^0.2.5", + "metro": ">=0.76.0", "react": ">=18.0.0", "react-native": ">=0.72.0" + }, + "peerDependenciesMeta": { + "@backtrace/sourcemap-tools": { + "optional": true + }, + "metro": { + "optional": true + } } }, "packages/react-native/node_modules/@react-native/assets-registry": { @@ -24531,7 +24536,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "mime-types": "^3.0.0", @@ -24545,7 +24550,7 @@ "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 14" @@ -24565,7 +24570,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "packages/react-native/node_modules/commander": { @@ -24635,7 +24640,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "agent-base": "^7.1.2", @@ -24649,7 +24654,7 @@ "version": "0.83.7", "resolved": "https://registry.npmjs.org/metro/-/metro-0.83.7.tgz", "integrity": "sha512-SPaPEyvTsTmd0LpT7RaZciQyDw2i/JB7+iY9L5VfBo72+psescFxBqpI1TL9dnL+pmnfkU+l/J1mEEGLeF65EQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.29.0", @@ -24703,7 +24708,7 @@ "version": "0.83.7", "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.83.7.tgz", "integrity": "sha512-sBqBkt6kNut/88bv+Ucvm4yqdPetbvAEsHzi3MAgJEifOSYYzX5Z5Kgw3TFOrwf/mHJTOBG2ONlaMHoyfP15TA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", @@ -24720,14 +24725,14 @@ "version": "0.35.0", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.35.0.tgz", "integrity": "sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "packages/react-native/node_modules/metro-babel-transformer/node_modules/hermes-parser": { "version": "0.35.0", "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.35.0.tgz", "integrity": "sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "hermes-estree": "0.35.0" @@ -24737,7 +24742,7 @@ "version": "0.83.7", "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.83.7.tgz", "integrity": "sha512-E9SRePXQ1Zvlj79VcOk57q7VC7rMHMFQ+jhmPHBiq+dJ0bJB5BL87lWZF6oh5X76Cci5tpDuQNaDwwuSCToEeg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "exponential-backoff": "^3.1.1", @@ -24753,7 +24758,7 @@ "version": "0.83.7", "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.83.7.tgz", "integrity": "sha512-W1c2Nmx8MiJTJt+eWhMO08z9VKi3kZOaz99IYGdqeqDgY9j+yZjXl62rUav4Di0heZfh4/n2s722PqRL1OODeg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6" @@ -24766,7 +24771,7 @@ "version": "0.83.7", "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.83.7.tgz", "integrity": "sha512-83mjWFbFOt2GeJ6pFIum5mSnc1uTsZJAtD8o4ej0s4NVsYsA7fB+pHvTfHhFrpeMONaobu2riKavkPei05Er/Q==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "connect": "^3.6.5", @@ -24786,7 +24791,7 @@ "version": "0.83.7", "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.83.7.tgz", "integrity": "sha512-6yn3w1wnltT6RQl7p7YES2l95ArC+mWrOssEiH8p5/DDrJS65/szf9LsC9JrBv8c5DdvSY3V3f0GRYg0Ox7hCg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6", @@ -24801,7 +24806,7 @@ "version": "0.83.7", "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.83.7.tgz", "integrity": "sha512-+j0F1m+FQYVAQ6syf+mwhIPV5GoFQrkInX8bppuc50IzNsZbMrp8R5H/Sx/K2daQ3YEa9F/XwkeZT8gzJfgeCw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -24822,7 +24827,7 @@ "version": "0.83.7", "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.83.7.tgz", "integrity": "sha512-MfJar2IS4tBRuLb9svwb0Gu5l9BsH+pcRm8eGcEi/wy8MzZinfinh5dFLt2nWkocnulIgtGB5NkFDdbXqMXKhQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6", @@ -24836,7 +24841,7 @@ "version": "0.83.7", "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.83.7.tgz", "integrity": "sha512-WSJIENlMcoSsuz66IfBHOkgfp3KJt2UW2TnEHPf1b8pIG2eEXNOVmo2+03A0H17WY2XGXWgxL0CG7FAopqgB1A==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6" @@ -24849,7 +24854,7 @@ "version": "0.83.7", "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.83.7.tgz", "integrity": "sha512-9GKkJURaB2iyYoEExKnedzAHzxmKtSi+k0tsZUvMoU27tBZJElchYt7JH/Ai/XzYAI9lCAaV7u5HZSI8J5Z+wQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.0", @@ -24863,7 +24868,7 @@ "version": "0.83.7", "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.83.7.tgz", "integrity": "sha512-JgA1h7oc1a1jydBe1GhVFsUoMYo3wLPk7oRA32rjlDsq+sP2JLt9x2p2lWbNSxTm/u8NV4VRid3hvEJgcX8tKw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.29.0", @@ -24884,7 +24889,7 @@ "version": "0.83.7", "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.83.7.tgz", "integrity": "sha512-g4suyxw20WOHWI680c+Kq4wC/NF+Hx5pRH9afrMp+sMTxqLeKcPR1Xf4wMhsjlbvx7LbIREdke6q928jEjvJWw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6", @@ -24905,7 +24910,7 @@ "version": "0.83.7", "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.83.7.tgz", "integrity": "sha512-Ss0FpBiZDjX2kwhukMDl5sNdYK8T/06IPqxNE4H6PTlRlfs9q11cef13c/xESY/Pm4VCkp1yJUZO3kXzvMxQFA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", @@ -24923,7 +24928,7 @@ "version": "0.83.7", "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.83.7.tgz", "integrity": "sha512-UegCo7ygB2fT64mRK2nbAjQVJ1zSwIIHy8d96jJv2nKZFDaViYBiughEdu5HM/Ceq0WN3LZrZk3zhl9aoiLYFw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", @@ -24948,14 +24953,14 @@ "version": "0.35.0", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.35.0.tgz", "integrity": "sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "packages/react-native/node_modules/metro/node_modules/hermes-parser": { "version": "0.35.0", "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.35.0.tgz", "integrity": "sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "hermes-estree": "0.35.0" @@ -24965,7 +24970,7 @@ "version": "7.5.11", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz", "integrity": "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8.3.0" @@ -25000,7 +25005,7 @@ "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -25010,7 +25015,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "mime-db": "^1.54.0" @@ -25027,7 +25032,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -25037,7 +25042,7 @@ "version": "0.83.7", "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.83.7.tgz", "integrity": "sha512-9M5kpuOLyTPogMtZiQUIxdAZxl7Dxs6tVBbJErSumsqGMuhVSoUbkfeZ3XNPpLpwBBtqY5QDUzGwggLHX3slQg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6" @@ -25211,7 +25216,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", "integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -25237,7 +25242,7 @@ "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -25352,7 +25357,7 @@ }, "packages/sdk-core": { "name": "@backtrace/sdk-core", - "version": "0.8.3", + "version": "0.8.4-rc", "license": "MIT", "devDependencies": { "@rollup/plugin-typescript": "^11.1.6", @@ -25403,7 +25408,7 @@ "version": "0.2.1", "license": "MIT", "dependencies": { - "@backtrace/sdk-core": "0.8.3", + "@backtrace/sdk-core": "0.8.3 || 0.8.4-rc", "rrweb": "^2.0.0-alpha.15" }, "devDependencies": { diff --git a/packages/browser/package.json b/packages/browser/package.json index fa6aaf5b..a0e1b566 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -12,7 +12,7 @@ "clean": "rimraf \"lib\"", "format": "prettier --write '**/*.ts'", "lint": "eslint . --ext .ts", - "prepublishOnly": "npm -w ../sdk-core run prepublishOnly && npm run clean && cross-env NODE_ENV=production npm run build", + "prepublishOnly": "npm -w ../sdk-core run prepack && npm run clean && cross-env NODE_ENV=production npm run build", "watch": "concurrently \"tsc -p tsconfig.build.json --noEmit --watch\" \"rollup -c rollup.config.mjs --watch\"", "test": "cross-env NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" NODE_NO_WARNINGS=1 NODE_ENV=test jest" }, @@ -39,7 +39,7 @@ "/lib" ], "dependencies": { - "@backtrace/sdk-core": "0.8.3" + "@backtrace/sdk-core": "0.8.3 || 0.8.4-rc" }, "devDependencies": { "@reduxjs/toolkit": "^1.9.5", @@ -58,4 +58,4 @@ "typescript": "^5.0.4", "ua-parser-js": "^1.0.35" } -} \ No newline at end of file +} diff --git a/packages/node/package.json b/packages/node/package.json index 268e2778..c924cb8b 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -56,7 +56,7 @@ "typescript": "^5.0.4" }, "dependencies": { - "@backtrace/sdk-core": "^0.8.3", + "@backtrace/sdk-core": "^0.8.3 || 0.8.4-rc", "form-data": "^4.0.0" } -} \ No newline at end of file +} diff --git a/packages/react-native/README.md b/packages/react-native/README.md index 83fac23d..44871f83 100644 --- a/packages/react-native/README.md +++ b/packages/react-native/README.md @@ -82,9 +82,20 @@ client.send(new Error('Something broke!')); Client-side error reports are based on minified code. Upload source maps and source code to resolve minified code to your original source identifiers. -[(Source Map feature documentation)](https://docs.saucelabs.com/error-reporting/platform-integrations/source-map/) +Install the build-time tools in the application when using Backtrace's source-map scripts: + +```sh +npm install --save-dev @backtrace/sourcemap-tools@^0.2.5 @backtrace/javascript-cli +``` - +The serializer also needs Metro (`>=0.76.0`). Use the Metro version compatible with the application's React Native version; +React Native applications normally already provide it. Metro and `@backtrace/sourcemap-tools` are optional peers, installing the SDK alone does not install these tools. +The CLI package supplies the `backtrace-js` executable used by the iOS upload script; the Android upload task also uses `@backtrace/javascript-cli`. + +Configure `@backtrace/react-native/scripts/processSourceMap` as the Metro custom serializer before enabling the upload scripts. +See the [example application's source-map setup](https://github.com/backtrace-labs/backtrace-javascript/tree/main/examples/sdk/reactNative#source-maps) for Metro, Android and iOS configuration (these tools are optional for basic runtime error reporting). + +[(Source Map feature documentation)](https://docs.saucelabs.com/error-reporting/platform-integrations/source-map/) ### Deobfuscate ProGuard and R8 builds (Android) diff --git a/packages/react-native/package.json b/packages/react-native/package.json index 6645c506..66671158 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -1,6 +1,6 @@ { "name": "@backtrace/react-native", - "version": "0.2.2", + "version": "0.2.3-rc", "description": "Backtrace-Javascript React-Native integration", "main": "lib/commonjs/index", "module": "lib/module/index", @@ -8,6 +8,7 @@ "react-native": "src/index", "source": "src/index", "files": [ + "RELEASE_CANDIDATE.md", "src", "lib", "android", @@ -32,8 +33,11 @@ "watch": "tsc --noEmit --watch", "clean": "rimraf \"lib\"", "format:check": "eslint \"**/*.{js,ts,tsx}\"", - "prepublishOnly": "npm run clean && cross-env NODE_ENV=production bob build", - "test": "cross-env NODE_ENV=test jest" + "test": "cross-env NODE_ENV=test jest", + "generate:version": "node scripts/generate-agent-version.cjs", + "prebuild": "npm run generate:version", + "pretest": "npm run generate:version", + "prepack": "npm run clean && cross-env NODE_ENV=production npm run build" }, "keywords": [ "Error", @@ -76,6 +80,8 @@ ] }, "peerDependencies": { + "@backtrace/sourcemap-tools": "^0.2.5", + "metro": ">=0.76.0", "react": ">=18.0.0", "react-native": ">=0.72.0" }, @@ -95,7 +101,21 @@ "typescript": "^5.0.2" }, "dependencies": { - "@backtrace/sdk-core": "^0.8.3", + "@backtrace/sdk-core": "0.8.4-rc", + "pretty-format": "^29.7.0", + "promise": "^8.3.0", "web-streams-polyfill": "^4.3.0" + }, + "publishConfig": { + "access": "public", + "tag": "rc" + }, + "peerDependenciesMeta": { + "@backtrace/sourcemap-tools": { + "optional": true + }, + "metro": { + "optional": true + } } } diff --git a/packages/react-native/scripts/generate-agent-version.cjs b/packages/react-native/scripts/generate-agent-version.cjs new file mode 100644 index 00000000..41439e7d --- /dev/null +++ b/packages/react-native/scripts/generate-agent-version.cjs @@ -0,0 +1,29 @@ +'use strict'; + +const fs = require('node:fs'); +const path = require('node:path'); + +const args = process.argv.slice(2); +if (args.length > 1 || (args.length === 1 && args[0] !== '--check')) { + throw new Error('Usage: node scripts/generate-agent-version.cjs [--check]'); +} + +const root = path.resolve(__dirname, '..'); +const { version } = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')); +// Validate before interpolating package metadata into TypeScript source. +if (typeof version !== 'string' || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(version)) { + throw new Error('package.json must contain a valid package version.'); +} + +const target = path.join(root, 'src/common/agentVersion.ts'); +const source = `// Generated from package.json; do not edit.\nexport const agentVersion = '${version}';\n`; +const current = fs.existsSync(target) ? fs.readFileSync(target, 'utf8') : undefined; + +if (current !== source) { + if (args[0] === '--check') { + throw new Error('Stale agentVersion.ts. Run npm run generate:version.'); + } + + fs.mkdirSync(path.dirname(target), { recursive: true }); + fs.writeFileSync(target, source, 'utf8'); +} diff --git a/packages/react-native/src/BacktraceClient.ts b/packages/react-native/src/BacktraceClient.ts index 03fd6aaa..b21050ee 100644 --- a/packages/react-native/src/BacktraceClient.ts +++ b/packages/react-native/src/BacktraceClient.ts @@ -24,9 +24,9 @@ import { DimensionChangeBreadcrumbSubscriber } from './breadcrumbs/events/Dimens import { WebRequestEventSubscriber } from './breadcrumbs/events/WebRequestEventSubscriber'; import { FileBreadcrumbsStorage } from './breadcrumbs/FileBreadcrumbsStorage'; import type { BacktraceClientSetup } from './builder/BacktraceClientSetup'; +import { agentVersion } from './common/agentVersion'; import { DebuggerHelper } from './common/DebuggerHelper'; import { version } from './common/platformHelper'; -import { version as agentVersion } from '../package.json'; import { CrashReporter } from './crashReporter/CrashReporter'; import { generateUnhandledExceptionHandler } from './handlers'; import { AndroidUnhandledException } from './handlers/android/AndroidUnhandledException'; @@ -44,6 +44,7 @@ const REQUIRED_APPLICATION_ATTRIBUTES = ['application', 'application.version']; const INERT_SUBMISSION_URL = 'https://submit.backtrace.io/unavailable/unavailable/json'; export class BacktraceClient extends BacktraceCoreClient { + private _disposed = false; private _crashReporter?: CrashReporter; private _anrWatchdogHandler?: AnrWatchdogHandler; private readonly _exceptionHandler: ExceptionHandler = generateUnhandledExceptionHandler(); @@ -103,6 +104,7 @@ export class BacktraceClient extends BacktraceCoreClient } public initialize(): void { + this._disposed = false; this.ensureApplicationAttributes(); const lockId = this.guard( @@ -142,10 +144,21 @@ export class BacktraceClient extends BacktraceCoreClient } public dispose(): void { + if (this._disposed) { + return; + } + this._disposed = true; + this.guard(() => this._exceptionHandler.dispose(), 'failed to restore the error handlers'); this.guard(() => this._anrWatchdogHandler?.dispose(), 'failed to stop the ANR watchdog'); this.guard(() => this._crashReporter?.dispose(), 'failed to release the native crash reporter'); - super.dispose(); + try { + super.dispose(); + } catch (err) { + // A custom module may fail during cleanup. Allow the caller to retry. + this._disposed = false; + throw err; + } // super.dispose() only clears BacktraceCoreClient._instance. if (BacktraceClient._instance === this) { BacktraceClient._instance = undefined; diff --git a/packages/react-native/src/common/agentVersion.ts b/packages/react-native/src/common/agentVersion.ts new file mode 100644 index 00000000..7f5632a2 --- /dev/null +++ b/packages/react-native/src/common/agentVersion.ts @@ -0,0 +1,2 @@ +// Generated from package.json; do not edit. +export const agentVersion = '0.2.3-rc'; diff --git a/packages/react-native/src/handlers/UnhandledExceptionHandler.ts b/packages/react-native/src/handlers/UnhandledExceptionHandler.ts index 539d636b..add2ae1b 100644 --- a/packages/react-native/src/handlers/UnhandledExceptionHandler.ts +++ b/packages/react-native/src/handlers/UnhandledExceptionHandler.ts @@ -1,131 +1,230 @@ import { BacktraceReport, warnFailure } from '@backtrace/sdk-core'; +import { format as prettyFormat } from 'pretty-format'; import type { BacktraceClient } from '../BacktraceClient'; import { hermes } from '../common/hermesHelper'; import { CrashReporter } from '../crashReporter/CrashReporter'; import { type ExceptionHandler } from './ExceptionHandler'; // eslint-disable-next-line @typescript-eslint/no-var-requires -const rejectionTracking = require('promise/setimmediate/rejection-tracking'); +const rejectionTracking = require('promise/setimmediate/rejection-tracking') as { + enable(options: { + allRejections: boolean; + onUnhandled: (id: number, rejection?: unknown) => void; + onHandled: (id: number) => void; + }): void; +}; -function getPrettyFormat() { - try { - return require('pretty-format'); - } catch { - return undefined; - } -} - -const cachedPrettyFormat = getPrettyFormat(); +type GlobalErrorHandler = ReturnType; export class UnhandledExceptionHandler implements ExceptionHandler { protected enabled = true; - public captureManagedErrors(client: BacktraceClient) { - const globalErrorHandler = ErrorUtils.getGlobalHandler(); - ErrorUtils.setGlobalHandler((error: Error, fatal?: boolean) => { - // The app's own handler always runs, also after dispose. - try { - if (this.enabled) { - client.send(error, { - 'error.type': 'Unhandled exception', - fatal, - }); + + private _managedClient?: BacktraceClient; + private _rejectionClient?: BacktraceClient; + private _installedHandler?: GlobalErrorHandler; + private _previousHandler?: GlobalErrorHandler; + private _reportingManagedError = false; + private _reportingRejection = false; + private _warning = false; + + public captureManagedErrors(client: BacktraceClient): void { + if (!this.enabled || this._installedHandler) { + return; + } + + const previousHandler = ErrorUtils.getGlobalHandler(); + this._managedClient = client; + + const installedHandler: GlobalErrorHandler = (error: Error, fatal?: boolean) => { + const managedClient = this._managedClient; + + if (this.enabled && managedClient && !this._reportingManagedError) { + this._reportingManagedError = true; + + try { + // send() can throw before returning a promise. Observe rejected + // submissions without delaying the application's fatal handler. + void Promise.resolve( + managedClient.send(error, { + 'error.type': 'Unhandled exception', + fatal, + }), + ).catch(() => undefined); + // iOS: RCTFatal turns a fatal into a native crash the reporter would double-report. + // Preserve native reporting when synchronous JS report construction fails. if (fatal) { CrashReporter.markFatalError(); } + } catch (err) { + this.warnReportingFailure('failed to report an unhandled error', err); + } finally { + this._reportingManagedError = false; } - } catch (err) { - warnFailure('failed to report an unhandled error', err); - } finally { - globalErrorHandler(error, fatal); } - }); + + // Disposed and re-entered wrappers still forward. + // Exceptions from the application's original handler must keep their original behavior. + previousHandler(error, fatal); + }; + + try { + ErrorUtils.setGlobalHandler(installedHandler); + this._previousHandler = previousHandler; + this._installedHandler = installedHandler; + } catch (err) { + this._managedClient = undefined; + throw err; + } } - public captureUnhandledPromiseRejections(client: BacktraceClient) { - const hermesInternal = hermes(); + public captureUnhandledPromiseRejections(client: BacktraceClient): void { + if (!this.enabled || this._rejectionClient) { + return; + } - if (hermesInternal?.hasPromise?.() && hermesInternal?.enablePromiseRejectionTracker) { - hermesInternal.enablePromiseRejectionTracker({ - allRejections: true, - onUnhandled: (id: number, rejection: Error | string = 'Unknown') => { - if (!this.enabled) { - return; - } - this.reportRejection(client, id, rejection); - }, - }); - } else { - // This is the same unhandled exception handler that exists in the react-native source code. - // The only difference is to make sure we do not execute it in the dev mode (react-native checks it earlier) - // and backtrace error reporting function - // refs: https://github.com/facebook/react-native/blob/a59b947a1e077d1c3f0d36926d374db5fe7d3291/packages/react-native/Libraries/promiseRejectionTrackingOptions.js#L38 + this._rejectionClient = client; + + try { + const hermesInternal = hermes(); + + if (hermesInternal?.hasPromise?.() && hermesInternal.enablePromiseRejectionTracker) { + hermesInternal.enablePromiseRejectionTracker({ + allRejections: true, + onUnhandled: (id, rejection) => this.reportRejection(id, rejection), + }); + return; + } + + // Preserve React Native's development warnings when replacing its + // fallback rejection tracker. rejectionTracking.enable({ allRejections: true, - onUnhandled: (id: number, rejection: Error) => { - if (this.enabled) { - this.reportRejection(client, id, rejection); - } - if (!__DEV__) { - return; - } - let message: string; - let stack: string | undefined; - - const stringValue = Object.prototype.toString.call(rejection); - if (stringValue === '[object Error]') { - message = Error.prototype.toString.call(rejection); - stack = rejection.stack as string; - } else { - message = cachedPrettyFormat - ? cachedPrettyFormat(rejection) - : typeof rejection === 'string' - ? rejection - : JSON.stringify({ ...rejection }); - } - - const warning = - `Possible Unhandled Promise Rejection (id: ${id}):\n` + - `${message ?? ''}\n` + - (stack == null ? '' : stack); - console.warn(warning); + onUnhandled: (id, rejection) => { + this.reportRejection(id, rejection); + this.warnUnhandledRejection(id, rejection); }, - onHandled: (id: number) => { - if (!__DEV__) { - return; + onHandled: (id) => { + if (__DEV__) { + this.warn( + `Promise Rejection Handled (id: ${id})\n` + + 'This means you can ignore any previous messages of the form ' + + `"Possible Unhandled Promise Rejection (id: ${id}):"`, + ); } - const warning = - `Promise Rejection Handled (id: ${id})\n` + - 'This means you can ignore any previous messages of the form ' + - `"Possible Unhandled Promise Rejection (id: ${id}):"`; - console.warn(warning); }, }); + } catch (err) { + this._rejectionClient = undefined; + throw err; } } public dispose(): void { this.enabled = false; + this._managedClient = undefined; + this._rejectionClient = undefined; + + // Another SDK may have wrapped our handler. + // Only restore a handler we still own, retained wrappers remain forwarding-only after disposal. + if ( + this._installedHandler && + this._previousHandler && + ErrorUtils.getGlobalHandler() === this._installedHandler + ) { + ErrorUtils.setGlobalHandler(this._previousHandler); + } + + this._installedHandler = undefined; + this._previousHandler = undefined; + + // Promise trackers do not expose ownership. Disabling tracking globally + // could disable a newer tracker installed by another SDK. } - private reportRejection(client: BacktraceClient, id: number, rejection: Error | string) { + private reportRejection(id: number, rejection: unknown = 'Unknown'): void { + const client = this._rejectionClient; + if (!this.enabled || !client || this._reportingRejection) { + return; + } + + this._reportingRejection = true; try { - client.send( - new BacktraceReport( - rejection, - { - 'error.type': 'Unhandled rejection', - unhandledPromiseRejectionId: id, - }, - [], - { - classifiers: ['UnhandledPromiseRejection'], - skipFrames: rejection instanceof Error ? 0 : 1, - }, - ), + const data = + rejection instanceof Error || typeof rejection === 'string' + ? rejection + : this.describeRejection(rejection); + const report = new BacktraceReport( + data, + { + 'error.type': 'Unhandled rejection', + unhandledPromiseRejectionId: id, + }, + [], + { + classifiers: ['UnhandledPromiseRejection'], + // Hide this helper and the tracker callback for synthesized stacks. + skipFrames: rejection instanceof Error ? 0 : 2, + }, ); + + void Promise.resolve(client.send(report)).catch(() => undefined); } catch (err) { - warnFailure('failed to report an unhandled rejection', err); + this.warnReportingFailure('failed to report an unhandled rejection', err); + } finally { + this._reportingRejection = false; + } + } + + private describeRejection(rejection: unknown): string { + try { + return typeof rejection === 'string' ? rejection : prettyFormat(rejection); + } catch { + return ''; + } + } + + private warnUnhandledRejection(id: number, rejection: unknown = 'Unknown'): void { + if (!__DEV__) { + return; + } + + try { + const message = + rejection instanceof Error + ? `${Error.prototype.toString.call(rejection)}\n${rejection.stack ?? ''}` + : this.describeRejection(rejection); + this.warn(`Possible Unhandled Promise Rejection (id: ${id}):\n${message}`); + } catch { + // Error objects can expose getters that throw. + } + } + + private warnReportingFailure(message: string, error: unknown): void { + if (this._warning) { + return; + } + this._warning = true; + try { + warnFailure(message, error); + } catch { + // Logging failures must not replace the application's error. + } finally { + this._warning = false; + } + } + + private warn(message: string): void { + if (this._warning) { + return; + } + this._warning = true; + try { + console.warn(message); + } catch { + // Console can be instrumented by the application or another SDK. + } finally { + this._warning = false; } } } diff --git a/packages/react-native/tests/clientInitializationTests.spec.ts b/packages/react-native/tests/clientInitializationTests.spec.ts index dfcdccbb..cabd2f42 100644 --- a/packages/react-native/tests/clientInitializationTests.spec.ts +++ b/packages/react-native/tests/clientInitializationTests.spec.ts @@ -1,5 +1,15 @@ +import { + BacktraceReportSubmissionResult, + BreadcrumbType, + type BacktraceAttachment, + type BacktraceCoreClientBuilder, + type Breadcrumb, +} from '@backtrace/sdk-core'; +import assert from 'assert'; import { NativeModules } from 'react-native'; import { promisify } from 'util'; +import { version as packageVersion } from '../package.json'; +import { ReactNativeRequestHandler } from '../src/ReactNativeRequestHandler'; import { mockStreamFileSystem } from './_mocks/fileSystem'; // This package's jest config replaces the react-native preset's setupFiles, so the real Platform throws. @@ -23,7 +33,8 @@ NativeModules.BacktraceDirectoryProvider = { applicationDirectory: () => '/' }; // Loaded through the package entry point on purpose: this is the path applications take, // it's where the circular imports between the client, the builder and the breadcrumbs storage used to surface. /* eslint-disable @typescript-eslint/no-var-requires */ -const { BacktraceClient, BacktraceClientBuilder, BacktraceFileAttachment } = require('../src'); +const { BacktraceClient, BacktraceClientBuilder, BacktraceFileAttachment } = + require('../src') as typeof import('../src'); /* eslint-enable @typescript-eslint/no-var-requires */ const nextTick = promisify(process.nextTick); @@ -41,8 +52,7 @@ const options = { describe('BacktraceClient initialization through the package entry point', () => { afterEach(() => { BacktraceClient.instance?.dispose(); - // initialize() stores the singleton on BacktraceClient itself, which dispose() does not clear. - (BacktraceClient as { _instance?: unknown })._instance = undefined; + jest.restoreAllMocks(); }); it('Should create the builder from the client', () => { @@ -50,20 +60,102 @@ describe('BacktraceClient initialization through the package entry point', () => }); it('Should initialize the client through the builder', () => { - const client = BacktraceClient.initialize(options, (builder: typeof BacktraceClientBuilder.prototype) => { + const client = BacktraceClient.initialize(options, (builder: BacktraceCoreClientBuilder) => { expect(builder).toBeInstanceOf(BacktraceClientBuilder); builder.useFileSystem(mockStreamFileSystem()); }); expect(client).toBeInstanceOf(BacktraceClient); expect(BacktraceClient.instance).toBe(client); + expect(client.agentVersion).toBe(packageVersion); }); - it('Should expose breadcrumb files as file attachments', async () => { - const client = BacktraceClient.initialize(options, (builder: typeof BacktraceClientBuilder.prototype) => + it('Should clear the disposed singleton and initialize a new enabled client', () => { + const first = BacktraceClient.initialize(options, (builder: BacktraceCoreClientBuilder) => builder.useFileSystem(mockStreamFileSystem()), ); + first.dispose(); + + expect(first.enabled).toBe(false); + expect(BacktraceClient.instance).toBeUndefined(); + + const replacement = BacktraceClient.initialize(options, (builder: BacktraceCoreClientBuilder) => + builder.useFileSystem(mockStreamFileSystem()), + ); + + expect(replacement).not.toBe(first); + expect(replacement.enabled).toBe(true); + expect(BacktraceClient.instance).toBe(replacement); + }); + + it('Should retain the current singleton and its console capture when an older client is disposed again', () => { + jest.spyOn(console, 'log').mockImplementation(() => undefined); + const first = BacktraceClient.initialize(options, (builder: BacktraceCoreClientBuilder) => + builder.useFileSystem(mockStreamFileSystem()), + ); + first.dispose(); + const replacement = BacktraceClient.initialize(options, (builder: BacktraceCoreClientBuilder) => + builder.useFileSystem(mockStreamFileSystem()), + ); + const breadcrumbs = replacement.breadcrumbs; + assert(breadcrumbs); + const addBreadcrumb = jest.spyOn(breadcrumbs, 'addBreadcrumb'); + const installedConsoleHandler = console.log; + + first.dispose(); + + expect(BacktraceClient.instance).toBe(replacement); + expect(replacement.enabled).toBe(true); + expect(BacktraceClient.initialize(options)).toBe(replacement); + expect(console.log).toBe(installedConsoleHandler); + console.log('replacement console breadcrumb'); + expect(addBreadcrumb).toHaveBeenCalledWith( + 'replacement console breadcrumb', + expect.any(Number), + BreadcrumbType.Log, + ); + }); + + it('Should allow retrying disposal after a module fails and then initialize a fresh client', () => { + const failure = new Error('temporary disposal failure'); + const disposeModule = jest.fn().mockImplementationOnce(() => { + throw failure; + }); + const first = BacktraceClient.initialize(options, (builder: BacktraceCoreClientBuilder) => + builder.useFileSystem(mockStreamFileSystem()).useModule({ dispose: disposeModule }), + ); + + expect(() => first.dispose()).toThrow(failure); + expect(first.enabled).toBe(false); + expect(BacktraceClient.instance).toBe(first); + expect(disposeModule).toHaveBeenCalledTimes(1); + + expect(() => first.dispose()).not.toThrow(); + expect(disposeModule).toHaveBeenCalledTimes(2); + expect(BacktraceClient.instance).toBeUndefined(); + const replacement = BacktraceClient.initialize(options, (builder: BacktraceCoreClientBuilder) => + builder.useFileSystem(mockStreamFileSystem()), + ); + expect(replacement).not.toBe(first); + expect(replacement.enabled).toBe(true); + expect(BacktraceClient.instance).toBe(replacement); + }); + + it('Should submit the retained breadcrumb contents after rotating the files', async () => { + const fileSystem = mockStreamFileSystem(); + const postError = jest + .spyOn(ReactNativeRequestHandler.prototype, 'postError') + .mockResolvedValue(BacktraceReportSubmissionResult.Ok({})); + const client = BacktraceClient.initialize( + { + ...options, + // Keep the submitted report from adding an automatic breadcrumb while we inspect the files. + breadcrumbs: { ...options.breadcrumbs, eventType: BreadcrumbType.Manual }, + }, + (builder: BacktraceCoreClientBuilder) => builder.useFileSystem(fileSystem), + ); + // Enough breadcrumbs to rotate the breadcrumb files at least once. for (let i = 0; i < 20; i++) { client.breadcrumbs?.info(`breadcrumb-${i}`); @@ -73,7 +165,40 @@ describe('BacktraceClient initialization through the package entry point', () => await nextTick(); } - const attachments = client.attachments.filter((attachment) => attachment instanceof BacktraceFileAttachment); - expect(attachments.length).toBeGreaterThan(0); + await client.send(new Error('report with breadcrumbs')); + + expect(postError).toHaveBeenCalledTimes(1); + const submission = postError.mock.calls[0]; + assert(submission); + const [, json, submittedAttachments] = submission; + const reportAttachments: BacktraceAttachment[] = submittedAttachments; + const attachments = reportAttachments.filter( + (attachment): attachment is InstanceType => + attachment instanceof BacktraceFileAttachment, + ); + expect(attachments).toHaveLength(2); + const breadcrumbs = attachments.flatMap((attachment): Breadcrumb[] => { + const location = attachment.get(); + expect(location).toEqual({ + uri: attachment.filePath, + filepath: attachment.filePath, + name: attachment.name, + filename: attachment.name, + type: 'application/json', + }); + return fileSystem + .readFileSync(attachment.filePath) + .trim() + .split('\n') + .map((line) => JSON.parse(line)); + }); + expect(breadcrumbs.map((breadcrumb) => breadcrumb.message)).toEqual([ + 'breadcrumb-16', + 'breadcrumb-17', + 'breadcrumb-18', + 'breadcrumb-19', + ]); + expect(JSON.parse(json).attributes['breadcrumbs.lastId']).toBe(breadcrumbs[3]?.id); + expect(JSON.parse(json).agentVersion).toBe(packageVersion); }); }); diff --git a/packages/react-native/tests/errorHandlerSafetyTests.spec.ts b/packages/react-native/tests/errorHandlerSafetyTests.spec.ts new file mode 100644 index 00000000..f5bef38c --- /dev/null +++ b/packages/react-native/tests/errorHandlerSafetyTests.spec.ts @@ -0,0 +1,404 @@ +import { BacktraceReport } from '@backtrace/sdk-core'; +import type { BacktraceClient } from '../src/BacktraceClient'; + +jest.mock('promise/setimmediate/rejection-tracking', () => ({ + enable: jest.fn(), + disable: jest.fn(), +})); + +jest.mock('../src/common/hermesHelper', () => ({ + hermes: jest.fn(), +})); + +jest.mock('../src/crashReporter/CrashReporter', () => ({ + CrashReporter: { markFatalError: jest.fn() }, +})); + +import { hermes } from '../src/common/hermesHelper'; +import { CrashReporter } from '../src/crashReporter/CrashReporter'; +import { UnhandledExceptionHandler } from '../src/handlers/UnhandledExceptionHandler'; + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const rejectionTracking = require('promise/setimmediate/rejection-tracking') as { + enable: jest.Mock; + disable: jest.Mock; +}; + +type Handler = (error: Error, fatal?: boolean) => void; +type RejectionOptions = { + allRejections: boolean; + onUnhandled: (id: number, rejection?: unknown) => void; + onHandled?: (id: number) => void; +}; + +describe('UnhandledExceptionHandler safety', () => { + let current: Handler; + let previous: jest.Mock; + let setGlobalHandler: jest.Mock; + let send: jest.Mock; + let client: BacktraceClient; + let handler: UnhandledExceptionHandler; + let originalErrorUtils: PropertyDescriptor | undefined; + let originalDev: PropertyDescriptor | undefined; + let warn: jest.SpyInstance; + + beforeEach(() => { + jest.resetAllMocks(); + previous = jest.fn(); + current = previous; + send = jest.fn().mockResolvedValue(undefined); + client = { send } as unknown as BacktraceClient; + handler = new UnhandledExceptionHandler(); + originalErrorUtils = Object.getOwnPropertyDescriptor(globalThis, 'ErrorUtils'); + originalDev = Object.getOwnPropertyDescriptor(globalThis, '__DEV__'); + setGlobalHandler = jest.fn((next: Handler) => { + current = next; + }); + Object.defineProperty(globalThis, 'ErrorUtils', { + configurable: true, + value: { getGlobalHandler: () => current, setGlobalHandler }, + }); + Object.defineProperty(globalThis, '__DEV__', { configurable: true, value: false }); + warn = jest.spyOn(console, 'warn').mockImplementation(() => undefined); + }); + + afterEach(() => { + handler.dispose(); + if (originalErrorUtils) { + Object.defineProperty(globalThis, 'ErrorUtils', originalErrorUtils); + } else { + Reflect.deleteProperty(globalThis, 'ErrorUtils'); + } + if (originalDev) { + Object.defineProperty(globalThis, '__DEV__', originalDev); + } else { + Reflect.deleteProperty(globalThis, '__DEV__'); + } + jest.restoreAllMocks(); + }); + + describe('managed errors', () => { + it('delegates exactly once when synchronous reporting fails and preserves the failure warning', () => { + send.mockImplementation(() => { + throw new Error('attachment construction failed'); + }); + handler.captureManagedErrors(client); + const error = new Error('application failure'); + expect(() => current(error, true)).not.toThrow(); + expect(previous).toHaveBeenCalledTimes(1); + expect(previous).toHaveBeenCalledWith(error, true); + expect(CrashReporter.markFatalError).not.toHaveBeenCalled(); + expect(warn).toHaveBeenCalledWith('Backtrace: failed to report an unhandled error (Error)'); + }); + + it('observes asynchronous failures without delaying delegation or fatal marking', async () => { + send.mockRejectedValue(new Error('transport failure')); + handler.captureManagedErrors(client); + const error = new Error('application failure'); + current(error, true); + expect(previous).toHaveBeenCalledTimes(1); + expect(previous).toHaveBeenCalledWith(error, true); + expect(CrashReporter.markFatalError).toHaveBeenCalledTimes(1); + // A rejected submission must be handled in the same turn, + // even if the process remains alive after the application's handler returns. + await new Promise((resolve) => setImmediate(resolve)); + }); + + it('delegates even when native fatal marking fails', () => { + (CrashReporter.markFatalError as jest.Mock).mockImplementation(() => { + throw new Error('native marker failed'); + }); + handler.captureManagedErrors(client); + const error = new Error('application failure'); + expect(() => current(error, true)).not.toThrow(); + expect(previous).toHaveBeenCalledTimes(1); + expect(previous).toHaveBeenCalledWith(error, true); + }); + + it('does not swallow exceptions from the previous global handler', () => { + const originalFailure = new Error('previous handler failure'); + previous.mockImplementation(() => { + throw originalFailure; + }); + handler.captureManagedErrors(client); + expect(() => current(new Error('application failure'), false)).toThrow(originalFailure); + expect(previous).toHaveBeenCalledTimes(1); + }); + + it('delegates reentrant errors without recursively reporting them and resumes reporting later errors', () => { + const nested = new Error('reporting failure'); + send.mockImplementationOnce(() => current(nested, false)); + handler.captureManagedErrors(client); + const original = new Error('application failure'); + current(original, false); + expect(send).toHaveBeenCalledTimes(1); + expect(previous.mock.calls).toEqual([ + [nested, false], + [original, false], + ]); + current(new Error('later failure'), false); + expect(send).toHaveBeenCalledTimes(2); + }); + + it('still delegates when reporting and diagnostic logging both throw', () => { + send.mockImplementation(() => { + throw new Error('report construction failure'); + }); + warn.mockImplementation(() => { + throw new Error('logger failure'); + }); + handler.captureManagedErrors(client); + const error = new Error('application failure'); + expect(() => current(error, false)).not.toThrow(); + expect(previous).toHaveBeenCalledTimes(1); + expect(previous).toHaveBeenCalledWith(error, false); + }); + + it('does not recursively report errors generated by an instrumented warning logger', () => { + send.mockImplementation(() => { + throw new Error('report construction failure'); + }); + const loggingError = new Error('logger failure'); + warn.mockImplementation(() => current(loggingError, false)); + handler.captureManagedErrors(client); + const original = new Error('application failure'); + current(original, false); + expect(send).toHaveBeenCalledTimes(1); + expect(warn).toHaveBeenCalledTimes(1); + expect(previous.mock.calls).toEqual([ + [loggingError, false], + [original, false], + ]); + }); + + it('registers only once even when capture is called again with another client', () => { + handler.captureManagedErrors(client); + const installed = current; + const otherSend = jest.fn(); + handler.captureManagedErrors({ send: otherSend } as unknown as BacktraceClient); + expect(current).toBe(installed); + expect(setGlobalHandler).toHaveBeenCalledTimes(1); + current(new Error('application failure')); + expect(send).toHaveBeenCalledTimes(1); + expect(otherSend).not.toHaveBeenCalled(); + }); + + it('restores its previous handler on disposal and leaves retained wrappers forwarding-only', () => { + handler.captureManagedErrors(client); + const retained = current; + handler.dispose(); + expect(current).toBe(previous); + const error = new Error('after disposal'); + retained(error, true); + expect(send).not.toHaveBeenCalled(); + expect(CrashReporter.markFatalError).not.toHaveBeenCalled(); + expect(previous).toHaveBeenCalledWith(error, true); + handler.dispose(); + expect(setGlobalHandler).toHaveBeenCalledTimes(2); + }); + + it('preserves later SDK wrappers and forwards through its disposed wrapper', () => { + handler.captureManagedErrors(client); + const retained = current; + const later = jest.fn((error: Error, fatal?: boolean) => retained(error, fatal)); + ErrorUtils.setGlobalHandler(later); + handler.dispose(); + expect(current).toBe(later); + const error = new Error('after disposal'); + current(error, false); + expect(later).toHaveBeenCalledWith(error, false); + expect(previous).toHaveBeenCalledWith(error, false); + expect(send).not.toHaveBeenCalled(); + }); + + it('does not register after disposal', () => { + handler.dispose(); + handler.captureManagedErrors(client); + handler.captureUnhandledPromiseRejections(client); + expect(setGlobalHandler).not.toHaveBeenCalled(); + expect(rejectionTracking.enable).not.toHaveBeenCalled(); + }); + + it('can retry registration after ErrorUtils rejects it', () => { + setGlobalHandler.mockImplementationOnce(() => { + throw new Error('cannot install handler'); + }); + expect(() => handler.captureManagedErrors(client)).toThrow('cannot install handler'); + handler.captureManagedErrors(client); + current(new Error('application failure')); + expect(send).toHaveBeenCalledTimes(1); + }); + }); + + describe.each(['Hermes', 'fallback'])('%s promise rejections', (engine) => { + let tracker: jest.Mock; + let options: RejectionOptions; + + beforeEach(() => { + tracker = engine === 'Hermes' ? jest.fn() : rejectionTracking.enable; + if (engine === 'Hermes') { + (hermes as jest.Mock).mockReturnValue({ + hasPromise: () => true, + enablePromiseRejectionTracker: tracker, + }); + } + handler.captureUnhandledPromiseRejections(client); + options = tracker.mock.calls[0][0]; + }); + + it('preserves error identity, classification, and rejection id', () => { + const error = new Error('rejected'); + options.onUnhandled(42, error); + const report = send.mock.calls[0][0] as BacktraceReport; + expect(report.data).toBe(error); + expect(report.attributes['error.type']).toBe('Unhandled rejection'); + expect(report.attributes['unhandledPromiseRejectionId']).toBe(42); + expect(report.classifiers).toContain('UnhandledPromiseRejection'); + expect(report.skipFrames).toBe(0); + expect(options.allRejections).toBe(true); + }); + + it.each([ + ['string', 'rejected', 'rejected'], + ['number', 42, '42'], + ['boolean', false, 'false'], + ['null', null, 'null'], + ['undefined', undefined, 'Unknown'], + ['symbol', Symbol('reason'), 'Symbol(reason)'], + ['object', { reason: 'bad request' }, 'bad request'], + ])('reports %s rejection values', (_label, value, expected) => { + expect(() => options.onUnhandled(7, value)).not.toThrow(); + expect(send).toHaveBeenCalledTimes(1); + const report = send.mock.calls[0][0] as BacktraceReport; + expect(report.data).toEqual(expect.stringContaining(expected as string)); + // BacktraceReport adds the constructor frame to the helper/callback frames. + expect(report.skipFrames).toBe(3); + }); + + it('reports circular objects and objects with throwing formatters safely', () => { + const circular: { self?: unknown } = {}; + circular.self = circular; + options.onUnhandled(1, circular); + expect((send.mock.calls[0][0] as BacktraceReport).data).toContain('[Circular]'); + options.onUnhandled(2, { + toJSON() { + throw new Error('formatter failure'); + }, + }); + expect((send.mock.calls[1][0] as BacktraceReport).data).toBe(''); + }); + + it('handles reporting and logging failures without throwing', () => { + send.mockImplementation(() => { + throw new Error('report construction failure'); + }); + warn.mockImplementation(() => { + throw new Error('logger failure'); + }); + expect(() => options.onUnhandled(1, new Error('rejected'))).not.toThrow(); + expect(send).toHaveBeenCalledTimes(1); + }); + + it('observes rejected reporting promises', async () => { + send.mockRejectedValue(new Error('transport failure')); + options.onUnhandled(1, new Error('rejected')); + expect(send).toHaveBeenCalledTimes(1); + await new Promise((resolve) => setImmediate(resolve)); + }); + + it('does not recursively report rejection callbacks invoked during send', () => { + send.mockImplementationOnce(() => options.onUnhandled(2, new Error('nested rejection'))); + options.onUnhandled(1, new Error('rejected')); + expect(send).toHaveBeenCalledTimes(1); + options.onUnhandled(3, new Error('later rejection')); + expect(send).toHaveBeenCalledTimes(2); + }); + + it('does not register again or disable another tracker during disposal', () => { + handler.captureUnhandledPromiseRejections(client); + expect(tracker).toHaveBeenCalledTimes(1); + handler.dispose(); + options.onUnhandled(1, new Error('after disposal')); + expect(send).not.toHaveBeenCalled(); + expect(rejectionTracking.disable).not.toHaveBeenCalled(); + expect(tracker).toHaveBeenCalledTimes(1); + }); + }); + + it('allows retrying promise tracker installation after failure', () => { + rejectionTracking.enable.mockImplementationOnce(() => { + throw new Error('tracker unavailable'); + }); + expect(() => handler.captureUnhandledPromiseRejections(client)).toThrow('tracker unavailable'); + handler.captureUnhandledPromiseRejections(client); + expect(rejectionTracking.enable).toHaveBeenCalledTimes(2); + rejectionTracking.enable.mock.calls[1][0].onUnhandled(1, 'rejected'); + expect(send).toHaveBeenCalledTimes(1); + }); + + it('uses the fallback when Hermes does not supply native promises', () => { + const hermesTracker = jest.fn(); + (hermes as jest.Mock).mockReturnValue({ + hasPromise: () => false, + enablePromiseRejectionTracker: hermesTracker, + }); + handler.captureUnhandledPromiseRejections(client); + expect(hermesTracker).not.toHaveBeenCalled(); + expect(rejectionTracking.enable).toHaveBeenCalledTimes(1); + }); + + describe('fallback development warnings', () => { + let options: RejectionOptions; + + beforeEach(() => { + Object.defineProperty(globalThis, '__DEV__', { configurable: true, value: true }); + handler.captureUnhandledPromiseRejections(client); + options = rejectionTracking.enable.mock.calls[0][0]; + }); + + it('preserves readable Error stacks and handled-rejection warnings', () => { + const error = new Error('rejected'); + options.onUnhandled(2, error); + expect(warn).toHaveBeenCalledWith( + `Possible Unhandled Promise Rejection (id: 2):\nError: rejected\n${error.stack}`, + ); + options.onHandled?.(2); + expect(warn).toHaveBeenCalledWith(expect.stringContaining('Promise Rejection Handled (id: 2)')); + }); + + it('formats arbitrary rejection values and keeps warnings after disposal', () => { + handler.dispose(); + options.onUnhandled(2, { reason: 'rejected' }); + expect(warn).toHaveBeenCalledWith(expect.stringContaining('rejected')); + expect(send).not.toHaveBeenCalled(); + }); + + it('does not throw for errors with throwing stack getters or an instrumented console', () => { + const error = new Error('rejected'); + Object.defineProperty(error, 'stack', { + get() { + throw new Error('stack getter failed'); + }, + }); + expect(() => options.onUnhandled(1, error)).not.toThrow(); + warn.mockImplementation(() => { + throw new Error('console failure'); + }); + expect(() => options.onUnhandled(2, 'rejected')).not.toThrow(); + expect(() => options.onHandled?.(2)).not.toThrow(); + }); + + it('does not recurse when the warning logger invokes rejection callbacks', () => { + warn.mockImplementation(() => options.onUnhandled(2, 'logger rejection')); + expect(() => options.onUnhandled(1, 'rejected')).not.toThrow(); + expect(warn).toHaveBeenCalledTimes(1); + }); + + it('omits React Native warnings in production', () => { + Object.defineProperty(globalThis, '__DEV__', { configurable: true, value: false }); + options.onUnhandled(1, 'rejected'); + options.onHandled?.(1); + expect(warn).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/react-native/tests/scripts/generateAgentVersionTests.spec.ts b/packages/react-native/tests/scripts/generateAgentVersionTests.spec.ts new file mode 100644 index 00000000..55df3c58 --- /dev/null +++ b/packages/react-native/tests/scripts/generateAgentVersionTests.spec.ts @@ -0,0 +1,61 @@ +import { spawnSync } from 'child_process'; +import fs from 'fs'; +import os from 'os'; +import path from 'path'; + +describe('agent version generation', () => { + let root: string; + let script: string; + let target: string; + + beforeEach(() => { + root = fs.mkdtempSync(path.join(os.tmpdir(), 'backtrace-agent-version-')); + script = path.join(root, 'scripts/generate-agent-version.cjs'); + target = path.join(root, 'src/common/agentVersion.ts'); + fs.mkdirSync(path.dirname(script)); + fs.copyFileSync(path.resolve(__dirname, '../../scripts/generate-agent-version.cjs'), script); + fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify({ version: '1.2.3-rc.4' })); + }); + + afterEach(() => { + fs.rmSync(root, { recursive: true, force: true }); + }); + + const run = (...args: string[]) => spawnSync(process.execPath, [script, ...args], { encoding: 'utf8' }); + + it('generates a version module and leaves matching files untouched', () => { + expect(run().status).toBe(0); + expect(fs.readFileSync(target, 'utf8')).toContain("export const agentVersion = '1.2.3-rc.4';"); + const before = fs.statSync(target).mtimeMs; + expect(run().status).toBe(0); + expect(run('--check').status).toBe(0); + expect(fs.statSync(target).mtimeMs).toBe(before); + }); + + it('detects missing and stale modules without changing them in check mode', () => { + expect(run('--check').status).not.toBe(0); + expect(fs.existsSync(target)).toBe(false); + expect(run().status).toBe(0); + const before = fs.readFileSync(target, 'utf8'); + fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify({ version: '1.2.3' })); + expect(run('--check').status).not.toBe(0); + expect(fs.readFileSync(target, 'utf8')).toBe(before); + expect(run().status).toBe(0); + expect(fs.readFileSync(target, 'utf8')).toContain("export const agentVersion = '1.2.3';"); + }); + + it.each([undefined, 123, "1.2.3'; throw new Error('injected')", 'not-a-version'])( + 'rejects unsafe or missing version metadata: %s', + (version) => { + fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify({ version })); + expect(run().status).not.toBe(0); + expect(fs.existsSync(target)).toBe(false); + }, + ); + + it('rejects unknown arguments without writing a module', () => { + expect(run('--unknown').status).not.toBe(0); + expect(run('--check', '--check').status).not.toBe(0); + expect(fs.existsSync(target)).toBe(false); + }); +}); diff --git a/packages/sdk-core/package.json b/packages/sdk-core/package.json index 93067401..dedbb831 100644 --- a/packages/sdk-core/package.json +++ b/packages/sdk-core/package.json @@ -1,6 +1,6 @@ { "name": "@backtrace/sdk-core", - "version": "0.8.3", + "version": "0.8.4-rc", "description": "Backtrace-JavaScript SDK core library", "main": "lib/bundle.cjs", "module": "lib/bundle.mjs", @@ -13,7 +13,7 @@ "lint": "eslint . --ext .ts", "watch": "concurrently \"tsc -p tsconfig.build.json --noEmit --watch\" \"rollup -c rollup.config.mjs --watch\"", "test": "cross-env NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" NODE_NO_WARNINGS=1 NODE_ENV=test jest", - "prepublishOnly": "npm run clean && cross-env NODE_ENV=production npm run build" + "prepack": "npm run clean && cross-env NODE_ENV=production npm run build" }, "repository": { "type": "git", @@ -45,5 +45,9 @@ "rollup": "^4.21.0", "ts-jest": "^29.1.0", "typescript": "^5.0.4" + }, + "publishConfig": { + "access": "public", + "tag": "rc" } -} \ No newline at end of file +} diff --git a/packages/sdk-core/src/BacktraceCoreClient.ts b/packages/sdk-core/src/BacktraceCoreClient.ts index c4581037..672210f5 100644 --- a/packages/sdk-core/src/BacktraceCoreClient.ts +++ b/packages/sdk-core/src/BacktraceCoreClient.ts @@ -368,7 +368,9 @@ export abstract class BacktraceCoreClient< } } - BacktraceCoreClient.destroy(); + if (BacktraceCoreClient._instance === this) { + BacktraceCoreClient.destroy(); + } } protected addModule(module: T): void; diff --git a/packages/sdk-core/tests/client/clientTests.spec.ts b/packages/sdk-core/tests/client/clientTests.spec.ts index 3e6d3f65..6b3ebc82 100644 --- a/packages/sdk-core/tests/client/clientTests.spec.ts +++ b/packages/sdk-core/tests/client/clientTests.spec.ts @@ -2,11 +2,45 @@ import { BacktraceReport, BacktraceStringAttachment } from '../../src/index.js'; import { AttributeManager } from '../../src/modules/attribute/AttributeManager.js'; import { BacktraceTestClient } from '../mocks/BacktraceTestClient.js'; import { testHttpClient } from '../mocks/testHttpClient.js'; + +class LifecycleTestClient extends BacktraceTestClient { + public static get instance() { + return this._instance; + } +} + describe('Client tests', () => { afterEach(() => { jest.restoreAllMocks(); }); + describe('Disposal tests', () => { + it('Should clear the current core instance when it is disposed', () => { + const client = BacktraceTestClient.buildFakeClient({ breadcrumbs: { enable: false } }); + + expect(LifecycleTestClient.instance).toBe(client); + client.dispose(); + + expect(client.enabled).toBe(false); + expect(LifecycleTestClient.instance).toBeUndefined(); + }); + + it('Should retain a newer core instance when an older client is disposed', () => { + const first = BacktraceTestClient.buildFakeClient({ breadcrumbs: { enable: false } }); + const replacement = BacktraceTestClient.buildFakeClient({ breadcrumbs: { enable: false } }); + + try { + first.dispose(); + + expect(first.enabled).toBe(false); + expect(LifecycleTestClient.instance).toBe(replacement); + expect(replacement.enabled).toBe(true); + } finally { + replacement.dispose(); + } + }); + }); + describe('Send tests', () => { const client = BacktraceTestClient.buildFakeClient(); diff --git a/packages/session-replay/package.json b/packages/session-replay/package.json index 5dc4a7bc..835f97f1 100644 --- a/packages/session-replay/package.json +++ b/packages/session-replay/package.json @@ -13,7 +13,7 @@ "format": "prettier --write '**/*.ts'", "lint": "eslint . --ext .ts", "watch": "concurrently \"tsc -p tsconfig.build.json --noEmit --watch\" \"rollup -c rollup.config.mjs --watch\"", - "prepublishOnly": "npm -w ../sdk-core run prepublishOnly && npm run clean && cross-env NODE_ENV=production npm run build", + "prepublishOnly": "npm -w ../sdk-core run prepack && npm run clean && cross-env NODE_ENV=production npm run build", "test": "cross-env NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" NODE_NO_WARNINGS=1 NODE_ENV=test jest" }, "repository": { @@ -31,7 +31,7 @@ "homepage": "https://github.com/backtrace-labs/backtrace-javascript#readme", "dependencies": { "rrweb": "^2.0.0-alpha.15", - "@backtrace/sdk-core": "0.8.3" + "@backtrace/sdk-core": "0.8.3 || 0.8.4-rc" }, "devDependencies": { "@rollup/plugin-commonjs": "^26.0.1", @@ -48,4 +48,4 @@ "ts-jest": "^29.1.0", "typescript": "^5.0.4" } -} \ No newline at end of file +}