var {
  _optionalChain
} = require('@sentry/utils/cjs/buildPolyfills');

Object.defineProperty(exports, '__esModule', { value: true });

const core = require('@sentry/core');
const utils = require('@sentry/utils');
const child_process = require('child_process');
const _debugger = require('./debugger.js');

const DEFAULT_INTERVAL = 50;
const DEFAULT_HANG_THRESHOLD = 5000;

function createAnrEvent(blockedMs, frames) {
  return {
    level: 'error',
    exception: {
      values: [
        {
          type: 'ApplicationNotResponding',
          value: `Application Not Responding for at least ${blockedMs} ms`,
          stacktrace: { frames },
          mechanism: {
            // This ensures the UI doesn't say 'Crashed in' for the stack trace
            type: 'ANR',
          },
        },
      ],
    },
  };
}

/**
 * Starts the node debugger and returns the inspector url.
 *
 * When inspector.url() returns undefined, it means the port is already in use so we try the next port.
 */
function startInspector(startPort = 9229) {
  // eslint-disable-next-line @typescript-eslint/no-var-requires
  const inspector = require('inspector');
  let inspectorUrl = undefined;
  let port = startPort;

  while (inspectorUrl === undefined && port < startPort + 100) {
    inspector.open(port);
    inspectorUrl = inspector.url();
    port++;
  }

  return inspectorUrl;
}

function startChildProcess(options) {
  function log(message, ...args) {
    utils.logger.log(`[ANR] ${message}`, ...args);
  }

  const hub = core.getCurrentHub();

  try {
    const env = { ...process.env };
    env.SENTRY_ANR_CHILD_PROCESS = 'true';

    if (options.captureStackTrace) {
      env.SENTRY_INSPECT_URL = startInspector();
    }

    log(`Spawning child process with execPath:'${process.execPath}' and entryScript:'${options.entryScript}'`);

    const child = child_process.spawn(process.execPath, [options.entryScript], {
      env,
      stdio: utils.logger.isEnabled() ? ['inherit', 'inherit', 'inherit', 'ipc'] : ['ignore', 'ignore', 'ignore', 'ipc'],
    });
    // The child process should not keep the main process alive
    child.unref();

    const timer = setInterval(() => {
      try {
        const currentSession = _optionalChain([hub, 'access', _2 => _2.getScope, 'call', _3 => _3(), 'optionalAccess', _4 => _4.getSession, 'call', _5 => _5()]);
        // We need to copy the session object and remove the toJSON method so it can be sent to the child process
        // serialized without making it a SerializedSession
        const session = currentSession ? { ...currentSession, toJSON: undefined } : undefined;
        // message the child process to tell it the main event loop is still running
        child.send({ session });
      } catch (_) {
        //
      }
    }, options.pollInterval);

    child.on('message', (msg) => {
      if (msg === 'session-ended') {
        log('ANR event sent from child process. Clearing session in this process.');
        _optionalChain([hub, 'access', _6 => _6.getScope, 'call', _7 => _7(), 'optionalAccess', _8 => _8.setSession, 'call', _9 => _9(undefined)]);
      }
    });

    const end = (type) => {
      return (...args) => {
        clearInterval(timer);
        log(`Child process ${type}`, ...args);
      };
    };

    child.on('error', end('error'));
    child.on('disconnect', end('disconnect'));
    child.on('exit', end('exit'));
  } catch (e) {
    log('Failed to start child process', e);
  }
}

function createHrTimer() {
  let lastPoll = process.hrtime();

  return {
    getTimeMs: () => {
      const [seconds, nanoSeconds] = process.hrtime(lastPoll);
      return Math.floor(seconds * 1e3 + nanoSeconds / 1e6);
    },
    reset: () => {
      lastPoll = process.hrtime();
    },
  };
}

