wiliot-sdk-rn 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.
Files changed (131) hide show
  1. package/GatewaySdk.podspec +27 -0
  2. package/README.md +99 -0
  3. package/android/build.gradle +72 -0
  4. package/android/src/main/AndroidManifest.xml +32 -0
  5. package/android/src/main/java/com/gatewaysdk/GatewayScanModule.kt +392 -0
  6. package/android/src/main/java/com/gatewaysdk/GatewayScanService.kt +521 -0
  7. package/android/src/main/java/com/gatewaysdk/GatewaySdkPackage.kt +22 -0
  8. package/ios/GatewaySdk/GatewayScanModule.m +59 -0
  9. package/ios/GatewaySdk/GatewayScanModule.swift +255 -0
  10. package/ios/GatewaySdk/GatewaySdk-Bridging-Header.h +14 -0
  11. package/lib/GatewaySDK.d.ts +150 -0
  12. package/lib/GatewaySDK.d.ts.map +1 -0
  13. package/lib/GatewaySDK.js +429 -0
  14. package/lib/GatewaySDK.js.map +1 -0
  15. package/lib/auth/ApiError.d.ts +27 -0
  16. package/lib/auth/ApiError.d.ts.map +1 -0
  17. package/lib/auth/ApiError.js +55 -0
  18. package/lib/auth/ApiError.js.map +1 -0
  19. package/lib/auth/AuthClient.d.ts +87 -0
  20. package/lib/auth/AuthClient.d.ts.map +1 -0
  21. package/lib/auth/AuthClient.js +198 -0
  22. package/lib/auth/AuthClient.js.map +1 -0
  23. package/lib/auth/TokenStorage.d.ts +49 -0
  24. package/lib/auth/TokenStorage.d.ts.map +1 -0
  25. package/lib/auth/TokenStorage.js +117 -0
  26. package/lib/auth/TokenStorage.js.map +1 -0
  27. package/lib/auth/index.d.ts +9 -0
  28. package/lib/auth/index.d.ts.map +1 -0
  29. package/lib/auth/index.js +18 -0
  30. package/lib/auth/index.js.map +1 -0
  31. package/lib/ble/BackgroundService.d.ts +85 -0
  32. package/lib/ble/BackgroundService.d.ts.map +1 -0
  33. package/lib/ble/BackgroundService.js +194 -0
  34. package/lib/ble/BackgroundService.js.map +1 -0
  35. package/lib/ble/BleScanner.d.ts +125 -0
  36. package/lib/ble/BleScanner.d.ts.map +1 -0
  37. package/lib/ble/BleScanner.js +482 -0
  38. package/lib/ble/BleScanner.js.map +1 -0
  39. package/lib/ble/index.d.ts +8 -0
  40. package/lib/ble/index.d.ts.map +1 -0
  41. package/lib/ble/index.js +13 -0
  42. package/lib/ble/index.js.map +1 -0
  43. package/lib/datapath/Datapath.d.ts +225 -0
  44. package/lib/datapath/Datapath.d.ts.map +1 -0
  45. package/lib/datapath/Datapath.js +527 -0
  46. package/lib/datapath/Datapath.js.map +1 -0
  47. package/lib/datapath/PacerFilter.d.ts +210 -0
  48. package/lib/datapath/PacerFilter.d.ts.map +1 -0
  49. package/lib/datapath/PacerFilter.js +415 -0
  50. package/lib/datapath/PacerFilter.js.map +1 -0
  51. package/lib/datapath/PacketBuilder.d.ts +218 -0
  52. package/lib/datapath/PacketBuilder.d.ts.map +1 -0
  53. package/lib/datapath/PacketBuilder.js +420 -0
  54. package/lib/datapath/PacketBuilder.js.map +1 -0
  55. package/lib/datapath/PacketDedup.d.ts +111 -0
  56. package/lib/datapath/PacketDedup.d.ts.map +1 -0
  57. package/lib/datapath/PacketDedup.js +236 -0
  58. package/lib/datapath/PacketDedup.js.map +1 -0
  59. package/lib/datapath/RssiEncoder.d.ts +187 -0
  60. package/lib/datapath/RssiEncoder.d.ts.map +1 -0
  61. package/lib/datapath/RssiEncoder.js +298 -0
  62. package/lib/datapath/RssiEncoder.js.map +1 -0
  63. package/lib/datapath/TagsTable.d.ts +261 -0
  64. package/lib/datapath/TagsTable.d.ts.map +1 -0
  65. package/lib/datapath/TagsTable.js +504 -0
  66. package/lib/datapath/TagsTable.js.map +1 -0
  67. package/lib/datapath/TbcCalculator.d.ts +190 -0
  68. package/lib/datapath/TbcCalculator.d.ts.map +1 -0
  69. package/lib/datapath/TbcCalculator.js +332 -0
  70. package/lib/datapath/TbcCalculator.js.map +1 -0
  71. package/lib/datapath/index.d.ts +23 -0
  72. package/lib/datapath/index.d.ts.map +1 -0
  73. package/lib/datapath/index.js +48 -0
  74. package/lib/datapath/index.js.map +1 -0
  75. package/lib/index.d.ts +28 -0
  76. package/lib/index.d.ts.map +1 -0
  77. package/lib/index.js +104 -0
  78. package/lib/index.js.map +1 -0
  79. package/lib/mqtt/MqttManager.d.ts +172 -0
  80. package/lib/mqtt/MqttManager.d.ts.map +1 -0
  81. package/lib/mqtt/MqttManager.js +551 -0
  82. package/lib/mqtt/MqttManager.js.map +1 -0
  83. package/lib/mqtt/index.d.ts +7 -0
  84. package/lib/mqtt/index.d.ts.map +1 -0
  85. package/lib/mqtt/index.js +11 -0
  86. package/lib/mqtt/index.js.map +1 -0
  87. package/lib/provider/SdkProvider.d.ts +478 -0
  88. package/lib/provider/SdkProvider.d.ts.map +1 -0
  89. package/lib/provider/SdkProvider.js +271 -0
  90. package/lib/provider/SdkProvider.js.map +1 -0
  91. package/lib/provider/index.d.ts +8 -0
  92. package/lib/provider/index.d.ts.map +1 -0
  93. package/lib/provider/index.js +19 -0
  94. package/lib/provider/index.js.map +1 -0
  95. package/lib/rtk-query/index.d.ts +936 -0
  96. package/lib/rtk-query/index.d.ts.map +1 -0
  97. package/lib/rtk-query/index.js +193 -0
  98. package/lib/rtk-query/index.js.map +1 -0
  99. package/lib/types/index.d.ts +318 -0
  100. package/lib/types/index.d.ts.map +1 -0
  101. package/lib/types/index.js +8 -0
  102. package/lib/types/index.js.map +1 -0
  103. package/lib/utils/index.d.ts +68 -0
  104. package/lib/utils/index.d.ts.map +1 -0
  105. package/lib/utils/index.js +181 -0
  106. package/lib/utils/index.js.map +1 -0
  107. package/package.json +100 -0
  108. package/src/GatewaySDK.ts +515 -0
  109. package/src/auth/ApiError.ts +66 -0
  110. package/src/auth/AuthClient.ts +283 -0
  111. package/src/auth/TokenStorage.ts +140 -0
  112. package/src/auth/index.ts +9 -0
  113. package/src/ble/BackgroundService.ts +217 -0
  114. package/src/ble/BleScanner.ts +555 -0
  115. package/src/ble/index.ts +8 -0
  116. package/src/datapath/Datapath.ts +714 -0
  117. package/src/datapath/PacerFilter.ts +600 -0
  118. package/src/datapath/PacketBuilder.ts +568 -0
  119. package/src/datapath/PacketDedup.ts +333 -0
  120. package/src/datapath/RssiEncoder.ts +412 -0
  121. package/src/datapath/TagsTable.ts +698 -0
  122. package/src/datapath/TbcCalculator.ts +446 -0
  123. package/src/datapath/index.ts +88 -0
  124. package/src/index.ts +138 -0
  125. package/src/mqtt/MqttManager.ts +668 -0
  126. package/src/mqtt/index.ts +7 -0
  127. package/src/provider/SdkProvider.tsx +314 -0
  128. package/src/provider/index.ts +18 -0
  129. package/src/rtk-query/index.ts +247 -0
  130. package/src/types/index.ts +363 -0
  131. package/src/utils/index.ts +195 -0
