feat: improve mixpanel flush strategy (#960)

* feat: improve mixpanel flush strategy

* fixes

* fix build

* update lock

* refactor methods

* conslidate calls

* update package and lock
This commit is contained in:
Justin Hernandez
2025-08-27 20:09:22 -07:00
committed by GitHub
parent ebf5d5105a
commit dba8ee1951
18 changed files with 275 additions and 69 deletions

View File

@@ -2,6 +2,10 @@
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
import { AppState, type AppStateStatus } from 'react-native';
import { NativeModules } from 'react-native';
import { ENABLE_DEBUG_LOGS, MIXPANEL_NFC_PROJECT_TOKEN } from '@env';
import NetInfo from '@react-native-community/netinfo';
import type { JsonMap, JsonValue } from '@segment/analytics-react-native';
import { TrackEventParams } from '@selfxyz/mobile-sdk-alpha';
@@ -10,6 +14,15 @@ import { createSegmentClient } from '@/Segment';
const segmentClient = createSegmentClient();
// --- Analytics flush strategy ---
let mixpanelConfigured = false;
let eventCount = 0;
let isConnected = true;
const eventQueue: Array<{
name: string;
properties?: Record<string, unknown>;
}> = [];
function coerceToJsonValue(
value: unknown,
seen = new WeakSet(),
@@ -136,3 +149,101 @@ const analytics = () => {
};
export default analytics;
/**
* Cleanup function to clear event queues
*/
export const cleanupAnalytics = () => {
eventQueue.length = 0;
eventCount = 0;
};
const setupFlushPolicies = () => {
AppState.addEventListener('change', (state: AppStateStatus) => {
if (state === 'background' || state === 'active') {
flushMixpanelEvents();
}
});
NetInfo.addEventListener(state => {
isConnected = state.isConnected ?? true;
if (isConnected) {
flushMixpanelEvents();
}
});
};
const flushMixpanelEvents = () => {
if (!MIXPANEL_NFC_PROJECT_TOKEN) return;
try {
if (__DEV__) console.log('[Mixpanel] flush');
// Send any queued events before flushing
while (eventQueue.length > 0) {
const evt = eventQueue.shift()!;
NativeModules.PassportReader?.trackEvent?.(evt.name, evt.properties);
}
NativeModules.PassportReader?.flush?.();
eventCount = 0;
} catch (err) {
if (__DEV__) console.warn('Mixpanel flush failed', err);
// re-queue on failure
if (typeof err !== 'undefined') {
// no-op, events are already queued if failure happened before flush
}
}
};
// --- Mixpanel NFC Analytics ---
export const configureNfcAnalytics = () => {
if (!MIXPANEL_NFC_PROJECT_TOKEN || mixpanelConfigured) return;
const enableDebugLogs = JSON.parse(String(ENABLE_DEBUG_LOGS));
NativeModules.PassportReader.configure(
MIXPANEL_NFC_PROJECT_TOKEN,
enableDebugLogs,
{
flushInterval: 20,
flushCount: 5,
flushOnBackground: true,
flushOnForeground: true,
flushOnNetworkChange: true,
},
);
setupFlushPolicies();
mixpanelConfigured = true;
};
/**
* Consolidated analytics flush function that flushes both Segment and Mixpanel events
* This should be called when you want to ensure all analytics events are sent immediately
*/
export const flushAllAnalytics = () => {
// Flush Segment analytics
const { flush: flushAnalytics } = analytics();
flushAnalytics();
// Flush Mixpanel events
flushMixpanelEvents();
};
export const trackNfcEvent = (
name: string,
properties?: Record<string, unknown>,
) => {
if (!MIXPANEL_NFC_PROJECT_TOKEN) return;
if (!mixpanelConfigured) configureNfcAnalytics();
if (!isConnected) {
eventQueue.push({ name, properties });
return;
}
try {
NativeModules.PassportReader?.trackEvent?.(name, properties);
eventCount++;
if (eventCount >= 5) {
flushMixpanelEvents();
}
} catch (err) {
eventQueue.push({ name, properties });
}
};

View File

@@ -2,14 +2,14 @@
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
/* eslint-disable @typescript-eslint/no-unused-vars */
import { Buffer } from 'buffer';
import { NativeModules, Platform } from 'react-native';
import PassportReader from 'react-native-passport-reader';
import { ENABLE_DEBUG_LOGS, MIXPANEL_NFC_PROJECT_TOKEN } from '@env';
import { reset, scan as scanDocument } from 'react-native-passport-reader';
import type { PassportData } from '@selfxyz/common/types';
import { configureNfcAnalytics } from '@/utils/analytics';
interface AndroidScanResponse {
mrz: string;
eContent: string;
@@ -43,9 +43,17 @@ export const parseScanResponse = (response: unknown) => {
: handleResponseIOS(response);
};
export const scan = async (inputs: Inputs) => {
configureNfcAnalytics();
return Platform.OS === 'android'
? await scanAndroid(inputs)
: await scanIOS(inputs);
};
const scanAndroid = async (inputs: Inputs) => {
PassportReader.reset();
return await PassportReader.scan({
reset();
return await scanDocument({
documentNumber: inputs.passportNumber,
dateOfBirth: inputs.dateOfBirth,
dateOfExpiry: inputs.dateOfExpiry,
@@ -55,7 +63,7 @@ const scanAndroid = async (inputs: Inputs) => {
};
const scanIOS = async (inputs: Inputs) => {
return await NativeModules.PassportReader.scanPassport(
return await NativeModules.PassportReader.scanDocument(
inputs.passportNumber,
inputs.dateOfBirth,
inputs.dateOfExpiry,
@@ -68,23 +76,6 @@ const scanIOS = async (inputs: Inputs) => {
);
};
export const scan = async (inputs: Inputs) => {
if (MIXPANEL_NFC_PROJECT_TOKEN) {
if (Platform.OS === 'ios') {
const enableDebugLogs = JSON.parse(String(ENABLE_DEBUG_LOGS));
NativeModules.PassportReader.configure(
MIXPANEL_NFC_PROJECT_TOKEN,
enableDebugLogs,
);
} else {
}
}
return Platform.OS === 'android'
? await scanAndroid(inputs)
: await scanIOS(inputs);
};
const handleResponseIOS = (response: unknown) => {
const parsed = JSON.parse(String(response));
const dgHashesObj = JSON.parse(parsed?.dataGroupHashes);