assemblyai 4.34.6 → 4.35.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -76,7 +76,7 @@ if (typeof navigator !== "undefined" && navigator.userAgent) {
76
76
  defaultUserAgentString += navigator.userAgent;
77
77
  }
78
78
  const defaultUserAgent = {
79
- sdk: { name: "JavaScript", version: "4.34.6" },
79
+ sdk: { name: "JavaScript", version: "4.35.3" },
80
80
  };
81
81
  if (typeof process !== "undefined") {
82
82
  if (process.versions.node && defaultUserAgentString.indexOf("Node") === -1) {
@@ -1077,6 +1077,24 @@ function toInt16View(audio) {
1077
1077
  }
1078
1078
  const defaultStreamingUrl$1 = "wss://streaming.assemblyai.com/v3/ws";
1079
1079
  const terminateSessionMessage = `{"type":"Terminate"}`;
1080
+ const DEFAULT_CONNECT_TIMEOUT_MS = 1000;
1081
+ const DEFAULT_MAX_CONNECTION_RETRIES = 2;
1082
+ const DEFAULT_CONNECTION_RETRY_DELAY_MS = 500;
1083
+ /**
1084
+ * Close/error codes that signal a permanent client-side problem (auth,
1085
+ * billing, malformed config). A retry would hit the same failure, so the
1086
+ * connection is never retried on these.
1087
+ */
1088
+ const NON_RETRYABLE_CLOSE_CODES = new Set([
1089
+ StreamingErrorType.BadSampleRate,
1090
+ StreamingErrorType.AuthFailed,
1091
+ StreamingErrorType.InsufficientFunds,
1092
+ StreamingErrorType.FreeTierUser,
1093
+ StreamingErrorType.BadSchema,
1094
+ ]);
1095
+ function isRetryableCloseCode(code) {
1096
+ return code !== 1000 && !NON_RETRYABLE_CLOSE_CODES.has(code);
1097
+ }
1080
1098
  /**
1081
1099
  * Per-send chunk cap in milliseconds for the dual-channel mixer. The streaming
1082
1100
  * server rejects audio messages longer than 1000 ms (`Input Duration Error`).
@@ -1208,8 +1226,12 @@ class StreamingTranscriber {
1208
1226
  searchParams.set("speech_model", this.params.speechModel.toString());
1209
1227
  }
1210
1228
  if (this.params.languageCode !== undefined) {
1229
+ console.warn("[Deprecation Warning] `languageCode` is deprecated and will be removed in a future release. Please use `languageCodes` instead.");
1211
1230
  searchParams.set("language_code", this.params.languageCode);
1212
1231
  }
1232
+ if (this.params.languageCodes !== undefined) {
1233
+ searchParams.set("language_codes", JSON.stringify(this.params.languageCodes));
1234
+ }
1213
1235
  if (this.params.languageDetection !== undefined) {
1214
1236
  searchParams.set("language_detection", this.params.languageDetection.toString());
1215
1237
  }
@@ -1283,12 +1305,85 @@ class StreamingTranscriber {
1283
1305
  on(event, listener) {
1284
1306
  this.listeners[event] = listener;
1285
1307
  }
1308
+ /**
1309
+ * Open the streaming session.
1310
+ *
1311
+ * Resolves with the server's `Begin` event once the handshake completes. A
1312
+ * single attempt is bounded by `connectTimeout` (default 1000ms); transient
1313
+ * failures (timeout, network drop, unexpected close) are retried up to
1314
+ * `maxConnectionRetries` times (default 2), waiting `connectionRetryDelay`
1315
+ * (default 500ms) between attempts. Permanent failures (auth, insufficient
1316
+ * funds, malformed config) are not retried.
1317
+ *
1318
+ * Unlike previously, a failed connection now rejects this promise rather
1319
+ * than only invoking the `error` listener — necessary for the caller (and
1320
+ * the retry loop) to observe the failure.
1321
+ */
1286
1322
  connect() {
1287
- return new Promise((resolve) => {
1323
+ return __awaiter(this, void 0, void 0, function* () {
1324
+ var _a, _b;
1288
1325
  if (this.socket) {
1289
1326
  throw new Error("Already connected");
1290
1327
  }
1328
+ const maxRetries = (_a = this.params.maxConnectionRetries) !== null && _a !== void 0 ? _a : DEFAULT_MAX_CONNECTION_RETRIES;
1329
+ const retryDelay = (_b = this.params.connectionRetryDelay) !== null && _b !== void 0 ? _b : DEFAULT_CONNECTION_RETRY_DELAY_MS;
1330
+ let lastError;
1331
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
1332
+ try {
1333
+ return yield this.connectOnce();
1334
+ }
1335
+ catch (err) {
1336
+ lastError = err;
1337
+ const retryable = err.retryable === true;
1338
+ if (!retryable || attempt === maxRetries) {
1339
+ throw err;
1340
+ }
1341
+ console.warn(`Streaming connect attempt ${attempt + 1}/${maxRetries + 1} failed (${err.message}); retrying`);
1342
+ if (retryDelay > 0) {
1343
+ yield new Promise((resolve) => setTimeout(resolve, retryDelay));
1344
+ }
1345
+ }
1346
+ }
1347
+ // The loop above always returns or throws; this only satisfies the type
1348
+ // checker that a value is produced on every path.
1349
+ throw lastError !== null && lastError !== void 0 ? lastError : new Error("Failed to connect to streaming server");
1350
+ });
1351
+ }
1352
+ connectOnce() {
1353
+ return new Promise((resolve, reject) => {
1354
+ var _a;
1291
1355
  const url = this.connectionUrl();
1356
+ const timeoutMs = (_a = this.params.connectTimeout) !== null && _a !== void 0 ? _a : DEFAULT_CONNECT_TIMEOUT_MS;
1357
+ // `settled` flips once this attempt has resolved (`Begin`) or rejected
1358
+ // (timeout / pre-`Begin` close / error). Before it flips the socket
1359
+ // handlers drive this promise; after it flips they revert to normal
1360
+ // runtime dispatch (close / error / message listeners).
1361
+ let settled = false;
1362
+ let timer;
1363
+ const failAttempt = (error) => {
1364
+ if (settled)
1365
+ return;
1366
+ settled = true;
1367
+ if (timer)
1368
+ clearTimeout(timer);
1369
+ this.discardPendingSocket();
1370
+ reject(error);
1371
+ };
1372
+ const succeed = (begin) => {
1373
+ if (settled)
1374
+ return;
1375
+ settled = true;
1376
+ if (timer)
1377
+ clearTimeout(timer);
1378
+ resolve(begin);
1379
+ };
1380
+ if (timeoutMs > 0) {
1381
+ timer = setTimeout(() => {
1382
+ const err = new StreamingError(`Streaming connection timed out after ${timeoutMs}ms`);
1383
+ err.retryable = true;
1384
+ failAttempt(err);
1385
+ }, timeoutMs);
1386
+ }
1292
1387
  if (this.token) {
1293
1388
  this.socket = factory(url.toString());
1294
1389
  }
@@ -1306,6 +1401,15 @@ class StreamingTranscriber {
1306
1401
  reason = StreamingErrorMessages[code];
1307
1402
  }
1308
1403
  }
1404
+ // A close before `Begin` is a failed connection attempt — reject so
1405
+ // connect() can retry (or surface a permanent failure).
1406
+ if (!settled) {
1407
+ const err = new StreamingError(reason || `Streaming connection closed (code=${code})`);
1408
+ err.code = code;
1409
+ err.retryable = isRetryableCloseCode(code);
1410
+ failAttempt(err);
1411
+ return;
1412
+ }
1309
1413
  // Stop the flush timer when the socket is gone (server-initiated close,
1310
1414
  // network drop, etc.) — otherwise subsequent ticks call send() on a
1311
1415
  // closed socket and spam the error listener.
@@ -1316,11 +1420,15 @@ class StreamingTranscriber {
1316
1420
  (_b = (_a = this.listeners).close) === null || _b === void 0 ? void 0 : _b.call(_a, code, reason);
1317
1421
  };
1318
1422
  this.socket.onerror = (event) => {
1319
- var _a, _b, _c, _d;
1320
- if (event.error)
1321
- (_b = (_a = this.listeners).error) === null || _b === void 0 ? void 0 : _b.call(_a, event.error);
1322
- else
1323
- (_d = (_c = this.listeners).error) === null || _d === void 0 ? void 0 : _d.call(_c, new Error(event.message));
1423
+ var _a, _b, _c;
1424
+ const error = (_a = event.error) !== null && _a !== void 0 ? _a : new Error(event.message);
1425
+ // A socket error before `Begin` is a failed attempt reject/retry.
1426
+ if (!settled) {
1427
+ error.retryable = true;
1428
+ failAttempt(error);
1429
+ return;
1430
+ }
1431
+ (_c = (_b = this.listeners).error) === null || _c === void 0 ? void 0 : _c.call(_b, error);
1324
1432
  };
1325
1433
  this.socket.onmessage = ({ data }) => {
1326
1434
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
@@ -1328,15 +1436,23 @@ class StreamingTranscriber {
1328
1436
  if ("error" in message) {
1329
1437
  const err = new StreamingError(message.error);
1330
1438
  if ("error_code" in message) {
1331
- err.code =
1332
- message.error_code;
1439
+ err.code = message.error_code;
1440
+ }
1441
+ // A server error frame before `Begin` fails the attempt; the code
1442
+ // decides whether a retry is worthwhile.
1443
+ if (!settled) {
1444
+ const attemptErr = err;
1445
+ attemptErr.retryable =
1446
+ err.code === undefined ? true : isRetryableCloseCode(err.code);
1447
+ failAttempt(attemptErr);
1448
+ return;
1333
1449
  }
1334
1450
  (_b = (_a = this.listeners).error) === null || _b === void 0 ? void 0 : _b.call(_a, err);
1335
1451
  return;
1336
1452
  }
1337
1453
  switch (message.type) {
1338
1454
  case "Begin": {
1339
- resolve(message);
1455
+ succeed(message);
1340
1456
  (_d = (_c = this.listeners).open) === null || _d === void 0 ? void 0 : _d.call(_c, message);
1341
1457
  break;
1342
1458
  }
@@ -1383,6 +1499,20 @@ class StreamingTranscriber {
1383
1499
  };
1384
1500
  });
1385
1501
  }
1502
+ /** Tear down a half-open socket from a failed connection attempt. */
1503
+ discardPendingSocket() {
1504
+ if (!this.socket)
1505
+ return;
1506
+ try {
1507
+ if (this.socket.removeAllListeners)
1508
+ this.socket.removeAllListeners();
1509
+ this.socket.close();
1510
+ }
1511
+ catch (_a) {
1512
+ // Best-effort cleanup; a half-open socket may throw on close.
1513
+ }
1514
+ this.socket = undefined;
1515
+ }
1386
1516
  /**
1387
1517
  * Returns a WritableStream that pumps PCM chunks into `sendAudio`. Single-channel
1388
1518
  * only — in dual-channel mode use `sendAudio(pcm, { channel })` directly, since
@@ -1656,6 +1786,16 @@ class StreamingTranscriber {
1656
1786
  };
1657
1787
  this.send(JSON.stringify(message));
1658
1788
  }
1789
+ /**
1790
+ * Reset the server's inactivity timer. Only needed when the session was
1791
+ * created with `inactivityTimeout` and no audio is being sent.
1792
+ */
1793
+ keepAlive() {
1794
+ const message = {
1795
+ type: "KeepAlive",
1796
+ };
1797
+ this.send(JSON.stringify(message));
1798
+ }
1659
1799
  send(data) {
1660
1800
  if (!this.socket || this.socket.readyState !== this.socket.OPEN) {
1661
1801
  throw new Error("Socket is not open for communication");
package/dist/index.mjs CHANGED
@@ -74,7 +74,7 @@ if (typeof navigator !== "undefined" && navigator.userAgent) {
74
74
  defaultUserAgentString += navigator.userAgent;
75
75
  }
76
76
  const defaultUserAgent = {
77
- sdk: { name: "JavaScript", version: "4.34.6" },
77
+ sdk: { name: "JavaScript", version: "4.35.3" },
78
78
  };
79
79
  if (typeof process !== "undefined") {
80
80
  if (process.versions.node && defaultUserAgentString.indexOf("Node") === -1) {
@@ -1075,6 +1075,24 @@ function toInt16View(audio) {
1075
1075
  }
1076
1076
  const defaultStreamingUrl$1 = "wss://streaming.assemblyai.com/v3/ws";
1077
1077
  const terminateSessionMessage = `{"type":"Terminate"}`;
1078
+ const DEFAULT_CONNECT_TIMEOUT_MS = 1000;
1079
+ const DEFAULT_MAX_CONNECTION_RETRIES = 2;
1080
+ const DEFAULT_CONNECTION_RETRY_DELAY_MS = 500;
1081
+ /**
1082
+ * Close/error codes that signal a permanent client-side problem (auth,
1083
+ * billing, malformed config). A retry would hit the same failure, so the
1084
+ * connection is never retried on these.
1085
+ */
1086
+ const NON_RETRYABLE_CLOSE_CODES = new Set([
1087
+ StreamingErrorType.BadSampleRate,
1088
+ StreamingErrorType.AuthFailed,
1089
+ StreamingErrorType.InsufficientFunds,
1090
+ StreamingErrorType.FreeTierUser,
1091
+ StreamingErrorType.BadSchema,
1092
+ ]);
1093
+ function isRetryableCloseCode(code) {
1094
+ return code !== 1000 && !NON_RETRYABLE_CLOSE_CODES.has(code);
1095
+ }
1078
1096
  /**
1079
1097
  * Per-send chunk cap in milliseconds for the dual-channel mixer. The streaming
1080
1098
  * server rejects audio messages longer than 1000 ms (`Input Duration Error`).
@@ -1206,8 +1224,12 @@ class StreamingTranscriber {
1206
1224
  searchParams.set("speech_model", this.params.speechModel.toString());
1207
1225
  }
1208
1226
  if (this.params.languageCode !== undefined) {
1227
+ console.warn("[Deprecation Warning] `languageCode` is deprecated and will be removed in a future release. Please use `languageCodes` instead.");
1209
1228
  searchParams.set("language_code", this.params.languageCode);
1210
1229
  }
1230
+ if (this.params.languageCodes !== undefined) {
1231
+ searchParams.set("language_codes", JSON.stringify(this.params.languageCodes));
1232
+ }
1211
1233
  if (this.params.languageDetection !== undefined) {
1212
1234
  searchParams.set("language_detection", this.params.languageDetection.toString());
1213
1235
  }
@@ -1281,12 +1303,85 @@ class StreamingTranscriber {
1281
1303
  on(event, listener) {
1282
1304
  this.listeners[event] = listener;
1283
1305
  }
1306
+ /**
1307
+ * Open the streaming session.
1308
+ *
1309
+ * Resolves with the server's `Begin` event once the handshake completes. A
1310
+ * single attempt is bounded by `connectTimeout` (default 1000ms); transient
1311
+ * failures (timeout, network drop, unexpected close) are retried up to
1312
+ * `maxConnectionRetries` times (default 2), waiting `connectionRetryDelay`
1313
+ * (default 500ms) between attempts. Permanent failures (auth, insufficient
1314
+ * funds, malformed config) are not retried.
1315
+ *
1316
+ * Unlike previously, a failed connection now rejects this promise rather
1317
+ * than only invoking the `error` listener — necessary for the caller (and
1318
+ * the retry loop) to observe the failure.
1319
+ */
1284
1320
  connect() {
1285
- return new Promise((resolve) => {
1321
+ return __awaiter(this, void 0, void 0, function* () {
1322
+ var _a, _b;
1286
1323
  if (this.socket) {
1287
1324
  throw new Error("Already connected");
1288
1325
  }
1326
+ const maxRetries = (_a = this.params.maxConnectionRetries) !== null && _a !== void 0 ? _a : DEFAULT_MAX_CONNECTION_RETRIES;
1327
+ const retryDelay = (_b = this.params.connectionRetryDelay) !== null && _b !== void 0 ? _b : DEFAULT_CONNECTION_RETRY_DELAY_MS;
1328
+ let lastError;
1329
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
1330
+ try {
1331
+ return yield this.connectOnce();
1332
+ }
1333
+ catch (err) {
1334
+ lastError = err;
1335
+ const retryable = err.retryable === true;
1336
+ if (!retryable || attempt === maxRetries) {
1337
+ throw err;
1338
+ }
1339
+ console.warn(`Streaming connect attempt ${attempt + 1}/${maxRetries + 1} failed (${err.message}); retrying`);
1340
+ if (retryDelay > 0) {
1341
+ yield new Promise((resolve) => setTimeout(resolve, retryDelay));
1342
+ }
1343
+ }
1344
+ }
1345
+ // The loop above always returns or throws; this only satisfies the type
1346
+ // checker that a value is produced on every path.
1347
+ throw lastError !== null && lastError !== void 0 ? lastError : new Error("Failed to connect to streaming server");
1348
+ });
1349
+ }
1350
+ connectOnce() {
1351
+ return new Promise((resolve, reject) => {
1352
+ var _a;
1289
1353
  const url = this.connectionUrl();
1354
+ const timeoutMs = (_a = this.params.connectTimeout) !== null && _a !== void 0 ? _a : DEFAULT_CONNECT_TIMEOUT_MS;
1355
+ // `settled` flips once this attempt has resolved (`Begin`) or rejected
1356
+ // (timeout / pre-`Begin` close / error). Before it flips the socket
1357
+ // handlers drive this promise; after it flips they revert to normal
1358
+ // runtime dispatch (close / error / message listeners).
1359
+ let settled = false;
1360
+ let timer;
1361
+ const failAttempt = (error) => {
1362
+ if (settled)
1363
+ return;
1364
+ settled = true;
1365
+ if (timer)
1366
+ clearTimeout(timer);
1367
+ this.discardPendingSocket();
1368
+ reject(error);
1369
+ };
1370
+ const succeed = (begin) => {
1371
+ if (settled)
1372
+ return;
1373
+ settled = true;
1374
+ if (timer)
1375
+ clearTimeout(timer);
1376
+ resolve(begin);
1377
+ };
1378
+ if (timeoutMs > 0) {
1379
+ timer = setTimeout(() => {
1380
+ const err = new StreamingError(`Streaming connection timed out after ${timeoutMs}ms`);
1381
+ err.retryable = true;
1382
+ failAttempt(err);
1383
+ }, timeoutMs);
1384
+ }
1290
1385
  if (this.token) {
1291
1386
  this.socket = factory(url.toString());
1292
1387
  }
@@ -1304,6 +1399,15 @@ class StreamingTranscriber {
1304
1399
  reason = StreamingErrorMessages[code];
1305
1400
  }
1306
1401
  }
1402
+ // A close before `Begin` is a failed connection attempt — reject so
1403
+ // connect() can retry (or surface a permanent failure).
1404
+ if (!settled) {
1405
+ const err = new StreamingError(reason || `Streaming connection closed (code=${code})`);
1406
+ err.code = code;
1407
+ err.retryable = isRetryableCloseCode(code);
1408
+ failAttempt(err);
1409
+ return;
1410
+ }
1307
1411
  // Stop the flush timer when the socket is gone (server-initiated close,
1308
1412
  // network drop, etc.) — otherwise subsequent ticks call send() on a
1309
1413
  // closed socket and spam the error listener.
@@ -1314,11 +1418,15 @@ class StreamingTranscriber {
1314
1418
  (_b = (_a = this.listeners).close) === null || _b === void 0 ? void 0 : _b.call(_a, code, reason);
1315
1419
  };
1316
1420
  this.socket.onerror = (event) => {
1317
- var _a, _b, _c, _d;
1318
- if (event.error)
1319
- (_b = (_a = this.listeners).error) === null || _b === void 0 ? void 0 : _b.call(_a, event.error);
1320
- else
1321
- (_d = (_c = this.listeners).error) === null || _d === void 0 ? void 0 : _d.call(_c, new Error(event.message));
1421
+ var _a, _b, _c;
1422
+ const error = (_a = event.error) !== null && _a !== void 0 ? _a : new Error(event.message);
1423
+ // A socket error before `Begin` is a failed attempt reject/retry.
1424
+ if (!settled) {
1425
+ error.retryable = true;
1426
+ failAttempt(error);
1427
+ return;
1428
+ }
1429
+ (_c = (_b = this.listeners).error) === null || _c === void 0 ? void 0 : _c.call(_b, error);
1322
1430
  };
1323
1431
  this.socket.onmessage = ({ data }) => {
1324
1432
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
@@ -1326,15 +1434,23 @@ class StreamingTranscriber {
1326
1434
  if ("error" in message) {
1327
1435
  const err = new StreamingError(message.error);
1328
1436
  if ("error_code" in message) {
1329
- err.code =
1330
- message.error_code;
1437
+ err.code = message.error_code;
1438
+ }
1439
+ // A server error frame before `Begin` fails the attempt; the code
1440
+ // decides whether a retry is worthwhile.
1441
+ if (!settled) {
1442
+ const attemptErr = err;
1443
+ attemptErr.retryable =
1444
+ err.code === undefined ? true : isRetryableCloseCode(err.code);
1445
+ failAttempt(attemptErr);
1446
+ return;
1331
1447
  }
1332
1448
  (_b = (_a = this.listeners).error) === null || _b === void 0 ? void 0 : _b.call(_a, err);
1333
1449
  return;
1334
1450
  }
1335
1451
  switch (message.type) {
1336
1452
  case "Begin": {
1337
- resolve(message);
1453
+ succeed(message);
1338
1454
  (_d = (_c = this.listeners).open) === null || _d === void 0 ? void 0 : _d.call(_c, message);
1339
1455
  break;
1340
1456
  }
@@ -1381,6 +1497,20 @@ class StreamingTranscriber {
1381
1497
  };
1382
1498
  });
1383
1499
  }
1500
+ /** Tear down a half-open socket from a failed connection attempt. */
1501
+ discardPendingSocket() {
1502
+ if (!this.socket)
1503
+ return;
1504
+ try {
1505
+ if (this.socket.removeAllListeners)
1506
+ this.socket.removeAllListeners();
1507
+ this.socket.close();
1508
+ }
1509
+ catch (_a) {
1510
+ // Best-effort cleanup; a half-open socket may throw on close.
1511
+ }
1512
+ this.socket = undefined;
1513
+ }
1384
1514
  /**
1385
1515
  * Returns a WritableStream that pumps PCM chunks into `sendAudio`. Single-channel
1386
1516
  * only — in dual-channel mode use `sendAudio(pcm, { channel })` directly, since
@@ -1654,6 +1784,16 @@ class StreamingTranscriber {
1654
1784
  };
1655
1785
  this.send(JSON.stringify(message));
1656
1786
  }
1787
+ /**
1788
+ * Reset the server's inactivity timer. Only needed when the session was
1789
+ * created with `inactivityTimeout` and no audio is being sent.
1790
+ */
1791
+ keepAlive() {
1792
+ const message = {
1793
+ type: "KeepAlive",
1794
+ };
1795
+ this.send(JSON.stringify(message));
1796
+ }
1657
1797
  send(data) {
1658
1798
  if (!this.socket || this.socket.readyState !== this.socket.OPEN) {
1659
1799
  throw new Error("Socket is not open for communication");