Skip to content

Commit fe0dc2f

Browse files
committed
Update Android/iOS dependencies and refactor security code
Upgraded Android and iOS build tools, Gradle, and React Native dependencies to latest versions. Refactored Android SensitiveInfo implementation for improved documentation and clarity, and updated example app to use new React Native entry points. Removed deprecated biometric and storage demo components, added new SecurityDemo, and updated project configuration for edge-to-edge support and immersive UI.
1 parent e169cf8 commit fe0dc2f

25 files changed

Lines changed: 2081 additions & 2211 deletions

File tree

README.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -622,7 +622,7 @@ struct SecurityStatusScreen: View {
622622
// Your custom UI
623623
Button("Test Secure Storage") {
624624
Task {
625-
let sensitiveInfo = SensitiveInfo()
625+
let sensitiveInfo = RNSensitiveInfo()
626626
try await sensitiveInfo.setItem(
627627
key: "test",
628628
value: "secure-data",
@@ -642,13 +642,12 @@ struct SecurityStatusScreen: View {
642642

643643
### Custom Integration
644644

645-
Use the SensitiveInfo Swift class directly in your SwiftUI views:
646-
645+
Use the RNSensitiveInfo Swift class directly in your SwiftUI views:
647646
```swift
648647
struct CustomSecureView: View {
649-
@State private var sensitiveInfo = SensitiveInfo()
648+
@State private var sensitiveInfo = RNSensitiveInfo()
650649
@State private var secureData: String = ""
651-
@State private var isLoading = false
650+
let sensitiveInfo = RNSensitiveInfo()
652651

653652
var body: some View {
654653
VStack {

android/gradle.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
SensitiveInfo_kotlinVersion=2.1.20
22
SensitiveInfo_minSdkVersion=24
3-
SensitiveInfo_targetSdkVersion=35
4-
SensitiveInfo_compileSdkVersion=35
3+
SensitiveInfo_targetSdkVersion=36
4+
SensitiveInfo_compileSdkVersion=36
55
SensitiveInfo_ndkVersion=27.1.12297006

android/src/main/java/com/margelo/nitro/sensitiveinfo/SensitiveInfo.kt

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,13 @@ import javax.crypto.KeyGenerator
1515

1616
/**
1717
* Secure storage implementation with multiple security levels.
18-
* Uses AndroidX EncryptedSharedPreferences with StrongBox when available.
19-
* Note: Biometric authentication must be handled at the JavaScript layer.
18+
*
19+
* - Standard: AES-256-GCM via EncryptedSharedPreferences
20+
* - Biometric: Same storage, but guarded by a JS-level biometric check
21+
* - StrongBox: Hardware-backed keys on supported devices (API 28+)
22+
*
23+
* Note: Biometric authentication is handled at the JS layer to allow
24+
* unified prompts and fallback behavior across platforms.
2025
*/
2126
@DoNotStrip
2227
class SensitiveInfo : HybridSensitiveInfoSpec() {
@@ -29,7 +34,7 @@ class SensitiveInfo : HybridSensitiveInfoSpec() {
2934
private const val STRONGBOX_KEYSTORE_ALIAS = "SensitiveInfoStrongBoxKey"
3035
}
3136

32-
// Standard EncryptedSharedPreferences
37+
// --- Standard EncryptedSharedPreferences ---
3338
private val standardPrefs by lazy {
3439
val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
3540

@@ -42,7 +47,7 @@ class SensitiveInfo : HybridSensitiveInfoSpec() {
4247
)
4348
}
4449

45-
// StrongBox EncryptedSharedPreferences (API 28+)
50+
// --- StrongBox EncryptedSharedPreferences (API 28+) ---
4651
private val strongBoxPrefs by lazy {
4752
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && isStrongBoxAvailableInternal()) {
4853
try {
@@ -74,7 +79,8 @@ class SensitiveInfo : HybridSensitiveInfoSpec() {
7479
}
7580
}
7681

77-
// Biometric storage uses standard encryption but requires JS-level authentication
82+
// --- Biometric ---
83+
// Uses standard encryption but requires JS-level biometric authentication
7884
private val biometricPrefs by lazy {
7985
val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
8086

@@ -87,6 +93,10 @@ class SensitiveInfo : HybridSensitiveInfoSpec() {
8793
)
8894
}
8995

96+
/**
97+
* Map a requested security level to the appropriate SharedPreferences.
98+
* Applies graceful fallbacks (biometric -> strongbox -> standard).
99+
*/
90100
private fun getPreferencesForSecurityLevel(securityLevel: SecurityLevel?): android.content.SharedPreferences {
91101
return when (securityLevel) {
92102
SecurityLevel.BIOMETRIC -> {
@@ -115,51 +125,69 @@ class SensitiveInfo : HybridSensitiveInfoSpec() {
115125
}
116126
}
117127

128+
/**
129+
* Get an item by key at the requested security level.
130+
*/
118131
@DoNotStrip
119132
override fun getItem(key: String, options: StorageOptions?): Promise<String?> = Promise.async {
120133
val securityLevel = options?.securityLevel
121134
val prefs = getPreferencesForSecurityLevel(securityLevel)
122135
prefs.getString(key, null)
123136
}
124137

138+
/**
139+
* Store a key/value pair at the requested security level.
140+
*/
125141
@DoNotStrip
126142
override fun setItem(key: String, value: String, options: StorageOptions?): Promise<Unit> = Promise.async {
127143
val securityLevel = options?.securityLevel
128144
val prefs = getPreferencesForSecurityLevel(securityLevel)
129145
prefs.edit().putString(key, value).apply()
130146
}
131147

148+
/**
149+
* Remove a single item.
150+
*/
132151
@DoNotStrip
133152
override fun removeItem(key: String, options: StorageOptions?): Promise<Unit> = Promise.async {
134153
val securityLevel = options?.securityLevel
135154
val prefs = getPreferencesForSecurityLevel(securityLevel)
136155
prefs.edit().remove(key).apply()
137156
}
138157

158+
/**
159+
* Return all items for the selected backing store.
160+
*/
139161
@DoNotStrip
140162
override fun getAllItems(options: StorageOptions?): Promise<Map<String, String>> = Promise.async {
141163
val securityLevel = options?.securityLevel
142164
val prefs = getPreferencesForSecurityLevel(securityLevel)
143165
prefs.all.mapValues { it.value as? String ?: "" }
144166
}
145167

168+
/**
169+
* Clear all items for the selected backing store.
170+
*/
146171
@DoNotStrip
147172
override fun clear(options: StorageOptions?): Promise<Unit> = Promise.async {
148173
val securityLevel = options?.securityLevel
149174
val prefs = getPreferencesForSecurityLevel(securityLevel)
150175
prefs.edit().clear().apply()
151176
}
152177

178+
/** Check if biometric authentication is available on this device. */
153179
@DoNotStrip
154180
override fun isBiometricAvailable(): Promise<Boolean> = Promise.async {
155181
isBiometricAvailableInternal()
156182
}
157183

184+
/** Check if StrongBox hardware security is available. */
158185
@DoNotStrip
159186
override fun isStrongBoxAvailable(): Promise<Boolean> = Promise.async {
160187
isStrongBoxAvailableInternal()
161188
}
162189

190+
/** Internal: Detect biometric availability via BiometricManager. */
163191
private fun isBiometricAvailableInternal(): Boolean {
164192
val biometricManager = BiometricManager.from(context)
165193
return when (biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG)) {
@@ -168,6 +196,7 @@ class SensitiveInfo : HybridSensitiveInfoSpec() {
168196
}
169197
}
170198

199+
/** Internal: Probe StrongBox by generating a temporary key. */
171200
private fun isStrongBoxAvailableInternal(): Boolean {
172201
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
173202
try {

example/android/app/build.gradle

Lines changed: 0 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -7,49 +7,6 @@ apply plugin: "com.facebook.react"
77
* By default you don't need to apply any configuration, just uncomment the lines you need.
88
*/
99
react {
10-
/* Folders */
11-
// The root of your project, i.e. where "package.json" lives. Default is '../..'
12-
// root = file("../../")
13-
// The folder where the react-native NPM package is. Default is ../../node_modules/react-native
14-
// reactNativeDir = file("../../node_modules/react-native")
15-
// The folder where the react-native Codegen package is. Default is ../../node_modules/@react-native/codegen
16-
// codegenDir = file("../../node_modules/@react-native/codegen")
17-
// The cli.js file which is the React Native CLI entrypoint. Default is ../../node_modules/react-native/cli.js
18-
// cliFile = file("../../node_modules/react-native/cli.js")
19-
20-
/* Variants */
21-
// The list of variants to that are debuggable. For those we're going to
22-
// skip the bundling of the JS bundle and the assets. By default is just 'debug'.
23-
// If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants.
24-
// debuggableVariants = ["liteDebug", "prodDebug"]
25-
26-
/* Bundling */
27-
// A list containing the node command and its flags. Default is just 'node'.
28-
// nodeExecutableAndArgs = ["node"]
29-
//
30-
// The command to run when bundling. By default is 'bundle'
31-
// bundleCommand = "ram-bundle"
32-
//
33-
// The path to the CLI configuration file. Default is empty.
34-
// bundleConfig = file(../rn-cli.config.js)
35-
//
36-
// The name of the generated asset file containing your JS bundle
37-
// bundleAssetName = "MyApplication.android.bundle"
38-
//
39-
// The entry file for bundle generation. Default is 'index.android.js' or 'index.js'
40-
// entryFile = file("../js/MyApplication.android.js")
41-
//
42-
// A list of extra flags to pass to the 'bundle' commands.
43-
// See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle
44-
// extraPackagerArgs = []
45-
46-
/* Hermes Commands */
47-
// The hermes compiler command to run. By default it is 'hermesc'
48-
// hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc"
49-
//
50-
// The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
51-
// hermesFlags = ["-O", "-output-source-map"]
52-
5310
/* Autolinking */
5411
autolinkLibrariesWithApp()
5512
}

example/android/app/src/main/java/sensitiveinfo/example/MainApplication.kt

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,9 @@ import com.facebook.react.ReactApplication
66
import com.facebook.react.ReactHost
77
import com.facebook.react.ReactNativeHost
88
import com.facebook.react.ReactPackage
9-
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
9+
import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
1010
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
1111
import com.facebook.react.defaults.DefaultReactNativeHost
12-
import com.facebook.react.soloader.OpenSourceMergedSoMapping
13-
import com.facebook.soloader.SoLoader
1412

1513
class MainApplication : Application(), ReactApplication {
1614

@@ -35,10 +33,7 @@ class MainApplication : Application(), ReactApplication {
3533

3634
override fun onCreate() {
3735
super.onCreate()
38-
SoLoader.init(this, OpenSourceMergedSoMapping)
39-
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
40-
// If you opted-in for the New Architecture, we load the native entry point for this app.
41-
load()
42-
}
36+
37+
loadReactNative(this)
4338
}
4439
}

example/android/build.gradle

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
buildscript {
22
ext {
3-
buildToolsVersion = "35.0.0"
3+
buildToolsVersion = "36.0.0"
44
minSdkVersion = 24
5-
compileSdkVersion = 35
6-
targetSdkVersion = 35
5+
compileSdkVersion = 36
6+
targetSdkVersion = 36
77
ndkVersion = "27.1.12297006"
8-
kotlinVersion = "2.0.21"
8+
kotlinVersion = "2.1.20"
99
}
1010
repositories {
1111
google()

example/android/gradle.properties

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,8 @@ newArchEnabled=true
3737
# Use this property to enable or disable the Hermes JS engine.
3838
# If set to false, you will be using JSC instead.
3939
hermesEnabled=true
40+
41+
# Use this property to enable edge-to-edge display support.
42+
# This allows your app to draw behind system bars for an immersive UI.
43+
# Note: Only works with ReactActivity and should not be used with custom Activity.
44+
edgeToEdgeEnabled=true
59 Bytes
Binary file not shown.

example/android/gradle/wrapper/gradle-wrapper.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
44
networkTimeout=10000
55
validateDistributionUrl=true
66
zipStoreBase=GRADLE_USER_HOME

example/android/gradlew

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)