@robylon/react-native-sdk 2.0.22 → 2.0.23-staging.0

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 (56) hide show
  1. package/lib/commonjs/Chatbotsdk.js +3 -3
  2. package/lib/commonjs/Chatbotsdk.js.map +1 -1
  3. package/lib/commonjs/config.js +1 -1
  4. package/lib/commonjs/config.js.map +1 -1
  5. package/lib/commonjs/constants.js +1 -1
  6. package/lib/commonjs/constants.js.map +1 -1
  7. package/lib/commonjs/openChatbot.js +2 -1
  8. package/lib/commonjs/versions/version.staging.js +1 -1
  9. package/lib/module/Chatbotsdk.js +3 -3
  10. package/lib/module/Chatbotsdk.js.map +1 -1
  11. package/lib/module/config.js +1 -1
  12. package/lib/module/config.js.map +1 -1
  13. package/lib/module/constants.js +1 -1
  14. package/lib/module/constants.js.map +1 -1
  15. package/lib/module/versions/version.staging.js +1 -1
  16. package/lib/typescript/Chatbotsdk.d.ts +11 -3
  17. package/lib/typescript/Chatbotsdk.d.ts.map +1 -1
  18. package/lib/typescript/index.d.ts +1 -1
  19. package/lib/typescript/index.d.ts.map +1 -1
  20. package/lib/typescript/versions/version.staging.d.ts +1 -1
  21. package/package.json +2 -2
  22. package/src/Chatbotsdk.tsx +853 -0
  23. package/src/FloatingButton/launchers/ImageLauncher.tsx +75 -0
  24. package/src/FloatingButton/launchers/TextLauncher.tsx +84 -0
  25. package/src/FloatingButton/launchers/TextualImageLauncher.tsx +133 -0
  26. package/src/FloatingButton.tsx +97 -0
  27. package/src/LoadingIndicator.tsx +11 -0
  28. package/src/Toast.tsx +65 -0
  29. package/src/components/DebugButton.tsx +46 -0
  30. package/src/components/ErrorBoundary.tsx +38 -0
  31. package/src/config.ts +4 -0
  32. package/src/constants/errorConstants.ts +21 -0
  33. package/src/constants/fontStyles.ts +3 -0
  34. package/src/constants.ts +4 -0
  35. package/src/global.d.ts +11 -0
  36. package/src/hooks/useChatbotEvents.ts +93 -0
  37. package/src/index.tsx +10 -0
  38. package/src/openChatbot.ts +11 -0
  39. package/src/openChatbot.tsx +0 -0
  40. package/src/services/ErrorTrackingService.ts +217 -0
  41. package/src/types/events.ts +25 -0
  42. package/src/types/react-native-flipper-performance-plugin.d.ts +3 -0
  43. package/src/types/react-native-globals.d.ts +10 -0
  44. package/src/utils/animations.ts +120 -0
  45. package/src/utils/colorUtils.ts +35 -0
  46. package/src/utils/cookieUtils.ts +7 -0
  47. package/src/utils/debugConfig.ts +56 -0
  48. package/src/utils/debugMenu.ts +14 -0
  49. package/src/utils/errorHandler.ts +58 -0
  50. package/src/utils/logger.ts +14 -0
  51. package/src/utils/systemInfo.ts +84 -0
  52. package/src/utils/webViewStorage.ts +62 -0
  53. package/src/version.ts +2 -0
  54. package/src/versions/version.dev.ts +2 -0
  55. package/src/versions/version.production.ts +2 -0
  56. package/src/versions/version.staging.ts +2 -0
