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 +46 -2
- package/dist/index.d.ts +21 -0
- package/dist/index.mjs +125 -107
- package/package.json +1 -1
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:
|
|
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
|
-
}),
|
|
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
|
-
}),
|
|
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
|
-
}),
|
|
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
|
-
}),
|
|
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
|
-
}),
|
|
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(
|
|
283
|
+
this.parseWithValidationError(ae, e, "Invalid discovery response");
|
|
284
284
|
}
|
|
285
285
|
validateCreateSessionRequest(e) {
|
|
286
|
-
this.parseWithValidationError(
|
|
286
|
+
this.parseWithValidationError(k, e, "Invalid CreateSessionRequest");
|
|
287
287
|
}
|
|
288
288
|
validateEndSessionRequest(e) {
|
|
289
|
-
this.parseWithValidationError(
|
|
289
|
+
this.parseWithValidationError(A, e, "Invalid EndSessionRequest");
|
|
290
290
|
}
|
|
291
291
|
validateCreateSessionResponse(e) {
|
|
292
|
-
this.parseWithValidationError(
|
|
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(
|
|
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)),
|
|
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
|
|
363
|
+
await F(r);
|
|
364
364
|
}
|
|
365
365
|
throw a ?? /* @__PURE__ */ Error("Retry failed: unknown error");
|
|
366
366
|
}
|
|
367
|
-
function
|
|
367
|
+
function fe(e) {
|
|
368
368
|
return typeof e == "number" ? e >= 400 && e < 500 && e !== 408 && e !== 429 : !1;
|
|
369
369
|
}
|
|
370
|
-
function
|
|
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
|
|
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
|
-
},
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
},
|
|
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
|
-
},
|
|
914
|
-
|
|
915
|
-
var
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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
|
|
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:
|
|
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
|
|
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
|
-
}),
|
|
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 =
|
|
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 ===
|
|
1231
|
-
if (!(
|
|
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[
|
|
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
|
-
},
|
|
1246
|
-
function
|
|
1245
|
+
}, K = { aws: () => new G() };
|
|
1246
|
+
function q(e) {
|
|
1247
1247
|
return typeof e == "string" ? e.trim().toLowerCase() : "";
|
|
1248
1248
|
}
|
|
1249
|
-
function
|
|
1250
|
-
return
|
|
1249
|
+
function xe(e) {
|
|
1250
|
+
return q(e) in K;
|
|
1251
1251
|
}
|
|
1252
|
-
function
|
|
1253
|
-
let t = q
|
|
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
|
|
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.
|
|
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,
|
|
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 /
|
|
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 =
|
|
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
|
-
|
|
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
|
-
}),
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
1993
|
-
|
|
1994
|
-
fileName:
|
|
1995
|
-
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: ${
|
|
2005
|
+
}), this.config.debug && console.log(`[ScribeSDK] Retry succeeded: ${o.fileName}`);
|
|
2017
2006
|
} catch (e) {
|
|
2018
|
-
i.push(
|
|
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:
|
|
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: ${
|
|
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
|
|
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
|
-
},
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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 };
|