@osano/osano-cmp-react-native 0.13.0 → 0.15.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 (145) hide show
  1. package/README.dev.md +237 -12
  2. package/android/build.gradle +1 -1
  3. package/android/src/main/java/com/OsanoCMPSDK/OsanoCMPSDKModule.kt +147 -64
  4. package/android/src/main/java/com/OsanoCMPSDK/OsanoCMPSDKPackage.kt +0 -1
  5. package/ios/OsanoCmpSDK.mm +33 -3
  6. package/ios/OsanoCmpSdk.h +0 -7
  7. package/lib/commonjs/ConsentManager.js +32 -4
  8. package/lib/commonjs/ConsentManager.js.map +1 -1
  9. package/lib/commonjs/components/Button.js +105 -95
  10. package/lib/commonjs/components/Button.js.map +1 -1
  11. package/lib/commonjs/components/Locale.js +5 -3
  12. package/lib/commonjs/components/Locale.js.map +1 -1
  13. package/lib/commonjs/index.js +1 -1
  14. package/lib/commonjs/index.js.map +1 -1
  15. package/lib/commonjs/native/OsanoCmp.js +35 -8
  16. package/lib/commonjs/native/OsanoCmp.js.map +1 -1
  17. package/lib/commonjs/native/OsanoCmpSdkSpec.js +9 -0
  18. package/lib/commonjs/native/OsanoCmpSdkSpec.js.map +1 -0
  19. package/lib/commonjs/services/consent.js +6 -2
  20. package/lib/commonjs/services/consent.js.map +1 -1
  21. package/lib/commonjs/utils/ExpoDetector.js +105 -0
  22. package/lib/commonjs/utils/ExpoDetector.js.map +1 -0
  23. package/lib/commonjs/utils/ExpoGoMemoryStorage.js +61 -0
  24. package/lib/commonjs/utils/ExpoGoMemoryStorage.js.map +1 -0
  25. package/lib/commonjs/views/Dialog/Buttons.js +26 -16
  26. package/lib/commonjs/views/Dialog/Buttons.js.map +1 -1
  27. package/lib/commonjs/views/Dialog/index.js +58 -95
  28. package/lib/commonjs/views/Dialog/index.js.map +1 -1
  29. package/lib/commonjs/views/Drawer/Drawer.ConsentCategories.js +12 -13
  30. package/lib/commonjs/views/Drawer/Drawer.ConsentCategories.js.map +1 -1
  31. package/lib/commonjs/views/Drawer/Drawer.Header.js +2 -2
  32. package/lib/commonjs/views/Drawer/Drawer.Header.js.map +1 -1
  33. package/lib/commonjs/views/Drawer/Drawer.Iab.Disclosures.js +10 -1
  34. package/lib/commonjs/views/Drawer/Drawer.Iab.Disclosures.js.map +1 -1
  35. package/lib/commonjs/views/Drawer/index.js +5 -4
  36. package/lib/commonjs/views/Drawer/index.js.map +1 -1
  37. package/lib/module/ConsentManager.js +32 -4
  38. package/lib/module/ConsentManager.js.map +1 -1
  39. package/lib/module/components/Button.js +99 -88
  40. package/lib/module/components/Button.js.map +1 -1
  41. package/lib/module/components/Locale.js +4 -3
  42. package/lib/module/components/Locale.js.map +1 -1
  43. package/lib/module/index.js +1 -1
  44. package/lib/module/index.js.map +1 -1
  45. package/lib/module/native/OsanoCmp.js +34 -8
  46. package/lib/module/native/OsanoCmp.js.map +1 -1
  47. package/lib/module/native/OsanoCmpSdkSpec.js +3 -0
  48. package/lib/module/native/OsanoCmpSdkSpec.js.map +1 -0
  49. package/lib/module/services/consent.js +7 -3
  50. package/lib/module/services/consent.js.map +1 -1
  51. package/lib/module/utils/ExpoDetector.js +98 -0
  52. package/lib/module/utils/ExpoDetector.js.map +1 -0
  53. package/lib/module/utils/ExpoGoMemoryStorage.js +54 -0
  54. package/lib/module/utils/ExpoGoMemoryStorage.js.map +1 -0
  55. package/lib/module/views/Dialog/Buttons.js +27 -17
  56. package/lib/module/views/Dialog/Buttons.js.map +1 -1
  57. package/lib/module/views/Dialog/index.js +59 -96
  58. package/lib/module/views/Dialog/index.js.map +1 -1
  59. package/lib/module/views/Drawer/Drawer.ConsentCategories.js +10 -12
  60. package/lib/module/views/Drawer/Drawer.ConsentCategories.js.map +1 -1
  61. package/lib/module/views/Drawer/Drawer.Header.js +2 -2
  62. package/lib/module/views/Drawer/Drawer.Header.js.map +1 -1
  63. package/lib/module/views/Drawer/Drawer.Iab.Disclosures.js +10 -1
  64. package/lib/module/views/Drawer/Drawer.Iab.Disclosures.js.map +1 -1
  65. package/lib/module/views/Drawer/index.js +6 -6
  66. package/lib/module/views/Drawer/index.js.map +1 -1
  67. package/lib/typescript/lib/commonjs/ConsentManager.d.ts.map +1 -1
  68. package/lib/typescript/lib/commonjs/components/Button.d.ts +4 -4
  69. package/lib/typescript/lib/commonjs/components/Button.d.ts.map +1 -1
  70. package/lib/typescript/lib/commonjs/components/Locale.d.ts.map +1 -1
  71. package/lib/typescript/lib/commonjs/hooks/useDeviceOrientation.d.ts +1 -1
  72. package/lib/typescript/lib/commonjs/native/OsanoCmp.d.ts.map +1 -1
  73. package/lib/typescript/lib/commonjs/native/OsanoCmpSdkSpec.d.ts +5 -0
  74. package/lib/typescript/lib/commonjs/native/OsanoCmpSdkSpec.d.ts.map +1 -0
  75. package/lib/typescript/lib/commonjs/services/consent.d.ts.map +1 -1
  76. package/lib/typescript/lib/commonjs/utils/ExpoDetector.d.ts +28 -0
  77. package/lib/typescript/lib/commonjs/utils/ExpoDetector.d.ts.map +1 -0
  78. package/lib/typescript/lib/commonjs/utils/ExpoGoMemoryStorage.d.ts +22 -0
  79. package/lib/typescript/lib/commonjs/utils/ExpoGoMemoryStorage.d.ts.map +1 -0
  80. package/lib/typescript/lib/commonjs/views/Dialog/Buttons.d.ts.map +1 -1
  81. package/lib/typescript/lib/commonjs/views/Dialog/index.d.ts.map +1 -1
  82. package/lib/typescript/lib/commonjs/views/Drawer/Drawer.ConsentCategories.d.ts.map +1 -1
  83. package/lib/typescript/lib/commonjs/views/Drawer/Drawer.Iab.Disclosures.d.ts.map +1 -1
  84. package/lib/typescript/lib/commonjs/views/Drawer/index.d.ts.map +1 -1
  85. package/lib/typescript/lib/module/ConsentManager.d.ts.map +1 -1
  86. package/lib/typescript/lib/module/components/Button.d.ts +7 -7
  87. package/lib/typescript/lib/module/components/Button.d.ts.map +1 -1
  88. package/lib/typescript/lib/module/components/Locale.d.ts +4 -1
  89. package/lib/typescript/lib/module/components/Locale.d.ts.map +1 -1
  90. package/lib/typescript/lib/module/hooks/useDeviceOrientation.d.ts +1 -1
  91. package/lib/typescript/lib/module/native/OsanoCmp.d.ts.map +1 -1
  92. package/lib/typescript/lib/module/native/OsanoCmpSdkSpec.d.ts +3 -0
  93. package/lib/typescript/lib/module/native/OsanoCmpSdkSpec.d.ts.map +1 -0
  94. package/lib/typescript/lib/module/services/consent.d.ts.map +1 -1
  95. package/lib/typescript/lib/module/utils/ExpoDetector.d.ts +27 -0
  96. package/lib/typescript/lib/module/utils/ExpoDetector.d.ts.map +1 -0
  97. package/lib/typescript/lib/module/utils/ExpoGoMemoryStorage.d.ts +21 -0
  98. package/lib/typescript/lib/module/utils/ExpoGoMemoryStorage.d.ts.map +1 -0
  99. package/lib/typescript/lib/module/views/Dialog/Buttons.d.ts.map +1 -1
  100. package/lib/typescript/lib/module/views/Dialog/index.d.ts.map +1 -1
  101. package/lib/typescript/lib/module/views/Drawer/Drawer.ConsentCategories.d.ts.map +1 -1
  102. package/lib/typescript/lib/module/views/Drawer/Drawer.Header.d.ts.map +1 -1
  103. package/lib/typescript/lib/module/views/Drawer/Drawer.Iab.Disclosures.d.ts.map +1 -1
  104. package/lib/typescript/lib/module/views/Drawer/index.d.ts.map +1 -1
  105. package/lib/typescript/src/ConsentManager.d.ts.map +1 -1
  106. package/lib/typescript/src/components/Button.d.ts +7 -7
  107. package/lib/typescript/src/components/Button.d.ts.map +1 -1
  108. package/lib/typescript/src/components/Locale.d.ts +2 -1
  109. package/lib/typescript/src/components/Locale.d.ts.map +1 -1
  110. package/lib/typescript/src/native/OsanoCmp.d.ts.map +1 -1
  111. package/lib/typescript/src/native/OsanoCmpSdkSpec.d.ts +17 -0
  112. package/lib/typescript/src/native/OsanoCmpSdkSpec.d.ts.map +1 -0
  113. package/lib/typescript/src/services/consent.d.ts.map +1 -1
  114. package/lib/typescript/src/utils/ExpoDetector.d.ts +29 -0
  115. package/lib/typescript/src/utils/ExpoDetector.d.ts.map +1 -0
  116. package/lib/typescript/src/utils/ExpoGoMemoryStorage.d.ts +21 -0
  117. package/lib/typescript/src/utils/ExpoGoMemoryStorage.d.ts.map +1 -0
  118. package/lib/typescript/src/views/Dialog/Buttons.d.ts +4 -2
  119. package/lib/typescript/src/views/Dialog/Buttons.d.ts.map +1 -1
  120. package/lib/typescript/src/views/Dialog/index.d.ts +1 -1
  121. package/lib/typescript/src/views/Dialog/index.d.ts.map +1 -1
  122. package/lib/typescript/src/views/Drawer/Drawer.ConsentCategories.d.ts.map +1 -1
  123. package/lib/typescript/src/views/Drawer/Drawer.Header.d.ts +1 -1
  124. package/lib/typescript/src/views/Drawer/Drawer.Header.d.ts.map +1 -1
  125. package/lib/typescript/src/views/Drawer/Drawer.Iab.Disclosures.d.ts.map +1 -1
  126. package/lib/typescript/src/views/Drawer/index.d.ts.map +1 -1
  127. package/osano-cmp-sdk.podspec +15 -15
  128. package/package.json +28 -3
  129. package/src/ConsentManager.ts +37 -4
  130. package/src/components/Button.tsx +122 -88
  131. package/src/components/Locale.tsx +28 -28
  132. package/src/constants.ts +2 -2
  133. package/src/index.tsx +1 -1
  134. package/src/native/OsanoCmp.ts +39 -23
  135. package/src/native/OsanoCmpSdkSpec.ts +25 -0
  136. package/src/services/consent.ts +9 -3
  137. package/src/utils/ExpoDetector.ts +114 -0
  138. package/src/utils/ExpoGoMemoryStorage.ts +61 -0
  139. package/src/views/Dialog/Buttons.tsx +33 -23
  140. package/src/views/Dialog/index.tsx +164 -208
  141. package/src/views/Drawer/Drawer.ConsentCategories.tsx +25 -25
  142. package/src/views/Drawer/Drawer.Header.tsx +7 -7
  143. package/src/views/Drawer/Drawer.Iab.Disclosures.tsx +6 -3
  144. package/src/views/Drawer/index.tsx +20 -19
  145. package/android/src/main/AndroidManifestNew.xml +0 -2
