@softomnitel/omnicall-kit 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +676 -563
- package/package.json +1 -10
package/README.md
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# @softomnitel/omnicall-kit
|
|
2
2
|
|
|
3
|
-
Browser SDK client for OmniCall Desktop (local protocol).
|
|
4
|
-
Каноническая документация для интеграторов — ниже (русский гайд). Полная копия в репозитории: `docs/guide/RU-DEVELOPER-GUIDE.md`. EN pages: `docs/guide/`.
|
|
3
|
+
Browser SDK client for OmniCall Desktop (local protocol).
|
|
5
4
|
|
|
6
5
|
```bash
|
|
7
6
|
npm install @softomnitel/omnicall-kit
|
|
@@ -9,768 +8,882 @@ npm install @softomnitel/omnicall-kit
|
|
|
9
8
|
|
|
10
9
|
---
|
|
11
10
|
|
|
12
|
-
# OmniCall Kit
|
|
11
|
+
# OmniCall Kit
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
`@softomnitel/omnicall-kit` — типизированный JavaScript/TypeScript-клиент для
|
|
14
|
+
CRM. Он подключает страницу CRM к уже установленному **OmniCall Desktop** на
|
|
15
|
+
компьютере оператора.
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
SDK получает состояние звонков и оператора, а также отправляет разрешённые
|
|
18
|
+
команды: начать звонок, ответить, положить трубку или изменить статус. SIP,
|
|
19
|
+
пароли и внутренняя телефония остаются внутри Desktop и не попадают в браузер.
|
|
17
20
|
|
|
18
|
-
|
|
21
|
+
```ts
|
|
22
|
+
import {
|
|
23
|
+
createIndexedDbPopKeyStore,
|
|
24
|
+
createOmniCallClient
|
|
25
|
+
} from '@softomnitel/omnicall-kit';
|
|
19
26
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
| Браузеры | Chromium / Edge (Chromium). Firefox/Safari — не заявлены |
|
|
27
|
-
| Модуль | ESM only, Node `>=20.19.0` |
|
|
28
|
-
| SDK **не** делает | SIP/OCP auth secrets, telephony FSM, `window.Softphone`, fetch-fallback к desktop HTTP |
|
|
27
|
+
const client = createOmniCallClient({ /* параметры ниже */ });
|
|
28
|
+
await client.connect();
|
|
29
|
+
await client.waitUntil((state) => state === 'ready');
|
|
30
|
+
const snapshot = await client.getSnapshot();
|
|
31
|
+
console.log(snapshot.revision);
|
|
32
|
+
```
|
|
29
33
|
|
|
30
|
-
|
|
34
|
+
## Содержание
|
|
31
35
|
|
|
32
|
-
|
|
36
|
+
- [Что нужно для работы](#что-нужно-для-работы)
|
|
37
|
+
- [Установка](#установка)
|
|
38
|
+
- [Быстрый старт](#быстрый-старт)
|
|
39
|
+
- [Основные понятия](#основные-понятия)
|
|
40
|
+
- [Состояния и события](#состояния-и-события)
|
|
41
|
+
- [API Reference](#api-reference)
|
|
42
|
+
- [Рецепты](#рецепты)
|
|
43
|
+
- [Ошибки и FAQ](#ошибки-и-faq)
|
|
44
|
+
- [Миграция и совместимость](#миграция-и-совместимость)
|
|
33
45
|
|
|
34
|
-
|
|
46
|
+
## Что нужно для работы
|
|
35
47
|
|
|
36
|
-
|
|
48
|
+
1. OmniCall Desktop должен быть установлен, запущен и иметь включённый SDK
|
|
49
|
+
gateway.
|
|
50
|
+
2. Страница CRM должна работать в Chromium или Edge на Chromium.
|
|
51
|
+
3. Для сборки проекта нужен Node.js `>=20.19.0` и npm `>=10`.
|
|
52
|
+
4. В браузере нужны Web Crypto и IndexedDB. Они хранят криптографическую
|
|
53
|
+
идентичность браузера.
|
|
54
|
+
5. Если CRM работает по HTTPS, браузер может спросить разрешение на связь с
|
|
55
|
+
локальной программой. Объясните это действие оператору в интерфейсе.
|
|
37
56
|
|
|
38
|
-
|
|
39
|
-
2. UI CRM = redacted **snapshot** + публичные **events**.
|
|
40
|
-
3. Мутации звонков / оператора / окна / logout / activate — только через namespaced API с `expectedRevision` и server-issued capabilities.
|
|
57
|
+
Firefox и Safari пока не входят в заявленную матрицу поддержки.
|
|
41
58
|
|
|
42
|
-
|
|
59
|
+
## Установка
|
|
43
60
|
|
|
44
|
-
|
|
61
|
+
```bash
|
|
62
|
+
npm install @softomnitel/omnicall-kit
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Для воспроизводимой production-сборки зафиксируйте версию, доступную в вашем
|
|
66
|
+
закрытом npm registry:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npm install @softomnitel/omnicall-kit@0.1.2
|
|
70
|
+
```
|
|
45
71
|
|
|
46
|
-
|
|
72
|
+
Пакет ESM-only. Импортируйте его через `import`, а не `require`.
|
|
47
73
|
|
|
48
|
-
|
|
74
|
+
## Быстрый старт
|
|
49
75
|
|
|
50
|
-
|
|
51
|
-
|
|
76
|
+
### 1. Создайте хранилище идентичности браузера
|
|
77
|
+
|
|
78
|
+
При первом подключении оператор подтверждает, что CRM может работать с Desktop.
|
|
79
|
+
Этот процесс называется **pairing**. После подтверждения SDK сохраняет
|
|
80
|
+
криптографическую идентичность браузера в IndexedDB. Не храните её в
|
|
81
|
+
`localStorage` или `sessionStorage`.
|
|
52
82
|
|
|
53
83
|
```ts
|
|
54
|
-
import {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
84
|
+
import { createIndexedDbPopKeyStore } from '@softomnitel/omnicall-kit';
|
|
85
|
+
|
|
86
|
+
const keyStore = createIndexedDbPopKeyStore({
|
|
87
|
+
// Стабильная строка для одной установки CRM в одном браузере.
|
|
88
|
+
installId: 'crm-production'
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### 2. Создайте клиент
|
|
60
93
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
94
|
+
`origin` — точный адрес CRM из браузера: схема, домен и порт. Например,
|
|
95
|
+
`https://crm.example`. Desktop не принимает маски и части строк.
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import { createOmniCallClient } from '@softomnitel/omnicall-kit';
|
|
65
99
|
|
|
66
100
|
const client = createOmniCallClient({
|
|
67
101
|
url: 'ws://127.0.0.1:17341/omnicall/v1/ws',
|
|
68
|
-
origin:
|
|
69
|
-
application: { name: 'my-crm', version: '1.
|
|
70
|
-
sdkVersion: '0.1.
|
|
102
|
+
origin: window.location.origin,
|
|
103
|
+
application: { name: 'my-crm', version: '1.0.0' },
|
|
104
|
+
sdkVersion: '0.1.2',
|
|
71
105
|
requestedProfile: 'call_controller',
|
|
72
106
|
requestedCapabilities: [
|
|
73
107
|
'session.read.redacted',
|
|
74
108
|
'window.show',
|
|
75
109
|
'call.originate',
|
|
76
110
|
'call.control',
|
|
77
|
-
'
|
|
78
|
-
'
|
|
111
|
+
'operator.status.write',
|
|
112
|
+
'session.logout'
|
|
79
113
|
],
|
|
80
114
|
keyStore
|
|
81
|
-
// transportFactory / scheduler / jitter опущены → browser defaults
|
|
82
115
|
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`requestedCapabilities` — это просьба о правах, а не их выдача. Desktop выдаёт
|
|
119
|
+
только права, разрешённые для этого Origin. Права `account.activate` и
|
|
120
|
+
`window.hide` нельзя запросить при pairing: оператор выдаёт их отдельно в
|
|
121
|
+
настройках Desktop.
|
|
83
122
|
|
|
123
|
+
### 3. Покажите оператору, что pairing ожидает подтверждения
|
|
124
|
+
|
|
125
|
+
```ts
|
|
84
126
|
client.onPairingRequired((info) => {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
127
|
+
console.info(
|
|
128
|
+
`Подтвердите подключение ${info.origin} в окне OmniCall Desktop.`
|
|
129
|
+
);
|
|
88
130
|
});
|
|
89
131
|
|
|
90
132
|
client.onStateChange((state) => {
|
|
91
|
-
|
|
133
|
+
console.info(`Состояние SDK: ${state}`);
|
|
92
134
|
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Подписки возвращают функцию отписки. Вызовите её при размонтировании компонента
|
|
138
|
+
или закрытии вкладки.
|
|
93
139
|
|
|
140
|
+
### 4. Подключитесь и получите состояние
|
|
141
|
+
|
|
142
|
+
`connect()` начинает сетевое подключение и возвращает `Promise`. `Promise`
|
|
143
|
+
означает результат, который появится позже. `await` ждёт этот результат, не
|
|
144
|
+
блокируя браузер.
|
|
145
|
+
|
|
146
|
+
```ts
|
|
94
147
|
await client.connect();
|
|
95
|
-
await client.waitUntil((
|
|
148
|
+
await client.waitUntil((state) => state === 'ready', 60_000);
|
|
96
149
|
|
|
97
150
|
const snapshot = await client.getSnapshot();
|
|
98
|
-
|
|
151
|
+
console.log('Версия состояния:', snapshot.revision);
|
|
152
|
+
console.log('Звонки:', snapshot.sections.calls);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Если состояние стало `pairing_required`, оператор должен подтвердить CRM в
|
|
156
|
+
OmniCall Desktop. Не создавайте второй клиент и не повторяйте `connect()` в
|
|
157
|
+
цикле.
|
|
158
|
+
|
|
159
|
+
## Основные понятия
|
|
160
|
+
|
|
161
|
+
| Понятие | Что это означает для CRM |
|
|
162
|
+
| --- | --- |
|
|
163
|
+
| OmniCall Desktop | Программа на компьютере оператора. Она владеет телефонией и учётной записью. |
|
|
164
|
+
| pairing | Первое подтверждение доступа конкретного сайта к Desktop. |
|
|
165
|
+
| capability | Право на одну группу действий, например `call.originate`. Проверяйте выданные права перед показом кнопки. |
|
|
166
|
+
| snapshot | Полный согласованный снимок состояния Desktop. Это главный источник данных для интерфейса CRM. |
|
|
167
|
+
| event | Уведомление об изменении, например `call:incoming`. Событие помогает быстро обновить UI, но не заменяет snapshot. |
|
|
168
|
+
| revision | Номер версии snapshot. Перед командой, меняющей состояние, передайте актуальный номер в `expectedRevision`. |
|
|
169
|
+
|
|
170
|
+
Поток данных выглядит так:
|
|
171
|
+
|
|
172
|
+
```text
|
|
173
|
+
CRM в браузере
|
|
174
|
+
→ OmniCall Kit
|
|
175
|
+
→ локальный WebSocket на 127.0.0.1
|
|
176
|
+
→ OmniCall Desktop
|
|
177
|
+
→ телефония и операторская платформа
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
SDK не является вторым телефоном. Он не даёт CRM SIP-пароль, не открывает
|
|
181
|
+
внутренний протокол операторской платформы и не поддерживает legacy
|
|
182
|
+
`window.Softphone`.
|
|
183
|
+
|
|
184
|
+
### Snapshot, события и revision
|
|
185
|
+
|
|
186
|
+
После `ready` вызовите `getSnapshot()`. Далее подпишитесь на события. После
|
|
187
|
+
reconnect снова получите snapshot.
|
|
188
|
+
|
|
189
|
+
Команда изменения состояния принимает `expectedRevision`. Если Desktop уже
|
|
190
|
+
изменился в другой вкладке, SDK отклонит команду с кодом `stale_state`. Получите
|
|
191
|
+
новый snapshot и дайте пользователю повторить намеренное действие.
|
|
99
192
|
|
|
100
|
-
|
|
101
|
-
|
|
193
|
+
```ts
|
|
194
|
+
async function getRevision(): Promise<number> {
|
|
195
|
+
return client.getRevision() ?? (await client.getSnapshot()).revision;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const result = await client.calls.originate({
|
|
199
|
+
destination: '+74951234567',
|
|
200
|
+
expectedRevision: await getRevision()
|
|
102
201
|
});
|
|
103
202
|
|
|
104
|
-
|
|
105
|
-
void isOmniCallClientError;
|
|
203
|
+
console.log(result.callId, result.revision);
|
|
106
204
|
```
|
|
107
205
|
|
|
108
|
-
|
|
206
|
+
Не повторяйте автоматически `originate`, `hangup`, `logout` или
|
|
207
|
+
`activateProfile` после reconnect. Эти действия могут сработать дважды.
|
|
208
|
+
|
|
209
|
+
## Состояния и события
|
|
109
210
|
|
|
110
|
-
|
|
111
|
-
<summary>Почему так / на что смотреть</summary>
|
|
211
|
+
### Состояния подключения
|
|
112
212
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
213
|
+
| Состояние | Что показать пользователю |
|
|
214
|
+
| --- | --- |
|
|
215
|
+
| `idle` | Кнопку подключения. |
|
|
216
|
+
| `connecting`, `handshaking`, `authenticating` | Индикатор процесса. |
|
|
217
|
+
| `pairing_required` | Инструкцию подтвердить CRM в Desktop. |
|
|
218
|
+
| `ready` | Основной интерфейс CRM. |
|
|
219
|
+
| `reconnecting` | Неблокирующее сообщение о восстановлении связи. |
|
|
220
|
+
| `revoked` | Очистите UI сессии и предложите пройти pairing снова. |
|
|
221
|
+
| `incompatible` | Предложите обновить CRM SDK или Desktop. |
|
|
222
|
+
| `failed` | Покажите безопасное описание ошибки из `getConnectError()`. |
|
|
223
|
+
| `closed` | Разрешите пользователю подключиться снова. |
|
|
117
224
|
|
|
118
|
-
|
|
225
|
+
### События
|
|
119
226
|
|
|
120
|
-
|
|
227
|
+
Подписывайтесь только на имена из `PUBLIC_EVENT_TYPES`.
|
|
121
228
|
|
|
122
|
-
|
|
|
229
|
+
| Событие | Назначение |
|
|
123
230
|
| --- | --- |
|
|
124
|
-
| `
|
|
125
|
-
| `
|
|
126
|
-
| `
|
|
127
|
-
| `
|
|
128
|
-
| `
|
|
129
|
-
| `
|
|
231
|
+
| `call:incoming` | Показать входящий звонок и кнопки ответа или отклонения. |
|
|
232
|
+
| `call:outgoing`, `call:ringing`, `call:answered` | Обновить ход исходящего или активного звонка. |
|
|
233
|
+
| `call:ended`, `call:failed` | Убрать карточку звонка или показать ошибку. |
|
|
234
|
+
| `call:held`, `call:resumed`, `call:muted`, `call:unmuted` | Обновить кнопки управления разговором. |
|
|
235
|
+
| `call:acd-context` | Получить публичный контекст очереди, если выдано право `ocp.acd_context.read`. |
|
|
236
|
+
| `registration:changed` | Обновить индикатор регистрации телефонии. |
|
|
237
|
+
| `account:session-activated`, `account:session-ended` | Обновить состояние учётной записи. |
|
|
238
|
+
| `operator:session-changed`, `operator:status-changed` | Обновить статус оператора и отложенную смену статуса. |
|
|
239
|
+
| `operator:campaign-offered`, `operator:campaign-cleared` | Обновить данные кампании. |
|
|
240
|
+
| `window:visibility-changed` | Обновить состояние окна Desktop. |
|
|
241
|
+
| `sdk:server-shutdown` | Показать сообщение, что Desktop завершает работу. |
|
|
130
242
|
|
|
131
|
-
|
|
243
|
+
```ts
|
|
244
|
+
const stop = client.subscribe('call:incoming', (event) => {
|
|
245
|
+
console.log('Входящий звонок:', event.payload.callId);
|
|
246
|
+
});
|
|
132
247
|
|
|
133
|
-
|
|
248
|
+
// Позднее, например при размонтировании UI:
|
|
249
|
+
stop();
|
|
250
|
+
```
|
|
134
251
|
|
|
135
|
-
##
|
|
252
|
+
## API Reference
|
|
136
253
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
→ Application / Call Engine / SIP / OCP
|
|
254
|
+
### `createOmniCallClient(options)`
|
|
255
|
+
|
|
256
|
+
Создаёт полный клиент CRM. Конструктор не открывает сеть; вызовите `connect()`.
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
function createOmniCallClient(options: OmniCallClientOptions): OmniCallClient;
|
|
144
260
|
```
|
|
145
261
|
|
|
146
|
-
|
|
147
|
-
| --- | --- |
|
|
148
|
-
| Telephony / operator FSM / credentials | Desktop |
|
|
149
|
-
| Session auth (pairing, PoP, capabilities) | Desktop + SDK auth orchestrator |
|
|
150
|
-
| UI CRM state | Snapshot + public events (клиент — thin reader/writer) |
|
|
151
|
-
| Transport bytes | Тонкий `TransportPort` — без JSON parse и без reconnect |
|
|
262
|
+
`OmniCallClientOptions` равен `AuthClientOptions`.
|
|
152
263
|
|
|
153
|
-
|
|
264
|
+
| Поле | Тип | Обязательно | Описание |
|
|
265
|
+
| --- | --- | --- | --- |
|
|
266
|
+
| `url` | `string` | Да | WebSocket URL SDK gateway Desktop. |
|
|
267
|
+
| `origin` | `string` | Да | Точный Origin CRM. |
|
|
268
|
+
| `application` | `ApplicationIdentity` | Да | Имя и версия вашего приложения. |
|
|
269
|
+
| `sdkVersion` | `string` | Да | Версия SDK, совместимая с Desktop. |
|
|
270
|
+
| `requestedProfile` | `PairingProfile` | Да | `presentation`, `operator` или `call_controller`. |
|
|
271
|
+
| `requestedCapabilities` | `readonly CapabilityId[]` | Нет | Запрашиваемые неприоритетные права. |
|
|
272
|
+
| `keyStore` | `PopKeyStore` | Да | Хранилище идентичности pairing. |
|
|
273
|
+
| `transportFactory`, `scheduler`, `jitter` | соответствующие интерфейсы | Нет | Заменяйте только в тестах или особой среде. Браузерные значения используются по умолчанию. |
|
|
274
|
+
| `diagnostics` | `DiagnosticsSink` | Нет | Получатель безопасных диагностических событий. |
|
|
275
|
+
| `defaultRequestTimeoutMs` | `number` | Нет | Таймаут команд в миллисекундах. |
|
|
276
|
+
| `reconnect` | `ReconnectPolicy` | Нет | Политика ограниченных повторных подключений. |
|
|
277
|
+
| `heartbeat` | `HeartbeatPolicy` | Нет | Настройка проверки живости соединения. |
|
|
154
278
|
|
|
155
|
-
|
|
279
|
+
Возвращает `OmniCallClient`. Ошибки настройки и соединения приходят через
|
|
280
|
+
`Promise` методов клиента как `OmniCallClientError`.
|
|
156
281
|
|
|
157
|
-
|
|
282
|
+
### `OmniCallClient`
|
|
158
283
|
|
|
159
|
-
|
|
284
|
+
Полный клиент состоит из lifecycle-методов и namespaces `calls`, `window`,
|
|
285
|
+
`operator`, `account`.
|
|
160
286
|
|
|
161
|
-
|
|
287
|
+
#### `client.connect()`
|
|
162
288
|
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
# pin: npm install @softomnitel/omnicall-kit@0.1.0
|
|
166
|
-
# RC: npm install @softomnitel/omnicall-kit@rc
|
|
289
|
+
```ts
|
|
290
|
+
(): Promise<void>
|
|
167
291
|
```
|
|
168
292
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
| Module | ESM only |
|
|
173
|
-
| Web Crypto | ECDSA P-256, non-extractable (PoP) |
|
|
174
|
-
| IndexedDB | Durable PoP в браузере |
|
|
175
|
-
| Desktop | OmniCall с включённым SDK gateway |
|
|
293
|
+
Открывает соединение и запускает pairing либо восстановление сессии. Promise
|
|
294
|
+
может быть отклонён ошибкой соединения. После успешного вызова всё ещё ждите
|
|
295
|
+
`ready` через `waitUntil()` или `onStateChange()`.
|
|
176
296
|
|
|
177
|
-
|
|
297
|
+
#### `client.disconnect()`
|
|
178
298
|
|
|
179
299
|
```ts
|
|
180
|
-
|
|
181
|
-
createOmniCallClient,
|
|
182
|
-
createIndexedDbPopKeyStore,
|
|
183
|
-
isOmniCallClientError,
|
|
184
|
-
type OmniCallClient,
|
|
185
|
-
type OmniCallEventOf,
|
|
186
|
-
type SnapshotMessage,
|
|
187
|
-
type CapabilityId
|
|
188
|
-
} from '@softomnitel/omnicall-kit';
|
|
300
|
+
(): void
|
|
189
301
|
```
|
|
190
302
|
|
|
191
|
-
|
|
303
|
+
Закрывает только соединение SDK, отменяет таймеры и ожидающие запросы. Не
|
|
304
|
+
завершает звонок, не выполняет logout и не закрывает Desktop.
|
|
192
305
|
|
|
193
|
-
|
|
306
|
+
#### `client.getState()`
|
|
194
307
|
|
|
195
|
-
|
|
308
|
+
```ts
|
|
309
|
+
(): ConnectionState
|
|
310
|
+
```
|
|
196
311
|
|
|
197
|
-
|
|
312
|
+
Возвращает текущее состояние из таблицы выше. Не выполняет сетевой запрос.
|
|
198
313
|
|
|
199
|
-
`
|
|
314
|
+
#### `client.waitUntil(predicate, timeoutMs?)`
|
|
200
315
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
| `application` | `{ name, version }` |
|
|
206
|
-
| `sdkVersion` | Версия SDK-строки на wire |
|
|
207
|
-
| `requestedProfile` | `presentation` \| `operator` \| `call_controller` |
|
|
208
|
-
| `requestedCapabilities?` | Non-privileged only; sanitize + strip privileged |
|
|
209
|
-
| `keyStore` | `PopKeyStore` (IndexedDB / memory) |
|
|
210
|
-
| `transportFactory?` | Default: browser WS |
|
|
211
|
-
| `scheduler?` / `jitter?` | Default: browser; inject в тестах |
|
|
212
|
-
| `reconnect?` | `ReconnectPolicy` |
|
|
213
|
-
| `heartbeat?` | `HeartbeatPolicy` |
|
|
214
|
-
| `diagnostics?` | Redaction-safe sink |
|
|
215
|
-
| `defaultRequestTimeoutMs?` | Таймаут обычных команд |
|
|
216
|
-
|
|
217
|
-
`createAuthClient` — только lifecycle/auth, без `calls` / `operator` / `account` / `window`.
|
|
218
|
-
|
|
219
|
-
### Состояния `CONNECTION_STATES`
|
|
220
|
-
|
|
221
|
-
| State | Смысл | Действие хоста |
|
|
222
|
-
| --- | --- | --- |
|
|
223
|
-
| `idle` | Сконструирован | Показать Connect |
|
|
224
|
-
| `connecting` | Открытие транспорта | Spinner |
|
|
225
|
-
| `handshaking` | Hello / protocol | Spinner |
|
|
226
|
-
| `pairing_required` | Нужен Approve в desktop | Инструкция оператору |
|
|
227
|
-
| `authenticating` | PoP challenge | Spinner |
|
|
228
|
-
| `ready` | Сессия + snapshot path | Product UI |
|
|
229
|
-
| `reconnecting` | Bounded retry | Banner; не replay мутаций |
|
|
230
|
-
| `incompatible` | Protocol mismatch | Upgrade; стоп |
|
|
231
|
-
| `revoked` | Сессия отозвана | Clear + re-pair |
|
|
232
|
-
| `failed` | Невосстановимый сбой | Показать ошибку (`getConnectError`) |
|
|
233
|
-
| `closed` | Закрыто | Connect заново при необходимости |
|
|
234
|
-
|
|
235
|
-
Типичный happy path: `idle` → `connecting` → `handshaking` → (`pairing_required` →) `authenticating` → `ready`.
|
|
236
|
-
|
|
237
|
-
### Подписки и методы lifecycle
|
|
238
|
-
|
|
239
|
-
| API | Назначение |
|
|
240
|
-
| --- | --- |
|
|
241
|
-
| `onStateChange(listener)` | Смена `ConnectionState`; возвращает unsubscribe |
|
|
242
|
-
| `onPairingRequired(listener)` | Origin/profile/clientId для UX pairing |
|
|
243
|
-
| `subscribe(type, listener)` | Публичные product events (`PUBLIC_EVENT_TYPES`) |
|
|
244
|
-
| `connect()` | Старт сети |
|
|
245
|
-
| `disconnect()` | Закрыть WS; **не** hangup/logout/activate/hide |
|
|
246
|
-
| `waitUntil(predicate, timeoutMs?)` | Дождаться состояния |
|
|
247
|
-
| `getState()` / `getSession()` / `getGrantedCapabilities()` | Интроспекция |
|
|
248
|
-
| `getConnectError()` | Последняя ошибка connect |
|
|
249
|
-
| `getSnapshot()` / `getCachedSnapshot()` / `getRevision()` | Snapshot / revision |
|
|
250
|
-
| `preauthDropCount()` | Диагностика preauth drops |
|
|
316
|
+
```ts
|
|
317
|
+
(predicate: (state: ConnectionState) => boolean, timeoutMs?: number)
|
|
318
|
+
=> Promise<ConnectionState>
|
|
319
|
+
```
|
|
251
320
|
|
|
252
|
-
|
|
253
|
-
|
|
321
|
+
Ждёт состояние, подходящее условию. `timeoutMs` не обязателен. При превышении
|
|
322
|
+
времени Promise отклоняется.
|
|
254
323
|
|
|
255
324
|
```ts
|
|
256
|
-
client.
|
|
257
|
-
|
|
258
|
-
const err = client.getConnectError();
|
|
259
|
-
void err?.code;
|
|
260
|
-
}
|
|
261
|
-
});
|
|
325
|
+
await client.waitUntil((state) => state === 'ready', 60_000);
|
|
326
|
+
```
|
|
262
327
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
void error.code;
|
|
269
|
-
}
|
|
270
|
-
throw error;
|
|
271
|
-
}
|
|
328
|
+
#### `client.onStateChange(listener)` и `client.onPairingRequired(listener)`
|
|
329
|
+
|
|
330
|
+
```ts
|
|
331
|
+
(listener: (state: ConnectionState) => void): () => void
|
|
332
|
+
(listener: (info: PairingRequiredInfo) => void): () => void
|
|
272
333
|
```
|
|
273
334
|
|
|
274
|
-
|
|
335
|
+
Подписывают на изменение состояния или ожидание pairing. Оба метода возвращают
|
|
336
|
+
функцию отписки. `PairingRequiredInfo` содержит `origin`, `requestedProfile` и
|
|
337
|
+
может содержать `clientId`.
|
|
275
338
|
|
|
276
|
-
|
|
339
|
+
#### `client.getSession()` и `client.getGrantedCapabilities()`
|
|
277
340
|
|
|
278
|
-
|
|
341
|
+
```ts
|
|
342
|
+
(): AuthSessionSnapshot | undefined
|
|
343
|
+
(): readonly CapabilityId[]
|
|
344
|
+
```
|
|
279
345
|
|
|
280
|
-
|
|
346
|
+
`getSession()` возвращает текущую сессию после авторизации либо `undefined`.
|
|
347
|
+
`getGrantedCapabilities()` возвращает права, которые Desktop действительно
|
|
348
|
+
выдал. Проверяйте их до показа кнопок.
|
|
281
349
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
| `operator` | presentation + `operator.status.write`, `operator.campaign.read`, `ocp.acd_context.read`, `session.logout` |
|
|
286
|
-
| `call_controller` | operator + `call.originate`, `call.control`, granular `call.answer\|reject\|hangup\|hold\|mute` |
|
|
350
|
+
```ts
|
|
351
|
+
const canDial = client.getGrantedCapabilities().includes('call.originate');
|
|
352
|
+
```
|
|
287
353
|
|
|
288
|
-
|
|
354
|
+
#### `client.getSnapshot()`, `getCachedSnapshot()` и `getRevision()`
|
|
289
355
|
|
|
290
|
-
|
|
356
|
+
```ts
|
|
357
|
+
(): Promise<SnapshotMessage>
|
|
358
|
+
(): SnapshotMessage | undefined
|
|
359
|
+
(): number | undefined
|
|
360
|
+
```
|
|
291
361
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
- Deny → `forbidden` + `origin_denied`, затем close.
|
|
362
|
+
`getSnapshot()` запрашивает свежий снимок. `getCachedSnapshot()` не обращается в
|
|
363
|
+
сеть. `getRevision()` возвращает номер кэшированного снимка. Используйте свежий
|
|
364
|
+
snapshot перед мутацией.
|
|
296
365
|
|
|
297
|
-
|
|
366
|
+
#### `client.subscribe(type, listener)`
|
|
298
367
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
| Privileged elevation | Только Settings → Origin matrix (оператор) |
|
|
368
|
+
```ts
|
|
369
|
+
<T extends PublicEventType>(
|
|
370
|
+
type: T,
|
|
371
|
+
listener: (event: OmniCallEventOf<T>) => void
|
|
372
|
+
): () => void
|
|
373
|
+
```
|
|
306
374
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
| `session.read.redacted` | Нет | snapshot / events |
|
|
310
|
-
| `window.show` | Нет | `window.show` |
|
|
311
|
-
| `window.hide` | **Да** | `window.hide` |
|
|
312
|
-
| `call.originate` | Нет | `calls.originate` |
|
|
313
|
-
| `call.control` | Нет | umbrella + DTMF |
|
|
314
|
-
| `call.answer` / `reject` / `hangup` / `hold` / `mute` | Нет | соответствующие методы |
|
|
315
|
-
| `operator.status.write` | Нет | `changeStatus` / `finishAppeal` |
|
|
316
|
-
| `operator.campaign.read` | Нет | campaign events + snapshot |
|
|
317
|
-
| `ocp.acd_context.read` | Нет | `call:acd-context` |
|
|
318
|
-
| `session.logout` | Нет | `account.logout` |
|
|
319
|
-
| `account.activate` | **Да** | `account.activateProfile` |
|
|
320
|
-
|
|
321
|
-
Revoke / matrix shrink mid-session → `forbidden` / state `revoked`. Хост: очистить UI, предложить re-pair / открыть Settings.
|
|
322
|
-
|
|
323
|
-
EN: [capabilities.md](../../docs/guide/capabilities.md), [pairing-quick-start.md](../../docs/guide/pairing-quick-start.md).
|
|
375
|
+
Подписывает на одно публичное событие и возвращает функцию отписки. Тип полезной
|
|
376
|
+
нагрузки выводится из значения `type`.
|
|
324
377
|
|
|
325
|
-
|
|
378
|
+
#### `client.getConnectError()` и `client.preauthDropCount()`
|
|
379
|
+
|
|
380
|
+
```ts
|
|
381
|
+
(): OmniCallClientError | undefined
|
|
382
|
+
(): number
|
|
383
|
+
```
|
|
326
384
|
|
|
327
|
-
|
|
385
|
+
Первый метод возвращает последнюю ошибку подключения. Второй возвращает
|
|
386
|
+
диагностический счётчик сообщений, отброшенных до авторизации. Не используйте его
|
|
387
|
+
как бизнес-метрику CRM.
|
|
328
388
|
|
|
329
|
-
|
|
389
|
+
### `client.calls`
|
|
330
390
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
|
335
|
-
|
|
|
336
|
-
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
const expectedRevision =
|
|
358
|
-
client.getRevision() ?? (await client.getSnapshot()).revision;
|
|
359
|
-
return run(expectedRevision);
|
|
360
|
-
}
|
|
391
|
+
Все методы ниже возвращают `Promise<CallMutationResult>`, где есть `callId` и
|
|
392
|
+
новый `revision`. Все требуют актуальный `expectedRevision`.
|
|
393
|
+
|
|
394
|
+
| Метод и сигнатура | Назначение | Право |
|
|
395
|
+
| --- | --- | --- |
|
|
396
|
+
| `originate({ destination, expectedRevision })` | Начать исходящий звонок на строку `destination`. | `call.originate` |
|
|
397
|
+
| `answer({ callId, expectedRevision })` | Ответить на входящий звонок. | `call.answer` или `call.control` |
|
|
398
|
+
| `reject({ callId, expectedRevision })` | Отклонить входящий звонок. | `call.reject` или `call.control` |
|
|
399
|
+
| `hangup({ callId, expectedRevision })` | Завершить звонок. | `call.hangup` или `call.control` |
|
|
400
|
+
| `hold({ callId, expectedRevision })` | Поставить звонок на удержание. | `call.hold` или `call.control` |
|
|
401
|
+
| `resume({ callId, expectedRevision })` | Снять звонок с удержания. | `call.hold` или `call.control` |
|
|
402
|
+
| `mute({ callId, expectedRevision })` | Выключить микрофон. | `call.mute` или `call.control` |
|
|
403
|
+
| `unmute({ callId, expectedRevision })` | Включить микрофон. | `call.mute` или `call.control` |
|
|
404
|
+
| `sendDtmf({ callId, digits, expectedRevision })` | Отправить тональные цифры `digits`. | Только `call.control` |
|
|
405
|
+
|
|
406
|
+
Параметр `callId` — идентификатор звонка из snapshot или события. Возможные
|
|
407
|
+
ошибки: `forbidden`, `not_ready`, `not_found`, `stale_state`, `conflict` и
|
|
408
|
+
`operation_failed`.
|
|
409
|
+
|
|
410
|
+
Каждый метод использует один из двух контрактов:
|
|
411
|
+
|
|
412
|
+
```ts
|
|
413
|
+
type CallActionInput = { callId: string; expectedRevision: number };
|
|
414
|
+
type OriginateInput = { destination: string; expectedRevision: number };
|
|
415
|
+
type DtmfInput = { callId: string; digits: string; expectedRevision: number };
|
|
416
|
+
type CallAction = (input: CallActionInput) => Promise<CallMutationResult>;
|
|
361
417
|
```
|
|
362
418
|
|
|
363
|
-
|
|
419
|
+
#### `client.calls.originate(input)`
|
|
364
420
|
|
|
365
|
-
|
|
421
|
+
```ts
|
|
422
|
+
(input: OriginateInput): Promise<CallMutationResult>
|
|
423
|
+
```
|
|
366
424
|
|
|
367
|
-
|
|
425
|
+
Набирает `destination`. Передавайте номер в формате, который поддерживает
|
|
426
|
+
ваша телефония. Проверьте `call.originate`; при `operation_failed` с
|
|
427
|
+
`failure_kind: 'sip_not_registered'` сначала восстановите регистрацию Desktop.
|
|
368
428
|
|
|
369
|
-
|
|
429
|
+
```ts
|
|
430
|
+
const revision = client.getRevision() ?? (await client.getSnapshot()).revision;
|
|
431
|
+
const result = await client.calls.originate({
|
|
432
|
+
destination: '+74951234567',
|
|
433
|
+
expectedRevision: revision
|
|
434
|
+
});
|
|
435
|
+
console.log(result.callId);
|
|
436
|
+
```
|
|
370
437
|
|
|
371
|
-
|
|
438
|
+
#### `client.calls.answer(input)`
|
|
372
439
|
|
|
373
|
-
|
|
440
|
+
```ts
|
|
441
|
+
(input: CallActionInput): Promise<CallMutationResult>
|
|
442
|
+
```
|
|
374
443
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
| `answer` / `reject` / `hangup` | `call.answer` / `reject` / `hangup` или `call.control` | `forbidden`, `not_found`, `stale_state`, `conflict` |
|
|
379
|
-
| `hold` / `resume` | `call.hold` или `call.control` | то же |
|
|
380
|
-
| `mute` / `unmute` | `call.mute` или `call.control` | то же |
|
|
381
|
-
| `sendDtmf({ callId, digits, expectedRevision })` | **только** `call.control` | `forbidden`, `stale_state`, `not_found` |
|
|
444
|
+
Отвечает на звонок `callId`. Вызывайте только для входящего звонка из snapshot
|
|
445
|
+
или `call:incoming`. Если звонок уже завершён в другой вкладке, получите
|
|
446
|
+
`not_found`, `conflict` или `stale_state`.
|
|
382
447
|
|
|
383
|
-
|
|
448
|
+
```ts
|
|
449
|
+
await client.calls.answer({ callId: 'call-123', expectedRevision: await getRevision() });
|
|
450
|
+
```
|
|
384
451
|
|
|
385
|
-
|
|
386
|
-
<summary>Пример: originate с обработкой ошибок</summary>
|
|
452
|
+
#### `client.calls.reject(input)`
|
|
387
453
|
|
|
388
454
|
```ts
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
isConflictError,
|
|
392
|
-
isOperationFailedError,
|
|
393
|
-
readOperationFailedDetails
|
|
394
|
-
} from '@softomnitel/omnicall-kit';
|
|
455
|
+
(input: CallActionInput): Promise<CallMutationResult>
|
|
456
|
+
```
|
|
395
457
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
const revision =
|
|
401
|
-
client.getRevision() ?? (await client.getSnapshot()).revision;
|
|
402
|
-
try {
|
|
403
|
-
await client.calls.originate({ destination, expectedRevision: revision });
|
|
404
|
-
} catch (error: unknown) {
|
|
405
|
-
if (isOperationFailedError(error)) {
|
|
406
|
-
const details = readOperationFailedDetails(error.details);
|
|
407
|
-
if (details?.failure_kind === 'sip_not_registered') {
|
|
408
|
-
return; // preflight deny — события call:failed не будет
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
if (isConflictError(error)) return;
|
|
412
|
-
if (!isOmniCallClientError(error)) throw error;
|
|
413
|
-
if (error.code === 'stale_state') {
|
|
414
|
-
await client.getSnapshot();
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
}
|
|
458
|
+
Отклоняет входящий звонок. Аргументы и ошибки совпадают с `answer()`.
|
|
459
|
+
|
|
460
|
+
```ts
|
|
461
|
+
await client.calls.reject({ callId: 'call-123', expectedRevision: await getRevision() });
|
|
418
462
|
```
|
|
419
463
|
|
|
420
|
-
|
|
464
|
+
#### `client.calls.hangup(input)`
|
|
421
465
|
|
|
422
|
-
|
|
466
|
+
```ts
|
|
467
|
+
(input: CallActionInput): Promise<CallMutationResult>
|
|
468
|
+
```
|
|
423
469
|
|
|
424
|
-
|
|
425
|
-
| --- | --- |
|
|
426
|
-
| `show()` | `window.show` — focus softphone |
|
|
427
|
-
| `hide({ expectedRevision })` | Privileged `window.hide`; busy telephony → `conflict`; recovery: tray Show / `show()` |
|
|
428
|
-
| `getState()` | `{ visible, revision }` |
|
|
470
|
+
Завершает звонок. Не вызывайте его автоматически в обработчике `disconnect()`.
|
|
429
471
|
|
|
430
|
-
|
|
472
|
+
```ts
|
|
473
|
+
await client.calls.hangup({ callId: 'call-123', expectedRevision: await getRevision() });
|
|
474
|
+
```
|
|
431
475
|
|
|
432
|
-
|
|
476
|
+
#### `client.calls.hold(input)` и `client.calls.resume(input)`
|
|
433
477
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
| `activateProfile({ login, expectedRevision, mode? })` | **`account.activate` (server-granted)** | `mode?: 'sip_only' \| 'ocp'`; никогда passwords |
|
|
478
|
+
```ts
|
|
479
|
+
(input: CallActionInput): Promise<CallMutationResult>
|
|
480
|
+
```
|
|
438
481
|
|
|
439
|
-
|
|
482
|
+
`hold()` ставит активный звонок на удержание, а `resume()` возвращает его в
|
|
483
|
+
разговор. Оба требуют `call.hold` или `call.control`.
|
|
440
484
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
| `SDK_ACTIVATE_OCP_AUTH_BUDGET_MS` | OCP auth budget |
|
|
446
|
-
| `SDK_ACTIVATE_CLIENT_TIMEOUT_MS` | Client wait ceiling (~420 s) |
|
|
485
|
+
```ts
|
|
486
|
+
await client.calls.hold({ callId: 'call-123', expectedRevision: await getRevision() });
|
|
487
|
+
await client.calls.resume({ callId: 'call-123', expectedRevision: await getRevision() });
|
|
488
|
+
```
|
|
447
489
|
|
|
448
|
-
|
|
490
|
+
#### `client.calls.mute(input)` и `client.calls.unmute(input)`
|
|
491
|
+
|
|
492
|
+
```ts
|
|
493
|
+
(input: CallActionInput): Promise<CallMutationResult>
|
|
494
|
+
```
|
|
449
495
|
|
|
450
|
-
|
|
451
|
-
|
|
496
|
+
`mute()` выключает, а `unmute()` включает микрофон оператора. Оба требуют
|
|
497
|
+
`call.mute` или `call.control`.
|
|
452
498
|
|
|
453
499
|
```ts
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
}
|
|
500
|
+
await client.calls.mute({ callId: 'call-123', expectedRevision: await getRevision() });
|
|
501
|
+
await client.calls.unmute({ callId: 'call-123', expectedRevision: await getRevision() });
|
|
502
|
+
```
|
|
458
503
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
});
|
|
504
|
+
#### `client.calls.sendDtmf(input)`
|
|
505
|
+
|
|
506
|
+
```ts
|
|
507
|
+
(input: DtmfInput): Promise<CallMutationResult>
|
|
464
508
|
```
|
|
465
509
|
|
|
466
|
-
|
|
510
|
+
Отправляет тональные цифры `digits` во время подходящего звонка. Требует только
|
|
511
|
+
`call.control`; не используйте для передачи секретов.
|
|
467
512
|
|
|
468
|
-
|
|
513
|
+
```ts
|
|
514
|
+
await client.calls.sendDtmf({
|
|
515
|
+
callId: 'call-123',
|
|
516
|
+
digits: '123#',
|
|
517
|
+
expectedRevision: await getRevision()
|
|
518
|
+
});
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
### `client.window`
|
|
469
522
|
|
|
470
|
-
| Метод |
|
|
523
|
+
| Метод | Сигнатура и результат | Условие |
|
|
471
524
|
| --- | --- | --- |
|
|
472
|
-
| `
|
|
473
|
-
| `
|
|
474
|
-
| `
|
|
525
|
+
| `show()` | `(): Promise<{ visible: boolean; revision: number }>` | Право `window.show`. |
|
|
526
|
+
| `hide(input)` | `({ expectedRevision: number }) => Promise<{ visible: boolean; revision: number }>` | Привилегированное право `window.hide`; Desktop может вернуть `conflict`, если идёт разговор. |
|
|
527
|
+
| `getState()` | `(): Promise<{ visible: boolean; revision: number }>` | Только чтение состояния окна. |
|
|
475
528
|
|
|
476
|
-
|
|
529
|
+
```ts
|
|
530
|
+
await client.window.show();
|
|
531
|
+
```
|
|
477
532
|
|
|
478
|
-
|
|
479
|
-
| --- | --- |
|
|
480
|
-
| Idle Ready/Break | `applied` |
|
|
481
|
-
| Busy / PCP | `reserved` (chip не сразу Break/Ready) |
|
|
533
|
+
#### `client.window.show()`
|
|
482
534
|
|
|
483
|
-
|
|
535
|
+
```ts
|
|
536
|
+
(): Promise<{ visible: boolean; revision: number }>
|
|
537
|
+
```
|
|
484
538
|
|
|
485
|
-
|
|
539
|
+
Показывает и фокусирует окно Desktop. Требует `window.show`. Результат содержит
|
|
540
|
+
фактическую видимость и новую версию состояния.
|
|
486
541
|
|
|
487
|
-
|
|
542
|
+
#### `client.window.hide(input)`
|
|
488
543
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
544
|
+
```ts
|
|
545
|
+
({ expectedRevision }: { expectedRevision: number })
|
|
546
|
+
=> Promise<{ visible: boolean; revision: number }>
|
|
547
|
+
```
|
|
493
548
|
|
|
494
|
-
|
|
495
|
-
|
|
549
|
+
Скрывает окно. Требует отдельно выданное привилегированное `window.hide`.
|
|
550
|
+
Desktop отклоняет запрос с `conflict`, когда скрытие небезопасно, например во
|
|
551
|
+
время разговора.
|
|
496
552
|
|
|
497
553
|
```ts
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
isInteractionRequiredError,
|
|
501
|
-
readInteractionRequiredDetails
|
|
502
|
-
} from '@softomnitel/omnicall-kit';
|
|
554
|
+
await client.window.hide({ expectedRevision: await getRevision() });
|
|
555
|
+
```
|
|
503
556
|
|
|
504
|
-
|
|
505
|
-
const logoutReasons = reasons.filter((r) => r.kind === 'logout');
|
|
557
|
+
#### `client.window.getState()`
|
|
506
558
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
const reasonId = details?.reasons[0]?.id ?? logoutReasons[0]?.id;
|
|
516
|
-
if (reasonId === undefined) return;
|
|
517
|
-
const next =
|
|
518
|
-
client.getRevision() ?? (await client.getSnapshot()).revision;
|
|
519
|
-
await client.account.logout({ reasonId, expectedRevision: next });
|
|
520
|
-
}
|
|
559
|
+
```ts
|
|
560
|
+
(): Promise<{ visible: boolean; revision: number }>
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
Запрашивает видимость окна, ничего не меняя.
|
|
564
|
+
|
|
565
|
+
```ts
|
|
566
|
+
const { visible } = await client.window.getState();
|
|
521
567
|
```
|
|
522
568
|
|
|
523
|
-
|
|
569
|
+
### `client.account`
|
|
524
570
|
|
|
525
|
-
|
|
571
|
+
#### `client.account.logout(input)`
|
|
526
572
|
|
|
527
|
-
|
|
573
|
+
```ts
|
|
574
|
+
({ reasonId?, expectedRevision }: {
|
|
575
|
+
reasonId?: number;
|
|
576
|
+
expectedRevision: number;
|
|
577
|
+
}) => Promise<LogoutResult>
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
Завершает операторскую сессию. Возвращает `{ loggedOut: true, revision }`.
|
|
581
|
+
Desktop может потребовать причину и вернуть `interaction_required`. В этом случае
|
|
582
|
+
получите причины через `client.operator.getReasons()`, покажите их пользователю и
|
|
583
|
+
повторите logout со свежим revision и выбранным `reasonId`.
|
|
584
|
+
|
|
585
|
+
#### `client.account.activateProfile(input)`
|
|
528
586
|
|
|
529
|
-
|
|
587
|
+
```ts
|
|
588
|
+
({ login, expectedRevision, mode? }: {
|
|
589
|
+
login: string;
|
|
590
|
+
expectedRevision: number;
|
|
591
|
+
mode?: 'sip_only' | 'ocp';
|
|
592
|
+
}) => Promise<ActivateProfileResult>
|
|
593
|
+
```
|
|
530
594
|
|
|
531
|
-
|
|
595
|
+
Активирует ранее сохранённый в Desktop профиль без передачи пароля в CRM.
|
|
596
|
+
`mode` по умолчанию определяет Desktop. Возвращает `activated`, `mode`,
|
|
597
|
+
необязательные `profileLabel`, `alreadyAuthenticated` и `revision`. Требует
|
|
598
|
+
выданное сервером право `account.activate`.
|
|
599
|
+
|
|
600
|
+
### `client.operator`
|
|
532
601
|
|
|
533
|
-
|
|
|
602
|
+
| Метод | Сигнатура и результат | Ошибки и ограничения |
|
|
534
603
|
| --- | --- | --- |
|
|
535
|
-
| `
|
|
536
|
-
| `
|
|
537
|
-
| `
|
|
538
|
-
| `call:held` / `call:resumed` | Hold | `sections.calls` |
|
|
539
|
-
| `call:muted` / `call:unmuted` | Mute | `sections.calls` |
|
|
540
|
-
| `call:acd-context` | OCP MainCallIDInfo (`ocp.acd_context.read`) | `calls[].acdContext` |
|
|
541
|
-
| `registration:changed` | SIP badge | registration section |
|
|
542
|
-
| `account:session-activated` / `account:session-ended` | Signed-in projection | account |
|
|
543
|
-
| `operator:session-changed` | `connected` | `sections.operator` |
|
|
544
|
-
| `operator:status-changed` | Coarse status + booking fields | `sections.operator` |
|
|
545
|
-
| `operator:campaign-offered` / `cleared` | Campaign UI (`operator.campaign.read`) | `operator.campaign` |
|
|
546
|
-
| `window:visibility-changed` | Softphone visibility | window |
|
|
547
|
-
| `sdk:server-shutdown` | Desktop stopping — wait/reconnect | — |
|
|
604
|
+
| `getReasons()` | `(): Promise<{ reasons: OperatorReason[]; revision: number }>` | Получает причины ready, break и logout. |
|
|
605
|
+
| `changeStatus(input)` | `({ target: 'ready' \| 'break', reasonId?, expectedRevision }) => Promise<OperatorStatusChangeResult>` | Требует `operator.status.write`. Результат содержит `kind: 'applied' \| 'reserved'`. |
|
|
606
|
+
| `finishAppeal(input)` | `({ expectedRevision }) => Promise<OperatorFinishAppealResult>` | Доступен в состоянии post-call processing. |
|
|
548
607
|
|
|
549
|
-
|
|
608
|
+
`reserved` означает: Desktop запомнил запрошенный статус и применит его после
|
|
609
|
+
текущего разговора. Не создавайте в CRM отдельную команду резервирования.
|
|
550
610
|
|
|
551
|
-
|
|
611
|
+
#### `client.operator.getReasons()`
|
|
552
612
|
|
|
553
|
-
|
|
613
|
+
```ts
|
|
614
|
+
(): Promise<OperatorReasonsResult>
|
|
615
|
+
```
|
|
554
616
|
|
|
555
|
-
|
|
556
|
-
|
|
617
|
+
Возвращает причины статусов и logout с полями `id`, `label`, `kind`, а также
|
|
618
|
+
`revision`. Используйте `id` выбранной оператором причины в следующей команде.
|
|
557
619
|
|
|
558
620
|
```ts
|
|
559
|
-
const
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
});
|
|
621
|
+
const { reasons } = await client.operator.getReasons();
|
|
622
|
+
console.log(reasons.filter((reason) => reason.kind === 'break'));
|
|
623
|
+
```
|
|
563
624
|
|
|
564
|
-
client.
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
}
|
|
625
|
+
#### `client.operator.changeStatus(input)`
|
|
626
|
+
|
|
627
|
+
```ts
|
|
628
|
+
({ target, reasonId?, expectedRevision }: {
|
|
629
|
+
target: 'ready' | 'break';
|
|
630
|
+
reasonId?: number;
|
|
631
|
+
expectedRevision: number;
|
|
632
|
+
}) => Promise<OperatorStatusChangeResult>
|
|
633
|
+
```
|
|
568
634
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
void snap.sections.calls;
|
|
635
|
+
Меняет статус на `ready` или `break`. Возвращает `accepted: true`, реальный
|
|
636
|
+
`targetStatus`, `reasonId`, `revision` и `kind`. При активном разговоре `kind`
|
|
637
|
+
может быть `reserved`, а не `applied`.
|
|
573
638
|
|
|
574
|
-
|
|
639
|
+
```ts
|
|
640
|
+
await client.operator.changeStatus({
|
|
641
|
+
target: 'ready',
|
|
642
|
+
expectedRevision: await getRevision()
|
|
643
|
+
});
|
|
575
644
|
```
|
|
576
645
|
|
|
577
|
-
|
|
646
|
+
#### `client.operator.finishAppeal(input)`
|
|
578
647
|
|
|
579
|
-
|
|
648
|
+
```ts
|
|
649
|
+
({ expectedRevision }: { expectedRevision: number })
|
|
650
|
+
=> Promise<OperatorFinishAppealResult>
|
|
651
|
+
```
|
|
580
652
|
|
|
581
|
-
|
|
653
|
+
Заканчивает постобработку обращения. Вызывайте только когда публичный статус
|
|
654
|
+
оператора — `post_call_processing`; в остальных случаях Desktop вернёт ошибку
|
|
655
|
+
состояния или конфликта.
|
|
656
|
+
|
|
657
|
+
```ts
|
|
658
|
+
await client.operator.finishAppeal({ expectedRevision: await getRevision() });
|
|
659
|
+
```
|
|
582
660
|
|
|
583
661
|
## Ошибки
|
|
584
662
|
|
|
585
|
-
|
|
663
|
+
### `OmniCallClientError`
|
|
664
|
+
|
|
665
|
+
```ts
|
|
666
|
+
new OmniCallClientError({
|
|
667
|
+
code: ProtocolErrorCode,
|
|
668
|
+
retryable: boolean,
|
|
669
|
+
currentRevision?: number,
|
|
670
|
+
details?: WireJsonObject
|
|
671
|
+
});
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
Это класс ошибок SDK. Свойства `code`, `retryable`, `currentRevision` и `details`
|
|
675
|
+
помогают выбрать действие. Не выводите весь `details` в production-логи: там
|
|
676
|
+
могут быть чувствительные данные.
|
|
586
677
|
|
|
587
|
-
|
|
|
678
|
+
| Проверка и reader | Когда применять |
|
|
588
679
|
| --- | --- |
|
|
589
|
-
| `isOmniCallClientError` |
|
|
590
|
-
| `isConflictError` + `readConflictErrorDetails` | `conflict
|
|
591
|
-
| `isInteractionRequiredError` + `readInteractionRequiredDetails` |
|
|
592
|
-
| `isOperationFailedError` + `readOperationFailedDetails` |
|
|
593
|
-
| `isOriginBlockedError` |
|
|
680
|
+
| `isOmniCallClientError(value)` | Проверить любую ошибку SDK. |
|
|
681
|
+
| `isConflictError(error)` + `readConflictErrorDetails(details)` | Обработать `conflict`. |
|
|
682
|
+
| `isInteractionRequiredError(error)` + `readInteractionRequiredDetails(details)` | Выбрать причину logout. |
|
|
683
|
+
| `isOperationFailedError(error)` + `readOperationFailedDetails(details)` | Узнать `failure_kind`, например `sip_not_registered`. |
|
|
684
|
+
| `isOriginBlockedError(value)` | Обработать заблокированный Origin. |
|
|
685
|
+
|
|
686
|
+
```ts
|
|
687
|
+
import {
|
|
688
|
+
isOmniCallClientError,
|
|
689
|
+
isOperationFailedError,
|
|
690
|
+
readOperationFailedDetails
|
|
691
|
+
} from '@softomnitel/omnicall-kit';
|
|
692
|
+
|
|
693
|
+
try {
|
|
694
|
+
await client.calls.originate({
|
|
695
|
+
destination: '+74951234567',
|
|
696
|
+
expectedRevision: await getRevision()
|
|
697
|
+
});
|
|
698
|
+
} catch (error: unknown) {
|
|
699
|
+
if (isOperationFailedError(error)) {
|
|
700
|
+
console.warn(readOperationFailedDetails(error.details)?.failure_kind);
|
|
701
|
+
} else if (isOmniCallClientError(error)) {
|
|
702
|
+
console.warn(error.code, error.retryable);
|
|
703
|
+
} else {
|
|
704
|
+
throw error;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
## Вспомогательные фабрики и низкоуровневые интерфейсы
|
|
594
710
|
|
|
595
|
-
|
|
711
|
+
Это API для тестов, нестандартных runtime-сред и диагностики. В обычной браузерной
|
|
712
|
+
CRM ничего из этого передавать в `createOmniCallClient()` не нужно.
|
|
713
|
+
|
|
714
|
+
| Экспорт | Сигнатура | Для чего нужен |
|
|
596
715
|
| --- | --- | --- |
|
|
597
|
-
| `
|
|
598
|
-
| `
|
|
599
|
-
| `
|
|
600
|
-
| `
|
|
601
|
-
| `
|
|
602
|
-
| `
|
|
603
|
-
| `
|
|
604
|
-
| `
|
|
605
|
-
| `
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
716
|
+
| `createAuthClient(options)` | `(AuthClientOptions) => AuthClient` | Клиент только pairing и lifecycle без `calls`, `window`, `operator`, `account`. |
|
|
717
|
+
| `createIndexedDbPopKeyStore({ installId })` | `({ installId: string }) => PopKeyStore` | Постоянное браузерное хранилище pairing. |
|
|
718
|
+
| `createMemoryPopKeyStore(initial?)` | `(StoredPopIdentity?) => PopKeyStore & { peek() }` | Временное хранилище для тестов. |
|
|
719
|
+
| `createBrowserWebSocketTransport(options?)` | `({ webSocket? }?) => TransportPort` | Стандартный транспорт браузера. |
|
|
720
|
+
| `createBrowserScheduler()` | `() => Scheduler` | Реальные таймеры браузера. |
|
|
721
|
+
| `createBrowserJitterSource()` | `() => JitterSource` | Случайная задержка reconnect. |
|
|
722
|
+
| `createFakeScheduler(startMs?)` | `(number?) => FakeScheduler` | Управляемые таймеры в тестах. |
|
|
723
|
+
| `createFixedJitterSource(value)` | `(number) => JitterSource` | Предсказуемый jitter в тестах. |
|
|
724
|
+
| `createRecordingDiagnosticsSink()` | `() => DiagnosticsSink & { events; clear() }` | Сбор безопасных диагностических событий. |
|
|
725
|
+
|
|
726
|
+
`PopKeyStore` имеет методы `load()`, `save(identity)` и `clear()`. Его
|
|
727
|
+
`StoredPopIdentity` содержит `clientId`, открытый ключ, непередаваемый
|
|
728
|
+
`CryptoKey`, профиль и выданные права. Не сериализуйте приватный `CryptoKey` и не
|
|
729
|
+
отправляйте его на сервер CRM.
|
|
730
|
+
|
|
731
|
+
`TransportPort` определяет `connect(url)`, `send(data)`, `close(code?, reason?)`
|
|
732
|
+
и подписки `onOpen`, `onMessage`, `onClose`, `onError`. Он работает только со
|
|
733
|
+
строковыми сообщениями. Не добавляйте в него собственный JSON-парсер или цикл
|
|
734
|
+
reconnect: этим управляет сессия SDK.
|
|
735
|
+
|
|
736
|
+
`Scheduler` определяет `now()` и `setTimeout(callback, delayMs)`. Возвращаемый
|
|
737
|
+
`TimerHandle` имеет `clear()`. `FakeScheduler` добавляет `advanceBy`,
|
|
738
|
+
`advanceByAsync`, `pendingTimerCount` и `clearAll`.
|
|
739
|
+
|
|
740
|
+
### Политики и диагностика
|
|
619
741
|
|
|
620
|
-
|
|
742
|
+
```ts
|
|
743
|
+
type ReconnectPolicy = {
|
|
744
|
+
maxAttempts: number;
|
|
745
|
+
initialDelayMs: number;
|
|
746
|
+
maxDelayMs: number;
|
|
747
|
+
jitterRatio: number;
|
|
748
|
+
};
|
|
749
|
+
|
|
750
|
+
type HeartbeatPolicy = {
|
|
751
|
+
enabled: boolean;
|
|
752
|
+
intervalMs: number;
|
|
753
|
+
timeoutMs: number;
|
|
754
|
+
};
|
|
755
|
+
```
|
|
621
756
|
|
|
622
|
-
|
|
757
|
+
`DiagnosticsSink.emit(event)` получает `DiagnosticEvent`: уровень
|
|
758
|
+
`debug | info | warn | error`, код, состояние и необязательные `requestId`,
|
|
759
|
+
`commandType`, длительность и ошибку. Логируйте коды и идентификаторы запросов,
|
|
760
|
+
но не логируйте номера телефонов, токены или полный payload.
|
|
623
761
|
|
|
624
|
-
|
|
625
|
-
| --- | --- |
|
|
626
|
-
| Policy | Bounded (`maxAttempts`), jittered, cancellable |
|
|
627
|
-
| После успеха | Новый auth + **fresh snapshot** |
|
|
628
|
-
| In-flight mutations | Rejected typed; **не** auto-resent |
|
|
629
|
-
| Host intent | Переиздать после нового `expectedRevision` |
|
|
762
|
+
## Типы, константы и полный индекс экспорта
|
|
630
763
|
|
|
631
|
-
|
|
764
|
+
Ниже перечислены все публичные экспорты пакета. Для структур сообщений протокола
|
|
765
|
+
`SnapshotMessage`, `SnapshotSections`, `SnapshotCallSummary`,
|
|
766
|
+
`ApplicationIdentity`, `CapabilityId`, `ProtocolErrorCode`,
|
|
767
|
+
`PublicOperatorStatus` и `WireJsonObject` используйте TypeScript-типы из
|
|
768
|
+
`@softomnitel/omnicall-kit` и `@softomnitel/omnicall-protocol`: они
|
|
769
|
+
re-exported из протокольного пакета.
|
|
632
770
|
|
|
633
|
-
|
|
|
634
|
-
| --- | --- |
|
|
635
|
-
|
|
|
636
|
-
|
|
|
637
|
-
|
|
|
638
|
-
|
|
|
771
|
+
| Группа | Экспорты |
|
|
772
|
+
| --- | --- |
|
|
773
|
+
| Клиент и auth | `OmniCallClient`, `OmniCallClientOptions`, `AuthClient`, `AuthClientOptions`, `AuthSessionSnapshot`, `PairingRequiredInfo`, `CONNECTION_STATES`, `ConnectionState` |
|
|
774
|
+
| Команды и результаты | `OmniCallCallsApi`, `OmniCallWindowApi`, `OmniCallAccountApi`, `OmniCallOperatorApi`, `CallMutationResult`, `LogoutResult`, `ActivateProfileMode`, `ActivateProfileResult`, `OperatorReason`, `OperatorReasonsResult`, `OperatorStatusChangeKind`, `OperatorStatusChangeResult`, `OperatorFinishAppealResult` |
|
|
775
|
+
| События | `PUBLIC_EVENT_TYPES`, `PublicEventType`, `OmniCallEvent`, `OmniCallEventOf` |
|
|
776
|
+
| Ошибки | `OmniCallClientError`, `ConflictErrorDetails`, `InteractionRequiredDetails`, `OperationFailedDetails`, пять type guard/readers из раздела ошибок |
|
|
777
|
+
| Хранилище | `PopKeyStore`, `StoredPopIdentity`, `createIndexedDbPopKeyStore`, `createMemoryPopKeyStore` |
|
|
778
|
+
| Транспорт | `BrowserWebSocketConstructor`, `BrowserWebSocketLike`, `CreateBrowserWebSocketTransportOptions`, `TransportFactory`, `TransportPort`, `TransportCloseInfo`, `TransportErrorInfo`, `createBrowserWebSocketTransport` |
|
|
779
|
+
| Время и reconnect | `Scheduler`, `TimerHandle`, `FakeScheduler`, `JitterSource`, `ReconnectPolicy`, `HeartbeatPolicy`, `createBrowserScheduler`, `createBrowserJitterSource`, `createFakeScheduler`, `createFixedJitterSource` |
|
|
780
|
+
| Диагностика | `DiagnosticsSink`, `DiagnosticEvent`, `DiagnosticLevel`, `DiagnosticResult`, `createRecordingDiagnosticsSink` |
|
|
781
|
+
| Протокольные re-export | `CapabilityId`, `ProtocolErrorCode`, `PublicOperatorStatus`, `SnapshotMessage`, `SnapshotSections`, `SnapshotCallSummary`, `WireJsonObject` |
|
|
782
|
+
| Константы activation | `SDK_ACTIVATE_CONSENT_TTL_MS`, `SDK_ACTIVATE_SIP_ONLY_AUTH_BUDGET_MS`, `SDK_ACTIVATE_OCP_AUTH_BUDGET_MS`, `SDK_ACTIVATE_CLIENT_TIMEOUT_MS` |
|
|
783
|
+
|
|
784
|
+
Четыре константы activation задают верхние границы ожидания согласия и
|
|
785
|
+
авторизации в миллисекундах. Используйте их, если вашему UI нужно показать
|
|
786
|
+
таймер; не меняйте их смысл локальными таймаутами.
|
|
639
787
|
|
|
640
|
-
|
|
788
|
+
## Рецепты
|
|
641
789
|
|
|
642
|
-
|
|
643
|
-
<summary>Пример: banner на reconnecting</summary>
|
|
790
|
+
### Обновить UI после reconnect
|
|
644
791
|
|
|
645
792
|
```ts
|
|
646
793
|
client.onStateChange((state) => {
|
|
647
|
-
if (state === 'reconnecting') {
|
|
648
|
-
// Banner only — не resend originate/hangup/logout/activate
|
|
649
|
-
}
|
|
650
794
|
if (state === 'ready') {
|
|
651
|
-
void client.getSnapshot()
|
|
795
|
+
void client.getSnapshot().then((snapshot) => {
|
|
796
|
+
console.log('Обновите UI из снимка', snapshot.revision);
|
|
797
|
+
});
|
|
652
798
|
}
|
|
653
799
|
});
|
|
654
800
|
```
|
|
655
801
|
|
|
656
|
-
|
|
802
|
+
### Безопасно сменить статус оператора
|
|
657
803
|
|
|
658
|
-
|
|
804
|
+
```ts
|
|
805
|
+
const expectedRevision =
|
|
806
|
+
client.getRevision() ?? (await client.getSnapshot()).revision;
|
|
807
|
+
|
|
808
|
+
const result = await client.operator.changeStatus({
|
|
809
|
+
target: 'break',
|
|
810
|
+
expectedRevision
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
if (result.kind === 'reserved') {
|
|
814
|
+
console.info('Перерыв начнётся после текущего разговора.');
|
|
815
|
+
}
|
|
816
|
+
```
|
|
659
817
|
|
|
660
|
-
|
|
818
|
+
### Logout с выбором причины
|
|
661
819
|
|
|
662
|
-
|
|
820
|
+
```ts
|
|
821
|
+
const { reasons } = await client.operator.getReasons();
|
|
822
|
+
const reason = reasons.find((item) => item.kind === 'logout');
|
|
663
823
|
|
|
664
|
-
|
|
824
|
+
if (reason !== undefined) {
|
|
825
|
+
await client.account.logout({
|
|
826
|
+
reasonId: reason.id,
|
|
827
|
+
expectedRevision: await getRevision()
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
```
|
|
665
831
|
|
|
666
|
-
|
|
832
|
+
## Ошибки и FAQ
|
|
667
833
|
|
|
668
|
-
|
|
834
|
+
### `stale_state`: команда отклонена
|
|
669
835
|
|
|
670
|
-
|
|
836
|
+
Другая вкладка или Desktop изменили состояние после вашего snapshot. Вызовите
|
|
837
|
+
`getSnapshot()`, обновите UI и попросите пользователя повторить действие. Не
|
|
838
|
+
повторяйте команду со старым `expectedRevision`.
|
|
671
839
|
|
|
672
|
-
|
|
840
|
+
### `forbidden`: кнопка не работает
|
|
673
841
|
|
|
674
|
-
|
|
842
|
+
Desktop не выдал capability или оператор запретил Origin. Проверьте
|
|
843
|
+
`getGrantedCapabilities()`. Для `window.hide` и `account.activate` оператор
|
|
844
|
+
должен выдать право в Origin matrix Desktop.
|
|
675
845
|
|
|
676
|
-
|
|
846
|
+
### `pairing_required` не заканчивается
|
|
677
847
|
|
|
678
|
-
|
|
848
|
+
Оператор ещё не подтвердил CRM в OmniCall Desktop. Покажите адрес из
|
|
849
|
+
`PairingRequiredInfo.origin` и не скрывайте это состояние под бесконечным
|
|
850
|
+
спиннером.
|
|
679
851
|
|
|
680
|
-
|
|
852
|
+
### `local_network_permission_required` или `_denied`
|
|
681
853
|
|
|
682
|
-
|
|
854
|
+
Браузер не разрешил HTTPS-странице обратиться к локальной программе. Объясните,
|
|
855
|
+
где выдать разрешение, затем разрешите оператору подключиться снова.
|
|
683
856
|
|
|
684
|
-
|
|
857
|
+
### Нужен ли отдельный WebSocket-клиент?
|
|
685
858
|
|
|
686
|
-
|
|
859
|
+
Нет. Используйте `createOmniCallClient()` и транспорт SDK по умолчанию. Свой
|
|
860
|
+
WebSocket-клиент обходит проверку сообщений и может нарушить reconnect.
|
|
687
861
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
1. Делайте один `OmniCallClient` на вкладку/сессию CRM.
|
|
691
|
-
2. Держите UI state = snapshot + events; не дублируйте telephony FSM в CRM.
|
|
692
|
-
3. Вызывайте мутации только после `ready` и проверки capability.
|
|
693
|
-
4. Всегда обновляйте revision из последнего успешного результата или fresh snapshot.
|
|
694
|
-
5. Логируйте только `code` / `requestId` / command type — не payload.
|
|
695
|
-
6. Privileged flows — только через `getGrantedCapabilities()` feature-detect.
|
|
696
|
-
7. В тестах: `createMemoryPopKeyStore` + fake transport/scheduler; browser E2E отдельно.
|
|
697
|
-
8. На HTTPS CRM заранее объясняйте пользователю LNA / Local Network permission.
|
|
698
|
-
9. Не оборачивайте `TransportPort` собственной reconnect-логикой.
|
|
699
|
-
10. Pin версию `@softomnitel/omnicall-kit`; читайте [upgrade-deprecation.md](../../docs/guide/upgrade-deprecation.md).
|
|
700
|
-
11. При `stale_state` / `conflict` — один осознанный retry после snapshot, не tight-loop.
|
|
701
|
-
12. Key CRM cards по `callId`; `queueLabel` enrichment — update, не второй lead.
|
|
862
|
+
### Можно ли хранить SIP-пароль или ключ OCP в CRM?
|
|
702
863
|
|
|
703
|
-
|
|
864
|
+
Нет. Эти секреты остаются в OmniCall Desktop. Для ранее сохранённого профиля
|
|
865
|
+
используйте `activateProfile({ login, ... })` после явной выдачи capability.
|
|
704
866
|
|
|
705
|
-
##
|
|
867
|
+
## Миграция и совместимость
|
|
706
868
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
| Request `window.hide` на pairing | Strip | Matrix grant → `window.hide` |
|
|
711
|
-
| SIP password / OCP apiKey в SDK | Secrets только на desktop | `activateProfile({ login })` |
|
|
712
|
-
| PoP в `localStorage` / `sessionStorage` | XSS | IndexedDB / memory key store |
|
|
713
|
-
| Логировать phones / tokens / payloads | PII / leak | code + retryable + requestId |
|
|
714
|
-
| Auto-replay мутаций после reconnect | Double-originate | Fresh revision + user re-issue |
|
|
715
|
-
| Hangup/logout/activate на `disconnect()` | Desktop session должна жить | `disconnect` = transport only |
|
|
716
|
-
| Custom reconnect в TransportPort | Session owns policy | Thin browser WS adapter |
|
|
717
|
-
| Binary frames / свой JSON parser в port | Validation выше порта | Text frames via `onMessage` |
|
|
718
|
-
| `account:list-profiles` | Нет в protocol v1 | Передайте известный `login` |
|
|
719
|
-
| Auto-logout on disconnect | Destructive | Logout только после confirm |
|
|
720
|
-
| Origin substring / wildcard | Exact only | Exact Origin string |
|
|
721
|
-
| Отдельный CRM `reserveStatus` | OCP FSM leak | Всегда `changeStatus` → `kind` |
|
|
722
|
-
| Mid-call `unknown` как полный OCP enum | Anti-corruption | Coarse + `reservedTarget` |
|
|
723
|
-
| `fetch` fallback к desktop HTTP | Forbidden | Только SDK WS protocol |
|
|
724
|
-
| Patch call graph только из events | Gaps / drift | Snapshot SSoT + events hints |
|
|
725
|
-
| Слепой retry со старым revision | `stale_state` loop | `getSnapshot()` first |
|
|
726
|
-
| Два «ведущих» dialer на вкладках | Races | Single-writer UX |
|
|
727
|
-
|
|
728
|
-
EN: [security-anti-patterns.md](../../docs/guide/security-anti-patterns.md).
|
|
869
|
+
SDK не совместим с legacy `window.Softphone` и не предоставляет HTTP fallback.
|
|
870
|
+
Переносите интеграцию на `createOmniCallClient()`, snapshot, публичные события и
|
|
871
|
+
namespaces клиента.
|
|
729
872
|
|
|
730
|
-
|
|
873
|
+
При `incompatible_version` остановите функции телефонии и предложите обновить
|
|
874
|
+
пакет или Desktop. Добавление необязательных полей совместимо. Удаление,
|
|
875
|
+
переименование или изменение смысла API требует новой major-версии. Не опирайтесь
|
|
876
|
+
на недокументированные поля сетевых сообщений.
|
|
731
877
|
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
- [ ] Установлен `@softomnitel/omnicall-kit@0.1.0` (или осознанный pin/`rc`)
|
|
735
|
-
- [ ] OmniCall Desktop запущен; SDK gateway / Integrations включены
|
|
736
|
-
- [ ] Origin CRM exact match; TOFU Allow проверен; blacklist path понятен
|
|
737
|
-
- [ ] Pairing UX: инструкция при `pairing_required`; revoke → clear + re-pair
|
|
738
|
-
- [ ] PoP в IndexedDB (`installId` стабилен); нет Web Storage для ключей
|
|
739
|
-
- [ ] `requestedCapabilities` без privileged ids
|
|
740
|
-
- [ ] UI строится из snapshot + `PUBLIC_EVENT_TYPES`
|
|
741
|
-
- [ ] Все мутации с fresh `expectedRevision`; обработка `stale_state`
|
|
742
|
-
- [ ] Calls: originate/answer/reject/hangup/hold/mute/dtmf покрыты UX + errors
|
|
743
|
-
- [ ] Operator: `changeStatus`/`finishAppeal`/`getReasons` без самодельного reserve
|
|
744
|
-
- [ ] Logout single-shot + `interaction_required` modal
|
|
745
|
-
- [ ] Privileged `window.hide` / `account.activate` — только после matrix grant + feature-detect
|
|
746
|
-
- [ ] Reconnect: banner, fresh snapshot, no mutation replay
|
|
747
|
-
- [ ] Multi-tab: single-writer или явный user retry
|
|
748
|
-
- [ ] LNA permission объяснена на HTTPS
|
|
749
|
-
- [ ] Логи без PII/secrets; ошибки по `code`
|
|
750
|
-
- [ ] Нет SIP/OCP secrets в CRM bundle
|
|
751
|
-
- [ ] Версии SDK/desktop совместимы ([compatibility-matrix.md](../../docs/guide/compatibility-matrix.md))
|
|
752
|
-
- [ ] Upgrade notes прочитаны ([upgrade-deprecation.md](../../docs/guide/upgrade-deprecation.md))
|
|
878
|
+
Правила для интегратора:
|
|
753
879
|
|
|
754
|
-
|
|
880
|
+
1. Используйте только публичные экспорты пакета `@softomnitel/omnicall-kit`.
|
|
881
|
+
2. Игнорируйте неизвестные необязательные поля во входящих объектах.
|
|
882
|
+
3. Не стройте логику на недокументированных wire-ключах.
|
|
883
|
+
4. При `incompatible_version` остановите telephony UI и запросите обновление.
|
|
755
884
|
|
|
756
|
-
##
|
|
885
|
+
## Лицензия
|
|
757
886
|
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
| [API reference](../../docs/guide/api-reference.md) | Namespaces + inventory 77 |
|
|
762
|
-
| [API report](../../etc/api/sdk.api.md) | Канон публичных символов |
|
|
763
|
-
| [Events](../../docs/guide/events.md) | Каталог событий |
|
|
764
|
-
| [Errors](../../docs/guide/errors.md) | Коды + next steps |
|
|
765
|
-
| [Capabilities](../../docs/guide/capabilities.md) | Profiles / privileged |
|
|
766
|
-
| [Pairing quick start](../../docs/guide/pairing-quick-start.md) | Connect ladder |
|
|
767
|
-
| [Transport](../../docs/guide/transport.md) | WebSocket port |
|
|
768
|
-
| [Installation](../../docs/guide/installation.md) | Engines / LNA |
|
|
769
|
-
| [TypeScript](../../docs/guide/typescript.md) | Imports / `OmniCallEventOf` |
|
|
770
|
-
| [Reconnect & multi-tab](../../docs/guide/reconnect-multi-tab.md) | Fresh snapshot policy |
|
|
771
|
-
| [Logout](../../docs/guide/logout-workflow.md) | Single-shot logout |
|
|
772
|
-
| [Saved-account activation](../../docs/guide/saved-profile-activation.md) | activateProfile |
|
|
773
|
-
| [Operator status & reservation](../../docs/guide/operator-status-reservation.md) | applied \| reserved |
|
|
774
|
-
| [Security anti-patterns](../../docs/guide/security-anti-patterns.md) | Forbidden table |
|
|
775
|
-
| [Release & support](../../docs/guide/release-and-support.md) | RC / stable / support |
|
|
776
|
-
| [Example crm-pairing-lite](../../examples/crm-pairing-lite/) | Fake-peer demo |
|
|
887
|
+
В `package.json` указано `UNLICENSED`. Это не open-source лицензия: не
|
|
888
|
+
предполагайте право на свободное распространение или изменение вне согласованного
|
|
889
|
+
контура. Уточните условия у владельца пакета.
|