node-mac-recorder 2.16.10 โ†’ 2.16.12

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.
Files changed (41) hide show
  1. package/.claude/settings.local.json +1 -1
  2. package/CLAUDE.md +51 -3
  3. package/package.json +1 -1
  4. package/src/avfoundation_recorder.mm +85 -55
  5. package/src/mac_recorder.mm +49 -57
  6. package/bring-to-front-test.js +0 -159
  7. package/capture-test.js +0 -87
  8. package/cursor-test.js +0 -22
  9. package/debug-test.js +0 -95
  10. package/default-auto-front-test.js +0 -86
  11. package/final-multi-display-test.js +0 -74
  12. package/list-test.js +0 -46
  13. package/quick-cursor-test.js +0 -20
  14. package/quick-screen-test.js +0 -34
  15. package/quick-test.js +0 -35
  16. package/system-sound-test.js +0 -46
  17. package/test-audio-controls.js +0 -104
  18. package/test-both.js +0 -46
  19. package/test-console-logs.js +0 -81
  20. package/test-coordinate-debug.js +0 -25
  21. package/test-cursor-visible.js +0 -41
  22. package/test-electron-detection.js +0 -44
  23. package/test-final-coordinate-fix.js +0 -38
  24. package/test-hybrid.js +0 -53
  25. package/test-macos14-forced.js +0 -107
  26. package/test-macos14.js +0 -56
  27. package/test-multi-display-overlay.js +0 -185
  28. package/test-multi-screen-selection.js +0 -171
  29. package/test-multidisplay-fix.js +0 -120
  30. package/test-native-cursor.js +0 -31
  31. package/test-overlay-tracking.js +0 -175
  32. package/test-primary-live.js +0 -17
  33. package/test-primary-window-debug.js +0 -33
  34. package/test-quick.js +0 -55
  35. package/test-real-screen-ids.js +0 -32
  36. package/test-screencapture-only.js +0 -50
  37. package/test-screencapture-pure.js +0 -69
  38. package/test-screencapture.js +0 -52
  39. package/test-system-audio.js +0 -104
  40. package/test-window-selector-fix.js +0 -88
  41. package/window-selector-test.js +0 -160
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "permissions": {
3
3
  "allow": [
4
- "Bash(FORCE_AVFOUNDATION=1 node -e \"const MacRecorder = require(''./index.js''); const recorder = new MacRecorder(); recorder.checkPermissions().then(result => console.log(''Permission result (macOS 14 simulation):'', result))\")"
4
+ "Bash(FORCE_AVFOUNDATION=1 node -e \"\nconsole.log(''๐Ÿงช Testing fixed AVFoundation...'');\nconst MacRecorder = require(''./index.js'');\nconst recorder = new MacRecorder();\n\nrecorder.startRecording(''/tmp/electron-test2.mov'')\n .then(success => {\n console.log(''Start:'', success ? ''โœ… SUCCESS'' : ''โŒ FAILED'');\n if (success) {\n setTimeout(() => {\n console.log(''โน๏ธ Stopping...'');\n recorder.stopRecording().then(() => {\n console.log(''โœ… Stop completed'');\n const fs = require(''fs'');\n if (fs.existsSync(''/tmp/electron-test2.mov'')) {\n console.log(''๐Ÿ“น File:'', Math.round(fs.statSync(''/tmp/electron-test2.mov'').size/1024) + ''KB'');\n console.log(''๐ŸŽ‰ Fix successful!'');\n }\n });\n }, 2000);\n }\n })\n .catch(console.error);\n\")"
5
5
  ],
6
6
  "deny": [],
7
7
  "ask": []
package/CLAUDE.md CHANGED
@@ -4,11 +4,15 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
4
4
 
5
5
  ## Project Overview
6
6
 
7
- node-mac-recorder is a Node.js native addon that provides macOS screen recording capabilities using AVFoundation. The package allows recording of full screens, specific windows, or custom areas, with support for multi-display setups, audio capture, and cursor tracking.
7
+ **ELECTRON-FIRST PRIORITY**: node-mac-recorder is a Node.js native addon specifically designed and optimized for Electron.js applications. It provides macOS screen recording capabilities using ScreenCaptureKit (macOS 15+) and AVFoundation (macOS 13+). The package allows recording of full screens, specific windows, or custom areas, with support for multi-display setups, audio capture, and cursor tracking.
8
+
9
+ ### **๐Ÿš€ CRITICAL: Electron.js Support**
10
+ This module is **PRIMARILY BUILT FOR ELECTRON.JS**. All features work seamlessly in Electron applications without any restrictions. Both ScreenCaptureKit and AVFoundation are fully supported in Electron environments.
8
11
 
9
12
  ## Build System & Commands
10
13
 
11
14
  ### Building the Native Module
15
+
12
16
  ```bash
13
17
  npm run build # Build the native module using node-gyp
14
18
  npm run rebuild # Clean rebuild of the native module
@@ -17,6 +21,7 @@ npm install # Runs install.js which builds the module automatically
17
21
  ```
18
22
 
19
23
  ### Testing
24
+
20
25
  ```bash
21
26
  npm test # Run the main test suite (test.js)
22
27
  node cursor-test.js # Test cursor tracking functionality only
@@ -24,7 +29,9 @@ node test.js # Run comprehensive API tests
24
29
  ```
25
30
 
26
31
  ### Development
32
+
27
33
  The package uses node-gyp for building the native C++/Objective-C module. Requires:
34
+
28
35
  - macOS 10.15+ (Catalina or later)
29
36
  - Xcode Command Line Tools
30
37
  - Node.js 14+
@@ -34,16 +41,19 @@ The package uses node-gyp for building the native C++/Objective-C module. Requir
34
41
  ### Core Components
35
42
 
36
43
  **Main Entry Point**
44
+
37
45
  - `index.js` - Main MacRecorder class (EventEmitter-based)
38
46
  - Handles all high-level recording operations and coordinate transformations
39
47
 
40
48
  **Native Module** (`src/`)
49
+
41
50
  - `mac_recorder.mm` - Main native module entry point and N-API bindings
42
51
  - `screen_capture.mm` - AVFoundation-based screen/window recording
43
52
  - `audio_capture.mm` - Audio device enumeration and capture
44
53
  - `cursor_tracker.mm` - Real-time cursor position and event tracking
45
54
 
46
55
  **Build Configuration**
56
+
47
57
  - `binding.gyp` - Native module build configuration
48
58
  - Links against AVFoundation, ScreenCaptureKit, AppKit, and other macOS frameworks
49
59
 
@@ -58,6 +68,7 @@ The package uses node-gyp for building the native C++/Objective-C module. Requir
58
68
  ### Coordinate System Handling
59
69
 
60
70
  The package handles complex multi-display coordinate transformations:
71
+
61
72
  - Global macOS coordinates (can be negative for secondary displays)
62
73
  - Display-relative coordinates (always positive, 0-based)
63
74
  - Automatic window-to-display mapping for recording
@@ -65,6 +76,7 @@ The package handles complex multi-display coordinate transformations:
65
76
  ## API Structure
66
77
 
67
78
  ### Main Class Methods
79
+
68
80
  - `startRecording(outputPath, options)` - Begin screen/window recording
69
81
  - `stopRecording()` - Stop recording and finalize video file
70
82
  - `getWindows()` - List all recordable application windows
@@ -73,6 +85,7 @@ The package handles complex multi-display coordinate transformations:
73
85
  - `checkPermissions()` - Verify macOS recording permissions
74
86
 
75
87
  ### Cursor Tracking
88
+
76
89
  - `startCursorCapture(filepath, options)` - Begin real-time cursor tracking to JSON
77
90
  - `options.windowInfo` - Window information for window-relative coordinates
78
91
  - `options.windowRelative` - Set to true for window-relative coordinates
@@ -80,7 +93,9 @@ The package handles complex multi-display coordinate transformations:
80
93
  - `getCursorPosition()` - Get current cursor position and state
81
94
 
82
95
  ### Events
96
+
83
97
  The MacRecorder class emits the following events:
98
+
84
99
  - `recordingStarted` - Emitted immediately when recording starts with recording details
85
100
  - `started` - Emitted when recording is confirmed started (legacy event)
86
101
  - `stopped` - Emitted when recording stops
@@ -90,17 +105,20 @@ The MacRecorder class emits the following events:
90
105
  - `cursorCaptureStopped` - Emitted when cursor capture ends
91
106
 
92
107
  ### Thumbnails
108
+
93
109
  - `getWindowThumbnail(windowId, options)` - Capture window preview image
94
110
  - `getDisplayThumbnail(displayId, options)` - Capture display preview image
95
111
 
96
112
  ## Development Notes
97
113
 
98
114
  ### Testing Strategy
115
+
99
116
  - Use `npm test` for full API validation
100
117
  - `cursor-test.js` for testing cursor tracking specifically
101
118
  - Test files create output in `test-output/` directory
102
119
 
103
120
  ### Common Development Patterns
121
+
104
122
  - All recording operations are Promise-based
105
123
  - Event emission for recording state changes (`recordingStarted`, `started`, `stopped`, `completed`)
106
124
  - `recordingStarted` event provides immediate notification with recording details
@@ -111,12 +129,25 @@ The MacRecorder class emits the following events:
111
129
  - Display-relative coordinates (when recording)
112
130
  - Window-relative coordinates (with windowInfo parameter)
113
131
 
114
- ### Platform Requirements
132
+ ### Platform Requirements & Framework Selection
133
+
134
+ **ELECTRON-FIRST ARCHITECTURE:**
135
+ - **Primary Target**: Electron.js applications (full support, no restrictions)
136
+ - **Secondary**: Node.js standalone applications
115
137
  - macOS only (enforced in install.js)
116
138
  - Native module compilation required on install
117
139
  - Requires screen recording and accessibility permissions
118
140
 
141
+ **Framework Selection Logic (Electron Priority):**
142
+ - **macOS 15+ + Electron**: ScreenCaptureKit with full capabilities
143
+ - **macOS 15+ + Node.js**: ScreenCaptureKit with full capabilities
144
+ - **macOS 14 + Electron**: AVFoundation with full capabilities
145
+ - **macOS 13 + Electron**: AVFoundation with limited features
146
+ - **macOS 14 + Node.js**: AVFoundation with full capabilities
147
+ - **macOS 13 + Node.js**: AVFoundation with limited features
148
+
119
149
  ### File Outputs
150
+
120
151
  - Video recordings: `.mov` format (H.264/AAC)
121
152
  - Cursor data: JSON format with timestamped events
122
153
  - `x`, `y`: Cursor coordinates (coordinate system dependent)
@@ -131,14 +162,31 @@ The MacRecorder class emits the following events:
131
162
  ## Troubleshooting
132
163
 
133
164
  ### Build Issues
165
+
134
166
  1. Ensure Xcode Command Line Tools: `xcode-select --install`
135
167
  2. Clean rebuild: `npm run clean && npm run build`
136
168
  3. Check Node.js version compatibility (14+)
137
169
 
138
170
  ### Runtime Issues
171
+
139
172
  1. Permission failures: Check System Preferences > Security & Privacy
140
173
  2. Recording failures: Verify target windows/displays are accessible
141
174
  3. Audio issues: Check audio device availability and permissions
142
175
 
143
176
  ### Native Module Loading
144
- The module tries loading from `build/Release/` first, then falls back to `build/Debug/` with helpful error messages if neither exists.
177
+
178
+ The module tries loading from `build/Release/` first, then falls back to `build/Debug/` with helpful error messages if neither exists.
179
+
180
+ ### **โšก CRITICAL IMPLEMENTATION NOTES**
181
+
182
+ **ELECTRON.JS IS THE PRIMARY TARGET:**
183
+ 1. **No Electron Restrictions**: All Electron detection logic is for optimization, NOT blocking
184
+ 2. **Framework Support**: Both ScreenCaptureKit and AVFoundation work perfectly in Electron
185
+ 3. **Environment Detection**: Code detects Electron to provide enhanced logging and optimization
186
+ 4. **macOS Compatibility**:
187
+ - macOS 15+ (Sequoia+): Use ScreenCaptureKit for best performance
188
+ - macOS 14 (Sonoma): Use AVFoundation with full feature set
189
+ - macOS 13 (Ventura): Use AVFoundation with basic features
190
+ 5. **Error Handling**: Any "Recording failed to start" errors should trigger fallback logic, never blocking
191
+
192
+ **NEVER BLOCK ELECTRON ENVIRONMENTS** - This is a core design principle.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.16.10",
3
+ "version": "2.16.12",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -123,7 +123,7 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
123
123
  g_avCaptureRect = captureRect;
124
124
  g_avFrameNumber = 0;
125
125
 
126
- // Start capture timer (15 FPS for compatibility)
126
+ // Start capture timer (10 FPS for Electron compatibility)
127
127
  dispatch_queue_t captureQueue = dispatch_queue_create("AVFoundationCaptureQueue", DISPATCH_QUEUE_SERIAL);
128
128
  g_avTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, captureQueue);
