@robylon/react-native-sdk 2.0.22-staging.1 → 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.
@@ -4,6 +4,8 @@ import React, {
4
4
  useRef,
5
5
  useMemo,
6
6
  useCallback,
7
+ useImperativeHandle,
8
+ forwardRef,
7
9
  } from "react";
8
10
  import {
9
11
  View,
@@ -52,6 +54,18 @@ export interface ChatbotProps {
52
54
  is_test_user?: boolean;
53
55
  };
54
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;
55
69
  }
56
70
 
57
71
  interface InternalChatbotProps extends ChatbotProps {
@@ -59,8 +73,6 @@ interface InternalChatbotProps extends ChatbotProps {
59
73
  onMessage?: (message: any) => void;
60
74
  isFullScreen?: boolean;
61
75
  enableAnimation?: boolean;
62
- onOpen?: () => void;
63
- onClose?: () => void;
64
76
  }
65
77
 
66
78
  interface ChatbotMessage {
@@ -114,537 +126,593 @@ export interface ChatbotConfig {
114
126
  };
115
127
  }
116
128
 
117
- const Chatbot: React.FC<InternalChatbotProps> = ({
118
- api_key,
119
- user_id,
120
- user_token,
121
- show_floating_button = true,
122
- onEvent,
123
- onMessage,
124
- isFullScreen = true,
125
- enableAnimation = true,
126
- onOpen,
127
- onClose,
128
- user_profile,
129
- }) => {
130
- const [isWebViewVisible, setIsWebViewVisible] = useState(false);
131
- const [chatbotConfig, setChatbotConfig] = useState<ChatbotConfig | null>(
132
- null
133
- );
134
- const [loading, setLoading] = useState(true);
135
- const webViewRef = useRef<WebView>(null);
136
- const isFirstLoadRef = useRef(true);
137
- const [toastVisible, setToastVisible] = useState(false);
138
- const [toastMessage, setToastMessage] = useState("");
139
- const [effectiveUserId, setEffectiveUserId] = useState<string>();
140
- const [isStorageReady, setIsStorageReady] = useState(false);
141
- const pendingStorageOps = useRef<Array<() => void>>([]);
142
- const memoryCache = useRef<Record<string, string>>({});
143
- const [systemInfo, setSystemInfo] = useState({ os: "", browser: "" });
144
-
145
- // Initialize animated values
146
- const animatedValues = useRef({
147
- scale: new Animated.Value(1),
148
- translateY: new Animated.Value(0),
149
- opacity: new Animated.Value(1),
150
- }).current;
151
-
152
- // Create chatbotConfig object for events
153
- const chatbotConfigForEvents = useMemo(
154
- () => ({
155
- userId: effectiveUserId,
156
- isAnonymous: !user_id && user_id !== 0,
157
- // Add other config properties as needed
158
- }),
159
- [effectiveUserId, user_id]
160
- );
161
-
162
- // Initialize event handling
163
- const { emitEvent, onInternalEvent } = useChatbotEvents({
164
- api_key,
165
- chatbotConfig: chatbotConfigForEvents ?? {},
166
- user_profile: user_profile,
167
- onEvent: onEvent,
168
- systemInfo: systemInfo ?? { os: "", browser: "" },
169
- });
170
-
171
- const triggerAppInit = () => {
172
- if (webViewRef.current) {
173
- webViewRef.current.injectJavaScript(`
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(`
174
221
  // Trigger APP_READY equivalent
175
222
  window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'APP_READY' }));
176
223
  true;
177
224
  `);
178
- }
179
- };
180
-
181
- const executeStorageOp = useCallback(
182
- (operation: () => void) => {
183
- if (isStorageReady) {
184
- operation();
185
- } else {
186
- pendingStorageOps.current.push(operation);
187
225
  }
188
- },
189
- [isStorageReady]
190
- );
191
-
192
- // Handle WebView messages
193
- const handleMessage = useCallback(
194
- (event: WebViewMessageEvent) => {
195
- const { data } = event.nativeEvent;
196
-
197
- try {
198
- const parsedData: ChatbotMessage = JSON.parse(data);
199
-
200
- // Handle storage messages
201
- if (
202
- Object.values(StorageMessageType).includes(
203
- parsedData.type as StorageMessageType
204
- )
205
- ) {
206
- const storageMessage = parsedData as StorageMessage;
207
- switch (storageMessage.type) {
208
- case StorageMessageType.STORAGE_READY:
209
- setIsStorageReady(true);
210
- pendingStorageOps.current.forEach((op) => op());
211
- pendingStorageOps.current = [];
212
- // Try to get stored ID immediately after storage is ready
213
- webViewRef.current?.injectJavaScript(
214
- createStorageMessage(
215
- StorageMessageType.GET_STORAGE,
216
- "rblyn_anon"
217
- )
218
- );
219
- break;
220
- case StorageMessageType.STORAGE_RESPONSE:
221
- if (storageMessage.key && storageMessage.value) {
222
- memoryCache.current[storageMessage.key] = storageMessage.value;
223
- if (storageMessage.key === "rblyn_anon") {
224
- setEffectiveUserId(storageMessage.value);
225
- }
226
- }
227
- break;
228
- case StorageMessageType.STORAGE_ERROR:
229
- logger.error("WebView storage error:", storageMessage.error);
230
- break;
231
- }
232
- return;
226
+ };
227
+
228
+ const executeStorageOp = useCallback(
229
+ (operation: () => void) => {
230
+ if (isStorageReady) {
231
+ operation();
232
+ } else {
233
+ pendingStorageOps.current.push(operation);
233
234
  }
235
+ },
236
+ [isStorageReady]
237
+ );
234
238
 
235
- // Add debug logging
236
- if (__DEV__) {
237
- if (parsedData?.type === "CONSOLE") {
238
- console.log(`WebView ${parsedData?.level}:`, parsedData?.data);
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
+ }
239
280
  return;
240
281
  }
241
- if (parsedData?.type === "ERROR") {
242
- console.error("WebView Error:", parsedData?.data);
243
- return;
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
+ }
244
293
  }
245
- }
246
294
 
247
- if (parsedData?.type === "SYSTEM_INFO") {
248
- setSystemInfo(parsedData?.data ?? { os: "", browser: "" });
249
- return;
250
- }
295
+ if (parsedData?.type === "SYSTEM_INFO") {
296
+ setSystemInfo(parsedData?.data ?? { os: "", browser: "" });
297
+ return;
298
+ }
251
299
 
252
- if (parsedData?.type === "APP_READY") {
253
- emitEvent(ChatbotEventType.CHATBOT_APP_READY);
254
- if (webViewRef.current && isWebViewVisible) {
255
- webViewRef.current?.injectJavaScript(getBrowserAndOSInfoScript());
256
-
257
- const messageData = {
258
- name: "registerUserId",
259
- action: "registerUserId",
260
- data: {
261
- userId: effectiveUserId,
262
- token: user_token ? `${user_token}` : undefined,
263
- userProfile: {
264
- ...user_profile,
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
+ },
265
315
  },
266
- },
267
- };
268
- logger.debug("messageData====>", JSON.stringify(messageData));
269
- webViewRef.current?.injectJavaScript(`
316
+ };
317
+ logger.debug("messageData====>", JSON.stringify(messageData));
318
+ webViewRef.current?.injectJavaScript(`
270
319
  window.postMessage(${JSON.stringify({
271
320
  name: "openFrame",
272
321
  domain: "app-domain.com",
273
322
  })}, '*');
274
323
  window.postMessage(${JSON.stringify(messageData)}, '*');
275
324
  `);
325
+ }
276
326
  }