package/README.dev.md CHANGED
@@ -34,35 +34,229 @@ corepack yarn example android # or ios
34
34
  cd example && corepack yarn start android # or ios
35
35
  ```
36
36
 
37
- ### Specifying run destination
37
+ ## Architecture Testing
38
38
 
39
- **iOS**
39
+ This project supports testing for both React Native Legacy Architecture and New Architecture (TurboModules/Fabric). The testing framework provides automated testing across both architectures to ensure compatibility.
40
40
 
41
- `corepack yarn example ios --simulator "iPhone 15 Pro"`
41
+ ### Automated Architecture Testing
42
42
 
43
- **Android**
43
+ The project includes a comprehensive testing framework that can automatically test both architectures without manual configuration changes.
44
44
 
45
- `// TODO`
45
+ #### Available Test Commands
46
+
47
+ **Basic Test Commands:**
48
+
49
+ ```bash
50
+ # Run all tests (default - uses current architecture settings)
51
+ yarn test
52
+
53
+ # Run tests with Legacy Architecture
54
+ yarn test:legacy
55
+
56
+ # Run tests with New Architecture
57
+ yarn test:newarch
58
+ ```
59
+
60
+ #### CI/CD Integration
61
+
62
+ The project includes automated GitHub workflows that run comprehensive tests on all pull requests to the main branch.
63
+
64
+ **GitHub Workflows:**
65
+
66
+ - **Test Suite (`test.yml`)**: Automatically runs all tests for both Legacy and New Architecture when PRs are opened
67
+ - **Build Validation (`build.yml`)**: Ensures the project builds successfully
68
+ - **Commit Validation (`validate-commits.yml`)**: Validates commit message format with Jira ticket IDs
69
+
70
+ For manual CI/CD integration, you can run both architecture tests:
71
+
72
+ ```yaml
73
+ # GitHub Actions example
74
+ - name: Test Legacy Architecture
75
+ run: yarn test:legacy
76
+
77
+ - name: Test New Architecture
78
+ run: yarn test:newarch
79
+ ```
80
+
81
+ The automated workflow uses a matrix strategy to test both architectures in parallel, ensuring all tests pass before allowing PR merges.
82
+
83
+ ### Manual Architecture Testing (Example App)
84
+
85
+ For manual testing of the example application with different architectures:
86
+
87
+ #### Testing with Legacy Architecture (Default)
88
+
89
+ The legacy architecture uses React Native's bridge system for native module communication.
90
+
91
+ **Setup for Legacy Architecture:**
92
+
93
+ 1. **iOS Setup:**
94
+ ```shell
95
+ # Navigate to iOS example directory
96
+ cd example/ios
97
+
98
+ # Edit Podfile to disable new architecture
99
+ # Comment out or remove this line:
100
+ # ENV['RCT_NEW_ARCH_ENABLED'] = '1'
101
+
102
+ # Clean and reinstall pods
103
+ rm -rf Pods Podfile.lock
104
+ pod install
105
+ cd ../..
106
+ ```
107
+
108
+ 2. **Android Setup:**
109
+ ```shell
110
+ # Navigate to Android example directory
111
+ cd example/android
112
+
113
+ # Edit gradle.properties to disable new architecture
114
+ # Add or modify:
115
+ newArchEnabled=false
116
+
117
+ # Clean build
118
+ ./gradlew clean
119
+ cd ../..
120
+ ```
121
+
122
+ 3. **Run the app:**
123
+ ```shell
124
+ # iOS
125
+ corepack yarn example ios
126
+
127
+ # Android
128
+ corepack yarn example android
129
+ ```
130
+
131
+ #### Testing with New Architecture (TurboModules)
132
+
133
+ The new architecture uses TurboModules for improved performance and type safety.
134
+
135
+ **Setup for New Architecture:**
136
+
137
+ 1. **iOS Setup:**
138
+ ```shell
139
+ # Navigate to iOS example directory
140
+ cd example/ios
141
+
142
+ # Edit Podfile to enable new architecture
143
+ # Add or uncomment this line:
144
+ ENV['RCT_NEW_ARCH_ENABLED'] = '1'
145
+
146
+ # Clean and reinstall pods with new architecture
147
+ rm -rf Pods Podfile.lock
148
+ pod install
149
+ cd ../..
150
+ ```
151
+
152
+ 2. **Android Setup:**
153
+ ```shell
154
+ # Navigate to Android example directory
155
+ cd example/android
156
+
157
+ # Edit gradle.properties to enable new architecture
158
+ # Add or modify:
159
+ newArchEnabled=true
160
+
161
+ # Clean build
162
+ ./gradlew clean
163
+ cd ../..
164
+ ```
165
+
166
+ 3. **Rebuild the JavaScript bundle:**
167
+ ```shell
168
+ # Regenerate code for new architecture
169
+ corepack yarn prepare
170
+ ```
171
+
172
+ 4. **Run the app:**
173
+ ```shell
174
+ # iOS with new architecture
175
+ corepack yarn example ios
176
+
177
+ # Android with new architecture
178
+ corepack yarn example android
179
+ ```
180
+
181
+ #### Quick Switch Scripts
182
+
183
+ **Quick Switch Script (iOS):**
184
+
185
+ ```shell
186
+ # Switch to Legacy
187
+ cd example/ios
188
+ sed -i '' "s/ENV\['RCT_NEW_ARCH_ENABLED'\] = '1'/# ENV['RCT_NEW_ARCH_ENABLED'] = '1'/" Podfile
189
+ rm -rf Pods Podfile.lock && pod install && cd ../..
190
+
191
+ # Switch to New Architecture
192
+ cd example/ios
193
+ sed -i '' "s/# ENV\['RCT_NEW_ARCH_ENABLED'\] = '1'/ENV['RCT_NEW_ARCH_ENABLED'] = '1'/" Podfile
194
+ rm -rf Pods Podfile.lock && pod install && cd ../..
195
+ ```
196
+
197
+ **Quick Switch Script (Android):**
198
+
199
+ ```shell
200
+ # Switch to Legacy
201
+ cd example/android
202
+ sed -i '' 's/newArchEnabled=true/newArchEnabled=false/' gradle.properties
203
+ ./gradlew clean && cd ../..
204
+
205
+ # Switch to New Architecture
206
+ cd example/android
207
+ sed -i '' 's/newArchEnabled=false/newArchEnabled=true/' gradle.properties
208
+ ./gradlew clean && cd ../..
209
+ ```
46
210
 
