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/bun.mjs
CHANGED
|
@@ -30,7 +30,7 @@ if (typeof navigator !== "undefined" && navigator.userAgent) {
|
|
|
30
30
|
defaultUserAgentString += navigator.userAgent;
|
|
31
31
|
}
|
|
32
32
|
const defaultUserAgent = {
|
|
33
|
-
sdk: { name: "JavaScript", version: "4.
|
|
33
|
+
sdk: { name: "JavaScript", version: "4.35.3" },
|
|
34
34
|
};
|
|
35
35
|
if (typeof process !== "undefined") {
|
|
36
36
|
if (process.versions.node && defaultUserAgentString.indexOf("Node") === -1) {
|
|
@@ -981,6 +981,24 @@ function toInt16View(audio) {
|
|
|
981
981
|
}
|
|
982
982
|
const defaultStreamingUrl$1 = "wss://streaming.assemblyai.com/v3/ws";
|
|
983
983
|
const terminateSessionMessage = `{"type":"Terminate"}`;
|
|
984
|
+
const DEFAULT_CONNECT_TIMEOUT_MS = 1000;
|
|
985
|
+
const DEFAULT_MAX_CONNECTION_RETRIES = 2;
|
|
986
|
+
const DEFAULT_CONNECTION_RETRY_DELAY_MS = 500;
|
|
987
|
+
/**
|
|
988
|
+
* Close/error codes that signal a permanent client-side problem (auth,
|
|
989
|
+
* billing, malformed config). A retry would hit the same failure, so the
|
|
990
|
+
* connection is never retried on these.
|
|
991
|
+
*/
|
|
992
|
+
const NON_RETRYABLE_CLOSE_CODES = new Set([
|
|
993
|
+
StreamingErrorType.BadSampleRate,
|
|
994
|
+
StreamingErrorType.AuthFailed,
|
|
995
|
+
StreamingErrorType.InsufficientFunds,
|
|
996
|
+
StreamingErrorType.FreeTierUser,
|
|
997
|
+
StreamingErrorType.BadSchema,
|
|
998
|
+
]);
|
|
999
|
+
function isRetryableCloseCode(code) {
|
|
1000
|
+
return code !== 1000 && !NON_RETRYABLE_CLOSE_CODES.has(code);
|
|
1001
|
+
}
|
|
984
1002
|
/**
|
|
985
1003
|
* Per-send chunk cap in milliseconds for the dual-channel mixer. The streaming
|
|
986
1004
|
* server rejects audio messages longer than 1000 ms (`Input Duration Error`).
|
|
@@ -1114,8 +1132,12 @@ class StreamingTranscriber {
|
|
|
1114
1132
|
searchParams.set("speech_model", this.params.speechModel.toString());
|
|
1115
1133
|
}
|
|
1116
1134
|
if (this.params.languageCode !== undefined) {
|
|
1135
|
+
console.warn("[Deprecation Warning] `languageCode` is deprecated and will be removed in a future release. Please use `languageCodes` instead.");
|
|
1117
1136
|
searchParams.set("language_code", this.params.languageCode);
|
|
1118
1137
|
}
|
|
1138
|
+
if (this.params.languageCodes !== undefined) {
|
|
1139
|
+
searchParams.set("language_codes", JSON.stringify(this.params.languageCodes));
|
|
1140
|
+
}
|
|
1119
1141
|
if (this.params.languageDetection !== undefined) {
|
|
1120
1142
|
searchParams.set("language_detection", this.params.languageDetection.toString());
|
|
1121
1143
|
}
|
|
@@ -1189,12 +1211,81 @@ class StreamingTranscriber {
|
|
|
1189
1211
|
on(event, listener) {
|
|
1190
1212
|
this.listeners[event] = listener;
|
|
1191
1213
|
}
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1214
|
+
/**
|
|
1215
|
+
* Open the streaming session.
|
|
1216
|
+
*
|
|
1217
|
+
* Resolves with the server's `Begin` event once the handshake completes. A
|
|
1218
|
+
* single attempt is bounded by `connectTimeout` (default 1000ms); transient
|
|
1219
|
+
* failures (timeout, network drop, unexpected close) are retried up to
|
|
1220
|
+
* `maxConnectionRetries` times (default 2), waiting `connectionRetryDelay`
|
|
1221
|
+
* (default 500ms) between attempts. Permanent failures (auth, insufficient
|
|
1222
|
+
* funds, malformed config) are not retried.
|
|
1223
|
+
*
|
|
1224
|
+
* Unlike previously, a failed connection now rejects this promise rather
|
|
1225
|
+
* than only invoking the `error` listener — necessary for the caller (and
|
|
1226
|
+
* the retry loop) to observe the failure.
|
|
1227
|
+
*/
|
|
1228
|
+
async connect() {
|
|
1229
|
+
if (this.socket) {
|
|
1230
|
+
throw new Error("Already connected");
|
|
1231
|
+
}
|
|
1232
|
+
const maxRetries = this.params.maxConnectionRetries ?? DEFAULT_MAX_CONNECTION_RETRIES;
|
|
1233
|
+
const retryDelay = this.params.connectionRetryDelay ?? DEFAULT_CONNECTION_RETRY_DELAY_MS;
|
|
1234
|
+
let lastError;
|
|
1235
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
1236
|
+
try {
|
|
1237
|
+
return await this.connectOnce();
|
|
1238
|
+
}
|
|
1239
|
+
catch (err) {
|
|
1240
|
+
lastError = err;
|
|
1241
|
+
const retryable = err.retryable === true;
|
|
1242
|
+
if (!retryable || attempt === maxRetries) {
|
|
1243
|
+
throw err;
|
|
1244
|
+
}
|
|
1245
|
+
console.warn(`Streaming connect attempt ${attempt + 1}/${maxRetries + 1} failed (${err.message}); retrying`);
|
|
1246
|
+
if (retryDelay > 0) {
|
|
1247
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
1248
|
+
}
|
|
1196
1249
|
}
|
|
1250
|
+
}
|
|
1251
|
+
// The loop above always returns or throws; this only satisfies the type
|
|
1252
|
+
// checker that a value is produced on every path.
|
|
1253
|
+
throw lastError ?? new Error("Failed to connect to streaming server");
|
|
1254
|
+
}
|
|
1255
|
+
connectOnce() {
|
|
1256
|
+
return new Promise((resolve, reject) => {
|
|
1197
1257
|
const url = this.connectionUrl();
|
|
1258
|
+
const timeoutMs = this.params.connectTimeout ?? DEFAULT_CONNECT_TIMEOUT_MS;
|
|
1259
|
+
// `settled` flips once this attempt has resolved (`Begin`) or rejected
|
|
1260
|
+
// (timeout / pre-`Begin` close / error). Before it flips the socket
|
|
1261
|
+
// handlers drive this promise; after it flips they revert to normal
|
|
1262
|
+
// runtime dispatch (close / error / message listeners).
|
|
1263
|
+
let settled = false;
|
|
1264
|
+
let timer;
|
|
1265
|
+
const failAttempt = (error) => {
|
|
1266
|
+
if (settled)
|
|
1267
|
+
return;
|
|
1268
|
+
settled = true;
|
|
1269
|
+
if (timer)
|
|
1270
|
+
clearTimeout(timer);
|
|
1271
|
+
this.discardPendingSocket();
|
|
1272
|
+
reject(error);
|
|
1273
|
+
};
|
|
1274
|
+
const succeed = (begin) => {
|
|
1275
|
+
if (settled)
|
|
1276
|
+
return;
|
|
1277
|
+
settled = true;
|
|
1278
|
+
if (timer)
|
|
1279
|
+
clearTimeout(timer);
|
|
1280
|
+
resolve(begin);
|
|
1281
|
+
};
|
|
1282
|
+
if (timeoutMs > 0) {
|
|
1283
|
+
timer = setTimeout(() => {
|
|
1284
|
+
const err = new StreamingError(`Streaming connection timed out after ${timeoutMs}ms`);
|
|
1285
|
+
err.retryable = true;
|
|
1286
|
+
failAttempt(err);
|
|
1287
|
+
}, timeoutMs);
|
|
1288
|
+
}
|
|
1198
1289
|
if (this.token) {
|
|
1199
1290
|
this.socket = factory(url.toString());
|
|
1200
1291
|
}
|
|
@@ -1211,6 +1302,15 @@ class StreamingTranscriber {
|
|
|
1211
1302
|
reason = StreamingErrorMessages[code];
|
|
1212
1303
|
}
|
|
1213
1304
|
}
|
|
1305
|
+
// A close before `Begin` is a failed connection attempt — reject so
|
|
1306
|
+
// connect() can retry (or surface a permanent failure).
|
|
1307
|
+
if (!settled) {
|
|
1308
|
+
const err = new StreamingError(reason || `Streaming connection closed (code=${code})`);
|
|
1309
|
+
err.code = code;
|
|
1310
|
+
err.retryable = isRetryableCloseCode(code);
|
|
1311
|
+
failAttempt(err);
|
|
1312
|
+
return;
|
|
1313
|
+
}
|
|
1214
1314
|
// Stop the flush timer when the socket is gone (server-initiated close,
|
|
1215
1315
|
// network drop, etc.) — otherwise subsequent ticks call send() on a
|
|
1216
1316
|
// closed socket and spam the error listener.
|
|
@@ -1221,25 +1321,37 @@ class StreamingTranscriber {
|
|
|
1221
1321
|
this.listeners.close?.(code, reason);
|
|
1222
1322
|
};
|
|
1223
1323
|
this.socket.onerror = (event) => {
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1324
|
+
const error = event.error ?? new Error(event.message);
|
|
1325
|
+
// A socket error before `Begin` is a failed attempt → reject/retry.
|
|
1326
|
+
if (!settled) {
|
|
1327
|
+
error.retryable = true;
|
|
1328
|
+
failAttempt(error);
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
this.listeners.error?.(error);
|
|
1228
1332
|
};
|
|
1229
1333
|
this.socket.onmessage = ({ data }) => {
|
|
1230
1334
|
const message = JSON.parse(data.toString());
|
|
1231
1335
|
if ("error" in message) {
|
|
1232
1336
|
const err = new StreamingError(message.error);
|
|
1233
1337
|
if ("error_code" in message) {
|
|
1234
|
-
err.code =
|
|
1235
|
-
|
|
1338
|
+
err.code = message.error_code;
|
|
1339
|
+
}
|
|
1340
|
+
// A server error frame before `Begin` fails the attempt; the code
|
|
1341
|
+
// decides whether a retry is worthwhile.
|
|
1342
|
+
if (!settled) {
|
|
1343
|
+
const attemptErr = err;
|
|
1344
|
+
attemptErr.retryable =
|
|
1345
|
+
err.code === undefined ? true : isRetryableCloseCode(err.code);
|
|
1346
|
+
failAttempt(attemptErr);
|
|
1347
|
+
return;
|
|
1236
1348
|
}
|
|
1237
1349
|
this.listeners.error?.(err);
|
|
1238
1350
|
return;
|
|
1239
1351
|
}
|
|
1240
1352
|
switch (message.type) {
|
|
1241
1353
|
case "Begin": {
|
|
1242
|
-
|
|
1354
|
+
succeed(message);
|
|
1243
1355
|
this.listeners.open?.(message);
|
|
1244
1356
|
break;
|
|
1245
1357
|
}
|
|
@@ -1286,6 +1398,20 @@ class StreamingTranscriber {
|
|
|
1286
1398
|
};
|
|
1287
1399
|
});
|
|
1288
1400
|
}
|
|
1401
|
+
/** Tear down a half-open socket from a failed connection attempt. */
|
|
1402
|
+
discardPendingSocket() {
|
|
1403
|
+
if (!this.socket)
|
|
1404
|
+
return;
|
|
1405
|
+
try {
|
|
1406
|
+
if (this.socket.removeAllListeners)
|
|
1407
|
+
this.socket.removeAllListeners();
|
|
1408
|
+
this.socket.close();
|
|
1409
|
+
}
|
|
1410
|
+
catch {
|
|
1411
|
+
// Best-effort cleanup; a half-open socket may throw on close.
|
|
1412
|
+
}
|
|
1413
|
+
this.socket = undefined;
|
|
1414
|
+
}
|
|
1289
1415
|
/**
|
|
1290
1416
|
* Returns a WritableStream that pumps PCM chunks into `sendAudio`. Single-channel
|
|
1291
1417
|
* only — in dual-channel mode use `sendAudio(pcm, { channel })` directly, since
|
|
@@ -1559,6 +1685,16 @@ class StreamingTranscriber {
|
|
|
1559
1685
|
};
|
|
1560
1686
|
this.send(JSON.stringify(message));
|
|
1561
1687
|
}
|
|
1688
|
+
/**
|
|
1689
|
+
* Reset the server's inactivity timer. Only needed when the session was
|
|
1690
|
+
* created with `inactivityTimeout` and no audio is being sent.
|
|
1691
|
+
*/
|
|
1692
|
+
keepAlive() {
|
|
1693
|
+
const message = {
|
|
1694
|
+
type: "KeepAlive",
|
|
1695
|
+
};
|
|
1696
|
+
this.send(JSON.stringify(message));
|
|
1697
|
+
}
|
|
1562
1698
|
send(data) {
|
|
1563
1699
|
if (!this.socket || this.socket.readyState !== this.socket.OPEN) {
|
|
1564
1700
|
throw new Error("Socket is not open for communication");
|
package/dist/deno.mjs
CHANGED
|
@@ -30,7 +30,7 @@ if (typeof navigator !== "undefined" && navigator.userAgent) {
|
|
|
30
30
|
defaultUserAgentString += navigator.userAgent;
|
|
31
31
|
}
|
|
32
32
|
const defaultUserAgent = {
|
|
33
|
-
sdk: { name: "JavaScript", version: "4.
|
|
33
|
+
sdk: { name: "JavaScript", version: "4.35.3" },
|
|
34
34
|
};
|
|
35
35
|
if (typeof process !== "undefined") {
|
|
36
36
|
if (process.versions.node && defaultUserAgentString.indexOf("Node") === -1) {
|
|
@@ -981,6 +981,24 @@ function toInt16View(audio) {
|
|
|
981
981
|
}
|
|
982
982
|
const defaultStreamingUrl$1 = "wss://streaming.assemblyai.com/v3/ws";
|
|
983
983
|
const terminateSessionMessage = `{"type":"Terminate"}`;
|
|
984
|
+
const DEFAULT_CONNECT_TIMEOUT_MS = 1000;
|
|
985
|
+
const DEFAULT_MAX_CONNECTION_RETRIES = 2;
|
|
986
|
+
const DEFAULT_CONNECTION_RETRY_DELAY_MS = 500;
|
|
987
|
+
/**
|
|
988
|
+
* Close/error codes that signal a permanent client-side problem (auth,
|
|
989
|
+
* billing, malformed config). A retry would hit the same failure, so the
|
|
990
|
+
* connection is never retried on these.
|
|
991
|
+
*/
|
|
992
|
+
const NON_RETRYABLE_CLOSE_CODES = new Set([
|
|
993
|
+
StreamingErrorType.BadSampleRate,
|
|
994
|
+
StreamingErrorType.AuthFailed,
|
|
995
|
+
StreamingErrorType.InsufficientFunds,
|
|
996
|
+
StreamingErrorType.FreeTierUser,
|
|
997
|
+
StreamingErrorType.BadSchema,
|
|
998
|
+
]);
|
|
999
|
+
function isRetryableCloseCode(code) {
|
|
1000
|
+
return code !== 1000 && !NON_RETRYABLE_CLOSE_CODES.has(code);
|
|
1001
|
+
}
|
|
984
1002
|
/**
|
|
985
1003
|
* Per-send chunk cap in milliseconds for the dual-channel mixer. The streaming
|
|
986
1004
|
* server rejects audio messages longer than 1000 ms (`Input Duration Error`).
|
|
@@ -1114,8 +1132,12 @@ class StreamingTranscriber {
|
|
|
1114
1132
|
searchParams.set("speech_model", this.params.speechModel.toString());
|
|
1115
1133
|
}
|
|
1116
1134
|
if (this.params.languageCode !== undefined) {
|
|
1135
|
+
console.warn("[Deprecation Warning] `languageCode` is deprecated and will be removed in a future release. Please use `languageCodes` instead.");
|
|
1117
1136
|
searchParams.set("language_code", this.params.languageCode);
|
|
1118
1137
|
}
|
|
1138
|
+
if (this.params.languageCodes !== undefined) {
|
|
1139
|
+
searchParams.set("language_codes", JSON.stringify(this.params.languageCodes));
|
|
1140
|
+
}
|
|
1119
1141
|
if (this.params.languageDetection !== undefined) {
|
|
1120
1142
|
searchParams.set("language_detection", this.params.languageDetection.toString());
|
|
1121
1143
|
}
|
|
@@ -1189,12 +1211,81 @@ class StreamingTranscriber {
|
|
|
1189
1211
|
on(event, listener) {
|
|
1190
1212
|
this.listeners[event] = listener;
|
|
1191
1213
|
}
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1214
|
+
/**
|
|
1215
|
+
* Open the streaming session.
|
|
1216
|
+
*
|
|
1217
|
+
* Resolves with the server's `Begin` event once the handshake completes. A
|
|
1218
|
+
* single attempt is bounded by `connectTimeout` (default 1000ms); transient
|
|
1219
|
+
* failures (timeout, network drop, unexpected close) are retried up to
|
|
1220
|
+
* `maxConnectionRetries` times (default 2), waiting `connectionRetryDelay`
|
|
1221
|
+
* (default 500ms) between attempts. Permanent failures (auth, insufficient
|
|
1222
|
+
* funds, malformed config) are not retried.
|
|
1223
|
+
*
|
|
1224
|
+
* Unlike previously, a failed connection now rejects this promise rather
|
|
1225
|
+
* than only invoking the `error` listener — necessary for the caller (and
|
|
1226
|
+
* the retry loop) to observe the failure.
|
|
1227
|
+
*/
|
|
1228
|
+
async connect() {
|
|
1229
|
+
if (this.socket) {
|
|
1230
|
+
throw new Error("Already connected");
|
|
1231
|
+
}
|
|
1232
|
+
const maxRetries = this.params.maxConnectionRetries ?? DEFAULT_MAX_CONNECTION_RETRIES;
|
|
1233
|
+
const retryDelay = this.params.connectionRetryDelay ?? DEFAULT_CONNECTION_RETRY_DELAY_MS;
|
|
1234
|
+
let lastError;
|
|
1235
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
1236
|
+
try {
|
|
1237
|
+
return await this.connectOnce();
|
|
1238
|
+
}
|
|
1239
|
+
catch (err) {
|
|
1240
|
+
lastError = err;
|
|
1241
|
+
const retryable = err.retryable === true;
|
|
1242
|
+
if (!retryable || attempt === maxRetries) {
|
|
1243
|
+
throw err;
|
|
1244
|
+
}
|
|
1245
|
+
console.warn(`Streaming connect attempt ${attempt + 1}/${maxRetries + 1} failed (${err.message}); retrying`);
|
|
1246
|
+
if (retryDelay > 0) {
|
|
1247
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
1248
|
+
}
|
|
1196
1249
|
}
|
|
1250
|
+
}
|
|
1251
|
+
// The loop above always returns or throws; this only satisfies the type
|
|
1252
|
+
// checker that a value is produced on every path.
|
|
1253
|
+
throw lastError ?? new Error("Failed to connect to streaming server");
|
|
1254
|
+
}
|
|
1255
|
+
connectOnce() {
|
|
1256
|
+
return new Promise((resolve, reject) => {
|
|
1197
1257
|
const url = this.connectionUrl();
|
|
1258
|
+
const timeoutMs = this.params.connectTimeout ?? DEFAULT_CONNECT_TIMEOUT_MS;
|
|
1259
|
+
// `settled` flips once this attempt has resolved (`Begin`) or rejected
|
|
1260
|
+
// (timeout / pre-`Begin` close / error). Before it flips the socket
|
|
1261
|
+
// handlers drive this promise; after it flips they revert to normal
|
|
1262
|
+
// runtime dispatch (close / error / message listeners).
|
|
1263
|
+
let settled = false;
|
|
1264
|
+
let timer;
|
|
1265
|
+
const failAttempt = (error) => {
|
|
1266
|
+
if (settled)
|
|
1267
|
+
return;
|
|
1268
|
+
settled = true;
|
|
1269
|
+
if (timer)
|
|
1270
|
+
clearTimeout(timer);
|
|
1271
|
+
this.discardPendingSocket();
|
|
1272
|
+
reject(error);
|
|
1273
|
+
};
|
|
1274
|
+
const succeed = (begin) => {
|
|
1275
|
+
if (settled)
|
|
1276
|
+
return;
|
|
1277
|
+
settled = true;
|
|
1278
|
+
if (timer)
|
|
1279
|
+
clearTimeout(timer);
|
|
1280
|
+
resolve(begin);
|
|
1281
|
+
};
|
|
1282
|
+
if (timeoutMs > 0) {
|
|
1283
|
+
timer = setTimeout(() => {
|
|
1284
|
+
const err = new StreamingError(`Streaming connection timed out after ${timeoutMs}ms`);
|
|
1285
|
+
err.retryable = true;
|
|
1286
|
+
failAttempt(err);
|
|
1287
|
+
}, timeoutMs);
|
|
1288
|
+
}
|
|
1198
1289
|
if (this.token) {
|
|
1199
1290
|
this.socket = factory(url.toString());
|
|
1200
1291
|
}
|
|
@@ -1211,6 +1302,15 @@ class StreamingTranscriber {
|
|
|
1211
1302
|
reason = StreamingErrorMessages[code];
|
|
1212
1303
|
}
|
|
1213
1304
|
}
|
|
1305
|
+
// A close before `Begin` is a failed connection attempt — reject so
|
|
1306
|
+
// connect() can retry (or surface a permanent failure).
|
|
1307
|
+
if (!settled) {
|
|
1308
|
+
const err = new StreamingError(reason || `Streaming connection closed (code=${code})`);
|
|
1309
|
+
err.code = code;
|
|
1310
|
+
err.retryable = isRetryableCloseCode(code);
|
|
1311
|
+
failAttempt(err);
|
|
1312
|
+
return;
|
|
1313
|
+
}
|
|
1214
1314
|
// Stop the flush timer when the socket is gone (server-initiated close,
|
|
1215
1315
|
// network drop, etc.) — otherwise subsequent ticks call send() on a
|
|
1216
1316
|
// closed socket and spam the error listener.
|
|
@@ -1221,25 +1321,37 @@ class StreamingTranscriber {
|
|
|
1221
1321
|
this.listeners.close?.(code, reason);
|
|
1222
1322
|
};
|
|
1223
1323
|
this.socket.onerror = (event) => {
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1324
|
+
const error = event.error ?? new Error(event.message);
|
|
1325
|
+
// A socket error before `Begin` is a failed attempt → reject/retry.
|
|
1326
|
+
if (!settled) {
|
|
1327
|
+
error.retryable = true;
|
|
1328
|
+
failAttempt(error);
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
this.listeners.error?.(error);
|
|
1228
1332
|
};
|
|
1229
1333
|
this.socket.onmessage = ({ data }) => {
|
|
1230
1334
|
const message = JSON.parse(data.toString());
|
|
1231
1335
|
if ("error" in message) {
|
|
1232
1336
|
const err = new StreamingError(message.error);
|
|
1233
1337
|
if ("error_code" in message) {
|
|
1234
|
-
err.code =
|
|
1235
|
-
|
|
1338
|
+
err.code = message.error_code;
|
|
1339
|
+
}
|
|
1340
|
+
// A server error frame before `Begin` fails the attempt; the code
|
|
1341
|
+
// decides whether a retry is worthwhile.
|
|
1342
|
+
if (!settled) {
|
|
1343
|
+
const attemptErr = err;
|
|
1344
|
+
attemptErr.retryable =
|
|
1345
|
+
err.code === undefined ? true : isRetryableCloseCode(err.code);
|
|
1346
|
+
failAttempt(attemptErr);
|
|
1347
|
+
return;
|
|
1236
1348
|
}
|
|
1237
1349
|
this.listeners.error?.(err);
|
|
1238
1350
|
return;
|
|
1239
1351
|
}
|
|
1240
1352
|
switch (message.type) {
|
|
1241
1353
|
case "Begin": {
|
|
1242
|
-
|
|
1354
|
+
succeed(message);
|
|
1243
1355
|
this.listeners.open?.(message);
|
|
1244
1356
|
break;
|
|
1245
1357
|
}
|
|
@@ -1286,6 +1398,20 @@ class StreamingTranscriber {
|
|
|
1286
1398
|
};
|
|
1287
1399
|
});
|
|
1288
1400
|
}
|
|
1401
|
+
/** Tear down a half-open socket from a failed connection attempt. */
|
|
1402
|
+
discardPendingSocket() {
|
|
1403
|
+
if (!this.socket)
|
|
1404
|
+
return;
|
|
1405
|
+
try {
|
|
1406
|
+
if (this.socket.removeAllListeners)
|
|
1407
|
+
this.socket.removeAllListeners();
|
|
1408
|
+
this.socket.close();
|
|
1409
|
+
}
|
|
1410
|
+
catch {
|
|
1411
|
+
// Best-effort cleanup; a half-open socket may throw on close.
|
|
1412
|
+
}
|
|
1413
|
+
this.socket = undefined;
|
|
1414
|
+
}
|
|
1289
1415
|
/**
|
|
1290
1416
|
* Returns a WritableStream that pumps PCM chunks into `sendAudio`. Single-channel
|
|
1291
1417
|
* only — in dual-channel mode use `sendAudio(pcm, { channel })` directly, since
|
|
@@ -1559,6 +1685,16 @@ class StreamingTranscriber {
|
|
|
1559
1685
|
};
|
|
1560
1686
|
this.send(JSON.stringify(message));
|
|
1561
1687
|
}
|
|
1688
|
+
/**
|
|
1689
|
+
* Reset the server's inactivity timer. Only needed when the session was
|
|
1690
|
+
* created with `inactivityTimeout` and no audio is being sent.
|
|
1691
|
+
*/
|
|
1692
|
+
keepAlive() {
|
|
1693
|
+
const message = {
|
|
1694
|
+
type: "KeepAlive",
|
|
1695
|
+
};
|
|
1696
|
+
this.send(JSON.stringify(message));
|
|
1697
|
+
}
|
|
1562
1698
|
send(data) {
|
|
1563
1699
|
if (!this.socket || this.socket.readyState !== this.socket.OPEN) {
|
|
1564
1700
|
throw new Error("Socket is not open for communication");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export * from "../types/asyncapi.generated";
|
|
2
2
|
export * from "../types/realtime";
|
|
3
3
|
export * from "../types/helpers";
|
|
4
|
-
export * from "../types/streaming
|
|
4
|
+
export * from "../types/streaming";
|
|
5
5
|
export * from "../services/realtime/service";
|
|
6
6
|
export * from "../services/streaming/service";
|
|
7
7
|
export * from "../services/streaming/factory";
|