277
- }
278
327
 
279
- // Handle other message types
280
- switch (parsedData?.type) {
281
- case "close_chatbot":
282
- closeWebView();
283
- break;
284
- case "CHATBOT_LOADED":
285
- emitEvent(ChatbotEventType.CHATBOT_LOADED, parsedData?.data);
286
- break;
287
- case "CHAT_INITIALIZED":
288
- emitEvent(ChatbotEventType.CHAT_INITIALIZED, parsedData?.data);
289
- break;
290
- case "SESSION_REFRESHED":
291
- emitEvent(ChatbotEventType.SESSION_REFRESHED, parsedData?.data);
292
- break;
293
- case "CHAT_INITIALIZATION_FAILED":
294
- emitEvent(
295
- ChatbotEventType.CHAT_INITIALIZATION_FAILED,
296
- parsedData?.data
297
- );
298
- break;
299
- default:
300
- if (onMessage) onMessage(parsedData ?? {});
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
+ );
301
362
  }
302
- } catch (error) {
303
- errorTracker.trackError(
304
- error instanceof Error
305
- ? error
306
- : new Error("Failed to parse WebView message"),
307
- "ChatbotSDK",
308
- {
309
- type: ErrorTypes.RUNTIME_ERROR,
310
- context: { messageData: data },
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
+ });
311
395
  }
