@recallai/desktop-sdk 1.0.5 → 1.1.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 +23 -1
  2. package/index.d.ts +125 -160
  3. package/index.js +277 -276
  4. package/package.json +5 -3
package/CHANGELOG.md CHANGED
@@ -1,4 +1,26 @@
1
- ### 1.0.5 [patch] (MacOS) - 2025-07-11
1
+ ### 1.1.0 [minor] (MacOS+Windows) - 2025-08-29
2
+
3
+ - Reduce idle power consumption by 80%
4
+ - Add capture support for MS Teams on MacOS
5
+ - Add capture support for MS Teams on Windows
6
+ - Fixed a bug detecting Zoom on Windows for non-premium zoom members
7
+ - Fixed a bug capuring video on old Win10 Windows installs
8
+ - Add support for gathering meeting urls for Zoom/Teams on Windows
9
+ - Add support for AssemblyAI Universal Streaming Model (v3)
10
+ - Add support for transcript.partial_data
11
+ - Fix permissions to allow system only audio without requiring an initial request
12
+ - Add Google Meet url to URL field
13
+ - Generate index.js and index.d.ts from an index.ts file
14
+ - Prevent rogue screen capture kit calls from causing CPU drain in system audio only mode
15
+ - Fix memory growth during recording
16
+ - Fix memory leaks on idle
17
+ - Prevent zombie exe on stdin kill
18
+
19
+ ### 1.0.6 [patch] (MacOS) - 2025-08-15
20
+
21
+ - Fixes a bug where whole-desktop audio capture would produce corrupted recordings.
22
+
23
+ ### 1.0.5 [patch] (MacOS) - 2025-08-11
2
24
 
3
25
  - Fixes a bug that would cause an occasional crash.
4
26
 
package/index.d.ts CHANGED
@@ -1,163 +1,128 @@
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 startRecording(config: StartRecordingConfig): Promise<null>;
109
+ export declare function stopRecording({ windowId }: StopRecordingConfig): Promise<null>;
110
+ export declare function pauseRecording({ windowId }: PauseRecordingConfig): Promise<null>;
111
+ export declare function resumeRecording({ windowId }: ResumeRecordingConfig): Promise<null>;
112
+ export declare function uploadRecording({ windowId }: UploadRecordingConfig): Promise<null>;
113
+ export declare function prepareDesktopAudioRecording(): Promise<string>;
114
+ export declare function requestPermission(permission: Permission): Promise<null>;
115
+ export declare function addEventListener<T extends keyof EventTypeToPayloadMap>(type: T, callback: (event: EventTypeToPayloadMap[T]) => void): void;
116
+ declare const RecallAiSdk: {
117
+ init: typeof init;
118
+ shutdown: typeof shutdown;
119
+ startRecording: typeof startRecording;
120
+ stopRecording: typeof stopRecording;
121
+ pauseRecording: typeof pauseRecording;
122
+ resumeRecording: typeof resumeRecording;
123
+ uploadRecording: typeof uploadRecording;
124
+ prepareDesktopAudioRecording: typeof prepareDesktopAudioRecording;
125
+ requestPermission: typeof requestPermission;
126
+ addEventListener: typeof addEventListener;
127
+ };
128
+ export default RecallAiSdk;
package/index.js CHANGED
@@ -1,336 +1,337 @@
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.startRecording = startRecording;
39
+ exports.stopRecording = stopRecording;
40
+ exports.pauseRecording = pauseRecording;
41
+ exports.resumeRecording = resumeRecording;
42
+ exports.uploadRecording = uploadRecording;
43
+ exports.prepareDesktopAudioRecording = prepareDesktopAudioRecording;
44
+ exports.requestPermission = requestPermission;
45
+ exports.addEventListener = addEventListener;
46
+ const path = __importStar(require("path"));
47
+ const child_process_1 = require("child_process");
48
+ const readline = __importStar(require("node:readline"));
49
+ const fs = __importStar(require("node:fs"));
50
+ const os = __importStar(require("node:os"));
51
+ const uuid_1 = require("uuid");
8
52
  // HACK: This is needed because of https://github.com/electron/electron/issues/6262
9
-
10
53
  // Search for the unpacked directory first, since fs.existsSync actually reports
11
54
  // the "virtual" files in the .asar as being present
12
-
13
55
  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"));
