55
66import * as SDK from '../../core/sdk/sdk.js' ;
77import * as ReactNativeModels from '../../models/react_native/react_native.js' ;
8+ import * as ReactDevTools from '../../third_party/react-devtools/react-devtools.js' ;
89
910import type * as ReactDevToolsTypes from '../../third_party/react-devtools/react-devtools.js' ;
1011import type * as Common from '../../core/common/common.js' ;
@@ -13,14 +14,12 @@ export const enum Events {
1314 InitializationCompleted = 'InitializationCompleted' ,
1415 InitializationFailed = 'InitializationFailed' ,
1516 Destroyed = 'Destroyed' ,
16- MessageReceived = 'MessageReceived' ,
1717}
1818
1919export type EventTypes = {
2020 [ Events . InitializationCompleted ] : void ,
2121 [ Events . InitializationFailed ] : string ,
2222 [ Events . Destroyed ] : void ,
23- [ Events . MessageReceived ] : ReactDevToolsTypes . Message ,
2423} ;
2524
2625type ReactDevToolsBindingsBackendExecutionContextUnavailableEvent = Common . EventTarget . EventTargetEvent <
@@ -31,91 +30,149 @@ type ReactDevToolsBindingsBackendExecutionContextUnavailableEvent = Common.Event
3130
3231export class ReactDevToolsModel extends SDK . SDKModel . SDKModel < EventTypes > {
3332 private static readonly FUSEBOX_BINDING_NAMESPACE = 'react-devtools' ;
34- private readonly rdtBindingsModel : ReactNativeModels . ReactDevToolsBindingsModel . ReactDevToolsBindingsModel | null ;
33+
34+ readonly #wall: ReactDevToolsTypes . Wall ;
35+ readonly #bindingsModel: ReactNativeModels . ReactDevToolsBindingsModel . ReactDevToolsBindingsModel ;
36+ readonly #listeners: Set < ReactDevToolsTypes . WallListener > = new Set ( ) ;
37+ #initializeCalled: boolean = false ;
38+ #initialized: boolean = false ;
39+ #bridge: ReactDevToolsTypes . Bridge | null ;
40+ #store: ReactDevToolsTypes . Store | null ;
3541
3642 constructor ( target : SDK . Target . Target ) {
3743 super ( target ) ;
3844
39- const rdtBindingsModel = target . model ( ReactNativeModels . ReactDevToolsBindingsModel . ReactDevToolsBindingsModel ) ;
40- if ( ! rdtBindingsModel ) {
45+ this . #wall = {
46+ listen : ( listener ) : Function => {
47+ this . #listeners. add ( listener ) ;
48+
49+ return ( ) : void => {
50+ this . #listeners. delete ( listener ) ;
51+ } ;
52+ } ,
53+ send : ( event , payload ) : void => void this . #sendMessage( { event, payload} ) ,
54+ } ;
55+ this . #bridge = ReactDevTools . createBridge ( this . #wall) ;
56+ this . #store = ReactDevTools . createStore ( this . #bridge) ;
57+
58+ const bindingsModel = target . model ( ReactNativeModels . ReactDevToolsBindingsModel . ReactDevToolsBindingsModel ) ;
59+ if ( bindingsModel == null ) {
4160 throw new Error ( 'Failed to construct ReactDevToolsModel: ReactDevToolsBindingsModel was null' ) ;
4261 }
4362
44- this . rdtBindingsModel = rdtBindingsModel ;
45-
46- rdtBindingsModel . addEventListener ( ReactNativeModels . ReactDevToolsBindingsModel . Events . BackendExecutionContextCreated , this . onBackendExecutionContextCreated , this ) ;
47- rdtBindingsModel . addEventListener ( ReactNativeModels . ReactDevToolsBindingsModel . Events . BackendExecutionContextUnavailable , this . onBackendExecutionContextUnavailable , this ) ;
48- rdtBindingsModel . addEventListener ( ReactNativeModels . ReactDevToolsBindingsModel . Events . BackendExecutionContextDestroyed , this . onBackendExecutionContextDestroyed , this ) ;
63+ this . #bindingsModel = bindingsModel ;
4964
50- void this . initialize ( rdtBindingsModel ) ;
51- }
65+ bindingsModel . addEventListener (
66+ ReactNativeModels . ReactDevToolsBindingsModel . Events . BackendExecutionContextCreated ,
67+ this . #handleBackendExecutionContextCreated,
68+ this ,
69+ ) ;
70+ bindingsModel . addEventListener (
71+ ReactNativeModels . ReactDevToolsBindingsModel . Events . BackendExecutionContextUnavailable ,
72+ this . #handleBackendExecutionContextUnavailable,
73+ this ,
74+ ) ;
75+ bindingsModel . addEventListener (
76+ ReactNativeModels . ReactDevToolsBindingsModel . Events . BackendExecutionContextDestroyed ,
77+ this . #handleBackendExecutionContextDestroyed,
78+ this ,
79+ ) ;
5280
53- private async initialize ( rdtBindingsModel : ReactNativeModels . ReactDevToolsBindingsModel . ReactDevToolsBindingsModel ) : Promise < void > {
54- return rdtBindingsModel . enable ( )
55- . then ( ( ) => this . onBindingsModelInitializationCompleted ( ) )
56- . catch ( ( error : Error ) => this . onBindingsModelInitializationFailed ( error ) ) ;
81+ // Notify backend if Chrome DevTools was closed, marking frontend as disconnected
82+ window . addEventListener ( 'beforeunload' , ( ) => this . #bridge?. shutdown ( ) ) ;
5783 }
5884
59- private onBindingsModelInitializationCompleted ( ) : void {
60- const rdtBindingsModel = this . rdtBindingsModel ;
61- if ( ! rdtBindingsModel ) {
62- throw new Error ( 'Failed to initialize ReactDevToolsModel: ReactDevToolsBindingsModel was null' ) ;
85+ async ensureInitialized ( ) : Promise < void > {
86+ if ( this . #initializeCalled) {
87+ return ;
6388 }
6489
65- rdtBindingsModel . subscribeToDomainMessages (
66- ReactDevToolsModel . FUSEBOX_BINDING_NAMESPACE ,
67- message => this . onMessage ( message as ReactDevToolsTypes . Message ) ,
68- ) ;
90+ this . #initializeCalled = true ;
91+
92+ try {
93+ const bindingsModel = this . #bindingsModel;
94+ await bindingsModel . enable ( ) ;
95+
96+ bindingsModel . subscribeToDomainMessages (
97+ ReactDevToolsModel . FUSEBOX_BINDING_NAMESPACE ,
98+ message => this . #handleMessage( message as ReactDevToolsTypes . Message ) ,
99+ ) ;
100+
101+ await bindingsModel . initializeDomain ( ReactDevToolsModel . FUSEBOX_BINDING_NAMESPACE ) ;
69102
70- void rdtBindingsModel . initializeDomain ( ReactDevToolsModel . FUSEBOX_BINDING_NAMESPACE )
71- . then ( ( ) => this . onDomainInitializationCompleted ( ) )
72- . catch ( ( error : Error ) => this . onDomainInitializationFailed ( error ) ) ;
103+ this . #initialized = true ;
104+ this . dispatchEventToListeners ( Events . InitializationCompleted ) ;
105+ } catch ( e ) {
106+ this . dispatchEventToListeners ( Events . InitializationFailed , e . message ) ;
107+ }
73108 }
74109
75- private onBindingsModelInitializationFailed ( error : Error ) : void {
76- this . dispatchEventToListeners ( Events . InitializationFailed , error . message ) ;
110+ isInitialized ( ) : boolean {
111+ return this . #initialized ;
77112 }
78113
79- private onDomainInitializationCompleted ( ) : void {
80- this . dispatchEventToListeners ( Events . InitializationCompleted ) ;
114+ getBridgeOrThrow ( ) : ReactDevToolsTypes . Bridge {
115+ if ( this . #bridge == null ) {
116+ throw new Error ( 'Failed to get bridge from ReactDevToolsModel: bridge was null' ) ;
117+ }
118+
119+ return this . #bridge;
81120 }
82121
83- private onDomainInitializationFailed ( error : Error ) : void {
84- this . dispatchEventToListeners ( Events . InitializationFailed , error . message ) ;
122+ getStoreOrThrow ( ) : ReactDevToolsTypes . Store {
123+ if ( this . #store == null ) {
124+ throw new Error ( 'Failed to get store from ReactDevToolsModel: store was null' ) ;
125+ }
126+
127+ return this . #store;
85128 }
86129
87- private onMessage ( message : ReactDevToolsTypes . Message ) : void {
88- this . dispatchEventToListeners ( Events . MessageReceived , message ) ;
130+ #handleMessage( message : ReactDevToolsTypes . Message ) : void {
131+ if ( ! message ) {
132+ return ;
133+ }
134+
135+ for ( const listener of this . #listeners) {
136+ listener ( message ) ;
137+ }
89138 }
90139
91- async sendMessage ( message : ReactDevToolsTypes . Message ) : Promise < void > {
92- const rdtBindingsModel = this . rdtBindingsModel ;
140+ async # sendMessage( message : ReactDevToolsTypes . Message ) : Promise < void > {
141+ const rdtBindingsModel = this . #bindingsModel ;
93142 if ( ! rdtBindingsModel ) {
94143 throw new Error ( 'Failed to send message from ReactDevToolsModel: ReactDevToolsBindingsModel was null' ) ;
95144 }
96145
97146 return rdtBindingsModel . sendMessage ( ReactDevToolsModel . FUSEBOX_BINDING_NAMESPACE , message ) ;
98147 }
99148
100- private onBackendExecutionContextCreated ( ) : void {
101- const rdtBindingsModel = this . rdtBindingsModel ;
149+ #handleBackendExecutionContextCreated ( ) : void {
150+ const rdtBindingsModel = this . #bindingsModel ;
102151 if ( ! rdtBindingsModel ) {
103152 throw new Error ( 'ReactDevToolsModel failed to handle BackendExecutionContextCreated event: ReactDevToolsBindingsModel was null' ) ;
104153 }
105154
106- // This could happen if the app was reloaded while ReactDevToolsBindingsModel was initialing
155+ this . #bridge = ReactDevTools . createBridge ( this . #wall) ;
156+ this . #store = ReactDevTools . createStore ( this . #bridge) ;
157+
158+ // This could happen if the app was reloaded while ReactDevToolsBindingsModel was initializing
107159 if ( ! rdtBindingsModel . isEnabled ( ) ) {
108- void this . initialize ( rdtBindingsModel ) ;
160+ void this . ensureInitialized ( ) ;
109161 } else {
110162 this . dispatchEventToListeners ( Events . InitializationCompleted ) ;
111163 }
112164 }
113165
114- private onBackendExecutionContextUnavailable ( { data : errorMessage } : ReactDevToolsBindingsBackendExecutionContextUnavailableEvent ) : void {
166+ #handleBackendExecutionContextUnavailable ( { data : errorMessage } : ReactDevToolsBindingsBackendExecutionContextUnavailableEvent ) : void {
115167 this . dispatchEventToListeners ( Events . InitializationFailed , errorMessage ) ;
116168 }
117169
118- private onBackendExecutionContextDestroyed ( ) : void {
170+ #handleBackendExecutionContextDestroyed( ) : void {
171+ this . #bridge?. shutdown ( ) ;
172+ this . #bridge = null ;
173+ this . #store = null ;
174+ this . #listeners. clear ( ) ;
175+
119176 this . dispatchEventToListeners ( Events . Destroyed ) ;
120177 }
121178}
0 commit comments