312
- );
313
- }
314
- },
315
- [
316
- emitEvent,
317
- isWebViewVisible,
318
- onMessage,
319
- systemInfo,
320
- effectiveUserId,
321
- user_token,
322
- user_profile,
323
- ]
324
- );
396
+ }
325
397
 
326
- // Initialize user ID
327
- useEffect(() => {
328
- const initializeUserId = async () => {
329
- let userId = user_id ? String(user_id) : undefined;
398
+ setEffectiveUserId(userId);
399
+ };
330
400
 
331
- if (!userId) {
332
- // Try memory cache first
333
- userId = memoryCache.current["rblyn_anon"];
401
+ initializeUserId();
402
+ }, [user_id, executeStorageOp]);
334
403
 
335
- if (!userId) {
336
- userId = generateUUID();
337
- executeStorageOp(() => {
338
- webViewRef.current?.injectJavaScript(
339
- createStorageMessage(
340
- StorageMessageType.SET_STORAGE,
341
- "rblyn_anon",
342
- userId!
343
- )
344
- );
345
- });
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();
346
411
  }
347
412
  }
348
-
349
- setEffectiveUserId(userId);
350
- };
351
-
352
- initializeUserId();
353
- }, [user_id, executeStorageOp]);
354
-
355
- // Initialize WebView storage
356
- useEffect(() => {
357
- if (webViewRef.current) {
358
- webViewRef.current.injectJavaScript(getStorageScript());
359
- // Try to get stored ID once storage is ready
360
- executeStorageOp(() => {
361
- webViewRef.current?.injectJavaScript(
362
- createStorageMessage(StorageMessageType.GET_STORAGE, "rblyn_anon")
363
- );
364
- });
365
- }
366
- }, []);
367
-
368
- useEffect(() => {
369
- const fetchChatbotConfig = async () => {
370
- try {
371
- const endpointUrl = `${API_URL}/chat/chatbot/get/`;
372
- const payload = {
373
- client_user_id: effectiveUserId,
374
- org_id: api_key,
375
- token: user_token,
376
- extra_info: {},
377
- };
378
-
379
- // setToastMessage(endpointUrl + " : " + BASE_CHATBOT_URL);
380
- // setToastVisible(true);
381
-
382
- const response = await fetch(endpointUrl, {
383
- method: "POST",
384
- headers: {
385
- "Content-Type": "application/json",
386
- },
387
- body: JSON.stringify(payload),
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
+ );
388
424
  });
425
+ }
426
+ }, []);
389
427
 
390
- if (!response.ok) {
391
- throw new Error("Failed to fetch chatbot configuration");
392
- }
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
+ };
393
438
 
