med-scribe-alliance-ts-sdk 2.0.33 → 2.0.34

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
@@ -198,6 +198,8 @@ await client.reset(); // stops recording if active, clears all state and caches
198
198
  - **`cancelSession()` does NOT trigger processing.** It stops the recorder locally, cleans up state, and tells the server the session is cancelled. No `endSession` call is made to the backend.
199
199
  - **All async methods return `SDKResult<T>`, never throw.** Always check `result.success` before accessing `result.data`. Errors are in `result.error`.
200
200
  - **The SDK validates inputs against the discovery document.** If the server doesn't support an upload type, language, or model you requested, you'll get a `ValidationError` before the API call is made.
201
+ - **Audio uploads go to a server-selected storage backend.** Discovery's `capabilities.storage_provider` (default `aws`) decides the backend; uploads go directly to it (e.g. S3 presigned POST).
202
+
201
203
  - **SharedWorker is optional.** If you provide `workerScriptUrl`, the SDK offloads MP3 compression and upload to a SharedWorker. If the worker fails to load, it silently falls back to main-thread processing.
202
204
  - **Microphone permission is requested on `startRecording()`.** The browser will prompt the user for mic access. If denied, you'll get an error via `onError` callback.
203
205
  - **`reset()` is a full teardown.** It destroys the transport, clears discovery cache, removes all callbacks, and sets the client back to uninitialized state. You'll need to call `init()` (or `startRecording()`) again after reset.
@@ -273,6 +275,42 @@ if (client.hasFailedUploads()) {
273
275
  }
274
276
  ```
275
277
 
278
+ ### Upload a Pre-recorded Audio File
279
+
280
+ For processing an already-recorded file (no mic/VAD). Create a session, then upload the file using the session's `upload_url`, then end the session and poll.
281
+
282
+ ```ts
283
+ // 1. Create a session (gives you upload_url)
284
+ const session = await client.createSession({
285
+ templates: ['soap'],
286
+ upload_type: 'single',
287
+ communication_protocol: 'http',
288
+ });
289
+ if (!session.success) return;
290
+
291
+ // 2. Upload the file to storage using the session's upload_url
292
+ const upload = await client.uploadAudioFile(
293
+ myAudioFile,
294
+ 'audio_0.mp3', // storage object name
295
+ session.data.upload_url
296
+ );
297
+ if (!upload.success) {
298
+ console.error('Upload failed:', upload.error.message);
299
+ return;
300
+ }
301
+ console.log(upload.data.status, upload.data.headers); // full storage response
302
+
303
+ // 3. End the session to trigger processing, then poll getSessionStatus()
304
+ await client.endSession(
305
+ { audio_files_sent: 1, audio_files_uploaded: 1 },
306
+ session.data.session_id
307
+ );
308
+ ```
309
+
310
+ - `file` is a `File`/`Blob`; `fileName` is the storage object name; `upload` is the `upload_url` object from the create-session response.
311
+ - Returns `UploadAudioFileResult` — `{ fileName, status, headers, response }` (the full storage response; the body is usually empty for an S3 presigned POST).
312
+ - Failures (network, malformed `upload_url`) come back as `UploadError` in `result.error`; an unknown provider as `UnsupportedStorageProviderError`.
313
+
276
314
  ### Update Auth Token
277
315
 
278
316
  ```ts