@@ -0,0 +1,853 @@
1
+ import React, {
2
+ useState,
3
+ useEffect,
4
+ useRef,
5
+ useMemo,
6
+ useCallback,
7
+ useImperativeHandle,
8
+ forwardRef,
9
+ } from "react";
10
+ import {
11
+ View,
12
+ StyleSheet,
13
+ Animated,
14
+ Dimensions,
15
+ Platform,
16
+ StatusBar,
17
+ PixelRatio,
18
+ FlexAlignType,
19
+ } from "react-native";
20
+ import { WebView, WebViewMessageEvent } from "react-native-webview";
21
+ import FloatingButton from "./FloatingButton";
22
+ import { API_URL, BASE_CHATBOT_URL } from "./constants";
23
+ import { getSystemInfo, getBrowserAndOSInfoScript } from "./utils/systemInfo";
24
+ import { useChatbotEvents } from "./hooks/useChatbotEvents";
25
+ import { ChatbotEventType, ChatbotEventHandler } from "./types/events";
26
+ import { logger } from "./utils/logger";
27
+ import { enableNetworkDebug, enableWebViewDebug } from "./utils/debugConfig";
28
+ import DebugButton from "./components/DebugButton";
29
+ import { ErrorBoundary } from "./components/ErrorBoundary";
30
+ import {
31
+ ErrorCodes,
32
+ ErrorTypes,
33
+ ErrorMessages,
34
+ } from "./constants/errorConstants";
35
+ import { setupGlobalErrorHandlers } from "./utils/errorHandler";
36
+ import { errorTracker } from "./services/ErrorTrackingService";
37
+ import { generateUUID } from "./utils/cookieUtils";
38
+ import {
39
+ StorageMessageType,
40
+ StorageMessage,
41
+ getStorageScript,
42
+ createStorageMessage,
43
+ } from "./utils/webViewStorage";
44
+ import { animateWebViewOpen, animateWebViewClose } from "./utils/animations";
45
+
46
+ export interface ChatbotProps {
47
+ api_key: string;
48
+ user_id?: string | null | number;
49
+ user_token?: string;
50
+ user_profile?: {
51
+ email?: string;
52
+ name?: string;
53
+ mobile?: string;
54
+ is_test_user?: boolean;
55
+ };
56
+ onEvent?: ChatbotEventHandler;
57
+ // Controlled component props
58
+ isOpen?: boolean;
59
+ onOpen?: () => void;
60
+ onClose?: () => void;
61
+ onReady?: () => void;
62
+ }
63
+
64
+ export interface ChatbotRef {
65
+ open: () => void;
66
+ close: () => void;
67
+ toggle: () => void;
68
+ isReady: () => boolean;
69
+ }
70
+
71
+ interface InternalChatbotProps extends ChatbotProps {
72
+ show_floating_button?: boolean;
73
+ onMessage?: (message: any) => void;
74
+ isFullScreen?: boolean;
75
+ enableAnimation?: boolean;
76
+ }
77
+
78
+ interface ChatbotMessage {
79
+ type: string;
80
+ data?: any;
81
+ level?: string;
82
+ }
83
+
84
+ export enum ChatbotInterfaceType {
85
+ WIDGET = "WIDGET",
86
+ POPOVER = "POPOVER",
87
+ EMBED = "EMBED",
88
+ }
89
+
90
+ export enum WidgetPositionEnums {
91
+ RIGHT = "Right",
92
+ LEFT = "Left",
93
+ }
94
+
95
+ export interface WidgetInterfaceProperties {
96
+ position: WidgetPositionEnums;
97
+ side_spacing?: number;
98
+ bottom_spacing?: number;
99
+ }
100
+
101
+ export enum LauncherType {
102
+ TEXT = "TEXT",
103
+ IMAGE = "IMAGE",
104
+ TEXTUAL_IMAGE = "TEXTUAL_IMAGE",
105
+ }
106
+
107
+ export interface ChatbotConfig {
108
+ brand_colour: string;
109
+ image_url?: string;
110
+ chat_interface_config?: {
111
+ chat_bubble_prompts?: string[];
112
+ display_name?: string;
113
+ welcome_message?: string;
114
+ redirect_url?: string;
115
+ };
116
+ interface_type?: ChatbotInterfaceType;
117
+ interface_properties?: WidgetInterfaceProperties;
118
+ launcher_type: LauncherType;
119
+ launcher_properties: {
120
+ text?: string;
121
+ };
122
+ images: {
123
+ launcher_image_url?: {
124
+ url: string;
125
+ };
126
+ };
127
+ }
128
+
129
+ const Chatbot = forwardRef<ChatbotRef, InternalChatbotProps>(
130
+ (
131
+ {
132
+ api_key,
133
+ user_id,
134
+ user_token,
135
+ show_floating_button = true,
136
+ onEvent,
137
+ onMessage,
138
+ isFullScreen = true,
139
+ enableAnimation = true,
140
+ isOpen: externalIsOpen,
141
+ onOpen: externalOnOpen,
142
+ onClose: externalOnClose,
143
+ onReady,
144
+ user_profile,
145
+ },
146
+ ref
147
+ ) => {
148
+ const [isWebViewVisible, setIsWebViewVisible] = useState(false);
149
+ const [chatbotConfig, setChatbotConfig] = useState<ChatbotConfig | null>(
150
+ null
151
+ );
152
+ const [loading, setLoading] = useState(true);
153
+ const [isInitialized, setIsInitialized] = useState(false);
154
+ const webViewRef = useRef<WebView>(null);
155
+ const isFirstLoadRef = useRef(true);
156
+ const [toastVisible, setToastVisible] = useState(false);
157
+ const [toastMessage, setToastMessage] = useState("");
158
+ const [effectiveUserId, setEffectiveUserId] = useState<string>();
159
+ const [isStorageReady, setIsStorageReady] = useState(false);
160
+ const pendingStorageOps = useRef<Array<() => void>>([]);
161
+ const memoryCache = useRef<Record<string, string>>({});
162
+ const [systemInfo, setSystemInfo] = useState({ os: "", browser: "" });
163
+
164
+ // Initialize animated values
165
+ const animatedValues = useRef({
166
+ scale: new Animated.Value(1),
167
+ translateY: new Animated.Value(0),
168
+ opacity: new Animated.Value(1),
169
+ }).current;
170
+
171
+ // Create chatbotConfig object for events
172
+ const chatbotConfigForEvents = useMemo(
173
+ () => ({
174
+ userId: effectiveUserId,
175
+ isAnonymous: !user_id && user_id !== 0,
176
+ // Add other config properties as needed
177
+ }),
178
+ [effectiveUserId, user_id]
179
+ );
180
+
181
+ // Initialize event handling
182
+ const { emitEvent, onInternalEvent } = useChatbotEvents({
183
+ api_key,
184
+ chatbotConfig: chatbotConfigForEvents ?? {},
185
+ user_profile: user_profile,
186
+ onEvent: onEvent,
187
+ systemInfo: systemInfo ?? { os: "", browser: "" },
188
+ });
189
+
190
+ // Expose ref methods
191
+ useImperativeHandle(
192
+ ref,
193
+ () => ({
194
+ open: () => {
195
+ if (isInitialized) {
196
+ openWebView();
197
+ }
198
+ },
199
+ close: () => {
200
+ if (isInitialized) {
201
+ closeWebView();
202
+ }
203
+ },
204
+ toggle: () => {
205
+ if (isInitialized) {
206
+ if (isWebViewVisible) {
207
+ closeWebView();
208
+ } else {
209
+ openWebView();
210
+ }
211
+ }
212
+ },
213
+ isReady: () => isInitialized,
214
+ }),
215
+ [isInitialized, isWebViewVisible]
216
+ );
217
+
218
+ const triggerAppInit = () => {
219
+ if (webViewRef.current) {
220
+ webViewRef.current.injectJavaScript(`
221
+ // Trigger APP_READY equivalent
222
+ window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'APP_READY' }));
223
+ true;
224
+ `);
225
+ }
226
+ };
227
+
228
+ const executeStorageOp = useCallback(
229
+ (operation: () => void) => {
230
+ if (isStorageReady) {
231
+ operation();
232
+ } else {
233
+ pendingStorageOps.current.push(operation);
234
+ }
235
+ },
236
+ [isStorageReady]
237
+ );
238
+
239
+ // Handle WebView messages
240
+ const handleMessage = useCallback(
241
+ (event: WebViewMessageEvent) => {
242
+ const { data } = event.nativeEvent;
243
+
244
+ try {
245
+ const parsedData: ChatbotMessage = JSON.parse(data);
246
+
247
+ // Handle storage messages
248
+ if (
249
+ Object.values(StorageMessageType).includes(
250
+ parsedData.type as StorageMessageType
251
+ )
252
+ ) {
253
+ const storageMessage = parsedData as StorageMessage;
254
+ switch (storageMessage.type) {
255
+ case StorageMessageType.STORAGE_READY:
256
+ setIsStorageReady(true);
257
+ pendingStorageOps.current.forEach((op) => op());
258
+ pendingStorageOps.current = [];
259
+ // Try to get stored ID immediately after storage is ready
260
+ webViewRef.current?.injectJavaScript(
261
+ createStorageMessage(
262
+ StorageMessageType.GET_STORAGE,
263
+ "rblyn_anon"
264
+ )
265
+ );
266
+ break;
267
+ case StorageMessageType.STORAGE_RESPONSE:
268
+ if (storageMessage.key && storageMessage.value) {
269
+ memoryCache.current[storageMessage.key] =
270
+ storageMessage.value;
271
+ if (storageMessage.key === "rblyn_anon") {
272
+ setEffectiveUserId(storageMessage.value);
273
+ }
274
+ }
275
+ break;
276
+ case StorageMessageType.STORAGE_ERROR:
277
+ logger.error("WebView storage error:", storageMessage.error);
278
+ break;
279
+ }
280
+ return;
281
+ }
282
+
283
+ // Add debug logging
284
+ if (__DEV__) {
285
+ if (parsedData?.type === "CONSOLE") {
286
+ console.log(`WebView ${parsedData?.level}:`, parsedData?.data);
287
+ return;
288
+ }
289
+ if (parsedData?.type === "ERROR") {
290
+ console.error("WebView Error:", parsedData?.data);
291
+ return;
292
+ }
293
+ }
294
+
295
+ if (parsedData?.type === "SYSTEM_INFO") {
296
+ setSystemInfo(parsedData?.data ?? { os: "", browser: "" });
297
+ return;
298
+ }
299
+
300
+ if (parsedData?.type === "APP_READY") {
301
+ emitEvent(ChatbotEventType.CHATBOT_APP_READY);
302
+ if (webViewRef.current && isWebViewVisible) {
303
+ webViewRef.current?.injectJavaScript(getBrowserAndOSInfoScript());
304
+
305
+ const messageData = {
306
+ name: "registerUserId",
307
+ action: "registerUserId",
308
+ data: {
309
+ userId: effectiveUserId,
310
+ token: user_token ? `${user_token}` : undefined,
311
+ userProfile: {
312
+ ...user_profile,
313
+ ...getSystemInfo(systemInfo),
314
+ },
315
+ },
316
+ };
317
+ logger.debug("messageData====>", JSON.stringify(messageData));
318
+ webViewRef.current?.injectJavaScript(`
319
+ window.postMessage(${JSON.stringify({
320
+ name: "openFrame",
321
+ domain: "app-domain.com",
322
+ })}, '*');
323
+ window.postMessage(${JSON.stringify(messageData)}, '*');
324
+ `);
325
+ }
326
+ }
327
+
328
+ // Handle other message types
329
+ switch (parsedData?.type) {
330
+ case "close_chatbot":
331
+ closeWebView();
332
+ break;
333
+ case "CHATBOT_LOADED":
334
+ emitEvent(ChatbotEventType.CHATBOT_LOADED, parsedData?.data);
335
+ break;
336
+ case "CHAT_INITIALIZED":
337
+ emitEvent(ChatbotEventType.CHAT_INITIALIZED, parsedData?.data);
338
+ break;
339
+ case "SESSION_REFRESHED":
340
+ emitEvent(ChatbotEventType.SESSION_REFRESHED, parsedData?.data);
341
+ break;
342
+ case "CHAT_INITIALIZATION_FAILED":
343
+ emitEvent(
344
+ ChatbotEventType.CHAT_INITIALIZATION_FAILED,
345
+ parsedData?.data
346
+ );
347
+ break;
348
+ default:
349
+ if (onMessage) onMessage(parsedData ?? {});
350
+ }
351
+ } catch (error) {
352
+ errorTracker.trackError(
353
+ error instanceof Error
354
+ ? error
355
+ : new Error("Failed to parse WebView message"),
356
+ "ChatbotSDK",
357
+ {
358
+ type: ErrorTypes.RUNTIME_ERROR,
359
+ context: { messageData: data },
360
+ }
361
+ );
362
+ }
363
+ },
364
+ [
365
+ emitEvent,
366
+ isWebViewVisible,
367
+ onMessage,
368
+ systemInfo,
369
+ effectiveUserId,
370
+ user_token,
371
+ user_profile,
372
+ ]
373
+ );
374
+
375
+ // Initialize user ID
376
+ useEffect(() => {
377
+ const initializeUserId = async () => {
378
+ let userId = user_id ? String(user_id) : undefined;
379
+
380
+ if (!userId) {
381
+ // Try memory cache first
382
+ userId = memoryCache.current["rblyn_anon"];
383
+
384
+ if (!userId) {
385
+ userId = generateUUID();
386
+ executeStorageOp(() => {
387
+ webViewRef.current?.injectJavaScript(
388
+ createStorageMessage(
389
+ StorageMessageType.SET_STORAGE,
390
+ "rblyn_anon",
391
+ userId!
392
+ )
393
+ );
394
+ });
395
+ }
396
+ }
397
+
398
+ setEffectiveUserId(userId);
399
+ };
400
+
401
+ initializeUserId();
402
+ }, [user_id, executeStorageOp]);
403
+
404
+ // Handle external isOpen prop changes
405
+ useEffect(() => {
406
+ if (externalIsOpen !== undefined && isInitialized) {
407
+ if (externalIsOpen && !isWebViewVisible) {
408
+ openWebView();
409
+ } else if (!externalIsOpen && isWebViewVisible) {
410
+ closeWebView();
411
+ }
412
+ }
413
+ }, [externalIsOpen, isInitialized, isWebViewVisible]);
414
+
415
+ // Initialize WebView storage
416
+ useEffect(() => {
417
+ if (webViewRef.current) {
418
+ webViewRef.current.injectJavaScript(getStorageScript());
419
+ // Try to get stored ID once storage is ready
420
+ executeStorageOp(() => {
421
+ webViewRef.current?.injectJavaScript(
422
+ createStorageMessage(StorageMessageType.GET_STORAGE, "rblyn_anon")
423
+ );
424
+ });
425
+ }
426
+ }, []);
427
+
428
+ useEffect(() => {
429
+ const fetchChatbotConfig = async () => {
430
+ try {
431
+ const endpointUrl = `${API_URL}/chat/chatbot/get/`;
432
+ const payload = {
433
+ client_user_id: effectiveUserId,
434
+ org_id: api_key,
435
+ token: user_token,
436
+ extra_info: {},
437
+ };
438
+
439
+ // setToastMessage(endpointUrl + " : " + BASE_CHATBOT_URL);
440
+ // setToastVisible(true);
441
+
442
+ const response = await fetch(endpointUrl, {
443
+ method: "POST",
444
+ headers: {
445
+ "Content-Type": "application/json",
446
+ },
447
+ body: JSON.stringify(payload),
448
+ });
449
+
450
+ if (!response.ok) {
451
+ throw new Error("Failed to fetch chatbot configuration");
452
+ }
453
+
454
+ const data = await response.json();
455
+ const orgInfo = data?.user?.org_info;
456
+
457
+ if (orgInfo) {
458
+ const config: ChatbotConfig = {
459
+ brand_colour: orgInfo.brand_config?.colors?.brand_color || "",
460
+ image_url: orgInfo.brand_config?.launcher_logo_url || "",
461
+ chat_interface_config: {
462
+ chat_bubble_prompts: [],
463
+ display_name: orgInfo.brand_config?.display_name || "",
464
+ welcome_message:
465
+ orgInfo.brand_config?.welcome_message ||
466
+ "Hey! What can we help you with today?",
467
+ redirect_url: orgInfo.brand_config?.redirect_url || "",
468
+ },
469
+ interface_properties: {
470
+ position:
471
+ orgInfo.brand_config?.interface_properties?.position ||
472
+ WidgetPositionEnums.RIGHT,
473
+ side_spacing:
474
+ orgInfo.brand_config?.interface_properties?.side_spacing ??
475
+ 20,
476
+ bottom_spacing:
477
+ orgInfo.brand_config?.interface_properties?.bottom_spacing ??
478
+ 20,
479
+ },
480
+ interface_type:
481
+ orgInfo.brand_config?.interface_type ||
482
+ ChatbotInterfaceType.WIDGET,
483
+ launcher_type:
484
+ orgInfo.brand_config?.launcher_type || LauncherType.IMAGE,
485
+ launcher_properties: {
486
+ text: orgInfo.brand_config?.launcher_properties?.text || "",
487
+ },
488
+ images: {
489
+ launcher_image_url: {
490
+ url:
491
+ orgInfo.brand_config?.images?.launcher_image_url?.url || "",
492
+ },
493
+ },
494
+ };
495
+ setChatbotConfig(config);
496
+ }
497
+ } catch (error) {
498
+ errorTracker.trackError(
499
+ error instanceof Error
500
+ ? error
501
+ : new Error("Failed to fetch chatbot configuration"),
502
+ "ChatbotSDK",
503
+ {
504
+ type: ErrorTypes.NETWORK_ERROR,
505
+ context: { api_key, user_id },
506
+ }
507
+ );
508
+ } finally {
509
+ setLoading(false);
510
+ }
511
+ };
512
+
513
+ if (api_key && effectiveUserId) {
514
+ fetchChatbotConfig();
515
+ }
516
+ }, [api_key, effectiveUserId, user_token]);
517
+
518
+ function constructUrl(): string {
519
+ if (!effectiveUserId) return "";
520
+ const params = new URLSearchParams({
521
+ id: api_key,
522
+ });
523
+ console.log("BASE_CHATBOT_URL SDK_VERSION", BASE_CHATBOT_URL);
524
+ return `${BASE_CHATBOT_URL}?${params.toString()}`;
525
+ }
526
+
527
+ // Handle opening the chatbot
528
+ const openWebView = useCallback(() => {
529
+ setIsWebViewVisible(true);
530
+ if (enableAnimation) {
531
+ animateWebViewOpen(animatedValues, () => {
532
+ externalOnOpen?.();
533
+ emitEvent(ChatbotEventType.CHATBOT_OPENED);
534
+ if (!isFirstLoadRef.current) {
535
+ setTimeout(triggerAppInit, 100);
536
+ }
537
+ });
538
+ } else {
539
+ requestAnimationFrame(() => {
540
+ externalOnOpen?.();
541
+ emitEvent(ChatbotEventType.CHATBOT_OPENED);
542
+ if (!isFirstLoadRef.current) {
543
+ setTimeout(triggerAppInit, 100);
544
+ }
545
+ });
546
+ }
547
+ }, [enableAnimation, animatedValues, externalOnOpen, emitEvent]);
548
+
549
+ // Handle closing the chatbot
550
+ const closeWebView = useCallback(() => {
551
+ if (webViewRef.current) {
552
+ webViewRef.current.postMessage(
553
+ JSON.stringify({
554
+ name: "closeFrame",
555
+ domain: "app-domain.com",
556
+ })
557
+ );
558
+ }
559
+
560
+ if (enableAnimation) {
561
+ animateWebViewClose(animatedValues, () => {
562
+ setIsWebViewVisible(false);
563
+ externalOnClose?.();
564
+ emitEvent(ChatbotEventType.CHATBOT_CLOSED);
565
+ });
566
+ } else {
567
+ setIsWebViewVisible(false);
568
+ externalOnClose?.();
569
+ emitEvent(ChatbotEventType.CHATBOT_CLOSED);
570
+ }
571
+ }, [enableAnimation, animatedValues, externalOnClose, emitEvent]);
572
+
573
+ // Emit button loaded event when component mounts
574
+ useEffect(() => {
575
+ if (show_floating_button && chatbotConfig) {
576
+ emitEvent(ChatbotEventType.CHATBOT_BUTTON_LOADED);
577
+ }
578
+ }, [show_floating_button, chatbotConfig, emitEvent]);
579
+
580
+ // Set initialization state when chatbot is ready
581
+ useEffect(() => {
582
+ if (!loading && chatbotConfig && effectiveUserId && !isInitialized) {
583
+ setIsInitialized(true);
584
+ onReady?.();
585
+ }
586
+ }, [loading, chatbotConfig, effectiveUserId, isInitialized, onReady]);
587
+
588
+ const getScreenDimensions = () => {
589
+ const windowHeight = Dimensions.get("window").height;
590
+ const windowWidth = Dimensions.get("window").width;
591
+ const statusBarHeight =
592
+ Platform.OS === "ios" ? 0 : StatusBar.currentHeight || 0;
593
+
594
+ return {
595
+ height: windowHeight - statusBarHeight,
596
+ width: windowWidth,
597
+ };
598
+ };
599
+
600
+ const screenDimensions = getScreenDimensions();
601
+
602
+ useEffect(() => {
603
+ if (__DEV__) {
604
+ enableNetworkDebug();
605
+ }
606
+ }, []);
607
+
608
+ // Deliberate error for testing error boundary
609
+ // useEffect(() => {
610
+ // if (__DEV__) {
611
+ // // Set up global error handlers
612
+ // setupGlobalErrorHandlers();
613
+
614
+ // // Test async error
615
+ // setTimeout(() => {
616
+ // Promise.reject(new Error("Test async error"));
617
+ // }, 1000);
618
+
619
+ // // Test sync error
620
+ // setTimeout(() => {
621
+ // throw new Error("Test sync error");
622
+ // }, 2000);
623
+ // }
624
+ // }, []);
625
+
626
+ const position = chatbotConfig?.interface_properties?.position || "Right";
627
+ const sideSpacing = chatbotConfig?.interface_properties?.side_spacing ?? 20;
628
+ const bottomSpacing =
629
+ chatbotConfig?.interface_properties?.bottom_spacing ?? 20;
630
+
631
+ // const mainContainerStyle = useMemo(() => {
632
+ // return {
633
+ // ...styles.mainContainer,
634
+ // [position.toLowerCase()]: `${sideSpacing ?? 20}px`,
635
+ // alignItems:
636
+ // position === "Right" ? "flex-end" : ("flex-start" as FlexAlignType),
637
+ // // Explicitly clear mobile-specific styles
638
+ // top: undefined,
639
+ // [position === "Right" ? "left" : "right"]: undefined,
640
+ // backgroundColor: "green",
641
+ // };
642
+ // }, [position, sideSpacing, styles.mainContainer]);
643
+
644
+ return (
645
+ <ErrorBoundary componentName="ChatbotSDK">
646
+ <View style={styles?.mainContainer}>
647
+ {!loading && (
648
+ <>
649
+ <ErrorBoundary componentName="FloatingButton">
650
+ {show_floating_button && api_key && chatbotConfig && (
651
+ <FloatingButton
652
+ chatbotConfig={chatbotConfig}
653
+ isWebViewVisible={isWebViewVisible}
654
+ onPress={() => {
655
+ emitEvent(ChatbotEventType.CHATBOT_BUTTON_CLICKED);
656
+ openWebView();
657
+ }}
658
+ brandColor={chatbotConfig.brand_colour}
659
+ imageUrl={chatbotConfig.image_url}
660
+ />
661
+ )}
662
+ </ErrorBoundary>
663
+ <View
664
+ style={[
665
+ styles.webViewWrapper,
666
+ { height: isWebViewVisible ? "100%" : 0 },
667
+ ]}
668
+ pointerEvents={isWebViewVisible ? "auto" : "none"}
669
+ >
670
+ <Animated.View
671
+ style={[
672
+ styles.fullScreenContainer,
673
+ {
674
+ opacity: animatedValues.opacity,
675
+ transform: [
676
+ { scale: animatedValues.scale },
677
+ { translateY: animatedValues.translateY },
678
+ ],
679
+ },
680
+ ]}
681
+ >
682
+ <ErrorBoundary componentName="ChatbotWebView">
683
+ <WebView
684
+ ref={webViewRef}
685
+ source={{
686
+ uri: constructUrl(),
687
+ }}
688
+ onMessage={handleMessage}
689
+ style={styles.webview}
690
+ containerStyle={
691
+ isFullScreen
692
+ ? {
693
+ width: screenDimensions.width,
694
+ height: screenDimensions.height - 40,
695
+ }
696
+ : undefined
697
+ }
698
+ allowsInlineMediaPlayback={true}
699
+ mediaPlaybackRequiresUserAction={false}
700
+ allowFileAccess={false}
701
+ geolocationEnabled={false}
702
+ javaScriptEnabled={true}
703
+ domStorageEnabled={true}
704
+ cacheEnabled={true}
705
+ scrollEnabled={true}
706
+ bounces={false}
707
+ onShouldStartLoadWithRequest={(request) => {
708
+ return request.url.startsWith(BASE_CHATBOT_URL);
709
+ }}
710
+ startInLoadingState={isFirstLoadRef.current}
711
+ onLoadEnd={() => {
712
+ if (__DEV__) {
713
+ enableWebViewDebug(webViewRef);
714
+ }
715
+ webViewRef.current?.injectJavaScript(`
716
+ if (!document.querySelector('meta[name="viewport"]')) {
717
+ var meta = document.createElement('meta');
718
+ meta.name = 'viewport';
719
+ meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no';
720
+ document.getElementsByTagName('head')[0].appendChild(meta);
721
+ }
722
+
723
+ if (!document.querySelector('#injectedStyle')) {
724
+ const style = document.createElement('style');
725
+ style.id = 'injectedStyle';
726
+ style.textContent = \`
727
+ html, body, #root, #__next {
728
+ height: 100% !important;
729
+ min-height: 100% !important;
730
+ overflow: hidden !important;
731
+ }
732
+ \`;
733
+ document.head.appendChild(style);
734
+ }
735
+
736
+ window.sendToChatbot = function(message) {
737
+ window.ReactNativeWebView.postMessage(JSON.stringify(message));
738
+ };
739
+
740
+ // Only create observer if it doesn't exist
741
+ if (!window.closeButtonObserver) {
742
+ // Setup mutation observer to watch for close button
743
+ window.closeButtonObserver = new MutationObserver(function(mutations) {
744
+ const closeButton = document.querySelector('.chatbot-close-button');
745
+ if (closeButton && !closeButton.hasAttribute('listener-attached')) {
746
+ closeButton.setAttribute('listener-attached', 'true');
747
+ closeButton.addEventListener('click', () => {
748
+ try {
749
+ const message = { type: 'close_chatbot' };
750
+ window.ReactNativeWebView.postMessage(JSON.stringify(message));
751
+ } catch (error) {
752
+ console.error('Error sending close message:', error);
753
+ }
754
+ });
755
+ console.log('Close button listener attached');
756
+ }
757
+ });
758
+
759
+ // Start observing
760
+ window.closeButtonObserver.observe(document.body, {
761
+ childList: true,
762
+ subtree: true
763
+ });
764
+
765
+ // Initial check for existing button
766
+ const existingButton = document.querySelector('.chatbot-close-button');
767
+ if (existingButton && !existingButton.hasAttribute('listener-attached')) {
768
+ existingButton.setAttribute('listener-attached', 'true');
769
+ existingButton.addEventListener('click', () => {
770
+ try {
771
+ const message = { type: 'close_chatbot' };
772
+ window.ReactNativeWebView.postMessage(JSON.stringify(message));
773
+ } catch (error) {
774
+ console.error('Error sending close message:', error);
775
+ }
776
+ });
777
+ console.log('Close button listener attached to existing button');
778
+ }
779
+ }
780
+
781
+ true;
782
+ `);
783
+ isFirstLoadRef.current = false;
784
+ }}
785
+ onError={(syntheticEvent) => {
786
+ const { nativeEvent } = syntheticEvent;
787
+ errorTracker.trackError(
788
+ new Error(nativeEvent.description),
789
+ "ChatbotWebView",
790
+ {
791
+ type: ErrorTypes.RUNTIME_ERROR,
792
+ context: {
793
+ url: nativeEvent.url,
794
+ code: nativeEvent.code,
795
+ },
796
+ }
797
+ );
798
+ }}
799
+ />
800
+ </ErrorBoundary>
801
+ </Animated.View>
802
+ </View>
803
+ </>
804
+ )}
805
+ {/* <Toast message={toastMessage} visible={toastVisible} duration={10000} /> */}
806
+ {__DEV__ && <DebugButton />}
807
+ </View>
808
+ </ErrorBoundary>
809
+ );
810
+ }
811
+ );
812
+
813
+ Chatbot.displayName = "Chatbot";
814
+
815
+ const styles = StyleSheet.create({
816
+ mainContainer: {
817
+ flex: 1,
818
+ position: "relative",
819
+ overflow: "visible",
820
+ },
821
+ webViewWrapper: {
822
+ position: "absolute",
823
+ top: 0,
824
+ left: 0,
825
+ right: 0,
826
+ bottom: 0,
827
+ overflow: "visible",
828
+ zIndex: 10000,
829
+ backgroundColor: "#ffffff",
830
+ },
831
+ fullScreenContainer: {
832
+ position: "absolute",
833
+ top: 0,
834
+ left: 0,
835
+ right: 0,
836
+ bottom: 0,
837
+ backgroundColor: "white",
838
+ zIndex: 10000,
839
+ overflow: "visible",
840
+ // height: Platform.OS === "android" ? "100%" : "100%",
841
+ },
842
+ inlineContainer: {
843
+ flex: 1,
844
+ backgroundColor: "white",
845
+ },
846
+ webview: {
847
+ flex: 1,
848
+ backgroundColor: "white",
849
+ zIndex: 10000,
850
+ },
851
+ });
852
+
853
+ export default Chatbot;