UNPKG

@salutejs/client

Version:

Модуль взаимодействия с виртуальным ассистентом

1,087 lines (1,080 loc) 54.5 kB
import { d as createNanoEvents, b as __awaiter, c as __generator, _ as __assign, a as __rest } from './common-ba25e019.js'; import { b as createTransport, d as convertFieldValuesToString, e as createClient, D as DEFAULT_APP, i as isDefaultApp, B as BASIC_SMART_APP_COMMANDS_TYPES, g as getTime, p as promiseTimeout, f as getAnswerForRequestPermissions, S as STATE_UPDATE_TIMEOUT } from './client-ed11c12c.js'; import { M as MessageNames, d as createProtocol } from './sdk-7c3cb15e.js'; import { i as iosSilentModePatch, c as createVoicePlayer } from './voicePlayer-705b49ef.js'; var isAudioSupported = typeof window !== 'undefined' && (window.AudioContext || window.webkitAudioContext); /** * Возвращает новый инстанс AudioContext или ошибку * @param options AudioContextOptions * @returns AudioContext */ function createAudioContext(options) { if (window.AudioContext) { return new AudioContext(options); } if (window.webkitAudioContext) { // eslint-disable-next-line new-cap return new window.webkitAudioContext(); } throw new Error('Audio not supported'); } var _a = createNanoEvents(), on = _a.on, emit = _a.emit; var audioContext; /** * При помощи вызова функции из аргумента, возвращает, готовый к воспроизведению звука, AudioContext. * Всегда возвращает один и тот же AudioContext * @param onReady Функция, в аргумент которой будет возвращен AudioContext */ var resolveAudioContext = function (onReady) { if (!audioContext) { var isSafari_1 = navigator.vendor.search('Apple') >= 0; var context_1 = createAudioContext(); audioContext = { context: context_1, ready: !isSafari_1 && context_1.state === 'running', on: on, }; context_1.onstatechange = function () { if (audioContext.context.state === 'running') { audioContext.ready = true; emit('ready'); } }; /// Контекст может быть не готов для использования сразу после создания /// Если попробовать что-то воспроизвести в этом контексте - звука не будет if (!audioContext.ready) { var handleClick_1 = function () { document.removeEventListener('click', handleClick_1); iosSilentModePatch.initialize(); if (isSafari_1) { /// проигрываем тишину, т.к нужно что-то проиграть, /// чтобы сафари разрешил воспроизводить звуки в любой момент в этом контексте var oscillator = audioContext.context.createOscillator(); oscillator.frequency.value = 0; oscillator.connect(audioContext.context.destination); oscillator.start(0); oscillator.stop(0.5); } if (audioContext.context.state === 'suspended') { /// Developers who write games, WebRTC applications, or other websites that use the Web Audio API /// should call context.resume() after the first user gesture (e.g. a click, or tap) /// https://sites.google.com/a/chromium.org/dev/audio-video/autoplay audioContext.context.resume(); } }; /// чтобы сделать контекст готовым к использованию (воспроизведению звука), /// необходимо событие от пользователя (только не touch) document.addEventListener('click', handleClick_1); } } if (audioContext.ready) { onReady && onReady(audioContext.context, function () { return iosSilentModePatch.destroy(); }); } else { var unsubscribe_1 = on('ready', function () { onReady(audioContext.context, function () { return iosSilentModePatch.destroy(); }); unsubscribe_1(); }); } }; /** * Понижает sample rate c inSampleRate до значения outSampleRate и преобразует Float32Array в ArrayBuffer * @param buffer Аудио * @param inSampleRate текущий sample rate * @param outSampleRate требуемый sample rate * @returns Аудио со значением sample rate = outSampleRate */ var downsampleBuffer = function (buffer, inSampleRate, outSampleRate) { if (outSampleRate > inSampleRate) { throw new Error('downsampling rate show be smaller than original sample rate'); } var sampleRateRatio = inSampleRate / outSampleRate; var newLength = Math.round(buffer.length / sampleRateRatio); var result = new Int16Array(newLength); var offsetResult = 0; var offsetBuffer = 0; while (offsetResult < result.length) { var nextOffsetBuffer = Math.round((offsetResult + 1) * sampleRateRatio); var accum = 0; var count = 0; for (var i = offsetBuffer; i < nextOffsetBuffer && i < buffer.length; i++) { accum += buffer[i]; count++; } result[offsetResult] = Math.min(1, accum / count) * 0x7fff; offsetResult++; offsetBuffer = nextOffsetBuffer; } return result.buffer; }; var TARGET_SAMPLE_RATE = 16000; var IS_FIREFOX = typeof window !== 'undefined' && navigator.userAgent.toLowerCase().indexOf('firefox') > -1; var IS_SAFARI = typeof window !== 'undefined' && /^((?!chrome|android).)*safari/i.test(navigator.userAgent); var context; var processor; var analyser = null; var destination; /** * Преобразует stream в чанки (кусочки), и передает их в cb, * будет это делать, пока не будет вызвана функция остановки * @param stream Аудио-поток * @param cb callback, куда будут переданы чанки из потока * @returns Функция, вызов которой остановит передачу чанков */ var createAudioRecorder = function (stream, cb, targetSampleRate, useAnalyser) { return new Promise(function (resolve) { var state = 'inactive'; var input; var stop = function () { if (state === 'inactive') { return; } state = 'inactive'; stream.getTracks().forEach(function (track) { track.stop(); }); input.disconnect(); }; var start = function () { if (state !== 'inactive') { throw new Error("Can't start not inactive recorder"); } state = 'recording'; if (!context) { context = createAudioContext({ // firefox не умеет выравнивать samplerate, будем делать это самостоятельно sampleRate: IS_FIREFOX ? undefined : targetSampleRate, }); } input = context.createMediaStreamSource(stream); if (!processor) { processor = context.createScriptProcessor(2048, 1, 1); } if (!analyser && useAnalyser) { analyser = context.createAnalyser(); analyser.fftSize = 1024; } var listener = function (e) { var buffer = e.inputBuffer.getChannelData(0); var data = downsampleBuffer(buffer, context.sampleRate, targetSampleRate); var last = state === 'inactive'; // отсылаем только чанки где есть звук voiceData > 0, т.к. // в safari первые несколько чанков со звуком пустые if (!IS_SAFARI || new Uint8Array(data).some(function (voiceData) { return voiceData > 0; })) { var analyserArray = null; if (analyser) { analyserArray = new Uint8Array(analyser.frequencyBinCount); analyser === null || analyser === void 0 ? void 0 : analyser.getByteFrequencyData(analyserArray); } cb(data, analyserArray, last); resolve(stop); } if (last) { processor.removeEventListener('audioprocess', listener); } }; processor.addEventListener('audioprocess', listener); input.connect(processor); if (analyser) { input.connect(analyser); } if (!destination) { destination = context.createMediaStreamDestination(); } processor.connect(destination); }; start(); }); }; /** * Запрашивает у браузера доступ к микрофону и резолвит Promise, если разрешение получено. * После получения разрешения, чанки с голосом будут передаваться в cb - пока не будет вызвана функция из результата. * @param cb Callback, куда будут передаваться чанки с голосом пользователя * @returns Promise, который содержит функцию прерывающую слушание */ var createNavigatorAudioProvider = function (cb, useAnalyser, options) { if (options === void 0) { options = {}; } return navigator.mediaDevices .getUserMedia({ audio: { echoCancellation: options === null || options === void 0 ? void 0 : options.echoCancellation, noiseSuppression: options === null || options === void 0 ? void 0 : options.noiseSuppression, }, }) .then(function (stream) { return createAudioRecorder(stream, cb, options.targetSampleRate || TARGET_SAMPLE_RATE, useAnalyser); }) .catch(function (err) { if (window.location.protocol === 'http:') { throw new Error('Audio is supported only on a secure connection'); } throw err; }); }; /** * Возвращает объект, позволяющий получать запись голоса пользователя и управлять ею. * @param createAudioProvider Источник голоса * @returns Api для запуска и остановки слушания */ var createVoiceListener = function (createAudioProvider) { if (createAudioProvider === void 0) { createAudioProvider = createNavigatorAudioProvider; } var _a = createNanoEvents(), emit = _a.emit, on = _a.on; var stopRecord; var status = 'stopped'; var cancelableToken = { current: false }; var stop = function () { cancelableToken.current = true; cancelableToken = { current: false }; status = 'stopped'; stopRecord === null || stopRecord === void 0 ? void 0 : stopRecord(); emit('status', 'stopped'); }; var listen = function (handleVoice) { cancelableToken = { current: false }; var capturedToken = cancelableToken; status = 'started'; emit('status', 'started'); return createAudioProvider(function (data, analyser, last) { return handleVoice(new Uint8Array(data), analyser, last); }) .then(function (recStop) { stopRecord = recStop; }) .then(function () { if (capturedToken.current === true || status === 'stopped') { stopRecord(); } else { status = 'listen'; emit('status', 'listen'); } }) .catch(function (err) { status = 'stopped'; emit('status', 'stopped'); throw err; }); }; return { listen: listen, stop: stop, on: on, get status() { return status; }, }; }; /** Фильтр тишины */ var filterEmptyChunks = function (chunksOriginal) { return chunksOriginal.reduce(function (acc, chunkOriginal) { var chunk = chunkOriginal.filter(function (int) { return int; }); if (chunk.length) { acc.push(chunk); } return acc; }, []); }; var createVoice = function (client, settings, emit, /// пока onReady не вызван, треки не воспроизводятся /// когда случится onReady, очередь треков начнет проигрываться onReady) { var useAnalyser = false; var voicePlayer; var listener = createVoiceListener(function (cb) { return createNavigatorAudioProvider(cb, useAnalyser); }); var subscriptions = []; var appInfoDict = {}; var mesIdQueue = []; /** в процессе инициализации слушания */ var isRecognizeInitializing = false; /** проигрывается/не проигрывается озвучка */ var isPlaying = false; /** id сообщения, после проигрывания которого, нужно активировать слушание */ var autolistenMessageId = null; /** id сообщения со звуком, отправляемое в данный момент */ var currentVoiceMessageId = null; /** стримит поток чанков. Если метода нет, то стриминг не идёт */ var streaming = null; /** Уничтожает аудио-контекст */ var destroyAudioContext = null; /** Останавливает слушание голоса, отправляет cancel. Возвращает true - если слушание было активно */ var stopVoice = function (sendCancel) { if (sendCancel === void 0) { sendCancel = true; } autolistenMessageId = null; streaming = null; if (sendCancel && currentVoiceMessageId) { client.sendCancel(currentVoiceMessageId); } currentVoiceMessageId = null; if (listener.status === 'listen') { listener.stop(); return true; } return false; }; /** Останавливает слушание и воспроизведение */ var stop = function () { // здесь важен порядок остановки голоса stopVoice(); voicePlayer === null || voicePlayer === void 0 ? void 0 : voicePlayer.stop(); }; var recognize = function (_a) { var _b = _a === void 0 ? {} : _a, begin = _b.begin, messageName = _b.messageName, _c = _b.isAutoListening, isAutoListening = _c === void 0 ? false : _c; return __awaiter(void 0, void 0, void 0, function () { var unsubscribe_1; return __generator(this, function (_d) { switch (_d.label) { case 0: if (stopVoice()) { return [2 /*return*/]; } if (isPlaying) { voicePlayer === null || voicePlayer === void 0 ? void 0 : voicePlayer.stop(); return [2 /*return*/]; } if (settings.current.disableListening) { return [2 /*return*/]; } if (!(listener.status === 'stopped' && !isRecognizeInitializing)) return [3 /*break*/, 2]; isRecognizeInitializing = true; unsubscribe_1 = listener.on('status', function () { isRecognizeInitializing = false; unsubscribe_1(); }); return [4 /*yield*/, client.init().catch(function (error) { isRecognizeInitializing = false; throw error; })]; case 1: _d.sent(); return [2 /*return*/, client.createVoiceStream(function (_a) { var sendVoice = _a.sendVoice, messageId = _a.messageId; begin === null || begin === void 0 ? void 0 : begin.forEach(function (chunk) { return sendVoice(new Uint8Array(chunk), false); }); currentVoiceMessageId = messageId; return listener.listen(function (chunk, analyser, last) { if (analyser) { emit({ voiceAnalyser: { data: analyser } }); } sendVoice(chunk, last, messageName); }); }, { source: { sourceType: isAutoListening ? 'autoListening' : 'lavashar', }, })]; case 2: return [2 /*return*/]; } }); }); }; /** * Стримит переданные чанки звука в VPS. * При отсутствии last=true через 3 секунды тишины отправляет Cancel. * Если было активно слушание или проигрывание – останавливает. * * @param chunks одноканальные, sampleRate: 16000 * @param last последние чанки этого стрима? * @param messageName указать, если чанки для шазама */ var streamVoice = function (chunks, last, messageName) { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: chunks = filterEmptyChunks(chunks); if (streaming === null || streaming === void 0 ? void 0 : streaming(chunks, last)) { return [2 /*return*/]; } stopVoice(); if (isPlaying) { voicePlayer === null || voicePlayer === void 0 ? void 0 : voicePlayer.stop(); } if (!(!isRecognizeInitializing && chunks.length)) return [3 /*break*/, 2]; isRecognizeInitializing = true; return [4 /*yield*/, client.init()]; case 1: _a.sent(); return [2 /*return*/, client.createVoiceStream(function (_a) { var messageId = _a.messageId, sendVoice = _a.sendVoice; return __awaiter(void 0, void 0, void 0, function () { var cancelTimeoutId; return __generator(this, function (_b) { cancelTimeoutId = -1; isRecognizeInitializing = false; currentVoiceMessageId = messageId; streaming = function (ch, l) { clearTimeout(cancelTimeoutId); ch.forEach(function (chunk) { return sendVoice(new Uint8Array(chunk), l, messageName); }); if (l) { streaming = null; } else { cancelTimeoutId = setTimeout(function () { if (streaming) { stopVoice(); } }, 3000); } return true; }; streaming(chunks, last); return [2 /*return*/]; }); }); }, { source: { sourceType: 'lavashar', }, })]; case 2: return [2 /*return*/]; } }); }); }; /** * Отправляет готовые чанки звука в VPS. * Чанки считаются завершёнными (сообщение отправляется с last=true). * Если было активно слушание или проигрывание – останавливает. * * @param chunks одноканальные, sampleRate: 16000 * @param messageName указать, если чанки для шазама */ var sendVoice = function (chunks, messageName) { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: chunks = filterEmptyChunks(chunks); stopVoice(); if (isPlaying) { voicePlayer === null || voicePlayer === void 0 ? void 0 : voicePlayer.stop(); } if (!(!isRecognizeInitializing && chunks.length)) return [3 /*break*/, 2]; isRecognizeInitializing = true; return [4 /*yield*/, client.init()]; case 1: _a.sent(); return [2 /*return*/, client.createVoiceStream(function (_a) { var messageId = _a.messageId, sendVoiceStream = _a.sendVoice; isRecognizeInitializing = false; currentVoiceMessageId = messageId; chunks.forEach(function (chunk) { return sendVoiceStream(new Uint8Array(chunk), true, messageName); }); return Promise.resolve(); }, { source: { sourceType: 'lavashar', }, })]; case 2: return [2 /*return*/]; } }); }); }; /** * Активирует слушание голоса. * Если было активно слушание или проигрывание - останавливает, слушание в этом случае не активируется. * * @param begin одноканальные чанки, sampleRate: 16000 – будут отправлены перед голосом пользователя */ var listen = function (_a, isAutoListening) { var _b = _a === void 0 ? {} : _a, begin = _b.begin; return recognize({ begin: begin, isAutoListening: isAutoListening }); }; /** * Активирует распознавание музыки. * Если было активно слушание или проигрывание – останавливает */ var shazam = function () { return recognize({ messageName: MessageNames.MTT, isAutoListening: false }); }; if (isAudioSupported) { resolveAudioContext(function (context, destroy) { /// создаем плеер только если поддерживается аудио /// и только когда готов AudioContext voicePlayer = createVoicePlayer(context, { startVoiceDelay: 1 }); destroyAudioContext = destroy; // начало проигрывания озвучки subscriptions.push(voicePlayer.on('play', function (mesId) { isPlaying = true; emit({ emotion: 'talk' }); emit({ tts: { status: 'start', messageId: Number(mesId), appInfo: appInfoDict[mesId] } }); })); subscriptions.push(voicePlayer.on('stop', function (mesId) { client.sendMute(Number(mesId)); })); // окончание проигрывания озвучки subscriptions.push(voicePlayer.on('end', function (mesId) { isPlaying = false; emit({ emotion: 'idle' }); emit({ tts: { status: 'stop', messageId: Number(mesId), appInfo: appInfoDict[mesId] } }); if (mesId === autolistenMessageId) { listen().catch(function (error) { // eslint-disable-next-line no-console console.error(error); }); } // очистка сохраненных appInfo и messageId var idx = 0; do { delete appInfoDict[mesIdQueue[0]]; } while (mesIdQueue[idx++] !== mesId && mesIdQueue.length > idx); mesIdQueue.splice(0, idx); })); // оповещаем о готовности к воспроизведению звука onReady === null || onReady === void 0 ? void 0 : onReady(); }); } subscriptions.push( // обработка входящей озвучки client.on('voice', function (data, message) { if (settings.current.disableDubbing) { return; } voicePlayer === null || voicePlayer === void 0 ? void 0 : voicePlayer.append(data, message.messageId.toString(), message.last === 1); }), // статусы слушания речи listener.on('status', function (status) { emit({ listener: { status: status } }); if (status === 'listen') { voicePlayer === null || voicePlayer === void 0 ? void 0 : voicePlayer.setActive(false); emit({ emotion: 'listen' }); } else if (status === 'stopped') { voicePlayer === null || voicePlayer === void 0 ? void 0 : voicePlayer.setActive(!settings.current.disableDubbing); emit({ asr: { text: '' }, emotion: 'idle' }); } }), // активация автослушания client.on('systemMessage', function (systemMessage, originalMessage) { var autoListening = systemMessage.auto_listening; var messageId = originalMessage.messageId.toString(); if (typeof systemMessage.app_info !== 'undefined') { appInfoDict[messageId] = systemMessage.app_info; mesIdQueue.push(messageId); } if (autoListening) { /// если озвучка включена - сохраняем mesId чтобы включить слушание после озвучки /// если озвучка выключена - включаем слушание сразу if (settings.current.disableDubbing === false) { autolistenMessageId = messageId; } else { listen({}, autoListening).catch(function (error) { // eslint-disable-next-line no-console console.error(error); }); } } }), client.on('status', function (_a) { var code = _a.code; if (code < 0) { stopVoice(false); } }), client.on('stt', function (_a, originalMessage) { var _b; var text = _a.text, response = _a.response; var listening = listener.status === 'listen' && !settings.current.disableListening; if (text) { var last = originalMessage.last === 1; if (last || listening) { emit({ asr: { mid: originalMessage.messageId, text: text.data || '', last: last, }, }); } if (last) { stopVoice(false); } } if (response) { var decoderResultField = response.decoderResultField, errorResponse = response.errorResponse; var last = !!(decoderResultField && (decoderResultField === null || decoderResultField === void 0 ? void 0 : decoderResultField.isFinal)); if ((last || listening) && ((_b = decoderResultField === null || decoderResultField === void 0 ? void 0 : decoderResultField.hypothesis) === null || _b === void 0 ? void 0 : _b.length)) { emit({ asr: { mid: originalMessage.messageId, text: decoderResultField.hypothesis[0].normalizedText || '', last: last, }, }); } if (last || errorResponse) { stopVoice(false); } } }), client.on('musicRecognition', function (response, originalMessage) { var _a; emit({ mtt: { response: response, mid: originalMessage.messageId } }); if (((_a = response.decoderResultField) === null || _a === void 0 ? void 0 : _a.isFinal) || response.errorResponse) { stopVoice(false); } }), settings.on('change-request', function (nextSettings) { var disableDubbing = nextSettings.disableDubbing, disableListening = nextSettings.disableListening; /// Важен порядок обработки флагов слушания и озвучки — /// сначала слушание, потом озвучка disableListening && stopVoice(); // Такой вызов необходим, чтобы включая озвучку она тут же проигралась (при её наличии), и наоборот settings.current.disableDubbing !== disableDubbing && (voicePlayer === null || voicePlayer === void 0 ? void 0 : voicePlayer.setActive(!disableDubbing)); })); return { destroy: function () { stopVoice(); voicePlayer === null || voicePlayer === void 0 ? void 0 : voicePlayer.setActive(false); subscriptions.splice(0, subscriptions.length).map(function (unsubscribe) { return unsubscribe(); }); destroyAudioContext === null || destroyAudioContext === void 0 ? void 0 : destroyAudioContext(); }, listen: listen, shazam: shazam, sendVoice: sendVoice, streamVoice: streamVoice, stop: stop, stopPlaying: function () { voicePlayer === null || voicePlayer === void 0 ? void 0 : voicePlayer.stop(); }, toggleAnalyser: function (enable) { useAnalyser = enable; }, }; }; var createMutexedObject = function (initialObject) { var _a = createNanoEvents(), on = _a.on, emit = _a.emit; var object = __assign({}, initialObject); var nextObject = {}; var mode = 'released'; var tryApply = function () { if (mode === 'released') { var prevObject_1 = object; object = __assign(__assign({}, prevObject_1), nextObject); var isObjectChanged = Object.keys(nextObject).some(function (name) { return nextObject[name] !== prevObject_1[name]; }); if (isObjectChanged) { emit('changed', object, prevObject_1); } } }; var lock = function () { mode = 'locked'; }; var release = function () { mode = 'released'; tryApply(); }; var change = function (setts) { nextObject = __assign(__assign({}, nextObject), setts); emit('change-request', setts); tryApply(); }; var current = {}; Object.keys(initialObject).forEach(function (prop) { Object.defineProperty(current, prop, { get: function () { return object[prop]; }, }); }); return { on: on, lock: lock, release: release, change: change, current: current, }; }; var createMutexSwitcher = function (_a, initialDeps) { var lock = _a.lock, release = _a.release; var deps = __assign({}, initialDeps); return { change: function (nextDeps) { deps = __assign(__assign({}, deps), nextDeps); if (Object.values(deps).every(function (dep) { return dep; })) { release(); } else { lock(); } }, }; }; var createAssistant = function (_a) { var _b; var getMeta = _a.getMeta, getInitialMeta = _a.getInitialMeta, getVoiceMeta = _a.getVoiceMeta, checkCertUrl = _a.checkCertUrl, configuration = __rest(_a, ["getMeta", "getInitialMeta", "getVoiceMeta", "checkCertUrl"]); var _c = createNanoEvents(), on = _c.on, emit = _c.emit; // default_character отправляется в мета при отправке InitialSettings var defaultCharacter = 'sber'; // хеш [messageId]: requestId, где requestId - пользовательский ид экшена var requestIdMap = {}; // mid для последнего отправленного/принятого сообщения (кроме server_action) var lastMid = 0; var subscriptions = []; var backgroundApps = {}; var settings = createMutexedObject({ disableDubbing: configuration.settings.dubbing === -1, disableListening: false, sendTextAsSsml: false, }); var settingsSwitcher = createMutexSwitcher(settings, { isListenerStopped: true, isVoicePlayerEnded: true }); // готов/не готов воспроизводить озвучку var voiceReady = false; // текущий апп var app = { info: DEFAULT_APP }; var sdkMeta = { theme: 'dark' }; var metaProvider = function (additionalMeta) { return __awaiter(void 0, void 0, void 0, function () { var appState, _a, current_app, getBackgroundAppsMeta, background_apps; return __generator(this, function (_b) { switch (_b.label) { case 0: if (!(app !== null && app.info.frontendType === 'WEB_APP' && app.getState)) return [3 /*break*/, 2]; return [4 /*yield*/, promiseTimeout(app.getState(), STATE_UPDATE_TIMEOUT).catch(function () { // eslint-disable-next-line no-console console.error('App-state wasn`t resolved, timeout had been expired'); return undefined; })]; case 1: _a = _b.sent(); return [3 /*break*/, 3]; case 2: _a = undefined; _b.label = 3; case 3: appState = _a; current_app = { app_info: app.info, state: appState || {}, }; getBackgroundAppsMeta = function () { return __awaiter(void 0, void 0, void 0, function () { var apps, backgroundAppsIds, backgroundAppsMeta; return __generator(this, function (_a) { switch (_a.label) { case 0: apps = __assign({}, backgroundApps); backgroundAppsIds = Object.keys(apps); backgroundAppsMeta = []; return [4 /*yield*/, Promise.all(backgroundAppsIds.map(function (applicationId) { return __awaiter(void 0, void 0, void 0, function () { var _a, getState; return __generator(this, function (_b) { _a = apps[applicationId].getState, getState = _a === void 0 ? function () { return Promise.resolve({}); } : _a; return [2 /*return*/, promiseTimeout(getState(), STATE_UPDATE_TIMEOUT).then(function (state) { return state; }, function () { return ({}); })]; }); }); })).then(function (results) { results.forEach(function (appResult, index) { var state = appResult; var applicationId = backgroundAppsIds[index]; backgroundAppsMeta.push({ app_info: apps[applicationId].appInfo, state: state, }); }); })]; case 1: _a.sent(); return [2 /*return*/, backgroundAppsMeta]; } }); }); }; return [4 /*yield*/, getBackgroundAppsMeta()]; case 4: background_apps = _b.sent(); return [2 /*return*/, convertFieldValuesToString(__assign(__assign(__assign(__assign({}, sdkMeta), { time: getTime(), current_app: current_app, background_apps: background_apps }), (additionalMeta || {})), (getMeta ? getMeta() : {})))]; } }); }); }; var transport = createTransport({ createWS: (_b = configuration.fakeVps) === null || _b === void 0 ? void 0 : _b.createFakeWS, checkCertUrl: checkCertUrl, }); var protocol = createProtocol(transport, __assign(__assign({}, configuration), { getInitialMeta: typeof getInitialMeta !== 'undefined' ? function () { return getInitialMeta().then(function (meta) { return convertFieldValuesToString(__assign(__assign({}, meta), { default_character: defaultCharacter })); }); } : function () { return convertFieldValuesToString({ default_character: defaultCharacter, }); }, // пока голос не готов, выключаем озвучку settings: __assign(__assign({}, configuration.settings), { dubbing: -1 }) })); var client = createClient(protocol, metaProvider, { getVoiceMeta: function () { return (getVoiceMeta ? convertFieldValuesToString(getVoiceMeta()) : {}); }, }); var voice = createVoice(client, settings, function (event) { if (typeof event.tts !== 'undefined') { emit('tts', event.tts); settingsSwitcher.change({ isVoicePlayerEnded: event.tts.status === 'stop' }); return; } if (typeof event.listener !== 'undefined') { settingsSwitcher.change({ isListenerStopped: event.listener.status === 'stopped' }); } emit('assistant', event); }, function () { voiceReady = true; if (!settings.current.disableDubbing) { protocol.changeSettings({ dubbing: 1 }); } }); /** завершает текущий апп */ var closeApp = function (closing) { if (closing === void 0) { closing = app.info; } // переключить на дефолтный апп if (closing.applicationId === app.info.applicationId) { /// выглядит как нарушение логики, /// но с точки зрения апи - ок /// иначе потребителю нужно знать про DEFAULT_APP app = { info: DEFAULT_APP, }; } if (!isDefaultApp(closing)) { emit('app', { type: 'close', app: closing }); } }; /** отправляет текст */ var sendText = function (text, shouldSendDisableDubbing, additionalMeta) { if (shouldSendDisableDubbing === void 0) { shouldSendDisableDubbing = false; } voice.stop(); return client.sendText(text, settings.current.sendTextAsSsml, shouldSendDisableDubbing, additionalMeta); }; /** отправляет server_action */ var sendServerAction = function (serverAction, messageName, requestId, actionApp, mode) { if (messageName === void 0) { messageName = 'SERVER_ACTION'; } if (requestId === void 0) { requestId = undefined; } if (actionApp === void 0) { actionApp = app.info; } client.sendServerAction(serverAction, actionApp, messageName, mode).then(function (messageId) { if (requestId && messageId) { requestIdMap[messageId.toString()] = requestId; } }); }; /** отправляет ответ на запрос доступа к местоположению и пр. меты */ var sendMetaForPermissionRequest = function (requestMessageId, appInfo, items) { return __awaiter(void 0, void 0, void 0, function () { var _a, props, data, meta; return __generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, getAnswerForRequestPermissions(requestMessageId, appInfo, items)]; case 1: _a = _b.sent(), props = __rest(_a.meta, []), data = __rest(_a, ["meta"]); return [4 /*yield*/, metaProvider()]; case 2: meta = _b.sent(); client.sendData(__assign({}, data), 'SERVER_ACTION', __assign(__assign({}, meta), convertFieldValuesToString(props))); return [2 /*return*/]; } }); }); }; subscriptions.push(protocol.on('ready', function () { return emit('vps', { type: 'ready' }); })); // пока voicePlayer не доступен, включение озвучки не будет отправлено subscriptions.push(settings.on('changed', function (nextSettings, prevSettings) { if (nextSettings.disableDubbing !== prevSettings.disableDubbing) { voiceReady && protocol.changeSettings({ dubbing: nextSettings.disableDubbing ? -1 : 1 }); } })); // при неудачном переподключении к сокету subscriptions.push(transport.on('error', function (error) { voice.stop(); protocol.clearQueue(); emit('vps', { type: 'error', error: error }); })); // обработка исходящих сообщений subscriptions.push(protocol.on('outcoming', function (message) { if (message.text || message.voice) { /// не прерываем множественные ответы для сервер-экшенов /// прервем, если получим карточку или бабл в ответ lastMid = message.messageId; } emit('vps', { type: 'outcoming', message: message }); })); // обработка ошибок subscriptions.push(protocol.on('error', function (error) { emit('error', error); })); // оповещение о статусах subscriptions.push(client.on('status', function (status, _a) { var messageId = _a.messageId; emit('status', status, messageId); })); // история на запрос GetHistoryRequest subscriptions.push(client.on('history', function (history) { emit('history', history); })); // обработка входящих команд, и событий аппа subscriptions.push(client.on('systemMessage', function (systemMessage, originalMessage) { if (originalMessage.messageName === 'ANSWER_TO_USER') { var _a = systemMessage.answerId, answerId = _a === void 0 ? 0 : _a, activate_app_info = systemMessage.activate_app_info, _b = systemMessage.items, items = _b === void 0 ? [] : _b, mesAppInfo = systemMessage.app_info; var isChatApp = mesAppInfo && mesAppInfo.frontendType === 'CHAT_APP'; var isDialog = mesAppInfo && mesAppInfo.frontendType === 'DIALOG'; var isAppChanged = mesAppInfo && mesAppInfo.applicationId !== app.info.applicationId; if ( // DIALOG не попадает в current_app !isDialog && isAppChanged && /// игнорируем activate_app_info для чатапов /// по-умолчанию activate_app_info: true (isChatApp || activate_app_info !== false)) { emit('app', { type: 'run', app: mesAppInfo }); } if (isDialog && isAppChanged && app.info.applicationId !== DEFAULT_APP.applicationId) { emit('app', { type: 'run', app: DEFAULT_APP }); } // cancel для множественных ответов if (answerId >= 2 && lastMid > originalMessage.messageId) { client.sendCancel(originalMessage.messageId); } // последним сообщением считаем, только если пришли карточки/баблы if (lastMid < originalMessage.messageId && items.findIndex(function (_a) { var bubble = _a.bubble, card = _a.card; return bubble || card; }) >= 0) { lastMid = originalMessage.messageId; } if (items.length) { var _loop_1 = function (i) { var command = items[i].command; if (typeof command !== 'undefined') { window.setTimeout(function () { return emit('command', command); }); if (command.type === 'start_music_recognition') { voice.shazam(); return { value: void 0 }; } if (command.type === 'request_permissions' && mesAppInfo) { sendMetaForPermissionRequest(originalMessage.messageId, mesAppInfo, command.permissions); return { value: void 0 }; } if (command.type === 'action') { emit('actionCommand', { type: 'command', command: command, appInfo: mesAppInfo, }); } if (mesAppInfo && BASIC_SMART_APP_COMMANDS_TYPES.includes(command.type)) { // эмитим все команды, т.к бывают фоновые команды emit('app', { type: 'command', command: __assign(__assign({}, command), { sdk_meta: { mid: originalMessage.messageId.toString(), requestId: requestIdMap[originalMessage.messageId.toString()], } }), app: mesAppInfo, }); } if (command.type === 'close_app' && !isDialog) { closeApp(mesAppInfo); } } }; for (var i = 0; i < items.length; i++) { var state_1 = _loop_1(i); if (typeof state_1 === "object") return state_1.value; } } emit('vps', { type: 'incoming', systemMessage: systemMessage, originalMessage: originalMessage }); } })); // прокидывает команды backgroundApp'ов в их подписчики on('app', function (event) { var _a; if (event.type === 'command') { var backgroundAppOnCommand = (_a = backgroundApps[event.app.applicationId]) === null || _a === void 0 ? void 0 : _a.commandsSubscribers; if (Array.isArray(backgroundAppOnCommand)) { backgroundAppOnCommand.forEach(function (onCommand) { var _a; onCommand(event.command, (_a = event.command.sdk_meta) === null || _a === void 0 ? void 0 : _a.mid); }); } } }); /** уничтожает ассистент, очищает подписки */ var destroy = function () { voice.destroy(); client.destroy(); protocol.destroy(); subscriptions.splice(0, subscriptions.length).map(function (unsubscribe) { return unsubscribe(); }); }; /** запускает ассистент (приветствие) */ var start = function (_a) { var _b = _a === void 0 ? {} : _a, _c = _b.disableGreetings, disableGreetings = _c === void 0 ? false : _c, _d = _b.initPhrase, initPhrase = _d === void 0 ? undefined : _d, _e = _b.isFirstSession, isFirstSession = _e === void 0 ? false : _e; return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_f) { switch (_f.label) { case 0: if (!(!disableGreetings && isDefaultApp(app.info))) return [3 /*break*/, 2]; return [4 /*yield*/, client.sendOpenAssistant({ isFirstSession: isFirstSession })]; case 1: _f.sent(); _f.label = 2; case 2: if (initPhrase) { return [2 /*return*/, client .sendText(initPhrase) .then(function (messageId) { return (messageId ? client.waitForAnswer(messageId) : undefined); })]; } return [2 /*return*/, undefined]; } }); }); }; return { get activeApp() { return !isDefaultApp(app.info) ? app.info : null; }, get settings() { return Object.create(Object.getPrototypeOf(settings.current), Object.getOwnPropertyDescriptors(settings.current)); }, destroy: destroy, closeApp: closeApp, listen: voice.listen, shazam: voice.shazam, sendServerAction: sendServerAction, getHistoryRequest: protocol.getHistoryRequest, sendText: sendText, sendVoice: voice.sendVoice, streamVoice: voice.streamVoice, start: start, stop: function () { voice.stop(); if (lastMid !== 0) { client.sendCancel(lastMid); } setTimeout(function () { protocol.clearQueue(); transport.close(); }); }, stopTts: voice.stopPlaying, stopVoice: voice.stop, emit: emit, on: on, changeConfiguration: protocol.changeConfiguration, changeSettings: settings.change, changeSdkMeta: function (nextSdkMeta) { sdkMeta = __assign(__assign({}, sdkMeta), nextSdkMeta); }, reconnect: protocol.reconnect, get protocol() { return protocol; }, setActiveApp: function (info, getState) { app = { info: info, getState: getState }; }, addBackgroundApp: function (_a) { var appInfo = _a.appInfo, getState = _a.getState; backgroundApps[appInfo.applicationId] = { appInfo: appInfo, getState: getState, commandsSubscribers: [], }; var remove = function () { delete backgroundApps[appInfo.applicationId]; }; var onCommand = function (subscriber) { var _a; (_a = backgroundApps[appInfo.applicationId]) === null || _a === void 0 ? void 0 : _a.commandsSubscribers.push(subscriber); return { clearSubscribers: function () { if (backgroundApps[appInfo.applicationId]) { backgroundApps[appInfo.applicationId].commandsSubscribers = []; } }, }; }; var sendServerActionToBackgroundApp = function (serverAction, messageName, requestId) { var _a; if (messageName === void 0) { messageName = 'S