react-native-edgee 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.
@@ -0,0 +1,369 @@
1
+ package com.reactnativeedgee
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.app.Activity
5
+ import android.content.Context
6
+ import android.content.Intent
7
+ import android.content.pm.PackageInfo
8
+ import android.content.res.Resources
9
+ import android.media.MediaDrm
10
+ import android.net.ConnectivityManager
11
+ import android.net.NetworkCapabilities
12
+ import android.net.Uri
13
+ import android.os.Build
14
+ import android.provider.Settings
15
+ import android.telephony.TelephonyManager
16
+ import android.util.Log
17
+ import android.webkit.WebSettings
18
+ import androidx.core.content.pm.PackageInfoCompat
19
+ import com.facebook.react.bridge.*
20
+ import com.facebook.react.module.annotations.ReactModule
21
+ import java.lang.Exception
22
+ import java.security.MessageDigest
23
+ import java.util.*
24
+
25
+ enum class ConnectionType {
26
+ Cellular, Unknown, Wifi, Ethernet
27
+ }
28
+
29
+ @ReactModule(name = "EdgeeReactNative")
30
+ class EdgeeReactNativeModule(reactContext: ReactApplicationContext) :
31
+ ReactContextBaseJavaModule(reactContext), ActivityEventListener, LifecycleEventListener {
32
+
33
+ private val pInfo: PackageInfo = reactContext.packageManager.getPackageInfo(reactContext.packageName, 0)
34
+ private var isColdLaunch = true
35
+
36
+ init {
37
+ reactContext.addActivityEventListener(this)
38
+ reactContext.addLifecycleEventListener(this)
39
+ }
40
+
41
+ override fun getName(): String {
42
+ return "EdgeeReactNative"
43
+ }
44
+
45
+ private fun getBuildNumber(): String {
46
+ return PackageInfoCompat.getLongVersionCode(pInfo).toString()
47
+ }
48
+
49
+ private fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) }
50
+
51
+ /**
52
+ * Get unique device identifier using MediaDRM API (privacy-compliant)
53
+ */
54
+ private fun getUniqueDeviceId(): String? {
55
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
56
+ return null
57
+ }
58
+
59
+ val WIDEVINE_UUID = UUID(-0x121074568629b532L, -0x5c37d8232ae2de13L)
60
+ var wvDrm: MediaDrm? = null
61
+ return try {
62
+ wvDrm = MediaDrm(WIDEVINE_UUID)
63
+ val wideVineId = wvDrm.getPropertyByteArray(MediaDrm.PROPERTY_DEVICE_UNIQUE_ID)
64
+ val md = MessageDigest.getInstance("SHA-256")
65
+ md.update(wideVineId)
66
+ md.digest().toHexString()
67
+ } catch (e: Exception) {
68
+ Log.w("EdgeeReactNative", "Failed to get device ID: ${e.message}")
69
+ null
70
+ } finally {
71
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
72
+ wvDrm?.close()
73
+ } else {
74
+ wvDrm?.release()
75
+ }
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Get Android ID (fallback identifier)
81
+ */
82
+ @SuppressLint("HardwareIds")
83
+ private fun getAndroidId(): String? {
84
+ return try {
85
+ Settings.Secure.getString(reactApplicationContext.contentResolver, Settings.Secure.ANDROID_ID)
86
+ } catch (e: Exception) {
87
+ Log.w("EdgeeReactNative", "Failed to get Android ID: ${e.message}")
88
+ null
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Detect network connection type
94
+ */
95
+ private fun getConnectionType(context: Context): ConnectionType {
96
+ val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager?
97
+ var result = ConnectionType.Unknown
98
+
99
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
100
+ cm?.run {
101
+ cm.getNetworkCapabilities(cm.activeNetwork)?.run {
102
+ result = when {
103
+ hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> ConnectionType.Wifi
104
+ hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> ConnectionType.Cellular
105
+ hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> ConnectionType.Ethernet
106
+ else -> ConnectionType.Unknown
107
+ }
108
+ }
109
+ }
110
+ } else {
111
+ @Suppress("DEPRECATION")
112
+ cm?.run {
113
+ cm.activeNetworkInfo?.run {
114
+ result = when (type) {
115
+ ConnectivityManager.TYPE_WIFI -> ConnectionType.Wifi
116
+ ConnectivityManager.TYPE_MOBILE -> ConnectionType.Cellular
117
+ ConnectivityManager.TYPE_ETHERNET -> ConnectionType.Ethernet
118
+ else -> ConnectionType.Unknown
119
+ }
120
+ }
121
+ }
122
+ }
123
+ return result
124
+ }
125
+
126
+ /**
127
+ * Get carrier information
128
+ */
129
+ private fun getCarrierInfo(): Map<String, String?> {
130
+ return try {
131
+ val telephonyManager = reactApplicationContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager?
132
+ mapOf(
133
+ "carrierName" to telephonyManager?.networkOperatorName,
134
+ "carrierCode" to telephonyManager?.networkOperator,
135
+ "isoCountryCode" to telephonyManager?.networkCountryIso?.uppercase()
136
+ )
137
+ } catch (e: Exception) {
138
+ Log.w("EdgeeReactNative", "Failed to get carrier info: ${e.message}")
139
+ mapOf("carrierName" to null, "carrierCode" to null, "isoCountryCode" to null)
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Get user agent string
145
+ */
146
+ private fun getUserAgent(): String? {
147
+ return try {
148
+ WebSettings.getDefaultUserAgent(reactApplicationContext)
149
+ } catch (e: Exception) {
150
+ Log.w("EdgeeReactNative", "Failed to get user agent: ${e.message}")
151
+ null
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Check if device is rooted
157
+ */
158
+ private fun isDeviceRooted(): Boolean {
159
+ return try {
160
+ val buildTags = Build.TAGS
161
+ buildTags != null && buildTags.contains("test-keys")
162
+ } catch (e: Exception) {
163
+ false
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Get memory information
169
+ */
170
+ private fun getMemoryInfo(): Map<String, Long> {
171
+ return try {
172
+ val runtime = Runtime.getRuntime()
173
+ mapOf(
174
+ "totalMemory" to runtime.totalMemory(),
175
+ "freeMemory" to runtime.freeMemory(),
176
+ "maxMemory" to runtime.maxMemory()
177
+ )
178
+ } catch (e: Exception) {
179
+ Log.w("EdgeeReactNative", "Failed to get memory info: ${e.message}")
180
+ mapOf("totalMemory" to 0L, "freeMemory" to 0L, "maxMemory" to 0L)
181
+ }
182
+ }
183
+
184
+ @ReactMethod
185
+ fun getContextInfo(config: ReadableMap, promise: Promise) {
186
+ try {
187
+ // Basic app information
188
+ val appName = reactApplicationContext.applicationInfo.loadLabel(reactApplicationContext.packageManager).toString()
189
+ val appVersion = pInfo.versionName ?: "unknown"
190
+ val buildNumber = getBuildNumber()
191
+ val bundleId = reactApplicationContext.packageName
192
+
193
+ // Device information
194
+ val connectionType = getConnectionType(reactApplicationContext)
195
+ val timezone = TimeZone.getDefault()
196
+ val currentLocale = Locale.getDefault()
197
+ val locale = "${currentLocale.language}-${currentLocale.country}"
198
+
199
+ // Screen information
200
+ val displayMetrics = Resources.getSystem().displayMetrics
201
+ val screenWidth = displayMetrics.widthPixels
202
+ val screenHeight = displayMetrics.heightPixels
203
+ val screenDensity = displayMetrics.density
204
+
205
+ // Carrier information
206
+ val carrierInfo = getCarrierInfo()
207
+
208
+ // Memory information
209
+ val memoryInfo = getMemoryInfo()
210
+
211
+ // Build context map
212
+ val contextInfo = Arguments.createMap().apply {
213
+ // App information
214
+ putString("appName", appName)
215
+ putString("appVersion", appVersion)
216
+ putString("buildNumber", buildNumber)
217
+ putString("bundleId", bundleId)
218
+
219
+ // Device information
220
+ putString("deviceName", Build.DEVICE)
221
+ putString("deviceType", "android")
222
+ putString("manufacturer", Build.MANUFACTURER)
223
+ putString("model", Build.MODEL)
224
+ putString("brand", Build.BRAND)
225
+ putString("product", Build.PRODUCT)
226
+
227
+ // System information
228
+ putString("osName", "Android")
229
+ putString("osVersion", Build.VERSION.RELEASE)
230
+ putInt("sdkVersion", Build.VERSION.SDK_INT)
231
+ putString("kernelVersion", System.getProperty("os.version"))
232
+
233
+ // Locale and timezone
234
+ putString("timezone", timezone.id)
235
+ putString("locale", locale)
236
+ putString("language", currentLocale.language)
237
+ putString("country", currentLocale.country)
238
+
239
+ // Network information
240
+ putString("networkType", connectionType.toString().lowercase(currentLocale))
241
+ carrierInfo["carrierName"]?.let { putString("carrierName", it) }
242
+ carrierInfo["carrierCode"]?.let { putString("carrierCode", it) }
243
+ carrierInfo["isoCountryCode"]?.let { putString("isoCountryCode", it) }
244
+
245
+ // Screen information
246
+ putInt("screenWidth", screenWidth)
247
+ putInt("screenHeight", screenHeight)
248
+ putDouble("screenDensity", screenDensity.toDouble())
249
+
250
+ // Hardware information
251
+ putString("architecture", Build.SUPPORTED_ABIS.firstOrNull() ?: "unknown")
252
+ putBoolean("isTablet", isTablet())
253
+ putBoolean("isEmulator", isEmulator())
254
+ putBoolean("isRooted", isDeviceRooted())
255
+
256
+ // Memory information
257
+ putDouble("totalMemoryMB", memoryInfo["totalMemory"]!! / (1024.0 * 1024.0))
258
+ putDouble("freeMemoryMB", memoryInfo["freeMemory"]!! / (1024.0 * 1024.0))
259
+ putDouble("maxMemoryMB", memoryInfo["maxMemory"]!! / (1024.0 * 1024.0))
260
+
261
+ // Optional device ID (privacy-compliant)
262
+ if (config.hasKey("collectDeviceId") && config.getBoolean("collectDeviceId")) {
263
+ val deviceId = getUniqueDeviceId() ?: getAndroidId() ?: UUID.randomUUID().toString()
264
+ putString("deviceId", deviceId)
265
+ }
266
+
267
+ // User agent
268
+ getUserAgent()?.let { putString("userAgent", it) }
269
+
270
+ // Installation info
271
+ putDouble("firstInstallTime", pInfo.firstInstallTime.toDouble())
272
+ putDouble("lastUpdateTime", pInfo.lastUpdateTime.toDouble())
273
+ }
274
+
275
+ promise.resolve(contextInfo)
276
+ } catch (e: Exception) {
277
+ Log.e("EdgeeReactNative", "Failed to get context info", e)
278
+ promise.reject("GET_CONTEXT_ERROR", "Failed to get context info: ${e.message}", e)
279
+ }
280
+ }
281
+
282
+ /**
283
+ * Check if device is a tablet
284
+ */
285
+ private fun isTablet(): Boolean {
286
+ return try {
287
+ val displayMetrics = Resources.getSystem().displayMetrics
288
+ val screenWidthDp = displayMetrics.widthPixels / displayMetrics.density
289
+ val screenHeightDp = displayMetrics.heightPixels / displayMetrics.density
290
+ val smallestWidth = minOf(screenWidthDp, screenHeightDp)
291
+ smallestWidth >= 600 // Android tablet threshold
292
+ } catch (e: Exception) {
293
+ false
294
+ }
295
+ }
296
+
297
+ /**
298
+ * Check if running on emulator
299
+ */
300
+ private fun isEmulator(): Boolean {
301
+ return (Build.FINGERPRINT.startsWith("generic") ||
302
+ Build.FINGERPRINT.startsWith("unknown") ||
303
+ Build.MODEL.contains("google_sdk") ||
304
+ Build.MODEL.contains("Emulator") ||
305
+ Build.MODEL.contains("Android SDK built for x86") ||
306
+ Build.MANUFACTURER.contains("Genymotion") ||
307
+ Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic") ||
308
+ "google_sdk" == Build.PRODUCT)
309
+ }
310
+
311
+ /**
312
+ * Track deep link data
313
+ */
314
+ fun trackDeepLinks(intent: Intent?) {
315
+ if (intent?.data == null) {
316
+ Log.d(name, "No Intent data found")
317
+ return
318
+ }
319
+
320
+ val properties = mutableMapOf<String, String>()
321
+ val uri = intent.data!!
322
+
323
+ try {
324
+ properties["url"] = uri.toString()
325
+ properties["scheme"] = uri.scheme ?: ""
326
+ properties["host"] = uri.host ?: ""
327
+ properties["path"] = uri.path ?: ""
328
+
329
+ // Extract query parameters
330
+ uri.queryParameterNames.forEach { parameter ->
331
+ uri.getQueryParameter(parameter)?.let { value ->
332
+ if (value.trim().isNotEmpty()) {
333
+ properties[parameter] = value
334
+ }
335
+ }
336
+ }
337
+
338
+ Log.d(name, "Deep link tracked: $properties")
339
+ } catch (e: Exception) {
340
+ Log.e(name, "Error tracking deep link: ${e.message}")
341
+ }
342
+ }
343
+
344
+ // Activity and lifecycle event handlers
345
+ override fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) {
346
+ // No-op
347
+ }
348
+
349
+ override fun onNewIntent(intent: Intent) {
350
+ Log.d(name, "onNewIntent: $intent")
351
+ trackDeepLinks(intent)
352
+ }
353
+
354
+ override fun onHostResume() {
355
+ if (reactApplicationContext.currentActivity != null && isColdLaunch) {
356
+ isColdLaunch = false
357
+ Log.d(name, "onHostResume (cold launch)")
358
+ trackDeepLinks(reactApplicationContext.currentActivity?.intent)
359
+ }
360
+ }
361
+
362
+ override fun onHostPause() {
363
+ // No-op
364
+ }
365
+
366
+ override fun onHostDestroy() {
367
+ isColdLaunch = true
368
+ }
369
+ }
@@ -0,0 +1,17 @@
1
+ package com.reactnativeedgee
2
+
3
+ import com.facebook.react.ReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.uimanager.ViewManager
7
+
8
+ class EdgeeReactNativePackage : ReactPackage {
9
+
10
+ override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
11
+ return listOf(EdgeeReactNativeModule(reactContext))
12
+ }
13
+
14
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
15
+ return emptyList()
16
+ }
17
+ }
@@ -0,0 +1,14 @@
1
+ #import <React/RCTBridgeModule.h>
2
+
3
+ @interface RCT_EXTERN_MODULE(EdgeeReactNative, NSObject)
4
+
5
+ RCT_EXTERN_METHOD(getContextInfo:(NSDictionary *)config
6
+ resolver:(RCTPromiseResolveBlock)resolve
7
+ rejecter:(RCTPromiseRejectBlock)reject)
8
+
9
+ + (BOOL)requiresMainQueueSetup
10
+ {
11
+ return YES;
12
+ }
13
+
14
+ @end
@@ -0,0 +1,35 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "../package.json")))
4
+ folly_version = '2021.07.22.00'
5
+
6
+ Pod::Spec.new do |s|
7
+ s.name = "EdgeeReactNative"
8
+ s.version = package["version"]
9
+ s.summary = package["description"]
10
+ s.homepage = package["homepage"]
11
+ s.license = package["license"]
12
+ s.authors = package["author"]
13
+
14
+ s.platforms = { :ios => "11.0" }
15
+ s.source = { :git => "https://github.com/edgee-cloud/react-native-edgee.git", :tag => "#{s.version}" }
16
+
17
+ s.source_files = "*.{h,m,mm,swift}"
18
+
19
+ s.dependency "React-Core"
20
+
21
+ # Don't install the dependencies when we run `pod install` in the old architecture.
22
+ if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then
23
+ s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1"
24
+ s.pod_target_xcconfig = {
25
+ "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
26
+ "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1",
27
+ "CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
28
+ }
29
+ s.dependency "React-Codegen"
30
+ s.dependency "RCT-Folly", folly_version
31
+ s.dependency "RCTRequired"
32
+ s.dependency "RCTTypeSafety"
33
+ s.dependency "ReactCommon/turbomodule/core"
34
+ end
35
+ end