cordova-plugin-admob-nextgen 1.9.9 → 2.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.
package/README.md CHANGED
@@ -147,6 +147,7 @@ Add this to your `config.xml` to restore the plugin automatically.
147
147
 
148
148
  ### Supporting Mediation: **[⚡ admob-mediation-suite ](https://github.com/swaplab-engine/admob-mediation-suite)** (Optional)
149
149
  ### Supporting Native ads: **[⚡ cordova-plugin-admob-nextgen-native ](https://github.com/swaplab-engine/cordova-plugin-admob-nextgen-native)** (Optional)
150
+ ### New COPPA : **[⚡ cordova-plugin-play-age-signals ](https://github.com/swaplab-engine/cordova-plugin-play-age-signals)** (Optional)
150
151
 
151
152
  ---
152
153
 
@@ -163,13 +164,11 @@ Add this to your `config.xml` to restore the plugin automatically.
163
164
 
164
165
  **IMPORTANT:** Configure Global Settings and handle Privacy Consent (UMP) **BEFORE** initializing the SDK.
165
166
 
166
- ### Step 1: Global Configuration (Run First)
167
-
168
167
  // Optional: Mute ads globally
169
168
  admobNextGen.setAppVolume(0.5);
170
169
  admobNextGen.setAppMuted(true);
171
170
 
172
- ### Step 2: Request Consent (UMP) & Initialize
171
+ ### Step 1: Request Consent (UMP) & Initialize
173
172
 
174
173
  // 1. Request Consent Information
