@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.
- package/CHANGELOG.md +34 -2
- package/index.d.ts +126 -160
- package/index.js +338 -276
- package/package.json +5 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,40 @@
|
|
|
1
|
-
### 1.0
|
|
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-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
158
|
+
doLog("info", log);
|
|
78
159
|
}
|
|
79
|
-
|
|
80
160
|
function logWarning(...log) {
|
|
81
|
-
|
|
161
|
+
doLog("warning", log);
|
|
82
162
|
}
|
|
83
|
-
|
|
84
163
|
function logError(...log) {
|
|
85
|
-
|
|
164
|
+
doLog("error", log);
|
|
86
165
|
}
|
|
87
|
-
|
|
88
166
|
function emitEvent(type, payload) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
143
|
-
|
|
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
|
-
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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
|
-
|
|
363
|
+
return sendCommand("startRecording", { config: JSON.stringify(config) });
|
|
295
364
|
}
|
|
296
|
-
|
|
297
365
|
function stopRecording({ windowId }) {
|
|
298
|
-
|
|
366
|
+
return sendCommand("stopRecording", { windowId });
|
|
299
367
|
}
|
|
300
|
-
|
|
301
368
|
function pauseRecording({ windowId }) {
|
|
302
|
-
|
|
369
|
+
return sendCommand("pauseRecording", { windowId });
|
|
303
370
|
}
|
|
304
|
-
|
|
305
371
|
function resumeRecording({ windowId }) {
|
|
306
|
-
|
|
372
|
+
return sendCommand("resumeRecording", { windowId });
|
|
307
373
|
}
|
|
308
|
-
|
|
309
374
|
function uploadRecording({ windowId }) {
|
|
310
|
-
|
|
375
|
+
return sendCommand("uploadRecording", { windowId });
|
|
311
376
|
}
|
|
312
|
-
|
|
313
377
|
function prepareDesktopAudioRecording() {
|
|
314
|
-
|
|
378
|
+
return sendCommand("prepareDesktopAudioRecording");
|
|
315
379
|
}
|
|
316
|
-
|
|
317
380
|
function requestPermission(permission) {
|
|
318
|
-
|
|
381
|
+
return sendCommand("requestPermission", { permission });
|
|
319
382
|
}
|
|
320
|
-
|
|
321
383
|
function addEventListener(type, callback) {
|
|
322
|
-
|
|
384
|
+
listeners.push({ type, callback });
|
|
323
385
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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
|
|
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
|
|
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": "
|
|
23
|
+
"commit_sha": "f1c3fbbb113f8f5a51d801dd9fe240b371125539"
|
|
22
24
|
}
|