129
129
 
@@ -132,78 +132,107 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
132
132
  return false;
133
133
  }
134
134
 
135
- uint64_t interval = NSEC_PER_SEC / 15; // 15 FPS
135
+ uint64_t interval = NSEC_PER_SEC / 10; // 10 FPS for Electron stability
136
136
  dispatch_source_set_timer(g_avTimer, dispatch_time(DISPATCH_TIME_NOW, 0), interval, interval / 10);
137
137
 
138
138
  dispatch_source_set_event_handler(g_avTimer, ^{
139
139
  if (!g_avIsRecording) return;
140
140
 
141
141
  @autoreleasepool {
142
- // Capture screen
143
- CGImageRef screenImage = nil;
144
- if (CGRectIsEmpty(g_avCaptureRect)) {
145
- screenImage = CGDisplayCreateImage(g_avDisplayID);
146
- } else {
147
- CGImageRef fullScreen = CGDisplayCreateImage(g_avDisplayID);
148
- if (fullScreen) {
149
- screenImage = CGImageCreateWithImageInRect(fullScreen, g_avCaptureRect);
150
- CGImageRelease(fullScreen);
142
+ @try {
143
+ // Capture screen with Electron-safe error handling
144
+ CGImageRef screenImage = nil;
145
+ if (CGRectIsEmpty(g_avCaptureRect)) {
146
+ screenImage = CGDisplayCreateImage(g_avDisplayID);
147
+ } else {
148
+ CGImageRef fullScreen = CGDisplayCreateImage(g_avDisplayID);
149
+ if (fullScreen) {
150
+ screenImage = CGImageCreateWithImageInRect(fullScreen, g_avCaptureRect);
151
+ CGImageRelease(fullScreen);
152
+ }
151
153
  }
152
- }
153
-
154
- if (!screenImage) return;
155
-
156
- // Convert to pixel buffer
157
- CVPixelBufferRef pixelBuffer = nil;
158
- CVReturn cvRet = CVPixelBufferPoolCreatePixelBuffer(NULL, g_avPixelBufferAdaptor.pixelBufferPool, &pixelBuffer);
159
-
160
- if (cvRet == kCVReturnSuccess && pixelBuffer) {
161
- CVPixelBufferLockBaseAddress(pixelBuffer, 0);
162
-
163
- void *pixelData = CVPixelBufferGetBaseAddress(pixelBuffer);
164
- size_t bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer);
165
-
166
- CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
167
154
 
168
- // Match bitmap info to pixel format for compatibility
169
- CGBitmapInfo bitmapInfo;
170
- OSType currentPixelFormat = CVPixelBufferGetPixelFormatType(pixelBuffer);
171
- if (currentPixelFormat == kCVPixelFormatType_32ARGB) {
172
- bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Big;
173
- } else { // kCVPixelFormatType_32BGRA
174
- bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little;
155
+ if (!screenImage) {
156
+ NSLog(@"โš ๏ธ Failed to capture screen image, skipping frame");
157
+ return;
175
158
  }
159
+
160
+ // Convert to pixel buffer with Electron-safe error handling
161
+ CVPixelBufferRef pixelBuffer = nil;
162
+ CVReturn cvRet = CVPixelBufferPoolCreatePixelBuffer(NULL, g_avPixelBufferAdaptor.pixelBufferPool, &pixelBuffer);
176
163
 
177
- CGContextRef context = CGBitmapContextCreate(pixelData,
178
- CVPixelBufferGetWidth(pixelBuffer),
179
- CVPixelBufferGetHeight(pixelBuffer),
180
- 8, bytesPerRow, colorSpace, bitmapInfo);
181
-
182
- if (context) {
183
- CGContextDrawImage(context, CGRectMake(0, 0, CVPixelBufferGetWidth(pixelBuffer), CVPixelBufferGetHeight(pixelBuffer)), screenImage);
184
- CGContextRelease(context);
185
- }
186
- CGColorSpaceRelease(colorSpace);
187
- CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
188
-
189
- // Write frame
190
- if (g_avVideoInput.readyForMoreMediaData) {
191
- CMTime frameTime = CMTimeAdd(g_avStartTime, CMTimeMakeWithSeconds(g_avFrameNumber / 15.0, 600));
192
- [g_avPixelBufferAdaptor appendPixelBuffer:pixelBuffer withPresentationTime:frameTime];
193
- g_avFrameNumber++;
164
+ if (cvRet == kCVReturnSuccess && pixelBuffer) {
165
+ CVPixelBufferLockBaseAddress(pixelBuffer, 0);
166
+
167
+ void *pixelData = CVPixelBufferGetBaseAddress(pixelBuffer);
168
+ if (!pixelData) {
169
+ NSLog(@"โš ๏ธ Failed to get pixel buffer base address");
170
+ CVPixelBufferRelease(pixelBuffer);
171
+ CGImageRelease(screenImage);
172
+ return;
173
+ }
174
+
175
+ size_t bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer);
176
+ CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
177
+ if (!colorSpace) {
178
+ NSLog(@"โš ๏ธ Failed to create color space");
179
+ CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
180
+ CVPixelBufferRelease(pixelBuffer);
181
+ CGImageRelease(screenImage);
182
+ return;
183
+ }
184
+
185
+ // Match bitmap info to pixel format for compatibility
186
+ CGBitmapInfo bitmapInfo;
187
+ OSType currentPixelFormat = CVPixelBufferGetPixelFormatType(pixelBuffer);
188
+ if (currentPixelFormat == kCVPixelFormatType_32ARGB) {
189
+ bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Big;
190
+ } else { // kCVPixelFormatType_32BGRA
191
+ bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little;
192
+ }
193
+
194
+ CGContextRef context = CGBitmapContextCreate(pixelData,
195
+ CVPixelBufferGetWidth(pixelBuffer),
196
+ CVPixelBufferGetHeight(pixelBuffer),
197
+ 8, bytesPerRow, colorSpace, bitmapInfo);
198
+
199
+ if (context) {
200
+ CGContextDrawImage(context, CGRectMake(0, 0, CVPixelBufferGetWidth(pixelBuffer), CVPixelBufferGetHeight(pixelBuffer)), screenImage);
201
+ CGContextRelease(context);
202
+
203
+ // Write frame only if input is ready
204
+ if (g_avVideoInput && g_avVideoInput.readyForMoreMediaData) {
205
+ CMTime frameTime = CMTimeAdd(g_avStartTime, CMTimeMakeWithSeconds(g_avFrameNumber / 10.0, 600));
206
+ BOOL appendSuccess = [g_avPixelBufferAdaptor appendPixelBuffer:pixelBuffer withPresentationTime:frameTime];
207
+ if (appendSuccess) {
208
+ g_avFrameNumber++;
209
+ } else {
210
+ NSLog(@"โš ๏ธ Failed to append pixel buffer");
211
+ }
212
+ }
213
+ } else {
214
+ NSLog(@"โš ๏ธ Failed to create bitmap context");
215
+ }
216
+
217
+ CGColorSpaceRelease(colorSpace);
218
+ CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
219
+ CVPixelBufferRelease(pixelBuffer);
220
+ } else {
221
+ NSLog(@"โš ๏ธ Failed to create pixel buffer: %d", cvRet);
194
222
  }
195
223
 
196
- CVPixelBufferRelease(pixelBuffer);
224
+ CGImageRelease(screenImage);
225
+ } @catch (NSException *exception) {
226
+ NSLog(@"โŒ Exception in AVFoundation capture loop: %@", exception.reason);
227
+ g_avIsRecording = false; // Stop recording on exception to prevent crash
197
228
  }
198
-
199
- CGImageRelease(screenImage);
200
229
  }
201
230
  });
202
231
 
203
232
  dispatch_resume(g_avTimer);
204
233
  g_avIsRecording = true;
205
234
 
206
- NSLog(@"๐ŸŽฅ AVFoundation recording started: %dx%d @ 15fps",
235
+ NSLog(@"๐ŸŽฅ AVFoundation recording started: %dx%d @ 10fps",
207
236
  (int)recordingSize.width, (int)recordingSize.height);
208
237
 
209
238
  return true;
@@ -222,10 +251,11 @@ extern "C" bool stopAVFoundationRecording() {
222
251
  g_avIsRecording = false;
223
252
 
224
253
  @try {
225
- // Stop timer
254
+ // Stop timer with Electron-safe cleanup
226
255
  if (g_avTimer) {
227
256
  dispatch_source_cancel(g_avTimer);
228
257
  g_avTimer = nil;
258
+ NSLog(@"โœ… AVFoundation timer stopped safely");
229
259
  }
230
260
 
231
261
  // Finish writing
@@ -51,14 +51,8 @@ void cleanupRecording() {
51
51
  }
52
52
  }
53
53
 
54
- // AVFoundation cleanup (only in non-Electron environments)
55
- BOOL isElectron = (NSBundle.mainBundle.bundleIdentifier &&
56
- [NSBundle.mainBundle.bundleIdentifier containsString:@"electron"]) ||
57
- (NSProcessInfo.processInfo.processName &&
58
- [NSProcessInfo.processInfo.processName containsString:@"Electron"]) ||
59
- (NSProcessInfo.processInfo.environment[@"ELECTRON_RUN_AS_NODE"] != nil);
60
-
61
- if (!isElectron && isAVFoundationRecording()) {
54
+ // AVFoundation cleanup (supports both Node.js and Electron)
55
+ if (isAVFoundationRecording()) {
62
56
  stopAVFoundationRecording();
63
57
  }
64
58
 
@@ -211,10 +205,15 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
211
205
  NSLog(@"๐Ÿ”ง FORCE_AVFOUNDATION environment variable detected - skipping ScreenCaptureKit");
212
206
  }
213
207
 
214
- // ONLY use ScreenCaptureKit on macOS 15+
215
- // macOS 14/13 ALWAYS use AVFoundation - NO ScreenCaptureKit attempts
216
- if (isM15Plus && !forceAVFoundation && !isElectron) {
217
- NSLog(@"โœ… macOS 15+ detected - ScreenCaptureKit available with full compatibility");
208
+ // Electron-first priority: This application is built for Electron.js
209
+ // macOS 15+ โ†’ ScreenCaptureKit (including Electron)
210
+ // macOS 14/13 โ†’ AVFoundation (including Electron)
211
+ if (isM15Plus && !forceAVFoundation) {
212
+ if (isElectron) {
213
+ NSLog(@"โšก ELECTRON PRIORITY: macOS 15+ Electron โ†’ ScreenCaptureKit with full support");
214
+ } else {
215
+ NSLog(@"โœ… macOS 15+ Node.js โ†’ ScreenCaptureKit available with full compatibility");
216
+ }
218
217
 
219
218
  // Try ScreenCaptureKit with extensive safety measures
220
219
  @try {
@@ -288,25 +287,30 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
288
287
  // If we reach here, ScreenCaptureKit failed, so fall through to AVFoundation
289
288
  NSLog(@"โญ๏ธ ScreenCaptureKit failed - falling back to AVFoundation");
290
289
  } else {
291
- // macOS 14/13 or forced AVFoundation - ALWAYS use AVFoundation
290
+ // macOS 14/13 or forced AVFoundation โ†’ ALWAYS use AVFoundation (Electron supported!)
292
291
  if (isElectron) {
293
- NSLog(@"โŒ Electron environment - Recording not supported");
294
- return Napi::Boolean::New(env, false);
292
+ if (isM14Plus) {
293
+ NSLog(@"โšก ELECTRON PRIORITY: macOS 14/13 Electron โ†’ AVFoundation with full support");
294
+ } else if (isM13Plus) {
295
+ NSLog(@"โšก ELECTRON PRIORITY: macOS 13 Electron โ†’ AVFoundation with limited features");
296
+ }
297
+ } else {
298
+ if (isM15Plus) {
299
+ NSLog(@"๐ŸŽฏ macOS 15+ Node.js with FORCE_AVFOUNDATION โ†’ using AVFoundation");
300
+ } else if (isM14Plus) {
301
+ NSLog(@"๐ŸŽฏ macOS 14 Node.js โ†’ using AVFoundation (primary method)");
302
+ } else if (isM13Plus) {
303
+ NSLog(@"๐ŸŽฏ macOS 13 Node.js โ†’ using AVFoundation (limited features)");
304
+ }
295
305
  }
296
306
 
297
- if (isM15Plus) {
298
- NSLog(@"๐ŸŽฏ macOS 15+ with FORCE_AVFOUNDATION - using AVFoundation");
299
- } else if (isM14Plus) {
300
- NSLog(@"๐ŸŽฏ macOS 14 detected - using AVFoundation (primary method)");
301
- } else if (isM13Plus) {
302
- NSLog(@"๐ŸŽฏ macOS 13 detected - using AVFoundation (limited features)");
303
- } else {
307
+ if (!isM13Plus) {
304
308
  NSLog(@"โŒ macOS version too old (< 13.0) - Not supported");
305
309
  return Napi::Boolean::New(env, false);
306
310
  }
307
311
 
308
- // DIRECT AVFoundation - NO fallback logic needed
309
- NSLog(@"โญ๏ธ Using AVFoundation directly - no ScreenCaptureKit attempts");
312
+ // DIRECT AVFoundation for all environments (Node.js + Electron)
313
+ NSLog(@"โญ๏ธ Using AVFoundation directly - supports both Node.js and Electron");
310
314
  }
311
315
 
312
316
  // AVFoundation recording (either fallback from ScreenCaptureKit or direct)
@@ -366,32 +370,26 @@ Napi::Value StopRecording(const Napi::CallbackInfo& info) {
366
370
  }
367
371
  }
368
372
 
369
- // Try AVFoundation fallback (only in non-Electron environments)
370
- BOOL isElectron = (NSBundle.mainBundle.bundleIdentifier &&
371
- [NSBundle.mainBundle.bundleIdentifier containsString:@"electron"]) ||
372
- (NSProcessInfo.processInfo.processName &&
373
- [NSProcessInfo.processInfo.processName containsString:@"Electron"]) ||
374
- (NSProcessInfo.processInfo.environment[@"ELECTRON_RUN_AS_NODE"] != nil);
373
+ // Try AVFoundation fallback (supports both Node.js and Electron)
374
+ extern bool isAVFoundationRecording();
375
+ extern bool stopAVFoundationRecording();
375
376
 
376
- if (!isElectron) {
377
- extern bool isAVFoundationRecording();
378
- extern bool stopAVFoundationRecording();
379
-
380
- @try {
381
- if (isAVFoundationRecording()) {
382
- NSLog(@"๐Ÿ›‘ Stopping AVFoundation recording");
383
- if (stopAVFoundationRecording()) {
384
- g_isRecording = false;
385
- return Napi::Boolean::New(env, true);
386
- } else {
387
- NSLog(@"โŒ Failed to stop AVFoundation recording");
388
- g_isRecording = false;
389
- return Napi::Boolean::New(env, false);
390
- }
377
+ @try {
378
+ if (isAVFoundationRecording()) {
379
+ NSLog(@"๐Ÿ›‘ Stopping AVFoundation recording");
380
+ if (stopAVFoundationRecording()) {
381
+ g_isRecording = false;
382
+ return Napi::Boolean::New(env, true);
383
+ } else {
384
+ NSLog(@"โŒ Failed to stop AVFoundation recording");
385
+ g_isRecording = false;
386
+ return Napi::Boolean::New(env, false);
391
387
  }
392
- } @catch (NSException *exception) {
393
- NSLog(@"โŒ Exception stopping AVFoundation: %@", exception.reason);
394
388
  }
389
+ } @catch (NSException *exception) {
390
+ NSLog(@"โŒ Exception stopping AVFoundation: %@", exception.reason);
391
+ g_isRecording = false;
392
+ return Napi::Boolean::New(env, false);
395
393
  }
396
394
 
397
395
  NSLog(@"โš ๏ธ No active recording found to stop");
@@ -662,14 +660,8 @@ Napi::Value GetRecordingStatus(const Napi::CallbackInfo& info) {
662
660
  }
663
661
  }
664
662
 
665
- // Check AVFoundation only in non-Electron environments
666
- BOOL isElectron = (NSBundle.mainBundle.bundleIdentifier &&
667
- [NSBundle.mainBundle.bundleIdentifier containsString:@"electron"]) ||
668
- (NSProcessInfo.processInfo.processName &&
669
- [NSProcessInfo.processInfo.processName containsString:@"Electron"]) ||
670
- (NSProcessInfo.processInfo.environment[@"ELECTRON_RUN_AS_NODE"] != nil);
671
-
672
- if (!isElectron && isAVFoundationRecording()) {
663
+ // Check AVFoundation (supports both Node.js and Electron)
664
+ if (isAVFoundationRecording()) {
673
665
  isRecording = true;
674
666
  }
675
667
 
@@ -935,8 +927,8 @@ Napi::Value CheckPermissions(const Napi::CallbackInfo& info) {
935
927
  NSLog(@"๐Ÿ”’ Permission check for macOS %ld.%ld.%ld",
936
928
  (long)osVersion.majorVersion, (long)osVersion.minorVersion, (long)osVersion.patchVersion);
937
929
 
938
- // Determine which framework will be used
939
- BOOL willUseScreenCaptureKit = (isM15Plus && !forceAVFoundation && !isElectron);
930
+ // Determine which framework will be used (Electron fully supported!)
931
+ BOOL willUseScreenCaptureKit = (isM15Plus && !forceAVFoundation); // Electron can use ScreenCaptureKit on macOS 15+
940
932
  BOOL willUseAVFoundation = (!willUseScreenCaptureKit && (isM13Plus || isM14Plus));
941
933
 
942
934
  if (willUseScreenCaptureKit) {