@recallai/desktop-sdk 2.0.8 → 2.0.9
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 +21 -0
- package/index.d.ts +6 -2
- package/index.js +199 -25
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
### 2.0.9 [patch] (MacOS+Windows) - 2026-03-27
|
|
2
|
+
- Forward invalid command errors back to user on Windows.
|
|
3
|
+
- Restart video capture on windows as necessary.
|
|
4
|
+
- Improvements to audio device tracking algorithms on Windows.
|
|
5
|
+
- Teams audio capture fixes on Windows.
|
|
6
|
+
- Teams delayed detection fixes on Windows.
|
|
7
|
+
- Fixed issue where Chromium browsers would occasionally experience slow detection speed when joining the meeting with the microphone muted on MacOS
|
|
8
|
+
- Reduced initial startup burden associated with mic logs on MacOS
|
|
9
|
+
- Zoom screenshare detection works with annotations disabled
|
|
10
|
+
- Fixed issues with speaker labels being misattributed in > 2 participant meetings on MacOS
|
|
11
|
+
- Fixed issue with Teams not firing meeting-closed when closed from PIP view with main window minimized on MacOS
|
|
12
|
+
- Fixed issues with Arc / Brave sometimes failing to detect after-call screen on MacOS
|
|
13
|
+
- Fixed issues with chromium browsers prematurely ending meeting when switching tabs without PIP enabled on MacOS
|
|
14
|
+
- Fixed issue where transcript events would stop firing after recovering from network outage on MacOS
|
|
15
|
+
- Fixed issue where having video frame capture enabled would occasionally cause crashes on MacOS
|
|
16
|
+
- Fixed issue where Brave meetings would sometimes not be detected after leaving and rejoining meeting quickly on MacOS
|
|
17
|
+
- Fixed issue where audio would sometimes die mid-stream causing transcription and audio loss
|
|
18
|
+
- Fixed issue where audio streams would die and never recover
|
|
19
|
+
- Add additional telemetry to audio stream lifecycle
|
|
20
|
+
- Add additional telemetry and fix to commands not properly responding on macOS
|
|
21
|
+
|
|
1
22
|
### 2.0.8 [patch] (MacOS+Windows) - 2026-03-13
|
|
2
23
|
- Reduced blips/audio-glitches in the audio mixing process
|
|
3
24
|
- Fixed Windows audio latency during capture
|
package/index.d.ts
CHANGED
|
@@ -52,7 +52,7 @@ export interface ResumeRecordingConfig {
|
|
|
52
52
|
windowId: string;
|
|
53
53
|
}
|
|
54
54
|
/**
|
|
55
|
-
* @deprecated Recordings are uploaded based on
|
|
55
|
+
* @deprecated Recordings are automatically uploaded based on your rentention configuration. This is now a no-op.
|
|
56
56
|
*/
|
|
57
57
|
export interface UploadRecordingConfig {
|
|
58
58
|
windowId: string;
|
|
@@ -134,13 +134,15 @@ export declare function stopRecording({ windowId }: StopRecordingConfig): Promis
|
|
|
134
134
|
export declare function pauseRecording({ windowId }: PauseRecordingConfig): Promise<null>;
|
|
135
135
|
export declare function resumeRecording({ windowId }: ResumeRecordingConfig): Promise<null>;
|
|
136
136
|
/**
|
|
137
|
-
* @deprecated Recordings are uploaded based on
|
|
137
|
+
* @deprecated Recordings are automatically uploaded based on your rentention configuration. This is now a no-op.
|
|
138
138
|
*/
|
|
139
139
|
export declare function uploadRecording({ windowId }: UploadRecordingConfig): Promise<null>;
|
|
140
140
|
export declare function prepareDesktopAudioRecording(): Promise<string>;
|
|
141
141
|
export declare function requestPermission(permission: Permission): Promise<null>;
|
|
142
142
|
export declare function testUnexpectedShutdown(): Promise<null>;
|
|
143
143
|
export declare function addEventListener<T extends keyof EventTypeToPayloadMap>(type: T, callback: (event: EventTypeToPayloadMap[T]) => void): void;
|
|
144
|
+
export declare function removeEventListener<T extends keyof EventTypeToPayloadMap>(type: T, callback: (event: EventTypeToPayloadMap[T]) => void): void;
|
|
145
|
+
export declare function removeAllEventListeners(): void;
|
|
144
146
|
declare const RecallAiSdk: {
|
|
145
147
|
init: typeof init;
|
|
146
148
|
shutdown: typeof shutdown;
|
|
@@ -152,6 +154,8 @@ declare const RecallAiSdk: {
|
|
|
152
154
|
prepareDesktopAudioRecording: typeof prepareDesktopAudioRecording;
|
|
153
155
|
requestPermission: typeof requestPermission;
|
|
154
156
|
addEventListener: typeof addEventListener;
|
|
157
|
+
removeEventListener: typeof removeEventListener;
|
|
158
|
+
removeAllEventListeners: typeof removeAllEventListeners;
|
|
155
159
|
testUnexpectedShutdown: typeof testUnexpectedShutdown;
|
|
156
160
|
};
|
|
157
161
|
export default RecallAiSdk;
|
package/index.js
CHANGED
|
@@ -46,6 +46,8 @@ exports.prepareDesktopAudioRecording = prepareDesktopAudioRecording;
|
|
|
46
46
|
exports.requestPermission = requestPermission;
|
|
47
47
|
exports.testUnexpectedShutdown = testUnexpectedShutdown;
|
|
48
48
|
exports.addEventListener = addEventListener;
|
|
49
|
+
exports.removeEventListener = removeEventListener;
|
|
50
|
+
exports.removeAllEventListeners = removeAllEventListeners;
|
|
49
51
|
const path = __importStar(require("path"));
|
|
50
52
|
const child_process_1 = require("child_process");
|
|
51
53
|
const readline = __importStar(require("node:readline"));
|
|
@@ -76,6 +78,14 @@ let lastOptions;
|
|
|
76
78
|
let remainingAutomaticRestarts = 10;
|
|
77
79
|
let unexpectedShutdown = false;
|
|
78
80
|
let exiting = false;
|
|
81
|
+
let pendingStdinWrites = [];
|
|
82
|
+
let flushingPendingStdinWrites = false;
|
|
83
|
+
let waitingForStdinDrain = false;
|
|
84
|
+
let stdinWriteSession = 0;
|
|
85
|
+
let nextWriteSequence = 1;
|
|
86
|
+
let activeDrainStartMs = null;
|
|
87
|
+
let activeDrainStartIso = null;
|
|
88
|
+
let activeDrainWriteSequence = null;
|
|
79
89
|
let logBuffer = [];
|
|
80
90
|
let logIndex = 0;
|
|
81
91
|
let packageVersion;
|
|
@@ -216,6 +226,157 @@ function flushPendingCommands(err) {
|
|
|
216
226
|
delete pendingCommands[commandId];
|
|
217
227
|
});
|
|
218
228
|
}
|
|
229
|
+
function resolvePendingCommand(commandId, result) {
|
|
230
|
+
const pendingCommand = pendingCommands[commandId];
|
|
231
|
+
if (!pendingCommand) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
pendingCommand.resolve(result);
|
|
235
|
+
delete pendingCommands[commandId];
|
|
236
|
+
}
|
|
237
|
+
function rejectPendingCommand(commandId, err) {
|
|
238
|
+
const pendingCommand = pendingCommands[commandId];
|
|
239
|
+
if (!pendingCommand) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
pendingCommand.reject(err);
|
|
243
|
+
delete pendingCommands[commandId];
|
|
244
|
+
}
|
|
245
|
+
function resetPendingStdinWrites() {
|
|
246
|
+
pendingStdinWrites = [];
|
|
247
|
+
flushingPendingStdinWrites = false;
|
|
248
|
+
waitingForStdinDrain = false;
|
|
249
|
+
stdinWriteSession++;
|
|
250
|
+
activeDrainStartMs = null;
|
|
251
|
+
activeDrainStartIso = null;
|
|
252
|
+
activeDrainWriteSequence = null;
|
|
253
|
+
}
|
|
254
|
+
// Serialize writes into the child stdin and stop when the stream applies backpressure.
|
|
255
|
+
function flushPendingStdinWrites() {
|
|
256
|
+
if (flushingPendingStdinWrites || waitingForStdinDrain) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const currentProc = proc;
|
|
260
|
+
const stdin = currentProc?.stdin;
|
|
261
|
+
if (!currentProc || !stdin || currentProc.exitCode !== null) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
flushingPendingStdinWrites = true;
|
|
265
|
+
const session = stdinWriteSession;
|
|
266
|
+
try {
|
|
267
|
+
while (pendingStdinWrites.length > 0) {
|
|
268
|
+
if (proc !== currentProc || currentProc.stdin !== stdin || currentProc.exitCode !== null) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
const nextWrite = pendingStdinWrites.shift();
|
|
272
|
+
if (!nextWrite) {
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
const canContinue = stdin.write(nextWrite.data, (err) => {
|
|
276
|
+
if (!err) {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (session !== stdinWriteSession) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
rejectPendingCommand(nextWrite.commandId, new Error(`Failed writing ${nextWrite.command} command [${nextWrite.writeSequence}] (${nextWrite.commandId}) to Desktop SDK stdin: ${err.message}`));
|
|
283
|
+
if (currentProc.exitCode === null && !currentProc.killed) {
|
|
284
|
+
currentProc.kill();
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
if (!canContinue) {
|
|
288
|
+
activeDrainStartMs = Date.now();
|
|
289
|
+
activeDrainStartIso = new Date(activeDrainStartMs).toISOString();
|
|
290
|
+
activeDrainWriteSequence = nextWrite.writeSequence;
|
|
291
|
+
waitingForStdinDrain = true;
|
|
292
|
+
stdin.once('drain', () => {
|
|
293
|
+
if (session !== stdinWriteSession) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (proc !== currentProc || currentProc.stdin !== stdin || currentProc.exitCode !== null) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
const drainStopMs = Date.now();
|
|
300
|
+
const drainStartMs = activeDrainStartMs;
|
|
301
|
+
const drainStartIso = activeDrainStartIso;
|
|
302
|
+
const drainWriteSequence = activeDrainWriteSequence;
|
|
303
|
+
activeDrainStartMs = null;
|
|
304
|
+
activeDrainStartIso = null;
|
|
305
|
+
activeDrainWriteSequence = null;
|
|
306
|
+
waitingForStdinDrain = false;
|
|
307
|
+
flushingPendingStdinWrites = false;
|
|
308
|
+
if (drainStartMs !== null && drainStartIso) {
|
|
309
|
+
const drainStopIso = new Date(drainStopMs).toISOString();
|
|
310
|
+
const durationMs = drainStopMs - drainStartMs;
|
|
311
|
+
void enqueueCommand("log", {
|
|
312
|
+
log: `stdin drain completed | start=${drainStartIso} | stop=${drainStopIso} | durationMs=${durationMs} | blockedWriteSequence=${drainWriteSequence ?? "unknown"}`,
|
|
313
|
+
level: "info",
|
|
314
|
+
echo: false
|
|
315
|
+
}, { front: true, suppressSendLog: true }).catch((error) => {
|
|
316
|
+
if (proc?.exitCode === 0 || exiting) {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
console.error("Failed to enqueue stdin drain log", error.stack, error.message);
|
|
320
|
+
});
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
flushPendingStdinWrites();
|
|
324
|
+
});
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
catch (err) {
|
|
330
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
331
|
+
resetPendingStdinWrites();
|
|
332
|
+
flushPendingCommands(error);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
finally {
|
|
336
|
+
if (!waitingForStdinDrain) {
|
|
337
|
+
flushingPendingStdinWrites = false;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
function enqueueStdinWrite(write, front = false) {
|
|
342
|
+
if (front) {
|
|
343
|
+
pendingStdinWrites.unshift(write);
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
pendingStdinWrites.push(write);
|
|
347
|
+
}
|
|
348
|
+
flushPendingStdinWrites();
|
|
349
|
+
}
|
|
350
|
+
function enqueueCommand(command, params = {}, options = {}) {
|
|
351
|
+
return new Promise((resolve, reject) => {
|
|
352
|
+
const currentProc = proc;
|
|
353
|
+
const stdin = currentProc?.stdin;
|
|
354
|
+
if (!currentProc ||
|
|
355
|
+
currentProc.exitCode !== null ||
|
|
356
|
+
currentProc.killed ||
|
|
357
|
+
!stdin ||
|
|
358
|
+
stdin.destroyed ||
|
|
359
|
+
stdin.writableEnded ||
|
|
360
|
+
!stdin.writable) {
|
|
361
|
+
reject(new Error("The Desktop SDK is not started or is no longer accepting commands; call `shutdown` and `init` to start it again."));
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
const commandId = (0, uuid_1.v4)();
|
|
365
|
+
const writeSequence = nextWriteSequence++;
|
|
366
|
+
pendingCommands[commandId] = { resolve, reject, command, writeSequence };
|
|
367
|
+
const payload = {
|
|
368
|
+
command,
|
|
369
|
+
commandId,
|
|
370
|
+
writeSequence,
|
|
371
|
+
params
|
|
372
|
+
};
|
|
373
|
+
const payloadStr = JSON.stringify(payload);
|
|
374
|
+
enqueueStdinWrite({ command, commandId, data: payloadStr + "\n", writeSequence }, options.front ?? false);
|
|
375
|
+
if (!options.suppressSendLog && command !== "log") {
|
|
376
|
+
doLog("info", [`Sending command [${writeSequence}]: ` + payloadStr], false);
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
}
|
|
219
380
|
function startProcess() {
|
|
220
381
|
if (proc && proc.exitCode === null) {
|
|
221
382
|
logError("Desktop SDK: Trying to start process while it is already started");
|
|
@@ -289,6 +450,10 @@ function startProcess() {
|
|
|
289
450
|
...envExtra,
|
|
290
451
|
}
|
|
291
452
|
});
|
|
453
|
+
const currentProc = proc;
|
|
454
|
+
stdinWriteSession++;
|
|
455
|
+
flushingPendingStdinWrites = false;
|
|
456
|
+
waitingForStdinDrain = false;
|
|
292
457
|
if (process.platform === "darwin") {
|
|
293
458
|
const fd3 = proc.stdio[3];
|
|
294
459
|
if (fd3 && fd3 instanceof node_stream_1.Readable) {
|
|
@@ -309,15 +474,13 @@ function startProcess() {
|
|
|
309
474
|
}
|
|
310
475
|
break;
|
|
311
476
|
case "response":
|
|
312
|
-
|
|
313
|
-
if (pendingCommand) {
|
|
477
|
+
if (pendingCommands[data.commandId]) {
|
|
314
478
|
if (data.status === "success") {
|
|
315
|
-
|
|
479
|
+
resolvePendingCommand(data.commandId, data.result);
|
|
316
480
|
}
|
|
317
481
|
else {
|
|
318
|
-
|
|
482
|
+
rejectPendingCommand(data.commandId, new Error(data.result));
|
|
319
483
|
}
|
|
320
|
-
delete pendingCommands[data.commandId];
|
|
321
484
|
}
|
|
322
485
|
break;
|
|
323
486
|
}
|
|
@@ -341,7 +504,18 @@ function startProcess() {
|
|
|
341
504
|
}
|
|
342
505
|
});
|
|
343
506
|
proc.on('error', (error) => {
|
|
507
|
+
if (proc !== currentProc) {
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
proc = null;
|
|
511
|
+
resetPendingStdinWrites();
|
|
344
512
|
flushPendingCommands(new Error(`Process error: ${error.message}`));
|
|
513
|
+
rlData?.close();
|
|
514
|
+
rlStdout?.close();
|
|
515
|
+
rlStderr?.close();
|
|
516
|
+
rlData = null;
|
|
517
|
+
rlStdout = null;
|
|
518
|
+
rlStderr = null;
|
|
345
519
|
emitEvent('error', {
|
|
346
520
|
type: 'process',
|
|
347
521
|
message: `The Desktop SDK server process has failed to start or exited improperly.`
|
|
@@ -349,10 +523,17 @@ function startProcess() {
|
|
|
349
523
|
logError(`Desktop SDK: Process error: ${error.message}`);
|
|
350
524
|
});
|
|
351
525
|
proc.on('close', async (code, signal) => {
|
|
526
|
+
if (proc !== currentProc) {
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
proc = null;
|
|
530
|
+
resetPendingStdinWrites();
|
|
352
531
|
flushPendingCommands(new Error(`Process exited with code ${code}, signal ${signal}.`));
|
|
353
532
|
emitEvent('shutdown', { code: code ?? 0, signal: signal ?? '' });
|
|
533
|
+
rlData?.close();
|
|
354
534
|
rlStdout?.close();
|
|
355
535
|
rlStderr?.close();
|
|
536
|
+
rlData = null;
|
|
356
537
|
rlStdout = null;
|
|
357
538
|
rlStderr = null;
|
|
358
539
|
if (code === 0 || signal === 'SIGINT' || exiting) {
|
|
@@ -363,7 +544,6 @@ function startProcess() {
|
|
|
363
544
|
type: 'process',
|
|
364
545
|
message: "The Desktop SDK server process exited unexpectedly."
|
|
365
546
|
});
|
|
366
|
-
proc = null;
|
|
367
547
|
unexpectedShutdown = true;
|
|
368
548
|
if (lastOptions.restartOnError && remainingAutomaticRestarts > 0) {
|
|
369
549
|
remainingAutomaticRestarts--;
|
|
@@ -375,24 +555,7 @@ function startProcess() {
|
|
|
375
555
|
});
|
|
376
556
|
}
|
|
377
557
|
function sendCommand(command, params = {}) {
|
|
378
|
-
return
|
|
379
|
-
if (!proc || !proc.stdin) {
|
|
380
|
-
reject(new Error("The Desktop SDK is not started; call `init` to start it."));
|
|
381
|
-
return;
|
|
382
|
-
}
|
|
383
|
-
const commandId = (0, uuid_1.v4)();
|
|
384
|
-
pendingCommands[commandId] = { resolve, reject };
|
|
385
|
-
const payload = {
|
|
386
|
-
command,
|
|
387
|
-
commandId,
|
|
388
|
-
params
|
|
389
|
-
};
|
|
390
|
-
const payloadStr = JSON.stringify(payload);
|
|
391
|
-
proc.stdin.write(payloadStr + "\n");
|
|
392
|
-
if (command !== "log") {
|
|
393
|
-
doLog("info", ["Sending command: " + payloadStr], false);
|
|
394
|
-
}
|
|
395
|
-
});
|
|
558
|
+
return enqueueCommand(command, params);
|
|
396
559
|
}
|
|
397
560
|
async function doInit(options) {
|
|
398
561
|
startProcess();
|
|
@@ -460,7 +623,7 @@ function resumeRecording({ windowId }) {
|
|
|
460
623
|
return sendCommand("resumeRecording", { windowId });
|
|
461
624
|
}
|
|
462
625
|
/**
|
|
463
|
-
* @deprecated Recordings are uploaded based on
|
|
626
|
+
* @deprecated Recordings are automatically uploaded based on your rentention configuration. This is now a no-op.
|
|
464
627
|
*/
|
|
465
628
|
function uploadRecording({ windowId }) {
|
|
466
629
|
return sendCommand("uploadRecording", { windowId });
|
|
@@ -477,6 +640,15 @@ function testUnexpectedShutdown() {
|
|
|
477
640
|
function addEventListener(type, callback) {
|
|
478
641
|
listeners.push({ type, callback });
|
|
479
642
|
}
|
|
643
|
+
function removeEventListener(type, callback) {
|
|
644
|
+
const index = listeners.findIndex(l => l.type === type && l.callback === callback);
|
|
645
|
+
if (index !== -1) {
|
|
646
|
+
listeners.splice(index, 1);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
function removeAllEventListeners() {
|
|
650
|
+
listeners.length = 0;
|
|
651
|
+
}
|
|
480
652
|
const RecallAiSdk = {
|
|
481
653
|
init,
|
|
482
654
|
shutdown,
|
|
@@ -488,6 +660,8 @@ const RecallAiSdk = {
|
|
|
488
660
|
prepareDesktopAudioRecording,
|
|
489
661
|
requestPermission,
|
|
490
662
|
addEventListener,
|
|
663
|
+
removeEventListener,
|
|
664
|
+
removeAllEventListeners,
|
|
491
665
|
testUnexpectedShutdown
|
|
492
666
|
};
|
|
493
667
|
exports.default = RecallAiSdk;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@recallai/desktop-sdk",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.9",
|
|
4
4
|
"description": "Recall Desktop SDK",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"types": "./index.d.ts",
|
|
@@ -20,5 +20,5 @@
|
|
|
20
20
|
"@types/node": "^24.2.0",
|
|
21
21
|
"typescript": "^5.3.3"
|
|
22
22
|
},
|
|
23
|
-
"commit_sha": "
|
|
23
|
+
"commit_sha": "2bc571dbad7ed8b15a925c9a4cdd0e7fab3c6955"
|
|
24
24
|
}
|