[![npm ui](https://img.shields.io/npm/v/@salutejs/client)](https://www.npmjs.com/package/@salutejs/client)

<img src="https://user-images.githubusercontent.com/982072/97004635-0888a900-1546-11eb-8f25-283a0693608e.png" height="150px" width="150px">


Assistant Client — это инструмент для локального тестирования и отладки [Сanvas App](https://smartapp-code.sberdevices.ru/documentation/#/docs/ru/methodology/research/canvasapp) c виртуальным ассистентом. Он реализован в виде JavaScript протокола, который эмулирует среду Android и вызывает нативные методы. Такой подход не требует от разработчика наличия физических устройств и позволяет запустить виртуального ассистента через браузер.

## Оглавление
   * [Конфигурация](#Конфигурация)
     * [Аутентификация](#Аутентификация)
     * [Требования](#требования-к-устройствам)
     * [Установка](#Установка)
     * [Использование](#пример-использования)
   * [API](#API)
     * [createAssistant](#createAssistant)
     * [createSmartappDebugger](#createSmartappDebugger)
     * [AssistantClient](#assistantclient)
   * [Форматы объектов](#форматы-объектов)
     * [AssistantAppState](#AssistantAppState)
     * [AssistantServerAction](#AssistantServerAction)
     * [AssistantCharacterCommand](#AssistantCharacterCommand)
     * [AssistantNavigationCommand](#AssistantNavigationCommand)
     * [AssistantInsetsCommand](#AssistantInsetsCommand)
     * [AssistantThemeCommand](#AssistantThemeCommand)
     * [AssistantVisibilityCommand](#AssistantVisibilityCommand)
     * [AssistantSmartAppError](#AssistantSmartAppError)
     * [AssistantSmartAppCommand](#AssistantSmartAppCommand)
   * [Пульт](#пульт)
     * [Нажатие кнопок на пульте](#нажатие-кнопок-на-пульте)
     * [Навигация по смартапу](#навигация-по-смартапу)
   * [Утилиты для тестирования](#утилиты-для-тестирования)
     * [Имитация команд ассистента](#имитация-команд-ассистента)
     * [Запись лога сообщений](#запись-лога-сообщений)
     * [Воспроизведение лога сообщений](#воспроизведение-лога-сообщений)
* [FAQ](#faq)

____

## Конфигурация

### Аутентификация

Для работы с Assistant Client необходимо:

1. Установить сертификат Минцифры если он не установлен.
2. Завести аккаунт в [SmartApp Studio](https://developers.sber.ru/studio).
3. Создать смартап типа [Сanvas App](https://developers.sber.ru/docs/ru/va/canvas/title-page).
4. Получить токен. Для этого необходимо перейти в *Настройки профиля* > пункт *Настройки сервисов* > пункт *SmartApp* > вкладка *Эмулятор* > опция *Скопировать ключ*.
5. Передать полученный токен в методе `createSmartappDebugger` в параметре `token`.


### Требования к устройствам

Смартапы должны корректно отображаться на разных устройствах (SberBox, SberPortal и др). Для этого необходимо проверять смартап на следующих разрешениях: 559x568, 768x400, 959x400, 1920x1080. Настроить эти разрешения можно на [вкладке Devices Chrome](https://developers.google.com/web/tools/chrome-devtools/device-mode#custom).


### Установка

Для установки Assistant Client выполните следующую команду:

```sh
$ npm i @salutejs/client
```

### Пример использования

```typescript
// Функция createSmartappDebugger используется в development среде. В production среде необходимо использовать createAssistant.
import { createAssistant, createSmartappDebugger } from '@salutejs/client';

const initialize = (getState, getRecoveryState) => {
    if (process.env.NODE_ENV === 'development') {
        return createSmartappDebugger({
            // Токен из Кабинета разработчика
            token: 'token',
            // Пример фразы для запуска смартапа
            initPhrase: 'Хочу попкорн',
            // Текущее состояние смартапа
            getState,
            // Состояние смартапа, с которым он будет восстановлен при следующем запуске
            getRecoveryState,
            // Необязательные параметры панели, имитирующей панель на реальном устройстве
            nativePanel: {
                // Стартовый текст в поле ввода пользовательского запроса
                defaultText: 'Покажи что-нибудь',
                // Позволяет включить вид панели, максимально приближенный к панели на реальном устройстве
                screenshotMode: false,
                // Атрибут `tabindex` поля ввода пользовательского запроса
                tabIndex: -1,
            },
        });
    }

	  // Только для среды production
    return createAssistant({ getState, getRecoveryState });
}

...

const assistant = initialize(() => state, () => recoveryState);
assistant.on('data', (command) => {
    // Подписка на команды ассистента, в т.ч. команда инициализации смартапа.
    // Ниже представлен пример обработки голосовых команд "ниже"/"выше"
    if (command.navigation) {
        switch(command.navigation.command) {
            case 'UP':
                window.scrollTo(0, 0);
                break;
            case 'DOWN':
                window.scrollTo(0, 1000);
                break;
        }
    }
});

const handleOnClick = () => {
    // Отправка сообщения ассистенту с фронтенд.
    // Структура может меняться на усмотрение разработчика, в зависимости от бэкенд
    assistant.sendData({ action: { action_id: 'some_action_name', parameters: { param: 'some' } } });
};

const handleOnRefreshClick = () => {
    // Отправка сообщения бэкенду с возможностью подписки на ответ.
    // В обработчик assistant.on('data') сообщение не передается
    const unsubscribe = assistant.sendAction(
        { action_id: 'some_action_name', parameters: { param: 'some' } },
        (data: { type: string; payload: Record<string, unknown> }) => {
            // Обработка данных, переданных от бэкенд
            unsubscribe();
        },
        (error: { code: number; description: string }) => {
            // Обработка ошибки, переданной от бэкенд
        });
}
```

### Альтернативное подключение

Assitant Client доступен для подключения через `<script>`.
Версию assistant сlient можно поменять в src. Доступ к API осуществляется через глобальную переменную `assistant`.

Пример, для разработки и отладки в браузере:
```html
<script src="https://unpkg.com/@salutejs/client@stable/umd/assistant.development.min.js"></script>
<script>
  const client = assistant.createSmartappDebugger({
            // Токен из Кабинета разработчика
            token: 'token',
            // Пример фразы для запуска смартапа
            initPhrase: 'Хочу попкорн',
            // Текущее состояние смартапа
            getState: () => ({}),
            // Состояние смартапа, с которым он будет восстановлен при следующем запуске
            getRecoveryState: () => ({}),
        });
</script>
```

Пример, для использования на устройствах:
```html
<script src="https://unpkg.com/@salutejs/client@stable/umd/assistant.production.min.js"></script>
<script>
  const client = assistant.createAssistant({ getState: () => ({}), getRecoveryState: () => ({}), });
</script>
```

____


## API

### `createAssistant`

Создает экземпляр [`AssistantClient`](#assistantclient) для запуска виртуального ассистента. Используется на устройствах в production среде.

| Параметр         | Обязательный | Описание                                                                   |
| :--------------- | :------: | :------------------------------------------------------------------------- |
| getState         | Да           |  Функция, которая возвращает актуальное состояние смартапа                 |
| getRecoveryState | Нет          |  Функция, которая сохраняет состояние смартапа на момент последнего закрытия |



### `createSmartappDebugger`

Создает экземпляр [`AssistantClient`](#assistantclient) и добавляет на экран браузера панель с голосовым ассистентом (подобно устройствам). Панель ассистента находится в нижней части отрисованного экрана и позволяет отправлять виртуальному ассистенту следующие типы сообщений:
* текстовые сообщения через текстовое поле ввода;
* голосовые сообщения через кнопку «Салют».

`createSmartappDebugger` используется для локальной отладки и разработки в development среде.

| Параметр         | Обязательный | Описание                                                                   |
| :--------------- | :----------: | :------------------------------------------------------------------------- |
| token            | Да           |  Токен из SmartApp Studio                                             |
| initPhrase       | Да           |  Фраза, которая запускает смартап                                |
| getState         | Да           |  Функция, которая возвращает актуальное состояние смартапа                 |
| getRecoveryState | Нет          |  Функция, которая сохраняет состояние смартапа на момент последнего закрытия |
| settings         | Нет          |  Объект [настроек ассистента](#AssistantSettings)                          |
| nativePanel      | Нет          |  Объект настроек панели ассистента                          |
| surface          | Нет          |  Строка, название поверхности. Возможные значения: `SBERBOX` (SberBox), `STARGATE` (SberPortal), `SATELLITE` (SberBox Top) `SBOL` (приложение СберБанк Онлайн), `COMPANION` (приложение Салют), `TV` (Салют ТВ), `TV_HUAWEI` (Huawei Vision), `TIME` (SberBox Time)  |

> Максимальная частота отправки сообщений при отладке приложения с помощью `createSmartappDebugger` — два запроса в секунду.
> 
> При превышении ограничения ассистент ответит ошибкой:
> 
> ```json
> {
>     "code": -1008,
>     "description": "Виртуальные ассистенты тоже иногда ломаются. Предлагаю немного подождать, пока меня починят (код ошибки -1008)"
> }
> ```

#### Свойства [Settings](#AssistantSettings)

| Свойство | Значения     | По умолчанию | Описание                      |
| :------- | :----------: | :----------: | :---------------------------- |
| dubbing  | true / false | true         | Озвучивание ответа ассистента |

#### Свойства `nativePanel`

Все свойства являются необязательными.

| Свойство        | Тип значения  | По умолчанию | Описание                                                                                |
| :-------------- | :-----------: | :----------: | :-------------------------------------------------------------------------------------- |
| defaultText     | string        | Покажи что-нибудь | Стартовый текст в поле ввода пользовательского запроса                          |
| screenshotMode  | boolean       | false        | Позволяет включить вид панели, максимально приближенный к панели на реальном устройстве |
| tabIndex        | number        | -1           | Атрибут `tabindex` поля ввода пользовательского запроса                                 |

### AssistantClient

### cancelTts(): void

Опциональный метод. Вызывается со стороны смартапа для остановки озвучивания ответа (pronounceText). Имеет значение undefined, если устройство не поддерживает данную функцию.

#### close(): void

Вызывается со стороны смартапа для завершения работы.

#### getInitialData(): [AssistantCommands](#AssistantCommands)[]

Возвращает данные, полученные при инициализации смартапа. Передает в Assistant Client текущего ассистента, а не ассистента, выставленного по умолчанию.
Если при запуске смартапа не вызвать команду `getInitialData()`, то команды из `appInitialData` будут отправляться в `on('data')`.

#### getRecoveryState(): unknown

Возвращает состояние, сохраненное при закрытии смартапа. Устройство запоминает последнее состояние, которое возвращает функция `getRecoveryState` при инициализации Assistant Client.

#### subscribeToCommand(commandType: AssistantClientCommandType, cb: (command: AssistantClientCommand) => void): () => void

Позволяет подписаться на любой тип команд, приходящих в смартап. Возвращает метод отписки.

#### on('start', cb: () => void): void

Осуществляет подписку на событие готовности ассистента к работе.

#### on('data', cb: (data: [AssistantCharacterCommand](#AssistantCharacterCommand) | [AssistantNavigationCommand](#AssistantNavigationCommand) | [AssistantInsetsCommand](#AssistantInsetsCommand) | [AssistantThemeCommand](#AssistantThemeCommand) | [AssistantSmartAppError](#AssistantSmartAppError) | [AssistantSmartAppCommand](#AssistantSmartAppCommand)) => {}): void

Осуществляет подписку на событие получения данных с бэкенда. Если при запуске смартапа не была вызвана команда `getInitialData()`, то получает команды из `appInitialData`.

#### on('tts', cb: ({ state: 'start' | 'stop', owner: boolean }) => void).

Выполняет подписку на события начала и окончания озвучки.<br>
`state` - состояние озвучки, начало или окончание.<br>
`owner` - флаг принадлежности озвучки текущему смартапу.


#### sendAction({ action_id: string; parameters: Record<string, unknown> }, params?: { name?: string; requestId?: string, mode?: 'background'|'foreground' }) => void

Передает ошибки и обработчики ответа от бэкенда. <br>
`sendAction` — отправляет server-action и типизирует сообщения data и error.<br>
`clear()` — делает отписку от сообщений бэкенда. Это означает, что сообщения не будут переданы в обработчик `assistant.on('data')`


Пример:
```ts
import { AssistantSmartAppCommand } from '@salutejs/client';

interface SomeBackendMessage extends AssistantSmartAppCommand['smart_app_data'] {
  type: 'target_action',
  payload: {
    data: ['some_data'],
  },
}

const unsubscribe = assistant.sendAction<SomeBackendMessage>({ action_id: 'some_action_name', parameters: { someParam: 'some_value' } },
  ({ payload }) => {
    // обработка payload.data
    unsubscribe();
  }, (error) => {});
```

#### sendData({ action: [AssistantServerAction](#AssistantServerAction), name?: string, requestId?: string, mode?: 'background'|'foreground' }, onData?: data: [AssistantCharacterCommand](#AssistantCharacterCommand) | [AssistantNavigationCommand](#AssistantNavigationCommand) | [AssistantSmartAppError](#AssistantSmartAppError) | [AssistantSmartAppCommand](#AssistantSmartAppCommand)) => void): () => void

Отправляет события с фронтенда на бэкенд через ассистента.
Первый параметр (обязательный) принимает данные для отправки.
Второй параметр (опциональный) принимает обработчик ответа (на переданные первым параметром данные). В этом случае в `on('data')` ответ не приходит.
Возвращает функцию, вызов которой отменяет обработчик ответа.

Пример с обработкой ответа:
```ts
...

const unsubscribe = assistant.sendData({ action: { action_id: 'some_action_name' } }, (data: command) => {
  if (data.type === 'smart_app_data' && data.smart_app_data.type === 'target_action') {
    unsubsribe();
    ... // обработка команды
  }
});

```

#### setGetState(nextGetState: () => [AssistantAppState](#AssistantAppState)): void

Подменяет callback, который возвращает актуальное состояние смартапа.

#### setGetRecoveryState(nextGetRecoveryState: () => unknown)

Подменяет callback, который возвращает объект, доступный только при следующем запуске смартапа. Данные приходят при вызове `getRecoveryState`.


____


## Форматы объектов

### `AssistantAppState`

Объект `AssistantAppState` — текущее состояние смартапа, которое не хранится в платформе или сценарии. Каждый раз, когда пользователь начинает говорить, Assistant Client вызывает `getState`, чтобы получить и передать в бэкенд состояние экрана пользователя.
То, что происходит на экране у пользователя и как он взаимодействует со смартапом в конкретный момент времени — ответственность смартапа. Assistant Client в данном случае — это буфер, который только передает состояние платформе или сценарию.


```typescript
interface AssistantAppState {
  // Любые данные, которые могут потребоваться в бэкенде для принятия решений
  [key: string]: unknown;
  item_selector?: {
    ignored_words?: string[];
    // Список соответствий между голосовыми командами и действиями в приложении
    items: AssistantViewItem[];
  };
}

interface AssistantViewItem {
  // Уникальный (в рамках items) порядковый номер элемента, который назначается смартапом
  number?: number;
  // Уникальный id элемента
  id?: string;
  // Ключевая фраза, которая должна приводить к данному действию
  title?: string;
  // Фразы-синонимы, которые должны приводить к данному действию
  aliases?: string[];
  // Проксирование action обратно на бэкенд
  server_action?: AssistantServerAction;
  // Выполнение действия от имени пользователя
  action?: Action | { type: string };
  // Дополнительные данные для бэкенд
  [key: string]: unknown;
}
```

Например, когда пользователь говорит «Покажи 1», бэкенд должен понимать, что скрывается за единицей (то есть, какой элемент у пользователя пронумерован этой цифрой). Ниже пример состояния, который позволяет понять бэкенду, что, называя «1», пользователь хочет чипсы.

```js
{
  item_selector: {
    ignored_words: ["покажи"],
    items: [
      { title: 'Сладкий попкорн' },
      { title: 'Соленый попкорн' },
      { title: 'Чипсы', number: 1 },
      { title: 'Начос', number: 2 },
      { title: 'Кола', number: 3 }
    ]
  }
}
```

### `AssistantServerAction`

Объект `AssistantServerAction` — это любое сообщение, которое отправляется с фронтенда на бэкенд. Оно может быть привязано к ui-элементу и приходить с бэкенд, или формироваться самостоятельно фронтовой частью при обработке событий внутри WebView смартапа.

```typescript
interface AssistantServerAction {
  // Тип Server Action
  action_id: string;
  // Любые параметры
  parameters?: Record<string, unknown>;
}
```

### `AssistantCharacterCommand`

Объект `AssistantCharacterCommand` — информирует смартап о текущем персонаже (Сбер, Афина или Джой). Персонаж может быть изменен в любой момент по инициативе пользователя. Поэтому разработчик может дополнительно добавить обработку таких изменений.

```typescript
interface AssistantCharacterCommand {
  type: "character";
  character: {
    id: "sber" | "eva" | "joy";
  };
  sdk_meta: {
    requestId: string;
  };
}
```

### `AssistantNavigationCommand`

Объект `AssistantNavigationCommand` — команда навигации пользователя по смартапу (вперед, назад, дальше и т. д.). В платформе виртуального ассистента есть стандартные фразы, которые приходят и обрабатываются одинаково для всех смартапов.

```typescript
interface AssistantNavigationCommand {
  // Тип команды
  type: "navigation";
  // Навигационная команда (направление навигации)
  navigation: { command: "UP" | "DOWN" | "LEFT" | "RIGHT" | "FORWARD" };
  sdk_meta: {
    requestId: string;
  };
}
```

### `AssistantInsetsCommand`

Объект `AssistantInsetsCommand` — команда, которая сообщает смартапу о том, что поверх него будет отображен нативный UI и его размеры (например, высота шторки с КПСС, саджесты, или клавиатура). Размеры нужно соблюдать, чтобы не было наложения нативных UI элементов и контента смартапа.

- `insets` — отступы от краев экрана;
- `dynamic_insets` — размеры нативного UI;
- `minimum_static_insets` — минимальные значения размеров нативного UI;
- `maximum_static_insets` — максимальные значения размеров нативного UI.

```typescript
interface AssistantInsetsCommand {
  type: 'insets' | 'dynamic_insets' | 'minimum_static_insets' | 'maximum_static_insets';
  insets: {
    left: number;    // px
    top: number;     // px
    right: number;   // px
    bottom: number;  // px
  };
}
```

### `AssistantThemeCommand`

Объект `AssistantThemeCommand` - команда, которая сообщает смартапу текущую тему платформы — тёмная или светлая. По умолчанию нужно использовать тёмную тему.

```typescript
interface AssistantThemeCommand {
   type: 'theme';
   theme: {
      name: 'dark' | 'light'
   }
}
```

### `AssistantVisibilityCommand`

Объект `AssistantVisibilityCommand` — команда, которая сообщает о состоянии приложения: развернуто оно или нет. Параметр `hidden` указывает, что разработчику навыка необходимо остановить звук и/или видео. Параметр `visible` указывает, что воспроизведение звука и/или видео можно продолжить.

```typescript
interface AssistantVisibilityCommand {
    type: 'visibility';
    visibility: 'visible' | 'hidden';
}
```

### `AssistantSmartAppError`

Объект `AssistantSmartAppError` — это уведомление об ошибке.

```typescript
interface AssistantSmartAppError {
  type: 'smart_app_error';
  smart_app_error: {
    code: number;
    description: string;
  };
}
```

### `AssistantSmartAppCommand`

Объект `AssistantSmartAppCommand` — это команда передачи смартапу любых данных с бэкенда.

```typescript
interface AssistantSmartAppCommand {
  // Тип команды
  type: "smart_app_data";
  smart_app_data: {
    type: string;
    // Любые данные, которые нужны смартапу
    payload: Record<string, unknown>;
  };
  sdk_meta: {
    requestId: string;
  };
}
```

____


## Пульт

### Нажатие кнопок на пульте

Для получения и обработки нажатия кнопок на пульте от SberBox необходимо подписаться на события нажатия клавиш клавиатуры. Пример ниже:

```javascript
window.addEventListener('keydown', (event) => {
  switch(event.code) {
    case 'ArrowDown':
      // вниз
      break;
     case 'ArrowUp':
      // вверх
      break;
     case 'ArrowLeft':
      // влево
      break;
     case 'ArrowRight':
      // вправо
      break;
     case 'Enter':
      // ок
     break;
  }
});
```

### Навигация по страницам смартапа

Для корректной обработки кнопки `back` и навигации по страницам смартапа необходимо построить историю переходов, используя `History API`. Например, подписываемся на `window.onpopstate` и реализуем изменение страницы в обработчике этого события. Когда хотим выполнить изменение страницы, вызываем `window.history.pushState`:

```typescript

const [page, setPage] = useState<string>('previous');

const handleNext = () => {
  window.history.pushState({ page: 'next' }, ''); // инициируем переход на следующую страницу
}

useEffect(() => {
  window.history.replaceState({ page: 'previous' }, ''); // устанавливаем текущую страницу
  window.onpopstate = ({ state }) => {
    setPage(state.page); // выполняем переход на заданную страницу: next - по вызову handleNext или previous - по нажатию кнопки back
  }
}, []);

```
____


## Утилиты для тестирования

### Имитация команд ассистента

Для имитации команд от ассистента используйте утилиту `createAssistantHostMock`. Ниже приведен пример использования.

```typescript
import { createAssistantHostMock } from '@salutejs/client';

const ITEMS = [
  {
    id: 1,
    title: 'Купить молоко',
    number: 1,
  },
  {
    id: 2,
    title: 'Купить хлеб',
    number: 2,
  },
];

describe('Мой список дел', () => {
  it('По клику на чекбокс - ожидаем экшен "done" c заголовком выбранного элемента', (done) => {
    cy.visit('/')
      .window()
      .then((window) => {
        const mock = createAssistantHostMock({ context: window });
        const selected = ITEMS[1];
        mock.onReady(() => {
          // эмулируем инициализационную команду от бэкенда со списком задач
          mock.receiveCommand({
            type: 'smart_app_data',
            action: {
              type: 'init',
              notes: [...ITEMS],
            },
          })
          .then(() =>
            // ожидаем вызов assistantClient.sendData
            mock.waitAction(() =>
                // эмулируем отметку выполнения пользователем, который должен вызвать sendData({ action: { type: 'done } })
                window.document.getElementById(`checkbox-note-${selected.id}`).click(),
            ),
          )
          .then(({ action, state }) => {
            expect(action.type).to.equal('done'); // ожидаем экшен data_note
            expect(action.payload?.title).to.equal(selected.title); // ожидаем в параметрах title экшена
            expect(state?.item_selector.items).to.deep.equal(ITEMS); // ожидаем отправку списка в стейте
            done();
          });
        });
      });
  });
});
```

`createAssistantHostMock` можно вызывать только при использовании [`createAssistant`](#createAssistant). Например, при использовании `cypress` функция инициализации ассистента может выглядеть следующим образом:

```typescript
import { createAssistant, createSmartappDebugger } from '@salutejs/client';

const initializeAssistant = (getState: AssistantAppState) => {
    if (process.env.NODE_ENV === 'development' && window.Cypress == null) {
        return createSmartappDebugger({
            token: process.env.REACT_APP_TOKEN ?? '',
            initPhrase: `Запусти ${process.env.REACT_APP_SMARTAPP}`,
            getState,
        });
    }

    if (window.cypress) {
      window.appInitialData = [];
    }

    return createAssistant({ getState });
};
```

#### addActionHandler(actionType: string, handler: (action: AssistantServerAction) => void): void

Подписка на экшены фронтенда с определенным type, который передается первым параметром.

#### removeActionHandler(actionType: string): void

Отмена подписки от экшенов фронтенда.

#### receiveCommand(command: AssistantClientCommand): Promise<void>

Эмуляция команды, полученной от бэкенда. Команда приходит подписчикам `AssistantClient.onData`.

#### waitAction(onAction?: () => void): Promise<{ state: AssistantAppState; action: AssistantServerAction; name?: string; requestId?: string; mode?: 'background'|'foreground' }>

Получение `promise`, который будет разрезолвлен при следующем вызове `AssistantClient.sendData`

#### onReady(cb: () => void): void

Подписка на события готовности утилиты. Параметр `cb` будет вызван по готовности к работе.


### Запись лога сообщений

В режиме разработки есть возможность записать и скачать лог сообщений.
Управление записью осуществляется кнопками `start` и `stop`. Кнопка `save` сохранит файл с логом в загрузки браузера. Пример активации панели управления записью лога:

```typescript
import { createSmartappDebugger } from '@salutejs/client';

const assistant = createSmartappDebugger({
    token: process.env.REACT_APP_TOKEN ?? '',
    initPhrase: `Запусти ${process.env.REACT_APP_SMARTAPP}`,
    getState,
    enableRecord: true, // активируем функцию записи лога
    recordParams: {
      defaultActive: true, // включать запись при старте приложения (по-умолчанию = true)
    }
  });
```

### Воспроизведение лога сообщений

Пример пошагового воспроизведения лога сообщений. Входящие сообщения от ассистента будут последовательно переданы подписчикам `AssistantClient.on('data')`.

```typescript
import { createRecordPlayer } from '@salutejs/client';
import assistantLog from './assistant-log.json';

const player = createRecordPlayer(assistantLog);
let end = false;

while(!end) {
  end = !player.continue();
}
```

#### continue(): boolean

Передает следующее сообщение от ассистента в AssistantClient (может содержать несколько команд). Возвращает флаг наличия в логе следующих сообщений от ассистента.

#### play(): void

Последовательно передает все сообщения лога от ассистента в AssistantClient.

#### getNextAction: { action: AssistantServerAction; name?: string; requestId?: string; mode?: 'background'|'foreground'; }

Возвращает следующее сообщение от AssistantClient (вызов `sendData`) в ассистент. Можно использовать для сравнения эталонного сообщения (из лога) и текущего в тесте.

#### setRecord(record: AssistantRecord): void

Загружает указанную запись в плеер.


____


## FAQ

### Не работает озвучка и/или микрофон в браузере

Нужно перейти в [настройки сайта](https://support.google.com/chrome/answer/114662) и разрешить доступ к звуку и микрофону.
