Created
March 7, 2024 08:22
-
-
Save Shahroz16/d7416a06b2c4e2122e2df308323b3335 to your computer and use it in GitHub Desktop.
Fetch Contextual Information
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import <React/RCTBridgeModule.h> | |
@interface RCT_EXTERN_MODULE(AnalyticsReactNative, NSObject) | |
RCT_EXTERN_METHOD(getContextInfo: (NSDictionary)configuration resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import CoreTelephony | |
import SystemConfiguration | |
import UIKit | |
import AdSupport | |
enum ConnectionType: String { | |
case wifi = "wifi" | |
case cellular = "cellular" | |
case unknown = "unknown" | |
} | |
@objc(AnalyticsReactNative) | |
public class AnalyticsReactNative: NSObject { | |
@objc | |
static func requiresMainQueueSetup() -> Bool { | |
return true | |
} | |
func getAppName() -> String { | |
guard let displayName = Bundle.main.infoDictionary!["CFBundleDisplayName"] else { | |
return Bundle.main.infoDictionary!["CFBundleName"] as! String | |
} | |
return displayName as! String | |
} | |
func getDeviceModel() -> String { | |
var systemInfo = utsname() | |
uname(&systemInfo) | |
let machineMirror = Mirror(reflecting: systemInfo.machine) | |
return machineMirror.children.reduce("") { identifier, element in | |
guard let value = element.value as? Int8, value != 0 else { return identifier } | |
return identifier + String(UnicodeScalar(UInt8(value))) | |
} | |
} | |
func getDeviceId() -> String { | |
guard let deviceId = UIDevice.current.identifierForVendor else { | |
return "UNKNOWN_ID" | |
} | |
return deviceId.uuidString | |
} | |
func getNetworkType() -> ConnectionType { | |
guard let reachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, "google.com") else { | |
return ConnectionType.unknown | |
} | |
var flags = SCNetworkReachabilityFlags() | |
SCNetworkReachabilityGetFlags(reachability, &flags) | |
let isReachable = flags.contains(.reachable) | |
let isWWAN = flags.contains(.isWWAN) | |
if isReachable { | |
if isWWAN { | |
let networkInfo = CTTelephonyNetworkInfo() | |
if #available(iOS 12.1, *) { | |
let carrierType = networkInfo.serviceCurrentRadioAccessTechnology | |
guard let _ = carrierType?.first?.value else { | |
return ConnectionType.unknown | |
} | |
return ConnectionType.cellular | |
} else { | |
let carrierType = networkInfo.currentRadioAccessTechnology | |
if (carrierType == nil) { | |
return ConnectionType.unknown | |
} | |
return ConnectionType.cellular | |
} | |
} else { | |
return ConnectionType.wifi | |
} | |
} | |
return ConnectionType.unknown | |
} | |
@objc(getContextInfo:resolver:rejecter:) | |
func getContextInfo(config: NSDictionary, resolver resolve:RCTPromiseResolveBlock, rejecter reject:RCTPromiseRejectBlock) -> Void { | |
let infoDictionary = Bundle.main.infoDictionary! | |
let appName = getAppName() | |
let appVersion = infoDictionary["CFBundleShortVersionString"] as? String ?? "" | |
let bundleId = Bundle.main.bundleIdentifier ?? "" | |
let buildNumber = infoDictionary["CFBundleVersion"] as? String ?? "" | |
let connectionType: ConnectionType = getNetworkType() | |
var locale = "" | |
if let languageCode = NSLocale.current.languageCode, | |
let regionCode = NSLocale.current.regionCode { | |
locale = "\(languageCode)-\(regionCode)" | |
} | |
let timezone = TimeZone.current.identifier | |
let osName = UIDevice.current.systemName | |
let osVersion = UIDevice.current.systemVersion | |
let screenWidth = Int(UIScreen.main.bounds.size.width) | |
let screenHeight = Int(UIScreen.main.bounds.size.height) | |
let context: [String: Any] = [ | |
"appName": appName, | |
"appVersion": appVersion, | |
"buildNumber": buildNumber, | |
"bundleId": bundleId, | |
"deviceId": getDeviceId(), | |
"deviceName": UIDevice.current.model, | |
"deviceType": "ios", | |
"manufacturer": "Apple", | |
"model": getDeviceModel(), | |
"locale": locale, | |
"networkType": "\(connectionType)", | |
"timezone": timezone, | |
"osName": osName, | |
"osVersion": osVersion, | |
"screenWidth": screenWidth, | |
"screenHeight": screenHeight | |
] | |
resolve(context) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.annotation.SuppressLint | |
import android.app.Activity | |
import android.content.Context | |
import android.content.Intent | |
import android.content.pm.PackageInfo | |
import android.content.res.Resources | |
import android.media.MediaDrm | |
import android.net.ConnectivityManager | |
import android.net.NetworkCapabilities | |
import android.net.Uri | |
import android.os.Build | |
import android.util.Log | |
import androidx.core.content.pm.PackageInfoCompat | |
import com.facebook.react.ReactActivity | |
import com.facebook.react.ReactApplication | |
import com.facebook.react.bridge.* | |
import com.facebook.react.module.annotations.ReactModule | |
import java.lang.Exception | |
import java.util.* | |
import java.security.MessageDigest | |
import java.util.UUID | |
enum class ConnectionType { | |
Cellular, Unknown, Wifi | |
} | |
@ReactModule(name="AnalyticsReactNative") | |
class AnalyticsReactNativeModule : ReactContextBaseJavaModule, ActivityEventListener, LifecycleEventListener { | |
var onInitialized: () -> Unit = {}; | |
constructor(reactContext: ReactApplicationContext) : super(reactContext) { | |
this.pInfo = reactContext.packageManager.getPackageInfo(reactContext.packageName, 0) | |
} | |
override fun initialize() { | |
super.initialize() | |
onInitialized() | |
} | |
private val pInfo: PackageInfo | |
override fun getName(): String { | |
return "AnalyticsReactNative" | |
} | |
private fun getBuildNumber(): String { | |
return PackageInfoCompat.getLongVersionCode(pInfo).toString() | |
} | |
fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) } | |
/** | |
* Workaround for not able to get device id on Android 10 or above using DRM API | |
* {@see https://stackoverflow.com/questions/58103580/android-10-imei-no-longer-available-on-api-29-looking-for-alternatives} | |
* {@see https://developer.android.com/training/articles/user-data-ids} | |
*/ | |
private fun getUniqueId(collectDeviceId : Boolean): String? { | |
if (collectDeviceId) { | |
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) | |
return null | |
val WIDEVINE_UUID = UUID(-0x121074568629b532L, -0x5c37d8232ae2de13L) | |
var wvDrm: MediaDrm? = null | |
try { | |
wvDrm = MediaDrm(WIDEVINE_UUID) | |
val wideVineId = wvDrm.getPropertyByteArray(MediaDrm.PROPERTY_DEVICE_UNIQUE_ID) | |
val md = MessageDigest.getInstance("SHA-256") | |
md.update(wideVineId) | |
return md.digest().toHexString() | |
} catch (e: Exception) { | |
return null | |
} finally { | |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { | |
wvDrm?.close() | |
} else { | |
wvDrm?.release() | |
} | |
} | |
} | |
return null | |
} | |
private fun getConnectionType(context: Context): ConnectionType { | |
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? | |
var result: ConnectionType = ConnectionType.Unknown | |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { | |
cm?.run { | |
cm.getNetworkCapabilities(cm.activeNetwork)?.run { | |
if (hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { | |
result = ConnectionType.Wifi | |
} else if (hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { | |
result = ConnectionType.Cellular | |
} else { | |
result = ConnectionType.Unknown | |
} | |
} | |
} | |
} else { | |
cm?.run { | |
cm.activeNetworkInfo?.run { | |
if (type == ConnectivityManager.TYPE_WIFI) { | |
result = ConnectionType.Wifi | |
} else if (type == ConnectivityManager.TYPE_MOBILE) { | |
result = ConnectionType.Cellular | |
} else { | |
result = ConnectionType.Unknown | |
} | |
} | |
} | |
} | |
return result | |
} | |
@ReactMethod | |
fun getContextInfo(config: ReadableMap, promise: Promise) { | |
val appName: String = reactApplicationContext.applicationInfo.loadLabel(reactApplicationContext.packageManager).toString() | |
val appVersion: String = pInfo.versionName | |
val buildNumber = getBuildNumber() | |
val bundleId = reactApplicationContext.packageName | |
val connectionType: ConnectionType = getConnectionType(reactApplicationContext) | |
val timezone: TimeZone = TimeZone.getDefault() | |
val currentLocale: Locale = Locale.getDefault() | |
val locale: String = "${currentLocale.language}-${currentLocale.country}" | |
val screenWidth = Resources.getSystem().displayMetrics.widthPixels | |
val screenHeight = Resources.getSystem().displayMetrics.heightPixels | |
val screenDensity = Resources.getSystem().displayMetrics.density; | |
val contextInfo: WritableMap = Arguments.createMap() | |
if (config.hasKey("collectDeviceId") && config.getBoolean("collectDeviceId")) { | |
val fallbackDeviceId = UUID.randomUUID().toString() | |
val deviceId = getUniqueId(config.hasKey("collectDeviceId") && config.getBoolean("collectDeviceId"))?: fallbackDeviceId | |
contextInfo.putString("deviceId", deviceId) | |
} | |
contextInfo.putString("appName", appName) | |
contextInfo.putString("appVersion", appVersion) | |
contextInfo.putString("buildNumber", buildNumber) | |
contextInfo.putString("bundleId", bundleId) | |
contextInfo.putString("deviceName", Build.DEVICE) | |
contextInfo.putString("deviceType", "android") | |
contextInfo.putString("manufacturer", Build.MANUFACTURER) | |
contextInfo.putString("model", Build.MODEL) | |
contextInfo.putString("timezone", timezone.id) | |
contextInfo.putString("locale", locale) | |
contextInfo.putString("networkType", connectionType.toString().toLowerCase(currentLocale)) | |
contextInfo.putString("osName", "Android") | |
contextInfo.putString("osVersion", Build.VERSION.RELEASE) | |
contextInfo.putInt("screenWidth", screenWidth) | |
contextInfo.putInt("screenHeight", screenHeight) | |
contextInfo.putDouble("screenDensity", screenDensity.toDouble()) | |
promise.resolve(contextInfo) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Native Module types | |
export interface GetContextConfig { | |
collectDeviceId: boolean; | |
} | |
export type AnalyticsReactNativeModule = NativeModule & { | |
getContextInfo: (config: GetContextConfig) => Promise<NativeContextInfo>; | |
}; | |
export type NativeContextInfo = { | |
appName: string; | |
appVersion: string; | |
buildNumber: string; | |
bundleId: string; | |
locale: string; | |
networkType: string; | |
osName: string; | |
osVersion: string; | |
screenHeight: number; | |
screenWidth: number; | |
screenDensity?: number; // android only | |
timezone: string; | |
manufacturer: string; | |
model: string; | |
deviceName: string; | |
deviceId?: string; | |
deviceType: string; | |
adTrackingEnabled?: boolean; // ios only | |
advertisingId?: string; // ios only | |
}; | |
const defaultContext = { | |
appName: '', | |
appVersion: '', | |
buildNumber: '', | |
bundleId: '', | |
locale: '', | |
networkType: '', | |
osName: '', | |
osVersion: '', | |
screenHeight: 0, | |
screenWidth: 0, | |
timezone: '', | |
manufacturer: '', | |
model: '', | |
deviceName: '', | |
deviceId: '', | |
deviceType: '', | |
screenDensity: 0, | |
}; | |
const { | |
appName, | |
appVersion, | |
buildNumber, | |
bundleId, | |
locale, | |
networkType, | |
osName, | |
osVersion, | |
screenHeight, | |
screenWidth, | |
timezone, | |
manufacturer, | |
model, | |
deviceName, | |
deviceId, | |
deviceType, | |
screenDensity, | |
}: NativeContextInfo = | |
(await nativeModule.getContextInfo(nativeConfig)) ?? defaultContext; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment