native-recorder-nodejs 1.0.7 → 1.1.1

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 (46) hide show
  1. package/CMakeLists.txt +1 -0
  2. package/README.md +17 -3
  3. package/dist/bindings.js +129 -4
  4. package/native/mac/AVFEngine.mm +5 -5
  5. package/native/mac/SCKAudioCapture.mm +5 -5
  6. package/package.json +6 -2
  7. package/prebuilds/darwin-arm64/NativeAudioSDK.node +0 -0
  8. package/prebuilds/darwin-arm64/electron-v29/NativeAudioSDK.node +0 -0
  9. package/prebuilds/darwin-arm64/electron-v30/NativeAudioSDK.node +0 -0
  10. package/prebuilds/darwin-arm64/electron-v31/NativeAudioSDK.node +0 -0
  11. package/prebuilds/darwin-arm64/electron-v32/NativeAudioSDK.node +0 -0
  12. package/prebuilds/darwin-arm64/electron-v33/NativeAudioSDK.node +0 -0
  13. package/prebuilds/darwin-arm64/electron-v34/NativeAudioSDK.node +0 -0
  14. package/prebuilds/darwin-arm64/electron-v35/NativeAudioSDK.node +0 -0
  15. package/prebuilds/darwin-arm64/electron-v36/NativeAudioSDK.node +0 -0
  16. package/prebuilds/darwin-arm64/electron-v37/NativeAudioSDK.node +0 -0
  17. package/prebuilds/darwin-arm64/electron-v38/NativeAudioSDK.node +0 -0
  18. package/prebuilds/darwin-arm64/electron-v39/NativeAudioSDK.node +0 -0
  19. package/prebuilds/darwin-arm64/electron-v40/NativeAudioSDK.node +0 -0
  20. package/prebuilds/darwin-x64/NativeAudioSDK.node +0 -0
  21. package/prebuilds/darwin-x64/electron-v29/NativeAudioSDK.node +0 -0
  22. package/prebuilds/darwin-x64/electron-v30/NativeAudioSDK.node +0 -0
  23. package/prebuilds/darwin-x64/electron-v31/NativeAudioSDK.node +0 -0
  24. package/prebuilds/darwin-x64/electron-v32/NativeAudioSDK.node +0 -0
  25. package/prebuilds/darwin-x64/electron-v33/NativeAudioSDK.node +0 -0
  26. package/prebuilds/darwin-x64/electron-v34/NativeAudioSDK.node +0 -0
  27. package/prebuilds/darwin-x64/electron-v35/NativeAudioSDK.node +0 -0
  28. package/prebuilds/darwin-x64/electron-v36/NativeAudioSDK.node +0 -0
  29. package/prebuilds/darwin-x64/electron-v37/NativeAudioSDK.node +0 -0
  30. package/prebuilds/darwin-x64/electron-v38/NativeAudioSDK.node +0 -0
  31. package/prebuilds/darwin-x64/electron-v39/NativeAudioSDK.node +0 -0
  32. package/prebuilds/darwin-x64/electron-v40/NativeAudioSDK.node +0 -0
  33. package/prebuilds/win32-x64/NativeAudioSDK.node +0 -0
  34. package/prebuilds/win32-x64/electron-v29/NativeAudioSDK.node +0 -0
  35. package/prebuilds/win32-x64/electron-v30/NativeAudioSDK.node +0 -0
  36. package/prebuilds/win32-x64/electron-v31/NativeAudioSDK.node +0 -0
  37. package/prebuilds/win32-x64/electron-v32/NativeAudioSDK.node +0 -0
  38. package/prebuilds/win32-x64/electron-v33/NativeAudioSDK.node +0 -0
  39. package/prebuilds/win32-x64/electron-v34/NativeAudioSDK.node +0 -0
  40. package/prebuilds/win32-x64/electron-v35/NativeAudioSDK.node +0 -0
  41. package/prebuilds/win32-x64/electron-v36/NativeAudioSDK.node +0 -0
  42. package/prebuilds/win32-x64/electron-v37/NativeAudioSDK.node +0 -0
  43. package/prebuilds/win32-x64/electron-v38/NativeAudioSDK.node +0 -0
  44. package/prebuilds/win32-x64/electron-v39/NativeAudioSDK.node +0 -0
  45. package/prebuilds/win32-x64/electron-v40/NativeAudioSDK.node +0 -0
  46. package/src/bindings.ts +161 -5