56
+ if (process.platform === "darwin") {
57
+ exe_paths.push(path.join(__dirname, "desktop_sdk_macos_exe").replace('app.asar', 'app.asar.unpacked'));
58
+ exe_paths.push(path.join(__dirname, "desktop_sdk_macos_exe"));
59
+ }
60
+ else if (process.platform === "win32") {
61
+ exe_paths.push(path.join(__dirname, "agent-windows.exe").replace('app.asar', 'app.asar.unpacked'));
62
+ exe_paths.push(path.join(__dirname, "agent-windows.exe"));
21
63
  }
22
-
23
64
  let proc;
24
65
  const listeners = [];
25
66
  const pendingCommands = {};
26
-
27
67
  let lastOptions;
28
68
  let remainingAutomaticRestarts = 10;
29
69
  let unexpectedShutdown = false;
30
-
31
70
  let logBuffer = [];
32
71
  let logIndex = 0;
33
-
34
72
  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
- }
73
+ const buf = logBuffer.slice(); // just here for the copy
74
+ logBuffer = [];
75
+ logIndex = 0;
76
+ // we use a for... in... here because it does not loop over the sparse (undefined) entries
77
+ // if you doubt this, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in#array_iteration_and_for...in
78
+ // and know that Nick and Tucker spent at least 10 minutes debating this
79
+ for (const idx in buf) {
80
+ const { level, log, echo } = buf[idx];
81
+ doLog(level, [log], echo);
82
+ }
44
83
  }
45
-
46
84
  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];
85
+ try {
86
+ const levelMap = {
87
+ "info": "log",
88
+ "warning": "warn",
89
+ "error": "error"
90
+ };
91
+ if (echo) {
92
+ const consoleMethod = console[levelMap[level] ?? "log"];
93
+ consoleMethod(...log);
94
+ }
95
+ const logPayload = {
96
+ log: log.join(" "),
97
+ level: level,
98
+ echo
99
+ };
100
+ const idx = logIndex++;
101
+ logBuffer[idx] = logPayload;
102
+ if (proc && proc.exitCode === null) {
103
+ await sendCommand("log", logPayload);
104
+ delete logBuffer[idx];
105
+ }
106
+ }
107
+ catch (e) {
108
+ console.error("Failed to send log to Desktop SDK", e.stack, e.message);
70
109
  }
71
- } catch (e) {
72
- console.error("Failed to send log to Desktop SDK", e.stack, e.message);
73
- }
74
110
  }
75
-
76
111
  function log(...log) {
77
- doLog("info", log);
112
+ doLog("info", log);
78
113
  }
79
-
80
114
  function logWarning(...log) {
81
- doLog("warning", log);
115
+ doLog("warning", log);
82
116
  }
83
-
84
117
  function logError(...log) {
85
- doLog("error", log);
118
+ doLog("error", log);
86
119
  }
87
-
88
120
  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
- }
121
+ if (type !== "upload-progress" && type !== "realtime-event") {
122
+ doLog("info", ["Receiving event: " + type + " | " + JSON.stringify(payload)], false);
123
+ }
124
+ for (const listener of listeners) {
125
+ if (listener.type === type) {
126
+ listener.callback(payload);
127
+ }
128
+ }
97
129
  }
98
-
99
130
  function flushPendingCommands(err) {
100
- Object.keys(pendingCommands).forEach(commandId => {
101
- pendingCommands[commandId].reject(err);
102
- delete pendingCommands[commandId];
103
- });
131
+ Object.keys(pendingCommands).forEach(commandId => {
132
+ pendingCommands[commandId].reject(err);
133
+ delete pendingCommands[commandId];
134
+ });
104
135
  }
105
-
106
136
  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,
