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,444 @@
1
+ import Foundation
2
+ import UIKit
3
+ import CoreTelephony
4
+ import SystemConfiguration
5
+ import AdSupport
6
+ import AppTrackingTransparency
7
+ import React
8
+
9
+ // Define promise block types
10
+ public typealias RCTPromiseResolveBlock = (_ result: Any?) -> Void
11
+ public typealias RCTPromiseRejectBlock = (_ code: String?, _ message: String?, _ error: Error?) -> Void
12
+
13
+ enum ConnectionType: String {
14
+ case wifi = "wifi"
15
+ case cellular = "cellular"
16
+ case ethernet = "ethernet"
17
+ case unknown = "unknown"
18
+ }
19
+
20
+ @objc(EdgeeReactNative)
21
+ public class EdgeeReactNative: NSObject {
22
+
23
+ @objc
24
+ static func requiresMainQueueSetup() -> Bool {
25
+ return true
26
+ }
27
+
28
+ // MARK: - App Information
29
+
30
+ func getAppName() -> String {
31
+ guard let displayName = Bundle.main.infoDictionary?["CFBundleDisplayName"] as? String else {
32
+ return Bundle.main.infoDictionary?["CFBundleName"] as? String ?? "Unknown"
33
+ }
34
+ return displayName
35
+ }
36
+
37
+ func getAppVersion() -> String {
38
+ return Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "unknown"
39
+ }
40
+
41
+ func getBuildNumber() -> String {
42
+ return Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "unknown"
43
+ }
44
+
45
+ func getBundleId() -> String {
46
+ return Bundle.main.bundleIdentifier ?? "unknown"
47
+ }
48
+
49
+ // MARK: - Device Information
50
+
51
+ func getDeviceModel() -> String {
52
+ var systemInfo = utsname()
53
+ uname(&systemInfo)
54
+ let machineMirror = Mirror(reflecting: systemInfo.machine)
55
+ return machineMirror.children.reduce("") { identifier, element in
56
+ guard let value = element.value as? Int8, value != 0 else { return identifier }
57
+ return identifier + String(UnicodeScalar(UInt8(value)))
58
+ }
59
+ }
60
+
61
+ func getDeviceId() -> String {
62
+ return UIDevice.current.identifierForVendor?.uuidString ?? "UNKNOWN_ID"
63
+ }
64
+
65
+ func getSystemUptime() -> TimeInterval {
66
+ return ProcessInfo.processInfo.systemUptime
67
+ }
68
+
69
+ func isJailbroken() -> Bool {
70
+ #if targetEnvironment(simulator)
71
+ return false
72
+ #else
73
+ let jailbreakPaths = [
74
+ "/Applications/Cydia.app",
75
+ "/Library/MobileSubstrate/MobileSubstrate.dylib",
76
+ "/bin/bash",
77
+ "/usr/sbin/sshd",
78
+ "/etc/apt",
79
+ "/private/var/lib/apt/"
80
+ ]
81
+
82
+ for path in jailbreakPaths {
83
+ if FileManager.default.fileExists(atPath: path) {
84
+ return true
85
+ }
86
+ }
87
+
88
+ // Try to write to a restricted directory
89
+ let testPath = "/private/test_jailbreak"
90
+ do {
91
+ try "test".write(toFile: testPath, atomically: true, encoding: .utf8)
92
+ try FileManager.default.removeItem(atPath: testPath)
93
+ return true
94
+ } catch {
95
+ // Normal behavior - can't write to restricted directory
96
+ }
97
+
98
+ return false
99
+ #endif
100
+ }
101
+
102
+ func isSimulator() -> Bool {
103
+ #if targetEnvironment(simulator)
104
+ return true
105
+ #else
106
+ return false
107
+ #endif
108
+ }
109
+
110
+ func isTablet() -> Bool {
111
+ return UIDevice.current.userInterfaceIdiom == .pad
112
+ }
113
+
114
+ // MARK: - Network Information
115
+
116
+ func getNetworkType() -> ConnectionType {
117
+ guard let reachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, "google.com") else {
118
+ return .unknown
119
+ }
120
+
121
+ var flags = SCNetworkReachabilityFlags()
122
+ SCNetworkReachabilityGetFlags(reachability, &flags)
123
+
124
+ let isReachable = flags.contains(.reachable)
125
+ let isWWAN = flags.contains(.isWWAN)
126
+
127
+ if isReachable {
128
+ if isWWAN {
129
+ return .cellular
130
+ } else {
131
+ return .wifi
132
+ }
133
+ }
134
+
135
+ return .unknown
136
+ }
137
+
138
+ func getCarrierInfo() -> [String: Any?] {
139
+ let networkInfo = CTTelephonyNetworkInfo()
140
+ var carrierInfo: [String: Any?] = [:]
141
+
142
+ if #available(iOS 12.1, *) {
143
+ if let carriers = networkInfo.serviceSubscriberCellularProviders {
144
+ let carrier = carriers.values.first
145
+ carrierInfo["carrierName"] = carrier?.carrierName
146
+ carrierInfo["mobileCountryCode"] = carrier?.mobileCountryCode
147
+ carrierInfo["mobileNetworkCode"] = carrier?.mobileNetworkCode
148
+ carrierInfo["isoCountryCode"] = carrier?.isoCountryCode?.uppercased()
149
+ }
150
+
151
+ if let radioTech = networkInfo.serviceCurrentRadioAccessTechnology?.values.first {
152
+ carrierInfo["radioTechnology"] = radioTech
153
+ }
154
+ } else {
155
+ let carrier = networkInfo.subscriberCellularProvider
156
+ carrierInfo["carrierName"] = carrier?.carrierName
157
+ carrierInfo["mobileCountryCode"] = carrier?.mobileCountryCode
158
+ carrierInfo["mobileNetworkCode"] = carrier?.mobileNetworkCode
159
+ carrierInfo["isoCountryCode"] = carrier?.isoCountryCode?.uppercased()
160
+ carrierInfo["radioTechnology"] = networkInfo.currentRadioAccessTechnology
161
+ }
162
+
163
+ return carrierInfo
164
+ }
165
+
166
+ // MARK: - System Information
167
+
168
+ func getMemoryInfo() -> [String: Double] {
169
+ var info = mach_task_basic_info()
170
+ var count = mach_msg_type_number_t(MemoryLayout<mach_task_basic_info>.size) / 4
171
+
172
+ let result = withUnsafeMutablePointer(to: &info) {
173
+ $0.withMemoryRebound(to: integer_t.self, capacity: 1) {
174
+ task_info(mach_task_self_, task_flavor_t(MACH_TASK_BASIC_INFO), $0, &count)
175
+ }
176
+ }
177
+
178
+ if result == KERN_SUCCESS {
179
+ let usedMemoryMB = Double(info.resident_size) / (1024 * 1024)
180
+ let totalMemoryMB = Double(ProcessInfo.processInfo.physicalMemory) / (1024 * 1024)
181
+ return [
182
+ "usedMemoryMB": usedMemoryMB,
183
+ "totalMemoryMB": totalMemoryMB,
184
+ "availableMemoryMB": totalMemoryMB - usedMemoryMB
185
+ ]
186
+ }
187
+
188
+ return [
189
+ "usedMemoryMB": 0,
190
+ "totalMemoryMB": 0,
191
+ "availableMemoryMB": 0
192
+ ]
193
+ }
194
+
195
+ func getBatteryInfo() -> [String: Any] {
196
+ UIDevice.current.isBatteryMonitoringEnabled = true
197
+
198
+ let batteryLevel = UIDevice.current.batteryLevel
199
+ let batteryState = UIDevice.current.batteryState
200
+
201
+ var stateString = "unknown"
202
+ switch batteryState {
203
+ case .unplugged:
204
+ stateString = "unplugged"
205
+ case .charging:
206
+ stateString = "charging"
207
+ case .full:
208
+ stateString = "full"
209
+ default:
210
+ stateString = "unknown"
211
+ }
212
+
213
+ return [
214
+ "batteryLevel": batteryLevel >= 0 ? batteryLevel : -1,
215
+ "batteryState": stateString,
216
+ "lowPowerMode": ProcessInfo.processInfo.isLowPowerModeEnabled
217
+ ]
218
+ }
219
+
220
+ func getStorageInfo() -> [String: Double] {
221
+ guard let systemAttributes = try? FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory()),
222
+ let freeSize = systemAttributes[.systemFreeSize] as? NSNumber,
223
+ let totalSize = systemAttributes[.systemSize] as? NSNumber else {
224
+ return ["freeStorageMB": 0, "totalStorageMB": 0, "usedStorageMB": 0]
225
+ }
226
+
227
+ let freeMB = freeSize.doubleValue / (1024 * 1024)
228
+ let totalMB = totalSize.doubleValue / (1024 * 1024)
229
+ let usedMB = totalMB - freeMB
230
+
231
+ return [
232
+ "freeStorageMB": freeMB,
233
+ "totalStorageMB": totalMB,
234
+ "usedStorageMB": usedMB
235
+ ]
236
+ }
237
+
238
+ // MARK: - Privacy Information
239
+
240
+ func getAdvertisingInfo() -> [String: Any] {
241
+ var adInfo: [String: Any] = [:]
242
+
243
+ if #available(iOS 14, *) {
244
+ switch ATTrackingManager.trackingAuthorizationStatus {
245
+ case .authorized:
246
+ adInfo["advertisingId"] = ASIdentifierManager.shared().advertisingIdentifier.uuidString
247
+ adInfo["adTrackingEnabled"] = true
248
+ adInfo["trackingStatus"] = "authorized"
249
+ case .denied:
250
+ adInfo["adTrackingEnabled"] = false
251
+ adInfo["trackingStatus"] = "denied"
252
+ case .restricted:
253
+ adInfo["adTrackingEnabled"] = false
254
+ adInfo["trackingStatus"] = "restricted"
255
+ case .notDetermined:
256
+ adInfo["adTrackingEnabled"] = false
257
+ adInfo["trackingStatus"] = "notDetermined"
258
+ @unknown default:
259
+ adInfo["adTrackingEnabled"] = false
260
+ adInfo["trackingStatus"] = "unknown"
261
+ }
262
+ } else {
263
+ // iOS 13 and below
264
+ if ASIdentifierManager.shared().isAdvertisingTrackingEnabled {
265
+ adInfo["advertisingId"] = ASIdentifierManager.shared().advertisingIdentifier.uuidString
266
+ adInfo["adTrackingEnabled"] = true
267
+ adInfo["trackingStatus"] = "authorized"
268
+ } else {
269
+ adInfo["adTrackingEnabled"] = false
270
+ adInfo["trackingStatus"] = "denied"
271
+ }
272
+ }
273
+
274
+ return adInfo
275
+ }
276
+
277
+ // MARK: - Main Context Method
278
+
279
+ @objc(getContextInfo:resolver:rejecter:)
280
+ func getContextInfo(config: NSDictionary, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
281
+
282
+ DispatchQueue.global(qos: .userInitiated).async { [weak self] in
283
+ guard let self = self else {
284
+ reject("CONTEXT_ERROR", "Module deallocated", nil)
285
+ return
286
+ }
287
+
288
+ do {
289
+ // Basic app information
290
+ let appName = self.getAppName()
291
+ let appVersion = self.getAppVersion()
292
+ let buildNumber = self.getBuildNumber()
293
+ let bundleId = self.getBundleId()
294
+
295
+ // Device information
296
+ let deviceModel = self.getDeviceModel()
297
+ let deviceId = self.getDeviceId()
298
+
299
+ // System information
300
+ let osName = UIDevice.current.systemName
301
+ let osVersion = UIDevice.current.systemVersion
302
+
303
+ // Locale and timezone information
304
+ var locale = ""
305
+ if let languageCode = NSLocale.current.languageCode,
306
+ let regionCode = NSLocale.current.regionCode {
307
+ locale = "\(languageCode)-\(regionCode)"
308
+ }
309
+ let timezone = TimeZone.current.identifier
310
+
311
+ // Screen information
312
+ let screen = UIScreen.main
313
+ let screenWidth = Int(screen.bounds.size.width * screen.scale)
314
+ let screenHeight = Int(screen.bounds.size.height * screen.scale)
315
+ let screenDensity = screen.scale
316
+
317
+ // Network and carrier information
318
+ let networkType = self.getNetworkType()
319
+ let carrierInfo = self.getCarrierInfo()
320
+
321
+ // Memory and system information
322
+ let memoryInfo = self.getMemoryInfo()
323
+ let batteryInfo = self.getBatteryInfo()
324
+ let storageInfo = self.getStorageInfo()
325
+
326
+ // Privacy information
327
+ let advertisingInfo = self.getAdvertisingInfo()
328
+
329
+ // Build context dictionary
330
+ var context: [String: Any] = [
331
+ // App information
332
+ "appName": appName,
333
+ "appVersion": appVersion,
334
+ "buildNumber": buildNumber,
335
+ "bundleId": bundleId,
336
+
337
+ // Device information
338
+ "deviceId": deviceId,
339
+ "deviceName": UIDevice.current.model,
340
+ "deviceType": "ios",
341
+ "manufacturer": "Apple",
342
+ "model": deviceModel,
343
+ "systemName": UIDevice.current.systemName,
344
+ "systemVersion": UIDevice.current.systemVersion,
345
+
346
+ // System information
347
+ "osName": osName,
348
+ "osVersion": osVersion,
349
+ "kernelVersion": ProcessInfo.processInfo.operatingSystemVersionString,
350
+
351
+ // Locale and timezone
352
+ "locale": locale,
353
+ "language": NSLocale.current.languageCode ?? "unknown",
354
+ "country": NSLocale.current.regionCode ?? "unknown",
355
+ "timezone": timezone,
356
+
357
+ // Screen information
358
+ "screenWidth": screenWidth,
359
+ "screenHeight": screenHeight,
360
+ "screenDensity": screenDensity,
361
+ "screenScale": screenDensity, // Alias for consistency
362
+
363
+ // Network information
364
+ "networkType": networkType.rawValue,
365
+
366
+ // Hardware capabilities
367
+ "isTablet": self.isTablet(),
368
+ "isSimulator": self.isSimulator(),
369
+ "isJailbroken": self.isJailbroken(),
370
+
371
+ // System metrics
372
+ "systemUptime": self.getSystemUptime(),
373
+ "lowPowerMode": ProcessInfo.processInfo.isLowPowerModeEnabled,
374
+
375
+ // Installation info
376
+ "firstInstallTime": 0, // Not available on iOS without additional setup
377
+ "lastUpdateTime": 0 // Not available on iOS without additional setup
378
+ ]
379
+
380
+ // Add carrier information if available
381
+ if let carrierName = carrierInfo["carrierName"] as? String {
382
+ context["carrierName"] = carrierName
383
+ }
384
+ if let mcc = carrierInfo["mobileCountryCode"] as? String {
385
+ context["mobileCountryCode"] = mcc
386
+ }
387
+ if let mnc = carrierInfo["mobileNetworkCode"] as? String {
388
+ context["mobileNetworkCode"] = mnc
389
+ }
390
+ if let isoCountryCode = carrierInfo["isoCountryCode"] as? String {
391
+ context["isoCountryCode"] = isoCountryCode
392
+ }
393
+ if let radioTech = carrierInfo["radioTechnology"] as? String {
394
+ context["radioTechnology"] = radioTech
395
+ }
396
+
397
+ // Add memory information
398
+ context.merge(memoryInfo) { (_, new) in new }
399
+
400
+ // Add battery information
401
+ context.merge(batteryInfo) { (_, new) in new }
402
+
403
+ // Add storage information
404
+ context.merge(storageInfo) { (_, new) in new }
405
+
406
+ // Add advertising information (privacy-compliant)
407
+ context.merge(advertisingInfo) { (_, new) in new }
408
+
409
+ // Optional: Add device ID if explicitly requested
410
+ if let collectDeviceId = config["collectDeviceId"] as? Bool, collectDeviceId {
411
+ // Device ID already added above
412
+ }
413
+
414
+ DispatchQueue.main.async {
415
+ resolve(context)
416
+ }
417
+
418
+ } catch {
419
+ DispatchQueue.main.async {
420
+ reject("CONTEXT_ERROR", "Failed to get context info: \(error.localizedDescription)", error)
421
+ }
422
+ }
423
+ }
424
+ }
425
+
426
+ // MARK: - Deep Link Tracking
427
+
428
+ @objc(trackDeepLink:withOptions:)
429
+ public static func trackDeepLink(url: NSURL, options: [UIApplication.OpenURLOptionsKey: Any]) {
430
+ let urlString = url.absoluteString ?? ""
431
+ let referringApp = options[.sourceApplication] as? String ?? ""
432
+
433
+ let deepLinkData: [String: Any] = [
434
+ "url": urlString,
435
+ "scheme": url.scheme ?? "",
436
+ "host": url.host ?? "",
437
+ "path": url.path ?? "",
438
+ "referringApplication": referringApp
439
+ ]
440
+
441
+ // You can emit this data to your analytics system
442
+ print("Deep link tracked: \(deepLinkData)")
443
+ }
444
+ }
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "react-native-edgee",
3
+ "version": "1.0.0",
4
+ "description": "Lightweight Edgee data collection client for React Native with native context collection.",
5
+ "license": "MIT",
6
+ "main": "dist/index.js",
7
+ "react-native": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "files": [
10
+ "dist",
11
+ "android",
12
+ "ios",
13
+ "react-native-edgee.podspec"
14
+ ],
15
+ "sideEffects": false,
16
+ "scripts": {
17
+ "build": "tsc -p tsconfig.json --jsx react-native",
18
+ "clean": "rimraf dist",
19
+ "typecheck": "tsc --noEmit"
20
+ },
21
+ "peerDependencies": {
22
+ "@react-native-async-storage/async-storage": ">=1.19.0",
23
+ "@react-native-community/netinfo": ">=11.0.0",
24
+ "react-native": ">=0.72"
25
+ },
26
+ "devDependencies": {
27
+ "@types/react": "^19.0.0",
28
+ "@types/react-native": "^0.73.0",
29
+ "rimraf": "^5.0.0",
30
+ "typescript": "^5.4.0"
31
+ },
32
+ "dependencies": {
33
+ "expo-router": "^5.1.4"
34
+ }
35
+ }
@@ -0,0 +1,36 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+ folly_version = '2024.11.18.00'
5
+ folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'
6
+
7
+ Pod::Spec.new do |s|
8
+ s.name = "react-native-edgee"
9
+ s.version = package["version"]
10
+ s.summary = package["description"]
11
+ s.homepage = "https://github.com/edgee-cloud/react-native-edgee"
12
+ s.license = package["license"]
13
+ s.authors = { "Edgee" => "hello@edgee.cloud" }
14
+
15
+ s.platforms = { :ios => "11.0" }
16
+ s.source = { :git => "https://github.com/edgee-cloud/react-native-edgee.git", :tag => "#{s.version}" }
17
+
18
+ s.source_files = "ios/**/*.{h,m,mm,swift}"
19
+
20
+ s.dependency "React-Core"
21
+
22
+ # Don't install the dependencies when we run `pod install` in the old architecture.
23
+ if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then
24
+ s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1"
25
+ s.pod_target_xcconfig = {
26
+ "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
27
+ "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1",
28
+ "CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
29
+ }
30
+ s.dependency "React-Codegen"
31
+ s.dependency "RCT-Folly", folly_version
32
+ s.dependency "RCTRequired"
33
+ s.dependency "RCTTypeSafety"
34
+ s.dependency "ReactCommon/turbomodule/core"
35
+ end
36
+ end