import { _optionalChain, _optionalChainDelete } from '@sentry/utils'; import { URL } from 'url'; import { defineIntegration, convertIntegrationFnToClass, getCurrentScope } from '@sentry/core'; import { logger, dynamicRequire } from '@sentry/utils'; import { NODE_VERSION } from '../../nodeVersion.js'; import { base64WorkerScript } from './worker-script.js'; const DEFAULT_INTERVAL = 50; const DEFAULT_HANG_THRESHOLD = 5000; function log(message, ...args) { logger.log(`[ANR] ${message}`, ...args); } /** * We need to use dynamicRequire because worker_threads is not available in node < v12 and webpack error will when * targeting those versions */ function getWorkerThreads() { return dynamicRequire(module, 'worker_threads'); } /** * Gets contexts by calling all event processors. This relies on being called after all integrations are setup */ async function getContexts(client) { let event = { message: 'ANR' }; const eventHint = {}; for (const processor of client.getEventProcessors()) { if (event === null) break; event = await processor(event, eventHint); } return _optionalChain([event, 'optionalAccess', _2 => _2.contexts]) || {}; } const INTEGRATION_NAME = 'Anr'; const _anrIntegration = ((options = {}) => { return { name: INTEGRATION_NAME, // TODO v8: Remove this setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function setup(client) { if (NODE_VERSION.major < 16 || (NODE_VERSION.major === 16 && NODE_VERSION.minor < 17)) { throw new Error('ANR detection requires Node 16.17.0 or later'); } // setImmediate is used to ensure that all other integrations have been setup setImmediate(() => _startWorker(client, options)); }, }; }) ; const anrIntegration = defineIntegration(_anrIntegration); /** * Starts a thread to detect App Not Responding (ANR) events * * ANR detection requires Node 16.17.0 or later * * @deprecated Use `anrIntegration()` instead. */ // eslint-disable-next-line deprecation/deprecation const Anr = convertIntegrationFnToClass(INTEGRATION_NAME, anrIntegration) ; // eslint-disable-next-line deprecation/deprecation /** * Starts the ANR worker thread */ async function _startWorker(client, _options) { const contexts = await getContexts(client); const dsn = client.getDsn(); if (!dsn) { return; } // These will not be accurate if sent later from the worker thread _optionalChainDelete([contexts, 'access', _3 => _3.app, 'optionalAccess', _4 => delete _4.app_memory]); _optionalChainDelete([contexts, 'access', _5 => _5.device, 'optionalAccess', _6 => delete _6.free_memory]); const initOptions = client.getOptions(); const sdkMetadata = client.getSdkMetadata() || {}; if (sdkMetadata.sdk) { sdkMetadata.sdk.integrations = initOptions.integrations.map(i => i.name); } const options = { debug: logger.isEnabled(), dsn, environment: initOptions.environment || 'production', release: initOptions.release, dist: initOptions.dist, sdkMetadata, appRootPath: _options.appRootPath, pollInterval: _options.pollInterval || DEFAULT_INTERVAL, anrThreshold: _options.anrThreshold || DEFAULT_HANG_THRESHOLD, captureStackTrace: !!_options.captureStackTrace, staticTags: _options.staticTags || {}, contexts, }; if (options.captureStackTrace) { // eslint-disable-next-line @typescript-eslint/no-var-requires const inspector = require('inspector'); if (!inspector.url()) { inspector.open(0); } } const { Worker } = getWorkerThreads(); const worker = new Worker(new URL(`data:application/javascript;base64,${base64WorkerScript}`), { workerData: options, }); process.on('exit', () => { worker.terminate(); }); const timer = setInterval(() => { try { const currentSession = getCurrentScope().getSession(); // We need to copy the session object and remove the toJSON method so it can be sent to the worker // serialized without making it a SerializedSession const session = currentSession ? { ...currentSession, toJSON: undefined } : undefined; // message the worker to tell it the main event loop is still running worker.postMessage({ session }); } catch (_) { // } }, options.pollInterval); // Timer should not block exit timer.unref(); worker.on('message', (msg) => { if (msg === 'session-ended') { log('ANR event sent from ANR worker. Clearing session in this thread.'); getCurrentScope().setSession(undefined); } }); worker.once('error', (err) => { clearInterval(timer); log('ANR worker error', err); }); worker.once('exit', (code) => { clearInterval(timer); log('ANR worker exit', code); }); // Ensure this thread can't block app exit worker.unref(); } export { Anr, anrIntegration }; //# sourceMappingURL=index.js.map