394
- const data = await response.json();
395
- const orgInfo = data?.user?.org_info;
396
-
397
- if (orgInfo) {
398
- const config: ChatbotConfig = {
399
- brand_colour: orgInfo.brand_config?.colors?.brand_color || "",
400
- image_url: orgInfo.brand_config?.launcher_logo_url || "",
401
- chat_interface_config: {
402
- chat_bubble_prompts: [],
403
- display_name: orgInfo.brand_config?.display_name || "",
404
- welcome_message:
405
- orgInfo.brand_config?.welcome_message ||
406
- "Hey! What can we help you with today?",
407
- redirect_url: orgInfo.brand_config?.redirect_url || "",
408
- },
409
- interface_properties: {
410
- position:
411
- orgInfo.brand_config?.interface_properties?.position ||
412
- WidgetPositionEnums.RIGHT,
413
- side_spacing:
414
- orgInfo.brand_config?.interface_properties?.side_spacing ?? 20,
415
- bottom_spacing:
416
- orgInfo.brand_config?.interface_properties?.bottom_spacing ??
417
- 20,
418
- },
419
- interface_type:
420
- orgInfo.brand_config?.interface_type ||
421
- ChatbotInterfaceType.WIDGET,
422
- launcher_type:
423
- orgInfo.brand_config?.launcher_type || LauncherType.IMAGE,
424
- launcher_properties: {
425
- text: orgInfo.brand_config?.launcher_properties?.text || "",
426
- },
427
- images: {
428
- launcher_image_url: {
429
- url:
430
- orgInfo.brand_config?.images?.launcher_image_url?.url || "",
431
- },
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",
432
446
  },
433
- };
434
- setChatbotConfig(config);
435
- }
436
- } catch (error) {
437
- errorTracker.trackError(
438
- error instanceof Error
439
- ? error
440
- : new Error("Failed to fetch chatbot configuration"),
441
- "ChatbotSDK",
442
- {
443
- type: ErrorTypes.NETWORK_ERROR,
444
- context: { api_key, user_id },
447
+ body: JSON.stringify(payload),
448
+ });
449
+
450
+ if (!response.ok) {
451
+ throw new Error("Failed to fetch chatbot configuration");
445
452
  }
446
- );
447
- } finally {
448
- setLoading(false);
449
- }
450
- };
451
453
 
452
- if (api_key && effectiveUserId) {
453
- fetchChatbotConfig();
454
- }
455
- }, [api_key, effectiveUserId, user_token]);
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
+ };
456
512
 
457
- function constructUrl(): string {
458
- if (!effectiveUserId) return "";
459
- const params = new URLSearchParams({
460
- id: api_key,
461
- });
462
- console.log("BASE_CHATBOT_URL SDK_VERSION", BASE_CHATBOT_URL);
463
- return `${BASE_CHATBOT_URL}?${params.toString()}`;
464
- }
513
+ if (api_key && effectiveUserId) {
514
+ fetchChatbotConfig();
515
+ }
516
+ }, [api_key, effectiveUserId, user_token]);
465
517
 
