react-native-debug-toolkit 3.3.2 → 3.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,87 +1,220 @@
1
+ #import "DebugToolkitDevConnect.h"
2
+
1
3
  #import <Foundation/Foundation.h>
4
+ #import <UIKit/UIKit.h>
2
5
  #import <React/RCTBridge.h>
3
6
  #import <React/RCTBundleManager.h>
4
7
  #import <React/RCTBridgeModule.h>
5
8
  #import <React/RCTBundleURLProvider.h>
6
- #import <React/RCTDefines.h>
7
9
  #import <React/RCTReloadCommand.h>
8
10
  #import <objc/runtime.h>
9
11
  #include <ifaddrs.h>
10
12
  #include <arpa/inet.h>
11
13
  #include <net/if.h>
12
14
 
13
- static NSString *const DebugToolkitBundleRoot = @"index";
14
- static NSString *const kDevConnectMetroHost = @"_devconnect_metro_host";
15
- static NSString *const kDevConnectDiagLog = @"_devconnect_diag_log";
15
+ // Debug-only Metro host switching — zero host-app native changes required.
16
+ //
17
+ // RN/Expo Debug templates call jsBundleURLForBundleRoot:fallbackURLProvider:, which consults
18
+ // -packagerServerHostPort (guessPackagerHost on simulator when Metro is running). We hook:
19
+ // 1. jsBundleURLForBundleRoot:fallbackURLProvider: — no DevConnect host → main.jsbundle first.
20
+ // 2. packagerServerHostPort — no DevConnect host → nil (block auto-discovered localhost:8081).
21
+ //
22
+ // Optional: host apps may call DebugToolkitMetroBundleURL() from bundleURL() for explicit control.
23
+
24
+ static NSString *const kBundleRoot = @"index";
25
+ // Expo prebuild templates pass this to jsBundleURLForBundleRoot: in Debug (see expo/expo#21643).
26
+ static NSString *const kExpoVirtualMetroEntry = @".expo/.virtual-metro-entry";
27
+ static NSString *const kMetroHostKey = @"_devconnect_metro_host";
28
+
29
+ static BOOL gPackagerHookInstalled = NO;
30
+ static BOOL gBundleRootHookInstalled = NO;
16
31
 
17
- // Forward-declared so appendDiag can reference them before the definitions below.
18
- static IMP original_sourceURLForBridge;
19
- static BOOL swizzleInvoked;
32
+ static NSURL *(*gOrigJsBundleURLForBundleRootWithFallback)(id, SEL, NSString *, NSURL * _Nonnull (^)(void));
20
33
 
21
- static void appendDiag(NSString *stage, NSString *detail)
34
+ static BOOL DevConnectEmbeddedFirstHooksActive(void)
22
35
  {
23
- NSString *msg = [NSString stringWithFormat:@"[%@] %@ | installed=%@ invoked=%@",
24
- stage, detail,
25
- original_sourceURLForBridge ? @"YES" : @"NO",
26
- swizzleInvoked ? @"YES" : @"NO"];
27
- NSMutableArray *log = [[[NSUserDefaults standardUserDefaults] arrayForKey:kDevConnectDiagLog] mutableCopy]
28
- ?: [NSMutableArray new];
29
- [log addObject:msg];
30
- if (log.count > 30) [log removeObjectAtIndex:0];
31
- [[NSUserDefaults standardUserDefaults] setObject:[log copy] forKey:kDevConnectDiagLog];
32
- [[NSUserDefaults standardUserDefaults] synchronize];
33
- NSLog(@"[DevConnect] diag: %@", msg);
36
+ return gBundleRootHookInstalled;
34
37
  }
35
38
 
36
- #pragma mark - AppDelegate Swizzling
39
+ static NSURL *DebugToolkitEmbeddedBundleURL(void)
40
+ {
41
+ return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
42
+ }
37
43
 
