assemblyai 4.4.1 → 4.4.2-beta.0

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/node.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { WritableStream } from 'stream/web';
2
- import WebSocket from '#ws';
2
+ import ws from 'ws';
3
3
  import { createReadStream } from 'fs';
4
4
  import { Readable } from 'stream';
5
5
 
@@ -22,6 +22,7 @@ class BaseService {
22
22
  "Content-Type": "application/json",
23
23
  ...init.headers,
24
24
  };
25
+ init.cache = "no-store";
25
26
  if (!input.startsWith("http"))
26
27
  input = this.params.baseUrl + input;
27
28
  const response = await fetch(input, init);
@@ -85,6 +86,8 @@ class LemurService extends BaseService {
85
86
  }
86
87
  }
87
88
 
89
+ const factory = (url, params) => new ws(url, params);
90
+
88
91
  var RealtimeErrorType;
89
92
  (function (RealtimeErrorType) {
90
93
  RealtimeErrorType[RealtimeErrorType["BadSampleRate"] = 4000] = "BadSampleRate";
@@ -180,10 +183,10 @@ class RealtimeTranscriber {
180
183
  }
181
184
  const url = this.connectionUrl();
182
185
  if (this.token) {
183
- this.socket = new WebSocket(url.toString());
186
+ this.socket = factory(url.toString());
184
187
  }
185
188
  else {
186
- this.socket = new WebSocket(url.toString(), {
189
+ this.socket = factory(url.toString(), {
187
190
  headers: { Authorization: this.apiKey },
188
191
  });
189
192
  }
@@ -276,14 +279,14 @@ class RealtimeTranscriber {
276
279
  this.send(`{"end_utterance_silence_threshold":${threshold}}`);
277
280
  }
278
281
  send(data) {
279
- if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
282
+ if (!this.socket || this.socket.readyState !== this.socket.OPEN) {
280
283
  throw new Error("Socket is not open for communication");
281
284
  }
282
285
  this.socket.send(data);
283
286
  }
284
287
  async close(waitForSessionTermination = true) {
285
288
  if (this.socket) {
286
- if (this.socket.readyState === WebSocket.OPEN) {
289
+ if (this.socket.readyState === this.socket.OPEN) {
287
290
  if (waitForSessionTermination) {
288
291
  const sessionTerminatedPromise = new Promise((resolve) => {
289
292
  this.sessionTerminatedResolve = resolve;
@@ -295,7 +298,7 @@ class RealtimeTranscriber {
295
298
  this.socket.send(terminateSessionMessage);
296
299
  }
297
300
  }
298
- if ("removeAllListeners" in this.socket)
301
+ if (this.socket?.removeAllListeners)
299
302
  this.socket.removeAllListeners();
300
303
  this.socket.close();
301
304
  }
@@ -346,6 +349,8 @@ function getPath(path) {
346
349
  return null;
347
350
  if (path.startsWith("https"))
348
351
  return null;
352
+ if (path.startsWith("data:"))
353
+ return null;
349
354
  if (path.startsWith("file://"))
350
355
  return path.substring(7);
351
356
  if (path.startsWith("file:"))
@@ -387,8 +392,13 @@ class TranscriptService extends BaseService {
387
392
  audioUrl = await this.files.upload(path);
388
393
  }
389
394
  else {
390
- // audio is not a local path, assume it's a URL
391
- audioUrl = audio;
395
+ if (audio.startsWith("data:")) {
396
+ audioUrl = await this.files.upload(audio);
397
+ }
398
+ else {
399
+ // audio is not a local path, and not a data-URI, assume it's a normal URL
400
+ audioUrl = audio;
401
+ }
392
402
  }
393
403
  }
394
404
  else {
@@ -565,8 +575,14 @@ class FileService extends BaseService {
565
575
  */
566
576
  async upload(input) {
567
577
  let fileData;
568
- if (typeof input === "string")
569
- fileData = await readFile(input);
578
+ if (typeof input === "string") {
579
+ if (input.startsWith("data:")) {
580
+ fileData = dataUrlToBlob(input);
581
+ }
582
+ else {
583
+ fileData = await readFile(input);
584
+ }
585
+ }
570
586
  else
571
587
  fileData = input;
572
588
  const data = await this.fetchJson("/v2/upload", {
@@ -580,6 +596,17 @@ class FileService extends BaseService {
580
596
  return data.upload_url;
581
597
  }
582
598
  }
599
+ function dataUrlToBlob(dataUrl) {
600
+ const arr = dataUrl.split(",");
601
+ const mime = arr[0].match(/:(.*?);/)[1];
602
+ const bstr = atob(arr[1]);
603
+ let n = bstr.length;
604
+ const u8arr = new Uint8Array(n);
605
+ while (n--) {
606
+ u8arr[n] = bstr.charCodeAt(n);
607
+ }
608
+ return new Blob([u8arr], { type: mime });
609
+ }
583
610
 
584
611
  const defaultBaseUrl = "https://api.assemblyai.com";
585
612
  class AssemblyAI {
@@ -0,0 +1,3 @@
1
+ import { PolyfillWebSocketFactory } from ".";
2
+ export { PolyfillWebSocket } from ".";
3
+ export declare const factory: PolyfillWebSocketFactory;
@@ -0,0 +1,3 @@
1
+ import { PolyfillWebSocketFactory } from ".";
2
+ export { PolyfillWebSocket } from ".";
3
+ export declare const factory: PolyfillWebSocketFactory;
@@ -0,0 +1,27 @@
1
+ /// <reference types="node" />
2
+ import ws, { Event, ErrorEvent, CloseEvent, MessageEvent } from "ws";
3
+ export type PolyfillWebSocket = {
4
+ OPEN: typeof ws.OPEN;
5
+ binaryType: string;
6
+ onopen: ((event: Event) => void) | null;
7
+ onerror: ((event: ErrorEvent) => void) | null;
8
+ onclose: ((event: CloseEvent) => void) | null;
9
+ onmessage: ((event: MessageEvent) => void) | null;
10
+ readonly readyState: typeof ws.CONNECTING | typeof ws.OPEN | typeof ws.CLOSING | typeof ws.CLOSED;
11
+ removeAllListeners?: () => void;
12
+ send(data: string | number | Buffer | DataView | ArrayBufferView | Uint8Array | ArrayBuffer | SharedArrayBuffer | readonly unknown[] | readonly number[] | {
13
+ valueOf(): ArrayBuffer;
14
+ } | {
15
+ valueOf(): SharedArrayBuffer;
16
+ } | {
17
+ valueOf(): Uint8Array;
18
+ } | {
19
+ valueOf(): readonly number[];
20
+ } | {
21
+ valueOf(): string;
22
+ } | {
23
+ [Symbol.toPrimitive](hint: string): string;
24
+ }): unknown;
25
+ close(): unknown;
26
+ };
27
+ export type PolyfillWebSocketFactory = (url: string, params?: unknown) => PolyfillWebSocket;
@@ -721,7 +721,7 @@ export type LemurQuestionAnswerResponse = LemurBaseResponse & {
721
721
  * ```js
722
722
  * {
723
723
  * "transcript_ids": [
724
- * "64nygnr62k-405c-4ae8-8a6b-d90b40ff3cce"
724
+ * "47b95ba5-8889-44d8-bc80-5de38306e582"
725
725
  * ],
726
726
  * "context": "This is an interview about wildfires.",
727
727
  * "final_model": "default",
@@ -828,7 +828,6 @@ export type ListTranscriptParams = {
828
828
  throttled_only?: boolean;
829
829
  };
830
830
  /**
831
- * Details of the transcript page.
832
831
  * Details of the transcript page. Transcripts are sorted from newest to oldest. The previous URL always points to a page with older transcripts.
833
832
  * @example
834
833
  * ```js
@@ -851,12 +850,10 @@ export type PageDetails = {
851
850
  */
852
851
  limit: number;
853
852
  /**
854
- * The URL to the next page of transcripts
855
853
  * The URL to the next page of transcripts. The next URL always points to a page with newer transcripts.
856
854
  */
857
855
  next_url: string | null;
858
856
  /**
859
- * The URL to the previous page of transcripts
860
857
  * The URL to the next page of transcripts. The previous URL always points to a page with older transcripts.
861
858
  */
862
859
  prev_url: string | null;
@@ -1124,7 +1121,7 @@ export type SentencesResponse = {
1124
1121
  };
1125
1122
  export type Sentiment = "POSITIVE" | "NEUTRAL" | "NEGATIVE";
1126
1123
  /**
1127
- * The result of the sentiment analysis model
1124
+ * The result of the Sentiment Analysis model
1128
1125
  * @example
1129
1126
  * ```js
1130
1127
  * {
@@ -2114,7 +2111,7 @@ export type Transcript = {
2114
2111
  auto_highlights: boolean;
2115
2112
  /**
2116
2113
  * An array of results for the Key Phrases model, if it is enabled.
2117
- * See {@link https://www.assemblyai.com/docs/models/key-phrases | Key phrases } for more information.
2114
+ * See {@link https://www.assemblyai.com/docs/models/key-phrases | Key Phrases } for more information.
2118
2115
  */
2119
2116
  auto_highlights_result?: AutoHighlightsResult | null;
2120
2117
  /**
@@ -2236,7 +2233,7 @@ export type Transcript = {
2236
2233
  sentiment_analysis?: boolean | null;
2237
2234
  /**
2238
2235
  * An array of results for the Sentiment Analysis model, if it is enabled.
2239
- * See {@link https://www.assemblyai.com/docs/models/sentiment-analysis | Sentiment analysis } for more information.
2236
+ * See {@link https://www.assemblyai.com/docs/models/sentiment-analysis | Sentiment Analysis } for more information.
2240
2237
  */
2241
2238
  sentiment_analysis_results?: SentimentAnalysisResult[] | null;
2242
2239
  /**
@@ -2313,7 +2310,7 @@ export type Transcript = {
2313
2310
  */
2314
2311
  webhook_status_code?: number | null;
2315
2312
  /**
2316
- * The URL to which we send webhooks upon trancription completion
2313
+ * The URL to which we send webhooks upon transcription completion
2317
2314
  */
2318
2315
  webhook_url?: string | null;
2319
2316
  /**
@@ -2358,9 +2355,8 @@ export type TranscriptCustomSpelling = {
2358
2355
  *
2359
2356
  * @defaultValue "en_us
2360
2357
  */
2361
- export type TranscriptLanguageCode = "en" | "en_au" | "en_uk" | "en_us" | "es" | "fr" | "de" | "it" | "pt" | "nl" | "hi" | "ja" | "zh" | "fi" | "ko" | "pl" | "ru" | "tr" | "uk" | "vi";
2358
+ export type TranscriptLanguageCode = "en" | "en_au" | "en_uk" | "en_us" | "es" | "fr" | "de" | "it" | "pt" | "nl" | "af" | "sq" | "am" | "ar" | "hy" | "as" | "az" | "ba" | "eu" | "be" | "bn" | "bs" | "br" | "bg" | "my" | "ca" | "zh" | "hr" | "cs" | "da" | "et" | "fo" | "fi" | "gl" | "ka" | "el" | "gu" | "ht" | "ha" | "haw" | "he" | "hi" | "hu" | "is" | "id" | "ja" | "jw" | "kn" | "kk" | "km" | "ko" | "lo" | "la" | "lv" | "ln" | "lt" | "lb" | "mk" | "mg" | "ms" | "ml" | "mt" | "mi" | "mr" | "mn" | "ne" | "no" | "nn" | "oc" | "pa" | "ps" | "fa" | "pl" | "ro" | "ru" | "sa" | "sr" | "sn" | "sd" | "si" | "sk" | "sl" | "so" | "su" | "sw" | "sv" | "tl" | "tg" | "ta" | "tt" | "te" | "th" | "bo" | "tr" | "tk" | "uk" | "ur" | "uz" | "vi" | "cy" | "yi" | "yo";
2362
2359
  /**
2363
- * A list of transcripts
2364
2360
  * A list of transcripts. Transcripts are sorted from newest to oldest. The previous URL always points to a page with older transcripts.
2365
2361
  * @example
2366
2362
  * ```js
@@ -2498,7 +2494,7 @@ export type TranscriptOptionalParams = {
2498
2494
  */
2499
2495
  auto_chapters?: boolean;
2500
2496
  /**
2501
- * Whether Key Phrases is enabled, either true or false
2497
+ * Enable Key Phrases, either true or false
2502
2498
  */
2503
2499
  auto_highlights?: boolean;
2504
2500
  /**
@@ -2510,7 +2506,7 @@ export type TranscriptOptionalParams = {
2510
2506
  */
2511
2507
  content_safety?: boolean;
2512
2508
  /**
2513
- * The confidence threshold for content moderation. Values must be between 25 and 100.
2509
+ * The confidence threshold for the Content Moderation model. Values must be between 25 and 100.
2514
2510
  */
2515
2511
  content_safety_confidence?: number;
2516
2512
  /**
@@ -2518,7 +2514,7 @@ export type TranscriptOptionalParams = {
2518
2514
  */
2519
2515
  custom_spelling?: TranscriptCustomSpelling[];
2520
2516
  /**
2521
- * Whether custom topics is enabled, either true or false
2517
+ * Enable custom topics, either true or false
2522
2518
  */
2523
2519
  custom_topics?: boolean;
2524
2520
  /**
@@ -2551,7 +2547,7 @@ export type TranscriptOptionalParams = {
2551
2547
  */
2552
2548
  language_code?: LiteralUnion<TranscriptLanguageCode, string> | null;
2553
2549
  /**
2554
- * Whether {@link https://www.assemblyai.com/docs/models/speech-recognition#automatic-language-detection | Automatic language detection } was enabled in the transcription request, either true or false.
2550
+ * Enable {@link https://www.assemblyai.com/docs/models/speech-recognition#automatic-language-detection | Automatic language detection }, either true or false.
2555
2551
  */
2556
2552
  language_detection?: boolean;
2557
2553
  /**
@@ -2619,7 +2615,7 @@ export type TranscriptOptionalParams = {
2619
2615
  */
2620
2616
  summary_type?: SummaryType;
2621
2617
  /**
2622
- * The list of custom topics provided, if custom topics is enabled
2618
+ * The list of custom topics
2623
2619
  */
2624
2620
  topics?: string[];
2625
2621
  /**
@@ -2633,7 +2629,7 @@ export type TranscriptOptionalParams = {
2633
2629
  */
2634
2630
  webhook_auth_header_value?: string | null;
2635
2631
  /**
2636
- * The URL to which AssemblyAI send webhooks upon trancription completion
2632
+ * The URL to which AssemblyAI send webhooks upon transcription completion
2637
2633
  */
2638
2634
  webhook_url?: string;
2639
2635
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "assemblyai",
3
- "version": "4.4.1",
3
+ "version": "4.4.2-beta.0",
4
4
  "description": "The AssemblyAI JavaScript SDK provides an easy-to-use interface for interacting with the AssemblyAI API, which supports async and real-time transcription, as well as the latest LeMUR models.",
5
5
  "engines": {
6
6
  "node": ">=18"
@@ -17,7 +17,8 @@
17
17
  "default": "./dist/deno.mjs"
18
18
  },
19
19
  "workerd": "./dist/index.mjs",
20
- "browser": "./dist/index.mjs",
20
+ "browser": "./dist/browser.mjs",
21
+ "react-native": "./dist/browser.mjs",
21
22
  "node": {
22
23
  "types": "./dist/index.d.ts",
23
24
  "import": "./dist/node.mjs",
@@ -40,14 +41,10 @@
40
41
  "node": "./src/polyfills/streams/node.ts",
41
42
  "default": "./src/polyfills/streams/index.ts"
42
43
  },
43
- "#ws": {
44
- "types": "./src/polyfills/ws/index.d.ts",
45
- "browser": "./src/polyfills/ws/browser.mjs",
46
- "default": {
47
- "types": "./src/polyfills/ws/index.d.ts",
48
- "import": "./src/polyfills/ws/index.mjs",
49
- "require": "./src/polyfills/ws/index.cjs"
50
- }
44
+ "#websocket": {
45
+ "browser": "./src/polyfills/websocket/browser.ts",
46
+ "node": "./src/polyfills/websocket/default.ts",
47
+ "default": "./src/polyfills/websocket/default.ts"
51
48
  }
52
49
  },
53
50
  "type": "commonjs",
@@ -61,7 +58,7 @@
61
58
  "url": "git+https://github.com/AssemblyAI/assemblyai-node-sdk.git"
62
59
  },
63
60
  "publishConfig": {
64
- "tag": "latest",
61
+ "tag": "beta",
65
62
  "access": "public",
66
63
  "registry": "https://registry.npmjs.org/"
67
64
  },
@@ -70,7 +67,7 @@
70
67
  "clean": "rimraf dist/* && rimraf temp/* && rimraf temp-docs/*",
71
68
  "lint": "eslint -c .eslintrc.json '{src,tests}/**/*.{js,ts}' && publint && tsc --noEmit -p tsconfig.json",
72
69
  "test": "pnpm run test:unit && pnpm run test:integration",
73
- "test:unit": "jest --config jest.unit.config.js",
70
+ "test:unit": "jest --config jest.unit.config.js --testTimeout 1000",
74
71
  "test:integration": "jest --config jest.integration.config.js --testTimeout 360000",
75
72
  "format": "prettier '**/*' --write",
76
73
  "generate:types": "tsx ./scripts/generate-types.ts && prettier 'src/types/*.generated.ts' --write",
@@ -0,0 +1,18 @@
1
+ import { PolyfillWebSocketFactory, PolyfillWebSocket } from ".";
2
+ export { PolyfillWebSocket } from ".";
3
+
4
+ const PolyfillWebSocket =
5
+ WebSocket ?? global?.WebSocket ?? window?.WebSocket ?? self?.WebSocket;
6
+
7
+ export const factory: PolyfillWebSocketFactory = (
8
+ url: string,
9
+ params?: unknown,
10
+ ) => {
11
+ if (params) {
12
+ return new PolyfillWebSocket(
13
+ url,
14
+ params as string | string[],
15
+ ) as unknown as PolyfillWebSocket;
16
+ }
17
+ return new PolyfillWebSocket(url) as unknown as PolyfillWebSocket;
18
+ };
@@ -0,0 +1,8 @@
1
+ import ws from "ws";
2
+ import { PolyfillWebSocket, PolyfillWebSocketFactory } from ".";
3
+ export { PolyfillWebSocket } from ".";
4
+
5
+ export const factory: PolyfillWebSocketFactory = (
6
+ url: string,
7
+ params?: unknown,
8
+ ) => new ws(url, params as ws.ClientOptions) as unknown as PolyfillWebSocket;
@@ -0,0 +1,41 @@
1
+ import ws, { Event, ErrorEvent, CloseEvent, MessageEvent } from "ws";
2
+
3
+ export type PolyfillWebSocket = {
4
+ OPEN: typeof ws.OPEN;
5
+ binaryType: string;
6
+ onopen: ((event: Event) => void) | null;
7
+ onerror: ((event: ErrorEvent) => void) | null;
8
+ onclose: ((event: CloseEvent) => void) | null;
9
+ onmessage: ((event: MessageEvent) => void) | null;
10
+ readonly readyState:
11
+ | typeof ws.CONNECTING
12
+ | typeof ws.OPEN
13
+ | typeof ws.CLOSING
14
+ | typeof ws.CLOSED;
15
+ removeAllListeners?: () => void;
16
+ send(
17
+ data:
18
+ | string
19
+ | number
20
+ | Buffer
21
+ | DataView
22
+ | ArrayBufferView
23
+ | Uint8Array
24
+ | ArrayBuffer
25
+ | SharedArrayBuffer
26
+ | readonly unknown[]
27
+ | readonly number[]
28
+ | { valueOf(): ArrayBuffer }
29
+ | { valueOf(): SharedArrayBuffer }
30
+ | { valueOf(): Uint8Array }
31
+ | { valueOf(): readonly number[] }
32
+ | { valueOf(): string }
33
+ | { [Symbol.toPrimitive](hint: string): string },
34
+ ): unknown;
35
+ close(): unknown;
36
+ };
37
+
38
+ export type PolyfillWebSocketFactory = (
39
+ url: string,
40
+ params?: unknown,
41
+ ) => PolyfillWebSocket;
@@ -21,6 +21,7 @@ export abstract class BaseService {
21
21
  "Content-Type": "application/json",
22
22
  ...init.headers,
23
23
  };
24
+ init.cache = "no-store";
24
25
  if (!input.startsWith("http")) input = this.params.baseUrl + input;
25
26
 
26
27
  const response = await fetch(input, init);
@@ -10,8 +10,13 @@ export class FileService extends BaseService {
10
10
  */
11
11
  async upload(input: FileUploadParams): Promise<string> {
12
12
  let fileData: FileUploadData;
13
- if (typeof input === "string") fileData = await readFile(input);
14
- else fileData = input;
13
+ if (typeof input === "string") {
14
+ if (input.startsWith("data:")) {
15
+ fileData = dataUrlToBlob(input);
16
+ } else {
17
+ fileData = await readFile(input);
18
+ }
19
+ } else fileData = input;
15
20
 
16
21
  const data = await this.fetchJson<UploadedFile>("/v2/upload", {
17
22
  method: "POST",
@@ -24,3 +29,15 @@ export class FileService extends BaseService {
24
29
  return data.upload_url;
25
30
  }
26
31
  }
32
+
33
+ function dataUrlToBlob(dataUrl: string) {
34
+ const arr = dataUrl.split(",");
35
+ const mime = arr[0].match(/:(.*?);/)![1];
36
+ const bstr = atob(arr[1]);
37
+ let n = bstr.length;
38
+ const u8arr = new Uint8Array(n);
39
+ while (n--) {
40
+ u8arr[n] = bstr.charCodeAt(n);
41
+ }
42
+ return new Blob([u8arr], { type: mime });
43
+ }
@@ -1,5 +1,8 @@
1
1
  import { WritableStream } from "#streams";
2
- import WebSocket from "#ws";
2
+ import {
3
+ PolyfillWebSocket,
4
+ factory as polyfillWebSocketFactory,
5
+ } from "#websocket";
3
6
  import { ErrorEvent, MessageEvent, CloseEvent } from "ws";
4
7
  import {
5
8
  RealtimeEvents,
@@ -52,7 +55,7 @@ export class RealtimeTranscriber {
52
55
  private endUtteranceSilenceThreshold?: number;
53
56
  private disablePartialTranscripts?: boolean;
54
57
 
55
- private socket?: WebSocket;
58
+ private socket?: PolyfillWebSocket;
56
59
  private listeners: RealtimeListeners = {};
57
60
  private sessionTerminatedResolve?: () => void;
58
61
 
@@ -136,15 +139,15 @@ export class RealtimeTranscriber {
136
139
  const url = this.connectionUrl();
137
140
 
138
141
  if (this.token) {
139
- this.socket = new WebSocket(url.toString());
142
+ this.socket = polyfillWebSocketFactory(url.toString());
140
143
  } else {
141
- this.socket = new WebSocket(url.toString(), {
144
+ this.socket = polyfillWebSocketFactory(url.toString(), {
142
145
  headers: { Authorization: this.apiKey },
143
146
  });
144
147
  }
145
- this.socket.binaryType = "arraybuffer";
148
+ this.socket!.binaryType = "arraybuffer";
146
149
 
147
- this.socket.onopen = () => {
150
+ this.socket!.onopen = () => {
148
151
  if (
149
152
  this.endUtteranceSilenceThreshold === undefined ||
150
153
  this.endUtteranceSilenceThreshold === null
@@ -156,7 +159,7 @@ export class RealtimeTranscriber {
156
159
  );
157
160
  };
158
161
 
159
- this.socket.onclose = ({ code, reason }: CloseEvent) => {
162
+ this.socket!.onclose = ({ code, reason }: CloseEvent) => {
160
163
  if (!reason) {
161
164
  if (code in RealtimeErrorType) {
162
165
  reason = RealtimeErrorMessages[code as RealtimeErrorType];
@@ -165,12 +168,12 @@ export class RealtimeTranscriber {
165
168
  this.listeners.close?.(code, reason);
166
169
  };
167
170
 
168
- this.socket.onerror = (event: ErrorEvent) => {
171
+ this.socket!.onerror = (event: ErrorEvent) => {
169
172
  if (event.error) this.listeners.error?.(event.error as Error);
170
173
  else this.listeners.error?.(new Error(event.message));
171
174
  };
172
175
 
173
- this.socket.onmessage = ({ data }: MessageEvent) => {
176
+ this.socket!.onmessage = ({ data }: MessageEvent) => {
174
177
  const message = JSON.parse(data.toString()) as RealtimeMessage;
175
178
  if ("error" in message) {
176
179
  this.listeners.error?.(new RealtimeError(message.error));
@@ -242,7 +245,7 @@ export class RealtimeTranscriber {
242
245
  }
243
246
 
244
247
  private send(data: BufferLike) {
245
- if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
248
+ if (!this.socket || this.socket.readyState !== this.socket.OPEN) {
246
249
  throw new Error("Socket is not open for communication");
247
250
  }
248
251
  this.socket.send(data);
@@ -250,7 +253,7 @@ export class RealtimeTranscriber {
250
253
 
251
254
  async close(waitForSessionTermination = true) {
252
255
  if (this.socket) {
253
- if (this.socket.readyState === WebSocket.OPEN) {
256
+ if (this.socket.readyState === this.socket.OPEN) {
254
257
  if (waitForSessionTermination) {
255
258
  const sessionTerminatedPromise = new Promise<void>((resolve) => {
256
259
  this.sessionTerminatedResolve = resolve;
@@ -261,7 +264,7 @@ export class RealtimeTranscriber {
261
264
  this.socket.send(terminateSessionMessage);
262
265
  }
263
266
  }
264
- if ("removeAllListeners" in this.socket) this.socket.removeAllListeners();
267
+ if (this.socket?.removeAllListeners) this.socket.removeAllListeners();
265
268
  this.socket.close();
266
269
  }
267
270
 
@@ -60,8 +60,12 @@ export class TranscriptService extends BaseService {
60
60
  // audio is local path, upload local file
61
61
  audioUrl = await this.files.upload(path);
62
62
  } else {
63
- // audio is not a local path, assume it's a URL
64
- audioUrl = audio;
63
+ if (audio.startsWith("data:")) {
64
+ audioUrl = await this.files.upload(audio);
65
+ } else {
66
+ // audio is not a local path, and not a data-URI, assume it's a normal URL
67
+ audioUrl = audio;
68
+ }
65
69
  }
66
70
  } else {
67
71
  // audio is of uploadable type