@@ -14,64 +14,25 @@ - (void)applicationDidFinishLaunching:(NSNotification *)notification
1414 self.initialProps = @{};
1515 self.dependencyProvider = [RCTAppDependencyProvider new ];
1616
17- // / Apple Events are processed without app focus, but require TCC permissions. Trigger as follows:
18- // / ```sh
19- // / osascript -e '
20- // / tell application "Electron"
21- // / «event MYEVCMD1» "payload text"
22- // / end tell
23- // / '
24- // / ```
25- [[NSAppleEventManager sharedAppleEventManager ]
26- setEventHandler: self
27- andSelector: @selector (handleEvent:withReplyEvent: )
28- forEventClass: ' MYEV'
29- andEventID: ' CMD1' ];
30-
31- // / DistributedNotifications require app focus to be processed, but don't require TCC permissions. Trigger as follows:
32- // / ```sh
33- // / swift -e 'import Foundation; DistributedNotificationCenter.default.post(name: Notification.Name("com.example.MyCommand"), object: nil, userInfo: ["action":"test"])'
34- // / ```
35- [[NSDistributedNotificationCenter defaultCenter ]
36- addObserverForName: @" com.example.MyCommand"
37- object: nil
38- queue: [NSOperationQueue mainQueue ]
39- usingBlock: ^(NSNotification * _Nonnull notification) {
40-
41- NSDictionary *userInfo = notification.userInfo ;
42- if (!userInfo) {
43- userInfo = @{};
44- }
45-
46- NSLog (@" Received notification: %@ " , userInfo);
47- RCTTriggerReloadCommandListeners (@" programmatic reload" );
48- }];
17+ #if DEBUG
18+ // Here, we trigger a dev reload upon any changes saved to our magic file at:
19+ // ~/Library/Application Support/uk.co.birchlabs.rnfiddleclient/trigger-reload.txt
20+ // This is a hacky workaround to enable React Native Fiddle to trigger our app
21+ // to reconnect to Metro when needed (as we sometimes have to relaunch Metro
22+ // in a new CWD).
23+ //
24+ // Compared to "proper" methods of IPC, this requires neither TCC permissions
25+ // nor app focus, and should work even with App Sandbox and Hardened Runtime
26+ // enabled.
27+ TouchFile (ReloadTriggerFilePath ());
28+ WatchFile (ReloadTriggerFilePath (), ^{
29+ RCTTriggerReloadCommandListeners (nil );
30+ });
31+ #endif
4932
5033 return [super applicationDidFinishLaunching: notification];
5134}
5235
53- - (void )handleEvent : (NSAppleEventDescriptor *)event
54- withReplyEvent : (NSAppleEventDescriptor *)replyEvent
55- {
56- NSAppleEventDescriptor *param = [event paramDescriptorForKeyword: keyDirectObject];
57-
58- NSString *payload = nil ;
59- if (param && param.descriptorType == typeUTF8Text) {
60- payload = param.stringValue ;
61- }
62-
63- NSLog (@" Received custom Apple Event: class='MYEV' id='CMD1' payload='%@ '" , payload);
64-
65- if (replyEvent) {
66- NSAppleEventDescriptor *replyString =
67- [NSAppleEventDescriptor descriptorWithString: @" Message received! Reloading..." ];
68- [replyEvent setDescriptor: replyString forKeyword: keyDirectObject];
69- }
70-
71- RCTTriggerReloadCommandListeners (@" programmatic reload" );
72- }
73-
74-
7536- (NSURL *)sourceURLForBridge : (RCTBridge *)bridge
7637{
7738 return [self bundleURL ];
@@ -100,4 +61,81 @@ - (BOOL)concurrentRootEnabled
10061#endif
10162}
10263
64+ #if DEBUG
65+ static NSString *ReloadTriggerFilePath (void ) {
66+ static NSString *reloadFilePath = nil ;
67+ static dispatch_once_t onceToken;
68+ dispatch_once (&onceToken, ^{
69+ NSString *appSupport = NSSearchPathForDirectoriesInDomains (NSApplicationSupportDirectory, NSUserDomainMask, YES ).firstObject ;
70+ NSString *bundleID = [[NSBundle mainBundle ] bundleIdentifier ];
71+ NSString *dir = [appSupport stringByAppendingPathComponent: bundleID];
72+ reloadFilePath = [dir stringByAppendingPathComponent: @" trigger-reload.txt" ];
73+ });
74+ return reloadFilePath;
75+ }
76+
77+ static NSError * TouchFile (NSString *filePath) {
78+ NSString *dir = [filePath stringByDeletingLastPathComponent ];
79+
80+ NSError *error = nil ;
81+ [[NSFileManager defaultManager ] createDirectoryAtPath: dir
82+ withIntermediateDirectories: YES
83+ attributes: nil
84+ error: &error];
85+ if (error) {
86+ NSLog (@" Failed to create folder %@ : %@ " , dir, error);
87+ return error;
88+ }
89+
90+ [NSData .data writeToFile: filePath options: NSDataWritingAtomic error: &error];
91+ if (error) {
92+ NSLog (@" Failed to touch reload file: %@ " , error);
93+ }
94+
95+ return error;
96+ }
97+
98+ /* *
99+ * Watch a file and run a block whenever it is written.
100+ * Stops watching if the file is deleted.
101+ */
102+ static void WatchFile (NSString *filePath, void (^onWrite)(void )) {
103+ if (![[NSFileManager defaultManager ] fileExistsAtPath: filePath]) {
104+ NSLog (@" File does not exist; skipping watcher." );
105+ return ;
106+ }
107+
108+ int fd = open ([filePath fileSystemRepresentation ], O_EVTONLY);
109+ if (fd < 0 ) {
110+ NSLog (@" Failed to open file for monitoring" );
111+ return ;
112+ }
113+
114+ dispatch_source_t source = dispatch_source_create (DISPATCH_SOURCE_TYPE_VNODE,
115+ fd,
116+ DISPATCH_VNODE_WRITE | DISPATCH_VNODE_DELETE,
117+ DISPATCH_TARGET_QUEUE_DEFAULT);
118+
119+ dispatch_source_set_event_handler (source, ^{
120+ unsigned long flags = dispatch_source_get_data (source);
121+
122+ if (flags & DISPATCH_VNODE_WRITE) {
123+ if (onWrite) {
124+ dispatch_async (dispatch_get_main_queue (), ^{
125+ onWrite ();
126+ });
127+ }
128+ }
129+
130+ if (flags & (DISPATCH_VNODE_DELETE | DISPATCH_VNODE_RENAME)) {
131+ NSLog (@" File deleted or renamed; stopping watcher." );
132+ dispatch_source_cancel (source);
133+ close (fd);
134+ }
135+ });
136+
137+ dispatch_resume (source);
138+ }
139+ #endif
140+
103141@end
0 commit comments