"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.detachStream = detachStream; exports.getImageNameFromBundleIdentifierAsync = getImageNameFromBundleIdentifierAsync; exports.getImageNameFromContainerPath = getImageNameFromContainerPath; exports.isStreamingLogs = isStreamingLogs; exports.onMessage = onMessage; exports.streamLogs = streamLogs; function _chalk() { const data = _interopRequireDefault(require("chalk")); _chalk = function () { return data; }; return data; } function _child_process() { const data = require("child_process"); _child_process = function () { return data; }; return data; } function _path() { const data = _interopRequireDefault(require("path")); _path = function () { return data; }; return data; } function _wrapAnsi() { const data = _interopRequireDefault(require("wrap-ansi")); _wrapAnsi = function () { return data; }; return data; } function _internal() { const data = require("./internal"); _internal = function () { return data; }; return data; } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const forks = {}; function parseMessageJson(data) { const stringData = data.toString(); try { return JSON.parse(stringData); } catch { _internal().Logger.global.debug('Failed to parse simctl JSON message:\n' + stringData); } return null; } // There are a lot of networking logs in RN that aren't relevant to the user. function isNetworkLog(simLog) { var _simLog$source; return simLog.subsystem === 'com.apple.network' || simLog.category === 'connection' || ((_simLog$source = simLog.source) === null || _simLog$source === void 0 ? void 0 : _simLog$source.image) === 'CFNetwork'; } function isReactLog(simLog) { var _simLog$source2; return simLog.subsystem === 'com.facebook.react.log' && ((_simLog$source2 = simLog.source) === null || _simLog$source2 === void 0 ? void 0 : _simLog$source2.file) === 'RCTLog.mm'; } // It's not clear what these are but they aren't very useful. // (The connection to service on pid 0 named com.apple.commcenter.coretelephony.xpc was invalidated) // We can add them later if need. function isCoreTelephonyLog(simLog) { // [CoreTelephony] Updating selectors failed with: Error Domain=NSCocoaErrorDomain Code=4099 // "The connection to service on pid 0 named com.apple.commcenter.coretelephony.xpc was invalidated." UserInfo={NSDebugDescription=The connection to service on pid 0 named com.apple.commcenter.coretelephony.xpc was invalidated.} return simLog.subsystem === 'com.apple.CoreTelephony'; } // https://stackoverflow.com/a/65313219/4047926 function isWebKitLog(simLog) { // [WebKit] 0x1143ca500 - ProcessAssertion: Failed to acquire RBS Background assertion 'WebProcess Background Assertion' for process with PID 27084, error: Error Domain=RBSAssertionErrorDomain Code=3 "Target is not running or required target // entitlement is missing" UserInfo={RBSAssertionAttribute=, NSLocalizedFailureReason=Target is not running or required target entitlement is missing} return simLog.subsystem === 'com.apple.WebKit'; } // Similar to WebKit logs function isRunningBoardServicesLog(simLog) { // [RunningBoardServices] Error acquiring assertion: , NSLocalizedFailureReason=Target is not running or required target entitlement is missing}> return simLog.subsystem === 'com.apple.runningboard'; } function formatMessage(simLog) { var _simLog$source$image, _simLog$source3; // TODO: Maybe change "TCC" to "Consent" or "System". const category = _chalk().default.gray(`[${(_simLog$source$image = (_simLog$source3 = simLog.source) === null || _simLog$source3 === void 0 ? void 0 : _simLog$source3.image) !== null && _simLog$source$image !== void 0 ? _simLog$source$image : simLog.subsystem}]`); const message = simLog.eventMessage; return (0, _wrapAnsi().default)(category + ' ' + message, process.stdout.columns || 80); } function onMessage(simLog) { let hasLogged = false; if (simLog.messageType === 'Error') { if ( // Hide all networking errors which are mostly useless. !isNetworkLog(simLog) && // Showing React errors will result in duplicate messages. !isReactLog(simLog) && !isCoreTelephonyLog(simLog) && !isWebKitLog(simLog) && !isRunningBoardServicesLog(simLog)) { hasLogged = true; // Sim: This app has crashed because it attempted to access privacy-sensitive data without a usage description. The app's Info.plist must contain an NSCameraUsageDescription key with a string value explaining to the user how the app uses this data. _internal().Logger.global.error(formatMessage(simLog)); } } else if (simLog.eventMessage) { var _simLog$source4; // If the source has a file (i.e. not a system log). if ((_simLog$source4 = simLog.source) !== null && _simLog$source4 !== void 0 && _simLog$source4.file || simLog.eventMessage.includes('Terminating app due to uncaught exception')) { hasLogged = true; _internal().Logger.global.info(formatMessage(simLog)); } } if (!hasLogged) { _internal().Logger.global.debug(formatMessage(simLog)); } else { // console.log('DATA:', JSON.stringify(simLog)); } } function isStreamingLogs(udid) { return !!forks[udid]; } // The primary purpose of this module is to surface logs related to fatal app crashes. // Everything else should come through the native React logger. function streamLogs({ pid, udid }) { // Prevent adding a duplicate listener. // This only works because our current usage of SimControlLogs only allows for one possible `pid` to be used. // If in the future, you can attach logs to two different apps from the same process, then this will need to be changed. if (forks[udid]) { return; } // xcrun simctl spawn booted log stream --process --style json const childProcess = (0, _child_process().spawn)('xcrun', ['simctl', 'spawn', udid, 'log', 'stream', '--process', pid, // ndjson provides a better format than json. '--style', 'ndjson', // Provide the source so we can filter logs better '--source', // log, activity, trace -- activity was related to layouts, trace didn't work, so that leaves log. // Passing nothing combines all three, but we don't use activity. '--type', 'log', // backtrace doesn't seem very useful in basic cases. // TODO: Maybe we can format as a stack trace for native errors. '--no-backtrace']); childProcess.stdout.on('data', data => { const simLog = parseMessageJson(data); if (!simLog) { return; } onMessage(simLog); }); childProcess.on('error', ({ message }) => { _internal().Logger.global.debug('[simctl error]:', message); }); forks[udid] = childProcess; // Ensure the process is removed. ensureExitHooksInstalled(); } async function detachStream(udid) { if (forks[udid]) { await killProcess(forks[udid]); delete forks[udid]; } } let hasInstalledExitHooks = false; function ensureExitHooksInstalled() { if (hasInstalledExitHooks) return; hasInstalledExitHooks = true; const killSignals = ['SIGINT', 'SIGTERM']; for (const signal of killSignals) { process.on(signal, async () => { await Promise.all(Object.keys(forks).map(udid => detachStream(udid))); }); } } async function killProcess(childProcess) { if (childProcess) { return new Promise(resolve => { childProcess.on('close', resolve); childProcess.kill(); }); } } /** * * @param udid * @param bundleIdentifier * @returns Image name like `Exponent` and `null` when the app is not installed on the provided simulator. */ async function getImageNameFromBundleIdentifierAsync(udid, bundleIdentifier) { const containerPath = await _internal().SimControl.getContainerPathAsync({ udid, bundleIdentifier }); if (containerPath) { return getImageNameFromContainerPath(containerPath); } return null; } function getImageNameFromContainerPath(binaryPath) { return _path().default.basename(binaryPath).split('.')[0]; } //# sourceMappingURL=SimControlLogs.js.map