@snugdesk/avaya-ipo-widget 0.2.3 → 0.2.4

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/index.d.ts CHANGED
@@ -62,6 +62,9 @@ declare class AvayaIPOService {
62
62
  getActiveCallCount(): number;
63
63
  setLastIncomingCall(callId: string, farEndNumber: string): void;
64
64
  private startRingTone;
65
+ private ringRetryHandler;
66
+ private armRingRetryOnGesture;
67
+ private disarmRingRetry;
65
68
  private stopRingTone;
66
69
  private agentId;
67
70
  private loginStatusSubject;
@@ -177,12 +180,18 @@ declare class EntitiesSearchService {
177
180
  private http;
178
181
  private authenticationService;
179
182
  private nameCache;
183
+ private entityIdCache;
180
184
  constructor(http: HttpClient, authenticationService: AuthenticationService);
181
185
  fetchContacts(tenantId: string, size?: number, searchAfter?: [string, string]): Promise<any>;
182
186
  searchByPhone(tenantId: string, phoneNumber: string, size?: number): Promise<any>;
183
187
  searchEntities(tenantId: string, query: string, size?: number, from?: number): Promise<any>;
184
188
  getCachedName(tenantId: string, phoneNumber: string): string | null;
185
189
  lookupNameByPhone(tenantId: string, phoneNumber: string, size?: number): Promise<string | null>;
190
+ /** Resolve the entity (id + name) matching a phone number; both are cached per tenant+number. */
191
+ lookupEntityByPhone(tenantId: string, phoneNumber: string, size?: number): Promise<{
192
+ id: string | null;
193
+ name: string | null;
194
+ }>;
186
195
  private postEntities;
187
196
  private cacheKey;
188
197
  private ensureWildcard;
@@ -212,6 +221,9 @@ declare class AvayaIPOWidgetComponent implements OnInit, OnDestroy, OnChanges {
212
221
  */
213
222
  displayMode: 'popup' | 'standalone';
214
223
  configurationMode?: string;
224
+ /** Recording upload strategy: 'complete' (one upload at call end) or 'chunked' (live-streamed
225
+ * over the recording WebSocket, survives a dead tab). Defaults to APP_CONFIG.RECORDING_UPLOAD_MODE. */
226
+ recordingUploadMode?: 'complete' | 'chunked';
215
227
  avayaIPOServerIP?: string;
216
228
  avayaIPOServerPort?: number;
217
229
  avayaIPOServerStunServerIP?: string;
@@ -248,8 +260,7 @@ declare class AvayaIPOWidgetComponent implements OnInit, OnDestroy, OnChanges {
248
260
  number: string;
249
261
  name: string | null;
250
262
  }>;
251
- readonly avayaLogoUrl: string;
252
- readonly widgetVersion: "0.2.3";
263
+ readonly widgetVersion: "0.2.4";
253
264
  isReconnectingInProgress: boolean;
254
265
  isFailoverInProgress: boolean;
255
266
  isFailbackInProgress: boolean;
@@ -270,6 +281,9 @@ declare class AvayaIPOWidgetComponent implements OnInit, OnDestroy, OnChanges {
270
281
  hideTabs: boolean;
271
282
  darkModeEnabled: boolean;
272
283
  activeCallNumber: string | null;
284
+ /** Entity id of the current call's counterpart (resolved via OpenSearch); sent with the
285
+ * chunked-recording socket so chunks land under <tenantId>/<entityId>/avaya-ipo-chunks/. */
286
+ activeCallEntityId: string | null;
273
287
  incomingCallName: string | null;
274
288
  incomingNameResolved: boolean;
275
289
  activeCallName: string | null;
@@ -337,7 +351,7 @@ declare class AvayaIPOWidgetComponent implements OnInit, OnDestroy, OnChanges {
337
351
  private clearSession;
338
352
  private loadAwlScript;
339
353
  static ɵfac: i0.ɵɵFactoryDeclaration<AvayaIPOWidgetComponent, never>;
340
- static ɵcmp: i0.ɵɵComponentDeclaration<AvayaIPOWidgetComponent, "snugdesk-avaya-ipo-widget", never, { "isVisible": { "alias": "isVisible"; "required": false; }; "displayMode": { "alias": "displayMode"; "required": false; }; "configurationMode": { "alias": "configurationMode"; "required": false; }; "avayaIPOServerIP": { "alias": "avayaIPOServerIP"; "required": false; }; "avayaIPOServerPort": { "alias": "avayaIPOServerPort"; "required": false; }; "avayaIPOServerStunServerIP": { "alias": "avayaIPOServerStunServerIP"; "required": false; }; "avayaIPOServerStunServerPort": { "alias": "avayaIPOServerStunServerPort"; "required": false; }; "avayaIPOServerTurnServerIP": { "alias": "avayaIPOServerTurnServerIP"; "required": false; }; "avayaIPOServerTurnServerPort": { "alias": "avayaIPOServerTurnServerPort"; "required": false; }; "token": { "alias": "token"; "required": false; }; "tenantId": { "alias": "tenantId"; "required": false; }; "userId": { "alias": "userId"; "required": false; }; "extension": { "alias": "extension"; "required": false; }; "password": { "alias": "password"; "required": false; }; "showHistory": { "alias": "showHistory"; "required": false; }; "showContacts": { "alias": "showContacts"; "required": false; }; }, { "notificationEvent": "notificationEvent"; "incomingCall": "incomingCall"; }, never, never, false, never>;
354
+ static ɵcmp: i0.ɵɵComponentDeclaration<AvayaIPOWidgetComponent, "snugdesk-avaya-ipo-widget", never, { "isVisible": { "alias": "isVisible"; "required": false; }; "displayMode": { "alias": "displayMode"; "required": false; }; "configurationMode": { "alias": "configurationMode"; "required": false; }; "recordingUploadMode": { "alias": "recordingUploadMode"; "required": false; }; "avayaIPOServerIP": { "alias": "avayaIPOServerIP"; "required": false; }; "avayaIPOServerPort": { "alias": "avayaIPOServerPort"; "required": false; }; "avayaIPOServerStunServerIP": { "alias": "avayaIPOServerStunServerIP"; "required": false; }; "avayaIPOServerStunServerPort": { "alias": "avayaIPOServerStunServerPort"; "required": false; }; "avayaIPOServerTurnServerIP": { "alias": "avayaIPOServerTurnServerIP"; "required": false; }; "avayaIPOServerTurnServerPort": { "alias": "avayaIPOServerTurnServerPort"; "required": false; }; "token": { "alias": "token"; "required": false; }; "tenantId": { "alias": "tenantId"; "required": false; }; "userId": { "alias": "userId"; "required": false; }; "extension": { "alias": "extension"; "required": false; }; "password": { "alias": "password"; "required": false; }; "showHistory": { "alias": "showHistory"; "required": false; }; "showContacts": { "alias": "showContacts"; "required": false; }; }, { "notificationEvent": "notificationEvent"; "incomingCall": "incomingCall"; }, never, never, false, never>;
341
355
  }
342
356
 
343
357
  declare class CallHistoryComponent implements OnInit {
@@ -382,9 +396,6 @@ declare class CallHistoryComponent implements OnInit {
382
396
  selectFromHistory(item: any): void;
383
397
  private resolveDialable;
384
398
  private resolveName;
385
- private formatDialOutFromInput;
386
- private getTenantIdFromSession;
387
- private getUserIdFromSession;
388
399
  getPhoneDisplay(item: any): string;
389
400
  getPhoneBadge(item: any): string;
390
401
  private getInitials;
@@ -398,28 +409,47 @@ type TranscriptLine = {
398
409
  speaker?: string;
399
410
  text?: string;
400
411
  };
401
- declare class CallDetailsComponent {
402
- private http;
412
+ /**
413
+ * Shows the same AI call analysis as snugdesk-app's conversation view: the data is read
414
+ * from the Interaction record (metadata.aiAnalysis), which the server-side pipeline fills
415
+ * after the recording is uploaded — no client-side transcription is run here.
416
+ */
417
+ declare class CallDetailsComponent implements OnChanges {
418
+ private appSync;
403
419
  call: any;
404
420
  transcriptLines: TranscriptLine[];
421
+ conversationSummary: string[];
422
+ nextActions: string[];
423
+ keyConcerns: string[];
424
+ callType: string;
425
+ customerIntent: string;
426
+ customerSentiment: string;
427
+ aiConfidenceScore: number | null;
405
428
  summaryText: string;
406
- customerBehavior: string;
407
- isTranscribing: boolean;
408
- transcriptionError: string;
429
+ aiProcessingStatus: string;
430
+ hasAnalysis: boolean;
431
+ isRefreshing: boolean;
432
+ refreshError: string;
409
433
  isTranscriptFullscreen: boolean;
410
- hasGeneratedTranscript: boolean;
411
- private readonly ANALYZE_CALL_API;
412
- constructor(http: HttpClient);
434
+ private readonly GET_INTERACTION_QUERY;
435
+ constructor(appSync: AppSyncHelperService);
436
+ ngOnChanges(changes: SimpleChanges): void;
437
+ get isAnalysisPending(): boolean;
413
438
  getBadge(): string;
414
439
  getDisplayName(): string;
415
440
  getPhone(): string;
416
441
  getTranscriptSpeaker(line: TranscriptLine): string;
417
- runTranscription(): Promise<void>;
442
+ getSentimentEmoji(): string;
443
+ /** Re-read the Interaction record (the AI pipeline fills metadata.aiAnalysis asynchronously). */
444
+ refreshAnalysis(): Promise<void>;
445
+ toggleTranscriptFullscreen(): void;
446
+ /** Pull the structured AI analysis off the interaction's metadata — same shape snugdesk-app reads. */
447
+ private parseAnalysisFromCall;
448
+ private normalizeText;
449
+ private normalizeBulletItems;
418
450
  private normalizeTranscript;
419
- getBehaviorEmoji(): string;
420
451
  private parseSpeakerLine;
421
- toggleTranscriptFullscreen(): void;
422
- private getRecordingFilename;
452
+ private safeParse;
423
453
  static ɵfac: i0.ɵɵFactoryDeclaration<CallDetailsComponent, never>;
424
454
  static ɵcmp: i0.ɵɵComponentDeclaration<CallDetailsComponent, "app-call-details", never, { "call": { "alias": "call"; "required": false; }; }, {}, never, never, false, never>;
425
455
  }
@@ -508,7 +538,7 @@ declare class SettingsComponent implements OnInit, OnDestroy {
508
538
  logout: EventEmitter<void>;
509
539
  audioInputs: MediaDeviceInfo[];
510
540
  audioOutputs: MediaDeviceInfo[];
511
- readonly widgetVersion: "0.2.3";
541
+ readonly widgetVersion: "0.2.4";
512
542
  ngOnInit(): void;
513
543
  onLogout(): void;
514
544
  onToggleDarkMode(): void;
@@ -544,8 +574,12 @@ interface CallMetadata {
544
574
  agentId: string;
545
575
  phoneNumber: string;
546
576
  callId: string;
577
+ /** Entity id of the call counterpart, when the widget has resolved one (chunked recordings
578
+ * use it so chunks land under <tenantId>/<entityId>/avaya-ipo-chunks/). */
579
+ entityId?: string | null;
547
580
  }
548
581
 
582
+ type RecordingUploadMode = 'complete' | 'chunked';
549
583
  interface RecordingSession {
550
584
  audioContext: AudioContext;
551
585
  recordingStream: MediaStreamAudioDestinationNode;
@@ -555,6 +589,19 @@ interface RecordingSession {
555
589
  chunks: Blob[];
556
590
  mimeType: string;
557
591
  uploaded: boolean;
592
+ mode: RecordingUploadMode;
593
+ socket?: WebSocket | null;
594
+ seq: number;
595
+ pendingChunks: string[];
596
+ sentAny: boolean;
597
+ reconnectTimer?: any;
598
+ reconnectAttempts: number;
599
+ sourceNodes: AudioNode[];
600
+ /** Index into `chunks` up to which slices have been streamed (rewound to 0 on reconnect). */
601
+ streamCursor: number;
602
+ pumping: boolean;
603
+ everConnected: boolean;
604
+ completedSent: boolean;
558
605
  }
559
606
  declare class RecordingManagerService {
560
607
  private http;
@@ -562,13 +609,34 @@ declare class RecordingManagerService {
562
609
  private readonly MIME_TYPE_PREFERRED;
563
610
  private readonly MIME_TYPE_FALLBACK;
564
611
  private readonly TIMESLICE_MS;
612
+ private readonly CHUNKED_TIMESLICE_MS;
613
+ private readonly CHUNKED_AUDIO_BPS;
614
+ private readonly MAX_WS_DATA_CHARS;
565
615
  private readonly AUDIO_CONTEXT_CLOSE_DELAY_MS;
616
+ private readonly SOCKET_RECONNECT_DELAY_MS;
617
+ private readonly CALL_COMPLETED_DELAY_MS;
618
+ private readonly SOCKET_MAX_RECONNECT_ATTEMPTS;
619
+ private readonly SOCKET_FINALIZE_GRACE_MS;
566
620
  private readonly RECORDING_API_URL;
621
+ private readonly RECORDING_WS_URL;
567
622
  constructor(http: HttpClient);
568
- startRecording(localMediaStream: MediaStream, remoteMediaStream: MediaStream, metadata: CallMetadata): RecordingSession | undefined;
623
+ startRecording(localMediaStream: MediaStream, remoteMediaStream: MediaStream, metadata: CallMetadata, mode?: RecordingUploadMode): RecordingSession | undefined;
569
624
  getSession(callId: string): RecordingSession | undefined;
570
625
  stopRecording(callId: string): void;
571
626
  private finalizeRecording;
627
+ /**
628
+ * Rewire fresh local/remote streams into the SAME recording destination. The MediaRecorder (and
629
+ * therefore the single WebM byte stream, the socket and the seq counter) continue uninterrupted —
630
+ * restarting the recorder would inject a second WebM header mid-file and break playback of the
631
+ * stitched recording.
632
+ */
633
+ private rebindMedia;
634
+ private openRecordingSocket;
635
+ /** Stream session.chunks in strict order; safe to call repeatedly (single active pump). */
636
+ private pumpChunks;
637
+ private streamChunk;
638
+ private flushPendingChunks;
639
+ private finalizeChunked;
572
640
  private uploadCallRecording;
573
641
  private convertBlobToBase64;
574
642
  private generateFilename;
@@ -618,7 +686,7 @@ declare class TranscriptionStreamService implements OnDestroy {
618
686
  static ɵprov: i0.ɵɵInjectableDeclaration<TranscriptionStreamService>;
619
687
  }
620
688
 
621
- declare class PhoneComponent implements OnInit, AfterViewInit, OnChanges, OnDestroy {
689
+ declare class PhoneComponent implements OnInit, AfterViewInit, OnDestroy {
622
690
  private cdr;
623
691
  private formBuilder;
624
692
  private avayaIpoService;
@@ -643,6 +711,9 @@ declare class PhoneComponent implements OnInit, AfterViewInit, OnChanges, OnDest
643
711
  turnIp?: string;
644
712
  turnPort?: number;
645
713
  configurationMode?: string;
714
+ recordingUploadMode?: 'complete' | 'chunked';
715
+ /** Entity id of the call counterpart (resolved by the shell) — included in recording metadata. */
716
+ entityId?: string | null;
646
717
  makeCallEv: EventEmitter<string>;
647
718
  endCallEv: EventEmitter<void>;
648
719
  audioElement: any;
@@ -662,7 +733,6 @@ declare class PhoneComponent implements OnInit, AfterViewInit, OnChanges, OnDest
662
733
  private pendingTranscriptionCallId;
663
734
  private pendingTranscriptionAllowBinary;
664
735
  private lastPartialIndex;
665
- private pendingIncomingCall;
666
736
  dialForm: FormGroup;
667
737
  directoryForm: FormGroup;
668
738
  showDirectoryPhonebook: boolean;
@@ -709,7 +779,6 @@ declare class PhoneComponent implements OnInit, AfterViewInit, OnChanges, OnDest
709
779
  get isConnecting(): boolean;
710
780
  constructor(cdr: ChangeDetectorRef, formBuilder: FormBuilder, avayaIpoService: AvayaIPOService, authenticationService: AuthenticationService, recordingManagerService: RecordingManagerService, transcriptionStreamService: TranscriptionStreamService);
711
781
  ngOnInit(): void;
712
- ngOnChanges(changes: SimpleChanges): void;
713
782
  ngAfterViewInit(): void;
714
783
  ngOnDestroy(): void;
715
784
  makeCall(): void;
@@ -780,8 +849,10 @@ declare class PhoneComponent implements OnInit, AfterViewInit, OnChanges, OnDest
780
849
  private formatDialOutFromInput;
781
850
  private formatTransferTargetFromInput;
782
851
  private startRecordingForCall;
852
+ /** Per-embed [recordingUploadMode] wins; otherwise the APP_CONFIG default applies. */
853
+ private resolvedRecordingMode;
783
854
  static ɵfac: i0.ɵɵFactoryDeclaration<PhoneComponent, never>;
784
- static ɵcmp: i0.ɵɵComponentDeclaration<PhoneComponent, "app-phone", never, { "contacts": { "alias": "contacts"; "required": false; }; "tenantId": { "alias": "tenantId"; "required": false; }; "agentId": { "alias": "agentId"; "required": false; }; "isActiveCall": { "alias": "isActiveCall"; "required": false; }; "activeCallNumber": { "alias": "activeCallNumber"; "required": false; }; "incomingCallName": { "alias": "incomingCallName"; "required": false; }; "incomingNameResolved": { "alias": "incomingNameResolved"; "required": false; }; "activeCallName": { "alias": "activeCallName"; "required": false; }; "serverIp": { "alias": "serverIp"; "required": false; }; "serverPort": { "alias": "serverPort"; "required": false; }; "stunIp": { "alias": "stunIp"; "required": false; }; "stunPort": { "alias": "stunPort"; "required": false; }; "turnIp": { "alias": "turnIp"; "required": false; }; "turnPort": { "alias": "turnPort"; "required": false; }; "configurationMode": { "alias": "configurationMode"; "required": false; }; }, { "callScreenChange": "callScreenChange"; "makeCallEv": "makeCallEv"; "endCallEv": "endCallEv"; }, never, never, false, never>;
855
+ static ɵcmp: i0.ɵɵComponentDeclaration<PhoneComponent, "app-phone", never, { "contacts": { "alias": "contacts"; "required": false; }; "tenantId": { "alias": "tenantId"; "required": false; }; "agentId": { "alias": "agentId"; "required": false; }; "isActiveCall": { "alias": "isActiveCall"; "required": false; }; "activeCallNumber": { "alias": "activeCallNumber"; "required": false; }; "incomingCallName": { "alias": "incomingCallName"; "required": false; }; "incomingNameResolved": { "alias": "incomingNameResolved"; "required": false; }; "activeCallName": { "alias": "activeCallName"; "required": false; }; "serverIp": { "alias": "serverIp"; "required": false; }; "serverPort": { "alias": "serverPort"; "required": false; }; "stunIp": { "alias": "stunIp"; "required": false; }; "stunPort": { "alias": "stunPort"; "required": false; }; "turnIp": { "alias": "turnIp"; "required": false; }; "turnPort": { "alias": "turnPort"; "required": false; }; "configurationMode": { "alias": "configurationMode"; "required": false; }; "recordingUploadMode": { "alias": "recordingUploadMode"; "required": false; }; "entityId": { "alias": "entityId"; "required": false; }; }, { "callScreenChange": "callScreenChange"; "makeCallEv": "makeCallEv"; "endCallEv": "endCallEv"; }, never, never, false, never>;
785
856
  }
786
857
 
787
858
  declare class PhoneIdleComponent {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snugdesk/avaya-ipo-widget",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "Snugdesk - Avaya IPO widget for Angular",
5
5
  "peerDependencies": {
6
6
  "@angular/animations": ">=19.0.0",
File without changes
@@ -1,20 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8" standalone="no" ?>
2
- <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
3
- <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1080" height="1080" viewBox="0 0 1080 1080" xml:space="preserve">
4
- <desc>Created with Fabric.js 5.2.4</desc>
5
- <defs>
6
- </defs>
7
- <rect x="0" y="0" width="100%" height="100%" fill="transparent"></rect>
8
- <g transform="matrix(1 0 0 1 540 540)" id="46946dc3-9491-43cb-8fe7-5b8ffbba78af" >
9
- <rect style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(255,255,255); fill-rule: nonzero; opacity: 1; visibility: hidden;" vector-effect="non-scaling-stroke" x="-540" y="-540" rx="0" ry="0" width="1080" height="1080" />
10
- </g>
11
- <g transform="matrix(1 0 0 1 540 540)" id="863c060c-d63b-4daf-a3bc-022136480777" >
12
- </g>
13
- <g transform="matrix(1 0 0 1 540 540)" id="facf6309-efdf-434e-a753-661bb2577e88" >
14
- <polygon style="stroke: rgb(0,0,0); stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(218,40,28); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" points="-232.07,221.69 106.75,221.69 161.6,357.36 -295.67,357.36 -368.7,513.14 -570.6,513.14 -69.8,-513.14 69.88,-513.14 570.6,513.14 368.77,513.14 -0.04,-279.29 -232.07,221.69 -232.07,221.69 -232.07,221.69 " />
15
- </g>
16
- <g transform="matrix(NaN NaN NaN NaN 0 0)" >
17
- <g style="" >
18
- </g>
19
- </g>
20
- </svg>
@@ -1,16 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8" standalone="no" ?>
2
- <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
3
- <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1080" height="1080" viewBox="0 0 1080 1080" xml:space="preserve">
4
- <desc>Created with Fabric.js 5.2.4</desc>
5
- <defs>
6
- </defs>
7
- <rect x="0" y="0" width="100%" height="100%" fill="transparent"></rect>
8
- <g transform="matrix(1 0 0 1 540 540)" id="46946dc3-9491-43cb-8fe7-5b8ffbba78af" >
9
- <rect style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(255,255,255); fill-rule: nonzero; opacity: 1; visibility: hidden;" vector-effect="non-scaling-stroke" x="-540" y="-540" rx="0" ry="0" width="1080" height="1080" />
10
- </g>
11
- <g transform="matrix(1 0 0 1 540 540)" id="863c060c-d63b-4daf-a3bc-022136480777" >
12
- </g>
13
- <g transform="matrix(1 0 0 1 540 540)" id="facf6309-efdf-434e-a753-661bb2577e88" >
14
- <polygon style="stroke: rgb(0,0,0); stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(136,136,136); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" points="-232.07,221.69 106.75,221.69 161.6,357.36 -295.67,357.36 -368.7,513.14 -570.6,513.14 -69.8,-513.14 69.88,-513.14 570.6,513.14 368.77,513.14 -0.04,-279.29 -232.07,221.69 -232.07,221.69 -232.07,221.69 " />
15
- </g>
16
- </svg>