function handleChildProcess(options) {
  process.title = 'sentry-anr';

  function log(message) {
    utils.logger.log(`[ANR child process] ${message}`);
  }

  log('Started');
  let session;

  function sendAnrEvent(frames) {
    if (session) {
      log('Sending abnormal session');
      core.updateSession(session, { status: 'abnormal', abnormal_mechanism: 'anr_foreground' });
      _optionalChain([core.getCurrentHub, 'call', _10 => _10(), 'access', _11 => _11.getClient, 'call', _12 => _12(), 'optionalAccess', _13 => _13.sendSession, 'call', _14 => _14(session)]);

      try {
        // Notify the main process that the session has ended so the session can be cleared from the scope
        _optionalChain([process, 'access', _15 => _15.send, 'optionalCall', _16 => _16('session-ended')]);
      } catch (_) {
        // ignore
      }
    }

    core.captureEvent(createAnrEvent(options.anrThreshold, frames));

    void core.flush(3000).then(() => {
      // We only capture one event to avoid spamming users with errors
      process.exit();
    });
  }

  core.addGlobalEventProcessor(event => {
    // Strip sdkProcessingMetadata from all child process events to remove trace info
    delete event.sdkProcessingMetadata;
    event.tags = {
      ...event.tags,
      'process.name': 'ANR',
    };
    return event;
  });

  let debuggerPause;

  // if attachStackTrace is enabled, we'll have a debugger url to connect to
  if (process.env.SENTRY_INSPECT_URL) {
    log('Connecting to debugger');

    debuggerPause = _debugger.captureStackTrace(process.env.SENTRY_INSPECT_URL, frames => {
      log('Capturing event with stack frames');
      sendAnrEvent(frames);
    });
  }

  async function watchdogTimeout() {
    log('Watchdog timeout');
    const pauseAndCapture = await debuggerPause;

    if (pauseAndCapture) {
      log('Pausing debugger to capture stack trace');
      pauseAndCapture();
    } else {
      log('Capturing event');
      sendAnrEvent();
    }
  }

  const { poll } = utils.watchdogTimer(createHrTimer, options.pollInterval, options.anrThreshold, watchdogTimeout);

  process.on('message', (msg) => {
    if (msg.session) {
      session = core.makeSession(msg.session);
    }
    poll();
  });
}

/**
 * Returns true if the current process is an ANR child process.
 */
function isAnrChildProcess() {
  return !!process.send && !!process.env.SENTRY_ANR_CHILD_PROCESS;
}

/**
 * **Note** This feature is still in beta so there may be breaking changes in future releases.
 *
 * Starts a child process that detects Application Not Responding (ANR) errors.
 *
 * It's important to await on the returned promise before your app code to ensure this code does not run in the ANR
 * child process.
 *
 * ```js
 * import { init, enableAnrDetection } from '@sentry/node';
 *
 * init({ dsn: "__DSN__" });
 *
 * // with ESM + Node 14+
 * await enableAnrDetection({ captureStackTrace: true });
 * runApp();
 *
 * // with CJS or Node 10+
 * enableAnrDetection({ captureStackTrace: true }).then(() => {
 *   runApp();
 * });
 * ```
 */
function enableAnrDetection(options) {
  // When pm2 runs the script in cluster mode, process.argv[1] is the pm2 script and process.env.pm_exec_path is the
  // path to the entry script
  const entryScript = options.entryScript || process.env.pm_exec_path || process.argv[1];

  const anrOptions = {
    entryScript,
    pollInterval: options.pollInterval || DEFAULT_INTERVAL,
    anrThreshold: options.anrThreshold || DEFAULT_HANG_THRESHOLD,
    captureStackTrace: !!options.captureStackTrace,
    // eslint-disable-next-line deprecation/deprecation
    debug: !!options.debug,
  };

  if (isAnrChildProcess()) {
    handleChildProcess(anrOptions);
    // In the child process, the promise never resolves which stops the app code from running
    return new Promise(() => {
      // Never resolve
    });
  } else {
    startChildProcess(anrOptions);
    // In the main process, the promise resolves immediately
    return Promise.resolve();
  }
}

exports.enableAnrDetection = enableAnrDetection;
exports.isAnrChildProcess = isAnrChildProcess;
//# sourceMappingURL=index.js.map