38
- static NSURL *devconnect_sourceURLForBridge(id self, SEL _cmd, RCTBridge *bridge)
44
+ static NSString *DevConnectPersistedMetroHost(void)
39
45
  {
40
- swizzleInvoked = YES;
41
- NSString *metroHost = [[NSUserDefaults standardUserDefaults] stringForKey:kDevConnectMetroHost];
42
- if (metroHost.length > 0) {
43
- NSString *urlStr = [NSString stringWithFormat:
44
- @"http://%@/%@.bundle?platform=ios&dev=true&minify=false&lazy=true", metroHost, DebugToolkitBundleRoot];
45
- NSURL *url = [NSURL URLWithString:urlStr];
46
- appendDiag(@"swizzle-called", [NSString stringWithFormat:@"host=%@ url=%@", metroHost, url]);
47
- return url;
46
+ return [[NSUserDefaults standardUserDefaults] stringForKey:kMetroHostKey];
47
+ }
48
+
49
+ static void DevConnectSetPersistedMetroHost(NSString *_Nullable hostPort)
50
+ {
51
+ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
52
+ if (hostPort.length > 0) {
53
+ [defaults setObject:hostPort forKey:kMetroHostKey];
54
+ } else {
55
+ [defaults removeObjectForKey:kMetroHostKey];
48
56
  }
49
- if (original_sourceURLForBridge) {
50
- return ((NSURL *(*)(id, SEL, RCTBridge *))original_sourceURLForBridge)(self, _cmd, bridge);
57
+ [defaults synchronize];
58
+ }
59
+
60
+ static void DebugToolkitPrepareBundleSourceIfNeeded(void)
61
+ {
62
+ // Touch sharedSettings so RCTBundleURLProvider is linked before hook install retries.
63
+ RCTBundleURLProvider *settings = [RCTBundleURLProvider sharedSettings];
64
+ if (DevConnectPersistedMetroHost().length > 0) {
65
+ return;
51
66
  }
52
- return nil;
67
+ [settings resetToDefaults];
68
+ }
69
+
70
+ static BOOL DevConnectIsExpoProject(void)
71
+ {
72
+ static dispatch_once_t onceToken;
73
+ static BOOL isExpo = NO;
74
+ dispatch_once(&onceToken, ^{
75
+ // Heuristic: matches common Expo prebuild / dev-client binaries.
76
+ isExpo = NSClassFromString(@"EXAppDelegateWrapper") != nil
77
+ || [[NSBundle mainBundle] objectForInfoDictionaryKey:@"EXUpdatesURL"] != nil
78
+ || [[NSBundle mainBundle] objectForInfoDictionaryKey:@"EXPO_RUNTIME_VERSION"] != nil;
79
+ });
80
+ return isExpo;
81
+ }
82
+
83
+ /// Bundle root Metro expects when DevConnect steers the packager (index for plain RN, virtual entry for Expo).
84
+ static NSString *DevConnectMetroBundleRoot(void)
85
+ {
86
+ return DevConnectIsExpoProject() ? kExpoVirtualMetroEntry : kBundleRoot;
87
+ }
88
+
89
+ static NSURL *DevConnectMetroURLForPersistedHost(void)
90
+ {
91
+ NSString *host = DevConnectPersistedMetroHost();
92
+ if (host.length == 0) {
93
+ return nil;
94
+ }
95
+ RCTBundleURLProvider *settings = [RCTBundleURLProvider sharedSettings];
96
+ settings.jsLocation = host;
97
+ return [settings jsBundleURLForBundleRoot:DevConnectMetroBundleRoot()];
98
+ }
99
+
100
+ /// When no DevConnect host: block guessPackagerHost (simulator often has Metro on :8081).
101
+ static NSString *replacement_packagerServerHostPort(id self, SEL _cmd)
102
+ {
103
+ NSString *host = DevConnectPersistedMetroHost();
104
+ if (host.length == 0) {
105
+ return nil;
106
+ }
107
+ [(RCTBundleURLProvider *)self setJsLocation:host];
108
+ return host;
109
+ }
110
+
111
+ /// Primary hook: return embedded main.jsbundle before RN/Expo tries Metro / virtual-metro-entry.
112
+ static NSURL *replacement_jsBundleURLForBundleRoot_fallback(
113
+ id self, SEL _cmd, NSString *bundleRoot, NSURL * _Nonnull (^fallbackURLProvider)(void))
114
+ {
115
+ if (DevConnectPersistedMetroHost().length == 0) {
116
+ NSURL *embedded = DebugToolkitEmbeddedBundleURL();
117
+ if (embedded) {
118
+ NSLog(@"[DevConnect] cold start → embedded bundle (root=%@)", bundleRoot);
119
+ return embedded;
120
+ }
121
+ NSLog(@"[DevConnect] no embedded main.jsbundle — falling back to Metro (root=%@)", bundleRoot);
122
+ }
123
+
124
+ if (gOrigJsBundleURLForBundleRootWithFallback) {
125
+ return gOrigJsBundleURLForBundleRootWithFallback(self, _cmd, bundleRoot, fallbackURLProvider);
126
+ }
127
+ return fallbackURLProvider ? fallbackURLProvider() : nil;
53
128
  }
54
129
 
55
- static void swizzleSourceURLForBridge(Class targetClass, NSString *stage)
130
+ static void DebugToolkitInstallPackagerHook(Class cls)
56
131
  {
57
- if (!targetClass) {
58
- appendDiag(stage, @"class=nil");
132
+ if (gPackagerHookInstalled) {
59
133
  return;
60
134
  }
61
- NSString *className = NSStringFromClass(targetClass);
62
- SEL selector = @selector(sourceURLForBridge:);
63
- Method method = class_getInstanceMethod(targetClass, selector);
135
+ Method method = class_getInstanceMethod(cls, @selector(packagerServerHostPort));
64
136
  if (!method) {
65
- appendDiag(stage, [NSString stringWithFormat:@"class=%@ method=NOT_FOUND", className]);
137
+ NSLog(@"[DevConnect] packagerServerHostPort not found");
66
138
  return;
67
139
  }
68
- if (original_sourceURLForBridge) {
69
- appendDiag(stage, [NSString stringWithFormat:@"class=%@ already_swizzled", className]);
140
+ IMP replacement = (IMP)replacement_packagerServerHostPort;
141
+ if (method_getImplementation(method) == replacement) {
142
+ gPackagerHookInstalled = YES;
70
143
  return;
71
144
  }
72
- // class_addMethod ensures the replacement lands on targetClass even when the method
73
- // is inherited — avoids modifying the parent class's IMP for all subclasses.
74
- IMP origIMP = method_getImplementation(method);
75
- const char *types = method_getTypeEncoding(method);
76
- BOOL added = class_addMethod(targetClass, selector, (IMP)devconnect_sourceURLForBridge, types);
77
- if (!added) {
78
- // Method already exists directly on targetClass; replace in place.
79
- method_setImplementation(method, (IMP)devconnect_sourceURLForBridge);
145
+ method_setImplementation(method, replacement);
146
+ gPackagerHookInstalled = YES;
147
+ }
148
+
149
+ static void DebugToolkitInstallBundleRootHook(Class cls)
150
+ {
151
+ if (gBundleRootHookInstalled) {
152
+ return;
153
+ }
154
+ SEL selector = @selector(jsBundleURLForBundleRoot:fallbackURLProvider:);
155
+ Method method = class_getInstanceMethod(cls, selector);
156
+ if (!method) {
157
+ NSLog(@"[DevConnect] jsBundleURLForBundleRoot:fallbackURLProvider: not found");
158
+ return;
80
159
  }
81
- original_sourceURLForBridge = origIMP;
82
- appendDiag(stage, [NSString stringWithFormat:@"class=%@ added=%@ SUCCESS", className, added ? @"YES" : @"NO"]);
160
+ IMP replacement = (IMP)replacement_jsBundleURLForBundleRoot_fallback;
161
+ gOrigJsBundleURLForBundleRootWithFallback =
162
+ (NSURL * (*)(id, SEL, NSString *, NSURL * _Nonnull (^)(void)))method_getImplementation(method);
163
+ if ((IMP)gOrigJsBundleURLForBundleRootWithFallback == replacement) {
164
+ gBundleRootHookInstalled = YES;
165
+ return;
166
+ }
167
+ method_setImplementation(method, replacement);
168
+ gBundleRootHookInstalled = YES;
83
169
  }
84
170
 
171
+ static void DebugToolkitInstallAllHooks(void)
172
+ {
173
+ if (gBundleRootHookInstalled && gPackagerHookInstalled) {
174
+ return;
175
+ }
176
+
177
+ Class cls = NSClassFromString(@"RCTBundleURLProvider");
178
+ if (!cls) {
179
+ NSLog(@"[DevConnect] RCTBundleURLProvider not loaded — hooks will retry");
180
+ return;
181
+ }
182
+
183
+ DebugToolkitInstallBundleRootHook(cls);
184
+ DebugToolkitInstallPackagerHook(cls);
185
+
186
+ static BOOL didLogOutcome = NO;
187
+ if (!didLogOutcome && (gBundleRootHookInstalled || gPackagerHookInstalled)) {
188
+ didLogOutcome = YES;
189
+ if (DevConnectEmbeddedFirstHooksActive()) {
190
+ NSLog(@"[DevConnect] embedded-first hooks active (bundleRoot=%@ packager=%@)",
191
+ gBundleRootHookInstalled ? @"Y" : @"N",
192
+ gPackagerHookInstalled ? @"Y" : @"N");
193
+ } else {
194
+ NSLog(@"[DevConnect] embedded-first hooks FAILED — rebuild / check React linkage");
195
+ }
196
+ }
197
+ }
198
+
199
+ NSURL *DebugToolkitMetroBundleURL(void)
200
+ {
201
+ return DevConnectMetroURLForPersistedHost();
202
+ }
203
+
204
+ // RCT_EXPORT_MODULE defines +load for module registration — use a separate class for hooks.
205
+ @interface DebugToolkitDevConnectBootstrap : NSObject
206
+ @end
207
+
208
+ @implementation DebugToolkitDevConnectBootstrap
209
+
210
+ + (void)load
211
+ {
212
+ DebugToolkitPrepareBundleSourceIfNeeded();
213
+ DebugToolkitInstallAllHooks();
214
+ }
215
+
216
+ @end
217
+
85
218
  #pragma mark - Module
86
219
 
87
220
  @interface DebugToolkitDevConnect : NSObject <RCTBridgeModule>
@@ -94,63 +227,64 @@ RCT_EXPORT_MODULE(DebugToolkitDevConnect)
94
227
  @synthesize bridge = _bridge;
95
228
  @synthesize bundleManager = _bundleManager;
96
229
 
97
- __attribute__((constructor))
98
- static void devconnect_swizzle_init(void)
99
- {
100
- swizzleSourceURLForBridge(NSClassFromString(@"AppDelegate"), @"constructor");
101
- }
102
-
103
230
  - (instancetype)init
104
231
  {
105
232
  if ((self = [super init])) {
106
- if (!original_sourceURLForBridge) {
107
- Class delegateClass = object_getClass([UIApplication sharedApplication].delegate);
108
- swizzleSourceURLForBridge(delegateClass, @"init");
109
- }
233
+ DebugToolkitInstallAllHooks();
110
234
  }
111
235
  return self;
112
236
  }
113
237
 
114
- + (BOOL)requiresMainQueueSetup
115
- {
116
- return NO;
117
- }
238
+ + (BOOL)requiresMainQueueSetup { return NO; }
239
+ - (dispatch_queue_t)methodQueue { return dispatch_get_main_queue(); }
240
+
241
+ #pragma mark - Bundle Manager Resolution
118
242
 
119
- - (dispatch_queue_t)methodQueue
243
+ - (RCTBundleManager *)resolveBundleManager
120
244
  {
121
- return dispatch_get_main_queue();
245
+ if (_bundleManager) return _bundleManager;
246
+ if (_bridge) return [_bridge moduleForClass:[RCTBundleManager class]];
247
+ return nil;
122
248
  }
123
249
 
124
- #pragma mark - Bundle Manager Resolution
250
+ #pragma mark - Host Parsing
125
251
 
126
- - (RCTBundleManager *)resolveBundleManager
252
+ - (NSString *)normalizeHostPort:(NSString *)hostPort
127
253
  {
128
- if (_bundleManager) {
129
- return _bundleManager;
130
- }
131
- if (_bridge) {
132
- return [_bridge moduleForClass:[RCTBundleManager class]];
254
+ NSRange sep = [hostPort rangeOfString:@":" options:NSBackwardsSearch];
255
+ NSString *host = sep.location == NSNotFound ? hostPort : [hostPort substringToIndex:sep.location];
256
+ NSString *portStr = sep.location == NSNotFound ? @"" : [hostPort substringFromIndex:sep.location + 1];
257
+
258
+ NSNumberFormatter *formatter = [NSNumberFormatter new];
259
+ formatter.numberStyle = NSNumberFormatterDecimalStyle;
260
+ NSNumber *parsed = [formatter numberFromString:portStr];
261
+ int port;
262
+ if (parsed && parsed.intValue > 0 && parsed.intValue <= 65535) {
263
+ port = parsed.intValue;
264
+ } else {
265
+ #ifdef RCT_METRO_PORT
266
+ port = RCT_METRO_PORT;
267
+ #else
268
+ port = 8081;
269
+ #endif
133
270
  }
134
- return nil;
271
+ return [NSString stringWithFormat:@"%@:%d", host, port];
135
272
  }
136
273
 
137
- #pragma mark - Exported Methods
274
+ #pragma mark - Reload helper
138
275
 
139
- RCT_EXPORT_METHOD(getMetroHost:(RCTPromiseResolveBlock)resolve
140
- rejecter:(__unused RCTPromiseRejectBlock)reject)
276
+ - (void)reloadWithBundleURL:(NSURL *)bundleURL reason:(NSString *)reason
141
277
  {
142
- @try {
143
- // kDevConnectMetroHost is written by applyMetroHost and survives restart in both Debug+Release.
144
- // jsLocation is only meaningful in Debug (bypassed in Release); use it as a fallback only.
145
- NSString *persisted = [[NSUserDefaults standardUserDefaults] stringForKey:kDevConnectMetroHost];
146
- NSString *jsLocation = [RCTBundleURLProvider sharedSettings].jsLocation;
147
- NSString *host = persisted.length > 0 ? persisted : jsLocation;
148
- resolve(host ?: [NSNull null]);
149
- } @catch (NSException *exception) {
150
- reject(@"native_error", exception.reason, nil);
278
+ if (bundleURL) {
279
+ RCTBundleManager *bm = [self resolveBundleManager];
280
+ if (bm) bm.bundleURL = bundleURL;
281
+ RCTReloadCommandSetBundleURL(bundleURL);
151
282
  }
283
+ RCTTriggerReloadCommandListeners(reason);
152
284
  }
153
285
 
286
+ #pragma mark - Exported Methods
287
+
154
288
  RCT_EXPORT_METHOD(applyMetroHost:(NSString *)hostPort
155
289
  resolver:(RCTPromiseResolveBlock)resolve
156
290
  rejecter:(RCTPromiseRejectBlock)reject)
@@ -160,71 +294,24 @@ RCT_EXPORT_METHOD(applyMetroHost:(NSString *)hostPort
160
294
  reject(@"invalid_host", @"Metro host cannot be empty.", nil);
161
295
  return;
162
296
  }
297
+ NSString *normalized = [self normalizeHostPort:hostPort];
163
298
 
164
- RCTBundleURLProvider *settings = [RCTBundleURLProvider sharedSettings];
165
-
166
- // Parse host and port
167
- NSRange separator = [hostPort rangeOfString:@":" options:NSBackwardsSearch];
168
- NSString *host = separator.location == NSNotFound
169
- ? hostPort
170
- : [hostPort substringToIndex:separator.location];
171
- NSString *port = separator.location == NSNotFound
172
- ? @""
173
- : [hostPort substringFromIndex:separator.location + 1];
174
-
175
- if (host.length == 0 && port.length == 0) {
176
- [self resetMetroHost:resolve rejecter:reject];
177
- return;
178
- }
179
-
180
- NSNumberFormatter *formatter = [NSNumberFormatter new];
181
- formatter.numberStyle = NSNumberFormatterDecimalStyle;
182
- NSNumber *portNumber = [formatter numberFromString:port];
183
- if (portNumber == nil) {
184
- #ifdef RCT_METRO_PORT
185
- portNumber = [NSNumber numberWithInt:RCT_METRO_PORT];
186
- #else
187
- portNumber = [NSNumber numberWithInt:8081];
188
- #endif
189
- }
190
-
191
- NSString *normalizedHostPort = [NSString stringWithFormat:@"%@:%d", host, portNumber.intValue];
192
-
193
- // Persist for AppDelegate swizzle (Release mode + restart)
194
- [[NSUserDefaults standardUserDefaults] setObject:normalizedHostPort forKey:kDevConnectMetroHost];
195
- [[NSUserDefaults standardUserDefaults] synchronize];
299
+ DevConnectSetPersistedMetroHost(normalized);
196
300
 
197
- // Also set jsLocation (Debug mode + hot reload)
198
- settings.jsLocation = normalizedHostPort;
199
-
200
- // Try hot reload via bundleManager (works in Debug)
201
- NSURL *bundleURL = nil;
202
- if (DebugToolkitBundleRoot.length > 0) {
203
- bundleURL = [settings jsBundleURLForBundleRoot:DebugToolkitBundleRoot];
204
- }
205
- RCTBundleManager *bm = [self resolveBundleManager];
206
- if (bm) {
207
- bm.bundleURL = bundleURL;
208
- }
209
- #ifdef RCTReloadCommandSetBundleURL
210
- else if (bundleURL) {
211
- RCTReloadCommandSetBundleURL(bundleURL);
212
- }
213
- #endif
214
-
215
- NSLog(@"[DevConnect] applyMetroHost: %@ | bm=%@ | bridge=%@ | url=%@",
216
- normalizedHostPort, bm ? @"YES" : @"nil", _bridge ? @"YES" : @"nil", bundleURL);
217
-
218
- RCTTriggerReloadCommandListeners(@"Dev menu - apply changes");
219
-
220
- NSMutableDictionary *result = [@{@"hostPort" : normalizedHostPort} mutableCopy];
221
- if (bm && bm.bundleURL.absoluteString) {
222
- result[@"bundleURL"] = bm.bundleURL.absoluteString;
223
- }
224
- resolve(result);
225
- } @catch (NSException *exception) {
226
- NSLog(@"[DevConnect] applyMetroHost EXCEPTION: %@", exception.reason);
227
- reject(@"native_error", exception.reason, nil);
301
+ RCTBundleURLProvider *settings = [RCTBundleURLProvider sharedSettings];
302
+ settings.jsLocation = normalized;
303
+ NSURL *bundleURL = [settings jsBundleURLForBundleRoot:DevConnectMetroBundleRoot()];
304
+
305
+ [self reloadWithBundleURL:bundleURL reason:@"Dev menu - apply changes"];
306
+
307
+ NSLog(@"[DevConnect] applyMetroHost host=%@ url=%@", normalized, bundleURL);
308
+ resolve(@{
309
+ @"hostPort": normalized,
310
+ @"bundleURL": bundleURL.absoluteString ?: [NSNull null],
311
+ });
312
+ } @catch (NSException *e) {
313
+ NSLog(@"[DevConnect] applyMetroHost EXCEPTION: %@", e.reason);
314
+ reject(@"native_error", e.reason ?: @"unknown", nil);
228
315
  }
229
316
  }
230
317
 
@@ -232,48 +319,55 @@ RCT_EXPORT_METHOD(resetMetroHost:(RCTPromiseResolveBlock)resolve
232
319
  rejecter:(__unused RCTPromiseRejectBlock)reject)
233
320
  {
234
321
  @try {
235
- // Clear stored Metro host
236
- [[NSUserDefaults standardUserDefaults] removeObjectForKey:kDevConnectMetroHost];
237
- [[NSUserDefaults standardUserDefaults] synchronize];
322
+ DevConnectSetPersistedMetroHost(nil);
238
323
 
239
- // Reset RN settings
240
324
  RCTBundleURLProvider *settings = [RCTBundleURLProvider sharedSettings];
241
325
  [settings resetToDefaults];
242
- NSURL *bundleURL = [settings jsBundleURLForFallbackExtension:nil];
243
326
 
244
- RCTBundleManager *bm = [self resolveBundleManager];
245
- if (bm) {
246
- bm.bundleURL = bundleURL;
327
+ NSURL *embedded = DebugToolkitEmbeddedBundleURL();
328
+ if (!embedded) {
329
+ embedded = [settings jsBundleURLForFallbackExtension:nil];
247
330
  }
248
331
 
249
- NSLog(@"[DevConnect] resetMetroHost | bm=%@ | url=%@", bm ? @"YES" : @"nil", bundleURL);
250
- RCTTriggerReloadCommandListeners(@"Dev menu - reset to default");
332
+ [self reloadWithBundleURL:embedded reason:@"Dev menu - reset to default"];
333
+
334
+ NSLog(@"[DevConnect] resetMetroHost url=%@", embedded);
251
335
  resolve([NSNull null]);
252
- } @catch (NSException *exception) {
253
- NSLog(@"[DevConnect] resetMetroHost EXCEPTION: %@", exception.reason);
254
- reject(@"native_error", exception.reason, nil);
336
+ } @catch (NSException *e) {
337
+ NSLog(@"[DevConnect] resetMetroHost EXCEPTION: %@", e.reason);
338
+ reject(@"native_error", e.reason ?: @"unknown", nil);
255
339
  }
256
340
  }
257
341
 
258
- RCT_EXPORT_METHOD(getDiagnostics:(RCTPromiseResolveBlock)resolve
342
+ RCT_EXPORT_METHOD(getMetroHost:(RCTPromiseResolveBlock)resolve
259
343
  rejecter:(__unused RCTPromiseRejectBlock)reject)
260
344
  {
261
- NSArray *log = [[NSUserDefaults standardUserDefaults] arrayForKey:kDevConnectDiagLog] ?: @[];
262
- NSString *metroHost = [[NSUserDefaults standardUserDefaults] stringForKey:kDevConnectMetroHost];
263
- resolve(@{
264
- @"log": log,
265
- @"swizzleInstalled": @(original_sourceURLForBridge != NULL),
266
- @"swizzleInvoked": @(swizzleInvoked),
267
- @"persistedMetroHost": metroHost ?: [NSNull null],
268
- });
345
+ NSString *host = DevConnectPersistedMetroHost();
346
+ resolve(host.length > 0 ? host : [NSNull null]);
269
347
  }
270
348
 
271
- RCT_EXPORT_METHOD(clearDiagnostics:(RCTPromiseResolveBlock)resolve
349
+ RCT_EXPORT_METHOD(getDiagnostics:(RCTPromiseResolveBlock)resolve
272
350
  rejecter:(__unused RCTPromiseRejectBlock)reject)
273
351
  {
274
- [[NSUserDefaults standardUserDefaults] removeObjectForKey:kDevConnectDiagLog];
275
- [[NSUserDefaults standardUserDefaults] synchronize];
276
- resolve([NSNull null]);
352
+ Class realDelegate = object_getClass([UIApplication sharedApplication].delegate);
353
+ NSString *delegateName = realDelegate ? NSStringFromClass(realDelegate) : @"unknown";
354
+ NSString *persisted = DevConnectPersistedMetroHost();
355
+
356
+ #if DEBUG
357
+ BOOL isDebug = YES;
358
+ #else
359
+ BOOL isDebug = NO;
360
+ #endif
361
+
362
+ resolve(@{
363
+ @"persistedMetroHost": persisted.length > 0 ? persisted : [NSNull null],
364
+ @"appDelegateClass": delegateName,
365
+ @"isDebugBuild": @(isDebug),
366
+ @"hasEmbeddedBundle": @(DebugToolkitEmbeddedBundleURL() != nil),
367
+ @"embeddedFirstHookInstalled": @(DevConnectEmbeddedFirstHooksActive()),
368
+ @"packagerHookInstalled": @(gPackagerHookInstalled),
369
+ @"bundleRootHookInstalled": @(gBundleRootHookInstalled),
370
+ });
277
371
  }
278
372
 
279
373
  RCT_EXPORT_METHOD(getPreference:(NSString *)key
@@ -283,8 +377,8 @@ RCT_EXPORT_METHOD(getPreference:(NSString *)key
283
377
  @try {
284
378
  NSString *value = [[NSUserDefaults standardUserDefaults] stringForKey:key];
285
379
  resolve(value ?: [NSNull null]);
286
- } @catch (NSException *exception) {
287
- reject(@"native_error", exception.reason, nil);
380
+ } @catch (NSException *e) {
381
+ reject(@"native_error", e.reason ?: @"unknown", nil);
288
382
  }
289
383
  }
290
384
 
@@ -297,8 +391,8 @@ RCT_EXPORT_METHOD(setPreference:(NSString *)key
297
391
  [[NSUserDefaults standardUserDefaults] setObject:value forKey:key];
298
392
  [[NSUserDefaults standardUserDefaults] synchronize];
299
393
  resolve([NSNull null]);
300
- } @catch (NSException *exception) {
301
- reject(@"native_error", exception.reason, nil);
394
+ } @catch (NSException *e) {
395
+ reject(@"native_error", e.reason ?: @"unknown", nil);
302
396
  }
303
397
  }
304
398
 
@@ -317,40 +411,27 @@ RCT_EXPORT_METHOD(getLocalIp:(RCTPromiseResolveBlock)resolve
317
411
  {
318
412
  @try {
319
413
  struct ifaddrs *interfaces = NULL;
320
- if (getifaddrs(&interfaces) == 0) {
321
- struct ifaddrs *iface = interfaces;
322
- while (iface != NULL) {
323
- if (iface->ifa_addr != NULL && iface->ifa_addr->sa_family == AF_INET && !(iface->ifa_flags & IFF_LOOPBACK)) {
324
- if (strcmp(iface->ifa_name, "en0") == 0) {
325
- char addrStr[INET_ADDRSTRLEN];
326
- struct sockaddr_in *sin = (struct sockaddr_in *)iface->ifa_addr;
327
- inet_ntop(AF_INET, &sin->sin_addr, addrStr, sizeof(addrStr));
328
- NSString *ip = [NSString stringWithUTF8String:addrStr];
329
- freeifaddrs(interfaces);
330
- resolve(ip);
331
- return;
332
- }
333
- }
334
- iface = iface->ifa_next;
335
- }
336
- iface = interfaces;
337
- while (iface != NULL) {
338
- if (iface->ifa_addr != NULL && iface->ifa_addr->sa_family == AF_INET && !(iface->ifa_flags & IFF_LOOPBACK)) {
339
- char addrStr[INET_ADDRSTRLEN];
340
- struct sockaddr_in *sin = (struct sockaddr_in *)iface->ifa_addr;
341
- inet_ntop(AF_INET, &sin->sin_addr, addrStr, sizeof(addrStr));
342
- NSString *ip = [NSString stringWithUTF8String:addrStr];
343
- freeifaddrs(interfaces);
344
- resolve(ip);
345
- return;
346
- }
347
- iface = iface->ifa_next;
348
- }
414
+ if (getifaddrs(&interfaces) != 0) {
415
+ resolve([NSNull null]);
416
+ return;
417
+ }
418
+
419
+ NSString *preferred = nil;
420
+ NSString *fallback = nil;
421
+ for (struct ifaddrs *iface = interfaces; iface != NULL; iface = iface->ifa_next) {
422
+ if (!iface->ifa_addr || iface->ifa_addr->sa_family != AF_INET) continue;
423
+ if (iface->ifa_flags & IFF_LOOPBACK) continue;
424
+ char addrStr[INET_ADDRSTRLEN];
425
+ struct sockaddr_in *sin = (struct sockaddr_in *)iface->ifa_addr;
426
+ inet_ntop(AF_INET, &sin->sin_addr, addrStr, sizeof(addrStr));
427
+ NSString *ip = [NSString stringWithUTF8String:addrStr];
428
+ if (strcmp(iface->ifa_name, "en0") == 0) { preferred = ip; break; }
429
+ if (!fallback) fallback = ip;
349
430
  }
350
431
  freeifaddrs(interfaces);
351
- resolve([NSNull null]);
352
- } @catch (NSException *exception) {
353
- reject(@"native_error", exception.reason, nil);
432
+ resolve(preferred ?: fallback ?: [NSNull null]);
433
+ } @catch (NSException *e) {
434
+ reject(@"native_error", e.reason ?: @"unknown", nil);
354
435
  }
355
436
  }
356
437