137
+ if (proc && proc.exitCode === null) {
138
+ logError("Desktop SDK: Trying to start process while it is already started");
139
+ return;
139
140
  }
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;
141
+ const exe_path = exe_paths.find(fs.existsSync);
142
+ if (!exe_path) {
143
+ logError(`Desktop SDK: Couldn't launch! This is likely an issue with the build tool you're using.`);
144
+ for (const exe_path of exe_paths) {
145
+ logError("Tried:", exe_path);
170
146
  }
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);
147
+ return;
148
+ }
149
+ let envExtra = {};
150
+ if (process.platform === "win32" && process.env.GLOBAL_GST_RECALL !== "1") {
151
+ envExtra["GST_PLUGIN_PATH"] = path.join(path.dirname(exe_path), "gstreamer-1.0");
177
152
  }
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.`
153
+ const gst_dump_dir = process.env.RECALLAI_DESKTOP_SDK_DEV ? path.join(os.tmpdir(), "gst.nocommit") : os.tmpdir();
154
+ if (!fs.existsSync(gst_dump_dir)) {
155
+ try {
156
+ fs.mkdirSync(gst_dump_dir);
157
+ }
158
+ catch (e) {
159
+ logError("Failed to create gst dump dir:", String(e));
160
+ }
161
+ }
162
+ proc = (0, child_process_1.spawn)(exe_path, {
163
+ stdio: "pipe",
164
+ env: {
165
+ GST_DEBUG: "2",
166
+ GST_DEBUG_DUMP_DOT_DIR: gst_dump_dir,
167
+ RUST_BACKTRACE: "1",
168
+ // "DYLD_INSERT_LIBRARIES":"/opt/homebrew/lib/libjemalloc.dylib",
169
+ // "MALLOC_CONF":"prof:true,prof_active:true,prof_prefix:jeprof",
170
+ // "DYLD_FORCE_FLAT_NAMESPACE":"1",
171
+ ...envExtra,
172
+ }
173
+ });
174
+ const rlStdout = readline.createInterface({ input: proc.stdout, crlfDelay: Infinity });
175
+ const rlStderr = readline.createInterface({ input: proc.stderr, crlfDelay: Infinity });
176
+ rlStdout.on('line', (line) => {
177
+ if (line.startsWith("recall_ai_command|")) {
178
+ try {
179
+ const data = JSON.parse(line.substring(18));
180
+ switch (data.type) {
181
+ case "event":
182
+ if (data.event) {
183
+ const event = JSON.parse(data.event);
184
+ emitEvent(event.type, event.payload);
185
+ }
186
+ break;
187
+ case "response":
188
+ const pendingCommand = pendingCommands[data.commandId];
189
+ if (pendingCommand) {
190
+ if (data.status === "success") {
191
+ pendingCommand.resolve(data.result);
192
+ }
193
+ else {
194
+ pendingCommand.reject(new Error(data.result));
195
+ }
196
+ delete pendingCommands[data.commandId];
197
+ }
198
+ break;
199
+ }
200
+ }
201
+ catch (err) {
202
+ logError("Desktop SDK: Failed to parse incoming data:", String(err));
203
+ }
204
+ }
205
+ else {
206
+ if (process.env.RECALLAI_DESKTOP_SDK_DEV) {
207
+ console.log(line);
208
+ }
209
+ }
210
+ });
211
+ rlStderr.on('line', (line) => {
212
+ if (process.env.RECALLAI_DESKTOP_SDK_DEV) {
213
+ console.error(line);
214
+ }
190
215
  });
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."
216
+ proc.on('error', (error) => {
217
+ flushPendingCommands(new Error(`Process error: ${error.message}`));
218
+ emitEvent('error', {
219
+ type: 'process',
220
+ message: `The Desktop SDK server process has failed to start or exited improperly.`
221
+ });
222
+ logError(`Desktop SDK: Process error: ${error.message}`);
223
+ });
224
+ proc.on('close', async (code, signal) => {
225
+ flushPendingCommands(new Error(`Process exited with code ${code}, signal ${signal}.`));
226
+ emitEvent('shutdown', { code: code ?? 0, signal: signal ?? '' });
227
+ if (code === 0 || signal === 'SIGINT') {
228
+ return;
229
+ }
230
+ logError(`Desktop SDK: Process exited with code ${code}, signal ${signal}`);
231
+ emitEvent('error', {
232
+ type: 'process',
233
+ message: "The Desktop SDK server process exited unexpectedly."
234
+ });
235
+ proc = null;
236
+ unexpectedShutdown = true;
237
+ if (lastOptions.restartOnError && remainingAutomaticRestarts > 0) {
238
+ remainingAutomaticRestarts--;
239
+ logError(`Automatically restarting Desktop SDK due to unexpected exit! Automatic restarts left: ${remainingAutomaticRestarts}`);
240
+ doInit(lastOptions);
241
+ }
206
242
  });
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);
215
- }
216
- });
217
243
  }
218
-
219
244
  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
- });
245
+ return new Promise((resolve, reject) => {
246
+ if (!proc || !proc.stdin) {
247
+ reject(new Error("The Desktop SDK is not started; call `init` to start it."));
248
+ return;
249
+ }
250
+ const commandId = (0, uuid_1.v4)();
251
+ pendingCommands[commandId] = { resolve, reject };
252
+ const payload = {
253
+ command,
254
+ commandId,
255
+ params
256
+ };
257
+ const payloadStr = JSON.stringify(payload);
258
+ proc.stdin.write(payloadStr + "\n");
259
+ if (command !== "log") {
260
+ doLog("info", ["Sending command: " + payloadStr], false);
261
+ }
262
+ });
242
263
  }
243
-
244
264
  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();
265
+ startProcess();
266
+ if (unexpectedShutdown) {
267
+ logError("Desktop SDK: Recovered from unexpected shutdown");
268
+ unexpectedShutdown = false;
269
+ }
270
+ await sendCommand("init", { config: JSON.stringify(options) });
271
+ flushLogBuffer();
254
272
  }
255
-
256
273
  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);
274
+ if (process.platform !== "darwin" && process.platform !== "win32") {
275
+ throw new Error(`Platform ${process.platform} is not supported by Desktop SDK`);
276
+ }
277
+ const { api_url, apiUrl, dev } = options;
278
+ options.api_url = api_url ?? apiUrl ?? "https://api.recall.ai";
279
+ if (!dev && (!options.api_url || !options.api_url.startsWith("https"))) {
280
+ throw new Error(`apiUrl must be an https url, got: ${options.api_url}`);
281
+ }
282
+ if (options.restartOnError === undefined) {
283
+ options.restartOnError = true;
284
+ }
285
+ lastOptions = options;
286
+ await doInit(options);
287
+ return null;
276
288
  }
277
-
278
289
  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;
290
+ const result = await sendCommand("shutdown");
291
+ if (proc) {
292
+ const currentProc = proc;
293
+ setTimeout(() => {
294
+ if (!currentProc.killed) {
295
+ currentProc.kill();
296
+ }
297
+ }, 5000);
298
+ }
299
+ return result;
291
300
  }
292
-
293
301
  function startRecording(config) {
294
- return sendCommand("startRecording", { config: JSON.stringify(config) });
302
+ return sendCommand("startRecording", { config: JSON.stringify(config) });
295
303
  }
296
-
297
304
  function stopRecording({ windowId }) {
298
- return sendCommand("stopRecording", { windowId });
305
+ return sendCommand("stopRecording", { windowId });
299
306
  }
300
-
301
307
  function pauseRecording({ windowId }) {
302
- return sendCommand("pauseRecording", { windowId });
308
+ return sendCommand("pauseRecording", { windowId });
303
309
  }
304
-
305
310
  function resumeRecording({ windowId }) {
306
- return sendCommand("resumeRecording", { windowId });
311
+ return sendCommand("resumeRecording", { windowId });
307
312
  }
308
-
309
313
  function uploadRecording({ windowId }) {
310
- return sendCommand("uploadRecording", { windowId });
314
+ return sendCommand("uploadRecording", { windowId });
311
315
  }
312
-
313
316
  function prepareDesktopAudioRecording() {
314
- return sendCommand("prepareDesktopAudioRecording");
317
+ return sendCommand("prepareDesktopAudioRecording");
315
318
  }
316
-
317
319
  function requestPermission(permission) {
318
- return sendCommand("requestPermission", { permission });
320
+ return sendCommand("requestPermission", { permission });
319
321
  }
320
-
321
322
  function addEventListener(type, callback) {
322
- listeners.push({ type, callback });
323
+ listeners.push({ type, callback });
323
324
  }
324
-
325
- module.exports = {
326
- init,
327
- shutdown,
328
- addEventListener,
329
- startRecording,
330
- pauseRecording,
331
- resumeRecording,
332
- stopRecording,
333
- uploadRecording,
334
- prepareDesktopAudioRecording,
335
- requestPermission
325
+ const RecallAiSdk = {
326
+ init,
327
+ shutdown,
328
+ startRecording,
329
+ stopRecording,
330
+ pauseRecording,
331
+ resumeRecording,
332
+ uploadRecording,
333
+ prepareDesktopAudioRecording,
334
+ requestPermission,
335
+ addEventListener,
336
336
  };
337
+ exports.default = RecallAiSdk;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@recallai/desktop-sdk",
3
- "version": "1.0.5",
3
+ "version": "1.1.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 index.d.ts",
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": "20f66a993409e83e5bd6bc79b0e17e75d7e30d38"
23
+ "commit_sha": "6b2ffc56b52550ef46336f4ed9646d20f0e4b722"
22
24
  }