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/CHANGELOG.md +16 -0
- package/README.md +7 -8
- package/dist/assemblyai.streaming.umd.js +150 -10
- package/dist/assemblyai.streaming.umd.min.js +1 -1
- package/dist/assemblyai.umd.js +150 -10
- package/dist/assemblyai.umd.min.js +1 -1
- package/dist/browser.mjs +148 -12
- package/dist/bun.mjs +148 -12
- package/dist/deno.mjs +148 -12
- package/dist/exports/streaming.d.ts +1 -1
- package/dist/index.cjs +150 -10
- package/dist/index.mjs +150 -10
- package/dist/node.cjs +148 -12
- package/dist/node.mjs +148 -12
- package/dist/services/streaming/service.d.ts +22 -0
- package/dist/streaming.browser.mjs +148 -12
- package/dist/streaming.cjs +149 -9
- package/dist/streaming.mjs +149 -9
- package/dist/types/asyncapi.generated.d.ts +1 -1
- package/dist/types/streaming/index.d.ts +38 -1
- package/dist/workerd.mjs +148 -12
- package/package.json +1 -1
- package/src/exports/streaming.ts +1 -1
- package/src/services/streaming/service.ts +177 -11
- package/src/types/asyncapi.generated.ts +1 -1
- package/src/types/streaming/index.ts +39 -0
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.
|
|
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
|
|
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
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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");
|