47
211
  ## Running Tests
48
212
 
49
- Testing should be done through both the example integration and the package itself.
213
+ Testing should be done through both the example integration and the package itself. The project includes comprehensive architecture testing to ensure compatibility with both Legacy and New Architecture.
50
214
 
51
- ### Testing the package
215
+ ### Architecture Testing
52
216
 
53
- 1. From the `packages/osano-comp-react-native` folder run the following. This specifically runs the tests associated with the main package
217
+ **Automated Testing Across Architectures:**
54
218
 
55
219
  ```shell
56
- npm test /src/__tests__
220
+ # Test with Legacy Architecture only
221
+ yarn test:legacy
222
+
223
+ # Test with New Architecture only
224
+ yarn test:newarch
225
+
226
+ # Test both architectures sequentially
227
+ yarn test:both
228
+
229
+ # Interactive test runner with comparison
230
+ yarn run-arch-tests compare
231
+ ```
232
+
233
+ #### Testing the Package
234
+
235
+ 1. **Run architecture-specific tests** from the root folder:
236
+
237
+ ```shell
238
+ # Run all tests with current architecture
239
+ yarn test
240
+
241
+ # Run tests across both architectures
242
+ yarn test:both
243
+ ```
244
+
245
+ 2. **Run all test files in the package** (including IAB tests):
246
+
247
+ ```shell
248
+ yarn test
57
249
  ```
