med-scribe-alliance-ts-sdk 2.0.39 → 2.0.41

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/README.md CHANGED
@@ -363,6 +363,7 @@ interface RecordingOptions {
363
363
  deviceId?: string; // Specific microphone device ID
364
364
  sessionMode?: string; // 'consultation' | 'dictation'
365
365
  patientDetails?: PatientDetails; // Patient info
366
+ version?: string; // Optional API version (sent as a `version` query param on create-session)
366
367
  txnId?: string; // External transaction ID
367
368
  }
368
369
  ```
@@ -381,7 +382,7 @@ interface RecordingOptions {
381
382
  | Method | Returns | Description |
382
383
  |---|---|---|
383
384
  | `startRecording(options)` | `SDKResult<CreateSessionResponse>` | Create session + start mic + begin upload. |
384
- | `startRecordingWithSession(session, options?)` | `SDKResult<void>` | Attach recorder to an existing session. |
385
+ | `startRecordingWithSession(session, options?)` | `SDKResult<void>` | Attach recorder to an existing session. `options.version` (the version the session was created with) is reused when refreshing `upload_url` on upload failure. |
385
386
  | `pauseRecording()` | `void` | Pause VAD (mic stays open, no chunks created). |
386
387
  | `resumeRecording()` | `void` | Resume VAD processing. |
387
388
  | `endRecording()` | `SDKResult<StopRecordingResult>` | Stop mic, flush audio, wait for uploads, end session. |
package/dist/index.d.ts CHANGED
@@ -493,6 +493,8 @@ export interface RecordingOptions {
493
493
  sessionMode?: string;
494
494
  patientDetails?: PatientDetails;
495
495
  sessionId?: string;
496
+ /** Optional API version; sent as a `version` query param on the create-session request. */
497
+ version?: string;
496
498
  }
497
499
  export interface RecorderConfig {
498
500
  accessToken?: string;
@@ -501,6 +503,7 @@ export interface RecorderConfig {
501
503
  storageProvider: string;
502
504
  uploadHeaders: Record<string, string>;
503
505
  sessionId: string;
506
+ refreshUploadUrl?: () => Promise<SessionUploadInfo | null>;
504
507
  }
505
508
  export interface IRecorder {
506
509
  initialize(session: CreateSessionResponse, config: RecorderConfig): void;
@@ -697,10 +700,16 @@ export interface WorkerUpdateTokenMessage {
697
700
  type: "update_auth_token";
698
701
  token: string;
699
702
  }
703
+ /** Fresh upload payload sent in response to an upload_url_required request. */
704
+ export interface WorkerUpdateUploadUrlMessage {
705
+ type: "update_upload_url";
706
+ /** Provider-specific upload payload from a fresh getSessionStatus call. Null = no refresh available. */
707
+ upload: unknown | null;
708
+ }
700
709
  export interface WorkerTerminateMessage {
701
710
  type: "terminate";
702
711
  }
703
- export type MainToWorkerMessage = WorkerCompressAndUploadMessage | WorkerWaitForUploadsMessage | WorkerUpdateTokenMessage | WorkerTerminateMessage;
712
+ export type MainToWorkerMessage = WorkerCompressAndUploadMessage | WorkerWaitForUploadsMessage | WorkerUpdateTokenMessage | WorkerUpdateUploadUrlMessage | WorkerTerminateMessage;
704
713
  export interface WorkerChunkEncodedMessage {
705
714
  type: "chunk_encoded";
706
715
  fileName: string;
@@ -722,7 +731,12 @@ export interface WorkerAllUploadsCompleteMessage {
722
731
  export interface WorkerTokenRequiredMessage {
723
732
  type: "token_required";
724
733
  }
725
- export type WorkerToMainMessage = WorkerChunkEncodedMessage | WorkerUploadSuccessMessage | WorkerUploadFailedMessage | WorkerAllUploadsCompleteMessage | WorkerTokenRequiredMessage;
734
+ /** Worker hit an upload error and wants a fresh upload_url before retrying. */
735
+ export interface WorkerUploadUrlRequiredMessage {
736
+ type: "upload_url_required";
737
+ fileName: string;
738
+ }
739
+ export type WorkerToMainMessage = WorkerChunkEncodedMessage | WorkerUploadSuccessMessage | WorkerUploadFailedMessage | WorkerAllUploadsCompleteMessage | WorkerTokenRequiredMessage | WorkerUploadUrlRequiredMessage;
726
740
  export declare class ScribeClient {
727
741
  private config;
728
742
  private transport;
@@ -754,6 +768,7 @@ export declare class ScribeClient {
754
768
  startRecordingWithSession(session: CreateSessionResponse, options?: {
755
769
  uploadType?: string;
756
770
  deviceId?: string;
771
+ version?: string;
757
772
  }): Promise<SDKResult<void>>;
758
773
  /**
759
774
  * Pause the active recording.
@@ -1127,6 +1142,7 @@ export declare class RecordingManager {
1127
1142
  private recorder;
1128
1143
  private activeSession;
1129
1144
  private activeBaseUrl;
1145
+ private activeUploadUrlRefresher;
1130
1146
  private _isRecording;
1131
1147
  private _isStarting;
1132
1148
  private _startGeneration;
@@ -1164,6 +1180,7 @@ export declare class RecordingManager {
1164
1180
  startWithExistingSession(baseUrl: string, session: CreateSessionResponse, options?: {
1165
1181
  uploadType?: string;
1166
1182
  deviceId?: string;
1183
+ version?: string;
1167
1184
  }, accessToken?: string): Promise<ApiCallResult<void>>;
1168
1185
  /**
1169
1186
  * Pause the active recording.
@@ -1236,6 +1253,8 @@ export declare class RecordingManager {
1236
1253
  * Build upload headers from the current auth state.
1237
1254
  */
1238
1255
  private buildUploadHeaders;
1256
+ private buildUploadUrlRefresher;
1257
+ private fetchFreshUploadUrl;
1239
1258
  /**
1240
1259
  * Apply discovery-driven overrides to ChunkedRecorder's VAD config.
1241
1260
  * For example, max_chunk_duration_seconds from discovery overrides the default.
package/dist/index.mjs CHANGED
@@ -277,7 +277,8 @@ var r = "/.well-known/medscribealliance", i = 3600 * 1e3, a = /* @__PURE__ */ fu
277
277
  gender: e.string().optional(),
278
278
  mobile: e.number().optional()
279
279
  }).optional(),
280
- sessionId: e.string().optional()
280
+ sessionId: e.string().optional(),
281
+ version: e.string().optional()
281
282
  }), N = class {
282
283
  validateDiscoveryResponse(e) {
283
284
  this.parseWithValidationError(ae, e, "Invalid discovery response");
@@ -381,7 +382,7 @@ var I = class {
381
382
  }
382
383
  async request(e) {
383
384
  try {
384
- return await P(() => this.executeRequest(e), this.getRetryOptions(e));
385
+ return e.isUpload ? await P(() => this.executeRequest(e), this.getRetryOptions(e)) : await this.executeRequest(e);
385
386
  } catch (t) {
386
387
  throw t instanceof v ? t : new T(`Network error: ${t instanceof Error ? t.message : "Unknown error"}`, {
387
388
  url: e.url,
@@ -505,7 +506,7 @@ var I = class {
505
506
  }
506
507
  async request(e) {
507
508
  try {
508
- return await P(() => this.executeRequest(e), this.getRetryOptions(e));
509
+ return e.isUpload ? await P(() => this.executeRequest(e), this.getRetryOptions(e)) : await this.executeRequest(e);
509
510
  } catch (t) {
510
511
  throw t instanceof v ? t : new T(`IPC error: ${t instanceof Error ? t.message : "Unknown error"}`, {
511
512
  url: e.url,
@@ -1286,7 +1287,7 @@ async function Y(e, t) {
1286
1287
  //#region src/worker/worker-manager.ts
1287
1288
  var Se = class {
1288
1289
  constructor(e, t, n, r) {
1289
- if (this.worker = null, this.port = null, this.useWorker = !1, this.uploadPayload = {}, this.storageProviderName = "", this.uploadHeaders = {}, this.pendingUploads = /* @__PURE__ */ new Set(), this.allUploadsResolver = null, this.callbackRegistry = e, this.fileManager = t, this.transport = n, !r?.forceMainThread && typeof SharedWorker < "u" && r?.workerScriptUrl) try {
1290
+ if (this.worker = null, this.port = null, this.useWorker = !1, this.uploadPayload = {}, this.storageProviderName = "", this.uploadHeaders = {}, this.refreshUploadUrl = null, this.inFlightRefresh = null, this.pendingUploads = /* @__PURE__ */ new Set(), this.allUploadsResolver = null, this.callbackRegistry = e, this.fileManager = t, this.transport = n, !r?.forceMainThread && typeof SharedWorker < "u" && r?.workerScriptUrl) try {
1290
1291
  this.worker = new SharedWorker(r.workerScriptUrl, { name: "scribe-sdk-worker" }), this.port = this.worker.port, this.port.onmessage = (e) => {
1291
1292
  this.handleWorkerMessage(e.data);
1292
1293
  }, this.port.start(), this.useWorker = !0;
@@ -1294,8 +1295,8 @@ var Se = class {
1294
1295
  console.warn("[ScribeSDK] SharedWorker failed to initialize, falling back to main thread:", e), this.worker = null, this.port = null, this.useWorker = !1;
1295
1296
  }
1296
1297
  }
1297
- setUploadConfig(e, t, n) {
1298
- this.uploadPayload = e, this.storageProviderName = t, this.uploadHeaders = n, J(t);
1298
+ setUploadConfig(e, t, n, r) {
1299
+ this.uploadPayload = e, this.storageProviderName = t, this.uploadHeaders = n, this.refreshUploadUrl = r ?? null, J(t);
1299
1300
  }
1300
1301
  compressAndUpload(e, t, n) {
1301
1302
  let r = e.length / H;
@@ -1405,6 +1406,28 @@ var Se = class {
1405
1406
  this.updateAuthToken(e);
1406
1407
  } });
1407
1408
  break;
1409
+ case "upload_url_required":
1410
+ this.handleUploadUrlRequired();
1411
+ break;
1412
+ }
1413
+ }
1414
+ async handleUploadUrlRequired() {
1415
+ let e = await this.refreshUploadPayload();
1416
+ this.postToWorker({
1417
+ type: "update_upload_url",
1418
+ upload: e
1419
+ });
1420
+ }
1421
+ async refreshUploadPayload() {
1422
+ if (!this.refreshUploadUrl) return null;
1423
+ try {
1424
+ this.inFlightRefresh ||= this.refreshUploadUrl();
1425
+ let e = await this.inFlightRefresh;
1426
+ return e && (this.uploadPayload = e), e;
1427
+ } catch (e) {
1428
+ return console.error("[ScribeSDK] Failed to refresh upload_url:", e), null;
1429
+ } finally {
1430
+ this.inFlightRefresh = null;
1408
1431
  }
1409
1432
  }
1410
1433
  compressAndUploadOnMainThread(e, t, n) {
@@ -1434,12 +1457,25 @@ var Se = class {
1434
1457
  fileName: t,
1435
1458
  chunkData: i.chunks
1436
1459
  }
1437
- }), await Y(this.transport, {
1438
- fileName: t,
1439
- blob: r,
1440
- upload: this.uploadPayload,
1441
- storageProvider: this.storageProviderName
1442
- }), this.fileManager.markSuccess(n), this.dispatchUploadProgress();
1460
+ });
1461
+ try {
1462
+ await Y(this.transport, {
1463
+ fileName: t,
1464
+ blob: r,
1465
+ upload: this.uploadPayload,
1466
+ storageProvider: this.storageProviderName
1467
+ });
1468
+ } catch (e) {
1469
+ let n = await this.refreshUploadPayload();
1470
+ if (!n) throw e;
1471
+ await Y(this.transport, {
1472
+ fileName: t,
1473
+ blob: r,
1474
+ upload: n,
1475
+ storageProvider: this.storageProviderName
1476
+ });
1477
+ }
1478
+ this.fileManager.markSuccess(n), this.dispatchUploadProgress();
1443
1479
  } catch (e) {
1444
1480
  this.fileManager.markFailure(n, r ?? new Blob(), e?.message ?? "Upload failed"), this.callbackRegistry.dispatch("onUploadEvent", {
1445
1481
  type: f.FAILED,
@@ -1483,7 +1519,7 @@ var Se = class {
1483
1519
  initialize(e, t) {
1484
1520
  if (!t.upload || typeof t.upload != "object") throw Error("Upload payload is required for chunked recording");
1485
1521
  if (!t.storageProvider) throw Error("Storage provider is required for chunked recording");
1486
- this.workerManager.setUploadConfig(t.upload, t.storageProvider, t.uploadHeaders), this.initialized = !0;
1522
+ this.workerManager.setUploadConfig(t.upload, t.storageProvider, t.uploadHeaders, t.refreshUploadUrl), this.initialized = !0;
1487
1523
  }
1488
1524
  async start(e) {
1489
1525
  if (await this.vadClient.init(e), this.vadClient.isVadLoading() && (await this.vadClient.init(e), this.vadClient.isVadLoading())) throw Error("VAD instance failed to initialize after retry");
@@ -1705,7 +1741,7 @@ var Se = class {
1705
1741
  }
1706
1742
  }, Q = class {
1707
1743
  constructor(e, t, n, r, i) {
1708
- 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 ?? {};
1744
+ this.recorder = null, this.activeSession = null, this.activeBaseUrl = "", this.activeUploadUrlRefresher = null, 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 ?? {};
1709
1745
  }
1710
1746
  async start(e, t, n) {
1711
1747
  if (this._isRecording || this._isStarting) throw new v("Recording is already in progress. Stop the current recording first.");
@@ -1727,8 +1763,8 @@ var Se = class {
1727
1763
  try {
1728
1764
  let a, s;
1729
1765
  try {
1730
- let t = await this.sessionManager.createSession(e, o);
1731
- a = t.data, s = t.httpStatus;
1766
+ let n = await this.sessionManager.createSession(e, o, t.version);
1767
+ a = n.data, s = n.httpStatus;
1732
1768
  } catch (e) {
1733
1769
  throw r === this._startGeneration && this.dispatchStartError(m.TRANSPORT_ERROR, g.SESSION_CREATION_FAILED, e), e;
1734
1770
  }
@@ -1746,13 +1782,14 @@ var Se = class {
1746
1782
  data: a
1747
1783
  });
1748
1784
  let l = this.createRecorder(i);
1749
- this.recorder = l;
1785
+ this.recorder = l, this.activeUploadUrlRefresher = this.buildUploadUrlRefresher(e, a.session_id, t.version);
1750
1786
  let d = {
1751
1787
  accessToken: n,
1752
1788
  upload: a.upload_url,
1753
1789
  storageProvider: c,
1754
1790
  uploadHeaders: this.buildUploadHeaders(n),
1755
- sessionId: a.session_id
1791
+ sessionId: a.session_id,
1792
+ refreshUploadUrl: this.activeUploadUrlRefresher
1756
1793
  };
1757
1794
  try {
1758
1795
  l.initialize(a, d);
@@ -1796,41 +1833,42 @@ var Se = class {
1796
1833
  let a = n?.uploadType ?? "chunked";
1797
1834
  this.activeSession = t;
1798
1835
  try {
1799
- let e;
1836
+ let o;
1800
1837
  try {
1801
- e = this.resolveStorageProviderName();
1838
+ o = this.resolveStorageProviderName();
1802
1839
  } catch (e) {
1803
1840
  throw i === this._startGeneration && (this.cleanupRecordingState(), this.dispatchStartError(m.VALIDATION_ERROR, g.UNSUPPORTED_STORAGE_PROVIDER, e)), e;
1804
1841
  }
1805
- let o = this.createRecorder(a);
1806
- this.recorder = o;
1807
- let s = {
1842
+ let s = this.createRecorder(a);
1843
+ this.recorder = s, this.activeUploadUrlRefresher = this.buildUploadUrlRefresher(e, t.session_id, n?.version);
1844
+ let c = {
1808
1845
  accessToken: r,
1809
1846
  upload: t.upload_url,
1810
- storageProvider: e,
1847
+ storageProvider: o,
1811
1848
  uploadHeaders: this.buildUploadHeaders(r),
1812
- sessionId: t.session_id
1849
+ sessionId: t.session_id,
1850
+ refreshUploadUrl: this.activeUploadUrlRefresher
1813
1851
  };
1814
1852
  try {
1815
- o.initialize(t, s);
1853
+ s.initialize(t, c);
1816
1854
  } catch (e) {
1817
1855
  try {
1818
- o.reset();
1856
+ s.reset();
1819
1857
  } catch {}
1820
1858
  throw i === this._startGeneration && (this.cleanupRecordingState(), this.dispatchStartError(m.VALIDATION_ERROR, g.RECORDER_INIT_FAILED, e)), e;
1821
1859
  }
1822
- o instanceof X && this.applyDiscoveryOverrides(o);
1860
+ s instanceof X && this.applyDiscoveryOverrides(s);
1823
1861
  try {
1824
- await o.start(n?.deviceId);
1862
+ await s.start(n?.deviceId);
1825
1863
  } catch (e) {
1826
1864
  try {
1827
- o.reset();
1865
+ s.reset();
1828
1866
  } catch {}
1829
1867
  throw i === this._startGeneration && (this.cleanupRecordingState(), this.dispatchStartError(m.VAD_ERROR, g.VAD_START_FAILED, e)), e;
1830
1868
  }
1831
1869
  if (i !== this._startGeneration) {
1832
1870
  try {
1833
- o.reset();
1871
+ s.reset();
1834
1872
  } catch {}
1835
1873
  throw new v("Recording start was superseded by a concurrent operation.");
1836
1874
  }
@@ -1985,7 +2023,7 @@ var Se = class {
1985
2023
  return (this.retryContext?.failedChunks.length ?? 0) > 0;
1986
2024
  }
1987
2025
  finalizeAfterExternalEndSession(e) {
1988
- this.activeSession && this.activeSession.session_id === e && (this.activeSession = null, this.activeBaseUrl = "", this.retryContext = null);
2026
+ this.activeSession && this.activeSession.session_id === e && (this.activeSession = null, this.activeBaseUrl = "", this.activeUploadUrlRefresher = null, this.retryContext = null);
1989
2027
  }
1990
2028
  async retryFailedUploads() {
1991
2029
  if (this._isRecording) throw new v("Cannot retry uploads while recording is active.");
@@ -1999,11 +2037,8 @@ var Se = class {
1999
2037
  };
2000
2038
  let { storageProvider: e, failedChunks: t } = this.retryContext, n = t.length, r = [], i = 0, a = this.retryContext.upload;
2001
2039
  try {
2002
- let e = this.activeSession?.session_id;
2003
- if (e) {
2004
- let t = await this.sessionManager.getSessionStatus(this.activeBaseUrl, e);
2005
- t.data.upload_url && (a = t.data.upload_url, this.retryContext.upload = a);
2006
- }
2040
+ let e = await this.activeUploadUrlRefresher?.();
2041
+ e && (a = e, this.retryContext.upload = e);
2007
2042
  } catch (e) {
2008
2043
  this.config.debug && console.log("[ScribeSDK] Failed to refresh upload_url, using existing:", e);
2009
2044
  }
@@ -2060,6 +2095,12 @@ var Se = class {
2060
2095
  let t = {};
2061
2096
  return e && (t.Authorization = `Bearer ${e}`), this.config.flavour && (t.flavour = this.config.flavour), t;
2062
2097
  }
2098
+ buildUploadUrlRefresher(e, t, n) {
2099
+ return () => this.fetchFreshUploadUrl(e, t, n);
2100
+ }
2101
+ async fetchFreshUploadUrl(e, t, n) {
2102
+ return (await this.sessionManager.getSessionStatus(e, t, void 0, n)).data.upload_url ?? null;
2103
+ }
2063
2104
  applyDiscoveryOverrides(e) {
2064
2105
  try {
2065
2106
  let t = this.discoveryManager.getResolvedConfig();
@@ -2096,7 +2137,7 @@ var Se = class {
2096
2137
  }, this.config.debug && console.log(`[ScribeSDK] Preserved ${e.length} failed uploads for retry`);
2097
2138
  }
2098
2139
  cleanupRecordingState() {
2099
- this.recorder = null, this.activeSession = null, this.activeBaseUrl = "", this._isRecording = !1, this._isStarting = !1;
2140
+ this.recorder = null, this.activeSession = null, this.activeBaseUrl = "", this.activeUploadUrlRefresher = null, this._isRecording = !1, this._isStarting = !1;
2100
2141
  }
2101
2142
  partialCleanupAfterFailedFinalize() {
2102
2143
  this.recorder = null, this._isRecording = !1, this._isStarting = !1;