@@ -0,0 +1,27 @@
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 = "GatewaySdk"
7
+ s.version = package['version']
8
+ s.summary = package['description']
9
+ s.homepage = "https://github.com/example/gateway-sdk"
10
+ s.license = package['license']
11
+ s.authors = { "Gateway SDK" => "sdk@example.com" }
12
+ s.platforms = { :ios => "13.0" }
13
+ s.source = { :git => "https://github.com/example/gateway-sdk.git", :tag => "v#{s.version}" }
14
+
15
+ s.source_files = "ios/**/*.{h,m,mm,swift}"
16
+ s.exclude_files = "ios/Pods/**/*"
17
+
18
+ s.dependency "React-Core"
19
+
20
+ # Enable Swift
21
+ s.swift_version = "5.0"
22
+
23
+ # Info.plist settings documentation
24
+ s.info_plist = {
25
+ 'UIBackgroundModes' => ['bluetooth-central']
26
+ }
27
+ end
package/README.md ADDED
@@ -0,0 +1,99 @@
1
+ # wiliot-sdk-rn
2
+
3
+ Wiliot SDK for React Native - BLE scanning, MQTT, bridges, pixels, and IoT device management.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install wiliot-sdk-rn react-native-ble-manager sp-react-native-mqtt @react-native-async-storage/async-storage
9
+ cd ios && pod install
10
+ ```
11
+
12
+ ## Quick Start
13
+
14
+ ```tsx
15
+ import { SdkProvider, useSdk, useSdkState } from 'wiliot-sdk-rn';
16
+
17
+ // 1. Wrap your app (in App.tsx)
18
+ export default function App() {
19
+ return (
20
+ <SdkProvider
21
+ config={{
22
+ apiBaseUrl: 'https://api.wiliot.com',
23
+ mqttBrokerUrl: 'mqtts://mqtt.wiliot.com:8883',
24
+ ownerId: 'YOUR_OWNER_ID',
25
+ apiKey: 'YOUR_API_KEY',
26
+ }}
27
+ autoInitialize
28
+ >
29
+ <YourApp />
30
+ </SdkProvider>
31
+ );
32
+ }
33
+
34
+ // 2. Use anywhere in your app
35
+ function ScanScreen() {
36
+ const { startScan, stopScan } = useSdk();
37
+ const { isScanning, connectionState, devices } = useSdkState();
38
+
39
+ return (
40
+ <View>
41
+ <Text>Status: {connectionState}</Text>
42
+ <Button title={isScanning ? 'Stop' : 'Scan'} onPress={() => isScanning ? stopScan() : startScan()} />
43
+ <FlatList data={Object.values(devices)} renderItem={({ item }) => <Text>{item.id}</Text>} />
44
+ </View>
45
+ );
46
+ }
47
+ ```
48
+
49
+ ## Platform Setup
50
+
51
+ ### iOS (Info.plist)
52
+ ```xml
53
+ <key>NSBluetoothAlwaysUsageDescription</key>
54
+ <string>Required for BLE scanning</string>
55
+ <key>UIBackgroundModes</key>
56
+ <array><string>bluetooth-central</string></array>
57
+ ```
58
+
59
+ ### Android
60
+ No additional setup needed - permissions are included automatically.
61
+
62
+ ## Hooks
63
+
64
+ | Hook | Returns |
65
+ |------|---------|
66
+ | `useSdk()` | `{ sdk, initialize, startScan, stopScan, connect, disconnect, shutdown }` |
67
+ | `useSdkState()` | `{ initialized, isScanning, connectionState, packetsForwarded, devices, ... }` |
68
+ | `useSdkReady()` | `boolean` - true when initialized and connected |
69
+ | `useDevices()` | `BleDevice[]` - discovered devices |
70
+ | `useConnectionState()` | `'disconnected' \| 'connecting' \| 'connected' \| 'error'` |
71
+ | `useIsScanning()` | `boolean` |
72
+
73
+ ## Background Scanning
74
+
75
+ ```tsx
76
+ // Enable background scanning (foreground service on Android)
77
+ const { startScan } = useSdk();
78
+ await startScan(true); // true = background enabled
79
+ ```
80
+
81
+ ## Advanced: Direct SDK Access
82
+
83
+ ```tsx
84
+ import { createGatewaySDK } from 'wiliot-sdk-rn';
85
+
86
+ const sdk = createGatewaySDK({
87
+ apiBaseUrl: 'https://api.wiliot.com',
88
+ mqttBrokerUrl: 'mqtts://mqtt.wiliot.com:8883',
89
+ ownerId: 'YOUR_OWNER_ID',
90
+ apiKey: 'YOUR_API_KEY',
91
+ });
92
+
93
+ await sdk.initialize();
94
+ await sdk.startScan();
95
+ ```
96
+
97
+ ## License
98
+
99
+ MIT
@@ -0,0 +1,72 @@
1
+ buildscript {
2
+ ext.safeExtGet = {prop, fallback ->
3
+ rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
4
+ }
5
+ repositories {
6
+ google()
7
+ mavenCentral()
8
+ }
9
+ dependencies {
10
+ classpath("com.android.tools.build:gradle:8.1.0")
11
+ classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${safeExtGet('kotlinVersion', '1.9.22')}")
12
+ }
13
+ }
14
+
15
+ plugins {
16
+ id 'com.android.library'
17
+ id 'kotlin-android'
18
+ }
19
+
20
+ def safeExtGet(prop, fallback) {
21
+ return rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
22
+ }
23
+
24
+ android {
25
+ namespace "com.gatewaysdk"
26
+ compileSdkVersion safeExtGet('compileSdkVersion', 34)
27
+
28
+ defaultConfig {
29
+ minSdkVersion safeExtGet('minSdkVersion', 24)
30
+ targetSdkVersion safeExtGet('targetSdkVersion', 34)
31
+ }
32
+
33
+ buildTypes {
34
+ release {
35
+ minifyEnabled false
36
+ }
37
+ }
38
+
39
+ compileOptions {
40
+ sourceCompatibility JavaVersion.VERSION_17
41
+ targetCompatibility JavaVersion.VERSION_17
42
+ }
43
+
44
+ kotlinOptions {
45
+ jvmTarget = '17'
46
+ }
47
+
48
+ sourceSets {
49
+ main {
50
+ java.srcDirs = ['src/main/java']
51
+ }
52
+ }
53
+ }
54
+
55
+ repositories {
56
+ maven {
57
+ url "$projectDir/../node_modules/react-native/android"
58
+ }
59
+ mavenCentral()
60
+ google()
61
+ }
62
+
63
+ dependencies {
64
+ implementation "com.facebook.react:react-android:+"
65
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:${safeExtGet('kotlinVersion', '1.9.22')}"
66
+
67
+ // Coroutines for async operations
68
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"
69
+
70
+ // BLE support (optional, for native-level scanning)
71
+ implementation "no.nordicsemi.android:ble:2.7.0"
72
+ }
@@ -0,0 +1,32 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
3
+ package="com.gatewaysdk">
4
+
5
+ <!-- BLE Permissions -->
6
+ <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30"/>
7
+ <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30"/>
8
+ <uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation"/>
9
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
10
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
11
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
12
+
13
+ <!-- Background Service Permissions -->
14
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
15
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE"/>
16
+ <uses-permission android:name="android.permission.WAKE_LOCK"/>
17
+ <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
18
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
19
+
20
+ <!-- BLE Feature -->
21
+ <uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
22
+
23
+ <application>
24
+ <!-- Background Scan Service -->
25
+ <service
26
+ android:name=".GatewayScanService"
27
+ android:enabled="true"
28
+ android:exported="false"
29
+ android:foregroundServiceType="connectedDevice"/>
30
+ </application>
31
+
32
+ </manifest>
@@ -0,0 +1,392 @@
1
+ package com.gatewaysdk
2
+
3
+ import android.Manifest
4
+ import android.annotation.SuppressLint
5
+ import android.bluetooth.BluetoothAdapter
6
+ import android.bluetooth.BluetoothManager
7
+ import android.content.BroadcastReceiver
8
+ import android.content.Context
9
+ import android.content.Intent
10
+ import android.content.IntentFilter
11
+ import android.content.pm.PackageManager
12
+ import android.net.Uri
13
+ import android.os.Build
14
+ import android.os.PowerManager
15
+ import android.provider.Settings
16
+ import androidx.core.content.ContextCompat
17
+ import com.facebook.react.bridge.*
18
+ import com.facebook.react.modules.core.DeviceEventManagerModule
19
+
20
+ /**
21
+ * Gateway SDK Native Scan Module
22
+ *
23
+ * Provides native Android functionality for BLE background scanning:
24
+ * - Foreground service management
25
+ * - Battery optimization handling
26
+ * - Permission checking
27
+ * - Bluetooth state monitoring
28
+ */
29
+ class GatewayScanModule(private val reactContext: ReactApplicationContext) :
30
+ ReactContextBaseJavaModule(reactContext) {
31
+
32
+ companion object {
33
+ const val NAME = "GatewayScanModule"
34
+
35
+ // Broadcast actions
36
+ const val ACTION_SCAN_RESTART = "com.gatewaysdk.SCAN_RESTART"
37
+ const val ACTION_BUFFERED_RESULTS = "com.gatewaysdk.BUFFERED_SCAN_RESULTS"
38
+ const val ACTION_DOZE_MODE_CHANGED = "com.gatewaysdk.DOZE_MODE_CHANGED"
39
+ }
40
+
41
+ private val bluetoothManager: BluetoothManager? by lazy {
42
+ reactContext.getSystemService(Context.BLUETOOTH_SERVICE) as? BluetoothManager
43
+ }
44
+
45
+ private val bluetoothAdapter: BluetoothAdapter? by lazy {
46
+ bluetoothManager?.adapter
47
+ }
48
+
49
+ private val powerManager: PowerManager? by lazy {
50
+ reactContext.getSystemService(Context.POWER_SERVICE) as? PowerManager
51
+ }
52
+
53
+ // Broadcast receivers
54
+ private var bluetoothReceiver: BroadcastReceiver? = null
55
+ private var scanRestartReceiver: BroadcastReceiver? = null
56
+ private var bufferedResultsReceiver: BroadcastReceiver? = null
57
+ private var dozeModeReceiver: BroadcastReceiver? = null
58
+
59
+ init {
60
+ registerReceivers()
61
+ }
62
+
63
+ override fun getName(): String = NAME
64
+
65
+ override fun getConstants(): Map<String, Any> {
66
+ return mapOf(
67
+ "moduleName" to NAME,
68
+ "supportedEvents" to listOf(
69
+ "BluetoothStateChanged",
70
+ "ScanRestartRequired",
71
+ "BufferedScanResults",
72
+ "DozeModeChanged"
73
+ )
74
+ )
75
+ }
76
+
77
+ // ========================================================================
78
+ // Background Scan Service Control
79
+ // ========================================================================
80
+
81
+ @ReactMethod
82
+ fun startBackgroundScan(promise: Promise) {
83
+ try {
84
+ val intent = Intent(reactContext, GatewayScanService::class.java).apply {
85
+ action = GatewayScanService.ACTION_START
86
+ }
87
+
88
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
89
+ reactContext.startForegroundService(intent)
90
+ } else {
91
+ reactContext.startService(intent)
92
+ }
93
+
94
+ promise.resolve(true)
95
+ } catch (e: Exception) {
96
+ promise.reject("START_ERROR", "Failed to start background scan: ${e.message}", e)
97
+ }
98
+ }
99
+
100
+ @ReactMethod
101
+ fun stopBackgroundScan(promise: Promise) {
102
+ try {
103
+ val intent = Intent(reactContext, GatewayScanService::class.java).apply {
104
+ action = GatewayScanService.ACTION_STOP
105
+ }
106
+ reactContext.stopService(intent)
107
+ promise.resolve(true)
108
+ } catch (e: Exception) {
109
+ promise.reject("STOP_ERROR", "Failed to stop background scan: ${e.message}", e)
110
+ }
111
+ }
112
+
113
+ @ReactMethod
114
+ fun isBackgroundScanSupported(promise: Promise) {
115
+ // Background scanning is supported on Android 8.0+
116
+ promise.resolve(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
117
+ }
118
+
119
+ @ReactMethod
120
+ fun isServiceRunning(promise: Promise) {
121
+ promise.resolve(GatewayScanService.isRunning)
122
+ }
123
+
124
+ // ========================================================================
125
+ // Permission Checking
126
+ // ========================================================================
127
+
128
+ @ReactMethod
129
+ fun hasPermissions(promise: Promise) {
130
+ val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
131
+ listOf(
132
+ Manifest.permission.BLUETOOTH_SCAN,
133
+ Manifest.permission.BLUETOOTH_CONNECT,
134
+ Manifest.permission.ACCESS_FINE_LOCATION
135
+ )
136
+ } else {
137
+ listOf(Manifest.permission.ACCESS_FINE_LOCATION)
138
+ }
139
+
140
+ val allGranted = permissions.all { permission ->
141
+ ContextCompat.checkSelfPermission(reactContext, permission) == PackageManager.PERMISSION_GRANTED
142
+ }
143
+
144
+ promise.resolve(allGranted)
145
+ }
146
+
147
+ @ReactMethod
148
+ fun hasNotificationPermission(promise: Promise) {
149
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
150
+ val granted = ContextCompat.checkSelfPermission(
151
+ reactContext,
152
+ Manifest.permission.POST_NOTIFICATIONS
153
+ ) == PackageManager.PERMISSION_GRANTED
154
+ promise.resolve(granted)
155
+ } else {
156
+ promise.resolve(true)
157
+ }
158
+ }
159
+
160
+ // ========================================================================
161
+ // Battery Optimization
162
+ // ========================================================================
163
+
164
+ @ReactMethod
165
+ fun isBatteryOptimizationDisabled(promise: Promise) {
166
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
167
+ val isIgnoring = powerManager?.isIgnoringBatteryOptimizations(reactContext.packageName) ?: false
168
+ promise.resolve(isIgnoring)
169
+ } else {
170
+ promise.resolve(true)
171
+ }
172
+ }
173
+
174
+ @SuppressLint("BatteryLife")
175
+ @ReactMethod
176
+ fun requestBatteryOptimizationExemption(promise: Promise) {
177
+ try {
178
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
179
+ val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply {
180
+ data = Uri.parse("package:${reactContext.packageName}")
181
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
182
+ }
183
+ reactContext.startActivity(intent)
184
+ }
185
+ promise.resolve(true)
186
+ } catch (e: Exception) {
187
+ promise.reject("BATTERY_OPT_ERROR", "Failed to request exemption: ${e.message}", e)
188
+ }
189
+ }
190
+
191
+ @ReactMethod
192
+ fun openBatteryOptimizationSettings(promise: Promise) {
193
+ try {
194
+ val intent = Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS).apply {
195
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
196
+ }
197
+ reactContext.startActivity(intent)
198
+ promise.resolve(true)
199
+ } catch (e: Exception) {
200
+ promise.reject("SETTINGS_ERROR", "Failed to open settings: ${e.message}", e)
201
+ }
202
+ }
203
+
204
+ // ========================================================================
205
+ // Bluetooth State
206
+ // ========================================================================
207
+
208
+ @ReactMethod
209
+ fun isBluetoothEnabled(promise: Promise) {
210
+ promise.resolve(bluetoothAdapter?.isEnabled ?: false)
211
+ }
212
+
213
+ @ReactMethod
214
+ fun openBluetoothSettings(promise: Promise) {
215
+ try {
216
+ val intent = Intent(Settings.ACTION_BLUETOOTH_SETTINGS).apply {
217
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
218
+ }
219
+ reactContext.startActivity(intent)
220
+ promise.resolve(true)
221
+ } catch (e: Exception) {
222
+ promise.reject("SETTINGS_ERROR", "Failed to open Bluetooth settings: ${e.message}", e)
223
+ }
224
+ }
225
+
226
+ // ========================================================================
227
+ // Device Info
228
+ // ========================================================================
229
+
230
+ @ReactMethod
231
+ fun getDeviceManufacturer(promise: Promise) {
232
+ promise.resolve(Build.MANUFACTURER)
233
+ }
234
+
235
+ @ReactMethod
236
+ fun isSamsungDevice(promise: Promise) {
237
+ promise.resolve(Build.MANUFACTURER.lowercase() == "samsung")
238
+ }
239
+
240
+ @ReactMethod
241
+ fun isDeviceInDozeMode(promise: Promise) {
242
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
243
+ promise.resolve(powerManager?.isDeviceIdleMode ?: false)
244
+ } else {
245
+ promise.resolve(false)
246
+ }
247
+ }
248
+
249
+ @ReactMethod
250
+ fun getPowerStateInfo(promise: Promise) {
251
+ val info = Arguments.createMap().apply {
252
+ putBoolean("isIgnoringBatteryOptimizations",
253
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
254
+ powerManager?.isIgnoringBatteryOptimizations(reactContext.packageName) ?: false
255
+ else true
256
+ )
257
+ putBoolean("isDeviceIdleMode",
258
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
259
+ powerManager?.isDeviceIdleMode ?: false
260
+ else false
261
+ )
262
+ putBoolean("isPowerSaveMode",
263
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
264
+ powerManager?.isPowerSaveMode ?: false
265
+ else false
266
+ )
267
+ putBoolean("isInteractive", powerManager?.isInteractive ?: true)
268
+ }
269
+ promise.resolve(info)
270
+ }
271
+
272
+ // ========================================================================
273
+ // Event Emitting
274
+ // ========================================================================
275
+
276
+ private fun sendEvent(eventName: String, params: WritableMap?) {
277
+ reactContext
278
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
279
+ .emit(eventName, params)
280
+ }
281
+
282
+ @ReactMethod
283
+ fun addListener(eventName: String) {
284
+ // Required for RN 0.65+
285
+ }
286
+
287
+ @ReactMethod
288
+ fun removeListeners(count: Int) {
289
+ // Required for RN 0.65+
290
+ }
291
+
292
+ // ========================================================================
293
+ // Broadcast Receivers
294
+ // ========================================================================
295
+
296
+ private fun registerReceivers() {
297
+ // Bluetooth state receiver
298
+ bluetoothReceiver = object : BroadcastReceiver() {
299
+ override fun onReceive(context: Context?, intent: Intent?) {
300
+ if (intent?.action == BluetoothAdapter.ACTION_STATE_CHANGED) {
301
+ val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)
302
+ val enabled = state == BluetoothAdapter.STATE_ON
303
+
304
+ val params = Arguments.createMap().apply {
305
+ putBoolean("enabled", enabled)
306
+ }
307
+ sendEvent("BluetoothStateChanged", params)
308
+ }
309
+ }
310
+ }
311
+
312
+ val bluetoothFilter = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
313
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
314
+ reactContext.registerReceiver(bluetoothReceiver, bluetoothFilter, Context.RECEIVER_NOT_EXPORTED)
315
+ } else {
316
+ reactContext.registerReceiver(bluetoothReceiver, bluetoothFilter)
317
+ }
318
+
319
+ // Scan restart receiver
320
+ scanRestartReceiver = object : BroadcastReceiver() {
321
+ override fun onReceive(context: Context?, intent: Intent?) {
322
+ val params = Arguments.createMap().apply {
323
+ putDouble("timestamp", System.currentTimeMillis().toDouble())
324
+ }
325
+ sendEvent("ScanRestartRequired", params)
326
+ }
327
+ }
328
+
329
+ val restartFilter = IntentFilter(ACTION_SCAN_RESTART)
330
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
331
+ reactContext.registerReceiver(scanRestartReceiver, restartFilter, Context.RECEIVER_NOT_EXPORTED)
332
+ } else {
333
+ reactContext.registerReceiver(scanRestartReceiver, restartFilter)
334
+ }
335
+
336
+ // Buffered results receiver
337
+ bufferedResultsReceiver = object : BroadcastReceiver() {
338
+ override fun onReceive(context: Context?, intent: Intent?) {
339
+ val results = intent?.getStringExtra("results") ?: "[]"
340
+ val count = intent?.getIntExtra("count", 0) ?: 0
341
+
342
+ val params = Arguments.createMap().apply {
343
+ putString("results", results)
344
+ putInt("count", count)
345
+ putDouble("timestamp", System.currentTimeMillis().toDouble())
346
+ }
347
+ sendEvent("BufferedScanResults", params)
348
+ }
349
+ }
350
+
351
+ val bufferedFilter = IntentFilter(ACTION_BUFFERED_RESULTS)
352
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
353
+ reactContext.registerReceiver(bufferedResultsReceiver, bufferedFilter, Context.RECEIVER_NOT_EXPORTED)
354
+ } else {
355
+ reactContext.registerReceiver(bufferedResultsReceiver, bufferedFilter)
356
+ }
357
+
358
+ // Doze mode receiver
359
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
360
+ dozeModeReceiver = object : BroadcastReceiver() {
361
+ override fun onReceive(context: Context?, intent: Intent?) {
362
+ val isDozeMode = powerManager?.isDeviceIdleMode ?: false
363
+ val isBatteryOptExempt = powerManager?.isIgnoringBatteryOptimizations(
364
+ reactContext.packageName
365
+ ) ?: false
366
+
367
+ val params = Arguments.createMap().apply {
368
+ putBoolean("isDozeMode", isDozeMode)
369
+ putBoolean("isBatteryOptExempt", isBatteryOptExempt)
370
+ putDouble("timestamp", System.currentTimeMillis().toDouble())
371
+ }
372
+ sendEvent("DozeModeChanged", params)
373
+ }
374
+ }
375
+
376
+ val dozeFilter = IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)
377
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
378
+ reactContext.registerReceiver(dozeModeReceiver, dozeFilter, Context.RECEIVER_NOT_EXPORTED)
379
+ } else {
380
+ reactContext.registerReceiver(dozeModeReceiver, dozeFilter)
381
+ }
382
+ }
383
+ }
384
+
385
+ override fun invalidate() {
386
+ super.invalidate()
387
+ bluetoothReceiver?.let { reactContext.unregisterReceiver(it) }
388
+ scanRestartReceiver?.let { reactContext.unregisterReceiver(it) }
389
+ bufferedResultsReceiver?.let { reactContext.unregisterReceiver(it) }
390
+ dozeModeReceiver?.let { reactContext.unregisterReceiver(it) }
391
+ }
392
+ }