466
- // Handle opening the chatbot
467
- const openWebView = useCallback(() => {
468
- setIsWebViewVisible(true);
469
- if (enableAnimation) {
470
- animateWebViewOpen(animatedValues, () => {
471
- onOpen?.();
472
- emitEvent(ChatbotEventType.CHATBOT_OPENED);
473
- if (!isFirstLoadRef.current) {
474
- setTimeout(triggerAppInit, 100);
475
- }
476
- });
477
- } else {
478
- requestAnimationFrame(() => {
479
- onOpen?.();
480
- emitEvent(ChatbotEventType.CHATBOT_OPENED);
481
- if (!isFirstLoadRef.current) {
482
- setTimeout(triggerAppInit, 100);
483
- }
518
+ function constructUrl(): string {
519
+ if (!effectiveUserId) return "";
520
+ const params = new URLSearchParams({
521
+ id: api_key,
484
522
  });
523
+ console.log("BASE_CHATBOT_URL SDK_VERSION", BASE_CHATBOT_URL);
524
+ return `${BASE_CHATBOT_URL}?${params.toString()}`;
485
525
  }
486
- }, [enableAnimation, animatedValues, onOpen, emitEvent]);
487
-
488
- // NOt Needed
489
- // Handle closing the chatbot
490
- const closeWebView = useCallback(() => {
491
- if (webViewRef.current) {
492
- webViewRef.current.postMessage(
493
- JSON.stringify({
494
- name: "closeFrame",
495
- domain: "app-domain.com",
496
- })
497
- );
498
- }
499
526
 
500
- if (enableAnimation) {
501
- animateWebViewClose(animatedValues, () => {
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 {
502
567
  setIsWebViewVisible(false);
503
- onClose?.();
568
+ externalOnClose?.();
504
569
  emitEvent(ChatbotEventType.CHATBOT_CLOSED);
505
- });
506
- } else {
507
- setIsWebViewVisible(false);
508
- onClose?.();
509
- emitEvent(ChatbotEventType.CHATBOT_CLOSED);
510
- }
511
- }, [enableAnimation, animatedValues, onClose, emitEvent]);
512
-
513
- // Emit button loaded event when component mounts
514
- useEffect(() => {
515
- if (show_floating_button && chatbotConfig) {
516
- emitEvent(ChatbotEventType.CHATBOT_BUTTON_LOADED);
517
- }
518
- }, [show_floating_button, chatbotConfig, emitEvent]);
570
+ }
571
+ }, [enableAnimation, animatedValues, externalOnClose, emitEvent]);
519
572
 
520
- const getScreenDimensions = () => {
521
- const windowHeight = Dimensions.get("window").height;
522
- const windowWidth = Dimensions.get("window").width;
523
- const statusBarHeight =
524
- Platform.OS === "ios" ? 0 : StatusBar.currentHeight || 0;
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]);
525
579
 
526
- return {
527
- height: windowHeight - statusBarHeight,
528
- width: windowWidth,
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
+ };
529
598
  };
530
- };
531
599
 
532
- const screenDimensions = getScreenDimensions();
600
+ const screenDimensions = getScreenDimensions();
533
601
 
