@robylon/react-native-sdk 2.2.0 → 2.2.1-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.
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/java/com/robylonreactnativesdk/RobylonDownloadModule.java +83 -0
- package/android/src/main/java/com/robylonreactnativesdk/RobylonReactNativeSdkPackage.java +24 -0
- 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/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/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 +1071 -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 +117 -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 +28 -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 +198 -0
- package/src/utils/logger.ts +14 -0
- package/src/utils/session.ts +26 -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,117 @@
|
|
|
1
|
+
import { useCallback, useRef } 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
|
+
// Held in refs so that emitEvent keeps a stable identity across renders.
|
|
27
|
+
// Host apps routinely pass an inline onEvent and an inline user_profile
|
|
28
|
+
// object literal; with those in the dependency arrays, emitEvent changed
|
|
29
|
+
// identity on every render, which re-ran the effects keyed on it, which
|
|
30
|
+
// re-emitted events, which re-rendered the host. That loop also POSTed to
|
|
31
|
+
// /users/sdk/record-logs/ on every iteration.
|
|
32
|
+
const onEventRef = useRef(onEvent);
|
|
33
|
+
const chatbotConfigRef = useRef(chatbotConfig);
|
|
34
|
+
const userProfileRef = useRef(user_profile);
|
|
35
|
+
const systemInfoRef = useRef(systemInfo);
|
|
36
|
+
onEventRef.current = onEvent;
|
|
37
|
+
chatbotConfigRef.current = chatbotConfig;
|
|
38
|
+
userProfileRef.current = user_profile;
|
|
39
|
+
systemInfoRef.current = systemInfo;
|
|
40
|
+
|
|
41
|
+
const onInternalEvent = useCallback(
|
|
42
|
+
async (
|
|
43
|
+
eventType: AllEventTypes,
|
|
44
|
+
additionalData: Record<string, any> = {}
|
|
45
|
+
) => {
|
|
46
|
+
if (!api_key) return;
|
|
47
|
+
|
|
48
|
+
const chatbotConfig = chatbotConfigRef.current;
|
|
49
|
+
const user_profile = userProfileRef.current;
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const apiUrl = API_URL;
|
|
53
|
+
const sysInfo = getSystemInfo(systemInfoRef.current);
|
|
54
|
+
|
|
55
|
+
const eventData = {
|
|
56
|
+
trigger_time: Date.now(),
|
|
57
|
+
channel: "CHATBOT",
|
|
58
|
+
org_id: api_key,
|
|
59
|
+
client_user_id: chatbotConfig?.isAnonymous
|
|
60
|
+
? "Anonymous"
|
|
61
|
+
: String(chatbotConfig?.userId || ""),
|
|
62
|
+
event_type: eventType,
|
|
63
|
+
user_profile: { ...(user_profile || {}) },
|
|
64
|
+
...additionalData,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const payload = {
|
|
68
|
+
org_id: api_key,
|
|
69
|
+
event_data: eventData,
|
|
70
|
+
metadata: {
|
|
71
|
+
timestamp: Date.now(),
|
|
72
|
+
...sysInfo,
|
|
73
|
+
},
|
|
74
|
+
event_type: "INFO",
|
|
75
|
+
user_id: chatbotConfig?.userId,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
logger.debug("Sending internal event:", payload);
|
|
79
|
+
|
|
80
|
+
const response = await fetch(`${apiUrl}/users/sdk/record-logs/`, {
|
|
81
|
+
method: "POST",
|
|
82
|
+
headers: { "Content-Type": "application/json" },
|
|
83
|
+
body: JSON.stringify(payload),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
if (!response.ok) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`Failed to send internal event: ${response.statusText}`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
} catch (error) {
|
|
92
|
+
logger.error("Failed to send internal event:", error);
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
[api_key]
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const emitEvent = useCallback(
|
|
99
|
+
(type: ChatbotEventType, data?: any) => {
|
|
100
|
+
const timestamp = Date.now();
|
|
101
|
+
// Isolated so that a throwing host handler cannot prevent the internal
|
|
102
|
+
// event from being recorded, or break the caller.
|
|
103
|
+
try {
|
|
104
|
+
onEventRef.current?.({ type, timestamp, data });
|
|
105
|
+
} catch (error) {
|
|
106
|
+
logger.error("onEvent handler threw an error:", error);
|
|
107
|
+
}
|
|
108
|
+
onInternalEvent(type, { ...data, timestamp });
|
|
109
|
+
},
|
|
110
|
+
[onInternalEvent]
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
emitEvent,
|
|
115
|
+
onInternalEvent,
|
|
116
|
+
};
|
|
117
|
+
};
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { default as Chatbot } from "./Chatbotsdk";
|
|
2
|
+
export { openChatbot } from "./openChatbot";
|
|
3
|
+
export {
|
|
4
|
+
errorTracker,
|
|
5
|
+
ErrorTrackingService,
|
|
6
|
+
} from "./services/ErrorTrackingService";
|
|
7
|
+
export * from "./constants/errorConstants";
|
|
8
|
+
export * from "./types/events";
|
|
9
|
+
|
|
10
|
+
export type { ChatbotProps, ChatbotRef } from "./Chatbotsdk";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Linking } from "react-native";
|
|
2
|
+
import { BASE_CHATBOT_URL } from "./constants";
|
|
3
|
+
|
|
4
|
+
export const openChatbot = (
|
|
5
|
+
chatbotId: string,
|
|
6
|
+
additionalParams: Record<string, string> = {}
|
|
7
|
+
): void => {
|
|
8
|
+
const params = new URLSearchParams({ id: chatbotId, ...additionalParams });
|
|
9
|
+
const url = `${BASE_CHATBOT_URL}?${params.toString()}`;
|
|
10
|
+
Linking.openURL(url);
|
|
11
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { getSystemInfo } from "../utils/systemInfo";
|
|
2
|
+
import { logger } from "../utils/logger";
|
|
3
|
+
import { API_URL } from "../constants";
|
|
4
|
+
import { Platform } from "react-native";
|
|
5
|
+
|
|
6
|
+
export enum EventType {
|
|
7
|
+
ERROR = "ERROR",
|
|
8
|
+
INFO = "INFO",
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ErrorEvent {
|
|
12
|
+
message: string;
|
|
13
|
+
stack?: string;
|
|
14
|
+
componentName?: string | null | undefined;
|
|
15
|
+
additionalInfo?: Record<string, any>;
|
|
16
|
+
timestamp: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface LogPayload {
|
|
20
|
+
org_id: string;
|
|
21
|
+
event_data: ErrorEvent;
|
|
22
|
+
metadata: Record<string, any>;
|
|
23
|
+
event_type: EventType;
|
|
24
|
+
user_id?: string | number | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class ErrorTrackingService {
|
|
28
|
+
private static instance: ErrorTrackingService;
|
|
29
|
+
private apiKey: string = "";
|
|
30
|
+
private userId: string | number | null | undefined = null;
|
|
31
|
+
private preInitQueue: Array<{
|
|
32
|
+
error: Error | string;
|
|
33
|
+
componentName?: string | null | undefined;
|
|
34
|
+
additionalInfo?: Record<string, any>;
|
|
35
|
+
}> = [];
|
|
36
|
+
private isInitialized = false;
|
|
37
|
+
|
|
38
|
+
private constructor() {}
|
|
39
|
+
|
|
40
|
+
static getInstance(): ErrorTrackingService {
|
|
41
|
+
if (!ErrorTrackingService.instance) {
|
|
42
|
+
ErrorTrackingService.instance = new ErrorTrackingService();
|
|
43
|
+
}
|
|
44
|
+
return ErrorTrackingService.instance;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
initialize(apiKey: string, userId: string | number | null = null) {
|
|
48
|
+
try {
|
|
49
|
+
// Handle pre-init errors
|
|
50
|
+
if (this.preInitQueue.length > 0) {
|
|
51
|
+
const payload: LogPayload = {
|
|
52
|
+
org_id: "UNINITALIZED",
|
|
53
|
+
event_data: {
|
|
54
|
+
message: `SDK Initialization Failed: Required parameter 'api_key' is ${
|
|
55
|
+
apiKey === undefined
|
|
56
|
+
? "undefined"
|
|
57
|
+
: apiKey === null
|
|
58
|
+
? "null"
|
|
59
|
+
: apiKey === ""
|
|
60
|
+
? "empty string"
|
|
61
|
+
: `invalid: ${apiKey}`
|
|
62
|
+
}`,
|
|
63
|
+
componentName: "ErrorTrackingService",
|
|
64
|
+
additionalInfo: {
|
|
65
|
+
preInitError: true,
|
|
66
|
+
apiKey,
|
|
67
|
+
userId,
|
|
68
|
+
timestamp: Date.now(),
|
|
69
|
+
},
|
|
70
|
+
timestamp: Date.now(),
|
|
71
|
+
},
|
|
72
|
+
metadata: getSystemInfo({ os: Platform.OS, browser: "React Native" }),
|
|
73
|
+
event_type: EventType.ERROR,
|
|
74
|
+
user_id: userId,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
this.sendLog(payload).catch((error) =>
|
|
78
|
+
logger.error("Send log error:", error)
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
this.apiKey = apiKey;
|
|
83
|
+
this.userId = userId;
|
|
84
|
+
this.isInitialized = true;
|
|
85
|
+
|
|
86
|
+
this.processPreInitQueue();
|
|
87
|
+
} catch (error) {
|
|
88
|
+
logger.error("Error during ErrorTrackingService initialization:", error);
|
|
89
|
+
this.preInitQueue.push({
|
|
90
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
91
|
+
componentName: "ErrorTrackingService",
|
|
92
|
+
additionalInfo: { apiKey, userId },
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private async processPreInitQueue() {
|
|
98
|
+
if (!this.preInitQueue.length) return;
|
|
99
|
+
|
|
100
|
+
logger.info(
|
|
101
|
+
`Processing ${this.preInitQueue.length} pre-initialization errors`
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
for (const item of this.preInitQueue) {
|
|
105
|
+
await this.trackError(item.error, item.componentName, {
|
|
106
|
+
...item.additionalInfo,
|
|
107
|
+
preInitError: true,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
this.preInitQueue = [];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private async sendLog(payload: LogPayload): Promise<void> {
|
|
115
|
+
try {
|
|
116
|
+
logger.debug("Sending error log:", payload);
|
|
117
|
+
const response = await fetch(`${API_URL}/users/sdk/record-logs/`, {
|
|
118
|
+
method: "POST",
|
|
119
|
+
headers: {
|
|
120
|
+
"Content-Type": "application/json",
|
|
121
|
+
},
|
|
122
|
+
body: JSON.stringify(payload),
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
if (!response.ok) {
|
|
126
|
+
logger.error("Failed to send error log:", await response.text());
|
|
127
|
+
}
|
|
128
|
+
} catch (error) {
|
|
129
|
+
logger.error("Error sending log:", error);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async trackError(
|
|
134
|
+
error: Error | string,
|
|
135
|
+
componentName: string | null | undefined,
|
|
136
|
+
additionalInfo?: Record<string, any>
|
|
137
|
+
) {
|
|
138
|
+
if (!this.isInitialized) {
|
|
139
|
+
const payload: LogPayload = {
|
|
140
|
+
org_id: "UNINITALIZED",
|
|
141
|
+
event_data: {
|
|
142
|
+
message: error instanceof Error ? error.message : error,
|
|
143
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
144
|
+
componentName,
|
|
145
|
+
additionalInfo: {
|
|
146
|
+
...additionalInfo,
|
|
147
|
+
preInitError: true,
|
|
148
|
+
timestamp: Date.now(),
|
|
149
|
+
},
|
|
150
|
+
timestamp: Date.now(),
|
|
151
|
+
},
|
|
152
|
+
metadata: getSystemInfo({ os: Platform.OS, browser: "React Native" }),
|
|
153
|
+
event_type: EventType.ERROR,
|
|
154
|
+
user_id: this.userId,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
await this.sendLog(payload);
|
|
159
|
+
} catch (sendError) {
|
|
160
|
+
this.preInitQueue.push({ error, componentName, additionalInfo });
|
|
161
|
+
logger.warn("Error tracked before initialization, queuing:", error);
|
|
162
|
+
}
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const errorEvent: ErrorEvent = {
|
|
167
|
+
message: error instanceof Error ? error.message : error,
|
|
168
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
169
|
+
componentName,
|
|
170
|
+
additionalInfo: {
|
|
171
|
+
...additionalInfo,
|
|
172
|
+
timestamp: Date.now(),
|
|
173
|
+
},
|
|
174
|
+
timestamp: Date.now(),
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const payload: LogPayload = {
|
|
178
|
+
org_id: this.apiKey || "UNINITALIZED",
|
|
179
|
+
event_data: errorEvent,
|
|
180
|
+
metadata: getSystemInfo({ os: Platform.OS, browser: "React Native" }),
|
|
181
|
+
event_type: EventType.ERROR,
|
|
182
|
+
user_id: this.userId,
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
await this.sendLog(payload);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async trackInfo(
|
|
189
|
+
message: string,
|
|
190
|
+
componentName: string | null | undefined,
|
|
191
|
+
additionalInfo?: Record<string, any>
|
|
192
|
+
) {
|
|
193
|
+
if (!this.apiKey) {
|
|
194
|
+
logger.error("Error tracking not initialized");
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const infoEvent: ErrorEvent = {
|
|
199
|
+
message,
|
|
200
|
+
componentName,
|
|
201
|
+
additionalInfo,
|
|
202
|
+
timestamp: Date.now(),
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const payload: LogPayload = {
|
|
206
|
+
org_id: this.apiKey,
|
|
207
|
+
event_data: infoEvent,
|
|
208
|
+
metadata: getSystemInfo({ os: Platform.OS, browser: "React Native" }),
|
|
209
|
+
event_type: EventType.INFO,
|
|
210
|
+
user_id: this.userId,
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
await this.sendLog(payload);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export const errorTracker = ErrorTrackingService.getInstance();
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export enum ChatbotEventType {
|
|
2
|
+
CHATBOT_BUTTON_LOADED = "CHATBOT_BUTTON_LOADED",
|
|
3
|
+
CHATBOT_BUTTON_CLICKED = "CHATBOT_BUTTON_CLICKED",
|
|
4
|
+
CHATBOT_OPENED = "CHATBOT_OPENED",
|
|
5
|
+
CHATBOT_CLOSED = "CHATBOT_CLOSED",
|
|
6
|
+
CHATBOT_APP_READY = "CHATBOT_APP_READY",
|
|
7
|
+
CHATBOT_LOADED = "CHATBOT_LOADED",
|
|
8
|
+
CHAT_INITIALIZED = "CHAT_INITIALIZED",
|
|
9
|
+
SESSION_REFRESHED = "SESSION_REFRESHED",
|
|
10
|
+
CHAT_INITIALIZATION_FAILED = "CHAT_INITIALIZATION_FAILED",
|
|
11
|
+
REQUEST_MIC_PERMISSION = "REQUEST_MIC_PERMISSION",
|
|
12
|
+
MIC_PERMISSION_GRANTED = "MIC_PERMISSION_GRANTED",
|
|
13
|
+
MIC_PERMISSION_DENIED = "MIC_PERMISSION_DENIED",
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export enum InternalEventType {
|
|
17
|
+
CHAT_MOVED = "CHAT_MOVED",
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type AllEventTypes = ChatbotEventType | InternalEventType;
|
|
21
|
+
|
|
22
|
+
export interface ChatbotEvent {
|
|
23
|
+
type: ChatbotEventType;
|
|
24
|
+
timestamp: number;
|
|
25
|
+
data?: any;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type ChatbotEventHandler = (event: ChatbotEvent) => void;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { Animated, Easing } from "react-native";
|
|
2
|
+
|
|
3
|
+
// Custom easing function that matches the web version
|
|
4
|
+
export const customEasing = Easing.bezier(0.2, 1.27, 0.29, 0.97);
|
|
5
|
+
|
|
6
|
+
export const ANIMATION_CONFIG = {
|
|
7
|
+
DURATION: {
|
|
8
|
+
ENTER: {
|
|
9
|
+
TRANSFORM: 200, // 0.2 seconds for enter
|
|
10
|
+
OPACITY: 150, // 0.15 seconds for enter
|
|
11
|
+
},
|
|
12
|
+
EXIT: {
|
|
13
|
+
TRANSFORM: 100, // 0.1 seconds for exit (faster)
|
|
14
|
+
OPACITY: 80, // 0.08 seconds for exit (faster)
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
SCALE: {
|
|
18
|
+
BUTTON: {
|
|
19
|
+
DEFAULT: 1,
|
|
20
|
+
ACTIVE: 0.85,
|
|
21
|
+
},
|
|
22
|
+
IFRAME: {
|
|
23
|
+
HIDDEN: 0.3,
|
|
24
|
+
VISIBLE: 1,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
TRANSLATE: {
|
|
28
|
+
Y: 40, // translateY value for animations
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
interface AnimatedValues {
|
|
33
|
+
scale: Animated.Value;
|
|
34
|
+
translateY: Animated.Value;
|
|
35
|
+
opacity: Animated.Value;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const createAnimatedValues = (): AnimatedValues => ({
|
|
39
|
+
scale: new Animated.Value(1),
|
|
40
|
+
translateY: new Animated.Value(0),
|
|
41
|
+
opacity: new Animated.Value(1),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
export const animateWebViewOpen = (
|
|
45
|
+
animatedValues: AnimatedValues,
|
|
46
|
+
callback?: () => void
|
|
47
|
+
) => {
|
|
48
|
+
// Reset initial values
|
|
49
|
+
animatedValues.scale.setValue(ANIMATION_CONFIG.SCALE.IFRAME.HIDDEN);
|
|
50
|
+
animatedValues.translateY.setValue(ANIMATION_CONFIG.TRANSLATE.Y);
|
|
51
|
+
animatedValues.opacity.setValue(0);
|
|
52
|
+
|
|
53
|
+
Animated.parallel([
|
|
54
|
+
// Scale and translate animation
|
|
55
|
+
Animated.timing(animatedValues.scale, {
|
|
56
|
+
toValue: ANIMATION_CONFIG.SCALE.IFRAME.VISIBLE,
|
|
57
|
+
duration: ANIMATION_CONFIG.DURATION.ENTER.TRANSFORM,
|
|
58
|
+
easing: customEasing,
|
|
59
|
+
useNativeDriver: true,
|
|
60
|
+
}),
|
|
61
|
+
Animated.timing(animatedValues.translateY, {
|
|
62
|
+
toValue: 0,
|
|
63
|
+
duration: ANIMATION_CONFIG.DURATION.ENTER.TRANSFORM,
|
|
64
|
+
easing: customEasing,
|
|
65
|
+
useNativeDriver: true,
|
|
66
|
+
}),
|
|
67
|
+
// Opacity animation
|
|
68
|
+
Animated.timing(animatedValues.opacity, {
|
|
69
|
+
toValue: 1,
|
|
70
|
+
duration: ANIMATION_CONFIG.DURATION.ENTER.OPACITY,
|
|
71
|
+
easing: Easing.ease,
|
|
72
|
+
useNativeDriver: true,
|
|
73
|
+
}),
|
|
74
|
+
]).start(callback);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const animateWebViewClose = (
|
|
78
|
+
animatedValues: AnimatedValues,
|
|
79
|
+
callback?: () => void
|
|
80
|
+
) => {
|
|
81
|
+
Animated.parallel([
|
|
82
|
+
// Scale and translate animation
|
|
83
|
+
Animated.timing(animatedValues.scale, {
|
|
84
|
+
toValue: ANIMATION_CONFIG.SCALE.IFRAME.HIDDEN,
|
|
85
|
+
duration: ANIMATION_CONFIG.DURATION.EXIT.TRANSFORM,
|
|
86
|
+
easing: customEasing,
|
|
87
|
+
useNativeDriver: true,
|
|
88
|
+
}),
|
|
89
|
+
Animated.timing(animatedValues.translateY, {
|
|
90
|
+
toValue: ANIMATION_CONFIG.TRANSLATE.Y,
|
|
91
|
+
duration: ANIMATION_CONFIG.DURATION.EXIT.TRANSFORM,
|
|
92
|
+
easing: customEasing,
|
|
93
|
+
useNativeDriver: true,
|
|
94
|
+
}),
|
|
95
|
+
// Opacity animation
|
|
96
|
+
Animated.timing(animatedValues.opacity, {
|
|
97
|
+
toValue: 0,
|
|
98
|
+
duration: ANIMATION_CONFIG.DURATION.EXIT.OPACITY,
|
|
99
|
+
easing: Easing.ease,
|
|
100
|
+
useNativeDriver: true,
|
|
101
|
+
}),
|
|
102
|
+
]).start(callback);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export const animateButtonScale = (
|
|
106
|
+
scale: Animated.Value,
|
|
107
|
+
isActive: boolean,
|
|
108
|
+
callback?: () => void
|
|
109
|
+
) => {
|
|
110
|
+
Animated.timing(scale, {
|
|
111
|
+
toValue: isActive
|
|
112
|
+
? ANIMATION_CONFIG.SCALE.BUTTON.ACTIVE
|
|
113
|
+
: ANIMATION_CONFIG.SCALE.BUTTON.DEFAULT,
|
|
114
|
+
duration: isActive
|
|
115
|
+
? ANIMATION_CONFIG.DURATION.ENTER.TRANSFORM
|
|
116
|
+
: ANIMATION_CONFIG.DURATION.EXIT.TRANSFORM,
|
|
117
|
+
easing: customEasing,
|
|
118
|
+
useNativeDriver: true,
|
|
119
|
+
}).start(callback);
|
|
120
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export const getBestFontColor = (
|
|
2
|
+
backgroundColor: string,
|
|
3
|
+
contrastThreshold = 0.5
|
|
4
|
+
): string => {
|
|
5
|
+
const whiteShade = "#FFFFFF"; // Light white shade
|
|
6
|
+
const blackShade = "#0E0E0F"; // Dark black shade
|
|
7
|
+
|
|
8
|
+
// Function to convert hex to RGB
|
|
9
|
+
const hexToRgb = (hex: string) => {
|
|
10
|
+
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
|
|
11
|
+
hex = hex.replace(shorthandRegex, (m, r, g, b) => r + r + g + g + b + b);
|
|
12
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
13
|
+
return result
|
|
14
|
+
? {
|
|
15
|
+
r: parseInt(result[1], 16),
|
|
16
|
+
g: parseInt(result[2], 16),
|
|
17
|
+
b: parseInt(result[3], 16),
|
|
18
|
+
}
|
|
19
|
+
: { r: 0, g: 0, b: 0 };
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Function to calculate luminance
|
|
23
|
+
const luminance = (r: number, g: number, b: number) => {
|
|
24
|
+
const a = [r, g, b].map((v) => {
|
|
25
|
+
v /= 255;
|
|
26
|
+
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
|
|
27
|
+
});
|
|
28
|
+
return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const { r, g, b } = hexToRgb(backgroundColor);
|
|
32
|
+
const bgLuminance = luminance(r, g, b);
|
|
33
|
+
|
|
34
|
+
return bgLuminance > contrastThreshold ? blackShade : whiteShade;
|
|
35
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Platform } from "react-native";
|
|
2
|
+
|
|
3
|
+
declare global {
|
|
4
|
+
interface Window {
|
|
5
|
+
ReactNativeWebView: any;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
declare let global: {
|
|
10
|
+
XMLHttpRequest: any;
|
|
11
|
+
FormData: any;
|
|
12
|
+
originalXMLHttpRequest: any;
|
|
13
|
+
originalFormData: any;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const enableNetworkDebug = () => {
|
|
17
|
+
if (__DEV__) {
|
|
18
|
+
// Enable network request debugging
|
|
19
|
+
global.XMLHttpRequest =
|
|
20
|
+
global.originalXMLHttpRequest || global.XMLHttpRequest;
|
|
21
|
+
global.FormData = global.originalFormData || global.FormData;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const enableWebViewDebug = (webViewRef: any) => {
|
|
26
|
+
if (__DEV__ && Platform.OS === "ios") {
|
|
27
|
+
// Enable console.log from WebView to show in native debugger
|
|
28
|
+
const script = `
|
|
29
|
+
window.onerror = function(message, sourcefile, lineno, colno, error) {
|
|
30
|
+
window.ReactNativeWebView.postMessage(JSON.stringify({
|
|
31
|
+
type: 'ERROR',
|
|
32
|
+
data: { message, sourcefile, lineno, colno, error: error?.toString() }
|
|
33
|
+
}));
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
console = new Proxy(console, {
|
|
37
|
+
get: function(target, property) {
|
|
38
|
+
const origMethod = target[property];
|
|
39
|
+
return function(...args) {
|
|
40
|
+
const logData = args.map(arg =>
|
|
41
|
+
typeof arg === 'object' ? JSON.stringify(arg) : arg
|
|
42
|
+
).join(' ');
|
|
43
|
+
|
|
44
|
+
window.ReactNativeWebView.postMessage(JSON.stringify({
|
|
45
|
+
type: 'CONSOLE',
|
|
46
|
+
level: property,
|
|
47
|
+
data: logData
|
|
48
|
+
}));
|
|
49
|
+
origMethod.apply(target, args);
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
true;`;
|
|
54
|
+
webViewRef.current?.injectJavaScript(script);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Platform, NativeModules } from "react-native";
|
|
2
|
+
|
|
3
|
+
export const openDebugMenu = () => {
|
|
4
|
+
if (Platform.OS === "ios") {
|
|
5
|
+
NativeModules.DevMenu.show();
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const openDebuggerFromApp = () => {
|
|
10
|
+
if (Platform.OS === "ios") {
|
|
11
|
+
// For iOS
|
|
12
|
+
NativeModules.DevSettings.setIsDebuggingRemotely(true);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { logger } from "./logger";
|
|
2
|
+
import { ErrorTypes } from "../constants/errorConstants";
|
|
3
|
+
import { Platform } from "react-native";
|
|
4
|
+
import { errorTracker } from "../services/ErrorTrackingService";
|
|
5
|
+
|
|
6
|
+
// Global error handler for uncaught errors and promise rejections
|
|
7
|
+
export const setupGlobalErrorHandlers = () => {
|
|
8
|
+
if (__DEV__) {
|
|
9
|
+
// Handle synchronous errors
|
|
10
|
+
const errorHandler = (error: Error, isFatal?: boolean) => {
|
|
11
|
+
errorTracker.trackError(error, "GlobalErrorHandler", {
|
|
12
|
+
isFatal,
|
|
13
|
+
type: ErrorTypes.RUNTIME_ERROR,
|
|
14
|
+
platform: Platform.OS,
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// Handle promise rejections
|
|
19
|
+
const rejectionHandler = (event: any) => {
|
|
20
|
+
errorTracker.trackError(
|
|
21
|
+
event?.reason || new Error("Unknown Promise Error"),
|
|
22
|
+
"UnhandledPromiseRejection",
|
|
23
|
+
{
|
|
24
|
+
type: ErrorTypes.RUNTIME_ERROR,
|
|
25
|
+
platform: Platform.OS,
|
|
26
|
+
}
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Set up handlers
|
|
31
|
+
if ((global as any).ErrorUtils) {
|
|
32
|
+
const prevHandler = (global as any).ErrorUtils.getGlobalHandler();
|
|
33
|
+
(global as any).ErrorUtils.setGlobalHandler(
|
|
34
|
+
(error: Error, isFatal?: boolean) => {
|
|
35
|
+
errorHandler(error, isFatal);
|
|
36
|
+
prevHandler(error, isFatal);
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Handle promise rejections
|
|
42
|
+
const setupPromiseRejectionTracking = () => {
|
|
43
|
+
const tracking = require("promise/setimmediate/rejection-tracking");
|
|
44
|
+
tracking.enable({
|
|
45
|
+
allRejections: true,
|
|
46
|
+
onUnhandled: (id: number, error: Error) => {
|
|
47
|
+
rejectionHandler({ reason: error });
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
setupPromiseRejectionTracking();
|
|
54
|
+
} catch (error) {
|
|
55
|
+
logger.warn("Promise rejection tracking setup failed:", error);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|