@sippet-ai/operator-widget 0.0.12

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.
Files changed (59) hide show
  1. package/LICENSE +2 -0
  2. package/README.md +136 -0
  3. package/cdn/loader.js +146 -0
  4. package/custom-elements.json +2241 -0
  5. package/dist/components/index.d.ts +1 -0
  6. package/dist/components/index.js +1 -0
  7. package/dist/components/voip-widget/index.d.ts +18 -0
  8. package/dist/components/voip-widget/index.js +4 -0
  9. package/dist/components/voip-widget/voip-widget-contacts-tab.d.ts +14 -0
  10. package/dist/components/voip-widget/voip-widget-contacts-tab.js +45 -0
  11. package/dist/components/voip-widget/voip-widget-history-tab.d.ts +13 -0
  12. package/dist/components/voip-widget/voip-widget-history-tab.js +43 -0
  13. package/dist/components/voip-widget/voip-widget-launcher.d.ts +18 -0
  14. package/dist/components/voip-widget/voip-widget-launcher.js +100 -0
  15. package/dist/components/voip-widget/voip-widget-panel.d.ts +21 -0
  16. package/dist/components/voip-widget/voip-widget-panel.js +193 -0
  17. package/dist/components/voip-widget/voip-widget-phone-tab.d.ts +21 -0
  18. package/dist/components/voip-widget/voip-widget-phone-tab.js +187 -0
  19. package/dist/components/voip-widget/voip-widget-queue-tab.d.ts +14 -0
  20. package/dist/components/voip-widget/voip-widget-queue-tab.js +49 -0
  21. package/dist/components/voip-widget/voip-widget-settings-tab.d.ts +21 -0
  22. package/dist/components/voip-widget/voip-widget-settings-tab.js +135 -0
  23. package/dist/components/voip-widget/voip-widget.d.ts +142 -0
  24. package/dist/components/voip-widget/voip-widget.js +1329 -0
  25. package/dist/components/voip-widget/voip-widget.types.d.ts +22 -0
  26. package/dist/components/voip-widget/voip-widget.types.js +1 -0
  27. package/dist/index.d.ts +3 -0
  28. package/dist/index.js +3 -0
  29. package/dist/lib/realtime.d.ts +1 -0
  30. package/dist/lib/realtime.js +1 -0
  31. package/dist/lib/sippet.d.ts +40 -0
  32. package/dist/lib/sippet.js +197 -0
  33. package/dist/lib/tailwindMixin.d.ts +6 -0
  34. package/dist/lib/tailwindMixin.js +24 -0
  35. package/dist/styles/tailwind.global.css +2 -0
  36. package/dist/styles/tailwind.global.css.js +1 -0
  37. package/package.json +126 -0
  38. package/react/SippetAIVoipWidget.d.ts +158 -0
  39. package/react/SippetAIVoipWidget.js +72 -0
  40. package/react/SippetAIVoipWidgetContactsTab.d.ts +67 -0
  41. package/react/SippetAIVoipWidgetContactsTab.js +29 -0
  42. package/react/SippetAIVoipWidgetHistoryTab.d.ts +55 -0
  43. package/react/SippetAIVoipWidgetHistoryTab.js +26 -0
  44. package/react/SippetAIVoipWidgetLauncher.d.ts +83 -0
  45. package/react/SippetAIVoipWidgetLauncher.js +41 -0
  46. package/react/SippetAIVoipWidgetPanel.d.ts +87 -0
  47. package/react/SippetAIVoipWidgetPanel.js +43 -0
  48. package/react/SippetAIVoipWidgetPhoneTab.d.ts +87 -0
  49. package/react/SippetAIVoipWidgetPhoneTab.js +43 -0
  50. package/react/SippetAIVoipWidgetQueueTab.d.ts +67 -0
  51. package/react/SippetAIVoipWidgetQueueTab.js +27 -0
  52. package/react/SippetAIVoipWidgetSettingsTab.d.ts +91 -0
  53. package/react/SippetAIVoipWidgetSettingsTab.js +47 -0
  54. package/react/index.d.ts +8 -0
  55. package/react/index.js +8 -0
  56. package/react/react-utils.js +67 -0
  57. package/types/custom-element-jsx.d.ts +956 -0
  58. package/types/custom-element-svelte.d.ts +264 -0
  59. package/types/custom-element-vuejs.d.ts +234 -0
