@vanira/sdk-react-native 0.0.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.
Files changed (148) hide show
  1. package/README.md +239 -0
  2. package/package.json +53 -0
  3. package/src/__tests__/WebRTCClient.integration.test.ts +396 -0
  4. package/src/__tests__/adapters.test.ts +475 -0
  5. package/src/__tests__/httpResponse.test.ts +25 -0
  6. package/src/__tests__/mocks/react-native-incall-manager.ts +8 -0
  7. package/src/__tests__/mocks/react-native-permissions.ts +15 -0
  8. package/src/__tests__/mocks/react-native-webrtc.ts +6 -0
  9. package/src/__tests__/mocks/react-native.ts +28 -0
  10. package/src/__tests__/preset.test.ts +239 -0
  11. package/src/__tests__/resolveRuntimeConfig.test.ts +90 -0
  12. package/src/__tests__/storage.test.ts +211 -0
  13. package/src/__tests__/webrtcSignaling.test.ts +42 -0
  14. package/src/adapters/PeerConnectionAdapter.ts +101 -0
  15. package/src/adapters/browser/BrowserAudioAdapter.ts +43 -0
  16. package/src/adapters/browser/BrowserDataChannelAdapter.ts +69 -0
  17. package/src/adapters/browser/BrowserMediaAdapter.ts +15 -0
  18. package/src/adapters/browser/BrowserPeerAdapter.ts +14 -0
  19. package/src/adapters/browser/index.ts +4 -0
  20. package/src/adapters/interfaces.ts +84 -0
  21. package/src/adapters/react-native/RNAudioAdapter.ts +42 -0
  22. package/src/adapters/react-native/RNDataChannelAdapter.ts +79 -0
  23. package/src/adapters/react-native/RNMediaAdapter.ts +46 -0
  24. package/src/adapters/react-native/RNPeerAdapter.ts +28 -0
  25. package/src/adapters/react-native/callAudioRouting.ts +115 -0
  26. package/src/adapters/react-native/decodeUtf8.ts +72 -0
  27. package/src/adapters/react-native/index.ts +4 -0
  28. package/src/adapters/react-native/rnUploadFile.ts +76 -0
  29. package/src/adapters/storage/BrowserDualStorageAdapter.ts +71 -0
  30. package/src/adapters/storage/MemoryStorageAdapter.ts +50 -0
  31. package/src/adapters/storage/StorageAdapter.ts +21 -0
  32. package/src/adapters/storage/createSyncStorageAdapter.ts +40 -0
  33. package/src/adapters/storage/index.ts +7 -0
  34. package/src/api/services/ChatService.ts +304 -0
  35. package/src/api/services/ConfigService.ts +33 -0
  36. package/src/assets/icons.js +35 -0
  37. package/src/cdn.ts +68 -0
  38. package/src/core/CallSessionStore.ts +137 -0
  39. package/src/core/DraggableController.ts +83 -0
  40. package/src/core/SessionManager.ts +322 -0
  41. package/src/core/VaniraAI.ts +464 -0
  42. package/src/core/WebRTCClient.ts +1012 -0
  43. package/src/core/httpResponse.ts +22 -0
  44. package/src/core/iceServers.ts +18 -0
  45. package/src/core/toolCallNormalize.ts +80 -0
  46. package/src/core/voice-client.js +236 -0
  47. package/src/core/webrtcSignaling.ts +72 -0
  48. package/src/index.js +34 -0
  49. package/src/index.ts +6 -0
  50. package/src/platforms/browser.ts +67 -0
  51. package/src/platforms/react-native.ts +105 -0
  52. package/src/presets/BookingCalendarModal.tsx +457 -0
  53. package/src/presets/CameraModal.tsx +576 -0
  54. package/src/presets/DynamicFormModal.tsx +378 -0
  55. package/src/presets/NativePresetRenderer.tsx +350 -0
  56. package/src/presets/NavigateHandler.tsx +75 -0
  57. package/src/presets/PresetHost.tsx +155 -0
  58. package/src/presets/PresetShellModal.tsx +97 -0
  59. package/src/presets/UploadModal.tsx +321 -0
  60. package/src/presets/calendar/calendarUtils.ts +386 -0
  61. package/src/presets/call/CallSpeakerToggle.tsx +59 -0
  62. package/src/presets/call/callAudioRouting.ts +2 -0
  63. package/src/presets/call/useCallSpeaker.ts +31 -0
  64. package/src/presets/camera/cameraPermissions.ts +18 -0
  65. package/src/presets/camera/cameraStream.ts +19 -0
  66. package/src/presets/camera/cameraUtils.ts +21 -0
  67. package/src/presets/camera/useLivenessFlow.ts +95 -0
  68. package/src/presets/chalkboard/ChalkboardOverlay.tsx +156 -0
  69. package/src/presets/chalkboard/EraseTextHandler.tsx +95 -0
  70. package/src/presets/chalkboard/TypeTextHandler.tsx +107 -0
  71. package/src/presets/chalkboard/boardAbort.ts +36 -0
  72. package/src/presets/chalkboard/boardQueue.ts +620 -0
  73. package/src/presets/chalkboard/chalkboardSession.ts +75 -0
  74. package/src/presets/chalkboard/drawUtils.ts +123 -0
  75. package/src/presets/chalkboard/textUtils.ts +109 -0
  76. package/src/presets/clipRegion/ClipRegionModal.tsx +261 -0
  77. package/src/presets/clipRegion/clipRegionBridge.ts +19 -0
  78. package/src/presets/form/formValidation.ts +104 -0
  79. package/src/presets/form/parseFormFields.ts +171 -0
  80. package/src/presets/host/HostElementPresetHandler.tsx +155 -0
  81. package/src/presets/host/hostPresetBridge.ts +71 -0
  82. package/src/presets/index.ts +63 -0
  83. package/src/presets/liveScreen/CloseLiveScreenHandler.tsx +36 -0
  84. package/src/presets/liveScreen/LiveScreenCaptureHost.tsx +312 -0
  85. package/src/presets/liveScreen/LiveScreenHandler.tsx +25 -0
  86. package/src/presets/liveScreen/LiveScreenPipOverlay.tsx +6 -0
  87. package/src/presets/liveScreen/liveScreenSession.ts +73 -0
  88. package/src/presets/liveVision/CloseLiveVisionHandler.tsx +29 -0
  89. package/src/presets/liveVision/LiveVisionCameraHost.tsx +317 -0
  90. package/src/presets/liveVision/LiveVisionHandler.tsx +26 -0
  91. package/src/presets/liveVision/LiveVisionPipOverlay.tsx +7 -0
  92. package/src/presets/liveVision/liveVisionFrameLoop.ts +38 -0
  93. package/src/presets/liveVision/liveVisionSession.ts +75 -0
  94. package/src/presets/liveVision/liveVisionUpload.ts +62 -0
  95. package/src/presets/navigation/internalRouteRegistry.ts +25 -0
  96. package/src/presets/navigation/navigationBridge.ts +76 -0
  97. package/src/presets/navigation/navigationTypes.ts +12 -0
  98. package/src/presets/parseToolCall.ts +60 -0
  99. package/src/presets/presetClientAdapter.ts +29 -0
  100. package/src/presets/presetCompletion.ts +91 -0
  101. package/src/presets/presetEventHelpers.ts +45 -0
  102. package/src/presets/registry.ts +128 -0
  103. package/src/presets/streaming/mediaFrameUpload.ts +93 -0
  104. package/src/presets/types.ts +74 -0
  105. package/src/presets/upload/pickUploadFile.ts +256 -0
  106. package/src/presets/upload/uploadFormats.ts +163 -0
  107. package/src/presets/upload/uploadUtils.ts +68 -0
  108. package/src/react/PresetRenderer.tsx +144 -0
  109. package/src/react/index.ts +1 -0
  110. package/src/runtime/browserRuntime.ts +54 -0
  111. package/src/runtime/platform.ts +17 -0
  112. package/src/runtime/reactNativeRuntime.ts +68 -0
  113. package/src/runtime/resolveRuntimeConfig.ts +75 -0
  114. package/src/runtime/runtimeBundles.ts +74 -0
  115. package/src/runtime/types.ts +135 -0
  116. package/src/types/react-native-incall-manager.d.ts +17 -0
  117. package/src/types/react-native-webrtc.d.ts +47 -0
  118. package/src/types.ts +133 -0
  119. package/src/ui/VaniraWidget.ts +87 -0
  120. package/src/ui/abstraction/AbstractWidgetProvider.ts +18 -0
  121. package/src/ui/abstraction/interfaces.ts +12 -0
  122. package/src/ui/adapters/VaniraChatAdapter.ts +42 -0
  123. package/src/ui/components/AvatarView.ts +81 -0
  124. package/src/ui/components/ChatWindow.ts +263 -0
  125. package/src/ui/components/FloatingButton.ts +163 -0
  126. package/src/ui/components/FloatingWelcomeChips.ts +137 -0
  127. package/src/ui/components/Panel.ts +120 -0
  128. package/src/ui/components/VoiceOrb.ts +79 -0
  129. package/src/ui/components/VoiceOverlay.ts +497 -0
  130. package/src/ui/components/index.ts +7 -0
  131. package/src/ui/factory/WidgetFactory.ts +16 -0
  132. package/src/ui/icons_data.ts +2 -0
  133. package/src/ui/presets/WidgetPresetRenderer.ts +1802 -0
  134. package/src/ui/presets/types.ts +16 -0
  135. package/src/ui/providers/VaniraInternalProvider.ts +1066 -0
  136. package/src/ui/styles/index.ts +323 -0
  137. package/src/ui/styles/keyframes.ts +76 -0
  138. package/src/ui/styles/theme.ts +57 -0
  139. package/src/ui/styles/widget.css.ts +838 -0
  140. package/src/ui/utils.ts +37 -0
  141. package/src/ui/views/AbstractChatView.ts +93 -0
  142. package/src/ui/views/AbstractVoiceView.ts +57 -0
  143. package/src/ui/views/AvatarOnlyView.ts +78 -0
  144. package/src/ui/views/ChatAvatarView.ts +66 -0
  145. package/src/ui/views/ChatOnlyView.ts +28 -0
  146. package/src/ui/views/ChatVoiceView.ts +15 -0
  147. package/src/ui/views/VoiceOnlyView.ts +25 -0
  148. package/src/ui/views/index.ts +5 -0
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Parse fetch Response body as JSON with a clear error when the worker returns plain text.
3
+ */
4
+ export async function readResponseJson<T extends Record<string, unknown> = Record<string, unknown>>(
5
+ response: Response,
6
+ context: string,
7
+ ): Promise<T> {
8
+ const text = await response.text();
9
+ const trimmed = text.trim();
10
+
11
+ if (!trimmed) {
12
+ throw new Error(`${context}: empty response body (HTTP ${response.status})`);
13
+ }
14
+
15
+ try {
16
+ return JSON.parse(trimmed) as T;
17
+ } catch {
18
+ throw new Error(
19
+ `${context}: expected JSON but got (HTTP ${response.status}): ${trimmed.slice(0, 240)}`,
20
+ );
21
+ }
22
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Default STUN/TURN ICE servers for WebRTC peer connections.
3
+ * Shared with vanira-sdk/flutter-sdk (see _kDefaultIceServers in vanira_session.dart).
4
+ */
5
+ export const DEFAULT_ICE_SERVERS: RTCIceServer[] = [
6
+ { urls: 'stun:stun.l.google.com:19302' },
7
+ { urls: 'stun:stun1.l.google.com:19302' },
8
+ { urls: 'stun:global.relay.metered.ca:80' }, // works on Jio (port 80)
9
+ {
10
+ urls: [
11
+ 'turns:global.relay.metered.ca:443?transport=tcp',
12
+ 'turn:global.relay.metered.ca:443?transport=tcp',
13
+ 'turn:global.relay.metered.ca:80?transport=tcp',
14
+ ],
15
+ username: 'fa97658be3343d21da3b65e6',
16
+ credential: 'HXHDoqeHbvZrmCuf',
17
+ },
18
+ ];
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Normalize client_tool_call payloads from the worker.
3
+ * Server may nest under tool_call, send arguments as JSON string, or use clientFields.
4
+ */
5
+
6
+ export {
7
+ TOOL_NAME_TO_PRESET_ID,
8
+ resolvePresetId,
9
+ isVaniraPresetId,
10
+ } from '../presets/registry';
11
+ import {resolvePresetId} from '../presets/registry';
12
+
13
+ export function unwrapToolCallData(raw: unknown): Record<string, unknown> {
14
+ if (!raw || typeof raw !== 'object') {
15
+ return {};
16
+ }
17
+ let data = (raw as { data?: Record<string, unknown> }).data ?? (raw as Record<string, unknown>);
18
+ const nested = data.tool_call;
19
+ if (nested && typeof nested === 'object') {
20
+ data = nested as Record<string, unknown>;
21
+ }
22
+ return data;
23
+ }
24
+
25
+ export function parseToolArguments(rawArgs: unknown): Record<string, unknown> {
26
+ if (typeof rawArgs === 'string') {
27
+ try {
28
+ const parsed = JSON.parse(rawArgs) as unknown;
29
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
30
+ return parsed as Record<string, unknown>;
31
+ }
32
+ } catch {
33
+ return {};
34
+ }
35
+ return {};
36
+ }
37
+ if (rawArgs && typeof rawArgs === 'object' && !Array.isArray(rawArgs)) {
38
+ return rawArgs as Record<string, unknown>;
39
+ }
40
+ return {};
41
+ }
42
+
43
+ export function normalizeToolCallFields(raw: unknown): {
44
+ name: string;
45
+ arguments: Record<string, unknown>;
46
+ tool_call_id: string;
47
+ execution_mode: string;
48
+ client_fields: Record<string, unknown>;
49
+ } {
50
+ const data = unwrapToolCallData(raw);
51
+ const args = parseToolArguments(data.arguments ?? data.args);
52
+ const clientFields = (data.client_fields ?? data.clientFields ?? {}) as Record<string, unknown>;
53
+ const name = String(
54
+ data.name ??
55
+ data.tool_name ??
56
+ data.ref_code ??
57
+ (data.function && typeof data.function === 'object'
58
+ ? String((data.function as Record<string, unknown>).name ?? '')
59
+ : ''),
60
+ );
61
+
62
+ const presetFromFields = clientFields.preset_id;
63
+ const resolvedPresetId = resolvePresetId(
64
+ clientFields as Record<string, unknown>,
65
+ args,
66
+ name,
67
+ );
68
+
69
+ const mergedClientFields = resolvedPresetId
70
+ ? { ...clientFields, preset_id: resolvedPresetId }
71
+ : clientFields;
72
+
73
+ return {
74
+ name,
75
+ arguments: args,
76
+ tool_call_id: String(data.tool_call_id ?? data.call_id ?? ''),
77
+ execution_mode: String(data.execution_mode ?? 'fire_and_forget'),
78
+ client_fields: mergedClientFields,
79
+ };
80
+ }
@@ -0,0 +1,236 @@
1
+ /**
2
+ * WebRTC Client - Pure HTTP Signaling (No WebSocket)
3
+ * Embedded version for the widget
4
+ */
5
+ export class VoiceClient {
6
+ constructor(config) {
7
+ if (!config.serverUrl) throw new Error("serverUrl is required");
8
+ if (!config.agentId) throw new Error("agentId is required");
9
+
10
+ this.serverUrl = config.serverUrl.replace(/\/$/, '');
11
+ this.agentId = config.agentId;
12
+ this.callId = config.callId || this.generateCallId();
13
+
14
+ // Callbacks
15
+ this.onConnected = config.onConnected || (() => { });
16
+ this.onDisconnected = config.onDisconnected || (() => { });
17
+ this.onError = config.onError || (e => console.error('[WebRTC]', e));
18
+ this.onTranscription = config.onTranscription || (() => { });
19
+
20
+ // State
21
+ this.pc = null;
22
+ this.dataChannel = null;
23
+ this.audioElement = null;
24
+ this.connected = false;
25
+ }
26
+
27
+ async connect() {
28
+ console.log('🔵 [WebRTC] Starting connection...');
29
+
30
+ try {
31
+ // 1. Create RTCPeerConnection
32
+ this.pc = new RTCPeerConnection({
33
+ iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
34
+ });
35
+
36
+ this.pc.addTransceiver('video', { direction: 'recvonly' });
37
+
38
+ // 2. Get microphone
39
+ let stream;
40
+ try {
41
+ stream = await navigator.mediaDevices.getUserMedia({
42
+ audio: {
43
+ echoCancellation: true,
44
+ noiseSuppression: true,
45
+ autoGainControl: true,
46
+ sampleRate: { ideal: 16000 },
47
+ channelCount: 1
48
+ }
49
+ });
50
+ } catch (err) {
51
+ console.error('🎤 [WebRTC] Microphone access failed:', err);
52
+ if (
53
+ err.name === 'NotAllowedError' ||
54
+ err.name === 'PermissionDeniedError' ||
55
+ err.message?.includes('Permission denied')
56
+ ) {
57
+ const ua = navigator.userAgent;
58
+ const isIOS = /iPad|iPhone|iPod/.test(ua) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
59
+ let customMsg = "Microphone access denied. Please allow microphone access in your browser settings.";
60
+
61
+ if (isIOS) {
62
+ if (ua.includes('CriOS')) {
63
+ customMsg = "Microphone access blocked. Please enable it in iOS Settings > Chrome > Microphone, then reload the page.";
64
+ } else if (ua.includes('FxiOS')) {
65
+ customMsg = "Microphone access blocked. Please enable it in iOS Settings > Firefox > Microphone, then reload the page.";
66
+ } else {
67
+ customMsg = "Microphone access blocked. Please enable it in iOS Settings > Safari > Microphone (or tap 'aA' > Website Settings > Allow Microphone).";
68
+ }
69
+ }
70
+ const newErr = new Error(customMsg);
71
+ newErr.name = err.name;
72
+ throw newErr;
73
+ }
74
+ throw err;
75
+ }
76
+ console.log('🎤 [WebRTC] Microphone access granted');
77
+
78
+ // 3. Add audio track
79
+ stream.getTracks().forEach(track => {
80
+ this.pc.addTrack(track, stream);
81
+ });
82
+
83
+ // 4. Create DataChannel
84
+ this.dataChannel = this.pc.createDataChannel('control');
85
+ this.dataChannel.onopen = () => console.log('📡 [WebRTC] DataChannel opened');
86
+ this.dataChannel.onmessage = (e) => this.handleControlEvent(JSON.parse(e.data));
87
+ this.dataChannel.onerror = (e) => console.error('❌ [WebRTC] DataChannel error:', e);
88
+
89
+ // 5. Handle incoming tracks
90
+ this.pc.ontrack = (event) => {
91
+ const track = event.track;
92
+ const stream = event.streams[0];
93
+ console.log(`📥 [WebRTC] Received ${track.kind} track`);
94
+
95
+ if (track.kind === 'audio') {
96
+ console.log('🔊 [WebRTC] Setting up audio playback');
97
+ this.audioElement = new Audio();
98
+ this.audioElement.srcObject = stream;
99
+ this.audioElement.play().catch(e => console.warn('Audio autoplay blocked:', e));
100
+
101
+ this.audioElement.onended = () => {
102
+ this.sendEvent('playedStream');
103
+ };
104
+ }
105
+ };
106
+
107
+ // 6. Connection state monitoring
108
+ this.pc.onconnectionstatechange = () => {
109
+ console.log('🔄 [WebRTC] State:', this.pc.connectionState);
110
+ if (this.pc.connectionState === 'connected') {
111
+ this.connected = true;
112
+ this.onConnected();
113
+ } else if (this.pc.connectionState === 'failed' ||
114
+ this.pc.connectionState === 'disconnected' ||
115
+ this.pc.connectionState === 'closed') {
116
+ this.connected = false;
117
+ this.onDisconnected();
118
+ }
119
+ };
120
+
121
+ // 7. Create offer
122
+ const offer = await this.pc.createOffer();
123
+ await this.pc.setLocalDescription(offer);
124
+ console.log('📝 [WebRTC] Created offer, waiting for ICE gathering...');
125
+
126
+ // 8. Wait for ICE gathering
127
+ await this.waitForIceGathering();
128
+ console.log('🧊 [WebRTC] ICE gathering complete');
129
+
130
+ // 9. Send offer via HTTP
131
+ console.log('📤 [WebRTC] Sending offer via HTTP...');
132
+ const response = await fetch(`${this.serverUrl}/webrtc?agent=${this.agentId}_${this.callId}`, {
133
+ method: 'POST',
134
+ headers: { 'Content-Type': 'application/json' },
135
+ body: JSON.stringify({
136
+ offer: this.pc.localDescription,
137
+ agentId: this.agentId,
138
+ callId: this.callId
139
+ })
140
+ });
141
+
142
+ if (!response.ok) {
143
+ const error = await response.json();
144
+ throw new Error(error.error || `HTTP ${response.status}`);
145
+ }
146
+
147
+ const { answer } = await response.json();
148
+ console.log('📥 [WebRTC] Received answer from server');
149
+
150
+ // 10. Set remote description
151
+ await this.pc.setRemoteDescription(answer);
152
+ console.log('✅ [WebRTC] Connection established!');
153
+
154
+ } catch (error) {
155
+ console.error('❌ [WebRTC] Connection failed:', error);
156
+ this.onError(error.message || error);
157
+ throw error;
158
+ }
159
+ }
160
+
161
+ waitForIceGathering() {
162
+ return new Promise((resolve) => {
163
+ if (this.pc.iceGatheringState === 'complete') {
164
+ resolve();
165
+ } else {
166
+ const checkState = () => {
167
+ if (this.pc.iceGatheringState === 'complete') {
168
+ this.pc.removeEventListener('icegatheringstatechange', checkState);
169
+ resolve();
170
+ }
171
+ };
172
+ this.pc.addEventListener('icegatheringstatechange', checkState);
173
+
174
+ setTimeout(() => {
175
+ this.pc.removeEventListener('icegatheringstatechange', checkState);
176
+ console.warn('⚠️ [WebRTC] ICE gathering timeout, proceeding anyway');
177
+ resolve();
178
+ }, 5000);
179
+ }
180
+ });
181
+ }
182
+
183
+ sendEvent(event, data = {}) {
184
+ if (this.dataChannel?.readyState === 'open') {
185
+ this.dataChannel.send(JSON.stringify({ event, ...data }));
186
+ }
187
+ }
188
+
189
+ handleControlEvent(msg) {
190
+ switch (msg.event) {
191
+ case 'clearAudio':
192
+ console.log('🛑 [WebRTC] Interrupt: stopping audio');
193
+ if (this.audioElement) {
194
+ this.audioElement.pause();
195
+ this.audioElement.currentTime = 0;
196
+ }
197
+ break;
198
+
199
+ case 'transcription':
200
+ console.log('📝 [WebRTC] Transcription:', msg.text);
201
+ this.onTranscription(msg.text, msg.isFinal);
202
+ break;
203
+
204
+ case 'mark':
205
+ console.log('🏷️ [WebRTC] Mark:', msg.name);
206
+ break;
207
+
208
+ default:
209
+ console.log('ℹ️ [WebRTC] Unknown event:', msg.event);
210
+ }
211
+ }
212
+
213
+ disconnect() {
214
+ console.log('🔴 [WebRTC] Disconnecting...');
215
+
216
+ if (this.audioElement) {
217
+ this.audioElement.pause();
218
+ this.audioElement.srcObject = null;
219
+ }
220
+
221
+ if (this.dataChannel) {
222
+ this.dataChannel.close();
223
+ }
224
+
225
+ if (this.pc) {
226
+ this.pc.close();
227
+ }
228
+
229
+ this.connected = false;
230
+ this.onDisconnected();
231
+ }
232
+
233
+ generateCallId() {
234
+ return 'web_' + Date.now() + '_' + Math.random().toString(36).substr(2, 8);
235
+ }
236
+ }
@@ -0,0 +1,72 @@
1
+ /** ICE server entry from POST /calls/create */
2
+ export type CreateCallIceServer = {
3
+ urls: string | string[];
4
+ username?: string | null;
5
+ credential?: string | null;
6
+ };
7
+
8
+ export type CreateCallResponse = {
9
+ call_id?: string;
10
+ prospect_id?: string;
11
+ worker_url?: string;
12
+ ice_servers?: CreateCallIceServer[];
13
+ status?: string;
14
+ };
15
+
16
+ /**
17
+ * Derive ICE trickle URL from worker_url — same host + query, path /webrtc/ice.
18
+ *
19
+ * API returns: https://host/webrtc?agent_id=...&token=...
20
+ * ICE trickle: https://host/webrtc/ice?agent_id=...&token=...
21
+ *
22
+ * Uses string replace first (Hermes-safe), then URL() fallback — matches web SDK.
23
+ */
24
+ export function getIceTrickleUrl(workerUrl: string, agentId?: string, callId?: string): string {
25
+ // Primary path: Vanira worker_url always uses /webrtc?query
26
+ if (workerUrl.includes('/webrtc?')) {
27
+ return workerUrl.replace('/webrtc?', '/webrtc/ice?');
28
+ }
29
+ if (workerUrl.includes('/webrtc/ice')) {
30
+ return workerUrl;
31
+ }
32
+
33
+ try {
34
+ const url = new URL(workerUrl);
35
+ if (url.pathname.endsWith('/webrtc')) {
36
+ url.pathname = `${url.pathname}/ice`;
37
+ } else {
38
+ url.pathname = '/webrtc/ice';
39
+ }
40
+ return url.toString();
41
+ } catch {
42
+ const origin = workerUrl.replace(/\/webrtc.*$/, '').replace(/\/$/, '');
43
+ if (agentId && callId) {
44
+ return `${origin}/webrtc/ice?agent_id=${agentId}&call_id=${callId}`;
45
+ }
46
+ throw new Error(`[VaniraSDK] Invalid worker_url for ICE trickle: ${workerUrl}`);
47
+ }
48
+ }
49
+
50
+ /** True when url is the SDP endpoint — must never be polled with GET. */
51
+ export function isWorkerSdpUrl(url: string): boolean {
52
+ return url.includes('/webrtc?') && !url.includes('/webrtc/ice');
53
+ }
54
+
55
+ /** Map /calls/create ice_servers → RTCPeerConnection config. */
56
+ export function mapIceServersFromCreate(
57
+ servers: CreateCallIceServer[] | undefined,
58
+ ): RTCIceServer[] {
59
+ if (!servers?.length) {
60
+ throw new Error(
61
+ '[VaniraAI] createCall response missing ice_servers — call cannot proceed',
62
+ );
63
+ }
64
+
65
+ return servers.map((server) => ({
66
+ urls: server.urls,
67
+ ...(server.username ? { username: server.username } : {}),
68
+ ...(server.credential ? { credential: server.credential } : {}),
69
+ }));
70
+ }
71
+
72
+ export const ICE_TRICKLE_POLL_MS = 150;
package/src/index.js ADDED
@@ -0,0 +1,34 @@
1
+ import { ConvAIWidget } from './ui/widget.js';
2
+
3
+ // Auto-detect settings from the script tag
4
+ const currentScript = document.currentScript;
5
+ const agentId = currentScript?.getAttribute('data-agent-id');
6
+ const position = currentScript?.getAttribute('data-position') || 'bottom-right';
7
+ const primaryColor = currentScript?.getAttribute('data-primary-color') || '#000000';
8
+ const serverUrl = currentScript?.getAttribute('data-server-url') || 'https://api.vanira.io';
9
+
10
+ // Define the custom element
11
+ if (!customElements.get('vanira-convai')) {
12
+ customElements.define('vanira-convai', ConvAIWidget);
13
+ }
14
+
15
+ // Auto-inject if script tag has data-agent-id (Zero-Code path)
16
+ if (agentId) {
17
+ const widget = document.createElement('vanira-convai');
18
+ widget.setAttribute('agent-id', agentId);
19
+ widget.setAttribute('position', position);
20
+ widget.setAttribute('primary-color', primaryColor);
21
+ widget.setAttribute('server-url', serverUrl);
22
+
23
+ // Wait for DOM to be ready
24
+ if (document.readyState === 'loading') {
25
+ document.addEventListener('DOMContentLoaded', () => {
26
+ document.body.appendChild(widget);
27
+ });
28
+ } else {
29
+ document.body.appendChild(widget);
30
+ }
31
+ }
32
+
33
+ // Export for advanced users who want manual placement
34
+ window.VaniraConvAI = ConvAIWidget;
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @vanira/sdk-react-native — React Native entry point.
3
+ * Web SDK lives in vanira-sdk/ (unchanged).
4
+ */
5
+ export * from './platforms/react-native';
6
+ export * from './presets';
@@ -0,0 +1,67 @@
1
+ /**
2
+ * @vanira/sdk — Browser entry point
3
+ *
4
+ * Usage:
5
+ * import { createBrowserClient, VaniraAI, WebRTCClient } from '@vanira/sdk/browser'
6
+ *
7
+ * This entry point:
8
+ * - Exports factory functions (createBrowserClient, createBrowserAI) that
9
+ * pre-wire all browser adapters via browserRuntime.
10
+ * - Re-exports core classes and adapter classes for manual wiring.
11
+ * - Does NOT include widget / custom-element / DOM bootstrapping code
12
+ * (that lives in cdn.ts and ui/).
13
+ */
14
+
15
+ // ── Runtime factory functions (recommended) ───────────────────────────────────
16
+ export {
17
+ createBrowserClient,
18
+ createBrowserAI,
19
+ browserRuntime,
20
+ browserCapabilities,
21
+ } from '../runtime/browserRuntime';
22
+
23
+ // ── Core session classes ──────────────────────────────────────────────────────
24
+ export { WebRTCClient } from '../core/WebRTCClient';
25
+ export { VaniraAI } from '../core/VaniraAI';
26
+ export { SessionManager } from '../core/SessionManager';
27
+
28
+ // ── Browser adapter implementations (available for manual wiring if needed) ──
29
+ export { BrowserAudioAdapter } from '../adapters/browser/BrowserAudioAdapter';
30
+ export { BrowserMediaAdapter } from '../adapters/browser/BrowserMediaAdapter';
31
+ export { BrowserPeerAdapter } from '../adapters/browser/BrowserPeerAdapter';
32
+ export { BrowserDataChannelAdapter } from '../adapters/browser/BrowserDataChannelAdapter';
33
+
34
+ // ── Services ──────────────────────────────────────────────────────────────────
35
+ export { ConfigService } from '../api/services/ConfigService';
36
+ export { ChatService } from '../api/services/ChatService';
37
+
38
+ // ── Types ─────────────────────────────────────────────────────────────────────
39
+ export type {
40
+ WebRTCClientConfig,
41
+ ControlEvent,
42
+ WidgetConfig,
43
+ WidgetMode,
44
+ Agent,
45
+ } from '../types';
46
+ export type {
47
+ VaniraAIConfig,
48
+ VaniraAIStatus,
49
+ ClientToolCall,
50
+ TranscriptionEvent,
51
+ } from '../core/VaniraAI';
52
+ export type {
53
+ AudioAdapter,
54
+ MediaAdapter,
55
+ AudioPlayerHandle,
56
+ MediaAudioConstraints,
57
+ } from '../adapters/interfaces';
58
+ export type {
59
+ PeerConnectionAdapter,
60
+ DataChannelAdapter,
61
+ PeerConnectionConfig,
62
+ DataChannelController,
63
+ } from '../adapters/PeerConnectionAdapter';
64
+ export type {
65
+ VaniraRuntime,
66
+ RuntimeCapabilities,
67
+ } from '../runtime/types';
@@ -0,0 +1,105 @@
1
+ /**
2
+ * @vanira/sdk-react-native — React Native entry point
3
+ *
4
+ * Usage:
5
+ * import { createReactNativeClient, VaniraAI } from '@vanira/sdk-react-native'
6
+ *
7
+ * Prerequisites in the React Native project:
8
+ * npm install react-native-webrtc permission-handler
9
+ * # iOS: pod install
10
+ * # Add NSMicrophoneUsageDescription to Info.plist
11
+ * # Add RECORD_AUDIO permission to AndroidManifest.xml
12
+ * # Call registerGlobals() from react-native-webrtc at app entry:
13
+ * # import { registerGlobals } from 'react-native-webrtc';
14
+ * # registerGlobals();
15
+ *
16
+ * Platform differences vs browser:
17
+ * - supportsAudioEndedEvent: false — audio routes via native stack, no onended.
18
+ * playedStream is sent ~1.8s after final transcription (not on mark events).
19
+ * - supportsDom: false — no document/window APIs.
20
+ * - supportsHtmlAudio: false — no Audio element.
21
+ * - supportsCustomElements: false — no customElements.define().
22
+ * - supportsLocalStorage: false — use AsyncStorage instead.
23
+ * - callType: 'web', runtimeName: 'react-native' in /calls/create body.
24
+ *
25
+ * Legacy names:
26
+ * createVaniraClient / createVaniraAI still exported for backward compat.
27
+ */
28
+
29
+ // ── Runtime factory functions (recommended) ───────────────────────────────────
30
+ export {
31
+ createReactNativeClient,
32
+ createReactNativeAI,
33
+ reactNativeRuntime,
34
+ reactNativeCapabilities,
35
+ } from '../runtime/reactNativeRuntime';
36
+
37
+ // ── Core session classes ──────────────────────────────────────────────────────
38
+ export { WebRTCClient } from '../core/WebRTCClient';
39
+ export { VaniraAI } from '../core/VaniraAI';
40
+ export {
41
+ hasContinueSession,
42
+ loadContinueSession,
43
+ saveCallSession,
44
+ } from '../core/CallSessionStore';
45
+ export type { StoredCallSession } from '../core/CallSessionStore';
46
+ export { createSyncStorageAdapter } from '../adapters/storage/createSyncStorageAdapter';
47
+ export type { SyncStorageBackend } from '../adapters/storage/createSyncStorageAdapter';
48
+
49
+ // ── RN adapter implementations ────────────────────────────────────────────────
50
+ export { RNAudioAdapter } from '../adapters/react-native/RNAudioAdapter';
51
+ export { RNMediaAdapter } from '../adapters/react-native/RNMediaAdapter';
52
+ export { RNPeerAdapter } from '../adapters/react-native/RNPeerAdapter';
53
+ export { RNDataChannelAdapter } from '../adapters/react-native/RNDataChannelAdapter';
54
+
55
+ // ── Types ─────────────────────────────────────────────────────────────────────
56
+ export type {
57
+ WebRTCClientConfig,
58
+ ControlEvent,
59
+ } from '../types';
60
+ export type {
61
+ VaniraAIConfig,
62
+ VaniraAIStatus,
63
+ ClientToolCall,
64
+ TranscriptionEvent,
65
+ } from '../core/VaniraAI';
66
+ export type {
67
+ AudioAdapter,
68
+ MediaAdapter,
69
+ AudioPlayerHandle,
70
+ MediaAudioConstraints,
71
+ } from '../adapters/interfaces';
72
+ export type {
73
+ PeerConnectionAdapter,
74
+ DataChannelAdapter,
75
+ PeerConnectionConfig,
76
+ DataChannelController,
77
+ } from '../adapters/PeerConnectionAdapter';
78
+ export type {
79
+ VaniraRuntime,
80
+ RuntimeCapabilities,
81
+ } from '../runtime/types';
82
+
83
+ // ── Backward-compatible legacy factories ─────────────────────────────────────
84
+ //
85
+ // These names existed before the runtime layer was introduced.
86
+ // They now delegate to createReactNativeClient / createReactNativeAI.
87
+ // Prefer the new names for new code.
88
+
89
+ import { createReactNativeClient, createReactNativeAI } from '../runtime/reactNativeRuntime';
90
+ import type { WebRTCClientConfig } from '../types';
91
+ import type { VaniraAIConfig } from '../core/VaniraAI';
92
+ import { WebRTCClient } from '../core/WebRTCClient';
93
+ import { VaniraAI } from '../core/VaniraAI';
94
+
95
+ /** @deprecated Use createReactNativeClient() instead */
96
+ export function createVaniraClient(
97
+ config: Omit<WebRTCClientConfig, 'audioAdapter' | 'mediaAdapter' | 'peerAdapter' | 'dataChannelAdapter'>
98
+ ): WebRTCClient {
99
+ return createReactNativeClient(config);
100
+ }
101
+
102
+ /** @deprecated Use createReactNativeAI() instead */
103
+ export function createVaniraAI(config: VaniraAIConfig): VaniraAI {
104
+ return createReactNativeAI(config);
105
+ }