@corti/dictation-web
Version:
Web component for Corti Dictation
289 lines • 19.4 kB
JavaScript
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var _DictationController_instances, _DictationController_cortiClient, _DictationController_webSocket, _DictationController_closeTimeout, _DictationController_callbacks, _DictationController_lastDictationConfig, _DictationController_lastSocketUrl, _DictationController_lastSocketProxy, _DictationController_outboundQueue, _DictationController_socketReady, _DictationController_connectingPromise, _DictationController_connectionGeneration, _DictationController_isConnecting, _DictationController_configHasChanged, _DictationController_doConnect, _DictationController_connectProxy, _DictationController_connectAuth, _DictationController_setupWebSocketHandlers, _DictationController_isSocketOpen, _DictationController_drain;
import { CortiClient, CortiWebSocketProxyClient, } from "@corti/sdk";
import { DEFAULT_DICTATION_CONFIG } from "../constants.js";
import { errorEvent } from "../utils/events.js";
export class DictationController {
constructor(host) {
_DictationController_instances.add(this);
_DictationController_cortiClient.set(this, null);
_DictationController_webSocket.set(this, null);
_DictationController_closeTimeout.set(this, void 0);
_DictationController_callbacks.set(this, void 0);
_DictationController_lastDictationConfig.set(this, null);
_DictationController_lastSocketUrl.set(this, void 0);
_DictationController_lastSocketProxy.set(this, void 0);
_DictationController_outboundQueue.set(this, []);
_DictationController_socketReady.set(this, false);
_DictationController_connectingPromise.set(this, null);
_DictationController_connectionGeneration.set(this, 0);
_DictationController_isConnecting.set(this, false);
this.mediaRecorderHandler = (data) => {
if (__classPrivateFieldGet(this, _DictationController_socketReady, "f") && __classPrivateFieldGet(this, _DictationController_instances, "m", _DictationController_isSocketOpen).call(this)) {
__classPrivateFieldGet(this, _DictationController_webSocket, "f")?.send(data);
__classPrivateFieldGet(this, _DictationController_callbacks, "f")?.onNetworkActivity?.("sent", {
size: data.size,
type: "audio",
});
return;
}
__classPrivateFieldGet(this, _DictationController_outboundQueue, "f").push(data);
};
this.host = host;
host.addController(this);
}
hostDisconnected() {
this.cleanup();
}
async connect(dictationConfig = DEFAULT_DICTATION_CONFIG, callbacks = {}) {
// If a connection attempt is already in progress with the same config, reuse it
// to avoid opening multiple sockets when connect() is called concurrently.
if (__classPrivateFieldGet(this, _DictationController_connectingPromise, "f") && !__classPrivateFieldGet(this, _DictationController_instances, "m", _DictationController_configHasChanged).call(this)) {
return __classPrivateFieldGet(this, _DictationController_connectingPromise, "f");
}
// #isConnecting must be set synchronously before #doConnect runs, because
// #doConnect calls cleanup() which closes the old socket, firing its "close"
// event synchronously. Handlers that check isConnecting() need to see true
// at that point — before #connectingPromise is even assigned.
__classPrivateFieldSet(this, _DictationController_isConnecting, true, "f");
__classPrivateFieldSet(this, _DictationController_connectingPromise, __classPrivateFieldGet(this, _DictationController_instances, "m", _DictationController_doConnect).call(this, dictationConfig, callbacks).finally(() => {
__classPrivateFieldSet(this, _DictationController_isConnecting, false, "f");
__classPrivateFieldSet(this, _DictationController_connectingPromise, null, "f");
}), "f");
return __classPrivateFieldGet(this, _DictationController_connectingPromise, "f");
}
async pause() {
if (__classPrivateFieldGet(this, _DictationController_socketReady, "f") && __classPrivateFieldGet(this, _DictationController_instances, "m", _DictationController_isSocketOpen).call(this)) {
__classPrivateFieldGet(this, _DictationController_webSocket, "f")?.send(JSON.stringify({ type: "flush" }));
__classPrivateFieldGet(this, _DictationController_callbacks, "f")?.onNetworkActivity?.("sent", { type: "flush" });
return;
}
__classPrivateFieldGet(this, _DictationController_outboundQueue, "f").push({ type: "flush" });
}
isConnectionOpen() {
return (__classPrivateFieldGet(this, _DictationController_webSocket, "f") !== null &&
(__classPrivateFieldGet(this, _DictationController_webSocket, "f").readyState === WebSocket.OPEN ||
__classPrivateFieldGet(this, _DictationController_webSocket, "f").readyState === WebSocket.CONNECTING));
}
isConnecting() {
return __classPrivateFieldGet(this, _DictationController_isConnecting, "f");
}
async waitForConnection() {
await __classPrivateFieldGet(this, _DictationController_connectingPromise, "f");
}
async closeConnection(onClose) {
await new Promise((resolve, reject) => {
const oldSocket = __classPrivateFieldGet(this, _DictationController_webSocket, "f");
__classPrivateFieldSet(this, _DictationController_webSocket, null, "f");
if (!oldSocket ||
(oldSocket.readyState !== WebSocket.OPEN &&
oldSocket.readyState !== WebSocket.CONNECTING)) {
__classPrivateFieldSet(this, _DictationController_socketReady, false, "f");
resolve();
return;
}
oldSocket.on("close", (event) => {
if (__classPrivateFieldGet(this, _DictationController_closeTimeout, "f")) {
clearTimeout(__classPrivateFieldGet(this, _DictationController_closeTimeout, "f"));
__classPrivateFieldSet(this, _DictationController_closeTimeout, undefined, "f");
}
if (onClose) {
onClose(event);
}
resolve();
});
const wasReady = __classPrivateFieldGet(this, _DictationController_socketReady, "f");
__classPrivateFieldSet(this, _DictationController_socketReady, false, "f");
oldSocket.on("message", (message) => {
__classPrivateFieldGet(this, _DictationController_callbacks, "f")?.onNetworkActivity?.("received", message);
if (__classPrivateFieldGet(this, _DictationController_callbacks, "f")?.onMessage) {
__classPrivateFieldGet(this, _DictationController_callbacks, "f")?.onMessage(message);
}
// closeConnection() may be called before CONFIG_ACCEPTED arrives (e.g.
// openConnection() followed immediately by closeConnection()). We can't
// use the outbound queue here because #webSocket is already null, so we
// send "end" directly on oldSocket as soon as config is accepted.
if (!wasReady && message.type === "CONFIG_ACCEPTED") {
oldSocket.sendEnd({ type: "end" });
__classPrivateFieldGet(this, _DictationController_callbacks, "f")?.onNetworkActivity?.("sent", { type: "end" });
return;
}
if (message.type === "ended") {
if (__classPrivateFieldGet(this, _DictationController_closeTimeout, "f")) {
clearTimeout(__classPrivateFieldGet(this, _DictationController_closeTimeout, "f"));
__classPrivateFieldSet(this, _DictationController_closeTimeout, undefined, "f");
}
resolve();
return;
}
});
if (wasReady) {
oldSocket.sendEnd({ type: "end" });
__classPrivateFieldGet(this, _DictationController_callbacks, "f")?.onNetworkActivity?.("sent", { type: "end" });
}
__classPrivateFieldSet(this, _DictationController_closeTimeout, window.setTimeout(() => {
reject(new Error("Connection close timeout"));
if (oldSocket?.readyState === WebSocket.OPEN) {
oldSocket.close();
}
}, 10000), "f");
});
}
cleanup() {
var _a;
// Incrementing generation invalidates any in-flight #doConnect awaits,
// causing them to discard their socket and return "superseded".
__classPrivateFieldSet(this, _DictationController_connectionGeneration, (_a = __classPrivateFieldGet(this, _DictationController_connectionGeneration, "f"), _a++, _a), "f");
__classPrivateFieldSet(this, _DictationController_socketReady, false, "f");
if (__classPrivateFieldGet(this, _DictationController_closeTimeout, "f")) {
clearTimeout(__classPrivateFieldGet(this, _DictationController_closeTimeout, "f"));
__classPrivateFieldSet(this, _DictationController_closeTimeout, undefined, "f");
}
if (this.isConnectionOpen()) {
__classPrivateFieldGet(this, _DictationController_webSocket, "f")?.close();
}
__classPrivateFieldSet(this, _DictationController_webSocket, null, "f");
__classPrivateFieldSet(this, _DictationController_cortiClient, null, "f");
__classPrivateFieldSet(this, _DictationController_lastDictationConfig, null, "f");
__classPrivateFieldSet(this, _DictationController_lastSocketUrl, undefined, "f");
__classPrivateFieldSet(this, _DictationController_lastSocketProxy, undefined, "f");
if (__classPrivateFieldGet(this, _DictationController_outboundQueue, "f").length > 0) {
this.host.dispatchEvent(errorEvent(`${__classPrivateFieldGet(this, _DictationController_outboundQueue, "f").length} unsent message(s) were discarded because the configuration changed before the connection was closed`));
}
__classPrivateFieldSet(this, _DictationController_outboundQueue, [], "f");
}
}
_DictationController_cortiClient = new WeakMap(), _DictationController_webSocket = new WeakMap(), _DictationController_closeTimeout = new WeakMap(), _DictationController_callbacks = new WeakMap(), _DictationController_lastDictationConfig = new WeakMap(), _DictationController_lastSocketUrl = new WeakMap(), _DictationController_lastSocketProxy = new WeakMap(), _DictationController_outboundQueue = new WeakMap(), _DictationController_socketReady = new WeakMap(), _DictationController_connectingPromise = new WeakMap(), _DictationController_connectionGeneration = new WeakMap(), _DictationController_isConnecting = new WeakMap(), _DictationController_instances = new WeakSet(), _DictationController_configHasChanged = function _DictationController_configHasChanged() {
return (JSON.stringify(this.host._dictationConfig) !==
JSON.stringify(__classPrivateFieldGet(this, _DictationController_lastDictationConfig, "f")) ||
this.host._socketUrl !== __classPrivateFieldGet(this, _DictationController_lastSocketUrl, "f") ||
JSON.stringify(this.host._socketProxy) !==
JSON.stringify(__classPrivateFieldGet(this, _DictationController_lastSocketProxy, "f")));
}, _DictationController_doConnect = async function _DictationController_doConnect(dictationConfig, callbacks) {
const newConnection = __classPrivateFieldGet(this, _DictationController_instances, "m", _DictationController_configHasChanged).call(this) || !this.isConnectionOpen();
if (newConnection) {
this.cleanup();
__classPrivateFieldSet(this, _DictationController_lastDictationConfig, this.host._dictationConfig || null, "f");
__classPrivateFieldSet(this, _DictationController_lastSocketUrl, this.host._socketUrl, "f");
__classPrivateFieldSet(this, _DictationController_lastSocketProxy, this.host._socketProxy, "f");
const generation = __classPrivateFieldGet(this, _DictationController_connectionGeneration, "f");
const socket = this.host._socketUrl || this.host._socketProxy
? await __classPrivateFieldGet(this, _DictationController_instances, "m", _DictationController_connectProxy).call(this, dictationConfig)
: await __classPrivateFieldGet(this, _DictationController_instances, "m", _DictationController_connectAuth).call(this, dictationConfig);
// If cleanup() was called while we were awaiting (e.g. config changed),
// the generation counter will have advanced — discard this stale socket.
if (__classPrivateFieldGet(this, _DictationController_connectionGeneration, "f") !== generation) {
socket.close();
return "superseded";
}
__classPrivateFieldSet(this, _DictationController_webSocket, socket, "f");
__classPrivateFieldGet(this, _DictationController_callbacks, "f")?.onNetworkActivity?.("sent", {
configuration: dictationConfig,
type: "config",
});
}
__classPrivateFieldSet(this, _DictationController_callbacks, callbacks, "f");
__classPrivateFieldGet(this, _DictationController_instances, "m", _DictationController_setupWebSocketHandlers).call(this, callbacks);
if (!newConnection && this.isConnectionOpen()) {
__classPrivateFieldSet(this, _DictationController_socketReady, true, "f");
__classPrivateFieldGet(this, _DictationController_instances, "m", _DictationController_drain).call(this);
}
return newConnection;
}, _DictationController_connectProxy = async function _DictationController_connectProxy(dictationConfig) {
const proxyOptions = this.host._socketProxy || {
url: this.host._socketUrl || "",
};
if (!proxyOptions.url) {
throw new Error("Proxy URL is required when using proxy client");
}
return await CortiWebSocketProxyClient.transcribe.connect({
// setting to "false" to have CONFIG_* message in network activity events
awaitConfiguration: false,
configuration: dictationConfig,
proxy: proxyOptions,
});
}, _DictationController_connectAuth = async function _DictationController_connectAuth(dictationConfig) {
if (!this.host._authConfig && !this.host._accessToken) {
throw new Error("Auth configuration or access token is required to connect");
}
// Use authConfig if available, otherwise create one from accessToken
const auth = this.host._authConfig || {
accessToken: this.host._accessToken || "",
refreshAccessToken: () => ({
accessToken: this.host._accessToken || "",
}),
};
__classPrivateFieldSet(this, _DictationController_cortiClient, new CortiClient({
auth,
environment: this.host._region,
tenantName: this.host._tenantName,
}), "f");
return await __classPrivateFieldGet(this, _DictationController_cortiClient, "f").transcribe.connect({
// setting to "false" to have CONFIG_* message in network activity events
awaitConfiguration: false,
configuration: dictationConfig,
});
}, _DictationController_setupWebSocketHandlers = function _DictationController_setupWebSocketHandlers(callbacks) {
if (!__classPrivateFieldGet(this, _DictationController_webSocket, "f")) {
throw new Error("WebSocket not initialized");
}
__classPrivateFieldGet(this, _DictationController_webSocket, "f").on("message", (message) => {
if (message.type === "CONFIG_ACCEPTED") {
__classPrivateFieldSet(this, _DictationController_socketReady, true, "f");
__classPrivateFieldGet(this, _DictationController_instances, "m", _DictationController_drain).call(this);
}
callbacks.onNetworkActivity?.("received", message);
if (callbacks.onMessage) {
callbacks.onMessage(message);
}
});
__classPrivateFieldGet(this, _DictationController_webSocket, "f").on("error", (event) => {
__classPrivateFieldSet(this, _DictationController_socketReady, false, "f");
if (callbacks.onError) {
callbacks.onError(event);
}
});
__classPrivateFieldGet(this, _DictationController_webSocket, "f").on("close", (event) => {
__classPrivateFieldSet(this, _DictationController_socketReady, false, "f");
if (callbacks.onClose) {
callbacks.onClose(event);
}
});
}, _DictationController_isSocketOpen = function _DictationController_isSocketOpen() {
return (__classPrivateFieldGet(this, _DictationController_webSocket, "f") !== null && __classPrivateFieldGet(this, _DictationController_webSocket, "f").readyState === WebSocket.OPEN);
}, _DictationController_drain = function _DictationController_drain() {
if (!__classPrivateFieldGet(this, _DictationController_socketReady, "f") ||
!__classPrivateFieldGet(this, _DictationController_instances, "m", _DictationController_isSocketOpen).call(this) ||
__classPrivateFieldGet(this, _DictationController_outboundQueue, "f").length === 0) {
return;
}
while (__classPrivateFieldGet(this, _DictationController_outboundQueue, "f").length > 0 && __classPrivateFieldGet(this, _DictationController_instances, "m", _DictationController_isSocketOpen).call(this)) {
const item = __classPrivateFieldGet(this, _DictationController_outboundQueue, "f").shift();
if (item === undefined) {
break;
}
if (item instanceof Blob) {
__classPrivateFieldGet(this, _DictationController_webSocket, "f").send(item);
__classPrivateFieldGet(this, _DictationController_callbacks, "f")?.onNetworkActivity?.("sent", {
size: item.size,
type: "audio",
});
continue;
}
__classPrivateFieldGet(this, _DictationController_webSocket, "f").send(JSON.stringify(item));
__classPrivateFieldGet(this, _DictationController_callbacks, "f")?.onNetworkActivity?.("sent", {
type: item.type,
});
}
};
//# sourceMappingURL=dictation-controller.js.map