expo-tiktok-business
Version:
A expo module for tiktok business sdk
370 lines (329 loc) • 10.5 kB
text/typescript
import { requireNativeModule, EventEmitter } from 'expo-modules-core';
import { AppStateStatus, AppState, Platform } from "react-native";
import Constants from "expo-constants";
import {
TiktokSDKModuleEvents,
TiktokEventName,
TiktokSDKConfig,
TiktokEventParams,
TiktokPlatformAppIds
} from "./TiktokSDK.types";
/**
* Native module interface
*/
interface TiktokSDKModule {
initialize(config: TiktokSDKConfig): Promise<boolean>;
setDebugMode(enabled: boolean): Promise<boolean>;
trackEvent(
eventName: string,
eventData: Record<string, any>
): Promise<boolean>;
trackRouteChange(
routeName: string,
params?: Record<string, any>
): Promise<boolean>;
}
// Create a getter function for the native module that will be called when needed
const getNativeModule = (): TiktokSDKModule | null => {
try {
// Use the modern requireNativeModule approach instead of deprecated NativeModulesProxy
console.log('Attempting to load TiktokSDK native module...');
const module = requireNativeModule('TiktokSDK');
console.log('TiktokSDK native module loaded:', module ? 'SUCCESS' : 'FAILED');
console.log('Platform:', Platform.OS);
return module as TiktokSDKModule || null;
} catch (error) {
console.error('Error accessing TiktokSDK native module:', error);
return null;
}
};
/**
* TikTok Business SDK wrapper class
*/
class TiktokSDK {
private _isInitialized: boolean = false;
private _config?: TiktokSDKConfig;
private _appStateSubscription: any = null;
/**
* Get the bundle ID or package name for the current platform
* @private
*/
private _getPlatformAppId(
appId: string | { ios?: string; android?: string; default: string }
): string {
if (typeof appId === 'string') {
return appId;
}
const platform = Platform.OS;
if (platform === 'ios' && appId.ios) {
return appId.ios;
} else if (platform === 'android' && appId.android) {
return appId.android;
}
return appId.default;
}
/**
* Get the TikTok App ID for the current platform
* @private
*/
private _getPlatformTikTokAppId(
tiktokAppId: string | TiktokPlatformAppIds
): string {
if (typeof tiktokAppId === 'string') {
return tiktokAppId;
}
const platform = Platform.OS;
if (platform === 'ios' && tiktokAppId.ios) {
return tiktokAppId.ios;
} else if (platform === 'android' && tiktokAppId.android) {
return tiktokAppId.android;
}
return tiktokAppId.default;
}
/**
* Try to get the app's bundle ID/package name from Expo Constants
* @private
*/
private _tryGetExpoBundleId(): string | null {
try {
// Try to get the bundle ID from Expo Constants
const { manifest } = Constants;
if (manifest) {
// For Expo SDK 46 and above
if (manifest.extra && manifest.extra.expoClient) {
if (Platform.OS === 'ios' && manifest.extra.expoClient.ios && manifest.extra.expoClient.ios.bundleIdentifier) {
return manifest.extra.expoClient.ios.bundleIdentifier;
} else if (Platform.OS === 'android' && manifest.extra.expoClient.android && manifest.extra.expoClient.android.package) {
return manifest.extra.expoClient.android.package;
}
}
// For older Expo SDK versions
if (Platform.OS === 'ios' && manifest.ios && manifest.ios.bundleIdentifier) {
return manifest.ios.bundleIdentifier;
} else if (Platform.OS === 'android' && manifest.android && manifest.android.package) {
return manifest.android.package;
}
}
} catch (error) {
console.warn('TiktokSDK: Failed to get bundle ID from Expo Constants', error);
}
return null;
}
/**
* Initialize the TikTok Business SDK
* @param appIdParam Your app ID or platform-specific app IDs
* @param tiktokAppIdParam Your TikTok app ID or platform-specific TikTok app IDs
* @param options Additional configuration options
*/
async initialize(
appIdParam: string | { ios?: string; android?: string; default: string },
tiktokAppIdParam: string | TiktokPlatformAppIds,
options: {
debugMode?: boolean;
autoTrackAppLifecycle?: boolean;
autoTrackRouteChanges?: boolean;
} = {}
): Promise<boolean> {
// Get platform-specific app ID
let appId: string;
if (typeof appIdParam === 'string') {
appId = appIdParam;
} else {
appId = this._getPlatformAppId(appIdParam);
}
// Get platform-specific TikTok app ID
let tiktokAppId: string;
if (typeof tiktokAppIdParam === 'string') {
tiktokAppId = tiktokAppIdParam;
} else {
tiktokAppId = this._getPlatformTikTokAppId(tiktokAppIdParam);
}
// Create config
const config: any = {
appId,
tiktokAppId,
debugMode: options.debugMode || false,
autoTrackAppLifecycle: options.autoTrackAppLifecycle !== false, // default to true
autoTrackRouteChanges: options.autoTrackRouteChanges !== false, // default to true,
};
this._config = config;
if (config.debugMode) {
console.log(`TiktokSDK: Initializing for ${Platform.OS} with appId=${appId}, tiktokAppId=${tiktokAppId}`);
}
// Initialize the native SDK
const module = getNativeModule();
if (!module) {
console.error("TiktokSDK: Native module not available");
return false;
}
try {
const success = await module.initialize(config) || false;
this._isInitialized = success;
// Set up app state change listener if lifecycle tracking is enabled
if (success && config.autoTrackAppLifecycle) {
this._setupAppStateListener();
}
return success;
} catch (error) {
console.error("TiktokSDK: Error initializing SDK", error);
return false;
}
}
/**
* Set debug mode for the SDK
* @param enabled Whether to enable debug mode
*/
async setDebugMode(enabled: boolean): Promise<boolean> {
const module = getNativeModule();
if (!module) {
console.error("TiktokSDK: Native module not available");
return false;
}
try {
return await module.setDebugMode(enabled) || false;
} catch (error) {
console.error("TiktokSDK: Error setting debug mode", error);
return false;
}
}
/**
* Track a standard or custom event
* @param eventName Event name (use TiktokEventName for standard events)
* @param eventParams Event parameters
*/
async trackEvent(
eventName: TiktokEventName | string,
eventParams: TiktokEventParams = {}
): Promise<boolean> {
if (!this._isInitialized) {
console.warn("TiktokSDK: SDK not initialized. Call initialize() first.");
return false;
}
const module = getNativeModule();
if (!module) {
console.error("TiktokSDK: Native module not available");
return false;
}
try {
return await module.trackEvent(eventName, eventParams) || false;
} catch (error) {
console.error("TiktokSDK: Error tracking event", error);
return false;
}
}
/**
* Track a route change (screen view) - useful for manual tracking
* @param routeName The name of the route/screen
* @param params Optional route parameters
*/
async trackRouteChange(
routeName: string,
params?: Record<string, any>
): Promise<boolean> {
if (!this._isInitialized) {
return false;
}
const module = getNativeModule();
if (!module) {
console.error("TiktokSDK: Native module not available");
return false;
}
try {
return await module.trackRouteChange(routeName, params) || false;
} catch (error) {
console.error("TiktokSDK: Error tracking route change", error);
return false;
}
}
/**
* Helper: Track a search event
* @param searchString The search query
* @param additionalParams Additional event parameters
*/
async trackSearch(
searchString: string,
additionalParams: TiktokEventParams = {}
): Promise<boolean> {
return this.trackEvent(TiktokEventName.SEARCH, {
search_string: searchString,
...additionalParams,
});
}
/**
* Helper: Track a content view
* @param contentId Content ID
* @param contentType Content type
* @param additionalParams Additional event parameters
*/
async trackViewContent(
contentId: string,
contentType: string,
additionalParams: TiktokEventParams = {}
): Promise<boolean> {
return this.trackEvent(TiktokEventName.VIEW_CONTENT, {
content_id: contentId,
content_type: contentType,
...additionalParams,
});
}
/**
* Helper: Track a completed purchase
* @param value The monetary value of the purchase
* @param currency The currency code (e.g., USD)
* @param contents The purchased items
* @param additionalParams Additional event parameters
*/
async trackCompletePurchase(
value: number,
currency: string,
contents: Array<{
content_id: string;
content_type?: string;
content_name?: string;
quantity?: number;
price?: number;
}>,
additionalParams: TiktokEventParams = {}
): Promise<boolean> {
return this.trackEvent(TiktokEventName.COMPLETE_PAYMENT, {
value,
currency,
contents,
...additionalParams,
});
}
/**
* Clean up resources when the module is no longer needed
*/
cleanup(): void {
if (this._appStateSubscription) {
this._appStateSubscription.remove();
this._appStateSubscription = null;
}
}
/**
* Set up app state listener for automatic event tracking
*/
private _setupAppStateListener(): void {
// Clean up existing listener if any
if (this._appStateSubscription) {
this._appStateSubscription.remove();
}
// Set up new listener
this._appStateSubscription = AppState.addEventListener(
'change',
this._handleAppStateChange
);
}
/**
* Handle app state changes for automatic event tracking
*/
private _handleAppStateChange = (nextAppState: AppStateStatus) => {
if (nextAppState === 'active') {
// App came to foreground - could track a custom event here if needed
// We don't track Launch here as it's handled during initialization
}
};
}
// Export the singleton instance
export default new TiktokSDK();