@solveo-ai/react-native 0.1.0 → 0.1.1

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/dist/index.mjs ADDED
@@ -0,0 +1,822 @@
1
+ import React, { createContext, useMemo, useState, useEffect, useContext, useCallback } from 'react';
2
+ import { SolveoSDK, createCustomerSocket, CustomerSocketManager, createAgentSocket, AgentSocketManager, createStreamingSocket } from '@solveo-ai/sdk-core';
3
+ import AsyncStorage from '@react-native-async-storage/async-storage';
4
+ import { Platform } from 'react-native';
5
+
6
+ // src/SolveoProvider.tsx
7
+ var SolveoContext = createContext(null);
8
+ function SolveoProvider({ children, config }) {
9
+ const sdk = useMemo(
10
+ () => new SolveoSDK({
11
+ ...config,
12
+ platform: "react-native"
13
+ }),
14
+ [config.apiUrl, config.apiKey, config.token, config.widgetId]
15
+ );
16
+ const [customerSocket, setCustomerSocket] = useState(null);
17
+ const [customerSocketManager, setCustomerSocketManager] = useState(null);
18
+ const [agentSocket, setAgentSocket] = useState(null);
19
+ const [agentSocketManager, setAgentSocketManager] = useState(null);
20
+ const [streamingSocket, setStreamingSocket] = useState(null);
21
+ const connectCustomerSocket = (conversationId) => {
22
+ if (customerSocket) {
23
+ customerSocket.disconnect();
24
+ }
25
+ const socket = createCustomerSocket(config.apiUrl, conversationId);
26
+ const manager = new CustomerSocketManager(socket);
27
+ setCustomerSocket(socket);
28
+ setCustomerSocketManager(manager);
29
+ socket.connect();
30
+ };
31
+ const disconnectCustomerSocket = () => {
32
+ if (customerSocket) {
33
+ customerSocket.disconnect();
34
+ setCustomerSocket(null);
35
+ setCustomerSocketManager(null);
36
+ }
37
+ };
38
+ const connectAgentSocket = (token) => {
39
+ if (agentSocket) {
40
+ agentSocket.disconnect();
41
+ }
42
+ const socket = createAgentSocket(config.apiUrl, token);
43
+ const manager = new AgentSocketManager(socket);
44
+ setAgentSocket(socket);
45
+ setAgentSocketManager(manager);
46
+ socket.connect();
47
+ };
48
+ const disconnectAgentSocket = () => {
49
+ if (agentSocket) {
50
+ agentSocket.disconnect();
51
+ setAgentSocket(null);
52
+ setAgentSocketManager(null);
53
+ }
54
+ };
55
+ const connectStreamingSocket = (conversationId) => {
56
+ if (streamingSocket) {
57
+ streamingSocket.disconnect();
58
+ }
59
+ const socket = createStreamingSocket(config.apiUrl, conversationId);
60
+ setStreamingSocket(socket);
61
+ socket.connect();
62
+ };
63
+ const disconnectStreamingSocket = () => {
64
+ if (streamingSocket) {
65
+ streamingSocket.disconnect();
66
+ setStreamingSocket(null);
67
+ }
68
+ };
69
+ useEffect(() => {
70
+ if (config.autoConnectCustomer && config.conversationId) {
71
+ connectCustomerSocket(config.conversationId);
72
+ }
73
+ if (config.autoConnectAgent && config.token) {
74
+ connectAgentSocket(config.token);
75
+ }
76
+ return () => {
77
+ disconnectCustomerSocket();
78
+ disconnectAgentSocket();
79
+ disconnectStreamingSocket();
80
+ };
81
+ }, [config.autoConnectCustomer, config.autoConnectAgent, config.conversationId, config.token]);
82
+ const value = {
83
+ sdk,
84
+ config,
85
+ customerSocket,
86
+ customerSocketManager,
87
+ agentSocket,
88
+ agentSocketManager,
89
+ streamingSocket,
90
+ connectCustomerSocket,
91
+ disconnectCustomerSocket,
92
+ connectAgentSocket,
93
+ disconnectAgentSocket,
94
+ connectStreamingSocket,
95
+ disconnectStreamingSocket
96
+ };
97
+ return /* @__PURE__ */ React.createElement(SolveoContext.Provider, { value }, children);
98
+ }
99
+ function useSolveo() {
100
+ const context = useContext(SolveoContext);
101
+ if (!context) {
102
+ throw new Error("useSolveo must be used within a SolveoProvider");
103
+ }
104
+ return context;
105
+ }
106
+ var STORAGE_KEYS = {
107
+ CONVERSATION_ID: "@solveo/conversation_id",
108
+ MESSAGES: "@solveo/messages",
109
+ USER_IDENTITY: "@solveo/user_identity",
110
+ WIDGET_CONFIG: "@solveo/widget_config"
111
+ };
112
+ var storage = {
113
+ async getConversationId() {
114
+ return AsyncStorage.getItem(STORAGE_KEYS.CONVERSATION_ID);
115
+ },
116
+ async setConversationId(id) {
117
+ await AsyncStorage.setItem(STORAGE_KEYS.CONVERSATION_ID, id);
118
+ },
119
+ async clearConversationId() {
120
+ await AsyncStorage.removeItem(STORAGE_KEYS.CONVERSATION_ID);
121
+ },
122
+ async getMessages(conversationId) {
123
+ const data = await AsyncStorage.getItem(`${STORAGE_KEYS.MESSAGES}_${conversationId}`);
124
+ return data ? JSON.parse(data) : [];
125
+ },
126
+ async setMessages(conversationId, messages) {
127
+ await AsyncStorage.setItem(`${STORAGE_KEYS.MESSAGES}_${conversationId}`, JSON.stringify(messages));
128
+ },
129
+ async getUserIdentity() {
130
+ const data = await AsyncStorage.getItem(STORAGE_KEYS.USER_IDENTITY);
131
+ return data ? JSON.parse(data) : null;
132
+ },
133
+ async setUserIdentity(identity) {
134
+ await AsyncStorage.setItem(STORAGE_KEYS.USER_IDENTITY, JSON.stringify(identity));
135
+ },
136
+ async getWidgetConfig(widgetId) {
137
+ const data = await AsyncStorage.getItem(`${STORAGE_KEYS.WIDGET_CONFIG}_${widgetId}`);
138
+ return data ? JSON.parse(data) : null;
139
+ },
140
+ async setWidgetConfig(widgetId, config) {
141
+ await AsyncStorage.setItem(`${STORAGE_KEYS.WIDGET_CONFIG}_${widgetId}`, JSON.stringify(config));
142
+ }
143
+ };
144
+
145
+ // src/hooks/useChat.ts
146
+ function useChat(options = {}) {
147
+ const { sdk, config, streamingSocket, connectStreamingSocket } = useSolveo();
148
+ const [conversation, setConversation] = useState(null);
149
+ const [messages, setMessages] = useState([]);
150
+ const [isLoading, setIsLoading] = useState(false);
151
+ const [isStreaming, setIsStreaming] = useState(false);
152
+ const [streamingContent, setStreamingContent] = useState("");
153
+ const [error, setError] = useState(null);
154
+ useEffect(() => {
155
+ if (options.persistConversation) {
156
+ loadPersistedConversation();
157
+ }
158
+ }, [options.persistConversation]);
159
+ useEffect(() => {
160
+ if (!streamingSocket || !conversation) return;
161
+ const handleMessage = (msg) => {
162
+ if (msg.type === "agent_typing") {
163
+ setIsStreaming(true);
164
+ setStreamingContent("");
165
+ } else if (msg.type === "agent_message_chunk") {
166
+ setStreamingContent((prev) => prev + (msg.content || ""));
167
+ } else if (msg.type === "agent_message_complete") {
168
+ setIsStreaming(false);
169
+ if (msg.message_id) {
170
+ loadMessages(conversation.id);
171
+ }
172
+ }
173
+ };
174
+ const handleError = (err) => {
175
+ setError(new Error(err.message || "Streaming error"));
176
+ setIsStreaming(false);
177
+ };
178
+ streamingSocket.on("message", handleMessage);
179
+ streamingSocket.on("error", handleError);
180
+ return () => {
181
+ streamingSocket.off("message", handleMessage);
182
+ streamingSocket.off("error", handleError);
183
+ };
184
+ }, [streamingSocket, conversation]);
185
+ const loadPersistedConversation = async () => {
186
+ try {
187
+ const convId = await storage.getConversationId();
188
+ if (convId) {
189
+ const conv = await sdk.widgetConversations.get(convId);
190
+ setConversation(conv);
191
+ await loadMessages(convId);
192
+ }
193
+ } catch (err) {
194
+ console.error("Failed to load persisted conversation:", err);
195
+ }
196
+ };
197
+ const createConversation = useCallback(async () => {
198
+ setIsLoading(true);
199
+ setError(null);
200
+ try {
201
+ const identity = await storage.getUserIdentity();
202
+ const conv = await sdk.widgetConversations.create({
203
+ widget_id: options.widgetId || config.widgetId,
204
+ agent_id: options.agentId,
205
+ customer_email: identity?.email || config.user?.email,
206
+ customer_name: identity?.name || config.user?.name
207
+ });
208
+ setConversation(conv);
209
+ if (options.persistConversation) {
210
+ await storage.setConversationId(conv.id);
211
+ }
212
+ connectStreamingSocket(conv.id);
213
+ return conv;
214
+ } catch (err) {
215
+ setError(err);
216
+ throw err;
217
+ } finally {
218
+ setIsLoading(false);
219
+ }
220
+ }, [sdk, options, config]);
221
+ const loadMessages = useCallback(async (conversationId) => {
222
+ try {
223
+ const msgs = await sdk.widgetMessages.list(conversationId);
224
+ setMessages(msgs);
225
+ if (options.persistConversation) {
226
+ await storage.setMessages(conversationId, msgs);
227
+ }
228
+ } catch (err) {
229
+ setError(err);
230
+ }
231
+ }, [sdk, options]);
232
+ const sendMessage = useCallback(async (content) => {
233
+ if (!conversation) {
234
+ throw new Error("No active conversation");
235
+ }
236
+ setIsLoading(true);
237
+ setError(null);
238
+ try {
239
+ if (streamingSocket?.isConnected()) {
240
+ streamingSocket.sendMessage(content);
241
+ } else {
242
+ const response = await sdk.widgetMessages.send(conversation.id, { content });
243
+ setMessages((prev) => [...prev, response.message, response.ai_response].filter(Boolean));
244
+ }
245
+ } catch (err) {
246
+ setError(err);
247
+ throw err;
248
+ } finally {
249
+ setIsLoading(false);
250
+ }
251
+ }, [conversation, streamingSocket, sdk]);
252
+ const submitFeedback = useCallback(async (messageId, rating) => {
253
+ if (!conversation) return;
254
+ try {
255
+ await sdk.widgetMessages.submitFeedback(conversation.id, messageId, rating);
256
+ } catch (err) {
257
+ setError(err);
258
+ }
259
+ }, [conversation, sdk]);
260
+ return {
261
+ conversation,
262
+ messages,
263
+ isLoading,
264
+ isStreaming,
265
+ streamingContent,
266
+ error,
267
+ createConversation,
268
+ sendMessage,
269
+ loadMessages,
270
+ submitFeedback
271
+ };
272
+ }
273
+ function useRealtime() {
274
+ const { customerSocketManager } = useSolveo();
275
+ const [isConnected, setIsConnected] = useState(false);
276
+ const [agentTyping, setAgentTyping] = useState(false);
277
+ const [queuePosition, setQueuePosition] = useState(null);
278
+ const [estimatedWait, setEstimatedWait] = useState(null);
279
+ useEffect(() => {
280
+ if (!customerSocketManager) return;
281
+ const socket = customerSocketManager.getSocket();
282
+ socket.on("connect", () => setIsConnected(true));
283
+ socket.on("disconnect", () => setIsConnected(false));
284
+ customerSocketManager.onAgentTyping(() => {
285
+ setAgentTyping(true);
286
+ setTimeout(() => setAgentTyping(false), 3e3);
287
+ });
288
+ customerSocketManager.onQueueUpdate((data) => {
289
+ setQueuePosition(data.queue_position);
290
+ setEstimatedWait(data.estimated_wait);
291
+ });
292
+ return () => {
293
+ socket.off("connect");
294
+ socket.off("disconnect");
295
+ };
296
+ }, [customerSocketManager]);
297
+ return {
298
+ isConnected,
299
+ agentTyping,
300
+ queuePosition,
301
+ estimatedWait
302
+ };
303
+ }
304
+ function useEscalation() {
305
+ const { customerSocketManager, connectCustomerSocket, disconnectCustomerSocket } = useSolveo();
306
+ const [escalationId, setEscalationId] = useState(null);
307
+ const [assignedAgent, setAssignedAgent] = useState(null);
308
+ const [status, setStatus] = useState("idle");
309
+ useEffect(() => {
310
+ if (!customerSocketManager) return;
311
+ customerSocketManager.onAgentConnected((data) => {
312
+ setEscalationId(data.escalation_id);
313
+ setAssignedAgent({
314
+ name: data.agent_name,
315
+ avatar_url: data.agent_avatar
316
+ });
317
+ setStatus("active");
318
+ });
319
+ customerSocketManager.onQueueUpdate((data) => {
320
+ setStatus("queued");
321
+ });
322
+ customerSocketManager.onEscalationResolved(() => {
323
+ setStatus("resolved");
324
+ });
325
+ customerSocketManager.onHandedBack(() => {
326
+ setStatus("idle");
327
+ setEscalationId(null);
328
+ setAssignedAgent(null);
329
+ });
330
+ }, [customerSocketManager]);
331
+ const requestHuman = useCallback((conversationId) => {
332
+ connectCustomerSocket(conversationId);
333
+ if (customerSocketManager) {
334
+ customerSocketManager.requestHuman();
335
+ setStatus("requesting");
336
+ }
337
+ }, [customerSocketManager, connectCustomerSocket]);
338
+ const endChat = useCallback(() => {
339
+ if (escalationId && customerSocketManager) {
340
+ customerSocketManager.endChat(escalationId);
341
+ disconnectCustomerSocket();
342
+ setStatus("idle");
343
+ setEscalationId(null);
344
+ setAssignedAgent(null);
345
+ }
346
+ }, [escalationId, customerSocketManager, disconnectCustomerSocket]);
347
+ const submitCSAT = useCallback((rating) => {
348
+ if (escalationId && customerSocketManager) {
349
+ customerSocketManager.submitCSAT(escalationId, rating);
350
+ }
351
+ }, [escalationId, customerSocketManager]);
352
+ return {
353
+ escalationId,
354
+ assignedAgent,
355
+ status,
356
+ requestHuman,
357
+ endChat,
358
+ submitCSAT
359
+ };
360
+ }
361
+ function useWidgetConfig(widgetId) {
362
+ const { sdk, config } = useSolveo();
363
+ const effectiveWidgetId = widgetId || config.widgetId;
364
+ const [widget, setWidget] = useState(null);
365
+ const [widgetConfig, setWidgetConfig] = useState(null);
366
+ const [isLoading, setIsLoading] = useState(false);
367
+ const [error, setError] = useState(null);
368
+ useEffect(() => {
369
+ if (effectiveWidgetId) {
370
+ loadConfig();
371
+ }
372
+ }, [effectiveWidgetId]);
373
+ const loadConfig = async () => {
374
+ if (!effectiveWidgetId) return;
375
+ setIsLoading(true);
376
+ setError(null);
377
+ try {
378
+ const cached = await storage.getWidgetConfig(effectiveWidgetId);
379
+ if (cached) {
380
+ setWidget(cached);
381
+ setWidgetConfig(cached.config);
382
+ }
383
+ const w = await sdk.widgetConfig.getConfig(effectiveWidgetId);
384
+ setWidget(w);
385
+ setWidgetConfig(w.config);
386
+ await storage.setWidgetConfig(effectiveWidgetId, w);
387
+ } catch (err) {
388
+ setError(err);
389
+ } finally {
390
+ setIsLoading(false);
391
+ }
392
+ };
393
+ return {
394
+ widget,
395
+ widgetConfig,
396
+ isLoading,
397
+ error,
398
+ reload: loadConfig
399
+ };
400
+ }
401
+ function usePushNotifications(options = {}) {
402
+ const { sdk } = useSolveo();
403
+ const {
404
+ mode = "widget",
405
+ conversationId,
406
+ autoRegister = false,
407
+ handlers
408
+ } = options;
409
+ const [state, setState] = useState({
410
+ device: null,
411
+ isRegistered: false,
412
+ isInitialized: false,
413
+ permissionStatus: "undetermined"
414
+ });
415
+ const requestPermission = useCallback(async () => {
416
+ try {
417
+ const messaging = await import('@react-native-firebase/messaging').then((m) => m.default);
418
+ const authStatus = await messaging().requestPermission();
419
+ const enabled = authStatus === messaging.AuthorizationStatus.AUTHORIZED || authStatus === messaging.AuthorizationStatus.PROVISIONAL;
420
+ setState((prev) => ({
421
+ ...prev,
422
+ permissionStatus: enabled ? "granted" : "denied"
423
+ }));
424
+ return enabled;
425
+ } catch (error) {
426
+ console.error("Failed to request permission (Firebase Messaging not installed?):", error);
427
+ try {
428
+ const Notifications = await import('expo-notifications');
429
+ const { status } = await Notifications.requestPermissionsAsync();
430
+ const granted = status === "granted";
431
+ setState((prev) => ({
432
+ ...prev,
433
+ permissionStatus: granted ? "granted" : "denied"
434
+ }));
435
+ return granted;
436
+ } catch (expoError) {
437
+ console.error("Failed to request permission (Expo Notifications not installed?):", expoError);
438
+ setState((prev) => ({ ...prev, permissionStatus: "denied" }));
439
+ return false;
440
+ }
441
+ }
442
+ }, []);
443
+ const getToken = useCallback(async () => {
444
+ try {
445
+ const messaging = await import('@react-native-firebase/messaging').then((m) => m.default);
446
+ const token = await messaging().getToken();
447
+ return token;
448
+ } catch (error) {
449
+ console.error("Failed to get FCM token:", error);
450
+ try {
451
+ const Notifications = await import('expo-notifications');
452
+ const token = await Notifications.getExpoPushTokenAsync();
453
+ return token.data;
454
+ } catch (expoError) {
455
+ console.error("Failed to get Expo push token:", expoError);
456
+ return null;
457
+ }
458
+ }
459
+ }, []);
460
+ const registerDevice = useCallback(
461
+ async (convId, pushToken, bundleId) => {
462
+ if (mode !== "widget") {
463
+ console.warn("registerDevice is only for widget mode. Use registerAgentDevice for agent mode.");
464
+ return null;
465
+ }
466
+ const targetConversationId = convId || conversationId;
467
+ if (!targetConversationId) {
468
+ console.error("conversationId is required for widget mode registration");
469
+ return null;
470
+ }
471
+ try {
472
+ const token = pushToken || await getToken();
473
+ if (!token) {
474
+ throw new Error("Failed to get push token");
475
+ }
476
+ const platform = Platform.OS === "ios" ? "IOS" : "ANDROID";
477
+ const device = await sdk.widgetDevices.register({
478
+ conversation_id: targetConversationId,
479
+ platform,
480
+ push_token: token,
481
+ bundle_id: bundleId
482
+ });
483
+ setState((prev) => ({ ...prev, device, isRegistered: true }));
484
+ return device;
485
+ } catch (error) {
486
+ console.error("Failed to register device:", error);
487
+ throw error;
488
+ }
489
+ },
490
+ [sdk, conversationId, mode, getToken]
491
+ );
492
+ const registerAgentDevice = useCallback(
493
+ async (pushToken, deviceName) => {
494
+ if (mode !== "agent") {
495
+ console.warn("registerAgentDevice is only for agent mode. Use registerDevice for widget mode.");
496
+ return null;
497
+ }
498
+ try {
499
+ const token = pushToken || await getToken();
500
+ if (!token) {
501
+ throw new Error("Failed to get push token");
502
+ }
503
+ const platform = Platform.OS === "ios" ? "IOS" : "ANDROID";
504
+ const response = await sdk.client.post("/user-devices", {
505
+ platform,
506
+ push_token: token,
507
+ device_name: deviceName || `${Platform.OS} Device`
508
+ });
509
+ setState((prev) => ({
510
+ ...prev,
511
+ device: response.data,
512
+ isRegistered: true
513
+ }));
514
+ return response.data;
515
+ } catch (error) {
516
+ console.error("Failed to register agent device:", error);
517
+ throw error;
518
+ }
519
+ },
520
+ [sdk, mode, getToken]
521
+ );
522
+ const unregisterDevice = useCallback(async () => {
523
+ if (!state.device) {
524
+ return;
525
+ }
526
+ try {
527
+ if (mode === "widget") {
528
+ await sdk.widgetDevices.unregister(state.device.id);
529
+ } else {
530
+ await sdk.client.delete(`/user-devices/${state.device.id}`);
531
+ }
532
+ setState((prev) => ({
533
+ ...prev,
534
+ device: null,
535
+ isRegistered: false
536
+ }));
537
+ } catch (error) {
538
+ console.error("Failed to unregister device:", error);
539
+ throw error;
540
+ }
541
+ }, [sdk, state.device, mode]);
542
+ useEffect(() => {
543
+ if (!handlers?.onNotificationReceived) {
544
+ return;
545
+ }
546
+ let unsubscribe;
547
+ const setupForegroundHandler = async () => {
548
+ try {
549
+ const messaging = await import('@react-native-firebase/messaging').then((m) => m.default);
550
+ unsubscribe = messaging().onMessage(async (remoteMessage) => {
551
+ console.log("Foreground notification received:", remoteMessage);
552
+ handlers.onNotificationReceived?.(remoteMessage);
553
+ });
554
+ } catch (error) {
555
+ try {
556
+ const Notifications = await import('expo-notifications');
557
+ const subscription = Notifications.addNotificationReceivedListener((notification) => {
558
+ console.log("Foreground notification received (Expo):", notification);
559
+ handlers.onNotificationReceived?.(notification);
560
+ });
561
+ unsubscribe = () => subscription.remove();
562
+ } catch (expoError) {
563
+ console.error("Failed to setup foreground handler:", expoError);
564
+ }
565
+ }
566
+ };
567
+ setupForegroundHandler();
568
+ return () => {
569
+ unsubscribe?.();
570
+ };
571
+ }, [handlers?.onNotificationReceived]);
572
+ useEffect(() => {
573
+ if (!handlers?.onNotificationTapped) {
574
+ return;
575
+ }
576
+ let unsubscribe;
577
+ const setupTapHandler = async () => {
578
+ try {
579
+ const messaging = await import('@react-native-firebase/messaging').then((m) => m.default);
580
+ messaging().getInitialNotification().then((remoteMessage) => {
581
+ if (remoteMessage) {
582
+ console.log("Notification opened app from quit state:", remoteMessage);
583
+ handlers.onNotificationTapped?.(remoteMessage);
584
+ }
585
+ });
586
+ unsubscribe = messaging().onNotificationOpenedApp((remoteMessage) => {
587
+ console.log("Notification opened app from background:", remoteMessage);
588
+ handlers.onNotificationTapped?.(remoteMessage);
589
+ });
590
+ } catch (error) {
591
+ try {
592
+ const Notifications = await import('expo-notifications');
593
+ const subscription = Notifications.addNotificationResponseReceivedListener((response) => {
594
+ console.log("Notification tapped (Expo):", response);
595
+ handlers.onNotificationTapped?.(response.notification);
596
+ });
597
+ unsubscribe = () => subscription.remove();
598
+ } catch (expoError) {
599
+ console.error("Failed to setup tap handler:", expoError);
600
+ }
601
+ }
602
+ };
603
+ setupTapHandler();
604
+ return () => {
605
+ unsubscribe?.();
606
+ };
607
+ }, [handlers?.onNotificationTapped]);
608
+ useEffect(() => {
609
+ let unsubscribe;
610
+ const setupTokenRefreshHandler = async () => {
611
+ try {
612
+ const messaging = await import('@react-native-firebase/messaging').then((m) => m.default);
613
+ unsubscribe = messaging().onTokenRefresh(async (newToken) => {
614
+ console.log("FCM token refreshed:", newToken);
615
+ if (state.isRegistered) {
616
+ if (mode === "widget" && conversationId) {
617
+ await registerDevice(conversationId, newToken);
618
+ } else if (mode === "agent") {
619
+ await registerAgentDevice(newToken);
620
+ }
621
+ }
622
+ });
623
+ } catch (error) {
624
+ console.error("Failed to setup token refresh handler:", error);
625
+ }
626
+ };
627
+ setupTokenRefreshHandler();
628
+ return () => {
629
+ unsubscribe?.();
630
+ };
631
+ }, [state.isRegistered, mode, conversationId, registerDevice, registerAgentDevice]);
632
+ useEffect(() => {
633
+ const checkPermission = async () => {
634
+ try {
635
+ const messaging = await import('@react-native-firebase/messaging').then((m) => m.default);
636
+ const authStatus = await messaging().hasPermission();
637
+ const granted = authStatus === messaging.AuthorizationStatus.AUTHORIZED || authStatus === messaging.AuthorizationStatus.PROVISIONAL;
638
+ setState((prev) => ({
639
+ ...prev,
640
+ permissionStatus: granted ? "granted" : authStatus === messaging.AuthorizationStatus.NOT_DETERMINED ? "undetermined" : "denied",
641
+ isInitialized: true
642
+ }));
643
+ if (granted && autoRegister && !state.isRegistered) {
644
+ if (mode === "widget" && conversationId) {
645
+ await registerDevice();
646
+ } else if (mode === "agent") {
647
+ await registerAgentDevice();
648
+ }
649
+ }
650
+ } catch (error) {
651
+ console.error("Failed to check permission:", error);
652
+ setState((prev) => ({ ...prev, isInitialized: true }));
653
+ }
654
+ };
655
+ checkPermission();
656
+ }, [autoRegister, mode, conversationId, registerDevice, registerAgentDevice]);
657
+ return {
658
+ ...state,
659
+ requestPermission,
660
+ registerDevice,
661
+ registerAgentDevice,
662
+ unregisterDevice
663
+ };
664
+ }
665
+ function useAttachments(conversationId) {
666
+ const { sdk } = useSolveo();
667
+ const [uploading, setUploading] = useState(false);
668
+ const [uploadProgress, setUploadProgress] = useState(0);
669
+ const uploadAttachment = useCallback(async (file, attachmentType) => {
670
+ if (!conversationId) {
671
+ throw new Error("No conversation ID provided");
672
+ }
673
+ setUploading(true);
674
+ setUploadProgress(0);
675
+ try {
676
+ const response = await fetch(file.uri);
677
+ const blob = await response.blob();
678
+ const attachment = await sdk.widgetAttachments.upload(conversationId, blob, attachmentType);
679
+ setUploadProgress(100);
680
+ return attachment;
681
+ } catch (error) {
682
+ console.error("Failed to upload attachment:", error);
683
+ throw error;
684
+ } finally {
685
+ setUploading(false);
686
+ setUploadProgress(0);
687
+ }
688
+ }, [sdk, conversationId]);
689
+ return {
690
+ uploading,
691
+ uploadProgress,
692
+ uploadAttachment
693
+ };
694
+ }
695
+ function useAgents(accountId) {
696
+ const { sdk } = useSolveo();
697
+ const [agents, setAgents] = useState([]);
698
+ const [isLoading, setIsLoading] = useState(false);
699
+ const [error, setError] = useState(null);
700
+ const loadAgents = useCallback(async () => {
701
+ if (!accountId) return;
702
+ setIsLoading(true);
703
+ try {
704
+ const data = await sdk.agents.list(accountId);
705
+ setAgents(data);
706
+ } catch (err) {
707
+ setError(err);
708
+ } finally {
709
+ setIsLoading(false);
710
+ }
711
+ }, [sdk, accountId]);
712
+ useEffect(() => {
713
+ if (accountId) {
714
+ loadAgents();
715
+ }
716
+ }, [accountId]);
717
+ const createAgent = useCallback(async (data) => {
718
+ if (!accountId) throw new Error("No account ID");
719
+ const agent = await sdk.agents.create(accountId, data);
720
+ setAgents((prev) => [...prev, agent]);
721
+ return agent;
722
+ }, [sdk, accountId]);
723
+ const updateAgent = useCallback(async (id, data) => {
724
+ const agent = await sdk.agents.update(id, data);
725
+ setAgents((prev) => prev.map((a) => a.id === id ? agent : a));
726
+ return agent;
727
+ }, [sdk]);
728
+ const deleteAgent = useCallback(async (id) => {
729
+ await sdk.agents.delete(id);
730
+ setAgents((prev) => prev.filter((a) => a.id !== id));
731
+ }, [sdk]);
732
+ return {
733
+ agents,
734
+ isLoading,
735
+ error,
736
+ createAgent,
737
+ updateAgent,
738
+ deleteAgent,
739
+ reload: loadAgents
740
+ };
741
+ }
742
+ function useConversations(accountId) {
743
+ const { sdk } = useSolveo();
744
+ const [conversations, setConversations] = useState([]);
745
+ const [isLoading, setIsLoading] = useState(false);
746
+ const [error, setError] = useState(null);
747
+ const loadConversations = useCallback(async () => {
748
+ if (!accountId) return;
749
+ setIsLoading(true);
750
+ try {
751
+ const data = await sdk.conversations.list(accountId);
752
+ setConversations(data);
753
+ } catch (err) {
754
+ setError(err);
755
+ } finally {
756
+ setIsLoading(false);
757
+ }
758
+ }, [sdk, accountId]);
759
+ useEffect(() => {
760
+ if (accountId) {
761
+ loadConversations();
762
+ }
763
+ }, [accountId]);
764
+ const resolveConversation = useCallback(async (id) => {
765
+ await sdk.conversations.resolve(id);
766
+ setConversations((prev) => prev.map((c) => c.id === id ? { ...c, status: "RESOLVED" } : c));
767
+ }, [sdk]);
768
+ return {
769
+ conversations,
770
+ isLoading,
771
+ error,
772
+ resolveConversation,
773
+ reload: loadConversations
774
+ };
775
+ }
776
+ function useAnalytics(accountId, days = 30) {
777
+ const { sdk } = useSolveo();
778
+ const [metrics, setMetrics] = useState(null);
779
+ const [sentiment, setSentiment] = useState(null);
780
+ const [feedback, setFeedback] = useState(null);
781
+ const [tokenUsage, setTokenUsage] = useState(null);
782
+ const [isLoading, setIsLoading] = useState(false);
783
+ const [error, setError] = useState(null);
784
+ const loadAnalytics = useCallback(async () => {
785
+ if (!accountId) return;
786
+ setIsLoading(true);
787
+ try {
788
+ const [m, s, f, t] = await Promise.all([
789
+ sdk.analytics.getMetrics(accountId, days),
790
+ sdk.analytics.getSentiment(accountId, days),
791
+ sdk.analytics.getFeedback(accountId, days),
792
+ sdk.analytics.getTokenUsage(accountId, days)
793
+ ]);
794
+ setMetrics(m);
795
+ setSentiment(s);
796
+ setFeedback(f);
797
+ setTokenUsage(t);
798
+ } catch (err) {
799
+ setError(err);
800
+ } finally {
801
+ setIsLoading(false);
802
+ }
803
+ }, [sdk, accountId, days]);
804
+ useEffect(() => {
805
+ if (accountId) {
806
+ loadAnalytics();
807
+ }
808
+ }, [accountId, days]);
809
+ return {
810
+ metrics,
811
+ sentiment,
812
+ feedback,
813
+ tokenUsage,
814
+ isLoading,
815
+ error,
816
+ reload: loadAnalytics
817
+ };
818
+ }
819
+
820
+ export { SolveoProvider, storage, useAgents, useAnalytics, useAttachments, useChat, useConversations, useEscalation, usePushNotifications, useRealtime, useSolveo, useWidgetConfig };
821
+ //# sourceMappingURL=index.mjs.map
822
+ //# sourceMappingURL=index.mjs.map