kewa-react-native 1.0.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/LICENSE +20 -0
- package/README.md +592 -0
- package/lib/KewaProvider.d.ts +26 -0
- package/lib/KewaProvider.js +91 -0
- package/lib/core/EventManager.d.ts +13 -0
- package/lib/core/EventManager.js +129 -0
- package/lib/core/KewaAnalytics.d.ts +41 -0
- package/lib/core/KewaAnalytics.js +317 -0
- package/lib/index.d.ts +23 -0
- package/lib/index.js +94 -0
- package/lib/types/config.d.ts +32 -0
- package/lib/types/config.js +2 -0
- package/lib/types/events.d.ts +104 -0
- package/lib/types/events.js +2 -0
- package/lib/types/index.d.ts +3 -0
- package/lib/types/index.js +19 -0
- package/lib/types/response.d.ts +15 -0
- package/lib/types/response.js +2 -0
- package/lib/utils/DeviceInfo.d.ts +4 -0
- package/lib/utils/DeviceInfo.js +32 -0
- package/lib/utils/NetworkManager.d.ts +17 -0
- package/lib/utils/NetworkManager.js +77 -0
- package/lib/utils/StorageManager.d.ts +16 -0
- package/lib/utils/StorageManager.js +120 -0
- package/lib/utils/constants.d.ts +26 -0
- package/lib/utils/constants.js +29 -0
- package/lib/utils/debug.d.ts +3 -0
- package/lib/utils/debug.js +17 -0
- package/package.json +59 -0
- package/src/KewaProvider.tsx +87 -0
- package/src/core/EventManager.ts +160 -0
- package/src/core/KewaAnalytics.ts +419 -0
- package/src/index.tsx +89 -0
- package/src/types/config.ts +33 -0
- package/src/types/events.ts +123 -0
- package/src/types/index.ts +3 -0
- package/src/types/response.ts +16 -0
- package/src/utils/DeviceInfo.ts +29 -0
- package/src/utils/NetworkManager.ts +85 -0
- package/src/utils/StorageManager.ts +121 -0
- package/src/utils/constants.ts +26 -0
- package/src/utils/debug.ts +15 -0
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
import { AppState, AppStateStatus } from 'react-native';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
KewaConfig,
|
|
5
|
+
DeviceInfo,
|
|
6
|
+
BaseEventData,
|
|
7
|
+
UserLoginEvent,
|
|
8
|
+
UserRegistrationEvent,
|
|
9
|
+
ScreenViewEvent,
|
|
10
|
+
ErrorEvent,
|
|
11
|
+
AppLaunchEvent,
|
|
12
|
+
ContactData,
|
|
13
|
+
} from '../types';
|
|
14
|
+
import { StorageManager } from '../utils/StorageManager';
|
|
15
|
+
import { NetworkManager } from '../utils/NetworkManager';
|
|
16
|
+
import { EventManager } from '../core/EventManager';
|
|
17
|
+
import { DeviceInfoCollector } from '../utils/DeviceInfo';
|
|
18
|
+
import { KEWA_CONSTANTS } from '../utils/constants';
|
|
19
|
+
import { kewaDebug, setDebugLogging } from '../utils/debug';
|
|
20
|
+
|
|
21
|
+
interface IdentityCache {
|
|
22
|
+
ktcId: string | null;
|
|
23
|
+
deviceId: string | null;
|
|
24
|
+
userProperties: ContactData;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class KewaAnalytics {
|
|
28
|
+
private config: KewaConfig | null = null;
|
|
29
|
+
private networkManager: NetworkManager | null = null;
|
|
30
|
+
private eventManager: EventManager | null = null;
|
|
31
|
+
private isInitialized: boolean = false;
|
|
32
|
+
private deviceInfo: DeviceInfo | null = null;
|
|
33
|
+
private appStateSubscription: { remove: () => void } | null = null;
|
|
34
|
+
private hasLaunched: boolean = false;
|
|
35
|
+
private currentAppState: AppStateStatus = 'active';
|
|
36
|
+
private identityCache: IdentityCache = {
|
|
37
|
+
ktcId: null,
|
|
38
|
+
deviceId: null,
|
|
39
|
+
userProperties: {},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
async init(config: KewaConfig): Promise<void> {
|
|
43
|
+
try {
|
|
44
|
+
this.config = this.normalizeConfig(config);
|
|
45
|
+
setDebugLogging(this.config.enableDebugLogging ?? false);
|
|
46
|
+
|
|
47
|
+
if (this.config.disableTracking) {
|
|
48
|
+
this.isInitialized = true;
|
|
49
|
+
kewaDebug('Kewa Analytics SDK initialized with tracking disabled');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!this.config.projectId) {
|
|
54
|
+
throw new Error('Kewa Analytics SDK: projectId is required when tracking is enabled');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
this.networkManager = new NetworkManager(
|
|
58
|
+
this.config.appUrl,
|
|
59
|
+
this.config.apiKey,
|
|
60
|
+
this.config.projectId,
|
|
61
|
+
);
|
|
62
|
+
this.eventManager = new EventManager(this.networkManager, this.config);
|
|
63
|
+
this.deviceInfo = await DeviceInfoCollector.collect();
|
|
64
|
+
await this.refreshIdentityCache();
|
|
65
|
+
|
|
66
|
+
if (!this.isAppStateTrackingDisabled() && !this.isInitialized) {
|
|
67
|
+
this.setupAppStateMonitoring();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
await this.eventManager.processQueuedEvents();
|
|
71
|
+
|
|
72
|
+
this.isInitialized = true;
|
|
73
|
+
this.hasLaunched = true;
|
|
74
|
+
|
|
75
|
+
if (!this.isEventTrackingDisabled()) {
|
|
76
|
+
await this.trackAppLaunch();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
kewaDebug('Kewa Analytics SDK initialized successfully');
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.warn('Failed to initialize Kewa Analytics SDK:', error);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private normalizeConfig(config: KewaConfig): KewaConfig {
|
|
86
|
+
const appUrl = config.appUrl ?? config.apiUrl;
|
|
87
|
+
if (!appUrl) {
|
|
88
|
+
throw new Error('Kewa Analytics SDK: appUrl is required');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
batchSize: KEWA_CONSTANTS.DEFAULTS.BATCH_SIZE,
|
|
93
|
+
maxQueueSize: KEWA_CONSTANTS.DEFAULTS.MAX_QUEUE_SIZE,
|
|
94
|
+
disableTracking: false,
|
|
95
|
+
disableEventTracking: false,
|
|
96
|
+
disableAppStateTracking: false,
|
|
97
|
+
disableScreenViewTracking: false,
|
|
98
|
+
enableDebugLogging: false,
|
|
99
|
+
...config,
|
|
100
|
+
appUrl,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private isTrackingDisabled(): boolean {
|
|
105
|
+
return this.config?.disableTracking ?? false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private isEventTrackingDisabled(): boolean {
|
|
109
|
+
return this.isTrackingDisabled() || (this.config?.disableEventTracking ?? false);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private isAppStateTrackingDisabled(): boolean {
|
|
113
|
+
return this.isTrackingDisabled() || (this.config?.disableAppStateTracking ?? false);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
isAutoScreenViewTrackingEnabled(): boolean {
|
|
117
|
+
if (this.isTrackingDisabled() || this.isEventTrackingDisabled()) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
return !(this.config?.disableScreenViewTracking ?? false);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private async refreshIdentityCache(): Promise<void> {
|
|
124
|
+
const [ktcId, deviceId, userProperties] = await Promise.all([
|
|
125
|
+
StorageManager.getKtcId(),
|
|
126
|
+
StorageManager.getDeviceId(),
|
|
127
|
+
StorageManager.getUserProperties(),
|
|
128
|
+
]);
|
|
129
|
+
|
|
130
|
+
this.identityCache = { ktcId, deviceId, userProperties };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private async trackAppLaunch(): Promise<void> {
|
|
134
|
+
const isFirstLaunch = !(await StorageManager.getDeviceId());
|
|
135
|
+
|
|
136
|
+
const launchData: AppLaunchEvent = {
|
|
137
|
+
...this.deviceInfo,
|
|
138
|
+
isFirstLaunch,
|
|
139
|
+
timestamp: new Date().toISOString(),
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
await this.emitEvent(KEWA_CONSTANTS.EVENTS.APP_LAUNCH, launchData);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private setupAppStateMonitoring(): void {
|
|
146
|
+
this.appStateSubscription = AppState.addEventListener('change', (nextAppState: AppStateStatus) => {
|
|
147
|
+
if (!this.hasLaunched || this.currentAppState === nextAppState) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const previousState = this.currentAppState;
|
|
152
|
+
this.currentAppState = nextAppState;
|
|
153
|
+
|
|
154
|
+
if (nextAppState === 'active' && (previousState === 'background' || previousState === 'inactive')) {
|
|
155
|
+
void this.handleAppForeground();
|
|
156
|
+
} else if (nextAppState === 'background' && (previousState === 'active' || previousState === 'inactive')) {
|
|
157
|
+
// Fire immediately without awaiting — the OS may suspend the app before async work finishes
|
|
158
|
+
void this.handleAppBackground();
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private async handleAppForeground(): Promise<void> {
|
|
164
|
+
this.backgroundEventPending = false;
|
|
165
|
+
await this.emitAppStateEvent(KEWA_CONSTANTS.EVENTS.APP_FOREGROUND);
|
|
166
|
+
|
|
167
|
+
if (this.eventManager) {
|
|
168
|
+
await this.eventManager.processQueuedEvents();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private backgroundEventPending = false;
|
|
173
|
+
|
|
174
|
+
private async handleAppBackground(): Promise<void> {
|
|
175
|
+
if (this.backgroundEventPending) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
this.backgroundEventPending = true;
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
await this.emitAppStateEvent(KEWA_CONSTANTS.EVENTS.APP_BACKGROUND);
|
|
183
|
+
} finally {
|
|
184
|
+
// Reset when app returns to foreground
|
|
185
|
+
if (AppState.currentState === 'active') {
|
|
186
|
+
this.backgroundEventPending = false;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private async emitAppStateEvent(eventName: string): Promise<void> {
|
|
192
|
+
if (this.isAppStateTrackingDisabled()) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (!this.isInitialized || !this.eventManager || !this.deviceInfo) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const { ktcId, deviceId, userProperties } = this.identityCache;
|
|
201
|
+
|
|
202
|
+
await this.eventManager.processAppStateEvent(
|
|
203
|
+
eventName,
|
|
204
|
+
{
|
|
205
|
+
...this.deviceInfo,
|
|
206
|
+
timestamp: new Date().toISOString(),
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
id: ktcId || undefined,
|
|
210
|
+
...userProperties,
|
|
211
|
+
},
|
|
212
|
+
deviceId,
|
|
213
|
+
this.deviceInfo,
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
await this.refreshIdentityCache();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private async emitEvent(
|
|
220
|
+
eventName: string,
|
|
221
|
+
eventData: BaseEventData = {},
|
|
222
|
+
contactData?: ContactData,
|
|
223
|
+
options?: { identity?: IdentityCache; skipIdentityRefresh?: boolean },
|
|
224
|
+
): Promise<void> {
|
|
225
|
+
if (!this.isInitialized) {
|
|
226
|
+
console.warn('Kewa Analytics SDK not initialized. Call init() first.');
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (!this.eventManager || !this.deviceInfo) {
|
|
231
|
+
console.warn('Kewa Analytics SDK not properly initialized');
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const identity = options?.identity ?? this.identityCache;
|
|
236
|
+
const { ktcId, deviceId, userProperties } = identity;
|
|
237
|
+
|
|
238
|
+
await this.eventManager.processEvent(
|
|
239
|
+
eventName,
|
|
240
|
+
eventData,
|
|
241
|
+
{
|
|
242
|
+
id: ktcId || undefined,
|
|
243
|
+
...userProperties,
|
|
244
|
+
...contactData,
|
|
245
|
+
},
|
|
246
|
+
deviceId,
|
|
247
|
+
this.deviceInfo,
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
if (!options?.skipIdentityRefresh) {
|
|
251
|
+
await this.refreshIdentityCache();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async trackEvent(eventName: string, eventData: BaseEventData = {}, contactData?: ContactData): Promise<void> {
|
|
256
|
+
if (this.isEventTrackingDisabled()) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
await this.emitEvent(eventName, eventData, contactData);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async setUserProperties(properties: ContactData): Promise<void> {
|
|
264
|
+
if (this.isTrackingDisabled()) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (!this.networkManager) {
|
|
269
|
+
console.warn('Kewa Analytics SDK not properly initialized');
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const currentUserProperties = await StorageManager.getUserProperties();
|
|
274
|
+
const userProperties = { ...currentUserProperties, ...properties };
|
|
275
|
+
await StorageManager.setUserProperties(userProperties);
|
|
276
|
+
|
|
277
|
+
const contact: ContactData = {
|
|
278
|
+
...properties,
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
await this.refreshIdentityCache();
|
|
282
|
+
|
|
283
|
+
if (!this.networkManager.getNetworkStatus()) {
|
|
284
|
+
kewaDebug('Offline — user properties saved locally only');
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
try {
|
|
289
|
+
kewaDebug('Kewa Analytics SDK: Updating contact:', contact);
|
|
290
|
+
|
|
291
|
+
const response = await this.networkManager.updateContact(contact);
|
|
292
|
+
await this.applyIdentityFromResponse(response);
|
|
293
|
+
|
|
294
|
+
kewaDebug('Kewa Analytics SDK: Contact update response:', response);
|
|
295
|
+
} catch (error) {
|
|
296
|
+
console.warn('Kewa Analytics SDK: Failed to update contact on Kewa:', error);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
private async applyIdentityFromResponse(response: { id?: string; device_id?: string }): Promise<void> {
|
|
301
|
+
const ktcId = await StorageManager.getKtcId();
|
|
302
|
+
const deviceId = await StorageManager.getDeviceId();
|
|
303
|
+
|
|
304
|
+
if (response.id && response.id !== ktcId) {
|
|
305
|
+
await StorageManager.setKtcId(response.id);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (response.device_id && response.device_id !== deviceId) {
|
|
309
|
+
await StorageManager.setDeviceId(response.device_id);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
await this.refreshIdentityCache();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
async trackLogin(userData: Partial<UserLoginEvent>): Promise<void> {
|
|
316
|
+
const loginData: UserLoginEvent = {
|
|
317
|
+
loginMethod: 'custom',
|
|
318
|
+
...userData,
|
|
319
|
+
userId: userData.userId || '',
|
|
320
|
+
timestamp: new Date().toISOString(),
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
await this.trackEvent(KEWA_CONSTANTS.EVENTS.USER_LOGIN, loginData);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async trackLogout(): Promise<void> {
|
|
327
|
+
if (this.isTrackingDisabled()) {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const logoutIdentity = { ...this.identityCache };
|
|
332
|
+
|
|
333
|
+
// Clear identity immediately so concurrent navigation events (screen_view) use no id
|
|
334
|
+
this.identityCache = { ktcId: null, deviceId: null, userProperties: {} };
|
|
335
|
+
|
|
336
|
+
if (!this.isEventTrackingDisabled()) {
|
|
337
|
+
await this.emitEvent(
|
|
338
|
+
KEWA_CONSTANTS.EVENTS.USER_LOGOUT,
|
|
339
|
+
{ timestamp: new Date().toISOString() },
|
|
340
|
+
undefined,
|
|
341
|
+
{ identity: logoutIdentity, skipIdentityRefresh: true },
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
await this.clearLocalIdentity();
|
|
346
|
+
kewaDebug('Kewa Analytics SDK: Logout complete — identity cleared');
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async trackRegistration(userData: Partial<UserRegistrationEvent>): Promise<void> {
|
|
350
|
+
const registrationData: UserRegistrationEvent = {
|
|
351
|
+
registrationMethod: 'email',
|
|
352
|
+
...userData,
|
|
353
|
+
userId: userData.userId || '',
|
|
354
|
+
timestamp: new Date().toISOString(),
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
await this.trackEvent(KEWA_CONSTANTS.EVENTS.USER_REGISTRATION, registrationData);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
async trackScreenView(screenName: string, additionalData: Partial<ScreenViewEvent> = {}): Promise<void> {
|
|
361
|
+
const screenData: ScreenViewEvent = {
|
|
362
|
+
screenName,
|
|
363
|
+
...additionalData,
|
|
364
|
+
timestamp: new Date().toISOString(),
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
await this.trackEvent(KEWA_CONSTANTS.EVENTS.SCREEN_VIEW, screenData);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
async trackError(error: Error, context: Record<string, any> = {}, isFatal = false): Promise<void> {
|
|
371
|
+
const errorData: ErrorEvent = {
|
|
372
|
+
errorMessage: error.message,
|
|
373
|
+
errorStack: error.stack,
|
|
374
|
+
errorName: error.name,
|
|
375
|
+
isFatal,
|
|
376
|
+
context,
|
|
377
|
+
timestamp: new Date().toISOString(),
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
await this.trackEvent(KEWA_CONSTANTS.EVENTS.ERROR, errorData);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
async reset(): Promise<void> {
|
|
384
|
+
await this.clearLocalIdentity();
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
private async clearLocalIdentity(): Promise<void> {
|
|
388
|
+
await StorageManager.removeKtcId();
|
|
389
|
+
await StorageManager.removeDeviceId();
|
|
390
|
+
await StorageManager.removeUserProperties();
|
|
391
|
+
this.identityCache = { ktcId: null, deviceId: null, userProperties: {} };
|
|
392
|
+
this.backgroundEventPending = false;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
async getKtcId(): Promise<string | null> {
|
|
396
|
+
return await StorageManager.getKtcId();
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
async getDeviceId(): Promise<string | null> {
|
|
400
|
+
return await StorageManager.getDeviceId();
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
getUserProperties(): Promise<ContactData> {
|
|
404
|
+
return StorageManager.getUserProperties();
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
isSDKInitialized(): boolean {
|
|
408
|
+
return this.isInitialized;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
cleanup(): void {
|
|
412
|
+
if (this.appStateSubscription) {
|
|
413
|
+
this.appStateSubscription.remove();
|
|
414
|
+
}
|
|
415
|
+
if (this.networkManager) {
|
|
416
|
+
this.networkManager.cleanup();
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import {
|
|
2
|
+
KewaProvider,
|
|
3
|
+
useKewa,
|
|
4
|
+
getDefaultKewaInstance,
|
|
5
|
+
} from './KewaProvider';
|
|
6
|
+
|
|
7
|
+
export * from './types';
|
|
8
|
+
|
|
9
|
+
export { KEWA_CONSTANTS } from './utils/constants';
|
|
10
|
+
export { DeviceInfoCollector } from './utils/DeviceInfo';
|
|
11
|
+
export { KewaProvider, useKewa };
|
|
12
|
+
|
|
13
|
+
const Kewa = getDefaultKewaInstance();
|
|
14
|
+
export default Kewa;
|
|
15
|
+
|
|
16
|
+
export class KewaTracker {
|
|
17
|
+
static setupNavigationTracking(navigationRef: any) {
|
|
18
|
+
const kewa = getDefaultKewaInstance();
|
|
19
|
+
const routeNameRef = { current: '' };
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
onReady: () => {
|
|
23
|
+
const currentRoute = navigationRef.current?.getCurrentRoute();
|
|
24
|
+
if (currentRoute) {
|
|
25
|
+
routeNameRef.current = currentRoute.name;
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
onStateChange: async () => {
|
|
29
|
+
if (!kewa.isAutoScreenViewTrackingEnabled()) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const previousRouteName = routeNameRef.current;
|
|
34
|
+
const currentRoute = navigationRef.current?.getCurrentRoute();
|
|
35
|
+
|
|
36
|
+
if (currentRoute && previousRouteName !== currentRoute.name) {
|
|
37
|
+
await kewa.trackScreenView(currentRoute.name, {
|
|
38
|
+
previousScreen: previousRouteName,
|
|
39
|
+
params: currentRoute.params,
|
|
40
|
+
});
|
|
41
|
+
routeNameRef.current = currentRoute.name;
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
static trackButtonPress(buttonName: string, additionalData: Record<string, any> = {}) {
|
|
48
|
+
const kewa = getDefaultKewaInstance();
|
|
49
|
+
return async () => {
|
|
50
|
+
await kewa.trackEvent('button_press', {
|
|
51
|
+
buttonName,
|
|
52
|
+
...additionalData,
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
static trackFormSubmission(formName: string, formData: Record<string, any> = {}) {
|
|
58
|
+
const kewa = getDefaultKewaInstance();
|
|
59
|
+
return async () => {
|
|
60
|
+
await kewa.trackEvent('form_submit', {
|
|
61
|
+
formName,
|
|
62
|
+
...formData,
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
static trackError(error: Error, context: Record<string, any> = {}) {
|
|
68
|
+
return getDefaultKewaInstance().trackError(error, context);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const KewaAutoTracker = KewaTracker;
|
|
73
|
+
|
|
74
|
+
export function useAutoTracker() {
|
|
75
|
+
const kewa = useKewa();
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
setupNavigationTracking: KewaTracker.setupNavigationTracking,
|
|
79
|
+
trackButtonPress: (buttonName: string, additionalData: Record<string, any> = {}) =>
|
|
80
|
+
async () => {
|
|
81
|
+
await kewa.trackEvent('button_press', { buttonName, ...additionalData });
|
|
82
|
+
},
|
|
83
|
+
trackFormSubmission: (formName: string, formData: Record<string, any> = {}) =>
|
|
84
|
+
async () => {
|
|
85
|
+
await kewa.trackEvent('form_submit', { formName, ...formData });
|
|
86
|
+
},
|
|
87
|
+
trackError: kewa.trackError.bind(kewa),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface KewaConfig {
|
|
2
|
+
appUrl: string;
|
|
3
|
+
apiKey: string;
|
|
4
|
+
projectId?: string;
|
|
5
|
+
disableTracking?: boolean;
|
|
6
|
+
disableEventTracking?: boolean;
|
|
7
|
+
disableAppStateTracking?: boolean;
|
|
8
|
+
disableScreenViewTracking?: boolean;
|
|
9
|
+
batchSize?: number;
|
|
10
|
+
maxQueueSize?: number;
|
|
11
|
+
enableDebugLogging?: boolean;
|
|
12
|
+
/** @deprecated Use appUrl instead */
|
|
13
|
+
apiUrl?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface DeviceInfo {
|
|
17
|
+
appName: string;
|
|
18
|
+
platform: string;
|
|
19
|
+
userAgent: string;
|
|
20
|
+
platformVersion: string | number;
|
|
21
|
+
appVersion: string;
|
|
22
|
+
deviceName?: string;
|
|
23
|
+
buildNumber?: string;
|
|
24
|
+
deviceModel: string;
|
|
25
|
+
deviceId?: string;
|
|
26
|
+
screenWidth: number;
|
|
27
|
+
screenHeight: number;
|
|
28
|
+
timezone: string;
|
|
29
|
+
carrier?: string;
|
|
30
|
+
isTablet?: boolean;
|
|
31
|
+
brand?: string;
|
|
32
|
+
manufacturer?: string;
|
|
33
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { DeviceInfo } from "./config";
|
|
2
|
+
|
|
3
|
+
export interface BaseEventData {
|
|
4
|
+
timestamp?: string;
|
|
5
|
+
screenName?: string;
|
|
6
|
+
previousScreen?: string;
|
|
7
|
+
[key: string]: any;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface KewaEvent {
|
|
11
|
+
eventName: string;
|
|
12
|
+
eventData: BaseEventData;
|
|
13
|
+
contactData?: ContactData;
|
|
14
|
+
deviceId?: string | null;
|
|
15
|
+
deviceInfo?: DeviceInfo;
|
|
16
|
+
metadata?: EventMetadata;
|
|
17
|
+
timestamp: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ContactData {
|
|
21
|
+
id?: string;
|
|
22
|
+
title?: string;
|
|
23
|
+
firstname?: string;
|
|
24
|
+
lastname?: string;
|
|
25
|
+
company?: string;
|
|
26
|
+
position?: string;
|
|
27
|
+
email?: string;
|
|
28
|
+
mobile?: string;
|
|
29
|
+
phone?: string;
|
|
30
|
+
points?: number;
|
|
31
|
+
fax?: string;
|
|
32
|
+
address1?: string;
|
|
33
|
+
address2?: string;
|
|
34
|
+
city?: string;
|
|
35
|
+
state?: string;
|
|
36
|
+
zipcode?: string;
|
|
37
|
+
country?: string;
|
|
38
|
+
preferred_locale?: string;
|
|
39
|
+
timezone?: string;
|
|
40
|
+
last_active?: string;
|
|
41
|
+
attribution_date?: string;
|
|
42
|
+
attribution?: number;
|
|
43
|
+
website?: string;
|
|
44
|
+
facebook?: string;
|
|
45
|
+
foursquare?: string;
|
|
46
|
+
instagram?: string;
|
|
47
|
+
linkedin?: string;
|
|
48
|
+
skype?: string;
|
|
49
|
+
twitter?: string;
|
|
50
|
+
companyaddress1?: string;
|
|
51
|
+
companyaddress2?: string;
|
|
52
|
+
companyemail?: string;
|
|
53
|
+
companyphone?: string;
|
|
54
|
+
companycity?: string;
|
|
55
|
+
companystate?: string;
|
|
56
|
+
companyzipcode?: string;
|
|
57
|
+
companycountry?: string;
|
|
58
|
+
companyname?: string;
|
|
59
|
+
companywebsite?: string;
|
|
60
|
+
companynumber_of_employees?: number;
|
|
61
|
+
companyfax?: string;
|
|
62
|
+
companyannual_revenue?: number;
|
|
63
|
+
companyindustry?: string;
|
|
64
|
+
companydescription?: string;
|
|
65
|
+
[key: string]: any;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface EventMetadata {
|
|
69
|
+
eventId: string;
|
|
70
|
+
attemptCount?: number;
|
|
71
|
+
queuedAt?: string;
|
|
72
|
+
sentAt?: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
export interface AppLaunchEvent extends BaseEventData {
|
|
77
|
+
isFirstLaunch: boolean;
|
|
78
|
+
installSource?: string;
|
|
79
|
+
referrer?: string;
|
|
80
|
+
campaignId?: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface UserLoginEvent extends BaseEventData {
|
|
84
|
+
userId: string;
|
|
85
|
+
email?: string;
|
|
86
|
+
loginMethod: 'email' | 'google' | 'facebook' | 'apple' | 'phone' | 'custom';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface UserRegistrationEvent extends BaseEventData {
|
|
90
|
+
userId: string;
|
|
91
|
+
email?: string;
|
|
92
|
+
registrationMethod: 'email' | 'google' | 'facebook' | 'apple' | 'phone';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
export interface ScreenViewEvent extends BaseEventData {
|
|
97
|
+
screenName: string;
|
|
98
|
+
screenClass?: string;
|
|
99
|
+
loadTime?: number;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface ErrorEvent extends BaseEventData {
|
|
103
|
+
errorMessage: string;
|
|
104
|
+
errorStack?: string;
|
|
105
|
+
errorName: string;
|
|
106
|
+
isFatal?: boolean;
|
|
107
|
+
context?: Record<string, any>;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface AppForegroundEvent extends BaseEventData {
|
|
111
|
+
timestamp: string;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface AppBackgroundEvent extends BaseEventData {
|
|
115
|
+
timestamp: string;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export type PredefinedEvents =
|
|
119
|
+
| AppLaunchEvent
|
|
120
|
+
| UserLoginEvent
|
|
121
|
+
| UserRegistrationEvent
|
|
122
|
+
| ScreenViewEvent
|
|
123
|
+
| ErrorEvent;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface KewaResponse {
|
|
2
|
+
success: boolean;
|
|
3
|
+
id?: string;
|
|
4
|
+
sid?: string;
|
|
5
|
+
device_id?: string;
|
|
6
|
+
message?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface KewaBatchResponse {
|
|
10
|
+
success: boolean;
|
|
11
|
+
user_id?: string;
|
|
12
|
+
processed_count: number;
|
|
13
|
+
failed_count: number;
|
|
14
|
+
errors?: string[];
|
|
15
|
+
timestamp?: string;
|
|
16
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Platform, Dimensions } from 'react-native';
|
|
2
|
+
import { DeviceInfo as DeviceInfoType } from '../types'
|
|
3
|
+
import DeviceInfo from 'react-native-device-info';
|
|
4
|
+
|
|
5
|
+
export class DeviceInfoCollector {
|
|
6
|
+
static async collect(): Promise<DeviceInfoType> {
|
|
7
|
+
const { width, height } = Dimensions.get('window');
|
|
8
|
+
|
|
9
|
+
const deviceInfo: DeviceInfoType = {
|
|
10
|
+
appName: DeviceInfo.getApplicationName(),
|
|
11
|
+
platform: Platform.OS,
|
|
12
|
+
platformVersion: Platform.Version,
|
|
13
|
+
userAgent: await DeviceInfo.getUserAgent(),
|
|
14
|
+
appVersion: DeviceInfo.getReadableVersion(),
|
|
15
|
+
buildNumber: DeviceInfo.getBuildNumber(),
|
|
16
|
+
deviceName: await DeviceInfo.getDeviceName(),
|
|
17
|
+
deviceModel: DeviceInfo.getModel(),
|
|
18
|
+
screenWidth: width,
|
|
19
|
+
screenHeight: height,
|
|
20
|
+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
21
|
+
carrier: await DeviceInfo.getCarrier(),
|
|
22
|
+
brand: DeviceInfo.getBrand(),
|
|
23
|
+
manufacturer: await DeviceInfo.getManufacturer(),
|
|
24
|
+
isTablet: DeviceInfo.isTablet(),
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return deviceInfo;
|
|
28
|
+
}
|
|
29
|
+
}
|