@recallai/desktop-sdk 1.0.6 → 1.2.0

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 (4) hide show
  1. package/CHANGELOG.md +34 -2
  2. package/index.d.ts +126 -160
  3. package/index.js +338 -276
  4. package/package.json +5 -3
package/CHANGELOG.md CHANGED
@@ -1,8 +1,40 @@
1
- ### 1.0.6 [patch] (MacOS) - 2025-07-11
1
+ ### 1.2.0 [minor] (MacOS+Windows) - 2025-09-26
2
+
3
+ - Initial support for Google Meet on Windows
4
+ - Fixed issues with participant mapping on Windows
5
+ - Mixed audio mp3 support for Windows
6
+ - Zoom, Teams, Google Meet url scraping for Windows
7
+ - Gladia streaming support for transcription
8
+ - Fixed an issue where Zoom video capture would be affected when chat is open
9
+ - Improved acoustic audio cancellation
10
+ - Added the ability to get URL from Zoom and Teams meetings
11
+ - Various improvements for Google Meet support on macOS
12
+ - Speeded up starting a recording
13
+ - Caused recording to stop when the laptop lid is closed
14
+
15
+ ### 1.1.0 [minor] (MacOS+Windows) - 2025-08-29
16
+
17
+ - Reduce idle power consumption by 80%
18
+ - Add capture support for MS Teams on MacOS
19
+ - Add capture support for MS Teams on Windows
20
+ - Fixed a bug detecting Zoom on Windows for non-premium zoom members
21
+ - Fixed a bug capuring video on old Win10 Windows installs
22
+ - Add support for gathering meeting urls for Zoom/Teams on Windows
23
+ - Add support for AssemblyAI Universal Streaming Model (v3)
24
+ - Add support for transcript.partial_data
25
+ - Fix permissions to allow system only audio without requiring an initial request
26
+ - Add Google Meet url to URL field
27
+ - Generate index.js and index.d.ts from an index.ts file
28
+ - Prevent rogue screen capture kit calls from causing CPU drain in system audio only mode
29
+ - Fix memory growth during recording
30
+ - Fix memory leaks on idle
31
+ - Prevent zombie exe on stdin kill
32
+
33
+ ### 1.0.6 [patch] (MacOS) - 2025-08-15
2
34
 
3
35
  - Fixes a bug where whole-desktop audio capture would produce corrupted recordings.
4
36
 
5
- ### 1.0.5 [patch] (MacOS) - 2025-07-11
37
+ ### 1.0.5 [patch] (MacOS) - 2025-08-11
6
38
 
7
39
  - Fixes a bug that would cause an occasional crash.
8
40
 