@@ -0,0 +1,1329 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ // @ts-nocheck
8
+ import { html, LitElement } from 'lit';
9
+ import { property, query, state } from 'lit/decorators.js';
10
+ import { UserAgent, Web } from 'sip.js';
11
+ import { TW } from '../../lib/tailwindMixin.js';
12
+ import { acceptCallQueueEntry, buildCSRFHeaders, DEFAULT_BASE_URL, initSocket, joinEventsChannel, issueOperatorSipAccess, listCallQueueEntries, listCalls, listContacts, setOperatorStatus, whoAmI, } from '@sippet-ai/sdk-js';
13
+ import './voip-widget-launcher.js';
14
+ import './voip-widget-panel.js';
15
+ import './voip-widget-phone-tab.js';
16
+ import './voip-widget-queue-tab.js';
17
+ import './voip-widget-contacts-tab.js';
18
+ import './voip-widget-history-tab.js';
19
+ import './voip-widget-settings-tab.js';
20
+ const TwLitElement = TW(LitElement);
21
+ const SESSION_SIP_TTL_MS = 9 * 60 * 60 * 1000;
22
+ const SESSION_SIP_REFRESH_BUFFER_MS = 10 * 60 * 1000;
23
+ const WIDGET_LOCK_KEY = 'sippetai_operator_widget_lock';
24
+ const WIDGET_LOCK_HEARTBEAT_MS = 2000;
25
+ const WIDGET_LOCK_STALE_MS = 8000;
26
+ /**
27
+ * A VoIP widget web component allowing real-time telephony for Sippet AI integrations
28
+ *
29
+ * @tag sippetai-voip-widget
30
+ * @since 0.0.1
31
+ * @status experimental
32
+ *
33
+ **/
34
+ export class SippetAIVoipWidget extends TwLitElement {
35
+ constructor() {
36
+ super(...arguments);
37
+ this.apiKey = '';
38
+ this.sessionAuth = false;
39
+ this.apiOrigin = '';
40
+ this.sipUrl = 'sip.sippet.ai';
41
+ this.sipUser = 'test-sip';
42
+ this.sipUserId = '';
43
+ this.sipPassword = 'supersecurepass';
44
+ this.sipWebSocketUrl = 'wss://sip.sippet.ai:7443';
45
+ this.userName = 'Agent';
46
+ this.userEmail = 'agent@example.com';
47
+ this.availability = 'logged_out';
48
+ this.queueCount = 0;
49
+ this.incomingCall = false;
50
+ this.incomingCallerName = 'Colleague';
51
+ this.incomingFromQueue = false;
52
+ this.contacts = [];
53
+ this.history = [];
54
+ this.queuedCalls = [];
55
+ this.open = false;
56
+ this.activeTab = 'phone';
57
+ this.dialNumber = '';
58
+ this.callState = 'idle';
59
+ this.sipStatus = 'idle';
60
+ this.callSeconds = 0;
61
+ this.isMuted = false;
62
+ this.isHeld = false;
63
+ this.dragOffsetX = 0;
64
+ this.dragOffsetY = 0;
65
+ this.dragX = 0;
66
+ this.dragY = 0;
67
+ this.audioInputs = [];
68
+ this.audioOutputs = [];
69
+ this.selectedMicId = '';
70
+ this.selectedSpeakerId = '';
71
+ this.hasMediaPermission = false;
72
+ this.widgetLocked = false;
73
+ this.sipConfigKey = '';
74
+ this.sessionSipCredentialsIssued = false;
75
+ this.widgetTabId = '';
76
+ this.handleWidgetStorage = (event) => {
77
+ if (event.key !== WIDGET_LOCK_KEY)
78
+ return;
79
+ this.syncWidgetLock();
80
+ };
81
+ this.handleVisibilityChange = () => {
82
+ if (document.visibilityState === 'visible') {
83
+ this.syncWidgetLock();
84
+ }
85
+ };
86
+ this.handlePageHide = () => {
87
+ this.releaseWidgetLock();
88
+ };
89
+ this.handleDeviceChange = () => {
90
+ void this.refreshDevices();
91
+ };
92
+ this.handleQueueEntryEvent = () => {
93
+ if (this.widgetLocked)
94
+ return;
95
+ void this.loadQueueEntries();
96
+ };
97
+ }
98
+ get sipServer() {
99
+ if (this.sipWebSocketUrl)
100
+ return this.sipWebSocketUrl;
101
+ if (!this.sipUrl)
102
+ return '';
103
+ return `wss://${this.sipUrl}/ws`;
104
+ }
105
+ ensureTabId() {
106
+ if (this.widgetTabId)
107
+ return;
108
+ if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {
109
+ this.widgetTabId = crypto.randomUUID();
110
+ }
111
+ else {
112
+ this.widgetTabId = `tab-${Math.random().toString(36).slice(2)}`;
113
+ }
114
+ }
115
+ readWidgetLock() {
116
+ try {
117
+ const raw = window.localStorage.getItem(WIDGET_LOCK_KEY);
118
+ if (!raw)
119
+ return null;
120
+ const parsed = JSON.parse(raw);
121
+ if (!parsed || typeof parsed !== 'object')
122
+ return null;
123
+ const id = parsed.id;
124
+ const updatedAt = parsed.updatedAt ?? parsed.updated_at;
125
+ if (typeof id !== 'string' || typeof updatedAt !== 'number')
126
+ return null;
127
+ return { id, updatedAt };
128
+ }
129
+ catch {
130
+ return null;
131
+ }
132
+ }
133
+ writeWidgetLock() {
134
+ this.ensureTabId();
135
+ try {
136
+ window.localStorage.setItem(WIDGET_LOCK_KEY, JSON.stringify({ id: this.widgetTabId, updatedAt: Date.now() }));
137
+ }
138
+ catch {
139
+ // Ignore storage failures.
140
+ }
141
+ }
142
+ releaseWidgetLock() {
143
+ try {
144
+ const lock = this.readWidgetLock();
145
+ if (!lock || lock.id !== this.widgetTabId)
146
+ return;
147
+ window.localStorage.removeItem(WIDGET_LOCK_KEY);
148
+ }
149
+ catch {
150
+ // Ignore storage failures.
151
+ }
152
+ }
153
+ startWidgetLockHeartbeat() {
154
+ if (this.widgetLockHeartbeat)
155
+ return;
156
+ this.widgetLockHeartbeat = window.setInterval(() => {
157
+ const lock = this.readWidgetLock();
158
+ if (!lock || lock.id !== this.widgetTabId) {
159
+ this.stopWidgetLockHeartbeat();
160
+ this.setWidgetLocked(true);
161
+ return;
162
+ }
163
+ this.writeWidgetLock();
164
+ }, WIDGET_LOCK_HEARTBEAT_MS);
165
+ }
166
+ stopWidgetLockHeartbeat() {
167
+ if (!this.widgetLockHeartbeat)
168
+ return;
169
+ window.clearInterval(this.widgetLockHeartbeat);
170
+ this.widgetLockHeartbeat = undefined;
171
+ }
172
+ setWidgetLocked(locked) {
173
+ if (this.widgetLocked === locked)
174
+ return;
175
+ this.widgetLocked = locked;
176
+ if (locked) {
177
+ void this.teardownSip();
178
+ this.teardownQueueChannel();
179
+ }
180
+ else {
181
+ void this.refreshSessionSipCredentials(true);
182
+ void this.setupSip();
183
+ this.initQueueChannel();
184
+ }
185
+ }
186
+ syncWidgetLock() {
187
+ if (typeof window === 'undefined' || !window.localStorage)
188
+ return;
189
+ this.ensureTabId();
190
+ const now = Date.now();
191
+ const lock = this.readWidgetLock();
192
+ if (!lock || now - lock.updatedAt > WIDGET_LOCK_STALE_MS) {
193
+ this.writeWidgetLock();
194
+ this.startWidgetLockHeartbeat();
195
+ this.setWidgetLocked(false);
196
+ return;
197
+ }
198
+ if (lock.id === this.widgetTabId) {
199
+ this.startWidgetLockHeartbeat();
200
+ this.setWidgetLocked(false);
201
+ return;
202
+ }
203
+ this.stopWidgetLockHeartbeat();
204
+ this.setWidgetLocked(true);
205
+ }
206
+ initWidgetLock() {
207
+ if (typeof window === 'undefined')
208
+ return;
209
+ this.syncWidgetLock();
210
+ if (!this.widgetLockPoller) {
211
+ this.widgetLockPoller = window.setInterval(() => {
212
+ this.syncWidgetLock();
213
+ }, WIDGET_LOCK_HEARTBEAT_MS);
214
+ }
215
+ window.addEventListener('storage', this.handleWidgetStorage);
216
+ window.addEventListener('visibilitychange', this.handleVisibilityChange);
217
+ window.addEventListener('pagehide', this.handlePageHide);
218
+ }
219
+ teardownWidgetLock() {
220
+ this.stopWidgetLockHeartbeat();
221
+ if (this.widgetLockPoller) {
222
+ window.clearInterval(this.widgetLockPoller);
223
+ this.widgetLockPoller = undefined;
224
+ }
225
+ window.removeEventListener('storage', this.handleWidgetStorage);
226
+ window.removeEventListener('visibilitychange', this.handleVisibilityChange);
227
+ window.removeEventListener('pagehide', this.handlePageHide);
228
+ this.releaseWidgetLock();
229
+ }
230
+ configureSdkEndpoints() {
231
+ if (!this.apiOrigin)
232
+ return;
233
+ if (typeof globalThis === 'undefined')
234
+ return;
235
+ const configHolder = globalThis;
236
+ configHolder.SippetAiSdkConfig = {
237
+ ...configHolder.SippetAiSdkConfig,
238
+ rpcRunEndpoint: `${this.apiOrigin}/rpc/run`,
239
+ rpcValidateEndpoint: `${this.apiOrigin}/rpc/validate`,
240
+ };
241
+ }
242
+ getRpcConfig() {
243
+ if (this.sessionAuth) {
244
+ this.configureSdkEndpoints();
245
+ return {
246
+ headers: buildCSRFHeaders(),
247
+ fetchOptions: { credentials: 'include' },
248
+ };
249
+ }
250
+ if (!this.apiKey)
251
+ return null;
252
+ return { headers: { 'x-api-key': this.apiKey } };
253
+ }
254
+ clearSipCredentialRefresh() {
255
+ if (!this.sipCredentialRefreshTimer)
256
+ return;
257
+ window.clearTimeout(this.sipCredentialRefreshTimer);
258
+ this.sipCredentialRefreshTimer = undefined;
259
+ }
260
+ scheduleSipCredentialRefresh(expiresAtMs) {
261
+ this.clearSipCredentialRefresh();
262
+ const now = Date.now();
263
+ const fallbackExpiry = now + SESSION_SIP_TTL_MS;
264
+ const expiry = expiresAtMs ?? fallbackExpiry;
265
+ const remaining = Math.max(0, expiry - now);
266
+ const refreshIn = Math.max(remaining - SESSION_SIP_REFRESH_BUFFER_MS, 60000);
267
+ this.sipCredentialRefreshTimer = window.setTimeout(() => {
268
+ void this.refreshSessionSipCredentials(true);
269
+ }, refreshIn);
270
+ }
271
+ parseSipExpiry(value) {
272
+ if (!value)
273
+ return null;
274
+ if (typeof value === 'number') {
275
+ return value < 1_000_000_000_000 ? value * 1000 : value;
276
+ }
277
+ if (typeof value === 'string') {
278
+ const parsed = Date.parse(value);
279
+ return Number.isNaN(parsed) ? null : parsed;
280
+ }
281
+ return null;
282
+ }
283
+ isSipCredentialExpiringSoon() {
284
+ if (!this.sipCredentialExpiresAt)
285
+ return true;
286
+ return (this.sipCredentialExpiresAt - Date.now() <=
287
+ SESSION_SIP_REFRESH_BUFFER_MS);
288
+ }
289
+ async refreshSessionSipCredentials(force = false) {
290
+ if (!this.sessionAuth)
291
+ return;
292
+ if (this.widgetLocked)
293
+ return;
294
+ if (this.sipCredentialPromise)
295
+ return this.sipCredentialPromise;
296
+ if (!force && this.sessionSipCredentialsIssued && !this.isSipCredentialExpiringSoon()) {
297
+ return;
298
+ }
299
+ const config = this.getRpcConfig();
300
+ if (!config)
301
+ return;
302
+ this.sipCredentialPromise = (async () => {
303
+ const result = await issueOperatorSipAccess({
304
+ input: {},
305
+ ...config,
306
+ });
307
+ if (!result.success) {
308
+ console.warn('SippetAIVoipWidget: failed to issue session SIP credentials.');
309
+ return;
310
+ }
311
+ const data = result.data;
312
+ const sip = data?.sip ??
313
+ data?.sipCredentials ??
314
+ data?.sip_credentials ??
315
+ data?.credentials ??
316
+ data;
317
+ if (!sip) {
318
+ console.warn('SippetAIVoipWidget: missing SIP credentials.');
319
+ return;
320
+ }
321
+ if (sip.username)
322
+ this.sipUser = sip.username;
323
+ if (sip.password)
324
+ this.sipPassword = sip.password;
325
+ if (sip.domain)
326
+ this.sipUrl = sip.domain;
327
+ if (sip.server)
328
+ this.sipWebSocketUrl = sip.server;
329
+ const expiresAt = this.parseSipExpiry(sip.expiresAt ??
330
+ sip.expires_at ??
331
+ data?.expiresAt ??
332
+ data?.expires_at) ??
333
+ Date.now() + SESSION_SIP_TTL_MS;
334
+ this.sipCredentialExpiresAt = expiresAt;
335
+ this.sessionSipCredentialsIssued = Boolean(sip.username && sip.password);
336
+ this.scheduleSipCredentialRefresh(expiresAt);
337
+ })().finally(() => {
338
+ this.sipCredentialPromise = undefined;
339
+ });
340
+ return this.sipCredentialPromise;
341
+ }
342
+ openPanel(tab = 'phone') {
343
+ this.open = true;
344
+ this.activeTab = tab;
345
+ }
346
+ async callNumber(phoneNumber) {
347
+ this.openPanel('phone');
348
+ this.dialNumber = phoneNumber;
349
+ return this.handleCall();
350
+ }
351
+ async answerCall() {
352
+ this.openPanel('phone');
353
+ return this.handleAccept();
354
+ }
355
+ async declineCall() {
356
+ this.openPanel('phone');
357
+ return this.handleDecline();
358
+ }
359
+ async toggleHold() {
360
+ this.openPanel('phone');
361
+ return this.handleHold();
362
+ }
363
+ transferTo(queue) {
364
+ this.openPanel('phone');
365
+ this.dialNumber = queue;
366
+ this.handleTransfer();
367
+ }
368
+ async refreshDevices() {
369
+ if (!navigator.mediaDevices?.enumerateDevices)
370
+ return;
371
+ try {
372
+ const devices = await navigator.mediaDevices.enumerateDevices();
373
+ const hasLabels = devices.some(device => device.label);
374
+ this.hasMediaPermission = hasLabels;
375
+ const audioInputs = devices
376
+ .filter(device => device.kind === 'audioinput')
377
+ .map((device, index) => ({
378
+ id: device.deviceId,
379
+ label: device.label || `Microphone ${index + 1}`,
380
+ }));
381
+ const audioOutputs = devices
382
+ .filter(device => device.kind === 'audiooutput')
383
+ .map((device, index) => ({
384
+ id: device.deviceId,
385
+ label: device.label || `Speaker ${index + 1}`,
386
+ }));
387
+ this.audioInputs = audioInputs;
388
+ this.audioOutputs = audioOutputs;
389
+ if (!this.selectedMicId && audioInputs[0]) {
390
+ this.selectedMicId = audioInputs[0].id;
391
+ }
392
+ if (!this.selectedSpeakerId && audioOutputs[0]) {
393
+ this.selectedSpeakerId = audioOutputs[0].id;
394
+ }
395
+ }
396
+ catch (error) {
397
+ // Ignore device enumeration errors.
398
+ }
399
+ }
400
+ async requestMediaPermission() {
401
+ if (!navigator.mediaDevices?.getUserMedia)
402
+ return;
403
+ try {
404
+ this.permissionStream = await navigator.mediaDevices.getUserMedia({
405
+ audio: true,
406
+ video: false,
407
+ });
408
+ await this.refreshDevices();
409
+ this.stopPermissionStream();
410
+ }
411
+ catch (error) {
412
+ // Ignore permission errors; labels may remain hidden.
413
+ }
414
+ }
415
+ async applySpeakerSelection() {
416
+ if (!this.remoteAudio)
417
+ return;
418
+ const element = this.remoteAudio;
419
+ if (!element.setSinkId || !this.selectedSpeakerId)
420
+ return;
421
+ try {
422
+ await element.setSinkId(this.selectedSpeakerId);
423
+ }
424
+ catch (error) {
425
+ // Some browsers restrict setSinkId without user gesture.
426
+ }
427
+ }
428
+ async setupSip() {
429
+ if (!this.remoteAudio)
430
+ return;
431
+ if (this.widgetLocked)
432
+ return;
433
+ if (this.sessionAuth) {
434
+ await this.refreshSessionSipCredentials();
435
+ if (!this.sessionSipCredentialsIssued)
436
+ return;
437
+ }
438
+ if (!this.sipUrl || !this.sipUser || !this.sipPassword)
439
+ return;
440
+ const server = this.sipServer;
441
+ if (!server)
442
+ return;
443
+ const aor = `sip:${this.sipUser}@${this.sipUrl}`;
444
+ const configKey = `${server}|${aor}|${this.sipPassword}|${this.userName}|${this.selectedMicId}`;
445
+ if (this.simpleUser && this.sipConfigKey === configKey)
446
+ return;
447
+ await this.teardownSip();
448
+ this.sipConfigKey = configKey;
449
+ this.sipStatus = 'connecting';
450
+ const uri = UserAgent.makeURI(aor);
451
+ const audioConstraint = this.selectedMicId
452
+ ? { deviceId: { exact: this.selectedMicId } }
453
+ : true;
454
+ const options = {
455
+ aor,
456
+ media: {
457
+ constraints: {
458
+ audio: audioConstraint,
459
+ video: false,
460
+ },
461
+ remote: { audio: this.remoteAudio },
462
+ },
463
+ delegate: {
464
+ onCallReceived: () => {
465
+ this.incomingCall = true;
466
+ this.incomingFromQueue = false;
467
+ this.setCallState('incoming');
468
+ },
469
+ onCallCreated: () => {
470
+ this.setCallState('outgoing');
471
+ },
472
+ onCallAnswered: () => {
473
+ this.incomingCall = false;
474
+ this.setCallState('active');
475
+ this.open = true;
476
+ },
477
+ onCallHangup: () => {
478
+ this.incomingCall = false;
479
+ this.setCallState('idle');
480
+ },
481
+ onCallHold: (held) => {
482
+ this.isHeld = held;
483
+ this.setCallState(held ? 'held' : 'active');
484
+ },
485
+ onServerConnect: () => {
486
+ this.sipStatus = 'connected';
487
+ },
488
+ onServerDisconnect: () => {
489
+ this.sipStatus = 'idle';
490
+ },
491
+ },
492
+ userAgentOptions: {
493
+ authorizationUsername: this.sipUser,
494
+ authorizationPassword: this.sipPassword,
495
+ displayName: this.userName,
496
+ uri: uri ?? undefined,
497
+ },
498
+ };
499
+ this.simpleUser = new Web.SimpleUser(server, options);
500
+ try {
501
+ await this.simpleUser.connect();
502
+ await this.simpleUser.register();
503
+ this.sipStatus = 'connected';
504
+ }
505
+ catch (error) {
506
+ this.sipStatus = 'error';
507
+ }
508
+ }
509
+ async teardownSip() {
510
+ if (!this.simpleUser)
511
+ return;
512
+ try {
513
+ await this.simpleUser.unregister();
514
+ await this.simpleUser.disconnect();
515
+ }
516
+ catch (error) {
517
+ // Best-effort cleanup.
518
+ }
519
+ this.simpleUser = undefined;
520
+ }
521
+ firstUpdated() {
522
+ this.configureSdkEndpoints();
523
+ this.initWidgetLock();
524
+ void this.refreshSessionSipCredentials();
525
+ void this.refreshDevices();
526
+ void this.setupSip();
527
+ void this.loadContacts();
528
+ void this.loadQueueEntries();
529
+ this.initQueueChannel();
530
+ if (navigator.mediaDevices) {
531
+ navigator.mediaDevices.addEventListener('devicechange', this.handleDeviceChange);
532
+ }
533
+ }
534
+ updated(changedProperties) {
535
+ if (changedProperties.has('apiOrigin')) {
536
+ this.configureSdkEndpoints();
537
+ if (this.sessionAuth) {
538
+ void this.refreshSessionSipCredentials(true);
539
+ }
540
+ this.resetQueueChannel();
541
+ }
542
+ if (changedProperties.has('sessionAuth')) {
543
+ this.sessionSipCredentialsIssued = false;
544
+ this.sipCredentialExpiresAt = undefined;
545
+ this.clearSipCredentialRefresh();
546
+ if (this.sessionAuth) {
547
+ void this.refreshSessionSipCredentials(true);
548
+ }
549
+ this.resetQueueChannel();
550
+ }
551
+ if (changedProperties.has('sipUrl') ||
552
+ changedProperties.has('sipUser') ||
553
+ changedProperties.has('sipPassword') ||
554
+ changedProperties.has('sipWebSocketUrl') ||
555
+ changedProperties.has('userName') ||
556
+ changedProperties.has('selectedMicId') ||
557
+ changedProperties.has('sessionAuth')) {
558
+ void this.setupSip();
559
+ }
560
+ if (changedProperties.has('selectedSpeakerId')) {
561
+ void this.applySpeakerSelection();
562
+ }
563
+ if (changedProperties.has('callState')) {
564
+ if (this.callState === 'active' || this.callState === 'held') {
565
+ this.startCallTimer();
566
+ }
567
+ else if (this.callState === 'idle') {
568
+ this.stopCallTimer();
569
+ }
570
+ }
571
+ if (changedProperties.has('open')) {
572
+ if (!this.open) {
573
+ this.activeTab = 'phone';
574
+ this.stopPermissionStream();
575
+ }
576
+ }
577
+ if (changedProperties.has('apiKey') || changedProperties.has('sessionAuth')) {
578
+ void this.loadContacts();
579
+ if (this.activeTab === 'history') {
580
+ void this.loadHistory();
581
+ }
582
+ void this.loadQueueEntries();
583
+ this.resetQueueChannel();
584
+ }
585
+ if (changedProperties.has('contacts') && this.history.length > 0) {
586
+ this.history = this.history.map(entry => ({
587
+ ...entry,
588
+ name: this.resolveHistoryName(entry.number),
589
+ }));
590
+ }
591
+ }
592
+ disconnectedCallback() {
593
+ void this.teardownSip();
594
+ this.stopCallTimer();
595
+ this.stopPermissionStream();
596
+ this.teardownQueueChannel();
597
+ this.clearSipCredentialRefresh();
598
+ this.teardownWidgetLock();
599
+ if (navigator.mediaDevices) {
600
+ navigator.mediaDevices.removeEventListener('devicechange', this.handleDeviceChange);
601
+ }
602
+ super.disconnectedCallback();
603
+ }
604
+ handleToggleOpen() {
605
+ this.open = !this.open;
606
+ if (this.open) {
607
+ void this.requestMediaPermission();
608
+ }
609
+ }
610
+ handleSelectTab(tab) {
611
+ this.activeTab = tab;
612
+ if (tab === 'history') {
613
+ void this.loadHistory();
614
+ }
615
+ if (tab === 'queue') {
616
+ void this.loadQueueEntries();
617
+ }
618
+ }
619
+ handleShowSettings() {
620
+ this.activeTab = 'settings';
621
+ }
622
+ handleStartDrag(event) {
623
+ this.dragOffsetX = event.clientX - this.dragX;
624
+ this.dragOffsetY = event.clientY - this.dragY;
625
+ }
626
+ handleDrag(event) {
627
+ if (!this.open)
628
+ return;
629
+ if (event.buttons === 0)
630
+ return;
631
+ this.dragX = event.clientX - this.dragOffsetX;
632
+ this.dragY = event.clientY - this.dragOffsetY;
633
+ }
634
+ handleEndDrag(event) {
635
+ void event;
636
+ }
637
+ resolveDialTarget() {
638
+ if (!this.dialNumber)
639
+ return '';
640
+ if (this.dialNumber.startsWith('sip:'))
641
+ return this.dialNumber;
642
+ if (this.dialNumber.includes('@'))
643
+ return `sip:${this.dialNumber}`;
644
+ return `sip:${this.dialNumber}@${this.sipUrl}`;
645
+ }
646
+ async handleCall() {
647
+ if (!this.simpleUser)
648
+ return;
649
+ const target = this.resolveDialTarget();
650
+ if (!target)
651
+ return;
652
+ try {
653
+ await this.simpleUser.call(target, {
654
+ sessionDescriptionHandlerOptions: {
655
+ constraints: { audio: true, video: false },
656
+ },
657
+ });
658
+ this.setCallState('active');
659
+ }
660
+ catch (error) {
661
+ this.setCallState('idle');
662
+ }
663
+ }
664
+ async handleHangup() {
665
+ if (!this.simpleUser)
666
+ return;
667
+ try {
668
+ await this.simpleUser.hangup();
669
+ }
670
+ catch (error) {
671
+ // Best-effort hangup.
672
+ }
673
+ this.setCallState('idle');
674
+ }
675
+ async handleAccept() {
676
+ if (!this.simpleUser)
677
+ return;
678
+ try {
679
+ await this.simpleUser.answer();
680
+ this.incomingCall = false;
681
+ this.setCallState('active');
682
+ this.open = true;
683
+ }
684
+ catch (error) {
685
+ // Best-effort.
686
+ }
687
+ }
688
+ async handleHold() {
689
+ if (!this.simpleUser)
690
+ return;
691
+ try {
692
+ if (this.callState === 'incoming') {
693
+ await this.simpleUser.answer();
694
+ this.setCallState('active');
695
+ }
696
+ if (this.isHeld) {
697
+ await this.simpleUser.unhold();
698
+ this.isHeld = false;
699
+ this.setCallState('active');
700
+ }
701
+ else {
702
+ await this.simpleUser.hold();
703
+ this.isHeld = true;
704
+ this.setCallState('held');
705
+ }
706
+ }
707
+ catch (error) {
708
+ // Best-effort.
709
+ }
710
+ }
711
+ async handleDecline() {
712
+ if (!this.simpleUser)
713
+ return;
714
+ try {
715
+ await this.simpleUser.decline();
716
+ }
717
+ catch (error) {
718
+ // Best-effort.
719
+ }
720
+ this.incomingCall = false;
721
+ this.setCallState('idle');
722
+ }
723
+ handleMuteToggle() {
724
+ if (!this.simpleUser)
725
+ return;
726
+ if (this.isMuted) {
727
+ this.simpleUser.unmute();
728
+ this.isMuted = false;
729
+ }
730
+ else {
731
+ this.simpleUser.mute();
732
+ this.isMuted = true;
733
+ }
734
+ }
735
+ handleTransfer() {
736
+ this.dispatchEvent(new CustomEvent('voip-transfer', {
737
+ detail: { number: this.dialNumber },
738
+ bubbles: true,
739
+ composed: true,
740
+ }));
741
+ }
742
+ emitCallState(state) {
743
+ this.dispatchEvent(new CustomEvent('voip-call-state', {
744
+ detail: { state },
745
+ bubbles: true,
746
+ composed: true,
747
+ }));
748
+ }
749
+ setCallState(state) {
750
+ this.callState = state;
751
+ this.emitCallState(state);
752
+ if (state === 'idle') {
753
+ this.callSeconds = 0;
754
+ this.isHeld = false;
755
+ this.isMuted = false;
756
+ }
757
+ }
758
+ startCallTimer() {
759
+ if (this.callTimer)
760
+ return;
761
+ this.callTimer = window.setInterval(() => {
762
+ this.callSeconds += 1;
763
+ }, 1000);
764
+ }
765
+ stopCallTimer() {
766
+ if (!this.callTimer)
767
+ return;
768
+ window.clearInterval(this.callTimer);
769
+ this.callTimer = undefined;
770
+ }
771
+ stopPermissionStream() {
772
+ if (!this.permissionStream)
773
+ return;
774
+ this.permissionStream.getTracks().forEach(track => track.stop());
775
+ this.permissionStream = undefined;
776
+ }
777
+ async loadContacts() {
778
+ const config = this.getRpcConfig();
779
+ if (!config) {
780
+ console.warn('SippetAIVoipWidget: api-key or session-auth is required to load contacts.');
781
+ this.contacts = [];
782
+ return;
783
+ }
784
+ const result = await listContacts({
785
+ fields: ['id', 'fullName', 'phoneE164'],
786
+ ...config,
787
+ });
788
+ if (!result.success) {
789
+ this.contacts = [];
790
+ return;
791
+ }
792
+ this.contacts = result.data
793
+ .filter(contact => Boolean(contact.phoneE164))
794
+ .map(contact => ({
795
+ name: contact.fullName || contact.phoneE164 || 'Unknown',
796
+ number: contact.phoneE164 ?? '',
797
+ }));
798
+ }
799
+ resolveHistoryName(number) {
800
+ if (!number)
801
+ return 'Unknown';
802
+ const match = this.contacts.find(contact => contact.number === number);
803
+ return match?.name ?? number;
804
+ }
805
+ formatHistoryTime(value) {
806
+ if (!value)
807
+ return 'Unknown';
808
+ const date = new Date(value);
809
+ if (Number.isNaN(date.getTime()))
810
+ return 'Unknown';
811
+ const now = new Date();
812
+ const isToday = date.toDateString() === now.toDateString();
813
+ if (isToday) {
814
+ return date.toLocaleTimeString([], {
815
+ hour: '2-digit',
816
+ minute: '2-digit',
817
+ });
818
+ }
819
+ return date.toLocaleDateString([], { month: 'short', day: 'numeric' });
820
+ }
821
+ async loadHistory() {
822
+ const config = this.getRpcConfig();
823
+ if (!config) {
824
+ console.warn('SippetAIVoipWidget: api-key or session-auth is required to load call history.');
825
+ this.history = [];
826
+ return;
827
+ }
828
+ const result = await listCalls({
829
+ fields: ['direction', 'fromNumber', 'toNumber', 'startedAt', 'status'],
830
+ ...config,
831
+ sort: '-startedAt',
832
+ page: { limit: 20 },
833
+ });
834
+ if (!result.success) {
835
+ this.history = [];
836
+ return;
837
+ }
838
+ const data = Array.isArray(result.data)
839
+ ? result.data
840
+ : (result.data?.results ?? []);
841
+ this.history = data.map(call => {
842
+ const number = call.direction === 'inbound' ? call.fromNumber : call.toNumber;
843
+ return {
844
+ name: this.resolveHistoryName(number),
845
+ number: number ?? '',
846
+ time: this.formatHistoryTime(call.startedAt ?? null),
847
+ type: call.direction === 'inbound' ? 'in' : 'out',
848
+ };
849
+ });
850
+ }
851
+ initQueueChannel() {
852
+ if (this.widgetLocked)
853
+ return;
854
+ if (this.queueChannel)
855
+ return;
856
+ if (!this.apiKey && !this.sessionAuth)
857
+ return;
858
+ const baseUrl = this.apiOrigin || DEFAULT_BASE_URL;
859
+ try {
860
+ this.queueSocket = initSocket({
861
+ publishableKey: this.apiKey || undefined,
862
+ baseUrl,
863
+ });
864
+ const channel = joinEventsChannel({}, this.queueSocket);
865
+ this.queueChannel = channel;
866
+ this.queueChannelUpdateRef = channel.on('call_queue_entry_updated', this.handleQueueEntryEvent);
867
+ this.queueChannelDeleteRef = channel.on('call_queue_entry_deleted', this.handleQueueEntryEvent);
868
+ }
869
+ catch (error) {
870
+ console.warn('SippetAIVoipWidget: failed to join events channel.');
871
+ }
872
+ }
873
+ teardownQueueChannel() {
874
+ if (this.queueChannel) {
875
+ if (this.queueChannelUpdateRef !== undefined) {
876
+ this.queueChannel.off('call_queue_entry_updated', this.queueChannelUpdateRef);
877
+ }
878
+ if (this.queueChannelDeleteRef !== undefined) {
879
+ this.queueChannel.off('call_queue_entry_deleted', this.queueChannelDeleteRef);
880
+ }
881
+ this.queueChannel.leave();
882
+ }
883
+ this.queueChannel = undefined;
884
+ this.queueChannelUpdateRef = undefined;
885
+ this.queueChannelDeleteRef = undefined;
886
+ if (this.queueSocket) {
887
+ this.queueSocket.disconnect();
888
+ this.queueSocket = undefined;
889
+ }
890
+ }
891
+ resetQueueChannel() {
892
+ this.teardownQueueChannel();
893
+ this.initQueueChannel();
894
+ }
895
+ formatQueueWaitTime(value) {
896
+ if (!value)
897
+ return '0:00';
898
+ const startedAt = new Date(value);
899
+ if (Number.isNaN(startedAt.getTime()))
900
+ return '0:00';
901
+ const seconds = Math.max(0, Math.floor((Date.now() - startedAt.getTime()) / 1000));
902
+ const minutes = Math.floor(seconds / 60);
903
+ const remainingSeconds = seconds % 60;
904
+ return `${minutes}:${String(remainingSeconds).padStart(2, '0')}`;
905
+ }
906
+ async loadQueueEntries() {
907
+ const config = this.getRpcConfig();
908
+ if (!config) {
909
+ console.warn('SippetAIVoipWidget: api-key or session-auth is required to load queue entries.');
910
+ this.queuedCalls = [];
911
+ this.queueCount = 0;
912
+ return;
913
+ }
914
+ const result = await listCallQueueEntries({
915
+ fields: [
916
+ 'id',
917
+ 'callerIdName',
918
+ 'callerIdNumber',
919
+ 'waitStartedAt',
920
+ 'state',
921
+ ],
922
+ filter: { state: { eq: 'waiting' } },
923
+ sort: 'waitStartedAt',
924
+ page: { limit: 20 },
925
+ ...config,
926
+ });
927
+ if (!result.success) {
928
+ this.queuedCalls = [];
929
+ this.queueCount = 0;
930
+ return;
931
+ }
932
+ const data = Array.isArray(result.data)
933
+ ? result.data
934
+ : (result.data?.results ?? []);
935
+ this.queuedCalls = data.map(entry => ({
936
+ id: entry.id,
937
+ name: entry.callerIdName || entry.callerIdNumber || 'Unknown caller',
938
+ time: this.formatQueueWaitTime(entry.waitStartedAt ?? null),
939
+ }));
940
+ this.queueCount = this.queuedCalls.length;
941
+ }
942
+ async acceptQueueEntry(entryId) {
943
+ const config = this.getRpcConfig();
944
+ if (!config)
945
+ return;
946
+ if (!this.sipUser) {
947
+ console.warn('SippetAIVoipWidget: sipUser is required to accept queue entries.');
948
+ return;
949
+ }
950
+ const result = await acceptCallQueueEntry({
951
+ identity: entryId,
952
+ input: {
953
+ operatorName: this.sipUser,
954
+ },
955
+ ...config,
956
+ });
957
+ if (!result.success) {
958
+ console.warn('SippetAIVoipWidget: failed to accept queue entry.');
959
+ return;
960
+ }
961
+ void this.loadQueueEntries();
962
+ }
963
+ async resolveSipUserId() {
964
+ if (this.sipUserId)
965
+ return this.sipUserId;
966
+ if (!this.sessionAuth)
967
+ return undefined;
968
+ const config = this.getRpcConfig();
969
+ if (!config)
970
+ return undefined;
971
+ const result = await whoAmI({
972
+ fields: ['id', { sipUser: ['id', 'sipUsername'] }],
973
+ ...config,
974
+ });
975
+ if (!result.success) {
976
+ return undefined;
977
+ }
978
+ const sipUser = result.data?.sipUser;
979
+ if (sipUser?.id) {
980
+ this.sipUserId = sipUser.id;
981
+ }
982
+ if (!this.sipUser && sipUser?.sipUsername) {
983
+ this.sipUser = sipUser.sipUsername;
984
+ }
985
+ return this.sipUserId || undefined;
986
+ }
987
+ async updateOperatorStatus(value) {
988
+ const config = this.getRpcConfig();
989
+ if (!config)
990
+ return;
991
+ const sipUserId = await this.resolveSipUserId();
992
+ if (!sipUserId)
993
+ return;
994
+ if (!['available', 'on_break', 'logged_out'].includes(value))
995
+ return;
996
+ const result = await setOperatorStatus({
997
+ input: {
998
+ sipUserId,
999
+ status: value,
1000
+ source: 'widget',
1001
+ },
1002
+ ...config,
1003
+ });
1004
+ if (!result.success) {
1005
+ console.warn('SippetAIVoipWidget: failed to update operator status.');
1006
+ }
1007
+ }
1008
+ render() {
1009
+ const widgetStyle = this.open
1010
+ ? `transform: translate(${this.dragX}px, ${this.dragY}px);`
1011
+ : '';
1012
+ const widgetClass = 'fixed bottom-6 right-6 z-50 font-sans';
1013
+ const contentClass = this.widgetLocked
1014
+ ? 'pointer-events-none opacity-50'
1015
+ : '';
1016
+ const lockOverlay = this.widgetLocked
1017
+ ? html `
1018
+ <div
1019
+ class="absolute inset-0 z-50 flex items-center justify-center"
1020
+ title="Use the widget opened in the first tab."
1021
+ >
1022
+ <div
1023
+ class="rounded-xl bg-slate-900/80 px-3 py-2 text-xs font-semibold text-white shadow"
1024
+ >
1025
+ Widget active in another tab
1026
+ </div>
1027
+ </div>
1028
+ `
1029
+ : null;
1030
+ return html `
1031
+ <div class=${widgetClass} style=${widgetStyle}>
1032
+ <div class=${contentClass}>
1033
+ ${this.open
1034
+ ? null
1035
+ : html `
1036
+ <sippetai-voip-widget-launcher
1037
+ .open=${this.open}
1038
+ .queueCount=${this.queueCount}
1039
+ .incomingCall=${this.incomingCall}
1040
+ .incomingFromQueue=${this.incomingFromQueue}
1041
+ .incomingCallerName=${this.incomingCallerName}
1042
+ @voip-toggle=${this.handleToggleOpen}
1043
+ @voip-accept=${this.handleAccept}
1044
+ @voip-hold=${this.handleHold}
1045
+ @voip-decline=${this.handleDecline}
1046
+ ></sippetai-voip-widget-launcher>
1047
+ `}
1048
+ ${this.open
1049
+ ? html `
1050
+ <div class="mt-3">
1051
+ <sippetai-voip-widget-panel
1052
+ .userName=${this.userName}
1053
+ .availability=${this.availability}
1054
+ .activeTab=${this.activeTab}
1055
+ .incomingCall=${this.incomingCall}
1056
+ .incomingFromQueue=${this.incomingFromQueue}
1057
+ .incomingCallerName=${this.incomingCallerName}
1058
+ @voip-close=${this.handleToggleOpen}
1059
+ @voip-settings=${this.handleShowSettings}
1060
+ @voip-tab=${(event) => this.handleSelectTab(event.detail.tab)}
1061
+ @voip-availability=${(event) => {
1062
+ this.availability = event.detail.value;
1063
+ void this.updateOperatorStatus(event.detail.value);
1064
+ }}
1065
+ @voip-accept=${this.handleAccept}
1066
+ @voip-hold=${this.handleHold}
1067
+ @voip-decline=${this.handleDecline}
1068
+ @voip-drag-start=${(event) => {
1069
+ const { x, y } = event.detail;
1070
+ this.handleStartDrag(new PointerEvent('pointerdown', {
1071
+ clientX: x,
1072
+ clientY: y,
1073
+ }));
1074
+ }}
1075
+ @voip-drag-move=${(event) => {
1076
+ const { x, y, buttons } = event.detail;
1077
+ this.handleDrag(new PointerEvent('pointermove', {
1078
+ clientX: x,
1079
+ clientY: y,
1080
+ buttons,
1081
+ }));
1082
+ }}
1083
+ @voip-drag-end=${() => {
1084
+ this.handleEndDrag(new PointerEvent('pointerup'));
1085
+ }}
1086
+ >
1087
+ ${this.activeTab === 'phone'
1088
+ ? html `
1089
+ ${this.incomingCall
1090
+ ? html `
1091
+ <div
1092
+ class="flex h-full flex-col items-center justify-center gap-4 rounded-2xl bg-slate-900 px-4 py-6 text-center text-white"
1093
+ >
1094
+ <div
1095
+ class="text-xs font-semibold uppercase tracking-wide text-white"
1096
+ >
1097
+ Incoming call
1098
+ ${this.incomingFromQueue ? '(queue)' : ''}
1099
+ </div>
1100
+ <div class="text-lg font-semibold text-white">
1101
+ ${this.incomingCallerName}
1102
+ </div>
1103
+ <div
1104
+ class="flex flex-wrap justify-center gap-2"
1105
+ >
1106
+ <button
1107
+ class="inline-flex items-center gap-2 rounded-xl bg-emerald-600 px-4 py-2 text-xs font-semibold text-white"
1108
+ @click=${this.handleAccept}
1109
+ >
1110
+ Accept
1111
+ </button>
1112
+ <button
1113
+ class="inline-flex items-center gap-2 rounded-xl bg-amber-400 px-4 py-2 text-xs font-semibold text-slate-900"
1114
+ @click=${this.handleHold}
1115
+ >
1116
+ Hold
1117
+ </button>
1118
+ <button
1119
+ class="inline-flex items-center gap-2 rounded-xl bg-red-600 px-4 py-2 text-xs font-semibold text-white"
1120
+ @click=${this.handleDecline}
1121
+ >
1122
+ Decline
1123
+ </button>
1124
+ </div>
1125
+ </div>
1126
+ `
1127
+ : html `
1128
+ <sippetai-voip-widget-phone-tab
1129
+ .dialNumber=${this.dialNumber}
1130
+ .sipStatus=${this.sipStatus}
1131
+ .callState=${this.callState}
1132
+ .callSeconds=${this.callSeconds}
1133
+ .isMuted=${this.isMuted}
1134
+ .isHeld=${this.isHeld}
1135
+ @voip-dial-change=${(event) => {
1136
+ this.dialNumber = event.detail.value;
1137
+ }}
1138
+ @voip-dial-digit=${(event) => {
1139
+ this.dialNumber = `${this.dialNumber}${event.detail.digit}`;
1140
+ }}
1141
+ @voip-call=${this.handleCall}
1142
+ @voip-hangup=${this.handleHangup}
1143
+ @voip-hold=${this.handleHold}
1144
+ @voip-mute=${this.handleMuteToggle}
1145
+ @voip-transfer=${this.handleTransfer}
1146
+ ></sippetai-voip-widget-phone-tab>
1147
+ `}
1148
+ `
1149
+ : null}
1150
+ ${this.activeTab === 'queue'
1151
+ ? html `
1152
+ <sippetai-voip-widget-queue-tab
1153
+ .queuedCalls=${this.queuedCalls}
1154
+ @voip-queue-pickup=${(event) => {
1155
+ const entryId = event.detail?.id;
1156
+ if (entryId) {
1157
+ void this.acceptQueueEntry(entryId);
1158
+ }
1159
+ }}
1160
+ ></sippetai-voip-widget-queue-tab>
1161
+ `
1162
+ : null}
1163
+ ${this.activeTab === 'contacts'
1164
+ ? html `
1165
+ <sippetai-voip-widget-contacts-tab
1166
+ .contacts=${this.contacts}
1167
+ @voip-contact-call=${(event) => {
1168
+ this.openPanel('phone');
1169
+ this.dialNumber = event.detail.number;
1170
+ void this.handleCall();
1171
+ }}
1172
+ ></sippetai-voip-widget-contacts-tab>
1173
+ `
1174
+ : null}
1175
+ ${this.activeTab === 'history'
1176
+ ? html `
1177
+ <sippetai-voip-widget-history-tab
1178
+ .history=${this.history}
1179
+ ></sippetai-voip-widget-history-tab>
1180
+ `
1181
+ : null}
1182
+ ${this.activeTab === 'settings'
1183
+ ? html `
1184
+ <sippetai-voip-widget-settings-tab
1185
+ .userEmail=${this.userEmail}
1186
+ .audioInputs=${this.audioInputs}
1187
+ .audioOutputs=${this.audioOutputs}
1188
+ .selectedMicId=${this.selectedMicId}
1189
+ .selectedSpeakerId=${this.selectedSpeakerId}
1190
+ .hasMediaPermission=${this.hasMediaPermission}
1191
+ .sipStatus=${this.sipStatus}
1192
+ @voip-email-change=${(event) => {
1193
+ this.userEmail = event.detail.value;
1194
+ }}
1195
+ @voip-mic-change=${(event) => {
1196
+ this.selectedMicId = event.detail.value;
1197
+ }}
1198
+ @voip-speaker-change=${(event) => {
1199
+ this.selectedSpeakerId = event.detail.value;
1200
+ }}
1201
+ @voip-request-permission=${() => {
1202
+ void this.requestMediaPermission();
1203
+ }}
1204
+ ></sippetai-voip-widget-settings-tab>
1205
+ `
1206
+ : null}
1207
+ </sippetai-voip-widget-panel>
1208
+ </div>
1209
+ `
1210
+ : null}
1211
+ </div>
1212
+ ${lockOverlay}
1213
+ </div>
1214
+ <audio class="hidden" autoplay></audio>
1215
+ `;
1216
+ }
1217
+ }
1218
+ __decorate([
1219
+ property({ type: String, attribute: 'api-key' })
1220
+ ], SippetAIVoipWidget.prototype, "apiKey", void 0);
1221
+ __decorate([
1222
+ property({ type: Boolean, attribute: 'session-auth' })
1223
+ ], SippetAIVoipWidget.prototype, "sessionAuth", void 0);
1224
+ __decorate([
1225
+ property({ type: String, attribute: 'api-origin' })
1226
+ ], SippetAIVoipWidget.prototype, "apiOrigin", void 0);
1227
+ __decorate([
1228
+ property({ type: String })
1229
+ ], SippetAIVoipWidget.prototype, "sipUrl", void 0);
1230
+ __decorate([
1231
+ property({ type: String })
1232
+ ], SippetAIVoipWidget.prototype, "sipUser", void 0);
1233
+ __decorate([
1234
+ property({ type: String, attribute: 'sip-user-id' })
1235
+ ], SippetAIVoipWidget.prototype, "sipUserId", void 0);
1236
+ __decorate([
1237
+ property({ type: String })
1238
+ ], SippetAIVoipWidget.prototype, "sipPassword", void 0);
1239
+ __decorate([
1240
+ property({ type: String })
1241
+ ], SippetAIVoipWidget.prototype, "sipWebSocketUrl", void 0);
1242
+ __decorate([
1243
+ property({ type: String })
1244
+ ], SippetAIVoipWidget.prototype, "userName", void 0);
1245
+ __decorate([
1246
+ property({ type: String })
1247
+ ], SippetAIVoipWidget.prototype, "userEmail", void 0);
1248
+ __decorate([
1249
+ property({ type: String })
1250
+ ], SippetAIVoipWidget.prototype, "availability", void 0);
1251
+ __decorate([
1252
+ property({ type: Number })
1253
+ ], SippetAIVoipWidget.prototype, "queueCount", void 0);
1254
+ __decorate([
1255
+ property({ type: Boolean })
1256
+ ], SippetAIVoipWidget.prototype, "incomingCall", void 0);
1257
+ __decorate([
1258
+ property({ type: String })
1259
+ ], SippetAIVoipWidget.prototype, "incomingCallerName", void 0);
1260
+ __decorate([
1261
+ property({ type: Boolean })
1262
+ ], SippetAIVoipWidget.prototype, "incomingFromQueue", void 0);
1263
+ __decorate([
1264
+ property({ type: Array })
1265
+ ], SippetAIVoipWidget.prototype, "contacts", void 0);
1266
+ __decorate([
1267
+ property({ type: Array })
1268
+ ], SippetAIVoipWidget.prototype, "history", void 0);
1269
+ __decorate([
1270
+ property({ type: Array })
1271
+ ], SippetAIVoipWidget.prototype, "queuedCalls", void 0);
1272
+ __decorate([
1273
+ property({ type: Boolean, reflect: true })
1274
+ ], SippetAIVoipWidget.prototype, "open", void 0);
1275
+ __decorate([
1276
+ property({ type: String })
1277
+ ], SippetAIVoipWidget.prototype, "activeTab", void 0);
1278
+ __decorate([
1279
+ state()
1280
+ ], SippetAIVoipWidget.prototype, "dialNumber", void 0);
1281
+ __decorate([
1282
+ state()
1283
+ ], SippetAIVoipWidget.prototype, "callState", void 0);
1284
+ __decorate([
1285
+ state()
1286
+ ], SippetAIVoipWidget.prototype, "sipStatus", void 0);
1287
+ __decorate([
1288
+ state()
1289
+ ], SippetAIVoipWidget.prototype, "callSeconds", void 0);
1290
+ __decorate([
1291
+ state()
1292
+ ], SippetAIVoipWidget.prototype, "isMuted", void 0);
1293
+ __decorate([
1294
+ state()
1295
+ ], SippetAIVoipWidget.prototype, "isHeld", void 0);
1296
+ __decorate([
1297
+ state()
1298
+ ], SippetAIVoipWidget.prototype, "dragOffsetX", void 0);
1299
+ __decorate([
1300
+ state()
1301
+ ], SippetAIVoipWidget.prototype, "dragOffsetY", void 0);
1302
+ __decorate([
1303
+ state()
1304
+ ], SippetAIVoipWidget.prototype, "dragX", void 0);
1305
+ __decorate([
1306
+ state()
1307
+ ], SippetAIVoipWidget.prototype, "dragY", void 0);
1308
+ __decorate([
1309
+ state()
1310
+ ], SippetAIVoipWidget.prototype, "audioInputs", void 0);
1311
+ __decorate([
1312
+ state()
1313
+ ], SippetAIVoipWidget.prototype, "audioOutputs", void 0);
1314
+ __decorate([
1315
+ state()
1316
+ ], SippetAIVoipWidget.prototype, "selectedMicId", void 0);
1317
+ __decorate([
1318
+ state()
1319
+ ], SippetAIVoipWidget.prototype, "selectedSpeakerId", void 0);
1320
+ __decorate([
1321
+ state()
1322
+ ], SippetAIVoipWidget.prototype, "hasMediaPermission", void 0);
1323
+ __decorate([
1324
+ state()
1325
+ ], SippetAIVoipWidget.prototype, "widgetLocked", void 0);
1326
+ __decorate([
1327
+ query('audio')
1328
+ ], SippetAIVoipWidget.prototype, "remoteAudio", void 0);
1329
+ export default SippetAIVoipWidget;