@robylon/react-native-sdk 2.0.28 → 2.0.29-staging.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/lib/commonjs/Chatbotsdk.js +1 -1
- package/lib/commonjs/Chatbotsdk.js.map +1 -1
- package/lib/commonjs/config.js +1 -1
- package/lib/commonjs/config.js.map +1 -1
- package/lib/commonjs/constants.js +1 -1
- package/lib/commonjs/constants.js.map +1 -1
- package/lib/commonjs/versions/version.staging.js +1 -1
- package/lib/module/Chatbotsdk.js +1 -1
- package/lib/module/Chatbotsdk.js.map +1 -1
- package/lib/module/config.js +1 -1
- package/lib/module/config.js.map +1 -1
- package/lib/module/constants.js +1 -1
- package/lib/module/constants.js.map +1 -1
- package/lib/module/openChatbot.js +2 -1
- package/lib/module/openChatbot.js.map +1 -1
- package/lib/module/versions/version.staging.js +1 -1
- package/lib/typescript/versions/version.staging.d.ts +1 -1
- package/package.json +1 -1
- package/src/Chatbotsdk.tsx +877 -0
- package/src/FloatingButton/launchers/ImageLauncher.tsx +75 -0
- package/src/FloatingButton/launchers/TextLauncher.tsx +84 -0
- package/src/FloatingButton/launchers/TextualImageLauncher.tsx +133 -0
- package/src/FloatingButton.tsx +97 -0
- package/src/LoadingIndicator.tsx +11 -0
- package/src/Toast.tsx +65 -0
- package/src/components/DebugButton.tsx +46 -0
- package/src/components/ErrorBoundary.tsx +38 -0
- package/src/config.ts +4 -0
- package/src/constants/errorConstants.ts +21 -0
- package/src/constants/fontStyles.ts +3 -0
- package/src/constants.ts +5 -0
- package/src/global.d.ts +11 -0
- package/src/hooks/useChatbotEvents.ts +93 -0
- package/src/index.tsx +10 -0
- package/src/openChatbot.ts +11 -0
- package/src/openChatbot.tsx +0 -0
- package/src/services/ErrorTrackingService.ts +217 -0
- package/src/types/events.ts +25 -0
- package/src/types/react-native-flipper-performance-plugin.d.ts +3 -0
- package/src/types/react-native-globals.d.ts +10 -0
- package/src/utils/animations.ts +120 -0
- package/src/utils/colorUtils.ts +35 -0
- package/src/utils/cookieUtils.ts +7 -0
- package/src/utils/debugConfig.ts +56 -0
- package/src/utils/debugMenu.ts +14 -0
- package/src/utils/errorHandler.ts +58 -0
- package/src/utils/fileDownload.ts +70 -0
- package/src/utils/logger.ts +14 -0
- package/src/utils/systemInfo.ts +110 -0
- package/src/utils/webViewStorage.ts +62 -0
- package/src/version.ts +2 -0
- package/src/versions/version.dev.ts +2 -0
- package/src/versions/version.production.ts +2 -0
- package/src/versions/version.staging.ts +2 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Image, StyleSheet, TouchableOpacity, View } from "react-native";
|
|
3
|
+
import { ChatbotConfig } from "../../Chatbotsdk";
|
|
4
|
+
import { errorTracker } from "../../services/ErrorTrackingService";
|
|
5
|
+
import { ErrorTypes } from "../../constants/errorConstants";
|
|
6
|
+
|
|
7
|
+
interface ImageLauncherProps {
|
|
8
|
+
chatbotConfig: ChatbotConfig;
|
|
9
|
+
onPress: () => void;
|
|
10
|
+
isWebViewVisible: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const ImageLauncher: React.FC<ImageLauncherProps> = ({
|
|
14
|
+
chatbotConfig,
|
|
15
|
+
onPress,
|
|
16
|
+
isWebViewVisible,
|
|
17
|
+
}) => {
|
|
18
|
+
const handleImageError = () => {
|
|
19
|
+
errorTracker.trackError(
|
|
20
|
+
new Error("Failed to load launcher image"),
|
|
21
|
+
"ImageLauncher",
|
|
22
|
+
{
|
|
23
|
+
type: ErrorTypes.RUNTIME_ERROR,
|
|
24
|
+
context: { imageUrl: chatbotConfig?.images?.launcher_image_url?.url },
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<TouchableOpacity
|
|
31
|
+
style={[
|
|
32
|
+
styles.button,
|
|
33
|
+
{
|
|
34
|
+
backgroundColor: isWebViewVisible
|
|
35
|
+
? "transparent"
|
|
36
|
+
: chatbotConfig.brand_colour,
|
|
37
|
+
},
|
|
38
|
+
isWebViewVisible && styles.disabledButton,
|
|
39
|
+
]}
|
|
40
|
+
onPress={isWebViewVisible ? undefined : onPress}
|
|
41
|
+
activeOpacity={isWebViewVisible ? 1 : 0.7}
|
|
42
|
+
>
|
|
43
|
+
<Image
|
|
44
|
+
source={{ uri: chatbotConfig?.images?.launcher_image_url?.url }}
|
|
45
|
+
style={styles.buttonImage}
|
|
46
|
+
onError={handleImageError}
|
|
47
|
+
/>
|
|
48
|
+
</TouchableOpacity>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const styles = StyleSheet.create({
|
|
53
|
+
button: {
|
|
54
|
+
borderRadius: 24,
|
|
55
|
+
width: 48,
|
|
56
|
+
height: 48,
|
|
57
|
+
justifyContent: "center",
|
|
58
|
+
alignItems: "center",
|
|
59
|
+
elevation: 5,
|
|
60
|
+
shadowColor: "#000",
|
|
61
|
+
shadowOffset: { width: 0, height: 2 },
|
|
62
|
+
shadowOpacity: 0.25,
|
|
63
|
+
shadowRadius: 3.84,
|
|
64
|
+
},
|
|
65
|
+
buttonImage: {
|
|
66
|
+
width: 48,
|
|
67
|
+
height: 48,
|
|
68
|
+
borderRadius: 24,
|
|
69
|
+
},
|
|
70
|
+
disabledButton: {
|
|
71
|
+
backgroundColor: "rgba(0, 0, 0, 0)",
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
export default ImageLauncher;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { StyleSheet, Text, TouchableOpacity, View } from "react-native";
|
|
3
|
+
import { ChatbotConfig } from "../../Chatbotsdk";
|
|
4
|
+
import { getBestFontColor } from "../../utils/colorUtils";
|
|
5
|
+
|
|
6
|
+
interface TextLauncherProps {
|
|
7
|
+
chatbotConfig: ChatbotConfig;
|
|
8
|
+
onPress: () => void;
|
|
9
|
+
isWebViewVisible: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const TextLauncher: React.FC<TextLauncherProps> = ({
|
|
13
|
+
chatbotConfig,
|
|
14
|
+
onPress,
|
|
15
|
+
isWebViewVisible,
|
|
16
|
+
}) => {
|
|
17
|
+
console.log("TEXT_LAUNCHER", {
|
|
18
|
+
brand_colour: chatbotConfig?.brand_colour,
|
|
19
|
+
isWebViewVisible,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const containerStyle = {
|
|
23
|
+
...styles.container,
|
|
24
|
+
backgroundColor: isWebViewVisible
|
|
25
|
+
? "transparent"
|
|
26
|
+
: chatbotConfig?.brand_colour || "#007AFF",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const buttonTextStyle = {
|
|
30
|
+
...styles.buttonText,
|
|
31
|
+
color: getBestFontColor(chatbotConfig?.brand_colour || "#ffffff"),
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<View style={[containerStyle, isWebViewVisible && styles.disabledButton]}>
|
|
36
|
+
<TouchableOpacity
|
|
37
|
+
style={[styles.button]}
|
|
38
|
+
onPress={isWebViewVisible ? undefined : onPress}
|
|
39
|
+
activeOpacity={isWebViewVisible ? 1 : 0.7}
|
|
40
|
+
>
|
|
41
|
+
<View style={styles.textContainer}>
|
|
42
|
+
<Text style={buttonTextStyle} numberOfLines={1}>
|
|
43
|
+
{chatbotConfig?.launcher_properties?.text || "Chat"}
|
|
44
|
+
</Text>
|
|
45
|
+
</View>
|
|
46
|
+
</TouchableOpacity>
|
|
47
|
+
</View>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const styles = StyleSheet.create({
|
|
52
|
+
container: {
|
|
53
|
+
maxWidth: 160,
|
|
54
|
+
minWidth: 120,
|
|
55
|
+
height: 40,
|
|
56
|
+
borderRadius: 9999,
|
|
57
|
+
shadowColor: "#000",
|
|
58
|
+
shadowOffset: { width: 0, height: 0 },
|
|
59
|
+
shadowOpacity: 0.24,
|
|
60
|
+
shadowRadius: 16,
|
|
61
|
+
elevation: 5,
|
|
62
|
+
},
|
|
63
|
+
button: {
|
|
64
|
+
flex: 1,
|
|
65
|
+
paddingHorizontal: 20,
|
|
66
|
+
paddingVertical: 9,
|
|
67
|
+
justifyContent: "center",
|
|
68
|
+
alignItems: "center",
|
|
69
|
+
},
|
|
70
|
+
textContainer: {
|
|
71
|
+
flex: 1,
|
|
72
|
+
minWidth: 0,
|
|
73
|
+
},
|
|
74
|
+
buttonText: {
|
|
75
|
+
fontSize: 14,
|
|
76
|
+
fontWeight: "600",
|
|
77
|
+
textAlign: "center",
|
|
78
|
+
},
|
|
79
|
+
disabledButton: {
|
|
80
|
+
backgroundColor: "rgba(0, 0, 0, 0)",
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
export default TextLauncher;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { StyleSheet, Text, TouchableOpacity, View, Image } from "react-native";
|
|
3
|
+
import { ChatbotConfig } from "../../Chatbotsdk";
|
|
4
|
+
import { errorTracker } from "../../services/ErrorTrackingService";
|
|
5
|
+
import { ErrorTypes } from "../../constants/errorConstants";
|
|
6
|
+
import { getBestFontColor } from "../../utils/colorUtils";
|
|
7
|
+
import { FONT_FAMILY, FONT_LINE_HEIGHT } from "../../constants/fontStyles";
|
|
8
|
+
|
|
9
|
+
interface TextualImageLauncherProps {
|
|
10
|
+
chatbotConfig: ChatbotConfig;
|
|
11
|
+
onPress: () => void;
|
|
12
|
+
isWebViewVisible: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const TextualImageLauncher: React.FC<TextualImageLauncherProps> = ({
|
|
16
|
+
chatbotConfig,
|
|
17
|
+
onPress,
|
|
18
|
+
isWebViewVisible,
|
|
19
|
+
}) => {
|
|
20
|
+
console.log("TEXTUALIMAGE_LAUNCHER_DEBUG", {
|
|
21
|
+
brand_colour: chatbotConfig?.brand_colour,
|
|
22
|
+
text: chatbotConfig?.launcher_properties?.text,
|
|
23
|
+
isWebViewVisible,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const containerStyle = {
|
|
27
|
+
...styles.container,
|
|
28
|
+
backgroundColor: isWebViewVisible
|
|
29
|
+
? "transparent"
|
|
30
|
+
: chatbotConfig?.brand_colour || "#007AFF",
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const handleImageError = () => {
|
|
34
|
+
errorTracker.trackError(
|
|
35
|
+
new Error("Failed to load launcher image"),
|
|
36
|
+
"TextualImageLauncher",
|
|
37
|
+
{
|
|
38
|
+
type: ErrorTypes.RUNTIME_ERROR,
|
|
39
|
+
context: { imageUrl: chatbotConfig?.images?.launcher_image_url?.url },
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const buttonTextStyle = {
|
|
45
|
+
...styles.buttonText,
|
|
46
|
+
color: getBestFontColor(chatbotConfig?.brand_colour || "#ffffff"),
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
console.log("TEXTUALIMAGE_LAUNCHER_TEXT_STYLE", {
|
|
50
|
+
brand_colour: chatbotConfig?.brand_colour,
|
|
51
|
+
text_color: buttonTextStyle.color,
|
|
52
|
+
text: chatbotConfig?.launcher_properties?.text,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<View style={[containerStyle, isWebViewVisible && styles.disabledButton]}>
|
|
57
|
+
<TouchableOpacity
|
|
58
|
+
style={styles.button}
|
|
59
|
+
onPress={isWebViewVisible ? undefined : onPress}
|
|
60
|
+
activeOpacity={isWebViewVisible ? 1 : 0.7}
|
|
61
|
+
>
|
|
62
|
+
<View style={styles.contentContainer}>
|
|
63
|
+
<View style={styles.textContainer}>
|
|
64
|
+
<Text
|
|
65
|
+
style={buttonTextStyle}
|
|
66
|
+
numberOfLines={1}
|
|
67
|
+
ellipsizeMode="tail"
|
|
68
|
+
>
|
|
69
|
+
{chatbotConfig?.launcher_properties?.text || "Chat"}
|
|
70
|
+
</Text>
|
|
71
|
+
</View>
|
|
72
|
+
<View style={styles.imageContainer}>
|
|
73
|
+
<Image
|
|
74
|
+
source={{ uri: chatbotConfig?.images?.launcher_image_url?.url }}
|
|
75
|
+
style={styles.buttonImage}
|
|
76
|
+
onError={handleImageError}
|
|
77
|
+
/>
|
|
78
|
+
</View>
|
|
79
|
+
</View>
|
|
80
|
+
</TouchableOpacity>
|
|
81
|
+
</View>
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const styles = StyleSheet.create({
|
|
86
|
+
container: {
|
|
87
|
+
minWidth: 140,
|
|
88
|
+
maxWidth: 208,
|
|
89
|
+
height: 40,
|
|
90
|
+
borderRadius: 9999,
|
|
91
|
+
shadowColor: "#000",
|
|
92
|
+
shadowOffset: { width: 0, height: 0 },
|
|
93
|
+
shadowOpacity: 0.24,
|
|
94
|
+
shadowRadius: 16,
|
|
95
|
+
elevation: 5,
|
|
96
|
+
},
|
|
97
|
+
button: {
|
|
98
|
+
flex: 1,
|
|
99
|
+
height: "100%",
|
|
100
|
+
},
|
|
101
|
+
contentContainer: {
|
|
102
|
+
flex: 1,
|
|
103
|
+
flexDirection: "row",
|
|
104
|
+
alignItems: "center",
|
|
105
|
+
paddingLeft: 20,
|
|
106
|
+
height: "100%",
|
|
107
|
+
},
|
|
108
|
+
textContainer: {
|
|
109
|
+
flexShrink: 1,
|
|
110
|
+
flexGrow: 0,
|
|
111
|
+
marginRight: 8,
|
|
112
|
+
},
|
|
113
|
+
buttonText: {
|
|
114
|
+
fontSize: 14,
|
|
115
|
+
fontWeight: "600",
|
|
116
|
+
},
|
|
117
|
+
imageContainer: {
|
|
118
|
+
width: 40,
|
|
119
|
+
height: 40,
|
|
120
|
+
borderRadius: 9999,
|
|
121
|
+
overflow: "hidden",
|
|
122
|
+
},
|
|
123
|
+
buttonImage: {
|
|
124
|
+
width: "100%",
|
|
125
|
+
height: "100%",
|
|
126
|
+
resizeMode: "cover",
|
|
127
|
+
},
|
|
128
|
+
disabledButton: {
|
|
129
|
+
backgroundColor: "rgba(0, 0, 0, 0)",
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
export default TextualImageLauncher;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import React, { useMemo, useEffect, useRef } from "react";
|
|
2
|
+
import { View, StyleSheet, FlexAlignType, Animated } from "react-native";
|
|
3
|
+
import { ChatbotConfig, LauncherType } from "./Chatbotsdk";
|
|
4
|
+
import ImageLauncher from "./FloatingButton/launchers/ImageLauncher";
|
|
5
|
+
import TextLauncher from "./FloatingButton/launchers/TextLauncher";
|
|
6
|
+
import TextualImageLauncher from "./FloatingButton/launchers/TextualImageLauncher";
|
|
7
|
+
import { animateButtonScale } from "./utils/animations";
|
|
8
|
+
|
|
9
|
+
interface FloatingButtonProps {
|
|
10
|
+
onPress: () => void;
|
|
11
|
+
brandColor?: string;
|
|
12
|
+
imageUrl?: string;
|
|
13
|
+
isWebViewVisible: boolean;
|
|
14
|
+
chatbotConfig: ChatbotConfig;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const FloatingButton: React.FC<FloatingButtonProps> = ({
|
|
18
|
+
onPress,
|
|
19
|
+
isWebViewVisible,
|
|
20
|
+
chatbotConfig,
|
|
21
|
+
}) => {
|
|
22
|
+
const buttonScale = useRef(new Animated.Value(1)).current;
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
animateButtonScale(buttonScale, isWebViewVisible);
|
|
26
|
+
}, [isWebViewVisible, buttonScale]);
|
|
27
|
+
|
|
28
|
+
console.log("FLOATING_BUTTON_CONFIG", {
|
|
29
|
+
config: chatbotConfig,
|
|
30
|
+
brand_colour: chatbotConfig?.brand_colour,
|
|
31
|
+
launcher_type: chatbotConfig?.launcher_type,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const position = chatbotConfig?.interface_properties?.position || "Right";
|
|
35
|
+
const sideSpacing = chatbotConfig?.interface_properties?.side_spacing ?? 20;
|
|
36
|
+
const bottomSpacing =
|
|
37
|
+
chatbotConfig?.interface_properties?.bottom_spacing ?? 20;
|
|
38
|
+
|
|
39
|
+
const buttonContainerStyle = useMemo(() => {
|
|
40
|
+
return {
|
|
41
|
+
...styles.container,
|
|
42
|
+
[position.toLowerCase()]: sideSpacing ?? 20,
|
|
43
|
+
alignItems:
|
|
44
|
+
position === "Right" ? "flex-end" : ("flex-start" as FlexAlignType),
|
|
45
|
+
bottom: bottomSpacing ?? 20,
|
|
46
|
+
transform: [{ scale: buttonScale }],
|
|
47
|
+
};
|
|
48
|
+
}, [position, sideSpacing, bottomSpacing, buttonScale]);
|
|
49
|
+
|
|
50
|
+
const renderLauncher = () => {
|
|
51
|
+
switch (chatbotConfig?.launcher_type) {
|
|
52
|
+
case LauncherType.TEXT:
|
|
53
|
+
return (
|
|
54
|
+
<TextLauncher
|
|
55
|
+
chatbotConfig={chatbotConfig}
|
|
56
|
+
onPress={onPress}
|
|
57
|
+
isWebViewVisible={isWebViewVisible}
|
|
58
|
+
/>
|
|
59
|
+
);
|
|
60
|
+
case LauncherType.TEXTUAL_IMAGE:
|
|
61
|
+
return (
|
|
62
|
+
<TextualImageLauncher
|
|
63
|
+
chatbotConfig={chatbotConfig}
|
|
64
|
+
onPress={onPress}
|
|
65
|
+
isWebViewVisible={isWebViewVisible}
|
|
66
|
+
/>
|
|
67
|
+
);
|
|
68
|
+
case LauncherType.IMAGE:
|
|
69
|
+
default:
|
|
70
|
+
return (
|
|
71
|
+
<ImageLauncher
|
|
72
|
+
chatbotConfig={chatbotConfig}
|
|
73
|
+
onPress={onPress}
|
|
74
|
+
isWebViewVisible={isWebViewVisible}
|
|
75
|
+
/>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<Animated.View
|
|
82
|
+
style={buttonContainerStyle}
|
|
83
|
+
pointerEvents={isWebViewVisible ? "none" : "auto"}
|
|
84
|
+
>
|
|
85
|
+
{renderLauncher()}
|
|
86
|
+
</Animated.View>
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const styles = StyleSheet.create({
|
|
91
|
+
container: {
|
|
92
|
+
position: "absolute",
|
|
93
|
+
zIndex: 998,
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
export default FloatingButton;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
|
2
|
+
import { View, ActivityIndicator } from "react-native";
|
|
3
|
+
import { WebView } from "react-native-webview";
|
|
4
|
+
|
|
5
|
+
const LoadingIndicator: React.FC = () => (
|
|
6
|
+
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
|
|
7
|
+
<ActivityIndicator size="large" color="#0000ff" />
|
|
8
|
+
</View>
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
export default LoadingIndicator;
|
package/src/Toast.tsx
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
|
+
import { View, Text, StyleSheet, Animated } from "react-native";
|
|
3
|
+
|
|
4
|
+
const Toast = ({
|
|
5
|
+
message,
|
|
6
|
+
visible,
|
|
7
|
+
duration = 3000,
|
|
8
|
+
}: {
|
|
9
|
+
message: any;
|
|
10
|
+
visible: boolean;
|
|
11
|
+
duration?: number;
|
|
12
|
+
}) => {
|
|
13
|
+
const [fadeAnim] = useState(new Animated.Value(0));
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (visible) {
|
|
17
|
+
// Fade in the toast
|
|
18
|
+
Animated.timing(fadeAnim, {
|
|
19
|
+
toValue: 1,
|
|
20
|
+
duration: 300,
|
|
21
|
+
useNativeDriver: true,
|
|
22
|
+
}).start(() => {
|
|
23
|
+
// Auto-hide after duration
|
|
24
|
+
setTimeout(() => {
|
|
25
|
+
Animated.timing(fadeAnim, {
|
|
26
|
+
toValue: 0,
|
|
27
|
+
duration: 300,
|
|
28
|
+
useNativeDriver: true,
|
|
29
|
+
}).start();
|
|
30
|
+
}, duration);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}, [visible]);
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<Animated.View
|
|
37
|
+
style={[
|
|
38
|
+
styles.toastContainer,
|
|
39
|
+
{ opacity: fadeAnim, display: visible ? "flex" : "none" },
|
|
40
|
+
]}
|
|
41
|
+
>
|
|
42
|
+
<Text style={styles.toastText}>{message}</Text>
|
|
43
|
+
</Animated.View>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const styles = StyleSheet.create({
|
|
48
|
+
toastContainer: {
|
|
49
|
+
position: "absolute",
|
|
50
|
+
bottom: 50,
|
|
51
|
+
left: "10%",
|
|
52
|
+
right: "10%",
|
|
53
|
+
backgroundColor: "rgba(0, 0, 0, 0.8)",
|
|
54
|
+
padding: 10,
|
|
55
|
+
borderRadius: 5,
|
|
56
|
+
alignItems: "center",
|
|
57
|
+
justifyContent: "center",
|
|
58
|
+
},
|
|
59
|
+
toastText: {
|
|
60
|
+
color: "#fff",
|
|
61
|
+
textAlign: "center",
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
export default Toast;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { TouchableOpacity, Text, StyleSheet, View } from "react-native";
|
|
3
|
+
import { openDebugMenu } from "../utils/debugMenu";
|
|
4
|
+
import { openDebuggerFromApp } from "../utils/debugMenu";
|
|
5
|
+
|
|
6
|
+
const DebugButton = () => {
|
|
7
|
+
if (!__DEV__) return null;
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<View style={styles.debugContainer}>
|
|
11
|
+
<TouchableOpacity style={styles.debugButton} onPress={openDebugMenu}>
|
|
12
|
+
<Text style={styles.debugText}>Menu</Text>
|
|
13
|
+
</TouchableOpacity>
|
|
14
|
+
<TouchableOpacity
|
|
15
|
+
style={[styles.debugButton, styles.debuggerButton]}
|
|
16
|
+
onPress={openDebuggerFromApp}
|
|
17
|
+
>
|
|
18
|
+
<Text style={styles.debugText}>Debug</Text>
|
|
19
|
+
</TouchableOpacity>
|
|
20
|
+
</View>
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const styles = StyleSheet.create({
|
|
25
|
+
debugContainer: {
|
|
26
|
+
position: "absolute",
|
|
27
|
+
top: 40,
|
|
28
|
+
right: 10,
|
|
29
|
+
flexDirection: "row",
|
|
30
|
+
gap: 8,
|
|
31
|
+
},
|
|
32
|
+
debugButton: {
|
|
33
|
+
backgroundColor: "rgba(0,0,0,0.7)",
|
|
34
|
+
padding: 8,
|
|
35
|
+
borderRadius: 4,
|
|
36
|
+
},
|
|
37
|
+
debuggerButton: {
|
|
38
|
+
backgroundColor: "rgba(255,0,0,0.7)",
|
|
39
|
+
},
|
|
40
|
+
debugText: {
|
|
41
|
+
color: "white",
|
|
42
|
+
fontSize: 12,
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
export default DebugButton;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React, { Component, ErrorInfo, ReactNode } from "react";
|
|
2
|
+
import { errorTracker } from "../services/ErrorTrackingService";
|
|
3
|
+
import { ErrorTypes } from "../constants/errorConstants";
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
componentName: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface State {
|
|
11
|
+
hasError: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class ErrorBoundary extends Component<Props, State> {
|
|
15
|
+
constructor(props: Props) {
|
|
16
|
+
super(props);
|
|
17
|
+
this.state = { hasError: false };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static getDerivedStateFromError(_: Error): State {
|
|
21
|
+
return { hasError: true };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
|
25
|
+
errorTracker.trackError(error, this.props.componentName, {
|
|
26
|
+
errorInfo,
|
|
27
|
+
type: ErrorTypes.RUNTIME_ERROR,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
render() {
|
|
32
|
+
if (this.state.hasError) {
|
|
33
|
+
return null; // Or a fallback UI if needed
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return this.props.children;
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export enum ErrorCodes {
|
|
2
|
+
MISSING_API_KEY = "MISSING_API_KEY",
|
|
3
|
+
ERROR_TRACKING_INIT_FAILED = "ERROR_TRACKING_INIT_FAILED",
|
|
4
|
+
WEBVIEW_LOAD_ERROR = "WEBVIEW_LOAD_ERROR",
|
|
5
|
+
CONFIG_FETCH_ERROR = "CONFIG_FETCH_ERROR",
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export enum ErrorTypes {
|
|
9
|
+
INITIALIZATION_ERROR = "INITIALIZATION_ERROR",
|
|
10
|
+
RUNTIME_ERROR = "RUNTIME_ERROR",
|
|
11
|
+
NETWORK_ERROR = "NETWORK_ERROR",
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const ErrorMessages = {
|
|
15
|
+
[ErrorCodes.MISSING_API_KEY]: (apiKey: any) =>
|
|
16
|
+
`Invalid API key: ${apiKey}. Please provide a valid API key.`,
|
|
17
|
+
[ErrorCodes.ERROR_TRACKING_INIT_FAILED]:
|
|
18
|
+
"Failed to initialize error tracking service.",
|
|
19
|
+
[ErrorCodes.WEBVIEW_LOAD_ERROR]: "Failed to load chatbot interface.",
|
|
20
|
+
[ErrorCodes.CONFIG_FETCH_ERROR]: "Failed to fetch chatbot configuration.",
|
|
21
|
+
};
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
const BASE_CHATBOT_DOMAIN = process.env.CHATBOT_DOMAIN || "";
|
|
2
|
+
const CHATBOT_PATH = process.env.CHATBOT_PATH || "";
|
|
3
|
+
export const BASE_CHATBOT_URL = `${BASE_CHATBOT_DOMAIN}/${CHATBOT_PATH}`;
|
|
4
|
+
export const API_URL = process.env.API_URL;
|
|
5
|
+
export const DEFAULT_LAUNCHER_IMAGE = `${BASE_CHATBOT_DOMAIN}/chatbubble.png`;
|
package/src/global.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// declare module "react-native" {
|
|
2
|
+
// export const View: any;
|
|
3
|
+
// export const ActivityIndicator: any;
|
|
4
|
+
// export const Linking: any;
|
|
5
|
+
// // Add other React Native exports as needed
|
|
6
|
+
// }
|
|
7
|
+
|
|
8
|
+
declare module "*.json" {
|
|
9
|
+
const value: any;
|
|
10
|
+
export default value;
|
|
11
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
import { getSystemInfo } from "../utils/systemInfo";
|
|
3
|
+
import { logger } from "../utils/logger";
|
|
4
|
+
import {
|
|
5
|
+
ChatbotEventType,
|
|
6
|
+
AllEventTypes,
|
|
7
|
+
ChatbotEventHandler,
|
|
8
|
+
} from "../types/events";
|
|
9
|
+
import { API_URL } from "../constants";
|
|
10
|
+
|
|
11
|
+
interface EventHookProps {
|
|
12
|
+
api_key: string;
|
|
13
|
+
chatbotConfig: any;
|
|
14
|
+
user_profile?: Record<string, any>;
|
|
15
|
+
onEvent?: ChatbotEventHandler;
|
|
16
|
+
systemInfo: { os: string; browser: string }; // Add this to get system info from WebView
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const useChatbotEvents = ({
|
|
20
|
+
api_key,
|
|
21
|
+
chatbotConfig,
|
|
22
|
+
user_profile,
|
|
23
|
+
onEvent,
|
|
24
|
+
systemInfo,
|
|
25
|
+
}: EventHookProps) => {
|
|
26
|
+
const onInternalEvent = useCallback(
|
|
27
|
+
async (
|
|
28
|
+
eventType: AllEventTypes,
|
|
29
|
+
additionalData: Record<string, any> = {}
|
|
30
|
+
) => {
|
|
31
|
+
if (!api_key) return;
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const apiUrl = API_URL;
|
|
35
|
+
const sysInfo = getSystemInfo(systemInfo);
|
|
36
|
+
|
|
37
|
+
const eventData = {
|
|
38
|
+
trigger_time: Date.now(),
|
|
39
|
+
channel: "CHATBOT",
|
|
40
|
+
org_id: api_key,
|
|
41
|
+
client_user_id: chatbotConfig?.isAnonymous
|
|
42
|
+
? "Anonymous"
|
|
43
|
+
: String(chatbotConfig?.userId || ""),
|
|
44
|
+
event_type: eventType,
|
|
45
|
+
user_profile: { ...(user_profile || {}) },
|
|
46
|
+
...additionalData,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const payload = {
|
|
50
|
+
org_id: api_key,
|
|
51
|
+
event_data: eventData,
|
|
52
|
+
metadata: {
|
|
53
|
+
timestamp: Date.now(),
|
|
54
|
+
...sysInfo,
|
|
55
|
+
},
|
|
56
|
+
event_type: "INFO",
|
|
57
|
+
user_id: chatbotConfig?.userId,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
logger.debug("Sending internal event:", payload);
|
|
61
|
+
|
|
62
|
+
const response = await fetch(`${apiUrl}/users/sdk/record-logs/`, {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: { "Content-Type": "application/json" },
|
|
65
|
+
body: JSON.stringify(payload),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`Failed to send internal event: ${response.statusText}`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
} catch (error) {
|
|
74
|
+
logger.error("Failed to send internal event:", error);
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
[api_key, chatbotConfig, user_profile, systemInfo]
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const emitEvent = useCallback(
|
|
81
|
+
(type: ChatbotEventType, data?: any) => {
|
|
82
|
+
const timestamp = Date.now();
|
|
83
|
+
onEvent?.({ type, timestamp, data });
|
|
84
|
+
onInternalEvent(type, { ...data, timestamp });
|
|
85
|
+
},
|
|
86
|
+
[onEvent, onInternalEvent]
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
emitEvent,
|
|
91
|
+
onInternalEvent,
|
|
92
|
+
};
|
|
93
|
+
};
|