58
250
 
59
- 1. In order to test all test files in the package, including IAB, run the following from the `packages/osano-comp-react-native` folder
251
+ 3. **Run specific test files:**
60
252
 
61
253
  ```shell
62
- npm test
254
+ # Test specific file patterns
255
+ yarn test src/__tests__/index.test.tsx
256
+ yarn test src/services/__tests__/
63
257
  ```
64
258
 
65
- ### Testing the examples
259
+ ### Testing the Examples
66
260
 
67
261
  #### Build the test applications from local builds
68
262
 
@@ -81,6 +275,25 @@ npm test
81
275
 
82
276
  Both files can be sent to coworkers with an ios simulator or android emulator to run for testing
83
277
 
278
+ ## Publishing
279
+
280
+ When publishing to NPM, the package version is automatically incremented by 1. This ensures that each published version has a unique version number for proper semantic versioning.
281
+
282
+ **Version Increment Examples:**
283
+ - If the current `package.json` version is `0.14.0`, the NPM published version will be `0.15.0`
284
+
285
+ **Publishing Process:**
286
+ 1. Create a pull request with your changes
287
+ 2. Once the PR is reviewed and merged to `main`, the GitHub Actions workflow automatically:
288
+ - Builds the package
289
+ - Publishes to NPM with the new version
290
+ - Creates a git tag with the new version
291
+
292
+ **Important Notes:**
293
+ - The version increment happens automatically during the `yarn release:prod` command
294
+ - Manually update the version in `package.json` before creating your PR
295
+ - Each merge to `main` results in a new NPM package version
296
+
84
297
  ## Troubleshooting