175
174
  admobNextGen.requestConsentInfo({
@@ -214,7 +213,7 @@ Add this to your `config.xml` to restore the plugin automatically.
214
213
  }
215
214
 
216
215
 
217
- // 4. Privacy options
216
+ // 4. Privacy options
218
217
  admobNextGen.showPrivacyOptionsForm(function() {
219
218
  startSdk();
220
219
  }, function(err) {
@@ -247,19 +246,81 @@ function checkAppTracking() {
247
246
  // admobNextGen.getTrackingAuthorizationStatus(success, error);
248
247
  ```
249
248
 
250
- ### Step 3: Check Consent Status (Smart Analysis)
251
- You can check if the user has granted permission for Personalized Ads without parsing complex IAB strings manually.
249
+ ### Step 2: Check Consent Status (Smart Analysis)
250
+ - Understanding Consent Flags (`getTCData`)
252
251
 
253
- admobNextGen.getTCData(function(data) {
254
- // --- SMART ANALYSIS (Easy) ---
255
- console.log("Personalized Allowed: " + data.isPersonalizedAllowed); // Boolean: true/false
256
- console.log("Status: " + data.statusMessage);
252
+ *(See [Publisher integration with the IAB Europe TCF](https://support.google.com/admanager/answer/9805023?hl=en#consent-policies))*
253
+ >
257
254
 
258
- // --- RAW DATA (Expert) ---
259
- console.log("TC String: " + data.tcString);
260
- console.log("GDPR Applies: " + data.gdprApplies); // 1=Yes, 0=No
261
- console.log("Purpose Consents: " + data.purposeConsents);
262
- });
255
+ When you call `admobNextGen.getTCData()`, the plugin returns a JSON object containing raw IAB TCF strings alongside pre-calculated boolean flags. To comply with AdMob's strict policies in GDPR regions, This plugin provide specific AdMob flags alongside the legacy flag.
256
+
257
+ | Flag | Description | GDPR Requirements Checked |
258
+ | :--- | :--- | :--- |
259
+ | `isAdMobPersonalizedAdsAllowed` | **(Recommended)** Returns `true` if the user has granted all necessary consents for AdMob to serve **Personalized Ads**. | Consent for Purposes 1, 3, 4 **AND** (Consent or Legitimate Interest) for Purposes 2, 7, 9, 10 **AND** Vendor 755 (Google). |
260
+ | `isAdMobNonPersonalizedAdsAllowed` | **(Recommended)** Returns `true` if the user has granted the minimum consent required for AdMob to serve **Non-Personalized Ads**. | Consent for Purpose 1 **AND** (Consent or Legitimate Interest) for Purposes 2, 7, 9, 10 **AND** Vendor 755 (Google). |
261
+ | `isPersonalizedAllowed` | **(Legacy)** Preserved for backward compatibility with older app versions. **Not recommended for strict AdMob policy compliance.** | Consent for Purpose 1 only. |
262
+
263
+ > **Note:** If the user is outside the GDPR region (e.g., USA, Indonesia), all of the ready-to-use flags above will return `true` by default.
264
+
265
+ ### A: Standard Usage (Ready-to-use Flags)
266
+ For most users, relying on the built-in boolean flags is the easiest way to ensure AdMob compliance without parsing complex strings.
267
+
268
+ ```javascript
269
+ // Request TC Data
270
+ admobNextGen.getTCData(function(tcData) {
271
+
272
+
273
+ console.log(tcData.adMobConsentStatus);
274
+
275
+ if (tcData.isAdMobPersonalizedAdsAllowed) {
276
+ console.log("Consent met: AdMob can serve Personalized Ads.");
277
+ // Proceed to load ads...
278
+ }
279
+ else if (tcData.isAdMobNonPersonalizedAdsAllowed) {
280
+ console.log("Consent met: AdMob will serve Non-Personalized Ads (Limited Ads).");
281
+ // Proceed to load ads...
282
+ }
283
+ else {
284
+ console.log("Consent insufficient: AdMob cannot serve any ads.");
285
+ // You might want to show the privacy options form again here
286
+ }
287
+
288
+ });
289
+ ```
290
+
291
+ #### B: Advanced Usage (Raw Data Control)
292
+ Advanced users who want full control over the consent logic can completely ignore the built-in boolean flags. The plugin exposes the raw IAB TCF data directly from the device's local storage (`purposeConsents`, `purposeLegitimateInterests`, `vendorConsents`, etc.), allowing you to manipulate and build custom conditions based on your own specific requirements.
293
+
294
+ ```javascript
295
+ admobNextGen.getTCData(function(tcData) {
296
+
297
+ // Ignore the built-in flags and parse the raw data yourself.
298
+ // Note: IAB TCF strings are 1-indexed, but JavaScript strings are 0-indexed.
299
+ // Index 0 represents Purpose 1, Index 2 represents Purpose 3, etc.
300
+
301
+ const purposes = tcData.purposeConsents || "";
302
+ const lis = tcData.purposeLegitimateInterests || "";
303
+ const vendors = tcData.vendorConsents || "";
304
+
305
+ // const tcs = tcData.tcString;
306
+ // const region = tcData.gdprApplies;
307
+
308
+ // Custom logic example: Check if Purpose 1 and Purpose 3 are explicitly granted
309
+ const hasPurpose1 = purposes.charAt(0) === '1';
310
+ const hasPurpose3 = purposes.charAt(2) === '1';
311
+
312
+ // Check if Vendor 755 (Google) is granted (Index 754)
313
+ const hasGoogleVendor = vendors.charAt(754) === '1';
314
+
315
+ if (hasPurpose1 && hasPurpose3 && hasGoogleVendor) {
316
+ console.log("Custom advanced condition met!");
317
+ // Your custom ad loading logic here
318
+ } else {
319
+ console.log("Custom advanced condition failed.");
320
+ }
321
+
322
+ });
323
+ ```
263
324
 
264
325
  ---
265
326
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cordova-plugin-admob-nextgen",
3
- "version": "1.9.9",
3
+ "version": "2.0.0",
4
4
  "description": "Google Mobile Ads Next Gen SDK for Cordova. High performance and modular architecture. ",
5
5
  "cordova": {
6
6
  "id": "cordova-plugin-admob-nextgen",
package/plugin.xml CHANGED
@@ -1,6 +1,6 @@
1
1
  <?xml version='1.0' encoding='utf-8'?>
2
2
  <plugin id="cordova-plugin-admob-nextgen"
3
- version="1.9.9"
3
+ version="2.0.0"
4
4
  xmlns="http://apache.org/cordova/ns/plugins/1.0"
5
5
  xmlns:android="http://schemas.android.com/apk/res/android">
6
6
 
@@ -130,41 +130,79 @@ public class ConsentExecutor {
130
130
 
131
131
  String tcString = prefs.getString("IABTCF_TCString", "");
132
132
  String purposeConsents = prefs.getString("IABTCF_PurposeConsents", "");
133
+ String purposeLegitimateInterests = prefs.getString("IABTCF_PurposeLegitimateInterests", "");
133
134
  String vendorConsents = prefs.getString("IABTCF_VendorConsents", "");
134
135
  int gdprApplies = prefs.getInt("IABTCF_gdprApplies", 0);
135
136
 
136
137
  tcData.put("tcString", tcString);
137
138
  tcData.put("purposeConsents", purposeConsents);
139
+ tcData.put("purposeLegitimateInterests", purposeLegitimateInterests);
138
140
  tcData.put("vendorConsents", vendorConsents);
139
141
  tcData.put("gdprApplies", gdprApplies);
140
142
 
141
143
  boolean isPersonalizedAllowed = false;
142
144
  String statusMessage = "Unknown";
143
145
 
146
+ boolean isAdMobPersonalizedAdsAllowed = false;
147
+ boolean isAdMobNonPersonalizedAdsAllowed = false;
148
+ String adMobConsentStatus = "Unknown";
149
+
144
150
  if (gdprApplies == 0) {
145
151
 
146
152
  isPersonalizedAllowed = true;
147
153
  statusMessage = "Not GDPR region. Personalized Ads allowed by default.";
154
+
155
+ isAdMobPersonalizedAdsAllowed = true;
156
+ isAdMobNonPersonalizedAdsAllowed = true;
157
+ adMobConsentStatus = "Not GDPR region. AdMob ads allowed by default.";
148
158
  } else {
149
159
 
150
160
  if (purposeConsents != null && purposeConsents.length() > 0) {
151
161
  char p1 = purposeConsents.charAt(0);
152
162
  if (p1 == '1') {
153
163
  isPersonalizedAllowed = true;
154
- statusMessage = "Purpose 1 Granted. Personalized Ads allowed.";
164
+ statusMessage = "Purpose 1 Granted. Legacy check passed.";
155
165
  } else {
156
166
  isPersonalizedAllowed = false;
157
- statusMessage = "Purpose 1 Denied. Non-Personalized / Limited Ads only.";
167
+ statusMessage = "Purpose 1 Denied. Legacy check failed.";
158
168
  }
159
- } else {
169
+ }
170
+
171
+ boolean hasPurpose1 = checkConsent(purposeConsents, 1);
172
+ boolean hasPurpose3 = checkConsent(purposeConsents, 3);
173
+ boolean hasPurpose4 = checkConsent(purposeConsents, 4);
160
174
 
161
- isPersonalizedAllowed = false;
162
- statusMessage = "No consent data found (User hasn't answered yet).";
175
+ boolean hasRequiredLI_or_Consent =
176
+ hasConsentOrLI(purposeConsents, purposeLegitimateInterests, 2) &&
177
+ hasConsentOrLI(purposeConsents, purposeLegitimateInterests, 7) &&
178
+ hasConsentOrLI(purposeConsents, purposeLegitimateInterests, 9) &&
179
+ hasConsentOrLI(purposeConsents, purposeLegitimateInterests, 10);
180
+
181
+ boolean hasVendorGoogle = checkConsent(vendorConsents, 755);
182
+
183
+ if (hasPurpose1 && hasVendorGoogle && hasRequiredLI_or_Consent) {
184
+ isAdMobNonPersonalizedAdsAllowed = true;
185
+
186
+ if (hasPurpose3 && hasPurpose4) {
187
+ isAdMobPersonalizedAdsAllowed = true;
188
+ adMobConsentStatus = "Strict requirements met for Personalized Ads (Purposes 1,3,4 + LI 2,7,9,10 + Vendor 755).";
189
+ } else {
190
+ isAdMobPersonalizedAdsAllowed = false;
191
+ adMobConsentStatus = "Requirements met for Non-Personalized Ads only.";
192
+ }
193
+ } else {
194
+ isAdMobPersonalizedAdsAllowed = false;
195
+ isAdMobNonPersonalizedAdsAllowed = false;
196
+ adMobConsentStatus = "Insufficient strict consent (Missing P1, Vendor 755, or P2,7,9,10). Limited Ads only.";
163
197
  }
164
198
  }
165
199
 
166
- tcData.put("isPersonalizedAllowed", isPersonalizedAllowed);
167
- tcData.put("statusMessage", statusMessage);
200
+ tcData.put("isPersonalizedAllowed", isPersonalizedAllowed);
201
+ tcData.put("statusMessage", statusMessage);
202
+
203
+ tcData.put("isAdMobPersonalizedAdsAllowed", isAdMobPersonalizedAdsAllowed);
204
+ tcData.put("isAdMobNonPersonalizedAdsAllowed", isAdMobNonPersonalizedAdsAllowed);
205
+ tcData.put("adMobConsentStatus", adMobConsentStatus);
168
206
 
169
207
  callbackContext.success(tcData);
170
208
  } catch (Exception e) {
@@ -172,6 +210,19 @@ public class ConsentExecutor {
172
210
  }
173
211
  }
174
212
 
213
+ private boolean checkConsent(String consentString, int id) {
214
+ if (consentString == null || consentString.length() < id) {
215
+ return false;
216
+ }
217
+ return consentString.charAt(id - 1) == '1';
218
+ }
219
+
220
+ private boolean hasConsentOrLI(String consents, String lis, int id) {
221
+ boolean hasConsent = checkConsent(consents, id);
222
+ boolean hasLI = checkConsent(lis, id);
223
+ return hasConsent || hasLI;
224
+ }
225
+
175
226
  private String getDeviceId() {
176
227
  try {
177
228
  Context context = cordova.getActivity();
@@ -100,12 +100,14 @@
100
100
 
101
101
  NSString *tcString = [prefs stringForKey:@"IABTCF_TCString"] ?: @"";
102
102
  NSString *purposeConsents = [prefs stringForKey:@"IABTCF_PurposeConsents"] ?: @"";
103
+ NSString *purposeLegitimateInterests = [prefs stringForKey:@"IABTCF_PurposeLegitimateInterests"] ?: @"";
103
104
  NSString *vendorConsents = [prefs stringForKey:@"IABTCF_VendorConsents"] ?: @"";
104
105
  NSString *additionalConsent = [prefs stringForKey:@"IABTCF_AddtlConsent"] ?: @"";
105
- NSInteger gdprApplies = [prefs integerForKey:@"IABTCF_gdprApplies"];
106
+ NSInteger gdprApplies = [prefs integerForKey:@"IABTCF_gdprApplies"];
106
107
 
107
108
  tcData[@"tcString"] = tcString;
108
109
  tcData[@"purposeConsents"] = purposeConsents;
110
+ tcData[@"purposeLegitimateInterests"] = purposeLegitimateInterests;
109
111
  tcData[@"vendorConsents"] = vendorConsents;
110
112
  tcData[@"additionalConsent"] = additionalConsent;
111
113
  tcData[@"gdprApplies"] = @(gdprApplies);
@@ -113,24 +115,55 @@
113
115
  BOOL isPersonalizedAllowed = NO;
114
116
  NSString *statusMessage = @"Unknown";
115
117
 
116
- if (gdprApplies == 0) {
118
+ BOOL isAdMobPersonalizedAdsAllowed = NO;
119
+ BOOL isAdMobNonPersonalizedAdsAllowed = NO;
120
+ NSString *adMobConsentStatus = @"Unknown";
117
121
 
122
+ if (gdprApplies == 0) {
118
123
  isPersonalizedAllowed = YES;
119
124
  statusMessage = @"Not GDPR region. Personalized Ads allowed by default.";
125
+
126
+ isAdMobPersonalizedAdsAllowed = YES;
127
+ isAdMobNonPersonalizedAdsAllowed = YES;
128
+ adMobConsentStatus = @"Not GDPR region. AdMob ads allowed by default.";
120
129
  } else {
121
130
 
122
131
  if (purposeConsents.length > 0) {
123
-
124
132
  if ([purposeConsents hasPrefix:@"1"]) {
125
133
  isPersonalizedAllowed = YES;
126
- statusMessage = @"Purpose 1 Granted. Personalized Ads allowed.";
134
+ statusMessage = @"Purpose 1 Granted. Legacy check passed.";
127
135
  } else {
128
136
  isPersonalizedAllowed = NO;
129
- statusMessage = @"Purpose 1 Denied. Non-Personalized / Limited Ads only.";
137
+ statusMessage = @"Purpose 1 Denied. Legacy check failed.";
138
+ }
139
+ }
140
+
141
+ BOOL hasPurpose1 = [self checkConsent:purposeConsents consentId:1];
142
+ BOOL hasPurpose3 = [self checkConsent:purposeConsents consentId:3];
143
+ BOOL hasPurpose4 = [self checkConsent:purposeConsents consentId:4];
144
+
145
+ BOOL hasRequiredLI_or_Consent =
146
+ [self hasConsentOrLI:purposeConsents lis:purposeLegitimateInterests consentId:2] &&
147
+ [self hasConsentOrLI:purposeConsents lis:purposeLegitimateInterests consentId:7] &&
148
+ [self hasConsentOrLI:purposeConsents lis:purposeLegitimateInterests consentId:9] &&
149
+ [self hasConsentOrLI:purposeConsents lis:purposeLegitimateInterests consentId:10];
150
+
151
+ BOOL hasVendorGoogle = [self checkConsent:vendorConsents consentId:755];
152
+
153
+ if (hasPurpose1 && hasVendorGoogle && hasRequiredLI_or_Consent) {
154
+ isAdMobNonPersonalizedAdsAllowed = YES;
155
+
156
+ if (hasPurpose3 && hasPurpose4) {
157
+ isAdMobPersonalizedAdsAllowed = YES;
158
+ adMobConsentStatus = @"Strict requirements met for Personalized Ads (Purposes 1,3,4 + LI 2,7,9,10 + Vendor 755).";
159
+ } else {
160
+ isAdMobPersonalizedAdsAllowed = NO;
161
+ adMobConsentStatus = @"Requirements met for Non-Personalized Ads only.";
130
162
  }
131
163
  } else {
132
- isPersonalizedAllowed = NO;
133
- statusMessage = @"No consent data found (User hasn't answered yet).";
164
+ isAdMobPersonalizedAdsAllowed = NO;
165
+ isAdMobNonPersonalizedAdsAllowed = NO;
166
+ adMobConsentStatus = @"Insufficient strict consent (Missing P1, Vendor 755, or P2,7,9,10). Limited Ads only.";
134
167
  }
135
168
  }
136
169
 
@@ -143,6 +176,10 @@
143
176
  tcData[@"statusMessage"] = statusMessage;
144
177
  tcData[@"isACVersion2"] = @(isACVersion2);
145
178
 
179
+ tcData[@"isAdMobPersonalizedAdsAllowed"] = @(isAdMobPersonalizedAdsAllowed);
180
+ tcData[@"isAdMobNonPersonalizedAdsAllowed"] = @(isAdMobNonPersonalizedAdsAllowed);
181
+ tcData[@"adMobConsentStatus"] = adMobConsentStatus;
182
+
146
183
  if (command) {
147
184
  CDVPluginResult* res = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:tcData];
148
185
  [self.plugin.commandDelegate sendPluginResult:res callbackId:command.callbackId];
@@ -156,6 +193,20 @@
156
193
  });
157
194
  }
158
195
 
196
+ - (BOOL)checkConsent:(NSString *)consentString consentId:(NSInteger)consentId {
197
+ if (!consentString || consentString.length < consentId) {
198
+ return NO;
199
+ }
200
+ unichar c = [consentString characterAtIndex:(consentId - 1)];
201
+ return c == '1';
202
+ }
203
+
204
+ - (BOOL)hasConsentOrLI:(NSString *)consents lis:(NSString *)lis consentId:(NSInteger)consentId {
205
+ BOOL hasConsent = [self checkConsent:consents consentId:consentId];
206
+ BOOL hasLI = [self checkConsent:lis consentId:consentId];
207
+ return hasConsent || hasLI;
208
+ }
209
+
159
210
  - (void)showPrivacyOptionsForm:(CDVInvokedUrlCommand *)command {
160
211
  dispatch_async(dispatch_get_main_queue(), ^{
161
212
  [UMPConsentForm presentPrivacyOptionsFormFromViewController:self.plugin.viewController completionHandler:^(NSError * _Nullable formError) {