BugReporting.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. /**
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. *
  7. * @format
  8. * @flow strict-local
  9. */
  10. 'use strict';
  11. const RCTDeviceEventEmitter = require('../EventEmitter/RCTDeviceEventEmitter');
  12. import type EmitterSubscription from '../vendor/emitter/EmitterSubscription';
  13. import NativeBugReporting from './NativeBugReporting';
  14. import NativeRedBox from '../NativeModules/specs/NativeRedBox';
  15. type ExtraData = {[key: string]: string, ...};
  16. type SourceCallback = () => string;
  17. type DebugData = {
  18. extras: ExtraData,
  19. files: ExtraData,
  20. ...
  21. };
  22. function defaultExtras() {
  23. BugReporting.addFileSource('react_hierarchy.txt', () =>
  24. require('./dumpReactTree')(),
  25. );
  26. }
  27. /**
  28. * A simple class for collecting bug report data. Components can add sources that will be queried when a bug report
  29. * is created via `collectExtraData`. For example, a list component might add a source that provides the list of rows
  30. * that are currently visible on screen. Components should also remember to call `remove()` on the object that is
  31. * returned by `addSource` when they are unmounted.
  32. */
  33. class BugReporting {
  34. static _extraSources: Map<string, SourceCallback> = new Map();
  35. static _fileSources: Map<string, SourceCallback> = new Map();
  36. static _subscription: ?EmitterSubscription = null;
  37. static _redboxSubscription: ?EmitterSubscription = null;
  38. static _maybeInit() {
  39. if (!BugReporting._subscription) {
  40. BugReporting._subscription = RCTDeviceEventEmitter.addListener(
  41. 'collectBugExtraData',
  42. BugReporting.collectExtraData,
  43. null,
  44. );
  45. defaultExtras();
  46. }
  47. if (!BugReporting._redboxSubscription) {
  48. BugReporting._redboxSubscription = RCTDeviceEventEmitter.addListener(
  49. 'collectRedBoxExtraData',
  50. BugReporting.collectExtraData,
  51. null,
  52. );
  53. }
  54. }
  55. /**
  56. * Maps a string key to a simple callback that should return a string payload to be attached
  57. * to a bug report. Source callbacks are called when `collectExtraData` is called.
  58. *
  59. * Returns an object to remove the source when the component unmounts.
  60. *
  61. * Conflicts trample with a warning.
  62. */
  63. static addSource(
  64. key: string,
  65. callback: SourceCallback,
  66. ): {remove: () => void, ...} {
  67. return this._addSource(key, callback, BugReporting._extraSources);
  68. }
  69. /**
  70. * Maps a string key to a simple callback that should return a string payload to be attached
  71. * to a bug report. Source callbacks are called when `collectExtraData` is called.
  72. *
  73. * Returns an object to remove the source when the component unmounts.
  74. *
  75. * Conflicts trample with a warning.
  76. */
  77. static addFileSource(
  78. key: string,
  79. callback: SourceCallback,
  80. ): {remove: () => void, ...} {
  81. return this._addSource(key, callback, BugReporting._fileSources);
  82. }
  83. static _addSource(
  84. key: string,
  85. callback: SourceCallback,
  86. source: Map<string, SourceCallback>,
  87. ): {remove: () => void, ...} {
  88. BugReporting._maybeInit();
  89. if (source.has(key)) {
  90. console.warn(
  91. `BugReporting.add* called multiple times for same key '${key}'`,
  92. );
  93. }
  94. source.set(key, callback);
  95. return {
  96. remove: () => {
  97. source.delete(key);
  98. },
  99. };
  100. }
  101. /**
  102. * This can be called from a native bug reporting flow, or from JS code.
  103. *
  104. * If available, this will call `NativeModules.BugReporting.setExtraData(extraData)`
  105. * after collecting `extraData`.
  106. */
  107. static collectExtraData(): DebugData {
  108. const extraData: ExtraData = {};
  109. for (const [key, callback] of BugReporting._extraSources) {
  110. extraData[key] = callback();
  111. }
  112. const fileData: ExtraData = {};
  113. for (const [key, callback] of BugReporting._fileSources) {
  114. fileData[key] = callback();
  115. }
  116. if (NativeBugReporting != null && NativeBugReporting.setExtraData != null) {
  117. NativeBugReporting.setExtraData(extraData, fileData);
  118. }
  119. if (NativeRedBox != null && NativeRedBox.setExtraData != null) {
  120. NativeRedBox.setExtraData(extraData, 'From BugReporting.js');
  121. }
  122. return {extras: extraData, files: fileData};
  123. }
  124. }
  125. module.exports = BugReporting;