package/index.d.ts CHANGED
@@ -1,163 +1,129 @@
1
- declare module '@recallai/desktop-sdk' {
2
- export type RecallAiSdkEvent =
3
- | RecordingStartEvent
4
- | RecordingStopEvent
5
- | UploadProgressEvent
6
- | MeetingDetectedEvent
7
- | MeetingUpdatedEvent
8
- | MeetingClosedEvent
9
- | SdkStateChangeEvent
10
- | ErrorEvent
11
- | MediaCaptureStatusEvent
12
- | ParticipantCaptureStatusEvent
13
- | PermissionsGrantedEvent
14
- | RealtimeEvent
15
- | ShutdownEvent;
16
-
17
- export type EventTypeToPayloadMap = {
18
- 'recording-started': RecordingStartEvent;
19
- 'recording-ended': RecordingStopEvent;
20
- 'upload-progress': UploadProgressEvent;
21
- 'meeting-detected': MeetingDetectedEvent;
22
- 'meeting-updated': MeetingUpdatedEvent;
23
- 'meeting-closed': MeetingClosedEvent;
24
- 'sdk-state-change': SdkStateChangeEvent;
25
- 'error': ErrorEvent;
26
- 'media-capture-status': MediaCaptureStatusEvent;
27
- 'participant-capture-status': ParticipantCaptureStatusEvent;
28
- 'permissions-granted': PermissionsGrantedEvent;
29
- 'permission-status': PermissionStatusEvent;
30
- 'realtime-event': RealtimeEvent;
31
- 'shutdown': ShutdownEvent;
32
- };
33
-
34
- export type Permission =
35
- | 'accessibility'
36
- | 'screen-capture'
37
- | 'microphone'
38
- | 'system-audio' // not necessary if you have screen-capture
39
- ;
40
-
41
- export function addEventListener<T extends keyof EventTypeToPayloadMap>(
42
- type: T,
43
- listener: (event: EventTypeToPayloadMap[T]) => void
44
- ): void;
45
-
46
- export function init(config: RecallAiSdkConfig): Promise<null>;
47
- export function startRecording(config: StartRecordingConfig): Promise<null>;
48
- export function pauseRecording(config: PauseRecordingConfig): Promise<null>;
49
- export function resumeRecording(config: ResumeRecordingConfig): Promise<null>;
50
- export function stopRecording(config: StopRecordingConfig): Promise<null>;
51
- export function uploadRecording(config: UploadRecordingConfig): Promise<null>;
52
- export function prepareDesktopAudioRecording(): Promise<string>;
53
- export function requestPermission(permission: Permission): Promise<null>;
54
- export function shutdown(): Promise<null>;
55
-
56
- ///
57
-
58
- export interface RecallAiSdkWindow {
1
+ export type RecallAiSdkEvent = RecordingStartEvent | RecordingStopEvent | UploadProgressEvent | MeetingDetectedEvent | MeetingUpdatedEvent | MeetingClosedEvent | SdkStateChangeEvent | ErrorEvent | MediaCaptureStatusEvent | ParticipantCaptureStatusEvent | PermissionsGrantedEvent | RealtimeEvent | ShutdownEvent;
2
+ export type EventTypeToPayloadMap = {
3
+ 'recording-started': RecordingStartEvent;
4
+ 'recording-ended': RecordingStopEvent;
5
+ 'upload-progress': UploadProgressEvent;
6
+ 'meeting-detected': MeetingDetectedEvent;
7
+ 'meeting-updated': MeetingUpdatedEvent;
8
+ 'meeting-closed': MeetingClosedEvent;
9
+ 'sdk-state-change': SdkStateChangeEvent;
10
+ 'error': ErrorEvent;
11
+ 'media-capture-status': MediaCaptureStatusEvent;
12
+ 'participant-capture-status': ParticipantCaptureStatusEvent;
13
+ 'permissions-granted': PermissionsGrantedEvent;
14
+ 'permission-status': PermissionStatusEvent;
15
+ 'realtime-event': RealtimeEvent;
16
+ 'shutdown': ShutdownEvent;
17
+ };
18
+ export type Permission = 'accessibility' | 'screen-capture' | 'microphone' | 'system-audio';
19
+ export interface RecallAiSdkWindow {
20
+ id: string;
21
+ title?: string;
22
+ url?: string;
23
+ platform: string;
24
+ }
25
+ export interface RecallAiSdkConfig {
26
+ api_url?: string;
27
+ apiUrl?: string;
28
+ acquirePermissionsOnStartup?: Permission[];
29
+ restartOnError?: boolean;
30
+ dev?: boolean;
31
+ }
32
+ export interface StartRecordingConfig {
33
+ windowId: string;
34
+ uploadToken: string;
35
+ }
36
+ export interface StopRecordingConfig {
37
+ windowId: string;
38
+ }
39
+ export interface PauseRecordingConfig {
40
+ windowId: string;
41
+ }
42
+ export interface ResumeRecordingConfig {
43
+ windowId: string;
44
+ }
45
+ export interface UploadRecordingConfig {
46
+ windowId: string;
47
+ }
48
+ export interface RecordingStartEvent {
49
+ window: RecallAiSdkWindow;
50
+ }
51
+ export interface RecordingStopEvent {
52
+ window: RecallAiSdkWindow;
53
+ }
54
+ export interface UploadProgressEvent {
55
+ window: {
59
56
  id: string;
60
- title?: string;
61
- url?: string;
62
- platform: string;
63
- }
64
-
65
- export interface RecallAiSdkConfig {
66
- api_url?: string;
67
- apiUrl?: string;
68
- acquirePermissionsOnStartup?: Permission[];
69
- restartOnError?: boolean;
70
- }
71
-
72
- export interface StartRecordingConfig {
73
- windowId: string;
74
- uploadToken: string;
75
- }
76
-
77
- export interface StopRecordingConfig {
78
- windowId: string;
79
- }
80
-
81
- export interface PauseRecordingConfig {
82
- windowId: string;
83
- }
84
-
85
- export interface ResumeRecordingConfig {
86
- windowId: string;
87
- }
88
-
89
- export interface UploadRecordingConfig {
90
- windowId: string;
91
- }
92
-
93
- ///
94
-
95
- export interface RecordingStartEvent {
96
- window: RecallAiSdkWindow;
97
- }
98
-
99
- export interface RecordingStopEvent {
100
- window: RecallAiSdkWindow;
101
- }
102
-
103
- export interface UploadProgressEvent {
104
- window: { id: string };
105
- progress: number;
106
- }
107
-
108
- export interface MeetingDetectedEvent {
109
- window: RecallAiSdkWindow;
110
- }
111
-
112
- export interface MeetingUpdatedEvent {
113
- window: RecallAiSdkWindow;
114
- }
115
-
116
- export interface MeetingClosedEvent {
117
- window: RecallAiSdkWindow;
118
- }
119
-
120
- export interface SdkStateChangeEvent {
121
- sdk: {
122
- state: {
123
- code: 'recording' | 'idle' | 'paused';
124
- };
57
+ };
58
+ progress: number;
59
+ }
60
+ export interface MeetingDetectedEvent {
61
+ window: RecallAiSdkWindow;
62
+ }
63
+ export interface MeetingUpdatedEvent {
64
+ window: RecallAiSdkWindow;
65
+ }
66
+ export interface MeetingClosedEvent {
67
+ window: RecallAiSdkWindow;
68
+ }
69
+ export interface SdkStateChangeEvent {
70
+ sdk: {
71
+ state: {
72
+ code: 'recording' | 'idle' | 'paused';
125
73
  };
126
- }
127
-
128
- export interface MediaCaptureStatusEvent {
129
- window: RecallAiSdkWindow;
130
- type: 'video' | 'audio';
131
- capturing: boolean;
132
- }
133
-
134
- export interface ParticipantCaptureStatusEvent {
135
- window: RecallAiSdkWindow;
136
- type: 'video' | 'audio' | 'screenshare';
137
- capturing: boolean;
138
- }
139
-
140
- export interface PermissionsGrantedEvent {}
141
-
142
- export interface PermissionStatusEvent {
143
- permission: Permission;
144
- status: string;
145
- }
146
-
147
- export interface ErrorEvent {
148
- window?: RecallAiSdkWindow;
149
- type: string;
150
- message: string;
151
- }
152
-
153
- export interface RealtimeEvent {
154
- window: RecallAiSdkWindow;
155
- event: string;
156
- data: any;
157
- }
158
-
159
- export interface ShutdownEvent {
160
- code: number;
161
- signal: string;
162
- }
74
+ };
75
+ }
76
+ export interface MediaCaptureStatusEvent {
77
+ window: RecallAiSdkWindow;
78
+ type: 'video' | 'audio';
79
+ capturing: boolean;
80
+ }
81
+ export interface ParticipantCaptureStatusEvent {
82
+ window: RecallAiSdkWindow;
83
+ type: 'video' | 'audio' | 'screenshare';
84
+ capturing: boolean;
85
+ }
86
+ export interface PermissionsGrantedEvent {
87
+ }
88
+ export interface PermissionStatusEvent {
89
+ permission: Permission;
90
+ status: string;
91
+ }
92
+ export interface ErrorEvent {
93
+ window?: RecallAiSdkWindow;
94
+ type: string;
95
+ message: string;
96
+ }
97
+ export interface RealtimeEvent {
98
+ window: RecallAiSdkWindow;
99
+ event: string;
100
+ data: any;
101
+ }
102
+ export interface ShutdownEvent {
103
+ code: number;
104
+ signal: string;
163
105
  }
106
+ export declare function init(options: RecallAiSdkConfig): Promise<null>;
107
+ export declare function shutdown(): Promise<null>;
108
+ export declare function dumpAXTree(procName: string): Promise<any>;
109
+ export declare function startRecording(config: StartRecordingConfig): Promise<null>;
110
+ export declare function stopRecording({ windowId }: StopRecordingConfig): Promise<null>;
111
+ export declare function pauseRecording({ windowId }: PauseRecordingConfig): Promise<null>;
112
+ export declare function resumeRecording({ windowId }: ResumeRecordingConfig): Promise<null>;
113
+ export declare function uploadRecording({ windowId }: UploadRecordingConfig): Promise<null>;
114
+ export declare function prepareDesktopAudioRecording(): Promise<string>;
115
+ export declare function requestPermission(permission: Permission): Promise<null>;
116
+ export declare function addEventListener<T extends keyof EventTypeToPayloadMap>(type: T, callback: (event: EventTypeToPayloadMap[T]) => void): void;
117
+ declare const RecallAiSdk: {
118
+ init: typeof init;
119
+ shutdown: typeof shutdown;
120
+ startRecording: typeof startRecording;
121
+ stopRecording: typeof stopRecording;
122
+ pauseRecording: typeof pauseRecording;
123
+ resumeRecording: typeof resumeRecording;
124
+ uploadRecording: typeof uploadRecording;
125
+ prepareDesktopAudioRecording: typeof prepareDesktopAudioRecording;
126
+ requestPermission: typeof requestPermission;
127
+ addEventListener: typeof addEventListener;
128
+ };
129
+ export default RecallAiSdk;
package/index.js CHANGED
@@ -1,336 +1,398 @@
1
- const path = require('path');
2
- const { spawn } = require('child_process');
3
- const readline = require('node:readline');
4
- const fs = require('node:fs');
5
- const os = require("node:os");
6
- const { v4: uuidv4 } = require('uuid');
7
-
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.init = init;
37
+ exports.shutdown = shutdown;
38
+ exports.dumpAXTree = dumpAXTree;
39
+ exports.startRecording = startRecording;
40
+ exports.stopRecording = stopRecording;
41
+ exports.pauseRecording = pauseRecording;
42
+ exports.resumeRecording = resumeRecording;
43
+ exports.uploadRecording = uploadRecording;
44
+ exports.prepareDesktopAudioRecording = prepareDesktopAudioRecording;
45
+ exports.requestPermission = requestPermission;
46
+ exports.addEventListener = addEventListener;
47
+ const path = __importStar(require("path"));
48
+ const child_process_1 = require("child_process");
49
+ const readline = __importStar(require("node:readline"));
50
+ const node_stream_1 = require("node:stream");
51
+ const fs = __importStar(require("node:fs"));
52
+ const os = __importStar(require("node:os"));
53
+ const uuid_1 = require("uuid");
8
54
  // HACK: This is needed because of https://github.com/electron/electron/issues/6262
9
-
10
55
  // Search for the unpacked directory first, since fs.existsSync actually reports
11
56
  // the "virtual" files in the .asar as being present
12
-
13
57
  let exe_paths = [];
14
-
15
- if (process.platform == "darwin") {
16
- exe_paths.push(path.join(__dirname, "desktop_sdk_macos_exe").replace('app.asar', 'app.asar.unpacked'));
17
- exe_paths.push(path.join(__dirname, "desktop_sdk_macos_exe"));
18
- } else if (process.platform == "win32") {
19
- exe_paths.push(path.join(__dirname, "agent-windows.exe").replace('app.asar', 'app.asar.unpacked'));
20
- exe_paths.push(path.join(__dirname, "agent-windows.exe"));
58
+ if (process.platform === "darwin") {
59
+ exe_paths.push(path.join(__dirname, "desktop_sdk_macos_exe").replace('app.asar', 'app.asar.unpacked'));
60
+ exe_paths.push(path.join(__dirname, "desktop_sdk_macos_exe"));
61
+ }
62
+ else if (process.platform === "win32") {
63
+ exe_paths.push(path.join(__dirname, "agent-windows.exe").replace('app.asar', 'app.asar.unpacked'));
64
+ exe_paths.push(path.join(__dirname, "agent-windows.exe"));
21
65
  }
22
-
23
66
  let proc;
67
+ let rlData;
68
+ let rlStdout;
69
+ let rlStderr;
24
70
  const listeners = [];
25
71
  const pendingCommands = {};
26
-
27
72
  let lastOptions;
28
73
  let remainingAutomaticRestarts = 10;
29
74
  let unexpectedShutdown = false;
30
-
31
75
  let logBuffer = [];
32
76
  let logIndex = 0;
33
-
34
77
  function flushLogBuffer() {
35
- const buf = logBuffer.slice();
36
-
37
- logBuffer = [];
38
- logIndex = 0;
39
-
40
- for (const idx in buf) {
41
- const { level, log, echo } = buf[idx];
42
- doLog(level, [log], echo);
43
- }
78
+ const buf = logBuffer.slice(); // just here for the copy
79
+ logBuffer = [];
80
+ logIndex = 0;
81
+ // we use a for... in... here because it does not loop over the sparse (undefined) entries
82
+ // if you doubt this, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in#array_iteration_and_for...in
83
+ // and know that Nick and Tucker spent at least 10 minutes debating this
84
+ for (const idx in buf) {
85
+ const { level, log, echo } = buf[idx];
86
+ doLog(level, [log], echo);
87
+ }
88
+ }
89
+ function isPlainObject(value) {
90
+ return (value !== null &&
91
+ typeof value === "object" &&
92
+ Object.getPrototypeOf(value) === Object.prototype);
93
+ }
94
+ /**
95
+ * JSON.stringify with alphabetically sorted object keys (deep).
96
+ * - Preserves array order
97
+ * - Leaves non-plain objects (Date, Map, Set, class instances with toJSON, etc.) intact
98
+ * - Detects circular references like JSON.stringify and throws
99
+ */
100
+ function sortedStringify(value, replacer, space) {
101
+ const seen = new WeakSet();
102
+ function normalize(v) {
103
+ if (v === null || typeof v !== "object")
104
+ return v;
105
+ const obj = v;
106
+ if (seen.has(obj)) {
107
+ throw new TypeError("Converting circular structure to JSON");
108
+ }
109
+ seen.add(obj);
110
+ if (Array.isArray(v)) {
111
+ const out = v.map(normalize);
112
+ seen.delete(obj);
113
+ return out;
114
+ }
115
+ if (isPlainObject(v)) {
116
+ const src = v;
117
+ const out = {};
118
+ for (const key of Object.keys(src).sort()) {
119
+ out[key] = normalize(src[key]);
120
+ }
121
+ seen.delete(obj);
122
+ return out;
123
+ }
124
+ // Non-plain object: keep as-is so built-in toJSON/serialization applies.
125
+ seen.delete(obj);
126
+ return v;
127
+ }
128
+ return JSON.stringify(normalize(value), replacer, space);
44
129
  }
45
-
46
130
  async function doLog(level, log, echo = true) {
47
- try {
48
- const levelMap = {
49
- "info": "log",
50
- "warning": "warn",
51
- "error": "error"
52
- };
53
-
54
- if (echo)
55
- console[levelMap[level] ?? "log"](...log);
56
-
57
- let logPayload = {
58
- log: log.join(" "),
59
- level: level,
60
- echo
61
- };
62
-
63
- let idx = logIndex++;
64
-
65
- logBuffer[idx] = logPayload;
66
-
67
- if (proc && proc.exitCode === null) {
68
- await sendCommand("log", logPayload);
69
- delete logBuffer[idx];
131
+ try {
132
+ const levelMap = {
133
+ "info": "log",
134
+ "warning": "warn",
135
+ "error": "error"
136
+ };
137
+ if (echo) {
138
+ const consoleMethod = console[levelMap[level] ?? "log"];
139
+ consoleMethod(...log);
140
+ }
141
+ const logPayload = {
142
+ log: log.join(" "),
143
+ level: level,
144
+ echo
145
+ };
146
+ const idx = logIndex++;
147
+ logBuffer[idx] = logPayload;
148
+ if (proc && proc.exitCode === null) {
149
+ await sendCommand("log", logPayload);
150
+ delete logBuffer[idx];
151
+ }
152
+ }
153
+ catch (e) {
154
+ console.error("Failed to send log to Desktop SDK", e.stack, e.message);
70
155
  }
71
- } catch (e) {
72
- console.error("Failed to send log to Desktop SDK", e.stack, e.message);
73
- }
74
156
  }
75
-
76
157
  function log(...log) {
77
- doLog("info", log);
158
+ doLog("info", log);
78
159
  }
79
-
80
160
  function logWarning(...log) {
81
- doLog("warning", log);
161
+ doLog("warning", log);
82
162
  }
83
-
84
163
  function logError(...log) {
85
- doLog("error", log);
164
+ doLog("error", log);
86
165
  }
87
-
88
166
  function emitEvent(type, payload) {
89
- if (type !== "upload-progress" && type !== "realtime-event") {
90
- doLog("info", ["Receiving event: " + type + " | " + JSON.stringify(payload)], false);
91
- }
92
-
93
- for (const listener of listeners) {
94
- if (listener.type === type)
95
- listener.callback(payload);
96
- }
167
+ if (type !== "upload-progress" && type !== "realtime-event") {
168
+ doLog("info", ["Receiving event: " + type + " | " + sortedStringify(payload)], false);
169
+ }
170
+ for (const listener of listeners) {
171
+ if (listener.type === type) {
172
+ listener.callback(payload);
173
+ }
174
+ }
97
175
  }
98
-
99
176
  function flushPendingCommands(err) {
100
- Object.keys(pendingCommands).forEach(commandId => {
101
- pendingCommands[commandId].reject(err);
102
- delete pendingCommands[commandId];
103
- });
177
+ Object.keys(pendingCommands).forEach(commandId => {
178
+ pendingCommands[commandId].reject(err);
179
+ delete pendingCommands[commandId];
180
+ });
104
181
  }
105
-
106
182
  function startProcess() {
107
- if (proc && proc.exitCode === null) {
108
- logError("Desktop SDK: Trying to start process while it is already started");
109
- return;
110
- }
111
-
112
- const exe_path = exe_paths.find(fs.existsSync);
113
-
114
- if (!exe_path) {
115
- logError(`Desktop SDK: Couldn't launch! This is likely an issue with the build tool you're using.`);
116
-
117
- for (const exe_path of exe_paths)
118
- logError("Tried:", exe_path);
119
-
120
- return;
121
- }
122
-
123
- let envExtra = {};
124
-
125
- if (process.platform === "win32" && process.env.GLOBAL_GST_RECALL !== "1") {
126
- envExtra["GST_PLUGIN_PATH"] = path.join(path.dirname(exe_path), "gstreamer-1.0");
127
- }
128
-
129
- proc = spawn(exe_path, {
130
- stdio: "pipe",
131
- env: {
132
- GST_DEBUG: "2",
133
- GST_DEBUG_DUMP_DOT_DIR: process.env.RECALLAI_DESKTOP_SDK_DEV ? "/tmp/gst.nocommit" : os.tmpdir(),
134
- RUST_BACKTRACE: true,
135
- // "DYLD_INSERT_LIBRARIES":"/opt/homebrew/lib/libjemalloc.dylib",
136
- // "MALLOC_CONF":"prof:true,prof_active:true,prof_prefix:jeprof",
137
- // "DYLD_FORCE_FLAT_NAMESPACE":"1",
138
- ...envExtra,
183
+ if (proc && proc.exitCode === null) {
184
+ logError("Desktop SDK: Trying to start process while it is already started");
185
+ return;
139
186
  }
140
- });
141
-
142
- const rlStdout = readline.createInterface({ input: proc.stdout, crlfDelay: Infinity });
143
- const rlStderr = readline.createInterface({ input: proc.stderr, crlfDelay: Infinity });
144
-
145
- rlStdout.on('line', (line) => {
146
-
147
-
148
- if (line.startsWith("recall_ai_command|")) {
149
- try {
150
- const data = JSON.parse(line.substring(18));
151
-
152
- switch (data.type) {
153
- case "event":
154
- const event = JSON.parse(data.event);
155
- emitEvent(event.type, event.payload);
156
- break;
157
-
158
- case "response":
159
- const pendingCommand = pendingCommands[data.commandId];
160
- if (pendingCommand) {
161
- if (data.status === "success") {
162
- pendingCommand.resolve(data.result);
163
- }
164
- else {
165
- pendingCommand.reject(new Error(data.result));
166
- }
167
- delete pendingCommands[data.commandId];
168
- }
169
- break;
187
+ const exe_path = exe_paths.find(fs.existsSync);
188
+ if (!exe_path) {
189
+ logError(`Desktop SDK: Couldn't launch! This is likely an issue with the build tool you're using.`);
190
+ for (const exe_path of exe_paths) {
191
+ logError("Tried:", exe_path);
170
192
  }
171
- } catch (err) {
172
- logError("Desktop SDK: Failed to parse incoming data:", err);
173
- }
174
- } else {
175
- if (process.env.RECALLAI_DESKTOP_SDK_DEV)
176
- console.log(line);
193
+ return;
177
194
  }
178
- });
179
-
180
- rlStderr.on('line', (line) => {
181
- if (process.env.RECALLAI_DESKTOP_SDK_DEV)
182
- console.error(line);
183
- });
184
-
185
- proc.on('error', (error) => {
186
- flushPendingCommands(new Error(`Process error: ${error.message}`));
187
- emitEvent('error', {
188
- type: 'process',
189
- message: `The Desktop SDK server process has failed to start or exited improperly.`
190
- });
191
- logError(`Desktop SDK: Process error: ${error.message}`);
192
- });
193
-
194
- proc.on('close', async (code, signal) => {
195
- flushPendingCommands(new Error(`Process exited with code ${code}, signal ${signal}.`));
196
- emitEvent('shutdown', { code, signal });
197
-
198
- if (code === 0 || signal == 'SIGINT')
199
- return;
200
-
201
- logError(`Desktop SDK: Process exited with code ${code}, signal ${signal}`);
202
-
203
- emitEvent('error', {
204
- type: 'process',
205
- message: "The Desktop SDK server process exited unexpectedly."
195
+ let envExtra = {};
196
+ if (process.platform === "win32" && process.env.GLOBAL_GST_RECALL !== "1") {
197
+ envExtra["GST_PLUGIN_PATH"] = path.join(path.dirname(exe_path), "gstreamer-1.0");
198
+ }
199
+ const gst_dump_dir = process.env.RECALLAI_DESKTOP_SDK_DEV ? path.join(os.tmpdir(), "gst.nocommit") : os.tmpdir();
200
+ if (!fs.existsSync(gst_dump_dir)) {
201
+ try {
202
+ fs.mkdirSync(gst_dump_dir);
203
+ }
204
+ catch (e) {
205
+ logError("Failed to create gst dump dir:", String(e));
206
+ }
207
+ }
208
+ proc = (0, child_process_1.spawn)(exe_path, {
209
+ stdio: (process.platform === "darwin" ? ["pipe", "pipe", "pipe", "pipe"] : "pipe"),
210
+ env: {
211
+ GST_DEBUG: "2",
212
+ GST_DEBUG_DUMP_DOT_DIR: gst_dump_dir,
213
+ RUST_BACKTRACE: "1",
214
+ // "DYLD_INSERT_LIBRARIES":"/opt/homebrew/lib/libjemalloc.dylib",
215
+ // "MALLOC_CONF":"prof:true,prof_active:true,prof_prefix:jeprof",
216
+ // "DYLD_FORCE_FLAT_NAMESPACE":"1",
217
+ ...envExtra,
218
+ }
206
219
  });
207
-
208
- proc = null;
209
- unexpectedShutdown = true;
210
-
211
- if (lastOptions.restartOnError && remainingAutomaticRestarts > 0) {
212
- remainingAutomaticRestarts--;
213
- logError(`Automatically restarting Desktop SDK due to unexpected exit! Automatic restarts left: ${remainingAutomaticRestarts}`);
214
- doInit(lastOptions);
220
+ if (process.platform === "darwin") {
221
+ const fd3 = proc.stdio[3];
222
+ if (fd3 && fd3 instanceof node_stream_1.Readable) {
223
+ rlData = readline.createInterface({ input: fd3, crlfDelay: Infinity });
224
+ }
215
225
  }
216
- });
226
+ rlStdout = readline.createInterface({ input: proc.stdout, crlfDelay: Infinity });
227
+ rlStderr = readline.createInterface({ input: proc.stderr, crlfDelay: Infinity });
228
+ let onLine = (line) => {
229
+ if (line?.startsWith("recall_ai_command|")) {
230
+ try {
231
+ const data = JSON.parse(line.substring(18));
232
+ switch (data.type) {
233
+ case "event":
234
+ if (data.event) {
235
+ const event = JSON.parse(data.event);
236
+ emitEvent(event.type, event.payload);
237
+ }
238
+ break;
239
+ case "response":
240
+ const pendingCommand = pendingCommands[data.commandId];
241
+ if (pendingCommand) {
242
+ if (data.status === "success") {
243
+ pendingCommand.resolve(data.result);
244
+ }
245
+ else {
246
+ pendingCommand.reject(new Error(data.result));
247
+ }
248
+ delete pendingCommands[data.commandId];
249
+ }
250
+ break;
251
+ }
252
+ }
253
+ catch (err) {
254
+ logError("Desktop SDK: Failed to parse incoming data:", String(err));
255
+ }
256
+ }
257
+ else {
258
+ if (process.env.RECALLAI_DESKTOP_SDK_DEV) {
259
+ console.log(line);
260
+ }
261
+ }
262
+ };
263
+ rlData?.on('line', onLine);
264
+ rlStdout.on('line', onLine);
265
+ rlStderr.on('line', (line) => {
266
+ if (process.env.RECALLAI_DESKTOP_SDK_DEV) {
267
+ console.error(line);
268
+ }
269
+ });
270
+ proc.on('error', (error) => {
271
+ flushPendingCommands(new Error(`Process error: ${error.message}`));
272
+ emitEvent('error', {
273
+ type: 'process',
274
+ message: `The Desktop SDK server process has failed to start or exited improperly.`
275
+ });
276
+ logError(`Desktop SDK: Process error: ${error.message}`);
277
+ });
278
+ proc.on('close', async (code, signal) => {
279
+ flushPendingCommands(new Error(`Process exited with code ${code}, signal ${signal}.`));
280
+ emitEvent('shutdown', { code: code ?? 0, signal: signal ?? '' });
281
+ rlStdout?.close();
282
+ rlStderr?.close();
283
+ rlStdout = null;
284
+ rlStderr = null;
285
+ if (code === 0 || signal === 'SIGINT') {
286
+ return;
287
+ }
288
+ logError(`Desktop SDK: Process exited with code ${code}, signal ${signal}`);
289
+ emitEvent('error', {
290
+ type: 'process',
291
+ message: "The Desktop SDK server process exited unexpectedly."
292
+ });
293
+ proc = null;
294
+ unexpectedShutdown = true;
295
+ if (lastOptions.restartOnError && remainingAutomaticRestarts > 0) {
296
+ remainingAutomaticRestarts--;
297
+ logError(`Automatically restarting Desktop SDK due to unexpected exit! Automatic restarts left: ${remainingAutomaticRestarts}`);
298
+ doInit(lastOptions);
299
+ }
300
+ });
217
301
  }
218
-
219
302
  function sendCommand(command, params = {}) {
220
- return new Promise((resolve, reject) => {
221
- if (!proc || !proc.stdin) {
222
- reject(new Error("The Desktop SDK is not started; call `init` to start it."));
223
- return;
224
- }
225
-
226
- const commandId = uuidv4();
227
- pendingCommands[commandId] = { resolve, reject };
228
-
229
- const payload = {
230
- command,
231
- commandId,
232
- params
233
- };
234
-
235
- let payloadStr = JSON.stringify(payload);
236
- proc.stdin.write(payloadStr + "\n");
237
-
238
- if (command !== "log") {
239
- doLog("info", ["Sending command: " + payloadStr], false);
240
- }
241
- });
303
+ return new Promise((resolve, reject) => {
304
+ if (!proc || !proc.stdin) {
305
+ reject(new Error("The Desktop SDK is not started; call `init` to start it."));
306
+ return;
307
+ }
308
+ const commandId = (0, uuid_1.v4)();
309
+ pendingCommands[commandId] = { resolve, reject };
310
+ const payload = {
311
+ command,
312
+ commandId,
313
+ params
314
+ };
315
+ const payloadStr = JSON.stringify(payload);
316
+ proc.stdin.write(payloadStr + "\n");
317
+ if (command !== "log") {
318
+ doLog("info", ["Sending command: " + payloadStr], false);
319
+ }
320
+ });
242
321
  }
243
-
244
322
  async function doInit(options) {
245
- startProcess();
246
-
247
- if (unexpectedShutdown) {
248
- logError("Desktop SDK: Recovered from unexpected shutdown");
249
- unexpectedShutdown = false;
250
- }
251
-
252
- await sendCommand("init", { config: JSON.stringify(options) });
253
- flushLogBuffer();
323
+ startProcess();
324
+ if (unexpectedShutdown) {
325
+ logError("Desktop SDK: Recovered from unexpected shutdown");
326
+ unexpectedShutdown = false;
327
+ }
328
+ await sendCommand("init", { config: JSON.stringify(options) });
329
+ flushLogBuffer();
254
330
  }
255
-
256
331
  async function init(options) {
257
- if (process.platform !== "darwin"
258
- && process.platform !== "win32"
259
- ) {
260
- throw new Error(`Platform ${process.platform} is not supported by Desktop SDK`);
261
- }
262
-
263
- let { api_url, apiUrl, dev } = options;
264
-
265
- options.api_url = api_url ?? apiUrl ?? "https://api.recall.ai";
266
-
267
- if (!dev && (!options.api_url || !options.api_url.startsWith("https"))) {
268
- throw new Error(`apiUrl must be an https url, got: ${options.api_url}`);
269
- }
270
-
271
- if (options.restartOnError === undefined)
272
- options.restartOnError = true;
273
-
274
- lastOptions = options;
275
- return doInit(options);
332
+ if (process.platform !== "darwin" && process.platform !== "win32") {
333
+ throw new Error(`Platform ${process.platform} is not supported by Desktop SDK`);
334
+ }
335
+ const { api_url, apiUrl, dev } = options;
336
+ options.api_url = api_url ?? apiUrl ?? "https://api.recall.ai";
337
+ if (!dev && (!options.api_url || !options.api_url.startsWith("https"))) {
338
+ throw new Error(`apiUrl must be an https url, got: ${options.api_url}`);
339
+ }
340
+ if (options.restartOnError === undefined) {
341
+ options.restartOnError = true;
342
+ }
343
+ lastOptions = options;
344
+ await doInit(options);
345
+ return null;
276
346
  }
277
-
278
347
  async function shutdown() {
279
- const result = await sendCommand("shutdown");
280
-
281
- if (proc) {
282
- const currentProc = proc;
283
- setTimeout(() => {
284
- if (!currentProc.killed) {
285
- currentProc.kill();
286
- }
287
- }, 5000);
288
- }
289
-
290
- return result;
348
+ const result = await sendCommand("shutdown");
349
+ if (proc) {
350
+ const currentProc = proc;
351
+ setTimeout(() => {
352
+ if (!currentProc.killed) {
353
+ currentProc.kill();
354
+ }
355
+ }, 5000);
356
+ }
357
+ return result;
358
+ }
359
+ function dumpAXTree(procName) {
360
+ return sendCommand("dumpAXTree", { procName });
291
361
  }
292
-
293
362
  function startRecording(config) {
294
- return sendCommand("startRecording", { config: JSON.stringify(config) });
363
+ return sendCommand("startRecording", { config: JSON.stringify(config) });
295
364
  }
296
-
297
365
  function stopRecording({ windowId }) {
298
- return sendCommand("stopRecording", { windowId });
366
+ return sendCommand("stopRecording", { windowId });
299
367
  }
300
-
301
368
  function pauseRecording({ windowId }) {
302
- return sendCommand("pauseRecording", { windowId });
369
+ return sendCommand("pauseRecording", { windowId });
303
370
  }
304
-
305
371
  function resumeRecording({ windowId }) {
306
- return sendCommand("resumeRecording", { windowId });
372
+ return sendCommand("resumeRecording", { windowId });
307
373
  }
308
-
309
374
  function uploadRecording({ windowId }) {
310
- return sendCommand("uploadRecording", { windowId });
375
+ return sendCommand("uploadRecording", { windowId });
311
376
  }
312
-
313
377
  function prepareDesktopAudioRecording() {
314
- return sendCommand("prepareDesktopAudioRecording");
378
+ return sendCommand("prepareDesktopAudioRecording");
315
379
  }
316
-
317
380
  function requestPermission(permission) {
318
- return sendCommand("requestPermission", { permission });
381
+ return sendCommand("requestPermission", { permission });
319
382
  }
320
-
321
383
  function addEventListener(type, callback) {
322
- listeners.push({ type, callback });
384
+ listeners.push({ type, callback });
323
385
  }
324
-
325
- module.exports = {
326
- init,
327
- shutdown,
328
- addEventListener,
329
- startRecording,
330
- pauseRecording,
331
- resumeRecording,
332
- stopRecording,
333
- uploadRecording,
334
- prepareDesktopAudioRecording,
335
- requestPermission
386
+ const RecallAiSdk = {
387
+ init,
388
+ shutdown,
389
+ startRecording,
390
+ stopRecording,
391
+ pauseRecording,
392
+ resumeRecording,
393
+ uploadRecording,
394
+ prepareDesktopAudioRecording,
395
+ requestPermission,
396
+ addEventListener,
336
397
  };
398
+ exports.default = RecallAiSdk;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@recallai/desktop-sdk",
3
- "version": "1.0.6",
3
+ "version": "1.2.0",
4
4
  "description": "Recall Desktop SDK",
5
5
  "main": "./index.js",
6
6
  "types": "./index.d.ts",
@@ -13,10 +13,12 @@
13
13
  },
14
14
  "scripts": {
15
15
  "install": "node ./setup.js",
16
- "validate-types": "tsc --noEmit index.d.ts"
16
+ "validate-types": "tsc --noEmit",
17
+ "build": "tsc -p tsconfig.build.json"
17
18
  },
18
19
  "devDependencies": {
20
+ "@types/node": "^24.2.0",
19
21
  "typescript": "^5.3.3"
20
22
  },
21
- "commit_sha": "78bcd926390af65c83ffa27a66147f8ad9144c9c"
23
+ "commit_sha": "f1c3fbbb113f8f5a51d801dd9fe240b371125539"
22
24
  }