534
- useEffect(() => {
535
- if (__DEV__) {
536
- enableNetworkDebug();
537
- }
538
- }, []);
539
-
540
- // Deliberate error for testing error boundary
541
- // useEffect(() => {
542
- // if (__DEV__) {
543
- // // Set up global error handlers
544
- // setupGlobalErrorHandlers();
545
-
546
- // // Test async error
547
- // setTimeout(() => {
548
- // Promise.reject(new Error("Test async error"));
549
- // }, 1000);
550
-
551
- // // Test sync error
552
- // setTimeout(() => {
553
- // throw new Error("Test sync error");
554
- // }, 2000);
555
- // }
556
- // }, []);
557
-
558
- const position = chatbotConfig?.interface_properties?.position || "Right";
559
- const sideSpacing = chatbotConfig?.interface_properties?.side_spacing ?? 20;
560
- const bottomSpacing =
561
- chatbotConfig?.interface_properties?.bottom_spacing ?? 20;
562
-
563
- // const mainContainerStyle = useMemo(() => {
564
- // return {
565
- // ...styles.mainContainer,
566
- // [position.toLowerCase()]: `${sideSpacing ?? 20}px`,
567
- // alignItems:
568
- // position === "Right" ? "flex-end" : ("flex-start" as FlexAlignType),
569
- // // Explicitly clear mobile-specific styles
570
- // top: undefined,
571
- // [position === "Right" ? "left" : "right"]: undefined,
572
- // backgroundColor: "green",
573
- // };
574
- // }, [position, sideSpacing, styles.mainContainer]);
575
-
576
- return (
577
- <ErrorBoundary componentName="ChatbotSDK">
578
- <View style={styles?.mainContainer}>
579
- {!loading && (
580
- <>
581
- <ErrorBoundary componentName="FloatingButton">
582
- {show_floating_button && api_key && chatbotConfig && (
583
- <FloatingButton
584
- chatbotConfig={chatbotConfig}
585
- isWebViewVisible={isWebViewVisible}
586
- onPress={() => {
587
- emitEvent(ChatbotEventType.CHATBOT_BUTTON_CLICKED);
588
- openWebView();
589
- }}
590
- brandColor={chatbotConfig.brand_colour}
591
- imageUrl={chatbotConfig.image_url}
592
- />
593
- )}
594
- </ErrorBoundary>
595
- <View
596
- style={[
597
- styles.webViewWrapper,
598
- { height: isWebViewVisible ? "100%" : 0 },
599
- ]}
600
- pointerEvents={isWebViewVisible ? "auto" : "none"}
601
- >
602
- <Animated.View
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
603
664
  style={[
604
- styles.fullScreenContainer,
605
- {
606
- opacity: animatedValues.opacity,
607
- transform: [
608
- { scale: animatedValues.scale },
609
- { translateY: animatedValues.translateY },
610
- ],
611
- },
665
+ styles.webViewWrapper,
666
+ { height: isWebViewVisible ? "100%" : 0 },
612
667
  ]}
668
+ pointerEvents={isWebViewVisible ? "auto" : "none"}
613
669
  >
614
- <ErrorBoundary componentName="ChatbotWebView">
615
- <WebView
616
- ref={webViewRef}
617
- source={{
618
- uri: constructUrl(),
619
- }}
620
- onMessage={handleMessage}
621
- style={styles.webview}
622
- containerStyle={
623
- isFullScreen
624
- ? {
625
- width: screenDimensions.width,
626
- height: screenDimensions.height - 40,
627
- }
628
- : undefined
629
- }
630
- allowsInlineMediaPlayback={true}
631
- mediaPlaybackRequiresUserAction={false}
632
- allowFileAccess={false}
633
- geolocationEnabled={false}
634
- javaScriptEnabled={true}
635
- domStorageEnabled={true}
636
- cacheEnabled={true}
637
- scrollEnabled={true}
638
- bounces={false}
639
- onShouldStartLoadWithRequest={(request) => {
640
- return request.url.startsWith(BASE_CHATBOT_URL);
641
- }}
642
- startInLoadingState={isFirstLoadRef.current}
643
- onLoadEnd={() => {
644
- if (__DEV__) {
645
- enableWebViewDebug(webViewRef);
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
646
697
  }
647
- webViewRef.current?.injectJavaScript(`
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(`
648
716
  if (!document.querySelector('meta[name="viewport"]')) {
649
717
  var meta = document.createElement('meta');
650
718
  meta.name = 'viewport';
@@ -712,34 +780,37 @@ const Chatbot: React.FC<InternalChatbotProps> = ({
712
780
 
713
781
  true;
714
782
  `);
715
- isFirstLoadRef.current = false;
716
- }}
717
- onError={(syntheticEvent) => {
718
- const { nativeEvent } = syntheticEvent;
719
- errorTracker.trackError(
720
- new Error(nativeEvent.description),
721
- "ChatbotWebView",
722
- {
723
- type: ErrorTypes.RUNTIME_ERROR,
724
- context: {
725
- url: nativeEvent.url,
726
- code: nativeEvent.code,
727
- },
728
- }
729
- );
730
- }}
731
- />
732
- </ErrorBoundary>
733
- </Animated.View>
734
- </View>
735
- </>
736
- )}
737
- {/* <Toast message={toastMessage} visible={toastVisible} duration={10000} /> */}
738
- {__DEV__ && <DebugButton />}
739
- </View>
740
- </ErrorBoundary>
741
- );
742
- };
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";
743
814
 
744
815
  const styles = StyleSheet.create({
745
816
  mainContainer: {