Skip to content

Instantly share code, notes, and snippets.

@Shahroz16
Created March 7, 2024 08:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Shahroz16/d7416a06b2c4e2122e2df308323b3335 to your computer and use it in GitHub Desktop.
Save Shahroz16/d7416a06b2c4e2122e2df308323b3335 to your computer and use it in GitHub Desktop.
Fetch Contextual Information
#import <React/RCTBridgeModule.h>
@interface RCT_EXTERN_MODULE(AnalyticsReactNative, NSObject)
RCT_EXTERN_METHOD(getContextInfo: (NSDictionary)configuration resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
@end
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)
}
}
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)
}
}
// 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