@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.
- package/README.dev.md +237 -12
- package/android/build.gradle +1 -1
- package/android/src/main/java/com/OsanoCMPSDK/OsanoCMPSDKModule.kt +147 -64
- package/android/src/main/java/com/OsanoCMPSDK/OsanoCMPSDKPackage.kt +0 -1
- package/ios/OsanoCmpSDK.mm +33 -3
- package/ios/OsanoCmpSdk.h +0 -7
- package/lib/commonjs/ConsentManager.js +32 -4
- package/lib/commonjs/ConsentManager.js.map +1 -1
- package/lib/commonjs/components/Button.js +105 -95
- package/lib/commonjs/components/Button.js.map +1 -1
- package/lib/commonjs/components/Locale.js +5 -3
- package/lib/commonjs/components/Locale.js.map +1 -1
- package/lib/commonjs/index.js +1 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/native/OsanoCmp.js +35 -8
- package/lib/commonjs/native/OsanoCmp.js.map +1 -1
- package/lib/commonjs/native/OsanoCmpSdkSpec.js +9 -0
- package/lib/commonjs/native/OsanoCmpSdkSpec.js.map +1 -0
- package/lib/commonjs/services/consent.js +6 -2
- package/lib/commonjs/services/consent.js.map +1 -1
- package/lib/commonjs/utils/ExpoDetector.js +105 -0
- package/lib/commonjs/utils/ExpoDetector.js.map +1 -0
- package/lib/commonjs/utils/ExpoGoMemoryStorage.js +61 -0
- package/lib/commonjs/utils/ExpoGoMemoryStorage.js.map +1 -0
- package/lib/commonjs/views/Dialog/Buttons.js +26 -16
- package/lib/commonjs/views/Dialog/Buttons.js.map +1 -1
- package/lib/commonjs/views/Dialog/index.js +58 -95
- package/lib/commonjs/views/Dialog/index.js.map +1 -1
- package/lib/commonjs/views/Drawer/Drawer.ConsentCategories.js +12 -13
- package/lib/commonjs/views/Drawer/Drawer.ConsentCategories.js.map +1 -1
- package/lib/commonjs/views/Drawer/Drawer.Header.js +2 -2
- package/lib/commonjs/views/Drawer/Drawer.Header.js.map +1 -1
- package/lib/commonjs/views/Drawer/Drawer.Iab.Disclosures.js +10 -1
- package/lib/commonjs/views/Drawer/Drawer.Iab.Disclosures.js.map +1 -1
- package/lib/commonjs/views/Drawer/index.js +5 -4
- package/lib/commonjs/views/Drawer/index.js.map +1 -1
- package/lib/module/ConsentManager.js +32 -4
- package/lib/module/ConsentManager.js.map +1 -1
- package/lib/module/components/Button.js +99 -88
- package/lib/module/components/Button.js.map +1 -1
- package/lib/module/components/Locale.js +4 -3
- package/lib/module/components/Locale.js.map +1 -1
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/native/OsanoCmp.js +34 -8
- package/lib/module/native/OsanoCmp.js.map +1 -1
- package/lib/module/native/OsanoCmpSdkSpec.js +3 -0
- package/lib/module/native/OsanoCmpSdkSpec.js.map +1 -0
- package/lib/module/services/consent.js +7 -3
- package/lib/module/services/consent.js.map +1 -1
- package/lib/module/utils/ExpoDetector.js +98 -0
- package/lib/module/utils/ExpoDetector.js.map +1 -0
- package/lib/module/utils/ExpoGoMemoryStorage.js +54 -0
- package/lib/module/utils/ExpoGoMemoryStorage.js.map +1 -0
- package/lib/module/views/Dialog/Buttons.js +27 -17
- package/lib/module/views/Dialog/Buttons.js.map +1 -1
- package/lib/module/views/Dialog/index.js +59 -96
- package/lib/module/views/Dialog/index.js.map +1 -1
- package/lib/module/views/Drawer/Drawer.ConsentCategories.js +10 -12
- package/lib/module/views/Drawer/Drawer.ConsentCategories.js.map +1 -1
- package/lib/module/views/Drawer/Drawer.Header.js +2 -2
- package/lib/module/views/Drawer/Drawer.Header.js.map +1 -1
- package/lib/module/views/Drawer/Drawer.Iab.Disclosures.js +10 -1
- package/lib/module/views/Drawer/Drawer.Iab.Disclosures.js.map +1 -1
- package/lib/module/views/Drawer/index.js +6 -6
- package/lib/module/views/Drawer/index.js.map +1 -1
- package/lib/typescript/lib/commonjs/ConsentManager.d.ts.map +1 -1
- package/lib/typescript/lib/commonjs/components/Button.d.ts +4 -4
- package/lib/typescript/lib/commonjs/components/Button.d.ts.map +1 -1
- package/lib/typescript/lib/commonjs/components/Locale.d.ts.map +1 -1
- package/lib/typescript/lib/commonjs/hooks/useDeviceOrientation.d.ts +1 -1
- package/lib/typescript/lib/commonjs/native/OsanoCmp.d.ts.map +1 -1
- package/lib/typescript/lib/commonjs/native/OsanoCmpSdkSpec.d.ts +5 -0
- package/lib/typescript/lib/commonjs/native/OsanoCmpSdkSpec.d.ts.map +1 -0
- package/lib/typescript/lib/commonjs/services/consent.d.ts.map +1 -1
- package/lib/typescript/lib/commonjs/utils/ExpoDetector.d.ts +28 -0
- package/lib/typescript/lib/commonjs/utils/ExpoDetector.d.ts.map +1 -0
- package/lib/typescript/lib/commonjs/utils/ExpoGoMemoryStorage.d.ts +22 -0
- package/lib/typescript/lib/commonjs/utils/ExpoGoMemoryStorage.d.ts.map +1 -0
- package/lib/typescript/lib/commonjs/views/Dialog/Buttons.d.ts.map +1 -1
- package/lib/typescript/lib/commonjs/views/Dialog/index.d.ts.map +1 -1
- package/lib/typescript/lib/commonjs/views/Drawer/Drawer.ConsentCategories.d.ts.map +1 -1
- package/lib/typescript/lib/commonjs/views/Drawer/Drawer.Iab.Disclosures.d.ts.map +1 -1
- package/lib/typescript/lib/commonjs/views/Drawer/index.d.ts.map +1 -1
- package/lib/typescript/lib/module/ConsentManager.d.ts.map +1 -1
- package/lib/typescript/lib/module/components/Button.d.ts +7 -7
- package/lib/typescript/lib/module/components/Button.d.ts.map +1 -1
- package/lib/typescript/lib/module/components/Locale.d.ts +4 -1
- package/lib/typescript/lib/module/components/Locale.d.ts.map +1 -1
- package/lib/typescript/lib/module/hooks/useDeviceOrientation.d.ts +1 -1
- package/lib/typescript/lib/module/native/OsanoCmp.d.ts.map +1 -1
- package/lib/typescript/lib/module/native/OsanoCmpSdkSpec.d.ts +3 -0
- package/lib/typescript/lib/module/native/OsanoCmpSdkSpec.d.ts.map +1 -0
- package/lib/typescript/lib/module/services/consent.d.ts.map +1 -1
- package/lib/typescript/lib/module/utils/ExpoDetector.d.ts +27 -0
- package/lib/typescript/lib/module/utils/ExpoDetector.d.ts.map +1 -0
- package/lib/typescript/lib/module/utils/ExpoGoMemoryStorage.d.ts +21 -0
- package/lib/typescript/lib/module/utils/ExpoGoMemoryStorage.d.ts.map +1 -0
- package/lib/typescript/lib/module/views/Dialog/Buttons.d.ts.map +1 -1
- package/lib/typescript/lib/module/views/Dialog/index.d.ts.map +1 -1
- package/lib/typescript/lib/module/views/Drawer/Drawer.ConsentCategories.d.ts.map +1 -1
- package/lib/typescript/lib/module/views/Drawer/Drawer.Header.d.ts.map +1 -1
- package/lib/typescript/lib/module/views/Drawer/Drawer.Iab.Disclosures.d.ts.map +1 -1
- package/lib/typescript/lib/module/views/Drawer/index.d.ts.map +1 -1
- package/lib/typescript/src/ConsentManager.d.ts.map +1 -1
- package/lib/typescript/src/components/Button.d.ts +7 -7
- package/lib/typescript/src/components/Button.d.ts.map +1 -1
- package/lib/typescript/src/components/Locale.d.ts +2 -1
- package/lib/typescript/src/components/Locale.d.ts.map +1 -1
- package/lib/typescript/src/native/OsanoCmp.d.ts.map +1 -1
- package/lib/typescript/src/native/OsanoCmpSdkSpec.d.ts +17 -0
- package/lib/typescript/src/native/OsanoCmpSdkSpec.d.ts.map +1 -0
- package/lib/typescript/src/services/consent.d.ts.map +1 -1
- package/lib/typescript/src/utils/ExpoDetector.d.ts +29 -0
- package/lib/typescript/src/utils/ExpoDetector.d.ts.map +1 -0
- package/lib/typescript/src/utils/ExpoGoMemoryStorage.d.ts +21 -0
- package/lib/typescript/src/utils/ExpoGoMemoryStorage.d.ts.map +1 -0
- package/lib/typescript/src/views/Dialog/Buttons.d.ts +4 -2
- package/lib/typescript/src/views/Dialog/Buttons.d.ts.map +1 -1
- package/lib/typescript/src/views/Dialog/index.d.ts +1 -1
- package/lib/typescript/src/views/Dialog/index.d.ts.map +1 -1
- package/lib/typescript/src/views/Drawer/Drawer.ConsentCategories.d.ts.map +1 -1
- package/lib/typescript/src/views/Drawer/Drawer.Header.d.ts +1 -1
- package/lib/typescript/src/views/Drawer/Drawer.Header.d.ts.map +1 -1
- package/lib/typescript/src/views/Drawer/Drawer.Iab.Disclosures.d.ts.map +1 -1
- package/lib/typescript/src/views/Drawer/index.d.ts.map +1 -1
- package/osano-cmp-sdk.podspec +15 -15
- package/package.json +28 -3
- package/src/ConsentManager.ts +37 -4
- package/src/components/Button.tsx +122 -88
- package/src/components/Locale.tsx +28 -28
- package/src/constants.ts +2 -2
- package/src/index.tsx +1 -1
- package/src/native/OsanoCmp.ts +39 -23
- package/src/native/OsanoCmpSdkSpec.ts +25 -0
- package/src/services/consent.ts +9 -3
- package/src/utils/ExpoDetector.ts +114 -0
- package/src/utils/ExpoGoMemoryStorage.ts +61 -0
- package/src/views/Dialog/Buttons.tsx +33 -23
- package/src/views/Dialog/index.tsx +164 -208
- package/src/views/Drawer/Drawer.ConsentCategories.tsx +25 -25
- package/src/views/Drawer/Drawer.Header.tsx +7 -7
- package/src/views/Drawer/Drawer.Iab.Disclosures.tsx +6 -3
- package/src/views/Drawer/index.tsx +20 -19
- 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
|
-
|
|
37
|
+
## Architecture Testing
|
|
38
38
|
|
|
39
|
-
|
|
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
|
-
|
|
41
|
+
### Automated Architecture Testing
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
The project includes a comprehensive testing framework that can automatically test both architectures without manual configuration changes.
|
|
44
44
|
|
|
45
|
-
|
|
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
|
|
215
|
+
### Architecture Testing
|
|
52
216
|
|
|
53
|
-
|
|
217
|
+
**Automated Testing Across Architectures:**
|
|
54
218
|
|
|
55
219
|
```shell
|
|
56
|
-
|
|
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
|
-
|
|
251
|
+
3. **Run specific test files:**
|
|
60
252
|
|
|
61
253
|
```shell
|
|
62
|
-
|
|
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
|
|
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]`.
|
package/android/build.gradle
CHANGED
|
@@ -8,7 +8,7 @@ buildscript {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
dependencies {
|
|
11
|
-
classpath "com.android.tools.build:gradle:7.2
|
|
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
|
-
|
|
16
|
+
ReactContextBaseJavaModule(reactContext) {
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
29
|
+
@ReactMethod
|
|
20
30
|
fun setStorage(key: String?, consentString: String?, promise: Promise) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
95
|
-
|
|
166
|
+
reactApplicationContext.contentResolver,
|
|
167
|
+
Settings.Secure.ANDROID_ID
|
|
96
168
|
)
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
111
|
-
|
|
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))
|
package/ios/OsanoCmpSDK.mm
CHANGED
|
@@ -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