@samitouri / QOS-React / commits / 344bc8128b

refactor[Agent/Store]: Store to send messages only after Agent is initialized (#30945)

Both for browser extension, and for React Native (as part of `react-devtools-core`) `Store` is initialized before the Backend (and `Agent` as a part of it): https://github.com/facebook/react/blob/bac33d1f82d9094b45d6f662dd7fa895abab8bce/packages/react-devtools-extensions/src/main/index.js#L111-L113 Any messages that we send from `Store`'s constructor are ignored, because there is nothing on the other end yet. With these changes, `Agent` will send `backendInitialized` message to `Store`, after which `getBackendVersion` and other events will be sent. Note that `isBackendStorageAPISupported` and `isSynchronousXHRSupported` are still sent from `Agent`'s constructor, because we don't explicitly ask for it from `Store`, but these are used. This the pre-requisite for fetching settings and unsupported renderers reliably from the Frontend.

Ruslan Lesiutin committed Sep 11, 2024 at 15:10 UTC 344bc8128bc8f135e3fe6bb3449580d216ec7639
3 files changed +32 -27
packages/react-devtools-shared/src/backend/agent.js
+6 -11
@@ -230,18 +230,16 @@ export default class Agent extends EventEmitter<{
230 bridge.addListener('overrideProps', this.overrideProps);
231 bridge.addListener('overrideState', this.overrideState);
232
233 + setupHighlighter(bridge, this);
234 + setupTraceUpdates(this);
235 +
236 + // By this time, Store should already be initialized and intercept events
237 + bridge.send('backendInitialized');
238 +
239 if (this._isProfiling) {
240 bridge.send('profilingStatus', true);
241 }
242
237 - // Send the Bridge protocol and backend versions, after initialization, in case the frontend has already requested it.
238 - // The Store may be instantiated beore the agent.
239 - const version = process.env.DEVTOOLS_VERSION;
240 - if (version) {
241 - this._bridge.send('backendVersion', version);
242 - }
243 - this._bridge.send('bridgeProtocol', currentBridgeProtocol);
244 -
243 // Notify the frontend if the backend supports the Storage API (e.g. localStorage).
244 // If not, features like reload-and-profile will not work correctly and must be disabled.
245 let isBackendStorageAPISupported = false;
@@ -251,9 +249,6 @@ export default class Agent extends EventEmitter<{
249 } catch (error) {}
250 bridge.send('isBackendStorageAPISupported', isBackendStorageAPISupported);
251 bridge.send('isSynchronousXHRSupported', isSynchronousXHRSupported());
254 -
255 - setupHighlighter(bridge, this);
256 - setupTraceUpdates(this);
252 }
253
254 get rendererInterfaces(): {[key: RendererID]: RendererInterface, ...} {
packages/react-devtools-shared/src/bridge.js
+1
@@ -178,6 +178,7 @@ type SavedPreferencesParams = {
178 };
179
180 export type BackendEvents = {
181 + backendInitialized: [],
182 backendVersion: [string],
183 bridgeProtocol: [BridgeProtocol],
184 extensionBackendInitialized: [],
packages/react-devtools-shared/src/devtools/store.js
+25 -16
@@ -191,6 +191,8 @@ export default class Store extends EventEmitter<{
191 // Used for windowing purposes.
192 _weightAcrossRoots: number = 0;
193
194 + _shouldCheckBridgeProtocolCompatibility: boolean = false;
195 +
196 constructor(bridge: FrontendBridge, config?: Config) {
197 super();
198
@@ -218,6 +220,7 @@ export default class Store extends EventEmitter<{
220 supportsReloadAndProfile,
221 supportsTimeline,
222 supportsTraceUpdates,
223 + checkBridgeProtocolCompatibility,
224 } = config;
225 if (supportsInspectMatchingDOMElement) {
226 this._supportsInspectMatchingDOMElement = true;
@@ -234,6 +237,9 @@ export default class Store extends EventEmitter<{
237 if (supportsTraceUpdates) {
238 this._supportsTraceUpdates = true;
239 }
240 + if (checkBridgeProtocolCompatibility) {
241 + this._shouldCheckBridgeProtocolCompatibility = true;
242 + }
243 }
244
245 this._bridge = bridge;
@@ -262,24 +268,9 @@ export default class Store extends EventEmitter<{
268
269 this._profilerStore = new ProfilerStore(bridge, this, isProfiling);
270
265 - // Verify that the frontend version is compatible with the connected backend.
266 - // See github.com/facebook/react/issues/21326
267 - if (config != null && config.checkBridgeProtocolCompatibility) {
268 - // Older backends don't support an explicit bridge protocol,
269 - // so we should timeout eventually and show a downgrade message.
270 - this._onBridgeProtocolTimeoutID = setTimeout(
271 - this.onBridgeProtocolTimeout,
272 - 10000,
273 - );
274 -
275 - bridge.addListener('bridgeProtocol', this.onBridgeProtocol);
276 - bridge.send('getBridgeProtocol');
277 - }
278 -
271 bridge.addListener('backendVersion', this.onBridgeBackendVersion);
280 - bridge.send('getBackendVersion');
281 -
272 bridge.addListener('saveToClipboard', this.onSaveToClipboard);
273 + bridge.addListener('backendInitialized', this.onBackendInitialized);
274 }
275
276 // This is only used in tests to avoid memory leaks.
@@ -1493,6 +1484,24 @@ export default class Store extends EventEmitter<{
1484 copy(text);
1485 };
1486
1487 + onBackendInitialized: () => void = () => {
1488 + // Verify that the frontend version is compatible with the connected backend.
1489 + // See github.com/facebook/react/issues/21326
1490 + if (this._shouldCheckBridgeProtocolCompatibility) {
1491 + // Older backends don't support an explicit bridge protocol,
1492 + // so we should timeout eventually and show a downgrade message.
1493 + this._onBridgeProtocolTimeoutID = setTimeout(
1494 + this.onBridgeProtocolTimeout,
1495 + 10000,
1496 + );
1497 +
1498 + this._bridge.addListener('bridgeProtocol', this.onBridgeProtocol);
1499 + this._bridge.send('getBridgeProtocol');
1500 + }
1501 +
1502 + this._bridge.send('getBackendVersion');
1503 + };
1504 +
1505 // The Store should never throw an Error without also emitting an event.
1506 // Otherwise Store errors will be invisible to users,
1507 // but the downstream errors they cause will be reported as bugs.