package/CMakeLists.txt CHANGED
@@ -29,6 +29,7 @@ if(WIN32)
29
29
  )
30
30
  add_definitions(-DNAPI_CPP_EXCEPTIONS -DWIN32_LEAN_AND_MEAN)
31
31
  elseif(APPLE)
32
+ set(CMAKE_OSX_DEPLOYMENT_TARGET "13.0")
32
33
  set(CMAKE_OBJCXX_FLAGS "${CMAKE_OBJCXX_FLAGS} -fobjc-arc")
33
34
  list(APPEND ENGINE_SOURCES
34
35
  native/mac/AVFEngine.mm
package/README.md CHANGED
@@ -21,8 +21,8 @@ High-performance, low-latency native audio recording SDK for Node.js. Supports b
21
21
  | ------------- | ------------ | ----------- | ----------------------- |
22
22
  | Windows 10/11 | x64 | Supported | Supported (per-device) |
23
23
  | Windows 10/11 | ia32 | Supported | Supported (per-device) |
24
- | macOS 12.3+ | arm64 | Supported | Supported (system-wide) |
25
- | macOS 12.3+ | x64 | Supported | Supported (system-wide) |
24
+ | macOS 13.0+ | arm64 | Supported | Supported (system-wide) |
25
+ | macOS 13.0+ | x64 | Supported | Supported (system-wide) |
26
26
 
27
27
  ### Platform Differences
28
28
 
@@ -31,7 +31,7 @@ High-performance, low-latency native audio recording SDK for Node.js. Supports b
31
31
  | Input Devices | Multiple (WASAPI) | Multiple (AVFoundation) |
32
32
  | Output Devices | Multiple (per-device) | Single "System Audio" device |
33
33
  | Permissions | None required | Microphone + Screen Recording |
34
- | Min OS Version | Windows 10+ | macOS 12.3+ (for system audio) |
34
+ | Min OS Version | Windows 10+ | macOS 13.0+ (for system audio) |
35
35
 
36
36
  ## Installation
37
37
 
@@ -41,6 +41,20 @@ npm install native-recorder-nodejs
41
41
 
42
42
  Prebuilt binaries are available for most platforms. If a prebuild is not available, the package will compile from source (requires CMake and a C++ compiler).
43
43
 
44
+ ### Electron Support
45
+
46
+ This package includes prebuilt binaries for Electron 29-40. In most cases, it will work out of the box. If you encounter issues:
47
+
48
+ ```bash
49
+ # Option 1: Use electron-rebuild (recommended)
50
+ npx electron-rebuild
51
+
52
+ # Option 2: Rebuild using cmake-js with Electron runtime
53
+ npx cmake-js compile --runtime=electron --runtime-version=YOUR_ELECTRON_VERSION --arch=x64
54
+ ```
55
+
56
+ **Note**: Make sure to rebuild native modules after updating Electron version.
57
+
44
58
  ## Quick Start
45
59
 
46
60
  ```typescript
package/dist/bindings.js CHANGED
@@ -2,30 +2,155 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const path_1 = require("path");
4
4
  const fs_1 = require("fs");
5
+ /**
6
+ * Detect if running in Electron environment
7
+ */
8
+ function isElectron() {
9
+ // Check for Electron-specific process properties
10
+ if (typeof process !== "undefined") {
11
+ // @ts-ignore
12
+ if (process.versions && process.versions.electron) {
13
+ return true;
14
+ }
15
+ // Check for Electron renderer process
16
+ if (typeof globalThis !== "undefined" &&
17
+ // @ts-ignore
18
+ globalThis.window &&
19
+ // @ts-ignore
20
+ globalThis.window.process &&
21
+ // @ts-ignore
22
+ globalThis.window.process.type === "renderer") {
23
+ return true;
24
+ }
25
+ }
26
+ return false;
27
+ }
28
+ /**
29
+ * Get Electron major version number
30
+ */
31
+ function getElectronMajorVersion() {
32
+ try {
33
+ // @ts-ignore
34
+ const electronVersion = process.versions.electron;
35
+ if (!electronVersion)
36
+ return null;
37
+ return parseInt(electronVersion.split(".")[0], 10);
38
+ }
39
+ catch {
40
+ return null;
41
+ }
42
+ }
43
+ /**
44
+ * Find available Electron prebuild versions in the prebuilds directory
45
+ */
46
+ function findAvailableElectronVersions(platformDir) {
47
+ try {
48
+ if (!(0, fs_1.existsSync)(platformDir))
49
+ return [];
50
+ const dirs = (0, fs_1.readdirSync)(platformDir, { withFileTypes: true });
51
+ const versions = [];
52
+ for (const dir of dirs) {
53
+ if (dir.isDirectory() && dir.name.startsWith("electron-v")) {
54
+ const version = parseInt(dir.name.replace("electron-v", ""), 10);
55
+ if (!isNaN(version)) {
56
+ versions.push(version);
57
+ }
58
+ }
59
+ }
60
+ return versions.sort((a, b) => b - a); // Sort descending (newest first)
61
+ }
62
+ catch {
63
+ return [];
64
+ }
65
+ }
66
+ /**
67
+ * Find the closest available Electron prebuild version
68
+ * Prefers newer versions that are closest to the target
69
+ */
70
+ function findClosestElectronVersion(targetVersion, availableVersions) {
71
+ if (availableVersions.length === 0)
72
+ return null;
73
+ // First, try to find an exact match
74
+ if (availableVersions.includes(targetVersion)) {
75
+ return targetVersion;
76
+ }
77
+ // Find the closest version (prefer slightly older over much newer for stability)
78
+ let closest = null;
79
+ let minDiff = Infinity;
80
+ for (const version of availableVersions) {
81
+ const diff = Math.abs(version - targetVersion);
82
+ // Prefer older versions when diff is the same (more stable)
83
+ if (diff < minDiff || (diff === minDiff && version < targetVersion)) {
84
+ minDiff = diff;
85
+ closest = version;
86
+ }
87
+ }
88
+ return closest;
89
+ }
5
90
  function getBinding() {
6
91
  const moduleName = "NativeAudioSDK.node";
7
92
  // Try prebuild first (installed via npm)
8
93
  const prebuildsDir = (0, path_1.join)(__dirname, "..", "prebuilds");
9
94
  const platform = process.platform;
10
95
  const arch = process.arch;
96
+ const platformDir = (0, path_1.join)(prebuildsDir, `${platform}-${arch}`);
97
+ const paths = [];
98
+ // For Electron, try Electron-specific prebuild first
99
+ if (isElectron()) {
100
+ const electronMajor = getElectronMajorVersion();
101
+ if (electronMajor) {
102
+ // Try exact version match first
103
+ const exactPath = (0, path_1.join)(platformDir, `electron-v${electronMajor}`, moduleName);
104
+ paths.push(exactPath);
105
+ if ((0, fs_1.existsSync)(exactPath)) {
106
+ return require(exactPath);
107
+ }
108
+ // Fallback: find closest available Electron version
109
+ const availableVersions = findAvailableElectronVersions(platformDir);
110
+ const closestVersion = findClosestElectronVersion(electronMajor, availableVersions);
111
+ if (closestVersion && closestVersion !== electronMajor) {
112
+ const fallbackPath = (0, path_1.join)(platformDir, `electron-v${closestVersion}`, moduleName);
113
+ paths.push(fallbackPath);
114
+ if ((0, fs_1.existsSync)(fallbackPath)) {
115
+ console.warn(`[native-recorder-nodejs] No prebuild for Electron ${electronMajor}, ` +
116
+ `using Electron ${closestVersion} prebuild as fallback. ` +
117
+ `Consider running 'npx electron-rebuild' for best compatibility.`);
118
+ return require(fallbackPath);
119
+ }
120
+ }
121
+ }
122
+ }
123
+ // Try N-API prebuild (works for both Node.js and Electron with N-API support)
124
+ // Since this module uses N-API, the Node.js prebuild should work in Electron too
11
125
  // prebuild-install stores binaries in: prebuilds/{platform}-{arch}/
12
- const prebuildPath = (0, path_1.join)(prebuildsDir, `${platform}-${arch}`, moduleName);
126
+ const prebuildPath = (0, path_1.join)(platformDir, moduleName);
127
+ paths.push(prebuildPath);
13
128
  if ((0, fs_1.existsSync)(prebuildPath)) {
14
129
  return require(prebuildPath);
15
130
  }
131
+ // Also try napi-v8 subfolder format
132
+ const napiPrebuildPath = (0, path_1.join)(platformDir, "napi-v8", moduleName);
133
+ paths.push(napiPrebuildPath);
134
+ if ((0, fs_1.existsSync)(napiPrebuildPath)) {
135
+ return require(napiPrebuildPath);
136
+ }
16
137
  // Fallback to local build (development)
17
138
  const localPath = (0, path_1.join)(__dirname, "..", "build", "Release", moduleName);
139
+ paths.push(localPath);
18
140
  if ((0, fs_1.existsSync)(localPath)) {
19
141
  return require(localPath);
20
142
  }
21
143
  // Final fallback for different build configurations
22
144
  const debugPath = (0, path_1.join)(__dirname, "..", "build", "Debug", moduleName);
145
+ paths.push(debugPath);
23
146
  if ((0, fs_1.existsSync)(debugPath)) {
24
147
  return require(debugPath);
25
148
  }
26
- throw new Error(`Could not find native module ${moduleName}. ` +
27
- `Tried:\n - ${prebuildPath}\n - ${localPath}\n - ${debugPath}\n` +
28
- `Please run 'npm run build:native' or reinstall the package.`);
149
+ const runtime = isElectron() ? "Electron" : "Node.js";
150
+ throw new Error(`Could not find native module ${moduleName} for ${runtime}. ` +
151
+ `Tried:\n - ${paths.join("\n - ")}\n` +
152
+ `Please run 'npm run build:native' or reinstall the package.\n` +
153
+ `For Electron users: try running 'npx electron-rebuild' in your project.`);
29
154
  }
30
155
  const bindings = getBinding();
31
156
  exports.default = bindings;
@@ -77,10 +77,10 @@ void AVFEngine::Start(const std::string &deviceType, const std::string &deviceId
77
77
  return;
78
78
  }
79
79
 
80
- if (@available(macOS 12.3, *)) {
80
+ if (@available(macOS 13.0, *)) {
81
81
  [impl->sckCapture startWithCallback:dataCb errorCallback:errorCb];
82
82
  } else {
83
- if (errorCb) errorCb("System audio recording requires macOS 12.3 or later.");
83
+ if (errorCb) errorCb("System audio recording requires macOS 13.0 or later.");
84
84
  }
85
85
  return;
86
86
  }
@@ -166,7 +166,7 @@ std::vector<AudioDevice> AVFEngine::GetDevices() {
166
166
  }
167
167
 
168
168
  // Add system audio output device (only one on macOS)
169
- if (@available(macOS 12.3, *)) {
169
+ if (@available(macOS 13.0, *)) {
170
170
  AudioDevice systemDevice;
171
171
  systemDevice.id = AudioEngine::SYSTEM_AUDIO_DEVICE_ID;
172
172
  systemDevice.name = "System Audio";
@@ -217,7 +217,7 @@ PermissionStatus AVFEngine::CheckPermission() {
217
217
  // Check screen capture permission (for system audio)
218
218
  // ScreenCaptureKit doesn't have a direct permission check API,
219
219
  // but we can check if we can get shareable content
220
- if (@available(macOS 12.3, *)) {
220
+ if (@available(macOS 13.0, *)) {
221
221
  __block BOOL hasScreenPermission = NO;
222
222
  dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
223
223
 
@@ -255,7 +255,7 @@ bool AVFEngine::RequestPermission(PermissionType type) {
255
255
  }
256
256
  else if (type == PermissionType::System) {
257
257
  // Request screen capture permission for system audio
258
- if (@available(macOS 12.3, *)) {
258
+ if (@available(macOS 13.0, *)) {
259
259
  // Attempting to get shareable content will trigger the permission prompt
260
260
  // if not already granted
261
261
  [SCShareableContent getShareableContentWithCompletionHandler:^(SCShareableContent * _Nullable shareableContent, NSError * _Nullable error) {
@@ -29,7 +29,7 @@
29
29
  self.dataCallback = dataCb;
30
30
  self.errorCallback = errorCb;
31
31
 
32
- if (@available(macOS 12.3, *)) {
32
+ if (@available(macOS 13.0, *)) {
33
33
  [SCShareableContent getShareableContentExcludingDesktopWindows:YES
34
34
  onScreenWindowsOnly:NO
35
35
  completionHandler:^(SCShareableContent *content, NSError *error) {
@@ -76,12 +76,12 @@
76
76
  });
77
77
  }];
78
78
  } else {
79
- if (self.errorCallback) self.errorCallback("ScreenCaptureKit is only available on macOS 12.3+");
79
+ if (self.errorCallback) self.errorCallback("ScreenCaptureKit audio capture requires macOS 13.0+");
80
80
  }
81
81
  }
82
82
 
83
83
  - (void)stop {
84
- if (@available(macOS 12.3, *)) {
84
+ if (@available(macOS 13.0, *)) {
85
85
  if (self.stream) {
86
86
  [self.stream stopCaptureWithCompletionHandler:nil];
87
87
  self.stream = nil;
@@ -92,7 +92,7 @@
92
92
  - (void)stream:(SCStream *)stream didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer ofType:(SCStreamOutputType)type {
93
93
  if (type != SCStreamOutputTypeAudio || !self.dataCallback) return;
94
94
 
95
- if (@available(macOS 12.3, *)) {
95
+ if (@available(macOS 13.0, *)) {
96
96
  CMFormatDescriptionRef formatDesc = CMSampleBufferGetFormatDescription(sampleBuffer);
97
97
  const AudioStreamBasicDescription *asbd = CMAudioFormatDescriptionGetStreamBasicDescription(formatDesc);
98
98
 
@@ -203,7 +203,7 @@
203
203
 
204
204
  // SCStreamDelegate method
205
205
  - (void)stream:(SCStream *)stream didStopWithError:(NSError *)error {
206
- if (@available(macOS 12.3, *)) {
206
+ if (@available(macOS 13.0, *)) {
207
207
  if (error && self.errorCallback) {
208
208
  self.errorCallback("Stream stopped with error: " + std::string(error.localizedDescription.UTF8String));
209
209
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "native-recorder-nodejs",
3
- "version": "1.0.7",
3
+ "version": "1.1.1",
4
4
  "description": "Cross-platform (Win/Mac) Native Audio SDK for Node.js",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -11,11 +11,14 @@
11
11
  "build:native:debug": "cmake-js compile --debug",
12
12
  "build:tests": "cmake -S . -B build -DBUILD_TESTS=ON && cmake --build build --config Release --target NativeTests",
13
13
  "prebuild": "prebuild --backend cmake-js -r napi --strip",
14
+ "prebuild:electron": "prebuild --backend cmake-js -r electron -t 29.0.0 -t 30.0.0 -t 31.0.0 -t 32.0.0 -t 33.0.0 -t 34.0.0 -t 35.0.0 -t 36.0.0 -t 37.0.0 -t 38.0.0 -t 39.0.0 -t 40.0.0 --strip",
15
+ "prebuild:all": "npm run prebuild && npm run prebuild:electron",
14
16
  "prebuild:upload": "prebuild --upload-all ${GITHUB_TOKEN}",
15
17
  "test": "jest",
16
18
  "test:native": "npm run build:tests && cd build && ctest -C Release --output-on-failure",
17
19
  "test:cli": "node ./test/cli_test.js",
18
20
  "clean": "rimraf dist build prebuilds",
21
+ "install": "prebuild-install || npm run build:native",
19
22
  "prepublishOnly": "npm run build:ts",
20
23
  "release": "npm version patch && git push && git push --tags",
21
24
  "release:minor": "npm version minor && git push && git push --tags",
@@ -57,7 +60,8 @@
57
60
  ]
58
61
  },
59
62
  "dependencies": {
60
- "node-addon-api": "^7.0.0"
63
+ "node-addon-api": "^7.0.0",
64
+ "prebuild-install": "^7.1.2"
61
65
  },
62
66
  "devDependencies": {
63
67
  "@types/jest": "^29.5.0",
package/src/bindings.ts CHANGED
@@ -1,5 +1,102 @@
1
1
  import { join, dirname } from "path";
2
- import { existsSync } from "fs";
2
+ import { existsSync, readdirSync } from "fs";
3
+
4
+ /**
5
+ * Detect if running in Electron environment
6
+ */
7
+ function isElectron(): boolean {
8
+ // Check for Electron-specific process properties
9
+ if (typeof process !== "undefined") {
10
+ // @ts-ignore
11
+ if (process.versions && process.versions.electron) {
12
+ return true;
13
+ }
14
+ // Check for Electron renderer process
15
+ if (
16
+ typeof globalThis !== "undefined" &&
17
+ // @ts-ignore
18
+ globalThis.window &&
19
+ // @ts-ignore
20
+ globalThis.window.process &&
21
+ // @ts-ignore
22
+ globalThis.window.process.type === "renderer"
23
+ ) {
24
+ return true;
25
+ }
26
+ }
27
+ return false;
28
+ }
29
+
30
+ /**
31
+ * Get Electron major version number
32
+ */
33
+ function getElectronMajorVersion(): number | null {
34
+ try {
35
+ // @ts-ignore
36
+ const electronVersion = process.versions.electron;
37
+ if (!electronVersion) return null;
38
+
39
+ return parseInt(electronVersion.split(".")[0], 10);
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Find available Electron prebuild versions in the prebuilds directory
47
+ */
48
+ function findAvailableElectronVersions(platformDir: string): number[] {
49
+ try {
50
+ if (!existsSync(platformDir)) return [];
51
+
52
+ const dirs = readdirSync(platformDir, { withFileTypes: true });
53
+ const versions: number[] = [];
54
+
55
+ for (const dir of dirs) {
56
+ if (dir.isDirectory() && dir.name.startsWith("electron-v")) {
57
+ const version = parseInt(dir.name.replace("electron-v", ""), 10);
58
+ if (!isNaN(version)) {
59
+ versions.push(version);
60
+ }
61
+ }
62
+ }
63
+
64
+ return versions.sort((a, b) => b - a); // Sort descending (newest first)
65
+ } catch {
66
+ return [];
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Find the closest available Electron prebuild version
72
+ * Prefers newer versions that are closest to the target
73
+ */
74
+ function findClosestElectronVersion(
75
+ targetVersion: number,
76
+ availableVersions: number[],
77
+ ): number | null {
78
+ if (availableVersions.length === 0) return null;
79
+
80
+ // First, try to find an exact match
81
+ if (availableVersions.includes(targetVersion)) {
82
+ return targetVersion;
83
+ }
84
+
85
+ // Find the closest version (prefer slightly older over much newer for stability)
86
+ let closest: number | null = null;
87
+ let minDiff = Infinity;
88
+
89
+ for (const version of availableVersions) {
90
+ const diff = Math.abs(version - targetVersion);
91
+ // Prefer older versions when diff is the same (more stable)
92
+ if (diff < minDiff || (diff === minDiff && version < targetVersion)) {
93
+ minDiff = diff;
94
+ closest = version;
95
+ }
96
+ }
97
+
98
+ return closest;
99
+ }
3
100
 
4
101
  function getBinding() {
5
102
  const moduleName = "NativeAudioSDK.node";
@@ -8,30 +105,89 @@ function getBinding() {
8
105
  const prebuildsDir = join(__dirname, "..", "prebuilds");
9
106
  const platform = process.platform;
10
107
  const arch = process.arch;
108
+ const platformDir = join(prebuildsDir, `${platform}-${arch}`);
109
+
110
+ const paths: string[] = [];
11
111
 
112
+ // For Electron, try Electron-specific prebuild first
113
+ if (isElectron()) {
114
+ const electronMajor = getElectronMajorVersion();
115
+ if (electronMajor) {
116
+ // Try exact version match first
117
+ const exactPath = join(
118
+ platformDir,
119
+ `electron-v${electronMajor}`,
120
+ moduleName,
121
+ );
122
+ paths.push(exactPath);
123
+ if (existsSync(exactPath)) {
124
+ return require(exactPath);
125
+ }
126
+
127
+ // Fallback: find closest available Electron version
128
+ const availableVersions = findAvailableElectronVersions(platformDir);
129
+ const closestVersion = findClosestElectronVersion(
130
+ electronMajor,
131
+ availableVersions,
132
+ );
133
+
134
+ if (closestVersion && closestVersion !== electronMajor) {
135
+ const fallbackPath = join(
136
+ platformDir,
137
+ `electron-v${closestVersion}`,
138
+ moduleName,
139
+ );
140
+ paths.push(fallbackPath);
141
+ if (existsSync(fallbackPath)) {
142
+ console.warn(
143
+ `[native-recorder-nodejs] No prebuild for Electron ${electronMajor}, ` +
144
+ `using Electron ${closestVersion} prebuild as fallback. ` +
145
+ `Consider running 'npx electron-rebuild' for best compatibility.`,
146
+ );
147
+ return require(fallbackPath);
148
+ }
149
+ }
150
+ }
151
+ }
152
+
153
+ // Try N-API prebuild (works for both Node.js and Electron with N-API support)
154
+ // Since this module uses N-API, the Node.js prebuild should work in Electron too
12
155
  // prebuild-install stores binaries in: prebuilds/{platform}-{arch}/
13
- const prebuildPath = join(prebuildsDir, `${platform}-${arch}`, moduleName);
156
+ const prebuildPath = join(platformDir, moduleName);
157
+ paths.push(prebuildPath);
14
158
 
15
159
  if (existsSync(prebuildPath)) {
16
160
  return require(prebuildPath);
17
161
  }
18
162
 
163
+ // Also try napi-v8 subfolder format
164
+ const napiPrebuildPath = join(platformDir, "napi-v8", moduleName);
165
+ paths.push(napiPrebuildPath);
166
+
167
+ if (existsSync(napiPrebuildPath)) {
168
+ return require(napiPrebuildPath);
169
+ }
170
+
19
171
  // Fallback to local build (development)
20
172
  const localPath = join(__dirname, "..", "build", "Release", moduleName);
173
+ paths.push(localPath);
21
174
  if (existsSync(localPath)) {
22
175
  return require(localPath);
23
176
  }
24
177
 
25
178
  // Final fallback for different build configurations
26
179
  const debugPath = join(__dirname, "..", "build", "Debug", moduleName);
180
+ paths.push(debugPath);
27
181
  if (existsSync(debugPath)) {
28
182
  return require(debugPath);
29
183
  }
30
184
 
185
+ const runtime = isElectron() ? "Electron" : "Node.js";
31
186
  throw new Error(
32
- `Could not find native module ${moduleName}. ` +
33
- `Tried:\n - ${prebuildPath}\n - ${localPath}\n - ${debugPath}\n` +
34
- `Please run 'npm run build:native' or reinstall the package.`
187
+ `Could not find native module ${moduleName} for ${runtime}. ` +
188
+ `Tried:\n - ${paths.join("\n - ")}\n` +
189
+ `Please run 'npm run build:native' or reinstall the package.\n` +
190
+ `For Electron users: try running 'npx electron-rebuild' in your project.`,
35
191
  );
36
192
  }
37
193