@slvssb/react-native-tiktok-business 0.1.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/.eslintrc.js ADDED
@@ -0,0 +1,5 @@
1
+ module.exports = {
2
+ root: true,
3
+ extends: ['universe/native', 'universe/web'],
4
+ ignorePatterns: ['build'],
5
+ };
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Selva
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # React Native TikTok Business
2
+
3
+ TikTok Business SDK for React Native.
4
+ In debug mode, events should appear in the "Test Event" tab.
5
+
6
+ ## Installation
7
+
8
+ ```bash
9
+ npm install @slvssb/react-native-tiktok-business
10
+ yarn add @slvssb/react-native-tiktok-business
11
+ bun add @slvssb/react-native-tiktok-business
12
+ ```
13
+
14
+ ## Expo config plugin:
15
+
16
+ For Expo apps, you'll need to add both the plugin configuration and the required iOS privacy permission:
17
+
18
+ ```json
19
+ {
20
+ "expo": {
21
+ "plugins": [
22
+ [
23
+ "@slvssb/react-native-tiktok-business",
24
+ {
25
+ "ios": {
26
+ "appId": "YOUR_APP_ID",
27
+ "tiktokAppId": "YOUR_TIKTOK_APP_ID",
28
+ "disableAppTrackingDialog": true // Optional, defaults to false
29
+ },
30
+ "android": {
31
+ "appId": "YOUR_APP_ID",
32
+ "tiktokAppId": "YOUR_TIKTOK_APP_ID"
33
+ }
34
+ }
35
+ ]
36
+ ],
37
+ "ios": {
38
+ "infoPlist": {
39
+ "NSUserTrackingUsageDescription": "This identifier will be used to provide a more personalized experience for you."
40
+ }
41
+ }
42
+ }
43
+ }
44
+ ```
45
+
46
+ The `NSUserTrackingUsageDescription` is required for iOS devices to request tracking authorization. Without this permission string, the tracking dialog will not be shown to users.
47
+
48
+ ## Usage
49
+
50
+ ```typescript
51
+ import TiktokBusiness from "@slvssb/react-native-tiktok-business"
52
+
53
+ // Identify a user
54
+ TiktokBusiness.identify("user-external-id", "username", "phone", "email")
55
+
56
+ // Clear user identification
57
+ TiktokBusiness.logout()
58
+
59
+ // Request tracking authorization (iOS only)
60
+ const status = await TiktokBusiness.requestTrackingAuthorization()
61
+
62
+ // Check if debug mode is enabled
63
+ const isDebug = TiktokBusiness.isDebugMode()
64
+
65
+ // Manually flush events (iOS only)
66
+ TiktokBusiness.flush()
67
+
68
+ // Track a simple event
69
+ TiktokBusiness.trackEvent("purchase")
70
+
71
+ // Track event with custom data
72
+ TiktokBusiness.trackEvent("purchase_completed", "order-123", [
73
+ { key: "currency", value: "USD" },
74
+ { key: "value", value: "99.99" },
75
+ { key: "quantity", value: "1" },
76
+ { key: "price", value: "99.99" },
77
+ ])
78
+ ```
@@ -0,0 +1,47 @@
1
+ apply plugin: 'com.android.library'
2
+
3
+ group = 'expo.modules.tiktokbusiness'
4
+ version = '0.1.0'
5
+
6
+ def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
7
+ apply from: expoModulesCorePlugin
8
+ applyKotlinExpoModulesCorePlugin()
9
+ useCoreDependencies()
10
+ useExpoPublishing()
11
+
12
+ // If you want to use the managed Android SDK versions from expo-modules-core, set this to true.
13
+ // The Android SDK versions will be bumped from time to time in SDK releases and may introduce breaking changes in your module code.
14
+ // Most of the time, you may like to manage the Android SDK versions yourself.
15
+ def useManagedAndroidSdkVersions = false
16
+ if (useManagedAndroidSdkVersions) {
17
+ useDefaultAndroidSdkVersions()
18
+ } else {
19
+ buildscript {
20
+ // Simple helper that allows the root project to override versions declared by this library.
21
+ ext.safeExtGet = { prop, fallback ->
22
+ rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
23
+ }
24
+ }
25
+ project.android {
26
+ compileSdkVersion safeExtGet("compileSdkVersion", 34)
27
+ defaultConfig {
28
+ minSdkVersion safeExtGet("minSdkVersion", 21)
29
+ targetSdkVersion safeExtGet("targetSdkVersion", 34)
30
+ }
31
+ }
32
+ }
33
+
34
+ android {
35
+ namespace "expo.modules.tiktokbusiness"
36
+ defaultConfig {
37
+ versionCode 1
38
+ versionName "0.1.0"
39
+ }
40
+ lintOptions {
41
+ abortOnError false
42
+ }
43
+ }
44
+
45
+ dependencies {
46
+ implementation 'com.github.tiktok:tiktok-business-android-sdk:1.3.7'
47
+ }
@@ -0,0 +1,5 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
+ xmlns:tools="http://schemas.android.com/tools">
3
+ <uses-permission android:name="android.permission.INTERNET" />
4
+ <uses-permission android:name="com.google.android.gms.permission.AD_ID" />
5
+ </manifest>
@@ -0,0 +1,54 @@
1
+ package expo.modules.tiktokbusiness
2
+
3
+ import android.app.Application
4
+ import expo.modules.core.interfaces.ApplicationLifecycleListener
5
+ import com.tiktok.TikTokBusinessSdk;
6
+ import android.content.pm.PackageManager
7
+
8
+ class TiktokBusinessApplicationLifecycleListener : ApplicationLifecycleListener {
9
+
10
+ override fun onCreate(application: Application) {
11
+ val appId = getAppId(application)
12
+ val ttAppId = getTikTokAppId(application)
13
+
14
+ val ttConfig = TikTokBusinessSdk.TTConfig(application.applicationContext)
15
+ .setAppId(appId)
16
+ .setTTAppId(ttAppId)
17
+
18
+ if (BuildConfig.DEBUG) {
19
+ ttConfig.openDebugMode()
20
+ ttConfig.setLogLevel(TikTokBusinessSdk.LogLevel.DEBUG)
21
+ }
22
+
23
+ TikTokBusinessSdk.initializeSdk(ttConfig, object : TikTokBusinessSdk.TTInitCallback {
24
+ override fun success() {
25
+ if (BuildConfig.DEBUG) {
26
+ println("TikTokBusinessSdk initialized")
27
+ }
28
+ }
29
+
30
+ override fun fail(code: Int, msg: String) {
31
+ if (BuildConfig.DEBUG) {
32
+ println("TikTokBusinessSdk initialization failed")
33
+ }
34
+ }
35
+ })
36
+ TikTokBusinessSdk.startTrack()
37
+ }
38
+
39
+ private fun getAppId(application: Application): String? {
40
+ val applicationInfo = application.packageManager.getApplicationInfo(
41
+ application.packageName,
42
+ PackageManager.GET_META_DATA
43
+ )
44
+ return applicationInfo?.metaData?.getString("TikTokBusinessAppId")
45
+ }
46
+
47
+ private fun getTikTokAppId(application: Application): String? {
48
+ val applicationInfo = application.packageManager.getApplicationInfo(
49
+ application.packageName,
50
+ PackageManager.GET_META_DATA
51
+ )
52
+ return applicationInfo?.metaData?.getString("TikTokBusinessTiktokAppId")
53
+ }
54
+ }
@@ -0,0 +1,65 @@
1
+ package expo.modules.tiktokbusiness
2
+
3
+ import expo.modules.kotlin.modules.Module
4
+ import expo.modules.kotlin.modules.ModuleDefinition
5
+ import com.tiktok.TikTokBusinessSdk;
6
+ import com.tiktok.appevents.base.TTBaseEvent
7
+ import expo.modules.kotlin.Promise
8
+
9
+
10
+ class TiktokBusinessModule : Module() {
11
+ // Each module class must implement the definition function. The definition consists of components
12
+ // that describes the module's functionality and behavior.
13
+ // See https://docs.expo.dev/modules/module-api for more details about available components.
14
+ override fun definition() = ModuleDefinition {
15
+ // Sets the name of the module that JavaScript code will use to refer to the module. Takes a string as an argument.
16
+ // Can be inferred from module's class name, but it's recommended to set it explicitly for clarity.
17
+ // The module will be accessible from `requireNativeModule('ReactNativeTiktokBusiness')` in JavaScript.
18
+ Name("ReactNativeTiktokBusiness")
19
+
20
+
21
+ // TODO: Think about if it would be better as an AsyncFunction to prevent blocking the js thread https://docs.expo.dev/modules/module-api/#function
22
+ Function("identify") { externalId: String, externalUsername: String?, phoneNumber: String?, email: String? ->
23
+ return@Function TikTokBusinessSdk.identify(externalId, externalUsername, phoneNumber, email)
24
+ }
25
+
26
+ Function("isDebugMode") {
27
+ return@Function TikTokBusinessSdk.isInSdkDebugMode()
28
+ }
29
+
30
+ Function("logout") {
31
+ return@Function TikTokBusinessSdk.logout()
32
+ }
33
+
34
+ AsyncFunction("requestTrackingAuthorization") { promise: Promise ->
35
+ promise.resolve(null)
36
+ }
37
+
38
+ Function("trackEvent") { eventName: String, eventId: String?, eventData: List<Map<String, String>>? ->
39
+ val customEvent = TTBaseEvent.newBuilder(eventName, eventId)
40
+
41
+ eventData?.forEach { item ->
42
+ when (item["key"]) {
43
+ "price", "value" -> {
44
+ item["value"]?.toDoubleOrNull()?.let { numericValue ->
45
+ customEvent.addProperty(item["key"], numericValue)
46
+ }
47
+ }
48
+ "quantity" -> {
49
+ item["value"]?.toIntOrNull()?.let { intValue ->
50
+ customEvent.addProperty(item["key"], intValue)
51
+ }
52
+ }
53
+ else -> {
54
+ item["value"]?.let { value ->
55
+ customEvent.addProperty(item["key"], value)
56
+ }
57
+ }
58
+ }
59
+ }
60
+
61
+ TikTokBusinessSdk.trackTTEvent(customEvent.build())
62
+ }
63
+
64
+ }
65
+ }
@@ -0,0 +1,11 @@
1
+ package expo.modules.tiktokbusiness
2
+
3
+ import android.content.Context
4
+ import expo.modules.core.interfaces.ApplicationLifecycleListener
5
+ import expo.modules.core.interfaces.Package
6
+
7
+ class TiktokBusinessPackage : Package {
8
+ override fun createApplicationLifecycleListeners(context: Context): List<ApplicationLifecycleListener> {
9
+ return listOf(TiktokBusinessApplicationLifecycleListener())
10
+ }
11
+ }
package/app.plugin.js ADDED
@@ -0,0 +1 @@
1
+ module.exports = require("./plugin/build");
@@ -0,0 +1,34 @@
1
+ import { NativeModule } from "expo";
2
+ export declare enum TikTokTrackingAuthorizationStatus {
3
+ NotDetermined = 0,
4
+ Restricted = 1,
5
+ Denied = 2,
6
+ Authorized = 3
7
+ }
8
+ interface TiktokEventData {
9
+ key: string;
10
+ value: string | number;
11
+ }
12
+ declare class ReactNativeTiktokBusinessModule extends NativeModule<{}> {
13
+ /**
14
+ * Identifies the user with the given external ID, external username, phone number, and email.
15
+ */
16
+ identify(externalID: string, externalUserName?: string, phoneNumber?: string, email?: string): void;
17
+ logout(): void;
18
+ /**
19
+ * Returns whether the SDK is in debug mode.
20
+ */
21
+ isDebugMode(): boolean;
22
+ /**
23
+ * Track an event with the given name, ID, and data.
24
+ */
25
+ trackEvent(eventName: string, eventId?: string, eventData?: TiktokEventData[]): void;
26
+ /**
27
+ * Displays a dialog to the user to request tracking authorization.
28
+ * Does nothing and returns null on Android.
29
+ */
30
+ requestTrackingAuthorization(): Promise<TikTokTrackingAuthorizationStatus | null>;
31
+ }
32
+ declare const _default: ReactNativeTiktokBusinessModule;
33
+ export default _default;
34
+ //# sourceMappingURL=ReactNativeTiktokBusinessModule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReactNativeTiktokBusinessModule.d.ts","sourceRoot":"","sources":["../src/ReactNativeTiktokBusinessModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,MAAM,CAAC;AAEzD,oBAAY,iCAAiC;IAC3C,aAAa,IAAI;IACjB,UAAU,IAAI;IACd,MAAM,IAAI;IACV,UAAU,IAAI;CACf;AAED,UAAU,eAAe;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;CACxB;AAED,OAAO,OAAO,+BAAgC,SAAQ,YAAY,CAAC,EAAE,CAAC;IACpE;;OAEG;IACH,QAAQ,CACN,UAAU,EAAE,MAAM,EAClB,gBAAgB,CAAC,EAAE,MAAM,EACzB,WAAW,CAAC,EAAE,MAAM,EACpB,KAAK,CAAC,EAAE,MAAM,GACb,IAAI;IACP,MAAM,IAAI,IAAI;IACd;;OAEG;IACH,WAAW,IAAI,OAAO;IACtB;;OAEG;IACH,UAAU,CACR,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,MAAM,EAChB,SAAS,CAAC,EAAE,eAAe,EAAE,GAC5B,IAAI;IACP;;;OAGG;IACH,4BAA4B,IAAI,OAAO,CAAC,iCAAiC,GAAG,IAAI,CAAC;CAClF;;AAGD,wBAEE"}
@@ -0,0 +1,11 @@
1
+ import { requireNativeModule } from "expo";
2
+ export var TikTokTrackingAuthorizationStatus;
3
+ (function (TikTokTrackingAuthorizationStatus) {
4
+ TikTokTrackingAuthorizationStatus[TikTokTrackingAuthorizationStatus["NotDetermined"] = 0] = "NotDetermined";
5
+ TikTokTrackingAuthorizationStatus[TikTokTrackingAuthorizationStatus["Restricted"] = 1] = "Restricted";
6
+ TikTokTrackingAuthorizationStatus[TikTokTrackingAuthorizationStatus["Denied"] = 2] = "Denied";
7
+ TikTokTrackingAuthorizationStatus[TikTokTrackingAuthorizationStatus["Authorized"] = 3] = "Authorized";
8
+ })(TikTokTrackingAuthorizationStatus || (TikTokTrackingAuthorizationStatus = {}));
9
+ // This call loads the native module object from the JSI.
10
+ export default requireNativeModule("ReactNativeTiktokBusiness");
11
+ //# sourceMappingURL=ReactNativeTiktokBusinessModule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReactNativeTiktokBusinessModule.js","sourceRoot":"","sources":["../src/ReactNativeTiktokBusinessModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAEzD,MAAM,CAAN,IAAY,iCAKX;AALD,WAAY,iCAAiC;IAC3C,2GAAiB,CAAA;IACjB,qGAAc,CAAA;IACd,6FAAU,CAAA;IACV,qGAAc,CAAA;AAChB,CAAC,EALW,iCAAiC,KAAjC,iCAAiC,QAK5C;AAqCD,yDAAyD;AACzD,eAAe,mBAAmB,CAChC,2BAA2B,CAC5B,CAAC","sourcesContent":["import { NativeModule, requireNativeModule } from \"expo\";\n\nexport enum TikTokTrackingAuthorizationStatus {\n NotDetermined = 0,\n Restricted = 1,\n Denied = 2,\n Authorized = 3,\n}\n\ninterface TiktokEventData {\n key: string;\n value: string | number;\n}\n\ndeclare class ReactNativeTiktokBusinessModule extends NativeModule<{}> {\n /**\n * Identifies the user with the given external ID, external username, phone number, and email.\n */\n identify(\n externalID: string,\n externalUserName?: string,\n phoneNumber?: string,\n email?: string\n ): void;\n logout(): void;\n /**\n * Returns whether the SDK is in debug mode.\n */\n isDebugMode(): boolean;\n /**\n * Track an event with the given name, ID, and data.\n */\n trackEvent(\n eventName: string,\n eventId?: string,\n eventData?: TiktokEventData[]\n ): void;\n /**\n * Displays a dialog to the user to request tracking authorization.\n * Does nothing and returns null on Android.\n */\n requestTrackingAuthorization(): Promise<TikTokTrackingAuthorizationStatus | null>;\n}\n\n// This call loads the native module object from the JSI.\nexport default requireNativeModule<ReactNativeTiktokBusinessModule>(\n \"ReactNativeTiktokBusiness\"\n);\n"]}
@@ -0,0 +1,2 @@
1
+ export { default, TikTokTrackingAuthorizationStatus, } from "./ReactNativeTiktokBusinessModule";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,OAAO,EACP,iCAAiC,GAClC,MAAM,mCAAmC,CAAC"}
package/build/index.js ADDED
@@ -0,0 +1,4 @@
1
+ // Reexport the native module. On web, it will be resolved to ReactNativeTiktokBusinessModule.web.ts
2
+ // and on native platforms to ReactNativeTiktokBusinessModule.ts
3
+ export { default, TikTokTrackingAuthorizationStatus, } from "./ReactNativeTiktokBusinessModule";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,oGAAoG;AACpG,gEAAgE;AAChE,OAAO,EACL,OAAO,EACP,iCAAiC,GAClC,MAAM,mCAAmC,CAAC","sourcesContent":["// Reexport the native module. On web, it will be resolved to ReactNativeTiktokBusinessModule.web.ts\n// and on native platforms to ReactNativeTiktokBusinessModule.ts\nexport {\n default,\n TikTokTrackingAuthorizationStatus,\n} from \"./ReactNativeTiktokBusinessModule\";\n"]}
package/bun.lockb ADDED
Binary file
@@ -0,0 +1,10 @@
1
+ {
2
+ "platforms": ["apple", "android"],
3
+ "apple": {
4
+ "modules": ["TiktokBusinessModule"],
5
+ "appDelegateSubscribers": ["TiktokBusinessAppDelegateSubscriber"]
6
+ },
7
+ "android": {
8
+ "modules": ["expo.modules.tiktokbusiness.TiktokBusinessModule"]
9
+ }
10
+ }
@@ -0,0 +1,30 @@
1
+ require 'json'
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = 'TiktokBusiness'
7
+ s.version = package['version']
8
+ s.summary = package['description']
9
+ s.description = package['description']
10
+ s.license = package['license']
11
+ s.author = package['author']
12
+ s.homepage = package['homepage']
13
+ s.platforms = {
14
+ :ios => '15.1',
15
+ :tvos => '15.1'
16
+ }
17
+ s.swift_version = '5.4'
18
+ s.source = { git: 'https://github.com/slvssb/react-native-tiktok-business' }
19
+ s.static_framework = true
20
+
21
+ s.dependency 'ExpoModulesCore'
22
+ s.dependency 'TikTokBusinessSDK'
23
+
24
+ # Swift/Objective-C compatibility
25
+ s.pod_target_xcconfig = {
26
+ 'DEFINES_MODULE' => 'YES',
27
+ }
28
+
29
+ s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}"
30
+ end
@@ -0,0 +1,111 @@
1
+ import ExpoModulesCore
2
+ import TikTokBusinessSDK
3
+
4
+ struct TiktokEventData: Record {
5
+ @Field
6
+ var key: String
7
+
8
+ @Field
9
+ var value: String
10
+ }
11
+
12
+ public class TiktokBusinessModule: Module {
13
+ public func definition() -> ModuleDefinition {
14
+ Name("ReactNativeTiktokBusiness")
15
+
16
+ Function("identify") { (externalID: String, externalUserName: String?, phoneNumber: String?, email: String?) in
17
+ TikTokBusiness.identify(withExternalID: externalID, externalUserName: externalUserName, phoneNumber: phoneNumber, email: email)
18
+ }
19
+
20
+ Function("logout") {
21
+ TikTokBusiness.logout()
22
+ }
23
+
24
+ Function("isDebugMode") {
25
+ return TikTokBusiness.isDebugMode()
26
+ }
27
+
28
+ Function("trackEvent") { (eventName: String, eventId: String?, eventData: [TiktokEventData]?) in
29
+ let customEvent = TikTokBaseEvent(eventName: eventName, eventId: eventId)
30
+
31
+ if let data = eventData {
32
+ for item in data {
33
+ if item.key == "price" || item.key == "value", let numericValue = Double(item.value) {
34
+ customEvent.addProperty(withKey: item.key, value: numericValue)
35
+ } else if item.key == "quantity", let intValue = Int(item.value) {
36
+ customEvent.addProperty(withKey: item.key, value: intValue)
37
+ } else {
38
+ customEvent.addProperty(withKey: item.key, value: item.value)
39
+ }
40
+ }
41
+ }
42
+
43
+ TikTokBusiness.trackTTEvent(customEvent)
44
+ }
45
+
46
+ Function("flush") {
47
+ TikTokBusiness.explicitlyFlush()
48
+ }
49
+
50
+ AsyncFunction("requestTrackingAuthorization") { (promise: Promise) in
51
+ TikTokBusiness.requestTrackingAuthorization(completionHandler: { (status) in
52
+ promise.resolve(status)
53
+ })
54
+ }
55
+ }
56
+ }
57
+
58
+ public class TiktokBusinessAppDelegateSubscriber: ExpoAppDelegateSubscriber {
59
+ public func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
60
+
61
+ print("[TiktokBusiness] Initializing TikTokBusiness")
62
+ let ttConfig = getTikTokConfig()
63
+ let config = TikTokConfig.init(appId: ttConfig.appId, tiktokAppId: ttConfig.tiktokAppId)
64
+
65
+ #if DEBUG
66
+ config?.enableDebugMode()
67
+ config?.setLogLevel(TikTokLogLevelVerbose)
68
+ #endif
69
+
70
+ if (ttConfig.disableTrackingDialog) {
71
+ if (ttConfig.debug) {
72
+ print("[TiktokBusiness] Disabling app tracking dialog")
73
+ }
74
+ config?.disableAppTrackingDialog()
75
+ }
76
+
77
+ if (ttConfig.disablePaymentTracking) {
78
+ if (ttConfig.debug) {
79
+ print("[TiktokBusiness] Disabling payment tracking")
80
+ }
81
+ config?.disablePaymentTracking()
82
+ }
83
+
84
+ TikTokBusiness.initializeSdk(config) { success, error in
85
+ if (!success) { // initialization failed
86
+ if (ttConfig.debug) {
87
+ print(error!.localizedDescription)
88
+ }
89
+ } else { // initialization successful
90
+ if (ttConfig.debug) {
91
+ print("[TiktokBusiness] TikTokBusiness initialized")
92
+ }
93
+ }
94
+ }
95
+
96
+ return true
97
+ }
98
+
99
+ private func getTikTokConfig() -> (appId: String, tiktokAppId: String, disableTrackingDialog: Bool, debug: Bool, disablePaymentTracking: Bool) {
100
+ guard let infoPlist = Bundle.main.infoDictionary,
101
+ let appId = infoPlist["TikTokBusinessAppId"] as? String,
102
+ let tiktokAppId = infoPlist["TikTokBusinessTiktokAppId"] as? String else {
103
+ return ("", "", false, false, false)
104
+ }
105
+
106
+ let disableTrackingDialog = infoPlist["TikTokBusinessDisableAppTrackingDialog"] as? Bool ?? false
107
+ let debug = infoPlist["TikTokBusinessDebug"] as? Bool ?? false
108
+ let disablePaymentTracking = infoPlist["TikTokBusinessDisablePaymentTracking"] as? Bool ?? false
109
+ return (appId, tiktokAppId, disableTrackingDialog, debug, disablePaymentTracking)
110
+ }
111
+ }
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@slvssb/react-native-tiktok-business",
3
+ "version": "0.1.0",
4
+ "description": "React native module to integrate TikTok business SDK",
5
+ "main": "build/index.js",
6
+ "types": "build/index.d.ts",
7
+ "scripts": {
8
+ "build": "expo-module build",
9
+ "clean": "expo-module clean",
10
+ "lint": "expo-module lint",
11
+ "test": "expo-module test",
12
+ "prepare": "expo-module prepare",
13
+ "prepublishOnly": "expo-module prepublishOnly",
14
+ "expo-module": "expo-module",
15
+ "open:ios": "xed example/ios",
16
+ "open:android": "open -a \"Android Studio\" example/android",
17
+ "plugin:build": "bun run build plugin"
18
+ },
19
+ "keywords": [
20
+ "react-native",
21
+ "expo",
22
+ "react-native-tiktok-business",
23
+ "@slvssb/react-native-tiktok-business",
24
+ "ReactNativeTiktokBusiness"
25
+ ],
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/slvssb/react-native-tiktok-business.git"
29
+ },
30
+ "bugs": {
31
+ "url": "https://github.com/slvssb/react-native-tiktok-business/issues"
32
+ },
33
+ "author": "Selva <slvssb08@gmail.com> (https://github.com/slvssb)",
34
+ "license": "MIT",
35
+ "homepage": "https://github.com/slvssb/react-native-tiktok-business#readme",
36
+ "devDependencies": {
37
+ "@types/react": "~18.3.12",
38
+ "expo-module-scripts": "^4.0.2",
39
+ "expo": "~52.0.0",
40
+ "react-native": "0.76.0"
41
+ },
42
+ "peerDependencies": {
43
+ "expo": "*",
44
+ "react": "*",
45
+ "react-native": "*"
46
+ }
47
+ }
@@ -0,0 +1,69 @@
1
+ import {
2
+ AndroidConfig,
3
+ ConfigPlugin,
4
+ withAndroidManifest,
5
+ withInfoPlist,
6
+ } from "expo/config-plugins";
7
+ import { withAndroidProguardRules } from "./withAndroidProguardRules";
8
+
9
+ interface PluginConfigType {
10
+ ios?: {
11
+ appId: string;
12
+ tiktokAppId: string;
13
+ disableAppTrackingDialog?: boolean;
14
+ disablePaymentTracking?: boolean;
15
+ };
16
+ android?: {
17
+ appId: string;
18
+ tiktokAppId: string;
19
+ enableAutoIapTracking?: boolean;
20
+ };
21
+ }
22
+
23
+ const withTiktokBusiness: ConfigPlugin<PluginConfigType> = (
24
+ config,
25
+ { ios, android }
26
+ ) => {
27
+ if (ios) {
28
+ config = withInfoPlist(config, (config) => {
29
+ config.modResults["TikTokBusinessAppId"] = ios.appId;
30
+ config.modResults["TikTokBusinessTiktokAppId"] = ios.tiktokAppId;
31
+ config.modResults["TikTokBusinessDisableAppTrackingDialog"] =
32
+ ios.disableAppTrackingDialog;
33
+ config.modResults["TikTokBusinessDisablePaymentTracking"] =
34
+ ios.disablePaymentTracking;
35
+ return config;
36
+ });
37
+ }
38
+
39
+ if (android) {
40
+ config = withAndroidManifest(config, (config) => {
41
+ const mainApplication = AndroidConfig.Manifest.getMainApplicationOrThrow(
42
+ config.modResults
43
+ );
44
+
45
+ AndroidConfig.Manifest.addMetaDataItemToMainApplication(
46
+ mainApplication,
47
+ "TikTokBusinessAppId",
48
+ android.appId
49
+ );
50
+ AndroidConfig.Manifest.addMetaDataItemToMainApplication(
51
+ mainApplication,
52
+ "TikTokBusinessTiktokAppId",
53
+ android.tiktokAppId
54
+ );
55
+ AndroidConfig.Manifest.addMetaDataItemToMainApplication(
56
+ mainApplication,
57
+ "TikTokBusinessEnableAutoIapTracking",
58
+ android.enableAutoIapTracking ? "true" : "false"
59
+ );
60
+ return config;
61
+ });
62
+ }
63
+
64
+ config = withAndroidProguardRules(config);
65
+
66
+ return config;
67
+ };
68
+
69
+ export default withTiktokBusiness;
@@ -0,0 +1,101 @@
1
+ /**
2
+ * A set of helper functions to update file contents. This is a simplified version to the config-plugins internal generateCode functions.
3
+ * Source: https://github.com/expo/expo/blob/main/packages/expo-build-properties/src/fileContentsUtils.ts
4
+ */
5
+
6
+ import assert from "assert";
7
+
8
+ /**
9
+ * Options for a generated section
10
+ */
11
+ export interface SectionOptions {
12
+ /**
13
+ * A meaningful tag to differentiate the generated section
14
+ */
15
+ tag: string;
16
+
17
+ /**
18
+ * Defines comment for the generated code.
19
+ * E.g. '//' for js code or '#' for shell script
20
+ */
21
+ commentPrefix: string;
22
+ }
23
+
24
+ /**
25
+ * Append new contents to src with generated section comments
26
+ *
27
+ * If there is already a generated section, this function will append the new contents at the end of the section.
28
+ * Otherwise, this function will generate a new section at the end of file.
29
+ */
30
+ export function appendContents(
31
+ src: string,
32
+ contents: string,
33
+ sectionOptions: SectionOptions
34
+ ) {
35
+ const start = createSectionComment(
36
+ sectionOptions.commentPrefix,
37
+ sectionOptions.tag,
38
+ true
39
+ );
40
+ const end = createSectionComment(
41
+ sectionOptions.commentPrefix,
42
+ sectionOptions.tag,
43
+ false
44
+ );
45
+
46
+ const regex = new RegExp(`(\\n${escapeRegExp(end)})`, "gm");
47
+ const match = regex.exec(src);
48
+ if (match) {
49
+ assert(src.indexOf(start) >= 0);
50
+ return src.replace(regex, `\n${contents}$1`);
51
+ } else {
52
+ const sectionedContents = `${start}\n${contents}\n${end}`;
53
+ return `${src}\n${sectionedContents}`;
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Purge a generated section
59
+ */
60
+ export function purgeContents(src: string, sectionOptions: SectionOptions) {
61
+ const start = createSectionComment(
62
+ sectionOptions.commentPrefix,
63
+ sectionOptions.tag,
64
+ true
65
+ );
66
+ const end = createSectionComment(
67
+ sectionOptions.commentPrefix,
68
+ sectionOptions.tag,
69
+ false
70
+ );
71
+
72
+ const regex = new RegExp(
73
+ `\\n${escapeRegExp(start)}\\n[\\s\\S]*\\n${escapeRegExp(end)}`,
74
+ "gm"
75
+ );
76
+ return src.replace(regex, "");
77
+ }
78
+
79
+ /**
80
+ * Create comments for generated section
81
+ */
82
+ function createSectionComment(
83
+ commentPrefix: string,
84
+ tag: string,
85
+ isBeginComment: boolean
86
+ ) {
87
+ if (isBeginComment) {
88
+ return `${commentPrefix} @generated begin ${tag} - expo prebuild (DO NOT MODIFY)`;
89
+ } else {
90
+ return `${commentPrefix} @generated end ${tag}`;
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Escape a string for regular expression
96
+ *
97
+ * Reference from {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping}
98
+ */
99
+ function escapeRegExp(input: string) {
100
+ return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
101
+ }
@@ -0,0 +1,64 @@
1
+ import { ConfigPlugin } from "expo/config-plugins";
2
+ import { withDangerousMod } from "expo/config-plugins";
3
+ import path from "path";
4
+ import fs from "fs";
5
+ import { appendContents, purgeContents } from "./utils";
6
+
7
+ /**
8
+ * Appends `props.android.extraProguardRules` content into `android/app/proguard-rules.pro`
9
+ * Source: https://github.com/expo/expo/blob/main/packages/expo-build-properties/src/android.ts
10
+ */
11
+ export const withAndroidProguardRules: ConfigPlugin = (config) => {
12
+ return withDangerousMod(config, [
13
+ "android",
14
+ async (config) => {
15
+ const extraProguardRules = `
16
+ -keep class com.tiktok.** { *; }
17
+ -keep class com.android.billingclient.api.** { *; }
18
+ `;
19
+ const proguardRulesFile = path.join(
20
+ config.modRequest.platformProjectRoot,
21
+ "app",
22
+ "proguard-rules.pro"
23
+ );
24
+
25
+ const contents = await fs.promises.readFile(proguardRulesFile, "utf8");
26
+ const newContents = updateAndroidProguardRules(
27
+ contents,
28
+ extraProguardRules,
29
+ "append"
30
+ );
31
+ if (contents !== newContents) {
32
+ await fs.promises.writeFile(proguardRulesFile, newContents);
33
+ }
34
+ return config;
35
+ },
36
+ ]);
37
+ };
38
+
39
+ /**
40
+ * Update `newProguardRules` to original `proguard-rules.pro` contents if needed
41
+ *
42
+ * @param contents the original `proguard-rules.pro` contents
43
+ * @param newProguardRules new proguard rules to add. If the value is null, the returned value will be original `contents`.
44
+ * @returns return updated contents
45
+ */
46
+ function updateAndroidProguardRules(
47
+ contents: string,
48
+ newProguardRules: string | null,
49
+ updateMode: "append" | "overwrite"
50
+ ): string {
51
+ if (newProguardRules == null) {
52
+ return contents;
53
+ }
54
+
55
+ const options = { tag: "expo-build-properties", commentPrefix: "#" };
56
+ let newContents = contents;
57
+ if (updateMode === "overwrite") {
58
+ newContents = purgeContents(contents, options);
59
+ }
60
+ if (newProguardRules !== "") {
61
+ newContents = appendContents(newContents, newProguardRules, options);
62
+ }
63
+ return newContents;
64
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "expo-module-scripts/tsconfig.plugin",
3
+ "compilerOptions": {
4
+ "outDir": "build",
5
+ "rootDir": "src"
6
+ },
7
+ "include": ["./src"],
8
+ "exclude": ["**/__mocks__/*", "**/__tests__/*"]
9
+ }
@@ -0,0 +1 @@
1
+ {"root":["./src/index.ts","./src/utils.ts","./src/withandroidproguardrules.ts"],"version":"5.7.2"}
@@ -0,0 +1,48 @@
1
+ import { NativeModule, requireNativeModule } from "expo";
2
+
3
+ export enum TikTokTrackingAuthorizationStatus {
4
+ NotDetermined = 0,
5
+ Restricted = 1,
6
+ Denied = 2,
7
+ Authorized = 3,
8
+ }
9
+
10
+ interface TiktokEventData {
11
+ key: string;
12
+ value: string | number;
13
+ }
14
+
15
+ declare class ReactNativeTiktokBusinessModule extends NativeModule<{}> {
16
+ /**
17
+ * Identifies the user with the given external ID, external username, phone number, and email.
18
+ */
19
+ identify(
20
+ externalID: string,
21
+ externalUserName?: string,
22
+ phoneNumber?: string,
23
+ email?: string
24
+ ): void;
25
+ logout(): void;
26
+ /**
27
+ * Returns whether the SDK is in debug mode.
28
+ */
29
+ isDebugMode(): boolean;
30
+ /**
31
+ * Track an event with the given name, ID, and data.
32
+ */
33
+ trackEvent(
34
+ eventName: string,
35
+ eventId?: string,
36
+ eventData?: TiktokEventData[]
37
+ ): void;
38
+ /**
39
+ * Displays a dialog to the user to request tracking authorization.
40
+ * Does nothing and returns null on Android.
41
+ */
42
+ requestTrackingAuthorization(): Promise<TikTokTrackingAuthorizationStatus | null>;
43
+ }
44
+
45
+ // This call loads the native module object from the JSI.
46
+ export default requireNativeModule<ReactNativeTiktokBusinessModule>(
47
+ "ReactNativeTiktokBusiness"
48
+ );
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ // Reexport the native module. On web, it will be resolved to ReactNativeTiktokBusinessModule.web.ts
2
+ // and on native platforms to ReactNativeTiktokBusinessModule.ts
3
+ export {
4
+ default,
5
+ TikTokTrackingAuthorizationStatus,
6
+ } from "./ReactNativeTiktokBusinessModule";
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ // @generated by expo-module-scripts
2
+ {
3
+ "extends": "expo-module-scripts/tsconfig.base",
4
+ "compilerOptions": {
5
+ "outDir": "./build"
6
+ },
7
+ "include": ["./src"],
8
+ "exclude": ["**/__mocks__/*", "**/__tests__/*", "**/__rsc_tests__/*"]
9
+ }