85
298
 
86
299
  1. If the `run` command fails saying there is no development project, make sure to open the relevant simulator/emulator before running.
@@ -152,6 +365,18 @@ import { Osano, OsanoContext } from '@osano/osano-cmp-react-native'
152
365
 
153
366
  Note that the `configId` and `customerId` parameters are not optional, and must match the values of your configuration on the [Osano website](https://my.osano.com).
154
367
 
368
+ To associate an external user identifier with the stored consent record, use `extUsrData`. This identifier is essential for
369
+ [Cross-Device Consent](https://docs.osano.com/hc/en-us/articles/22472143433364-Cross-Domain-and-Cross-Device-Consent).
370
+
371
+ By providing the same ID across devices and platforms (e.g., web and mobile), the SDK can look up and apply the user’s existing consent record, ensuring consistency across environments. For more internal details, see our [Cross-Device Consent Lookups documentation](https://osano.atlassian.net/wiki/spaces/CMPM/pages/604078081/Cross-Device+Consent+Lookups).
372
+
373
+ > **_Important considerations_**
374
+ >
375
+ > - SDK behavior: All Osano SDKs will always record `extUsrData`. This is required for cross-device consent to function properly.
376
+ > - Web CMP behavior: The Web CMP will not record `extUsrData` unless cross-device consent is explicitly enabled in your configuration. If cross-device consent is not enabled, the parameter will be ignored.
377
+ >
378
+ > Because of this distinction, developers should not expect extUsrData to have any effect in the Web CMP unless cross-device consent is turned on.
379
+
155
380
  #### Logging Configuration
156
381
 
157
382
  The SDK includes built-in logging for debugging. Use `setLoggingEnabled={__DEV__}` to enable logging in development and disable it in production. All logs are prefixed with `[OsanoSDK]`.
@@ -8,7 +8,7 @@ buildscript {
8
8
  }
9
9
 
10
10
  dependencies {
11
- classpath "com.android.tools.build:gradle:7.2.1"
11
+ classpath "com.android.tools.build:gradle:7.4.2"
12
12
  // noinspection DifferentKotlinGradleVersion
13
13
  classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
14
14
  }
@@ -4,110 +4,193 @@ import com.facebook.react.bridge.ReactApplicationContext
4
4
  import com.facebook.react.bridge.ReactContextBaseJavaModule
5
5
  import com.facebook.react.bridge.ReactMethod
6
6
  import com.facebook.react.bridge.Promise
7
+ import com.facebook.react.module.annotations.ReactModule
7
8
  import android.content.Context
9
+ import android.content.SharedPreferences
8
10
  import android.provider.Settings
9
11
  import android.preference.PreferenceManager
10
12
  import java.util.UUID
11
13
 
14
+ @ReactModule(name = OsanoCMPSDKModule.NAME)
12
15
  class OsanoCMPSDKModule(reactContext: ReactApplicationContext) :
13
- ReactContextBaseJavaModule(reactContext) {
16
+ ReactContextBaseJavaModule(reactContext) {
14
17
 
15
- override fun getName(): String {
16
- return NAME
17
- }
18
+ // Lazy initialization for better performance
19
+ private val appContext: Context by lazy { reactApplicationContext.applicationContext }
20
+ private val sharedPreferences: SharedPreferences by lazy {
21
+ appContext.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE)
22
+ }
23
+ private val defaultSharedPreferences: SharedPreferences by lazy {
24
+ PreferenceManager.getDefaultSharedPreferences(appContext)
25
+ }
26
+
27
+ override fun getName(): String = NAME
18
28
 
19
- @ReactMethod
29
+ @ReactMethod
20
30
  fun setStorage(key: String?, consentString: String?, promise: Promise) {
21
- val appContext = this.reactApplicationContext.applicationContext
22
- val sharedPreference = appContext.getSharedPreferences("com.osano.cmp.sdk", Context.MODE_PRIVATE)
23
- val editor = sharedPreference.edit()
24
- editor.putString(key, consentString)
25
- editor.apply()
26
- promise.resolve(consentString)
31
+ executeStorageOperation(promise, "Failed to set storage") {
32
+ if (key.isNullOrBlank()) {
33
+ promise.reject(ERROR_INVALID_KEY, "Key cannot be null or empty")
34
+ return@executeStorageOperation
35
+ }
36
+
37
+ sharedPreferences.edit()
38
+ .putString(key, consentString)
39
+ .apply()
40
+ promise.resolve(consentString)
41
+ }
27
42
  }
28
43
 
29
44
  @ReactMethod
30
45
  fun getStorage(key: String?, promise: Promise) {
31
- val appContext = this.reactApplicationContext.applicationContext
32
- val sharedPreference = appContext.getSharedPreferences("com.osano.cmp.sdk", Context.MODE_PRIVATE)
33
- val consentString = sharedPreference.getString(key, "")
34
- promise.resolve(consentString)
46
+ executeStorageOperation(promise, "Failed to get storage") {
47
+ if (key.isNullOrBlank()) {
48
+ promise.reject(ERROR_INVALID_KEY, "Key cannot be null or empty")
49
+ return@executeStorageOperation
50
+ }
51
+
52
+ val consentString = sharedPreferences.getString(key, "")
53
+ promise.resolve(consentString)
54
+ }
35
55
  }
36
56
 
37
57
  @ReactMethod
38
58
  fun clearStorage(key: String?, promise: Promise) {
39
- val appContext = this.reactApplicationContext.applicationContext
40
- val sharedPreference = appContext.getSharedPreferences("com.osano.cmp.sdk", Context.MODE_PRIVATE)
41
- val editor = sharedPreference.edit()
42
- editor.remove(key)
43
- editor.apply()
44
- promise.resolve(null)
59
+ executeStorageOperation(promise, "Failed to clear storage") {
60
+ if (key.isNullOrBlank()) {
61
+ promise.reject(ERROR_INVALID_KEY, "Key cannot be null or empty")
62
+ return@executeStorageOperation
63
+ }
64
+
65
+ sharedPreferences.edit()
66
+ .remove(key)
67
+ .apply()
68
+ promise.resolve(null)
69
+ }
45
70
  }
46
71
 
47
72
  @ReactMethod
48
73
  fun setStorageToDefault(key: String?, value: String?, promise: Promise) {
49
- val appContext = this.reactApplicationContext.applicationContext
50
- val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(appContext)
51
- val editor = defaultSharedPreferences.edit()
52
- editor.putString(key, value)
53
- editor.apply()
54
- promise.resolve(value)
74
+ executeStorageOperation(promise, "Failed to set default storage") {
75
+ if (key.isNullOrBlank()) {
76
+ promise.reject(ERROR_INVALID_KEY, "Key cannot be null or empty")
77
+ return@executeStorageOperation
78
+ }
79
+
80
+ defaultSharedPreferences.edit()
81
+ .putString(key, value)
82
+ .apply()
83
+ promise.resolve(value)
84
+ }
55
85
  }
56
86
 
57
87
  @ReactMethod
58
88
  fun setIntStorageToDefault(key: String?, value: Int, promise: Promise) {
59
- val appContext = this.reactApplicationContext.applicationContext
60
- val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(appContext)
61
- val editor = defaultSharedPreferences.edit()
62
- editor.putInt(key, value)
63
- editor.apply()
64
- promise.resolve(value)
89
+ executeStorageOperation(promise, "Failed to set int default storage") {
90
+ if (key.isNullOrBlank()) {
91
+ promise.reject(ERROR_INVALID_KEY, "Key cannot be null or empty")
92
+ return@executeStorageOperation
93
+ }
94
+
95
+ defaultSharedPreferences.edit()
96
+ .putInt(key, value)
97
+ .apply()
98
+ promise.resolve(value)
99
+ }
65
100
  }
66
101
 
67
102
  @ReactMethod
68
103
  fun setBoolStorageToDefault(key: String?, value: Boolean, promise: Promise) {
69
- val appContext = this.reactApplicationContext.applicationContext
70
- val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(appContext)
71
- val editor = defaultSharedPreferences.edit()
72
- editor.putBoolean(key, value)
73
- editor.apply()
74
- promise.resolve(value)
104
+ executeStorageOperation(promise, "Failed to set bool default storage") {
105
+ if (key.isNullOrBlank()) {
106
+ promise.reject(ERROR_INVALID_KEY, "Key cannot be null or empty")
107
+ return@executeStorageOperation
108
+ }
109
+
110
+ defaultSharedPreferences.edit()
111
+ .putBoolean(key, value)
112
+ .apply()
113
+ promise.resolve(value)
114
+ }
75
115
  }
76
116
 
77
117
  @ReactMethod
78
118
  fun clearStorageFromDefault(key: String?, promise: Promise) {
79
- val appContext = this.reactApplicationContext.applicationContext
80
- val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(appContext)
81
- val editor = defaultSharedPreferences.edit()
82
- editor.remove(key)
83
- editor.apply()
84
- promise.resolve(null)
119
+ executeStorageOperation(promise, "Failed to clear default storage") {
120
+ if (key.isNullOrBlank()) {
121
+ promise.reject(ERROR_INVALID_KEY, "Key cannot be null or empty")
122
+ return@executeStorageOperation
123
+ }
124
+
125
+ defaultSharedPreferences.edit()
126
+ .remove(key)
127
+ .apply()
128
+ promise.resolve(null)
129
+ }
85
130
  }
86
131
 
87
132
  @ReactMethod
88
133
  fun getDeviceId(promise: Promise) {
89
- // Get a unique vendor ID for the android device
90
- val appContext = this.reactApplicationContext.applicationContext
91
- val sharedPreference = appContext.getSharedPreferences("com.osano.cmp.sdk", Context.MODE_PRIVATE)
92
- val uuid = sharedPreference.getString("uuid", null)
134
+ executeStorageOperation(promise, "Failed to get device ID") {
135
+ val deviceId = getOrCreateDeviceId()
136
+ promise.resolve(deviceId)
137
+ }
138
+ }
139
+
140
+ override fun getConstants(): MutableMap<String, Any> {
141
+ return try {
142
+ val appId = reactApplicationContext.packageName
143
+ val isOfficialSample = SAMPLE_APP_IDS.contains(appId)
144
+ hashMapOf("IS_OFFICIAL_SAMPLE" to isOfficialSample)
145
+ } catch (e: Exception) {
146
+ hashMapOf("IS_OFFICIAL_SAMPLE" to false)
147
+ }
148
+ }
149
+
150
+ // Private helper methods
151
+ private inline fun executeStorageOperation(
152
+ promise: Promise,
153
+ errorMessage: String,
154
+ operation: () -> Unit
155
+ ) {
156
+ try {
157
+ operation()
158
+ } catch (e: Exception) {
159
+ promise.reject(ERROR_STORAGE, "$errorMessage: ${e.message}", e)
160
+ }
161
+ }
162
+
163
+ private fun getOrCreateDeviceId(): String {
164
+ val storedUuid = sharedPreferences.getString(UUID_KEY, null)
93
165
  val androidId = Settings.Secure.getString(
94
- this.reactApplicationContext.contentResolver,
95
- Settings.Secure.ANDROID_ID
166
+ reactApplicationContext.contentResolver,
167
+ Settings.Secure.ANDROID_ID
96
168
  )
97
- if (uuid === androidId) {
98
- promise.resolve(uuid)
99
- } else if (uuid != null && uuid != "") {
100
- promise.resolve(uuid)
101
- } else {
102
- val uniqueID = UUID.randomUUID().toString()
103
- val editor = sharedPreference.edit()
104
- editor.putString("uuid", uniqueID)
105
- editor.apply()
106
- promise.resolve(uniqueID)
169
+
170
+ return when {
171
+ storedUuid == androidId && storedUuid != null -> storedUuid
172
+ !storedUuid.isNullOrBlank() -> storedUuid
173
+ else -> {
174
+ val newUuid = UUID.randomUUID().toString()
175
+ sharedPreferences.edit()
176
+ .putString(UUID_KEY, newUuid)
177
+ .apply()
178
+ newUuid
179
+ }
107
180
  }
108
181
  }
109
182
 
110
- companion object {
111
- const val NAME = "OsanoCmpSdk"
112
- }
183
+ companion object {
184
+ const val NAME = "OsanoCmpSdk"
185
+
186
+ // Constants for better maintainability
187
+ private const val PREFERENCE_NAME = "com.osano.cmp.sdk"
188
+ private const val UUID_KEY = "uuid"
189
+
190
+ // Error codes
191
+ private const val ERROR_STORAGE = "STORAGE_ERROR"
192
+ private const val ERROR_INVALID_KEY = "INVALID_KEY_ERROR"
193
+
194
+ private val SAMPLE_APP_IDS = setOf("com.OsanoCMPExample")
195
+ }
113
196
  }
@@ -5,7 +5,6 @@ import com.facebook.react.bridge.NativeModule
5
5
  import com.facebook.react.bridge.ReactApplicationContext
6
6
  import com.facebook.react.uimanager.ViewManager
7
7
 
8
-
9
8
  class OsanoCMPSDKPackage : ReactPackage {
10
9
  override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
11
10
  return listOf(OsanoCMPSDKModule(reactContext))
@@ -1,8 +1,39 @@
1
1
  #import "OsanoCmpSdk.h"
2
+ #import <Foundation/Foundation.h>
3
+ #import <React/RCTBridgeModule.h>
4
+ #import <UIKit/UIKit.h>
5
+ #import <React/RCTLog.h>
6
+
7
+ static NSArray<NSString *> *OCMSampleBundleIDs(void) {
8
+ static NSArray<NSString *> *ids;
9
+ static dispatch_once_t onceToken;
10
+ dispatch_once(&onceToken, ^{
11
+ ids = @[
12
+ @"org.reactjs.native.example.OsanoCMPExample"
13
+ ];
14
+ });
15
+ return ids;
16
+ }
17
+
18
+ static BOOL OCMIsOfficialSample(void) {
19
+ NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier] ?: @"";
20
+ return [OCMSampleBundleIDs() containsObject:bundleID];
21
+ }
2
22
 
3
23
  @implementation OsanoCmpSdk
24
+
4
25
  RCT_EXPORT_MODULE()
5
26
 
27
+ + (BOOL)requiresMainQueueSetup { return YES; }
28
+
29
+ - (NSDictionary *)constantsToExport
30
+ {
31
+ BOOL isSample = OCMIsOfficialSample();
32
+ return @{
33
+ @"IS_OFFICIAL_SAMPLE": @(isSample),
34
+ };
35
+ }
36
+
6
37
  RCT_EXPORT_METHOD(setStorage:(NSString *)key
7
38
  val:(NSString *)value
8
39
  res:(RCTPromiseResolveBlock)resolve
@@ -44,7 +75,7 @@ RCT_EXPORT_METHOD(setStorageToDefault:(NSString *)key
44
75
  }
45
76
 
46
77
  RCT_EXPORT_METHOD(setIntStorageToDefault:(NSString *)key
47
- val:(NSNumber *)value
78
+ val:(nonnull NSNumber *)value
48
79
  res:(RCTPromiseResolveBlock)resolve
49
80
  rej:(RCTPromiseRejectBlock)reject
50
81
  ) {
@@ -55,7 +86,7 @@ RCT_EXPORT_METHOD(setIntStorageToDefault:(NSString *)key
55
86
  }
56
87
 
57
88
  RCT_EXPORT_METHOD(setBoolStorageToDefault:(NSString *)key
58
- val:(NSNumber *)value
89
+ val:(nonnull NSNumber *)value
59
90
  res:(RCTPromiseResolveBlock)resolve
60
91
  rej:(RCTPromiseRejectBlock)reject
61
92
  ) {
@@ -81,5 +112,4 @@ RCT_EXPORT_METHOD(getDeviceId:(RCTPromiseResolveBlock)resolve
81
112
  resolve(uuid);
82
113
  }
83
114
 
84
-
85
115
  @end
package/ios/OsanoCmpSdk.h CHANGED
@@ -1,12 +1,5 @@
1
-
2
- #ifdef RCT_NEW_ARCH_ENABLED
3
- #import "RNOsanoCmpSdkSpec.h"
4
-
5
- @interface OsanoCmpSdk : NSObject <NativeOsanoCmpSdkSpec>
6
- #else
7
1
  #import <React/RCTBridgeModule.h>
8
2
 
9
3
  @interface OsanoCmpSdk : NSObject <RCTBridgeModule>
10
- #endif
11
4
 
12
5
  @end