@@ -351,6 +389,7 @@ interface RecordingOptions {
351
389
  | `isRecordingPaused()` | `boolean` | Whether the active recording is paused. |
352
390
  | `retryFailedUploads()` | `SDKResult<RetryUploadResult>` | Retry uploads that failed during the last recording. |
353
391
  | `hasFailedUploads()` | `boolean` | Whether there are retryable failed uploads. |
392
+ | `uploadAudioFile(file, fileName, upload)` | `SDKResult<UploadAudioFileResult>` | Upload one pre-recorded audio file to storage using a session's `upload_url`. No mic/recorder. |
354
393
 
355
394
  ### Session
356
395
 
@@ -457,10 +496,12 @@ interface CreateSessionResponse {
457
496
  status: SessionStatus;
458
497
  created_at: string;
459
498
  expires_at: string;
460
- upload_url: string;
499
+ upload_url: SessionUploadInfo;
461
500
  patient_details?: PatientDetails;
462
501
  }
463
502
 
503
+ type SessionUploadInfo = Record<string, unknown>;
504
+
464
505
  interface PatchSessionRequest {
465
506
  user_status?: string;
466
507
  processing_status?: string;
@@ -585,7 +626,8 @@ console.log(result.data.session_id);
585
626
  | `DiscoveryError` | — | Discovery fetch/parse failed |
586
627
  | `TransportError` | — | Network / IPC failure |
587
628
  | `WorkerError` | — | SharedWorker failure |
588
- | `UploadError` | — | Audio upload failure |
629
+ | `UploadError` | — | Audio upload failure (failed transfer, or malformed `upload_url` payload). Has `failedFiles: string[]`. |
630
+ | `UnsupportedStorageProviderError` | — | Discovery advertised a `storage_provider` this SDK build has no wrapper for. Thrown at `startRecording`. Has `provider: string`. |
589
631
 
590
632
  ## SharedWorker Support
591
633
 
@@ -648,6 +690,8 @@ const client = new ScribeClient({
648
690
 
649
691
  IPC mode always uses main-thread compression (SharedWorker can't access the IPC bridge).
650
692
 
693
+ > **Presigned uploads over IPC need host support.** The SDK forwards `uploadFormFields` + base64 file (`blobData`) in the `IpcRequest`; your host must build the `multipart/form-data` POST (no `Authorization` header). Browser/HTTP mode works out of the box.
694
+
651
695
  ## Building from Source
652
696
 
653
697
  ```bash
package/dist/index.d.ts CHANGED
@@ -552,6 +552,17 @@ export interface RetryUploadResult {
552
552
  /** File names that still failed after retry */
553
553
  stillFailed: string[];
554
554
  }
555
+ /** Result of ScribeClient.uploadAudioFile() — the storage backend's full response. */
556
+ export interface UploadAudioFileResult {
557
+ /** The storage object name used. */
558
+ fileName: string;
559
+ /** HTTP status from the storage backend (e.g. 204 for an S3 presigned POST). */
560
+ status: number;
561
+ /** Response headers from the storage backend (e.g. ETag). */
562
+ headers: Record<string, string>;
563
+ /** Raw response body from the storage backend (often empty for S3). */
564
+ response: unknown;
565
+ }
555
566
  export interface RecordingStateChangeEvent {
556
567
  type: RecordingState$1;
557
568
  timestamp: string;
@@ -785,6 +796,14 @@ export declare class ScribeClient {
785
796
  * Call this after receiving a 'chunk_limit_reached' error to resume chunk uploads.
786
797
  */
787
798
  forceAllowMoreChunks(): void;
799
+ /**
800
+ * Upload a single pre-recorded audio file to storage.
801
+ * @param file - The audio file/blob to upload.
802
+ * @param upload - The `upload_url` payload from the create-session response.
803
+ * @param options.fileName - Storage object name. Defaults to the File's name,
804
+ * else `audio_0.<ext>` derived from the MIME type.
805
+ */
806
+ uploadAudioFile(file: Blob, fileName: string, upload: SessionUploadInfo): Promise<SDKResult<UploadAudioFileResult>>;
788
807
  /**
789
808
  * Create a session directly (without starting a recording).
790
809
  */
@@ -898,6 +917,8 @@ export declare class ScribeClient {
898
917
  */
899
918
  private createTransport;
900
919
  private resolveWorkerConfig;
920
+ /** Storage provider name from discovery; defaults to 'aws'. */
921
+ private getStorageProviderName;
901
922
  /**
902
923
  * Get the effective base URL — prefer discovery's base_url, fall back to config.
903
924
  */
package/dist/index.mjs CHANGED
@@ -139,7 +139,7 @@ var r = "/.well-known/medscribealliance", i = 3600 * 1e3, a = /* @__PURE__ */ fu
139
139
  authorization_endpoint: e.string(),
140
140
  token_endpoint: e.string(),
141
141
  scopes_supported: e.array(e.string())
142
- }), k = e.object({
142
+ }), ae = e.object({
143
143
  protocol: e.string().min(1, "protocol is required"),
144
144
  protocol_version: e.string().min(1, "protocol_version is required"),
145
145
  supported_versions: e.array(e.string()).optional(),
@@ -171,7 +171,7 @@ var r = "/.well-known/medscribealliance", i = 3600 * 1e3, a = /* @__PURE__ */ fu
171
171
  supported: e.array(e.string()),
172
172
  auto_detection: e.boolean().optional()
173
173
  })
174
- }), A = e.object({
174
+ }), k = e.object({
175
175
  templates: e.array(e.string()).max(2, "templates cannot have more than 2 items"),
176
176
  upload_type: e.string().min(1, "upload_type is required"),
177
177
  communication_protocol: e.string().min(1, "communication_protocol is required"),
@@ -187,10 +187,10 @@ var r = "/.well-known/medscribealliance", i = 3600 * 1e3, a = /* @__PURE__ */ fu
187
187
  mobile: e.number().optional()
188
188
  }).optional(),
189
189
  session_id: e.string().optional()
190
- }), j = e.object({
190
+ }), A = e.object({
191
191
  audio_files_sent: e.number().int().min(0, "audio_files_sent must be a non-negative integer"),
192
192
  audio_files_uploaded: e.number().int().min(0, "audio_files_uploaded must be a non-negative integer")
193
- }), M = e.object({
193
+ }), j = e.object({
194
194
  user_status: e.string().optional(),
195
195
  processing_status: e.string().optional(),
196
196
  patient_details: e.object({
@@ -203,7 +203,7 @@ var r = "/.well-known/medscribealliance", i = 3600 * 1e3, a = /* @__PURE__ */ fu
203
203
  language_hint: e.array(e.string()).optional(),
204
204
  transcript_language: e.string().optional(),
205
205
  templates: e.array(e.string()).optional()
206
- }), ae = e.object({
206
+ }), M = e.object({
207
207
  session_id: e.string().min(1, "session_id is required"),
208
208
  status: e.string(),
209
209
  created_at: e.string(),
@@ -280,16 +280,16 @@ var r = "/.well-known/medscribealliance", i = 3600 * 1e3, a = /* @__PURE__ */ fu
280
280
  sessionId: e.string().optional()
281
281
  }), N = class {
282
282
  validateDiscoveryResponse(e) {
283
- this.parseWithValidationError(k, e, "Invalid discovery response");
283
+ this.parseWithValidationError(ae, e, "Invalid discovery response");
284
284
  }
285
285
  validateCreateSessionRequest(e) {
286
- this.parseWithValidationError(A, e, "Invalid CreateSessionRequest");
286
+ this.parseWithValidationError(k, e, "Invalid CreateSessionRequest");
287
287
  }
288
288
  validateEndSessionRequest(e) {
289
- this.parseWithValidationError(j, e, "Invalid EndSessionRequest");
289
+ this.parseWithValidationError(A, e, "Invalid EndSessionRequest");
290
290
  }
291
291
  validateCreateSessionResponse(e) {
292
- this.parseWithValidationError(ae, e, "Invalid CreateSessionResponse");
292
+ this.parseWithValidationError(M, e, "Invalid CreateSessionResponse");
293
293
  }
294
294
  validateEndSessionResponse(e) {
295
295
  this.parseWithValidationError(oe, e, "Invalid EndSessionResponse");
@@ -304,7 +304,7 @@ var r = "/.well-known/medscribealliance", i = 3600 * 1e3, a = /* @__PURE__ */ fu
304
304
  this.parseWithValidationError(de, e, "Invalid RecordingOptions");
305
305
  }
306
306
  validatePatchSessionRequest(e) {
307
- this.parseWithValidationError(M, e, "Invalid PatchSessionRequest");
307
+ this.parseWithValidationError(j, e, "Invalid PatchSessionRequest");
308
308
  }
309
309
  validatePatchSessionResponse(e) {
310
310
  this.parseWithValidationError(ce, e, "Invalid PatchSessionResponse");
@@ -358,21 +358,21 @@ async function P(e, t = {}) {
358
358
  for (let t = 0; t <= n; t++) try {
359
359
  return await e();
360
360
  } catch (e) {
361
- if (a = e instanceof Error ? e : Error(String(e)), F(e?.httpStatus ?? e?.statusCode ?? e?.status)) throw a;
361
+ if (a = e instanceof Error ? e : Error(String(e)), fe(e?.httpStatus ?? e?.statusCode ?? e?.status)) throw a;
362
362
  if (t >= n || i && i(t + 1, a) === !1) break;
363
- await I(r);
363
+ await F(r);
364
364
  }
365
365
  throw a ?? /* @__PURE__ */ Error("Retry failed: unknown error");
366
366
  }
367
- function F(e) {
367
+ function fe(e) {
368
368
  return typeof e == "number" ? e >= 400 && e < 500 && e !== 408 && e !== 429 : !1;
369
369
  }
370
- function I(e) {
370
+ function F(e) {
371
371
  return new Promise((t) => setTimeout(t, e));
372
372
  }
373
373
  //#endregion
374
374
  //#region src/transport/http-transport.ts
375
- var L = class {
375
+ var I = class {
376
376
  constructor(e) {
377
377
  this.tokenRefreshPromise = null, this.accessToken = e.accessToken, this.flavour = e.flavour, this.debug = e.debug ?? !1, this.onUnauthorized = e.onUnauthorized;
378
378
  }
@@ -494,7 +494,7 @@ var L = class {
494
494
  }
495
495
  };
496
496
  }
497
- }, R = class {
497
+ }, L = class {
498
498
  constructor(e) {
499
499
  this.pendingRequests = /* @__PURE__ */ new Map(), this.correlationCounter = 0, this.tokenRefreshPromise = null, this.bridge = e.bridge, this.accessToken = e.accessToken, this.flavour = e.flavour, this.debug = e.debug ?? !1, this.onUnauthorized = e.onUnauthorized, this.bridge.onResponse((e) => {
500
500
  this.handleResponse(e);
@@ -649,7 +649,7 @@ var L = class {
649
649
  };
650
650
  //#endregion
651
651
  //#region src/discovery/resolved-config.ts
652
- function z(e) {
652
+ function R(e) {
653
653
  try {
654
654
  let t = /* @__PURE__ */ new Map(), n = e.models ?? [];
655
655
  for (let e of n) e.id && typeof e.max_session_duration_seconds == "number" && t.set(e.id, e.max_session_duration_seconds);
@@ -674,7 +674,7 @@ function z(e) {
674
674
  }
675
675
  //#endregion
676
676
  //#region src/discovery/discovery-manager.ts
677
- var B = class {
677
+ var z = class {
678
678
  constructor(e, t, n = !1) {
679
679
  this.cachedDocument = null, this.resolvedConfig = null, this.cacheTimestamp = 0, this.cacheTtlMs = i, this.transport = e, this.validator = t, this.debug = n;
680
680
  }
@@ -691,7 +691,7 @@ var B = class {
691
691
  url: n
692
692
  });
693
693
  this.validator.validateDiscoveryResponse(i.data);
694
- let a = i.data, o = z(a);
694
+ let a = i.data, o = R(a);
695
695
  return this.cachedDocument = a, this.resolvedConfig = o, this.cacheTimestamp = Date.now(), this.debug && console.log("[ScribeSDK] Discovery complete:", a.service?.name ?? a.protocol), {
696
696
  data: o,
697
697
  httpStatus: i.status
@@ -744,7 +744,7 @@ var B = class {
744
744
  isCacheValid() {
745
745
  return Date.now() - this.cacheTimestamp < this.cacheTtlMs;
746
746
  }
747
- }, V = class {
747
+ }, B = class {
748
748
  constructor(e, t, n = !1) {
749
749
  this.currentSession = null, this.transport = e, this.validator = t, this.debug = n;
750
750
  }
@@ -910,13 +910,13 @@ var B = class {
910
910
  }, { once: !0 });
911
911
  }) : this.sleep(e);
912
912
  }
913
- }, H = 1024, U = 16e3;
914
- H / U, U / H;
915
- var fe = {
913
+ }, V = 1024, H = 16e3;
914
+ V / H, H / V;
915
+ var pe = {
916
916
  m4a: "audio/m4a",
917
917
  wav: "audio/wav",
918
918
  mp3: "audio/mpeg"
919
- }, pe = class {
919
+ }, me = class {
920
920
  constructor(e, t) {
921
921
  this.vadPast = [], this.lastClipIndex = 0, this.silDurationAcc = 0, this.micVad = null, this.micStream = null, this.isLoading = !0, this.isRecording = !1, this.noSpeechStartTime = null, this.lastWarningTime = null;
922
922
  let n = e.samplingRate;
@@ -1048,7 +1048,7 @@ var fe = {
1048
1048
  } catch {}
1049
1049
  this.micStream = null;
1050
1050
  }
1051
- }, me = class {
1051
+ }, he = class {
1052
1052
  constructor(e, t) {
1053
1053
  this.currentSampleLength = 0, this.currentFrameLength = 0, this.samplingRate = e, this.incrementalAllocationSize = Math.floor(e * t), this.buffer = new Float32Array(this.incrementalAllocationSize);
1054
1054
  }
@@ -1098,7 +1098,7 @@ var fe = {
1098
1098
  formatTimestamp(e) {
1099
1099
  return `${Math.floor(e / 60).toString().padStart(2, "0")}:${(e % 60).toFixed(6).padStart(9, "0")}`;
1100
1100
  }
1101
- }, he = class {
1101
+ }, ge = class {
1102
1102
  constructor() {
1103
1103
  this.chunks = [], this.successfulUploads = [], this.totalRawSamples = 0, this.totalRawFrames = 0, this.totalInsertedSamples = 0, this.totalInsertedFrames = 0;
1104
1104
  }
@@ -1192,7 +1192,7 @@ var fe = {
1192
1192
  };
1193
1193
  //#endregion
1194
1194
  //#region src/audio/mp3-encoder.ts
1195
- function ge(e, t = U, r = 128) {
1195
+ function _e(e, t = H, r = 128) {
1196
1196
  try {
1197
1197
  let i = new n.Mp3Encoder(1, t, r), a = new Int16Array(e.length);
1198
1198
  for (let t = 0; t < e.length; t++) {
@@ -1203,7 +1203,7 @@ function ge(e, t = U, r = 128) {
1203
1203
  s && s.length > 0 && o.push(s);
1204
1204
  let c = i.flush();
1205
1205
  return c && c.length > 0 && o.push(c), o.length === 0 ? null : {
1206
- blob: new Blob(o, { type: fe.mp3 }),
1206
+ blob: new Blob(o, { type: pe.mp3 }),
1207
1207
  chunks: o
1208
1208
  };
1209
1209
  } catch (e) {
@@ -1212,25 +1212,25 @@ function ge(e, t = U, r = 128) {
1212
1212
  }
1213
1213
  //#endregion
1214
1214
  //#region src/storage/aws-s3-provider.ts
1215
- var _e = e.object({
1215
+ var ve = e.object({
1216
1216
  uploadData: e.object({
1217
1217
  url: e.string().min(1, "uploadData.url is required"),
1218
1218
  fields: e.record(e.string(), e.string())
1219
1219
  }),
1220
1220
  folderPath: e.string().optional(),
1221
1221
  txn_id: e.string().optional()
1222
- }), W = "key", ve = "${filename}", G = "Content-Type", ye = "audio/mp3", K = class {
1222
+ }), ye = "key", U = "${filename}", W = "Content-Type", be = "audio/mp3", G = class {
1223
1223
  constructor() {
1224
1224
  this.name = "aws";
1225
1225
  }
1226
1226
  prepareUpload({ fileName: e, blob: t, upload: n }) {
1227
- let r = _e.safeParse(n);
1227
+ let r = ve.safeParse(n);
1228
1228
  if (!r.success) throw new E(`Invalid AWS upload payload in session response: ${r.error.message}`, [e]);
1229
1229
  let { uploadData: i } = r.data, a = {};
1230
- for (let [t, n] of Object.entries(i.fields)) a[t] = t === W ? n.split(ve).join(e) : n;
1231
- if (!(G in a)) {
1230
+ for (let [t, n] of Object.entries(i.fields)) a[t] = t === ye ? n.split(U).join(e) : n;
1231
+ if (!(W in a)) {
1232
1232
  let e = t?.type;
1233
- a[G] = e && e.startsWith("audio/") ? e : ye;
1233
+ a[W] = e && e.startsWith("audio/") ? e : be;
1234
1234
  }
1235
1235
  return {
1236
1236
  url: i.url,
@@ -1242,23 +1242,44 @@ var _e = e.object({
1242
1242
  attachAuth: !1
1243
1243
  };
1244
1244
  }
1245
- }, q = { aws: () => new K() };
1246
- function J(e) {
1245
+ }, K = { aws: () => new G() };
1246
+ function q(e) {
1247
1247
  return typeof e == "string" ? e.trim().toLowerCase() : "";
1248
1248
  }
1249
- function be(e) {
1250
- return J(e) in q;
1249
+ function xe(e) {
1250
+ return q(e) in K;
1251
1251
  }
1252
- function Y(e) {
1253
- let t = q[J(e)];
1252
+ function J(e) {
1253
+ let t = K[q(e)];
1254
1254
  if (!t) throw new D(e);
1255
1255
  return t();
1256
1256
  }
1257
1257
  //#endregion
1258
+ //#region src/storage/upload-file.ts
1259
+ async function Y(e, t) {
1260
+ let n = J(t.storageProvider).prepareUpload({
1261
+ fileName: t.fileName,
1262
+ blob: t.blob,
1263
+ upload: t.upload
1264
+ });
1265
+ return await e.request({
1266
+ method: n.method,
1267
+ url: n.url,
1268
+ headers: n.headers,
1269
+ isUpload: !0,
1270
+ uploadBlob: t.blob,
1271
+ uploadFormFields: n.formFields,
1272
+ uploadFileFieldName: n.fileFieldName,
1273
+ uploadFileName: t.fileName,
1274
+ attachAuth: n.attachAuth,
1275
+ maxRetries: t.maxRetries
1276
+ });
1277
+ }
1278
+ //#endregion
1258
1279
  //#region src/worker/worker-manager.ts
1259
- var xe = class {
1280
+ var Se = class {
1260
1281
  constructor(e, t, n, r) {
1261
- if (this.worker = null, this.port = null, this.useWorker = !1, this.uploadPayload = {}, this.storageProviderName = "", this.storageProvider = null, 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 {
1282
+ 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 {
1262
1283
  this.worker = new SharedWorker(r.workerScriptUrl, { name: "scribe-sdk-worker" }), this.port = this.worker.port, this.port.onmessage = (e) => {
1263
1284
  this.handleWorkerMessage(e.data);
1264
1285
  }, this.port.start(), this.useWorker = !0;
@@ -1267,10 +1288,10 @@ var xe = class {
1267
1288
  }
1268
1289
  }
1269
1290
  setUploadConfig(e, t, n) {
1270
- this.uploadPayload = e, this.storageProviderName = t, this.uploadHeaders = n, this.storageProvider = Y(t);
1291
+ this.uploadPayload = e, this.storageProviderName = t, this.uploadHeaders = n, J(t);
1271
1292
  }
1272
1293
  compressAndUpload(e, t, n) {
1273
- let r = e.length / U;
1294
+ let r = e.length / H;
1274
1295
  if (r > 27) {
1275
1296
  this.fileManager.markFailure(n, new Blob(), `Chunk exceeds maximum length: ${r.toFixed(1)}s > 25s`), this.callbackRegistry.dispatch("onError", {
1276
1297
  type: m.VALIDATION_ERROR,
@@ -1382,7 +1403,7 @@ var xe = class {
1382
1403
  async doMainThreadUpload(e, t, n) {
1383
1404
  let r = null;
1384
1405
  try {
1385
- let i = ge(e);
1406
+ let i = _e(e);
1386
1407
  if (!i) {
1387
1408
  this.fileManager.markFailure(n, new Blob(), "MP3 encoding failed"), this.callbackRegistry.dispatch("onUploadEvent", {
1388
1409
  type: f.FAILED,
@@ -1394,7 +1415,7 @@ var xe = class {
1394
1415
  });
1395
1416
  return;
1396
1417
  }
1397
- if (r = i.blob, this.callbackRegistry.dispatch("onAudioEvent", {
1418
+ r = i.blob, this.callbackRegistry.dispatch("onAudioEvent", {
1398
1419
  type: d.CHUNK_READY,
1399
1420
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1400
1421
  data: {
@@ -1402,22 +1423,11 @@ var xe = class {
1402
1423
  fileName: t,
1403
1424
  chunkData: i.chunks
1404
1425
  }
1405
- }), !this.storageProvider) throw Error("Storage provider not configured. Call setUploadConfig() first.");
1406
- let a = this.storageProvider.prepareUpload({
1426
+ }), await Y(this.transport, {
1407
1427
  fileName: t,
1408
1428
  blob: r,
1409
- upload: this.uploadPayload
1410
- });
1411
- await this.transport.request({
1412
- method: a.method,
1413
- url: a.url,
1414
- headers: a.headers,
1415
- isUpload: !0,
1416
- uploadBlob: r,
1417
- uploadFormFields: a.formFields,
1418
- uploadFileFieldName: a.fileFieldName,
1419
- uploadFileName: t,
1420
- attachAuth: a.attachAuth
1429
+ upload: this.uploadPayload,
1430
+ storageProvider: this.storageProviderName
1421
1431
  }), this.fileManager.markSuccess(n), this.dispatchUploadProgress();
1422
1432
  } catch (e) {
1423
1433
  this.fileManager.markFailure(n, r ?? new Blob(), e?.message ?? "Upload failed"), this.callbackRegistry.dispatch("onUploadEvent", {
@@ -1446,7 +1456,7 @@ var xe = class {
1446
1456
  }
1447
1457
  }, X = class {
1448
1458
  constructor(e, t, n, r) {
1449
- this._isPaused = !1, this.initialized = !1, this.chunkLimitReached = !1, this.chunkLimitOverridden = !1, this.callbackRegistry = e, this.bufferManager = new me(U, 25), this.fileManager = new he();
1459
+ this._isPaused = !1, this.initialized = !1, this.chunkLimitReached = !1, this.chunkLimitOverridden = !1, this.callbackRegistry = e, this.bufferManager = new he(H, 25), this.fileManager = new ge();
1450
1460
  let i = {
1451
1461
  prefChunkLength: n?.prefChunkLength ?? 10,
1452
1462
  despChunkLength: n?.despChunkLength ?? 20,
@@ -1457,7 +1467,7 @@ var xe = class {
1457
1467
  shortSilenceThreshold: n?.shortSilenceThreshold,
1458
1468
  longSilenceThreshold: n?.longSilenceThreshold
1459
1469
  };
1460
- this.vadClient = new pe(i, e), this.workerManager = new xe(e, this.fileManager, t, r), this.wireVadCallbacks();
1470
+ this.vadClient = new me(i, e), this.workerManager = new Se(e, this.fileManager, t, r), this.wireVadCallbacks();
1461
1471
  }
1462
1472
  initialize(e, t) {
1463
1473
  if (!t.upload || typeof t.upload != "object") throw Error("Upload payload is required for chunked recording");
@@ -1553,12 +1563,12 @@ var xe = class {
1553
1563
  }
1554
1564
  }, Z = class {
1555
1565
  constructor(e, t) {
1556
- this.mediaRecorder = null, this.audioChunks = [], this.micStream = null, this._isPaused = !1, this.uploadPayload = {}, this.storageProvider = null, this.failedUploadData = null, this.callbackRegistry = e, this.transport = t;
1566
+ this.mediaRecorder = null, this.audioChunks = [], this.micStream = null, this._isPaused = !1, this.uploadPayload = {}, this.storageProviderName = "", this.failedUploadData = null, this.callbackRegistry = e, this.transport = t;
1557
1567
  }
1558
1568
  initialize(e, t) {
1559
1569
  if (!t.upload || typeof t.upload != "object") throw Error("Upload payload is required for single recording");
1560
1570
  if (!t.storageProvider) throw Error("Storage provider is required for single recording");
1561
- this.uploadPayload = t.upload, this.storageProvider = Y(t.storageProvider), this.failedUploadData = null;
1571
+ this.uploadPayload = t.upload, this.storageProviderName = t.storageProvider, J(t.storageProvider), this.failedUploadData = null;
1562
1572
  }
1563
1573
  async start(e) {
1564
1574
  try {
@@ -1595,22 +1605,11 @@ var xe = class {
1595
1605
  try {
1596
1606
  let e = await this.stopMediaRecorder(), t = `audio_0.${this.getFileExtension()}`;
1597
1607
  try {
1598
- if (!this.storageProvider) throw Error("Storage provider not configured. Call initialize() first.");
1599
- let n = this.storageProvider.prepareUpload({
1608
+ return await Y(this.transport, {
1600
1609
  fileName: t,
1601
1610
  blob: e,
1602
- upload: this.uploadPayload
1603
- });
1604
- return await this.transport.request({
1605
- method: n.method,
1606
- url: n.url,
1607
- headers: n.headers,
1608
- isUpload: !0,
1609
- uploadBlob: e,
1610
- uploadFormFields: n.formFields,
1611
- uploadFileFieldName: n.fileFieldName,
1612
- uploadFileName: t,
1613
- attachAuth: n.attachAuth
1611
+ upload: this.uploadPayload,
1612
+ storageProvider: this.storageProviderName
1614
1613
  }), this.callbackRegistry.dispatch("onUploadEvent", {
1615
1614
  type: f.PROGRESS,
1616
1615
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -1987,24 +1986,14 @@ var xe = class {
1987
1986
  },
1988
1987
  httpStatus: void 0
1989
1988
  };
1990
- let { upload: e, storageProvider: t, failedChunks: n } = this.retryContext, r = n.length, i = [], a = 0, o = Y(t);
1989
+ let { upload: e, storageProvider: t, failedChunks: n } = this.retryContext, r = n.length, i = [], a = 0;
1991
1990
  this.config.debug && console.log(`[ScribeSDK] Retrying ${r} failed uploads`);
1992
- for (let t of n) try {
1993
- let n = o.prepareUpload({
1994
- fileName: t.fileName,
1995
- blob: t.blob,
1996
- upload: e
1997
- });
1998
- await this.transport.request({
1999
- method: n.method,
2000
- url: n.url,
2001
- headers: n.headers,
2002
- isUpload: !0,
2003
- uploadBlob: t.blob,
2004
- uploadFormFields: n.formFields,
2005
- uploadFileFieldName: n.fileFieldName,
2006
- uploadFileName: t.fileName,
2007
- attachAuth: n.attachAuth,
1991
+ for (let o of n) try {
1992
+ await Y(this.transport, {
1993
+ fileName: o.fileName,
1994
+ blob: o.blob,
1995
+ upload: e,
1996
+ storageProvider: t,
2008
1997
  maxRetries: 0
2009
1998
  }), a++, this.callbackRegistry.dispatch("onUploadEvent", {
2010
1999
  type: f.PROGRESS,
@@ -2013,16 +2002,16 @@ var xe = class {
2013
2002
  successCount: a,
2014
2003
  totalCount: r
2015
2004
  }
2016
- }), this.config.debug && console.log(`[ScribeSDK] Retry succeeded: ${t.fileName}`);
2005
+ }), this.config.debug && console.log(`[ScribeSDK] Retry succeeded: ${o.fileName}`);
2017
2006
  } catch (e) {
2018
- i.push(t.fileName), this.callbackRegistry.dispatch("onUploadEvent", {
2007
+ i.push(o.fileName), this.callbackRegistry.dispatch("onUploadEvent", {
2019
2008
  type: f.FAILED,
2020
2009
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2021
2010
  data: {
2022
- fileName: t.fileName,
2011
+ fileName: o.fileName,
2023
2012
  error: e instanceof Error ? e.message : "Retry failed"
2024
2013
  }
2025
- }), this.config.debug && console.log(`[ScribeSDK] Retry failed: ${t.fileName}`, e);
2014
+ }), this.config.debug && console.log(`[ScribeSDK] Retry failed: ${o.fileName}`, e);
2026
2015
  }
2027
2016
  return i.length === 0 ? this.retryContext = null : this.retryContext.failedChunks = n.filter((e) => i.includes(e.fileName)), this.config.debug && console.log(`[ScribeSDK] Retry complete: ${a}/${r} succeeded`), {
2028
2017
  data: {
@@ -2045,7 +2034,7 @@ var xe = class {
2045
2034
  }
2046
2035
  resolveStorageProviderName() {
2047
2036
  let e = this.getStorageProviderName();
2048
- return Y(e), e;
2037
+ return J(e), e;
2049
2038
  }
2050
2039
  buildUploadHeaders(e) {
2051
2040
  let t = {};
@@ -2092,14 +2081,14 @@ var xe = class {
2092
2081
  partialCleanupAfterFailedFinalize() {
2093
2082
  this.recorder = null, this._isRecording = !1, this._isStarting = !1;
2094
2083
  }
2095
- }, Se = class {
2084
+ }, Ce = class {
2096
2085
  constructor(e) {
2097
2086
  this.isInitialized = !1, this.validateConfig(e), this.config = {
2098
2087
  debug: !1,
2099
2088
  autoDiscovery: !0,
2100
2089
  mode: l.DIRECT,
2101
2090
  ...e
2102
- }, this.callbackRegistry = new O(), this.validator = new N(), this.transport = this.createTransport(), this.discoveryManager = new B(this.transport, this.validator, this.config.debug), this.sessionManager = new V(this.transport, this.validator, this.config.debug), this.recordingManager = new Q(this.callbackRegistry, this.sessionManager, this.discoveryManager, this.transport, {
2091
+ }, this.callbackRegistry = new O(), this.validator = new N(), this.transport = this.createTransport(), this.discoveryManager = new z(this.transport, this.validator, this.config.debug), this.sessionManager = new B(this.transport, this.validator, this.config.debug), this.recordingManager = new Q(this.callbackRegistry, this.sessionManager, this.discoveryManager, this.transport, {
2103
2092
  debug: this.config.debug,
2104
2093
  flavour: this.config.flavour,
2105
2094
  workerConfig: this.resolveWorkerConfig()
@@ -2165,6 +2154,28 @@ var xe = class {
2165
2154
  forceAllowMoreChunks() {
2166
2155
  this.recordingManager.forceAllowMoreChunks();
2167
2156
  }
2157
+ async uploadAudioFile(e, t, n) {
2158
+ return this.wrapResult(async () => {
2159
+ if (!e || e.size === 0) throw new y("A non-empty audio file is required");
2160
+ if (!t || !t.trim()) throw new y("fileName is required");
2161
+ if (!n || typeof n != "object") throw new y("upload (upload_url payload) is required");
2162
+ let r = await Y(this.transport, {
2163
+ fileName: t,
2164
+ blob: e,
2165
+ upload: n,
2166
+ storageProvider: this.getStorageProviderName()
2167
+ });
2168
+ return {
2169
+ data: {
2170
+ fileName: t,
2171
+ status: r.status,
2172
+ headers: r.headers,
2173
+ response: r.data
2174
+ },
2175
+ httpStatus: r.status
2176
+ };
2177
+ });
2178
+ }
2168
2179
  async createSession(e) {
2169
2180
  let t = this.getEffectiveBaseUrl();
2170
2181
  return this.wrapResult(() => this.sessionManager.createSession(t, e));
@@ -2275,7 +2286,7 @@ var xe = class {
2275
2286
  }) : Promise.resolve(void 0);
2276
2287
  if (this.config.mode === l.IPC) {
2277
2288
  if (!this.config.ipcTransport) throw new y("ipcTransport (IpcBridge) is required when mode is \"ipc\"");
2278
- return new R({
2289
+ return new L({
2279
2290
  bridge: this.config.ipcTransport,
2280
2291
  accessToken: this.config.accessToken,
2281
2292
  flavour: this.config.flavour,
@@ -2283,7 +2294,7 @@ var xe = class {
2283
2294
  onUnauthorized: e
2284
2295
  });
2285
2296
  }
2286
- return new L({
2297
+ return new I({
2287
2298
  accessToken: this.config.accessToken,
2288
2299
  flavour: this.config.flavour,
2289
2300
  debug: this.config.debug,
@@ -2297,6 +2308,13 @@ var xe = class {
2297
2308
  workerScriptUrl: this.config.workerScriptUrl
2298
2309
  };
2299
2310
  }
2311
+ getStorageProviderName() {
2312
+ try {
2313
+ return this.discoveryManager.getResolvedConfig().storageProvider || "aws";
2314
+ } catch {
2315
+ return "aws";
2316
+ }
2317
+ }
2300
2318
  getEffectiveBaseUrl() {
2301
2319
  try {
2302
2320
  return this.discoveryManager.getResolvedConfig().baseUrl;
@@ -2308,7 +2326,7 @@ var xe = class {
2308
2326
  if (!e.baseUrl) throw new y("baseUrl is required");
2309
2327
  }
2310
2328
  }, $ = "worker.bundle.js";
2311
- function Ce() {
2329
+ function we() {
2312
2330
  if (typeof window < "u" && window.__MEDSCRIBE_WORKER_URL__) return window.__MEDSCRIBE_WORKER_URL__;
2313
2331
  if (typeof document < "u" && document.currentScript) {
2314
2332
  let e = document.currentScript.src;
@@ -2316,11 +2334,11 @@ function Ce() {
2316
2334
  }
2317
2335
  return `/${$}`;
2318
2336
  }
2319
- async function we(e) {
2337
+ async function Te(e) {
2320
2338
  let t = e ?? `https://cdn.jsdelivr.net/npm/med-scribe-alliance-ts-sdk/dist/${$}`, n = await fetch(t);
2321
2339
  if (!n.ok) throw Error(`Failed to fetch worker script: ${n.status} ${n.statusText}`);
2322
2340
  let r = await n.text(), i = new Blob([r], { type: "application/javascript" });
2323
2341
  return URL.createObjectURL(i);
2324
2342
  }
2325
2343
  //#endregion
2326
- export { d as AudioEventType, x as AuthenticationError, K as AwsS3StorageProvider, O as CallbackRegistry, c as CommunicationProtocol, h as DiscardReason, b as DiscoveryError, B as DiscoveryManager, g as ErrorCode, m as ErrorEventType, S as ForbiddenError, _ as HttpStatus, L as HttpTransport, R as IpcTransport, w as RateLimitError, Q as RecordingManager, u as RecordingState, Se as ScribeClient, v as ScribeError, p as SessionEventType, te as SessionExpiredError, V as SessionManager, C as SessionNotFoundError, a as SessionStatus, o as TemplateStatus, T as TransportError, l as TransportMode, D as UnsupportedStorageProviderError, E as UploadError, f as UploadEventType, s as UploadType, y as ValidationError, N as Validator, ne as WorkerError, we as createWorkerBlobUrl, Y as getStorageProvider, Ce as getWorkerUrl, be as isStorageProviderSupported };
2344
+ export { d as AudioEventType, x as AuthenticationError, G as AwsS3StorageProvider, O as CallbackRegistry, c as CommunicationProtocol, h as DiscardReason, b as DiscoveryError, z as DiscoveryManager, g as ErrorCode, m as ErrorEventType, S as ForbiddenError, _ as HttpStatus, I as HttpTransport, L as IpcTransport, w as RateLimitError, Q as RecordingManager, u as RecordingState, Ce as ScribeClient, v as ScribeError, p as SessionEventType, te as SessionExpiredError, B as SessionManager, C as SessionNotFoundError, a as SessionStatus, o as TemplateStatus, T as TransportError, l as TransportMode, D as UnsupportedStorageProviderError, E as UploadError, f as UploadEventType, s as UploadType, y as ValidationError, N as Validator, ne as WorkerError, Te as createWorkerBlobUrl, J as getStorageProvider, we as getWorkerUrl, xe as isStorageProviderSupported };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "med-scribe-alliance-ts-sdk",
3
- "version": "2.0.33",
3
+ "version": "2.0.34",
4
4
  "description": "TypeScript SDK for the MedScribe Alliance Protocol",
5
5
  "main": "dist/index.mjs",
6
6
  "module": "dist/index.mjs",