@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 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 the upload token config.
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 the upload token config.
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
- const pendingCommand = pendingCommands[data.commandId];
313
- if (pendingCommand) {
477
+ if (pendingCommands[data.commandId]) {
314
478
  if (data.status === "success") {
315
- pendingCommand.resolve(data.result);
479
+ resolvePendingCommand(data.commandId, data.result);
316
480
  }
317
481
  else {
318
- pendingCommand.reject(new Error(data.result));
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 new Promise((resolve, reject) => {
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 the upload token config.
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.8",
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": "b7a1c938d0de3e558014e49d4e79299e71d84f17"
23
+ "commit_sha": "2bc571dbad7ed8b15a925c9a4cdd0e7fab3c6955"
24
24
  }