med-scribe-alliance-ts-sdk 2.0.30 → 2.0.32

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/dist/index.d.ts CHANGED
@@ -431,6 +431,7 @@ export interface ProcessingError {
431
431
  export interface PollOptions {
432
432
  maxAttempts?: number;
433
433
  intervalMs?: number;
434
+ timeoutMs?: number;
434
435
  onProgress?: (status: GetSessionStatusResponse) => void;
435
436
  /** AbortSignal to cancel polling early. */
436
437
  signal?: AbortSignal;
@@ -1042,6 +1043,7 @@ export declare class RecordingManager {
1042
1043
  private activeBaseUrl;
1043
1044
  private _isRecording;
1044
1045
  private _isStarting;
1046
+ private _startGeneration;
1045
1047
  private retryContext;
1046
1048
  constructor(callbackRegistry: CallbackRegistry, sessionManager: SessionManager, discoveryManager: DiscoveryManager, transport: ITransport, config?: RecordingManagerConfig);
1047
1049
  /**
@@ -1052,6 +1054,11 @@ export declare class RecordingManager {
1052
1054
  * 4. Start recording
1053
1055
  * 5. Dispatch events
1054
1056
  *
1057
+ * Race-safety: Uses a generation counter (`_startGeneration`) so that if
1058
+ * clearRecordingState() or reset() is called while this method is suspended
1059
+ * at an `await`, the resumed call detects the mismatch and aborts instead
1060
+ * of creating an orphaned recorder with a leaked mic/VAD.
1061
+ *
1055
1062
  * @param baseUrl - Server base URL (from discovery or SDK config)
1056
1063
  * @param options - Recording options (templates, model, etc.)
1057
1064
  * @param accessToken - Current Bearer token for upload auth headers
package/dist/index.mjs CHANGED
@@ -838,21 +838,34 @@ var G = class {
838
838
  try {
839
839
  let r = t ?? this.currentSession?.session_id;
840
840
  if (!r) throw new y("No active session. Provide a sessionId or start a session first.");
841
- let i = n?.maxAttempts ?? 60, a = n?.intervalMs ?? 2e3;
841
+ let i = n?.maxAttempts ?? 60, a = n?.intervalMs ?? 2e3, o = n?.timeoutMs, s = o !== void 0 && o > 0 ? Date.now() + o : void 0;
842
842
  this.debug && console.log("[ScribeSDK] Polling for completion:", r, {
843
843
  maxAttempts: i,
844
- intervalMs: a
844
+ intervalMs: a,
845
+ timeoutMs: o
845
846
  });
846
847
  for (let t = 1; t <= i; t++) {
847
848
  if (n?.signal?.aborted) throw new y("Polling was aborted", "polling_aborted", void 0, { session_id: r });
848
- let o = await this.getSessionStatus(e, r), s = o.data;
849
+ if (s !== void 0 && Date.now() >= s) throw new y(`Polling timed out after ${o}ms for session '${r}'`, "polling_timeout", void 0, {
850
+ session_id: r,
851
+ timeout_ms: o
852
+ });
853
+ let c = await this.getSessionStatus(e, r), l = c.data;
849
854
  if (n?.onProgress) try {
850
- n.onProgress(s);
855
+ n.onProgress(l);
851
856
  } catch (e) {
852
857
  console.error("[ScribeSDK] Error in poll onProgress callback:", e);
853
858
  }
854
- if (this.isTerminalStatus(s.status)) return this.debug && console.log("[ScribeSDK] Poll complete:", r, s.status, `(attempt ${t})`), o;
855
- t < i && await this.sleepWithAbort(a, n?.signal);
859
+ if (this.isTerminalStatus(l.status)) return this.debug && console.log("[ScribeSDK] Poll complete:", r, l.status, `(attempt ${t})`), c;
860
+ if (t < i) {
861
+ let e = a;
862
+ if (s !== void 0) {
863
+ let t = s - Date.now();
864
+ if (t <= 0) continue;
865
+ e = Math.min(a, t);
866
+ }
867
+ await this.sleepWithAbort(e, n?.signal);
868
+ }
856
869
  }
857
870
  throw new y(`Polling timed out after ${i} attempts for session '${r}'`, "polling_timeout", void 0, {
858
871
  session_id: r,
@@ -957,7 +970,7 @@ var oe = {
957
970
  } catch (e) {
958
971
  console.error("[ScribeSDK] Error destroying VAD:", e);
959
972
  }
960
- this.isRecording = !1;
973
+ this.micVad = null, this.isRecording = !1;
961
974
  }
962
975
  reset() {
963
976
  this.destroy(), this.vadPast = [], this.lastClipIndex = 0, this.silDurationAcc = 0, this.noSpeechStartTime = null, this.lastWarningTime = null, this.isLoading = !0, this.micVad = null;
@@ -1605,15 +1618,17 @@ var Y = class {
1605
1618
  }
1606
1619
  }, Q = class {
1607
1620
  constructor(e, t, n, r, i) {
1608
- this.recorder = null, this.activeSession = null, this.activeBaseUrl = "", this._isRecording = !1, this._isStarting = !1, this.retryContext = null, this.callbackRegistry = e, this.sessionManager = t, this.discoveryManager = n, this.transport = r, this.config = i ?? {};
1621
+ this.recorder = null, this.activeSession = null, this.activeBaseUrl = "", this._isRecording = !1, this._isStarting = !1, this._startGeneration = 0, this.retryContext = null, this.callbackRegistry = e, this.sessionManager = t, this.discoveryManager = n, this.transport = r, this.config = i ?? {};
1609
1622
  }
1610
1623
  async start(e, t, n) {
1611
1624
  if (this._isRecording || this._isStarting) throw new y("Recording is already in progress. Stop the current recording first.");
1612
- this._isStarting = !0, this.retryContext = null, this.activeBaseUrl = e;
1613
- let r = t.uploadType ?? "chunked", i = t.communicationProtocol ?? "http", a = {
1625
+ this._isStarting = !0;
1626
+ let r = ++this._startGeneration;
1627
+ this.retryContext = null, this.activeBaseUrl = e;
1628
+ let i = t.uploadType ?? "chunked", a = t.communicationProtocol ?? "http", o = {
1614
1629
  templates: t.templates,
1615
- upload_type: r,
1616
- communication_protocol: i,
1630
+ upload_type: i,
1631
+ communication_protocol: a,
1617
1632
  model: t.model,
1618
1633
  language_hint: t.languageHint,
1619
1634
  transcript_language: t.transcriptLanguage,
@@ -1623,69 +1638,99 @@ var Y = class {
1623
1638
  session_id: t.sessionId
1624
1639
  };
1625
1640
  try {
1626
- let i, o;
1641
+ let a, s;
1627
1642
  try {
1628
- let t = await this.sessionManager.createSession(e, a);
1629
- i = t.data, o = t.httpStatus;
1643
+ let t = await this.sessionManager.createSession(e, o);
1644
+ a = t.data, s = t.httpStatus;
1630
1645
  } catch (e) {
1631
- throw this.dispatchStartError(m.TRANSPORT_ERROR, g.SESSION_CREATION_FAILED, e), e;
1646
+ throw r === this._startGeneration && this.dispatchStartError(m.TRANSPORT_ERROR, g.SESSION_CREATION_FAILED, e), e;
1632
1647
  }
1633
- this.activeSession = i, this.callbackRegistry.dispatch("onSessionEvent", {
1648
+ if (r !== this._startGeneration) throw new y("Recording start was superseded by a concurrent operation.");
1649
+ this.activeSession = a, this.callbackRegistry.dispatch("onSessionEvent", {
1634
1650
  type: p.CREATED,
1635
1651
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1636
- data: i
1637
- }), this.recorder = this.createRecorder(r);
1638
- let s = {
1652
+ data: a
1653
+ });
1654
+ let c = this.createRecorder(i);
1655
+ this.recorder = c;
1656
+ let l = {
1639
1657
  accessToken: n,
1640
- uploadUrl: i.upload_url,
1658
+ uploadUrl: a.upload_url,
1641
1659
  uploadHeaders: this.buildUploadHeaders(n),
1642
- sessionId: i.session_id
1660
+ sessionId: a.session_id
1643
1661
  };
1644
1662
  try {
1645
- this.recorder.initialize(i, s);
1663
+ c.initialize(a, l);
1646
1664
  } catch (e) {
1647
- throw this.cleanupRecordingState(), this.dispatchStartError(m.VALIDATION_ERROR, g.RECORDER_INIT_FAILED, e), e;
1665
+ try {
1666
+ c.reset();
1667
+ } catch {}
1668
+ throw r === this._startGeneration && (this.cleanupRecordingState(), this.dispatchStartError(m.VALIDATION_ERROR, g.RECORDER_INIT_FAILED, e)), e;
1648
1669
  }
1649
- this.recorder instanceof X && this.applyDiscoveryOverrides(this.recorder);
1670
+ c instanceof X && this.applyDiscoveryOverrides(c);
1650
1671
  try {
1651
- await this.recorder.start(t.deviceId);
1672
+ await c.start(t.deviceId);
1652
1673
  } catch (e) {
1653
- throw this.cleanupRecordingState(), this.dispatchStartError(m.VAD_ERROR, g.VAD_START_FAILED, e), e;
1674
+ try {
1675
+ c.reset();
1676
+ } catch {}
1677
+ throw r === this._startGeneration && (this.cleanupRecordingState(), this.dispatchStartError(m.VAD_ERROR, g.VAD_START_FAILED, e)), e;
1678
+ }
1679
+ if (r !== this._startGeneration) {
1680
+ try {
1681
+ c.reset();
1682
+ } catch {}
1683
+ throw new y("Recording start was superseded by a concurrent operation.");
1654
1684
  }
1655
1685
  return this._isRecording = !0, this.callbackRegistry.dispatch("onRecordingStateChange", {
1656
1686
  type: u.STARTED,
1657
1687
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1658
- }), this.config.debug && console.log("[ScribeSDK] Recording started:", i.session_id), {
1659
- data: i,
1660
- httpStatus: o
1688
+ }), this.config.debug && console.log("[ScribeSDK] Recording started:", a.session_id), {
1689
+ data: a,
1690
+ httpStatus: s
1661
1691
  };
1662
1692
  } finally {
1663
- this._isStarting = !1;
1693
+ r === this._startGeneration && (this._isStarting = !1);
1664
1694
  }
1665
1695
  }
1666
1696
  async startWithExistingSession(e, t, n, r) {
1667
1697
  if (this._isRecording || this._isStarting) throw new y("Recording is already in progress. Stop the current recording first.");
1668
- this._isStarting = !0, this.retryContext = null, this.activeBaseUrl = e;
1669
- let i = n?.uploadType ?? "chunked";
1698
+ this._isStarting = !0;
1699
+ let i = ++this._startGeneration;
1700
+ this.retryContext = null, this.activeBaseUrl = e;
1701
+ let a = n?.uploadType ?? "chunked";
1670
1702
  this.activeSession = t;
1671
1703
  try {
1672
- this.recorder = this.createRecorder(i);
1673
- let e = {
1704
+ let e = this.createRecorder(a);
1705
+ this.recorder = e;
1706
+ let o = {
1674
1707
  accessToken: r,
1675
1708
  uploadUrl: t.upload_url,
1676
1709
  uploadHeaders: this.buildUploadHeaders(r),
1677
1710
  sessionId: t.session_id
1678
1711
  };
1679
1712
  try {
1680
- this.recorder.initialize(t, e);
1681
- } catch (e) {
1682
- throw this.cleanupRecordingState(), this.dispatchStartError(m.VALIDATION_ERROR, g.RECORDER_INIT_FAILED, e), e;
1713
+ e.initialize(t, o);
1714
+ } catch (t) {
1715
+ try {
1716
+ e.reset();
1717
+ } catch {}
1718
+ throw i === this._startGeneration && (this.cleanupRecordingState(), this.dispatchStartError(m.VALIDATION_ERROR, g.RECORDER_INIT_FAILED, t)), t;
1683
1719
  }
1684
- this.recorder instanceof X && this.applyDiscoveryOverrides(this.recorder);
1720
+ e instanceof X && this.applyDiscoveryOverrides(e);
1685
1721
  try {
1686
- await this.recorder.start(n?.deviceId);
1687
- } catch (e) {
1688
- throw this.cleanupRecordingState(), this.dispatchStartError(m.VAD_ERROR, g.VAD_START_FAILED, e), e;
1722
+ await e.start(n?.deviceId);
1723
+ } catch (t) {
1724
+ try {
1725
+ e.reset();
1726
+ } catch {}
1727
+ throw i === this._startGeneration && (this.cleanupRecordingState(), this.dispatchStartError(m.VAD_ERROR, g.VAD_START_FAILED, t)), t;
1728
+ }
1729
+ if (i !== this._startGeneration) {
1730
+ try {
1731
+ e.reset();
1732
+ } catch {}
1733
+ throw new y("Recording start was superseded by a concurrent operation.");
1689
1734
  }
1690
1735
  return this._isRecording = !0, this.callbackRegistry.dispatch("onRecordingStateChange", {
1691
1736
  type: u.STARTED,
@@ -1695,7 +1740,7 @@ var Y = class {
1695
1740
  httpStatus: void 0
1696
1741
  };
1697
1742
  } finally {
1698
- this._isStarting = !1;
1743
+ i === this._startGeneration && (this._isStarting = !1);
1699
1744
  }
1700
1745
  }
1701
1746
  pause() {
@@ -1801,9 +1846,11 @@ var Y = class {
1801
1846
  }
1802
1847
  }
1803
1848
  forceStop() {
1804
- if (!(!this.recorder || !this._isRecording)) try {
1849
+ if (++this._startGeneration, !(!this.recorder || !this._isRecording)) try {
1805
1850
  this.recorder.reset();
1806
- } catch {} finally {
1851
+ } catch (e) {
1852
+ console.error("[ScribeSDK] Error in forceStop:", e);
1853
+ } finally {
1807
1854
  this.callbackRegistry.dispatch("onRecordingStateChange", {
1808
1855
  type: u.ENDED,
1809
1856
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -1821,7 +1868,7 @@ var Y = class {
1821
1868
  this.recorder && this.recorder instanceof X && this.recorder.updateAuthToken(e), this.transport.setAuthToken(e);
1822
1869
  }
1823
1870
  reset() {
1824
- this.recorder && this.recorder.reset(), this.retryContext = null, this.cleanupRecordingState();
1871
+ ++this._startGeneration, this.recorder && this.recorder.reset(), this.retryContext = null, this.cleanupRecordingState();
1825
1872
  }
1826
1873
  isRecording() {
1827
1874
  return this._isRecording;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "med-scribe-alliance-ts-sdk",
3
- "version": "2.0.30",
3
+ "version": "2.0.32",
4
4
  "description": "TypeScript SDK for the MedScribe Alliance Protocol",
5
5
  "main": "dist/index.mjs",
6
6
  "module": "dist/index.mjs",