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