hanc-webrtc-widgets 2.2.2 → 2.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.
@@ -3,15 +3,23 @@ import { OrbColors, SoundPreset } from '../../core/orb';
3
3
  export declare class FloatingCall extends LitElement {
4
4
  static styles: import('lit').CSSResult;
5
5
  private callManager;
6
+ private healthMonitor;
6
7
  private orbRenderer;
7
8
  private audioAnalyzer;
8
9
  private soundManager;
9
10
  private orbContainerRef;
10
11
  private animationId;
12
+ private pendingTimers;
13
+ private isPageVisible;
14
+ private boundHandleVisibilityChange;
15
+ private animationStartTime;
16
+ private intersectionObserver;
17
+ private isOrbInitialized;
11
18
  agentId: string;
12
19
  voiceServiceUrl?: string;
13
20
  private callStatus;
14
21
  private microphoneEnabled;
22
+ private serviceUnavailable;
15
23
  buttonStartText: string;
16
24
  buttonConnectingText: string;
17
25
  position: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left' | 'static';
@@ -33,9 +41,35 @@ export declare class FloatingCall extends LitElement {
33
41
  soundEnabled: boolean;
34
42
  soundVolume: number;
35
43
  soundPreset: SoundPreset;
44
+ constructor();
36
45
  connectedCallback(): void;
37
46
  disconnectedCallback(): void;
47
+ /**
48
+ * Handle page visibility change (critical for iOS Safari)
49
+ */
50
+ private handleVisibilityChange;
51
+ /**
52
+ * Stop animation loop (for page visibility)
53
+ */
54
+ private stopAnimation;
55
+ /**
56
+ * Resume animation based on current state
57
+ */
58
+ private resumeAnimation;
59
+ /**
60
+ * Schedule a timer that will be automatically cleaned up on disconnect
61
+ */
62
+ private scheduleTimer;
63
+ /**
64
+ * Clear all pending timers
65
+ */
66
+ private clearAllTimers;
38
67
  firstUpdated(): void;
68
+ /**
69
+ * Setup IntersectionObserver for lazy OrbRenderer initialization
70
+ * This significantly improves page load time when multiple widgets are present
71
+ */
72
+ private setupLazyInitialization;
39
73
  updated(changedProperties: Map<string | number | symbol, unknown>): void;
40
74
  private applyTheme;
41
75
  private initializeOrb;
@@ -46,6 +80,7 @@ export declare class FloatingCall extends LitElement {
46
80
  private handleLocalAudioTrack;
47
81
  private handleMicrophoneEnabled;
48
82
  private handleMicrophoneDisabled;
83
+ private handleHealthChange;
49
84
  private handleStatusChange;
50
85
  private toggleCall;
51
86
  private getButtonIcon;
@@ -3,15 +3,23 @@ import { OrbColors, SoundPreset } from '../../core/orb';
3
3
  export declare class InlineCall extends LitElement {
4
4
  static styles: import('lit').CSSResult;
5
5
  private callManager;
6
+ private healthMonitor;
6
7
  private orbRenderer;
7
8
  private audioAnalyzer;
8
9
  private soundManager;
9
10
  private orbContainerRef;
10
11
  private animationId;
12
+ private pendingTimers;
13
+ private isPageVisible;
14
+ private boundHandleVisibilityChange;
15
+ private animationStartTime;
16
+ private intersectionObserver;
17
+ private isOrbInitialized;
11
18
  agentId: string;
12
19
  voiceServiceUrl?: string;
13
20
  private callStatus;
14
21
  private microphoneEnabled;
22
+ private serviceUnavailable;
15
23
  buttonStartText: string;
16
24
  buttonConnectingText: string;
17
25
  size: number;
@@ -33,9 +41,35 @@ export declare class InlineCall extends LitElement {
33
41
  soundEnabled: boolean;
34
42
  soundVolume: number;
35
43
  soundPreset: SoundPreset;
44
+ constructor();
36
45
  connectedCallback(): void;
37
46
  disconnectedCallback(): void;
47
+ /**
48
+ * Handle page visibility change (critical for iOS Safari)
49
+ */
50
+ private handleVisibilityChange;
51
+ /**
52
+ * Stop animation loop (for page visibility)
53
+ */
54
+ private stopAnimation;
55
+ /**
56
+ * Resume animation based on current state
57
+ */
58
+ private resumeAnimation;
59
+ /**
60
+ * Schedule a timer that will be automatically cleaned up on disconnect
61
+ */
62
+ private scheduleTimer;
63
+ /**
64
+ * Clear all pending timers
65
+ */
66
+ private clearAllTimers;
38
67
  firstUpdated(): void;
68
+ /**
69
+ * Setup IntersectionObserver for lazy OrbRenderer initialization
70
+ * This significantly improves page load time when multiple widgets are present
71
+ */
72
+ private setupLazyInitialization;
39
73
  updated(changedProperties: Map<string | number | symbol, unknown>): void;
40
74
  private applyTheme;
41
75
  private initializeOrb;
@@ -46,6 +80,7 @@ export declare class InlineCall extends LitElement {
46
80
  private handleLocalAudioTrack;
47
81
  private handleMicrophoneEnabled;
48
82
  private handleMicrophoneDisabled;
83
+ private handleHealthChange;
49
84
  private handleStatusChange;
50
85
  private toggleCall;
51
86
  private getButtonIcon;
@@ -42,6 +42,11 @@ export declare class HancAiOrbElement extends LitElement {
42
42
  private destroyWidget;
43
43
  private buildConfig;
44
44
  private resolveColors;
45
+ /**
46
+ * Sanitize parsed JSON to prevent prototype pollution attacks.
47
+ * Creates a clean copy without __proto__, constructor, and prototype keys.
48
+ */
49
+ private sanitizeObject;
45
50
  private parseColors;
46
51
  private parseOrbConfig;
47
52
  private applyHostStyles;
@@ -3,15 +3,23 @@ import { OrbColors, SoundPreset } from '../../core/orb';
3
3
  export declare class PillCall extends LitElement {
4
4
  static styles: import('lit').CSSResult;
5
5
  private callManager;
6
+ private healthMonitor;
6
7
  private orbRenderer;
7
8
  private audioAnalyzer;
8
9
  private soundManager;
9
10
  private orbContainerRef;
10
11
  private animationId;
12
+ private pendingTimers;
13
+ private isPageVisible;
14
+ private boundHandleVisibilityChange;
15
+ private animationStartTime;
16
+ private intersectionObserver;
17
+ private isOrbInitialized;
11
18
  agentId: string;
12
19
  voiceServiceUrl?: string;
13
20
  private callStatus;
14
21
  private microphoneEnabled;
22
+ private serviceUnavailable;
15
23
  buttonStartText: string;
16
24
  buttonConnectingText: string;
17
25
  buttonEndText: string;
@@ -33,9 +41,35 @@ export declare class PillCall extends LitElement {
33
41
  soundEnabled: boolean;
34
42
  soundVolume: number;
35
43
  soundPreset: SoundPreset;
44
+ constructor();
36
45
  connectedCallback(): void;
37
46
  disconnectedCallback(): void;
47
+ /**
48
+ * Handle page visibility change (critical for iOS Safari)
49
+ */
50
+ private handleVisibilityChange;
51
+ /**
52
+ * Stop animation loop (for page visibility)
53
+ */
54
+ private stopAnimation;
55
+ /**
56
+ * Resume animation based on current state
57
+ */
58
+ private resumeAnimation;
59
+ /**
60
+ * Schedule a timer that will be automatically cleaned up on disconnect
61
+ */
62
+ private scheduleTimer;
63
+ /**
64
+ * Clear all pending timers
65
+ */
66
+ private clearAllTimers;
38
67
  firstUpdated(): void;
68
+ /**
69
+ * Setup IntersectionObserver for lazy OrbRenderer initialization
70
+ * This significantly improves page load time when multiple widgets are present
71
+ */
72
+ private setupLazyInitialization;
39
73
  updated(changedProperties: Map<string | number | symbol, unknown>): void;
40
74
  private applyTheme;
41
75
  private initializeOrb;
@@ -46,6 +80,7 @@ export declare class PillCall extends LitElement {
46
80
  private handleLocalAudioTrack;
47
81
  private handleMicrophoneEnabled;
48
82
  private handleMicrophoneDisabled;
83
+ private handleHealthChange;
49
84
  private handleStatusChange;
50
85
  private toggleCall;
51
86
  private getButtonIcon;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Background Health Monitor (Singleton)
3
+ *
4
+ * Polls the voice service health endpoint every 30 seconds and maintains
5
+ * an in-memory health status flag. Widget components read this flag
6
+ * before initiating calls to avoid unnecessary latency.
7
+ *
8
+ * Uses reference counting - multiple widgets share one monitor instance.
9
+ *
10
+ * Events Emitted:
11
+ * - 'health-changed': When health status transitions between states
12
+ */
13
+ export type HealthStatus = 'unknown' | 'healthy' | 'unhealthy';
14
+ /**
15
+ * HealthMonitor wrapper that uses the shared singleton
16
+ * Provides the same API as before for backwards compatibility
17
+ */
18
+ export declare class HealthMonitor {
19
+ get isHealthy(): boolean;
20
+ get status(): HealthStatus;
21
+ set serviceUrl(url: string);
22
+ get serviceUrl(): string;
23
+ start(): void;
24
+ stop(): void;
25
+ addEventListener(type: string, listener: (e: Event) => void): void;
26
+ removeEventListener(type: string, listener: (e: Event) => void): void;
27
+ }
@@ -53,6 +53,66 @@ export declare class LiveKitCallManager extends EventTarget {
53
53
  private userIdentity;
54
54
  private roomName;
55
55
  private audioPlaybackPrompt;
56
+ private audioElements;
57
+ private audioContainer;
58
+ private wasConnectedBeforeHidden;
59
+ private isPageVisible;
60
+ private reconnectAttempts;
61
+ private maxReconnectAttempts;
62
+ private isReconnecting;
63
+ private isDestroyed;
64
+ private pendingTimers;
65
+ private boundHandleVisibilityChange;
66
+ private boundHandlePageHide;
67
+ private boundHandlePageShow;
68
+ private boundHandleOnline;
69
+ private boundHandleOffline;
70
+ private readonly isAppleDevice;
71
+ constructor();
72
+ private createAudioContainer;
73
+ /**
74
+ * Schedule a timer that will be automatically cleaned up on destroy
75
+ */
76
+ private scheduleTimer;
77
+ /**
78
+ * Clear all pending timers
79
+ */
80
+ private clearAllTimers;
81
+ private setupLifecycleListeners;
82
+ private removeLifecycleListeners;
83
+ private handleVisibilityChange;
84
+ private handlePageHide;
85
+ private handlePageShow;
86
+ private handleOnline;
87
+ private handleOffline;
88
+ private pauseAllAudio;
89
+ private resumeAllAudio;
90
+ private resumeAfterVisibilityChange;
91
+ private attemptReconnect;
92
+ /**
93
+ * Clean up room without full destroy
94
+ */
95
+ private cleanupRoom;
96
+ private cleanupBeforeUnload;
97
+ private cleanupAudioElements;
98
+ /**
99
+ * Remove the audio container from DOM
100
+ */
101
+ private removeAudioContainer;
102
+ destroy(): void;
103
+ /**
104
+ * Attempt to play audio element with iOS Safari handling
105
+ * Returns true if played successfully, false if blocked
106
+ */
107
+ private playAudioElement;
108
+ /**
109
+ * Fetch with timeout - prevents hanging on slow iOS connections
110
+ */
111
+ private fetchWithTimeout;
112
+ /**
113
+ * Connect with timeout - prevents hanging on iOS
114
+ */
115
+ private connectWithTimeout;
56
116
  set serviceUrl(url: string | undefined);
57
117
  get serviceUrl(): string;
58
118
  get status(): CallStatus;
@@ -20,6 +20,7 @@ export declare class AudioAnalyzer {
20
20
  private mixer;
21
21
  private frequencyData;
22
22
  private waveformData;
23
+ private hasAcquiredContext;
23
24
  private smoothedBass;
24
25
  private smoothedMid;
25
26
  private smoothedTreble;
@@ -27,6 +28,11 @@ export declare class AudioAnalyzer {
27
28
  private readonly smoothingFactor;
28
29
  private readonly options;
29
30
  constructor(options?: AudioAnalyzerOptions);
31
+ /**
32
+ * Acquire shared AudioContext (iOS limit safe)
33
+ * Returns null if context couldn't be acquired or resumed
34
+ */
35
+ private ensureAudioContext;
30
36
  /**
31
37
  * Initialize analyzer with a MediaStream (remote audio)
32
38
  */
@@ -41,7 +41,23 @@ export declare class OrbRenderer {
41
41
  private currentRotation;
42
42
  private audioValues;
43
43
  private readonly canvasPadding;
44
+ private resizeTimeoutId;
45
+ private isContextLost;
46
+ private boundHandleContextLost;
47
+ private boundHandleContextRestored;
44
48
  constructor(container: HTMLElement, config?: OrbConfig);
49
+ /**
50
+ * Handle WebGL context loss (iOS background transition)
51
+ */
52
+ private handleContextLost;
53
+ /**
54
+ * Handle WebGL context restoration (iOS foreground transition)
55
+ */
56
+ private handleContextRestored;
57
+ /**
58
+ * Recreate WebGL resources after context loss
59
+ */
60
+ private recreateAfterContextLoss;
45
61
  private createOrb;
46
62
  /**
47
63
  * Update audio data with smoothing
@@ -0,0 +1,61 @@
1
+ /**
2
+ * SharedAudioContext - Singleton AudioContext manager for multiple widgets
3
+ *
4
+ * iOS Safari has a limit of ~6 AudioContext instances.
5
+ * This module provides a shared AudioContext that all widgets can use.
6
+ */
7
+ declare class SharedAudioContextManager {
8
+ private static instance;
9
+ private audioContext;
10
+ private refCount;
11
+ private resumePromise;
12
+ private constructor();
13
+ static getInstance(): SharedAudioContextManager;
14
+ /**
15
+ * Acquire a reference to the shared AudioContext
16
+ * Creates the context if it doesn't exist
17
+ */
18
+ acquire(): Promise<AudioContext | null>;
19
+ /**
20
+ * Release a reference to the shared AudioContext
21
+ * Closes the context when all references are released
22
+ */
23
+ release(): void;
24
+ /**
25
+ * Force close the AudioContext (call on page unload)
26
+ */
27
+ forceClose(): void;
28
+ /**
29
+ * Get the current AudioContext without acquiring a reference
30
+ * Use this for read-only operations
31
+ */
32
+ getContext(): AudioContext | null;
33
+ /**
34
+ * Ensure the AudioContext is resumed
35
+ * iOS Safari requires user interaction to resume
36
+ * Returns true if running, false if still suspended (needs user gesture)
37
+ */
38
+ ensureResumed(): Promise<boolean>;
39
+ /**
40
+ * Check if the AudioContext is available and running
41
+ */
42
+ isRunning(): boolean;
43
+ /**
44
+ * Get the current state of the AudioContext
45
+ */
46
+ getState(): AudioContextState | null;
47
+ /**
48
+ * Get the sample rate of the AudioContext
49
+ */
50
+ getSampleRate(): number;
51
+ /**
52
+ * Get current time from the AudioContext
53
+ */
54
+ getCurrentTime(): number;
55
+ /**
56
+ * Get the destination node for connecting audio nodes
57
+ */
58
+ getDestination(): AudioDestinationNode | null;
59
+ }
60
+ export declare const sharedAudioContext: SharedAudioContextManager;
61
+ export {};
@@ -1,6 +1,9 @@
1
1
  /**
2
2
  * SoundManager - Professional notification sound synthesis
3
3
  * Uses Web Audio API with creative sound design techniques
4
+ *
5
+ * Uses SharedAudioContext to avoid iOS AudioContext limit (max ~6 contexts)
6
+ * when multiple widgets are on the same page.
4
7
  */
5
8
  export type SoundPreset = '1' | '2' | '3' | '4' | 'none';
6
9
  export interface SoundConfig {
@@ -9,8 +12,8 @@ export interface SoundConfig {
9
12
  preset: SoundPreset;
10
13
  }
11
14
  export declare class SoundManager {
12
- private audioContext;
13
15
  private config;
16
+ private hasAcquired;
14
17
  constructor(config?: Partial<SoundConfig>);
15
18
  private ensureContext;
16
19
  prewarm(): Promise<void>;
@@ -48,6 +48,13 @@ export declare class HancAiWidget {
48
48
  private createButton;
49
49
  private getLogoIcon;
50
50
  private getPhoneOffIcon;
51
+ /**
52
+ * Safely set SVG content to an element.
53
+ * Uses DOMParser to parse SVG and avoid direct innerHTML assignment.
54
+ * Note: For static SVG strings this is safe, but this method provides
55
+ * an extra layer of protection against accidental XSS.
56
+ */
57
+ private setIconContent;
51
58
  private handleButtonHover;
52
59
  private handleButtonLeave;
53
60
  private handleMouseEnter;
@@ -113,6 +120,10 @@ export declare class HancAiWidget {
113
120
  * Disconnect microphone
114
121
  */
115
122
  disconnectMicrophone(): void;
123
+ /**
124
+ * Remove spinner animation style from document head
125
+ */
126
+ private removeSpinnerAnimation;
116
127
  /**
117
128
  * Cleanup
118
129
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hanc-webrtc-widgets",
3
- "version": "2.2.2",
3
+ "version": "2.2.4",
4
4
  "type": "module",
5
5
  "main": "dist/hanc-webrtc-widgets.umd.js",
6
6
  "module": "dist/hanc-webrtc-widgets.es.js",
@@ -22,6 +22,8 @@
22
22
  "@eslint/js": "^9.24.0",
23
23
  "@types/node": "^22.14.0",
24
24
  "@types/three": "^0.182.0",
25
+ "@vitejs/plugin-basic-ssl": "^2.1.4",
26
+ "concurrently": "^9.2.1",
25
27
  "eslint": "^9.24.0",
26
28
  "eslint-config-prettier": "^10.1.1",
27
29
  "eslint-plugin-import": "^2.31.0",
@@ -33,7 +35,8 @@
33
35
  "vite-plugin-dts": "^4.5.4"
34
36
  },
35
37
  "scripts": {
36
- "dev": "vite",
38
+ "dev": "concurrently \"vite\" \"./tunnel.sh\"",
39
+ "dev:local": "vite",
37
40
  "build": "tsc && vite build",
38
41
  "preview": "vite preview",
39
42
  "release": "pnpm build && changeset publish --access public"