/** * Copyright (c) 2013-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @format * @flow */ 'use strict'; const RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); const Map = require('Map'); const infoLog = require('infoLog'); import type EmitterSubscription from 'EmitterSubscription'; type ExtraData = {[key: string]: string}; type SourceCallback = () => string; type DebugData = {extras: ExtraData, files: ExtraData}; function defaultExtras() { BugReporting.addFileSource('react_hierarchy.txt', () => require('dumpReactTree')(), ); } /** * A simple class for collecting bug report data. Components can add sources that will be queried when a bug report * is created via `collectExtraData`. For example, a list component might add a source that provides the list of rows * that are currently visible on screen. Components should also remember to call `remove()` on the object that is * returned by `addSource` when they are unmounted. */ class BugReporting { static _extraSources: Map = new Map(); static _fileSources: Map = new Map(); static _subscription: ?EmitterSubscription = null; static _redboxSubscription: ?EmitterSubscription = null; static _maybeInit() { if (!BugReporting._subscription) { BugReporting._subscription = RCTDeviceEventEmitter.addListener( 'collectBugExtraData', BugReporting.collectExtraData, null, ); defaultExtras(); } if (!BugReporting._redboxSubscription) { BugReporting._redboxSubscription = RCTDeviceEventEmitter.addListener( 'collectRedBoxExtraData', BugReporting.collectExtraData, null, ); } } /** * Maps a string key to a simple callback that should return a string payload to be attached * to a bug report. Source callbacks are called when `collectExtraData` is called. * * Returns an object to remove the source when the component unmounts. * * Conflicts trample with a warning. */ static addSource( key: string, callback: SourceCallback, ): {remove: () => void} { return this._addSource(key, callback, BugReporting._extraSources); } /** * Maps a string key to a simple callback that should return a string payload to be attached * to a bug report. Source callbacks are called when `collectExtraData` is called. * * Returns an object to remove the source when the component unmounts. * * Conflicts trample with a warning. */ static addFileSource( key: string, callback: SourceCallback, ): {remove: () => void} { return this._addSource(key, callback, BugReporting._fileSources); } static _addSource( key: string, callback: SourceCallback, source: Map, ): {remove: () => void} { BugReporting._maybeInit(); if (source.has(key)) { console.warn( `BugReporting.add* called multiple times for same key '${key}'`, ); } source.set(key, callback); return { remove: () => { source.delete(key); }, }; } /** * This can be called from a native bug reporting flow, or from JS code. * * If available, this will call `NativeModules.BugReporting.setExtraData(extraData)` * after collecting `extraData`. */ static collectExtraData(): DebugData { const extraData: ExtraData = {}; for (const [key, callback] of BugReporting._extraSources) { extraData[key] = callback(); } const fileData: ExtraData = {}; for (const [key, callback] of BugReporting._fileSources) { fileData[key] = callback(); } infoLog('BugReporting extraData:', extraData); const BugReportingNativeModule = require('NativeModules').BugReporting; BugReportingNativeModule && BugReportingNativeModule.setExtraData && BugReportingNativeModule.setExtraData(extraData, fileData); const RedBoxNativeModule = require('NativeModules').RedBox; RedBoxNativeModule && RedBoxNativeModule.setExtraData && RedBoxNativeModule.setExtraData(extraData, 'From BugReporting.js'); return {extras: extraData, files: fileData}; } } module.exports = BugReporting;