{"id":19489387,"url":"https://github.com/salute-developers/salutejs-client","last_synced_at":"2025-08-11T07:04:13.148Z","repository":{"id":38086383,"uuid":"481669758","full_name":"salute-developers/salutejs-client","owner":"salute-developers","description":null,"archived":false,"fork":false,"pushed_at":"2025-06-04T10:12:50.000Z","size":5535,"stargazers_count":7,"open_issues_count":15,"forks_count":9,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-07-31T16:40:57.297Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/salute-developers.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-04-14T16:14:34.000Z","updated_at":"2025-06-16T08:22:50.000Z","dependencies_parsed_at":"2024-03-29T14:24:55.419Z","dependency_job_id":"90af4d64-9acc-4ddf-a476-9860c70ad556","html_url":"https://github.com/salute-developers/salutejs-client","commit_stats":{"total_commits":112,"total_committers":6,"mean_commits":"18.666666666666668","dds":0.4285714285714286,"last_synced_commit":"fa4d2b2a89b2e694832d60faecd9cf43d5f00e78"},"previous_names":[],"tags_count":103,"template":false,"template_full_name":null,"purl":"pkg:github/salute-developers/salutejs-client","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salute-developers%2Fsalutejs-client","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salute-developers%2Fsalutejs-client/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salute-developers%2Fsalutejs-client/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salute-developers%2Fsalutejs-client/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/salute-developers","download_url":"https://codeload.github.com/salute-developers/salutejs-client/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salute-developers%2Fsalutejs-client/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":269844089,"owners_count":24484132,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-11T02:00:10.019Z","response_time":75,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-10T21:08:22.665Z","updated_at":"2025-08-11T07:04:13.122Z","avatar_url":"https://github.com/salute-developers.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![npm ui](https://img.shields.io/npm/v/@salutejs/client)](https://www.npmjs.com/package/@salutejs/client)\n\n\u003cimg src=\"https://user-images.githubusercontent.com/982072/97004635-0888a900-1546-11eb-8f25-283a0693608e.png\" height=\"150px\" width=\"150px\"\u003e\n\n\nAssistant Client — это инструмент для локального тестирования и отладки [Сanvas App](https://smartapp-code.sberdevices.ru/documentation/#/docs/ru/methodology/research/canvasapp) c виртуальным ассистентом. Он реализован в виде JavaScript протокола, который эмулирует среду Android и вызывает нативные методы. Такой подход не требует от разработчика наличия физических устройств и позволяет запустить виртуального ассистента через браузер.\n\n## Оглавление\n   * [Конфигурация](#Конфигурация)\n     * [Аутентификация](#Аутентификация)\n     * [Требования](#требования-к-устройствам)\n     * [Установка](#Установка)\n     * [Использование](#пример-использования)\n   * [API](#API)\n     * [createAssistant](#createAssistant)\n     * [createSmartappDebugger](#createSmartappDebugger)\n     * [AssistantClient](#assistantclient)\n   * [Форматы объектов](#форматы-объектов)\n     * [AssistantAppState](#AssistantAppState)\n     * [AssistantServerAction](#AssistantServerAction)\n     * [AssistantCharacterCommand](#AssistantCharacterCommand)\n     * [AssistantNavigationCommand](#AssistantNavigationCommand)\n     * [AssistantInsetsCommand](#AssistantInsetsCommand)\n     * [AssistantThemeCommand](#AssistantThemeCommand)\n     * [AssistantVisibilityCommand](#AssistantVisibilityCommand)\n     * [AssistantSmartAppError](#AssistantSmartAppError)\n     * [AssistantSmartAppCommand](#AssistantSmartAppCommand)\n   * [Пульт](#пульт)\n     * [Нажатие кнопок на пульте](#нажатие-кнопок-на-пульте)\n     * [Навигация по смартапу](#навигация-по-смартапу)\n   * [Утилиты для тестирования](#утилиты-для-тестирования)\n     * [Имитация команд ассистента](#имитация-команд-ассистента)\n     * [Запись лога сообщений](#запись-лога-сообщений)\n     * [Воспроизведение лога сообщений](#воспроизведение-лога-сообщений)\n* [FAQ](#faq)\n\n____\n\n## Конфигурация\n\n### Аутентификация\n\nДля работы с Assistant Client необходимо:\n\n1. Установить сертификат Минцифры если он не установлен.\n2. Завести аккаунт в [SmartApp Studio](https://developers.sber.ru/studio).\n3. Создать смартап типа [Сanvas App](https://developers.sber.ru/docs/ru/va/canvas/title-page).\n4. Получить токен. Для этого необходимо перейти в *Настройки профиля* \u003e пункт *Настройки сервисов* \u003e пункт *SmartApp* \u003e вкладка *Эмулятор* \u003e опция *Скопировать ключ*.\n5. Передать полученный токен в методе `createSmartappDebugger` в параметре `token`.\n\n\n### Требования к устройствам\n\nСмартапы должны корректно отображаться на разных устройствах (SberBox, SberPortal и др). Для этого необходимо проверять смартап на следующих разрешениях: 559x568, 768x400, 959x400, 1920x1080. Настроить эти разрешения можно на [вкладке Devices Chrome](https://developers.google.com/web/tools/chrome-devtools/device-mode#custom).\n\n\n### Установка\n\nДля установки Assistant Client выполните следующую команду:\n\n```sh\n$ npm i @salutejs/client\n```\n\n### Пример использования\n\n```typescript\n// Функция createSmartappDebugger используется в development среде. В production среде необходимо использовать createAssistant.\nimport { createAssistant, createSmartappDebugger } from '@salutejs/client';\n\nconst initialize = (getState, getRecoveryState) =\u003e {\n    if (process.env.NODE_ENV === 'development') {\n        return createSmartappDebugger({\n            // Токен из Кабинета разработчика\n            token: 'token',\n            // Пример фразы для запуска смартапа\n            initPhrase: 'Хочу попкорн',\n            // Текущее состояние смартапа\n            getState,\n            // Состояние смартапа, с которым он будет восстановлен при следующем запуске\n            getRecoveryState,\n            // Необязательные параметры панели, имитирующей панель на реальном устройстве\n            nativePanel: {\n                // Стартовый текст в поле ввода пользовательского запроса\n                defaultText: 'Покажи что-нибудь',\n                // Позволяет включить вид панели, максимально приближенный к панели на реальном устройстве\n                screenshotMode: false,\n                // Атрибут `tabindex` поля ввода пользовательского запроса\n                tabIndex: -1,\n            },\n        });\n    }\n\n\t  // Только для среды production\n    return createAssistant({ getState, getRecoveryState });\n}\n\n...\n\nconst assistant = initialize(() =\u003e state, () =\u003e recoveryState);\nassistant.on('data', (command) =\u003e {\n    // Подписка на команды ассистента, в т.ч. команда инициализации смартапа.\n    // Ниже представлен пример обработки голосовых команд \"ниже\"/\"выше\"\n    if (command.navigation) {\n        switch(command.navigation.command) {\n            case 'UP':\n                window.scrollTo(0, 0);\n                break;\n            case 'DOWN':\n                window.scrollTo(0, 1000);\n                break;\n        }\n    }\n});\n\nconst handleOnClick = () =\u003e {\n    // Отправка сообщения ассистенту с фронтенд.\n    // Структура может меняться на усмотрение разработчика, в зависимости от бэкенд\n    assistant.sendData({ action: { action_id: 'some_action_name', parameters: { param: 'some' } } });\n};\n\nconst handleOnRefreshClick = () =\u003e {\n    // Отправка сообщения бэкенду с возможностью подписки на ответ.\n    // В обработчик assistant.on('data') сообщение не передается\n    const unsubscribe = assistant.sendAction(\n        { action_id: 'some_action_name', parameters: { param: 'some' } },\n        (data: { type: string; payload: Record\u003cstring, unknown\u003e }) =\u003e {\n            // Обработка данных, переданных от бэкенд\n            unsubscribe();\n        },\n        (error: { code: number; description: string }) =\u003e {\n            // Обработка ошибки, переданной от бэкенд\n        });\n}\n```\n\n### Альтернативное подключение\n\nAssitant Client доступен для подключения через `\u003cscript\u003e`.\nВерсию assistant сlient можно поменять в src. Доступ к API осуществляется через глобальную переменную `assistant`.\n\nПример, для разработки и отладки в браузере:\n```html\n\u003cscript src=\"https://unpkg.com/@salutejs/client@stable/umd/assistant.development.min.js\"\u003e\u003c/script\u003e\n\u003cscript\u003e\n  const client = assistant.createSmartappDebugger({\n            // Токен из Кабинета разработчика\n            token: 'token',\n            // Пример фразы для запуска смартапа\n            initPhrase: 'Хочу попкорн',\n            // Текущее состояние смартапа\n            getState: () =\u003e ({}),\n            // Состояние смартапа, с которым он будет восстановлен при следующем запуске\n            getRecoveryState: () =\u003e ({}),\n        });\n\u003c/script\u003e\n```\n\nПример, для использования на устройствах:\n```html\n\u003cscript src=\"https://unpkg.com/@salutejs/client@stable/umd/assistant.production.min.js\"\u003e\u003c/script\u003e\n\u003cscript\u003e\n  const client = assistant.createAssistant({ getState: () =\u003e ({}), getRecoveryState: () =\u003e ({}), });\n\u003c/script\u003e\n```\n\n____\n\n\n## API\n\n### `createAssistant`\n\nСоздает экземпляр [`AssistantClient`](#assistantclient) для запуска виртуального ассистента. Используется на устройствах в production среде.\n\n| Параметр         | Обязательный | Описание                                                                   |\n| :--------------- | :------: | :------------------------------------------------------------------------- |\n| getState         | Да           |  Функция, которая возвращает актуальное состояние смартапа                 |\n| getRecoveryState | Нет          |  Функция, которая сохраняет состояние смартапа на момент последнего закрытия |\n\n\n\n### `createSmartappDebugger`\n\nСоздает экземпляр [`AssistantClient`](#assistantclient) и добавляет на экран браузера панель с голосовым ассистентом (подобно устройствам). Панель ассистента находится в нижней части отрисованного экрана и позволяет отправлять виртуальному ассистенту следующие типы сообщений:\n* текстовые сообщения через текстовое поле ввода;\n* голосовые сообщения через кнопку «Салют».\n\n`createSmartappDebugger` используется для локальной отладки и разработки в development среде.\n\n| Параметр         | Обязательный | Описание                                                                   |\n| :--------------- | :----------: | :------------------------------------------------------------------------- |\n| token            | Да           |  Токен из SmartApp Studio                                             |\n| initPhrase       | Да           |  Фраза, которая запускает смартап                                |\n| getState         | Да           |  Функция, которая возвращает актуальное состояние смартапа                 |\n| getRecoveryState | Нет          |  Функция, которая сохраняет состояние смартапа на момент последнего закрытия |\n| settings         | Нет          |  Объект [настроек ассистента](#AssistantSettings)                          |\n| nativePanel      | Нет          |  Объект настроек панели ассистента                          |\n| surface          | Нет          |  Строка, название поверхности. Возможные значения: `SBERBOX` (SberBox), `STARGATE` (SberPortal), `SATELLITE` (SberBox Top) `SBOL` (приложение СберБанк Онлайн), `COMPANION` (приложение Салют), `TV` (Салют ТВ), `TV_HUAWEI` (Huawei Vision), `TIME` (SberBox Time)  |\n\n\u003e Максимальная частота отправки сообщений при отладке приложения с помощью `createSmartappDebugger` — два запроса в секунду.\n\u003e \n\u003e При превышении ограничения ассистент ответит ошибкой:\n\u003e \n\u003e ```json\n\u003e {\n\u003e     \"code\": -1008,\n\u003e     \"description\": \"Виртуальные ассистенты тоже иногда ломаются. Предлагаю немного подождать, пока меня починят (код ошибки -1008)\"\n\u003e }\n\u003e ```\n\n#### Свойства [Settings](#AssistantSettings)\n\n| Свойство | Значения     | По умолчанию | Описание                      |\n| :------- | :----------: | :----------: | :---------------------------- |\n| dubbing  | true / false | true         | Озвучивание ответа ассистента |\n\n#### Свойства `nativePanel`\n\nВсе свойства являются необязательными.\n\n| Свойство        | Тип значения  | По умолчанию | Описание                                                                                |\n| :-------------- | :-----------: | :----------: | :-------------------------------------------------------------------------------------- |\n| defaultText     | string        | Покажи что-нибудь | Стартовый текст в поле ввода пользовательского запроса                          |\n| screenshotMode  | boolean       | false        | Позволяет включить вид панели, максимально приближенный к панели на реальном устройстве |\n| tabIndex        | number        | -1           | Атрибут `tabindex` поля ввода пользовательского запроса                                 |\n\n### AssistantClient\n\n### cancelTts(): void\n\nОпциональный метод. Вызывается со стороны смартапа для остановки озвучивания ответа (pronounceText). Имеет значение undefined, если устройство не поддерживает данную функцию.\n\n#### close(): void\n\nВызывается со стороны смартапа для завершения работы.\n\n#### getInitialData(): [AssistantCommands](#AssistantCommands)[]\n\nВозвращает данные, полученные при инициализации смартапа. Передает в Assistant Client текущего ассистента, а не ассистента, выставленного по умолчанию.\nЕсли при запуске смартапа не вызвать команду `getInitialData()`, то команды из `appInitialData` будут отправляться в `on('data')`.\n\n#### getRecoveryState(): unknown\n\nВозвращает состояние, сохраненное при закрытии смартапа. Устройство запоминает последнее состояние, которое возвращает функция `getRecoveryState` при инициализации Assistant Client.\n\n#### subscribeToCommand(commandType: AssistantClientCommandType, cb: (command: AssistantClientCommand) =\u003e void): () =\u003e void\n\nПозволяет подписаться на любой тип команд, приходящих в смартап. Возвращает метод отписки.\n\n#### on('start', cb: () =\u003e void): void\n\nОсуществляет подписку на событие готовности ассистента к работе.\n\n#### on('data', cb: (data: [AssistantCharacterCommand](#AssistantCharacterCommand) | [AssistantNavigationCommand](#AssistantNavigationCommand) | [AssistantInsetsCommand](#AssistantInsetsCommand) | [AssistantThemeCommand](#AssistantThemeCommand) | [AssistantSmartAppError](#AssistantSmartAppError) | [AssistantSmartAppCommand](#AssistantSmartAppCommand)) =\u003e {}): void\n\nОсуществляет подписку на событие получения данных с бэкенда. Если при запуске смартапа не была вызвана команда `getInitialData()`, то получает команды из `appInitialData`.\n\n#### on('tts', cb: ({ state: 'start' | 'stop', owner: boolean }) =\u003e void).\n\nВыполняет подписку на события начала и окончания озвучки.\u003cbr\u003e\n`state` - состояние озвучки, начало или окончание.\u003cbr\u003e\n`owner` - флаг принадлежности озвучки текущему смартапу.\n\n\n#### sendAction({ action_id: string; parameters: Record\u003cstring, unknown\u003e }, params?: { name?: string; requestId?: string, mode?: 'background'|'foreground' }) =\u003e void\n\nПередает ошибки и обработчики ответа от бэкенда. \u003cbr\u003e\n`sendAction` — отправляет server-action и типизирует сообщения data и error.\u003cbr\u003e\n`clear()` — делает отписку от сообщений бэкенда. Это означает, что сообщения не будут переданы в обработчик `assistant.on('data')`\n\n\nПример:\n```ts\nimport { AssistantSmartAppCommand } from '@salutejs/client';\n\ninterface SomeBackendMessage extends AssistantSmartAppCommand['smart_app_data'] {\n  type: 'target_action',\n  payload: {\n    data: ['some_data'],\n  },\n}\n\nconst unsubscribe = assistant.sendAction\u003cSomeBackendMessage\u003e({ action_id: 'some_action_name', parameters: { someParam: 'some_value' } },\n  ({ payload }) =\u003e {\n    // обработка payload.data\n    unsubscribe();\n  }, (error) =\u003e {});\n```\n\n#### sendData({ action: [AssistantServerAction](#AssistantServerAction), name?: string, requestId?: string, mode?: 'background'|'foreground' }, onData?: data: [AssistantCharacterCommand](#AssistantCharacterCommand) | [AssistantNavigationCommand](#AssistantNavigationCommand) | [AssistantSmartAppError](#AssistantSmartAppError) | [AssistantSmartAppCommand](#AssistantSmartAppCommand)) =\u003e void): () =\u003e void\n\nОтправляет события с фронтенда на бэкенд через ассистента.\nПервый параметр (обязательный) принимает данные для отправки.\nВторой параметр (опциональный) принимает обработчик ответа (на переданные первым параметром данные). В этом случае в `on('data')` ответ не приходит.\nВозвращает функцию, вызов которой отменяет обработчик ответа.\n\nПример с обработкой ответа:\n```ts\n...\n\nconst unsubscribe = assistant.sendData({ action: { action_id: 'some_action_name' } }, (data: command) =\u003e {\n  if (data.type === 'smart_app_data' \u0026\u0026 data.smart_app_data.type === 'target_action') {\n    unsubsribe();\n    ... // обработка команды\n  }\n});\n\n```\n\n#### setGetState(nextGetState: () =\u003e [AssistantAppState](#AssistantAppState)): void\n\nПодменяет callback, который возвращает актуальное состояние смартапа.\n\n#### setGetRecoveryState(nextGetRecoveryState: () =\u003e unknown)\n\nПодменяет callback, который возвращает объект, доступный только при следующем запуске смартапа. Данные приходят при вызове `getRecoveryState`.\n\n\n____\n\n\n## Форматы объектов\n\n### `AssistantAppState`\n\nОбъект `AssistantAppState` — текущее состояние смартапа, которое не хранится в платформе или сценарии. Каждый раз, когда пользователь начинает говорить, Assistant Client вызывает `getState`, чтобы получить и передать в бэкенд состояние экрана пользователя.\nТо, что происходит на экране у пользователя и как он взаимодействует со смартапом в конкретный момент времени — ответственность смартапа. Assistant Client в данном случае — это буфер, который только передает состояние платформе или сценарию.\n\n\n```typescript\ninterface AssistantAppState {\n  // Любые данные, которые могут потребоваться в бэкенде для принятия решений\n  [key: string]: unknown;\n  item_selector?: {\n    ignored_words?: string[];\n    // Список соответствий между голосовыми командами и действиями в приложении\n    items: AssistantViewItem[];\n  };\n}\n\ninterface AssistantViewItem {\n  // Уникальный (в рамках items) порядковый номер элемента, который назначается смартапом\n  number?: number;\n  // Уникальный id элемента\n  id?: string;\n  // Ключевая фраза, которая должна приводить к данному действию\n  title?: string;\n  // Фразы-синонимы, которые должны приводить к данному действию\n  aliases?: string[];\n  // Проксирование action обратно на бэкенд\n  server_action?: AssistantServerAction;\n  // Выполнение действия от имени пользователя\n  action?: Action | { type: string };\n  // Дополнительные данные для бэкенд\n  [key: string]: unknown;\n}\n```\n\nНапример, когда пользователь говорит «Покажи 1», бэкенд должен понимать, что скрывается за единицей (то есть, какой элемент у пользователя пронумерован этой цифрой). Ниже пример состояния, который позволяет понять бэкенду, что, называя «1», пользователь хочет чипсы.\n\n```js\n{\n  item_selector: {\n    ignored_words: [\"покажи\"],\n    items: [\n      { title: 'Сладкий попкорн' },\n      { title: 'Соленый попкорн' },\n      { title: 'Чипсы', number: 1 },\n      { title: 'Начос', number: 2 },\n      { title: 'Кола', number: 3 }\n    ]\n  }\n}\n```\n\n### `AssistantServerAction`\n\nОбъект `AssistantServerAction` — это любое сообщение, которое отправляется с фронтенда на бэкенд. Оно может быть привязано к ui-элементу и приходить с бэкенд, или формироваться самостоятельно фронтовой частью при обработке событий внутри WebView смартапа.\n\n```typescript\ninterface AssistantServerAction {\n  // Тип Server Action\n  action_id: string;\n  // Любые параметры\n  parameters?: Record\u003cstring, unknown\u003e;\n}\n```\n\n### `AssistantCharacterCommand`\n\nОбъект `AssistantCharacterCommand` — информирует смартап о текущем персонаже (Сбер, Афина или Джой). Персонаж может быть изменен в любой момент по инициативе пользователя. Поэтому разработчик может дополнительно добавить обработку таких изменений.\n\n```typescript\ninterface AssistantCharacterCommand {\n  type: \"character\";\n  character: {\n    id: \"sber\" | \"eva\" | \"joy\";\n  };\n  sdk_meta: {\n    requestId: string;\n  };\n}\n```\n\n### `AssistantNavigationCommand`\n\nОбъект `AssistantNavigationCommand` — команда навигации пользователя по смартапу (вперед, назад, дальше и т. д.). В платформе виртуального ассистента есть стандартные фразы, которые приходят и обрабатываются одинаково для всех смартапов.\n\n```typescript\ninterface AssistantNavigationCommand {\n  // Тип команды\n  type: \"navigation\";\n  // Навигационная команда (направление навигации)\n  navigation: { command: \"UP\" | \"DOWN\" | \"LEFT\" | \"RIGHT\" | \"FORWARD\" };\n  sdk_meta: {\n    requestId: string;\n  };\n}\n```\n\n### `AssistantInsetsCommand`\n\nОбъект `AssistantInsetsCommand` — команда, которая сообщает смартапу о том, что поверх него будет отображен нативный UI и его размеры (например, высота шторки с КПСС, саджесты, или клавиатура). Размеры нужно соблюдать, чтобы не было наложения нативных UI элементов и контента смартапа.\n\n- `insets` — отступы от краев экрана;\n- `dynamic_insets` — размеры нативного UI;\n- `minimum_static_insets` — минимальные значения размеров нативного UI;\n- `maximum_static_insets` — максимальные значения размеров нативного UI.\n\n```typescript\ninterface AssistantInsetsCommand {\n  type: 'insets' | 'dynamic_insets' | 'minimum_static_insets' | 'maximum_static_insets';\n  insets: {\n    left: number;    // px\n    top: number;     // px\n    right: number;   // px\n    bottom: number;  // px\n  };\n}\n```\n\n### `AssistantThemeCommand`\n\nОбъект `AssistantThemeCommand` - команда, которая сообщает смартапу текущую тему платформы — тёмная или светлая. По умолчанию нужно использовать тёмную тему.\n\n```typescript\ninterface AssistantThemeCommand {\n   type: 'theme';\n   theme: {\n      name: 'dark' | 'light'\n   }\n}\n```\n\n### `AssistantVisibilityCommand`\n\nОбъект `AssistantVisibilityCommand` — команда, которая сообщает о состоянии приложения: развернуто оно или нет. Параметр `hidden` указывает, что разработчику навыка необходимо остановить звук и/или видео. Параметр `visible` указывает, что воспроизведение звука и/или видео можно продолжить.\n\n```typescript\ninterface AssistantVisibilityCommand {\n    type: 'visibility';\n    visibility: 'visible' | 'hidden';\n}\n```\n\n### `AssistantSmartAppError`\n\nОбъект `AssistantSmartAppError` — это уведомление об ошибке.\n\n```typescript\ninterface AssistantSmartAppError {\n  type: 'smart_app_error';\n  smart_app_error: {\n    code: number;\n    description: string;\n  };\n}\n```\n\n### `AssistantSmartAppCommand`\n\nОбъект `AssistantSmartAppCommand` — это команда передачи смартапу любых данных с бэкенда.\n\n```typescript\ninterface AssistantSmartAppCommand {\n  // Тип команды\n  type: \"smart_app_data\";\n  smart_app_data: {\n    type: string;\n    // Любые данные, которые нужны смартапу\n    payload: Record\u003cstring, unknown\u003e;\n  };\n  sdk_meta: {\n    requestId: string;\n  };\n}\n```\n\n____\n\n\n## Пульт\n\n### Нажатие кнопок на пульте\n\nДля получения и обработки нажатия кнопок на пульте от SberBox необходимо подписаться на события нажатия клавиш клавиатуры. Пример ниже:\n\n```javascript\nwindow.addEventListener('keydown', (event) =\u003e {\n  switch(event.code) {\n    case 'ArrowDown':\n      // вниз\n      break;\n     case 'ArrowUp':\n      // вверх\n      break;\n     case 'ArrowLeft':\n      // влево\n      break;\n     case 'ArrowRight':\n      // вправо\n      break;\n     case 'Enter':\n      // ок\n     break;\n  }\n});\n```\n\n### Навигация по страницам смартапа\n\nДля корректной обработки кнопки `back` и навигации по страницам смартапа необходимо построить историю переходов, используя `History API`. Например, подписываемся на `window.onpopstate` и реализуем изменение страницы в обработчике этого события. Когда хотим выполнить изменение страницы, вызываем `window.history.pushState`:\n\n```typescript\n\nconst [page, setPage] = useState\u003cstring\u003e('previous');\n\nconst handleNext = () =\u003e {\n  window.history.pushState({ page: 'next' }, ''); // инициируем переход на следующую страницу\n}\n\nuseEffect(() =\u003e {\n  window.history.replaceState({ page: 'previous' }, ''); // устанавливаем текущую страницу\n  window.onpopstate = ({ state }) =\u003e {\n    setPage(state.page); // выполняем переход на заданную страницу: next - по вызову handleNext или previous - по нажатию кнопки back\n  }\n}, []);\n\n```\n____\n\n\n## Утилиты для тестирования\n\n### Имитация команд ассистента\n\nДля имитации команд от ассистента используйте утилиту `createAssistantHostMock`. Ниже приведен пример использования.\n\n```typescript\nimport { createAssistantHostMock } from '@salutejs/client';\n\nconst ITEMS = [\n  {\n    id: 1,\n    title: 'Купить молоко',\n    number: 1,\n  },\n  {\n    id: 2,\n    title: 'Купить хлеб',\n    number: 2,\n  },\n];\n\ndescribe('Мой список дел', () =\u003e {\n  it('По клику на чекбокс - ожидаем экшен \"done\" c заголовком выбранного элемента', (done) =\u003e {\n    cy.visit('/')\n      .window()\n      .then((window) =\u003e {\n        const mock = createAssistantHostMock({ context: window });\n        const selected = ITEMS[1];\n        mock.onReady(() =\u003e {\n          // эмулируем инициализационную команду от бэкенда со списком задач\n          mock.receiveCommand({\n            type: 'smart_app_data',\n            action: {\n              type: 'init',\n              notes: [...ITEMS],\n            },\n          })\n          .then(() =\u003e\n            // ожидаем вызов assistantClient.sendData\n            mock.waitAction(() =\u003e\n                // эмулируем отметку выполнения пользователем, который должен вызвать sendData({ action: { type: 'done } })\n                window.document.getElementById(`checkbox-note-${selected.id}`).click(),\n            ),\n          )\n          .then(({ action, state }) =\u003e {\n            expect(action.type).to.equal('done'); // ожидаем экшен data_note\n            expect(action.payload?.title).to.equal(selected.title); // ожидаем в параметрах title экшена\n            expect(state?.item_selector.items).to.deep.equal(ITEMS); // ожидаем отправку списка в стейте\n            done();\n          });\n        });\n      });\n  });\n});\n```\n\n`createAssistantHostMock` можно вызывать только при использовании [`createAssistant`](#createAssistant). Например, при использовании `cypress` функция инициализации ассистента может выглядеть следующим образом:\n\n```typescript\nimport { createAssistant, createSmartappDebugger } from '@salutejs/client';\n\nconst initializeAssistant = (getState: AssistantAppState) =\u003e {\n    if (process.env.NODE_ENV === 'development' \u0026\u0026 window.Cypress == null) {\n        return createSmartappDebugger({\n            token: process.env.REACT_APP_TOKEN ?? '',\n            initPhrase: `Запусти ${process.env.REACT_APP_SMARTAPP}`,\n            getState,\n        });\n    }\n\n    if (window.cypress) {\n      window.appInitialData = [];\n    }\n\n    return createAssistant({ getState });\n};\n```\n\n#### addActionHandler(actionType: string, handler: (action: AssistantServerAction) =\u003e void): void\n\nПодписка на экшены фронтенда с определенным type, который передается первым параметром.\n\n#### removeActionHandler(actionType: string): void\n\nОтмена подписки от экшенов фронтенда.\n\n#### receiveCommand(command: AssistantClientCommand): Promise\u003cvoid\u003e\n\nЭмуляция команды, полученной от бэкенда. Команда приходит подписчикам `AssistantClient.onData`.\n\n#### waitAction(onAction?: () =\u003e void): Promise\u003c{ state: AssistantAppState; action: AssistantServerAction; name?: string; requestId?: string; mode?: 'background'|'foreground' }\u003e\n\nПолучение `promise`, который будет разрезолвлен при следующем вызове `AssistantClient.sendData`\n\n#### onReady(cb: () =\u003e void): void\n\nПодписка на события готовности утилиты. Параметр `cb` будет вызван по готовности к работе.\n\n\n### Запись лога сообщений\n\nВ режиме разработки есть возможность записать и скачать лог сообщений.\nУправление записью осуществляется кнопками `start` и `stop`. Кнопка `save` сохранит файл с логом в загрузки браузера. Пример активации панели управления записью лога:\n\n```typescript\nimport { createSmartappDebugger } from '@salutejs/client';\n\nconst assistant = createSmartappDebugger({\n    token: process.env.REACT_APP_TOKEN ?? '',\n    initPhrase: `Запусти ${process.env.REACT_APP_SMARTAPP}`,\n    getState,\n    enableRecord: true, // активируем функцию записи лога\n    recordParams: {\n      defaultActive: true, // включать запись при старте приложения (по-умолчанию = true)\n    }\n  });\n```\n\n### Воспроизведение лога сообщений\n\nПример пошагового воспроизведения лога сообщений. Входящие сообщения от ассистента будут последовательно переданы подписчикам `AssistantClient.on('data')`.\n\n```typescript\nimport { createRecordPlayer } from '@salutejs/client';\nimport assistantLog from './assistant-log.json';\n\nconst player = createRecordPlayer(assistantLog);\nlet end = false;\n\nwhile(!end) {\n  end = !player.continue();\n}\n```\n\n#### continue(): boolean\n\nПередает следующее сообщение от ассистента в AssistantClient (может содержать несколько команд). Возвращает флаг наличия в логе следующих сообщений от ассистента.\n\n#### play(): void\n\nПоследовательно передает все сообщения лога от ассистента в AssistantClient.\n\n#### getNextAction: { action: AssistantServerAction; name?: string; requestId?: string; mode?: 'background'|'foreground'; }\n\nВозвращает следующее сообщение от AssistantClient (вызов `sendData`) в ассистент. Можно использовать для сравнения эталонного сообщения (из лога) и текущего в тесте.\n\n#### setRecord(record: AssistantRecord): void\n\nЗагружает указанную запись в плеер.\n\n\n____\n\n\n## FAQ\n\n### Не работает озвучка и/или микрофон в браузере\n\nНужно перейти в [настройки сайта](https://support.google.com/chrome/answer/114662) и разрешить доступ к звуку и микрофону.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsalute-developers%2Fsalutejs-client","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsalute-developers%2Fsalutejs-client","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsalute-developers%2Fsalutejs-client/lists"}