Skip to content

Commit 5d8f7c2

Browse files
authored
feat: add publisherProvidedSignals for Google Ad Manager PPS
1 parent 6891e85 commit 5d8f7c2

File tree

5 files changed

+145
-2
lines changed

5 files changed

+145
-2
lines changed

__tests__/requestOptions.test.tsx

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,82 @@
1+
import { validateAdRequestOptions } from '../src/validateAdRequestOptions';
2+
13
describe('Admob RequestOptions', () => {
2-
// eslint-disable-next-line jest/no-disabled-tests
3-
test.skip('skip', () => {});
4+
describe('publisherProvidedSignals', () => {
5+
it('throws if publisherProvidedSignals is not an object', () => {
6+
expect(() =>
7+
validateAdRequestOptions({
8+
// @ts-ignore
9+
publisherProvidedSignals: 'not-an-object',
10+
}),
11+
).toThrow("'options.publisherProvidedSignals' expected an object of key/value pairs");
12+
});
13+
14+
it('throws if a publisherProvidedSignals value is not an array', () => {
15+
expect(() =>
16+
validateAdRequestOptions({
17+
// @ts-ignore
18+
publisherProvidedSignals: { IAB_CONTENT_2_2: 'not-an-array' },
19+
}),
20+
).toThrow("'options.publisherProvidedSignals.IAB_CONTENT_2_2' expected an array of numbers");
21+
});
22+
23+
it('sets publisherProvidedSignals if valid', () => {
24+
const result = validateAdRequestOptions({
25+
publisherProvidedSignals: { IAB_CONTENT_2_2: [533, 483, 1020] },
26+
});
27+
expect(result.publisherProvidedSignals).toEqual({
28+
IAB_CONTENT_2_2: [533, 483, 1020],
29+
});
30+
});
31+
32+
it('passes through multiple signal keys', () => {
33+
const pps = {
34+
IAB_CONTENT_2_2: [533, 483],
35+
IAB_AUDIENCE_1_1: [6, 7],
36+
};
37+
const result = validateAdRequestOptions({ publisherProvidedSignals: pps });
38+
expect(result.publisherProvidedSignals).toEqual(pps);
39+
});
40+
41+
it('does not set publisherProvidedSignals if not provided', () => {
42+
const result = validateAdRequestOptions({});
43+
expect(result.publisherProvidedSignals).toBeUndefined();
44+
});
45+
});
46+
47+
describe('publisherProvidedId', () => {
48+
it('throws if publisherProvidedId is not a string', () => {
49+
expect(() =>
50+
validateAdRequestOptions({
51+
// @ts-ignore
52+
publisherProvidedId: 123,
53+
}),
54+
).toThrow("'options.publisherProvidedId' expected a string value");
55+
});
56+
57+
it('sets publisherProvidedId if valid', () => {
58+
const result = validateAdRequestOptions({
59+
publisherProvidedId: 'user-abc-123',
60+
});
61+
expect(result.publisherProvidedId).toBe('user-abc-123');
62+
});
63+
});
64+
65+
describe('customTargeting', () => {
66+
it('throws if customTargeting is not an object', () => {
67+
expect(() =>
68+
validateAdRequestOptions({
69+
// @ts-ignore
70+
customTargeting: 'not-an-object',
71+
}),
72+
).toThrow("'options.customTargeting' expected an object of key/value pairs");
73+
});
74+
75+
it('sets customTargeting if valid', () => {
76+
const result = validateAdRequestOptions({
77+
customTargeting: { key: 'value' },
78+
});
79+
expect(result.customTargeting).toEqual({ key: 'value' });
80+
});
81+
});
482
});

android/src/main/java/io/invertase/googlemobileads/ReactNativeGoogleMobileAdsCommon.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@
2323
import android.view.ViewGroup;
2424
import com.facebook.react.bridge.Arguments;
2525
import com.facebook.react.bridge.ReactContext;
26+
import com.facebook.react.bridge.ReadableArray;
2627
import com.facebook.react.bridge.ReadableMap;
28+
import com.facebook.react.bridge.ReadableMapKeySetIterator;
2729
import com.facebook.react.bridge.WritableMap;
2830
import com.google.ads.mediation.admob.AdMobAdapter;
2931
import com.google.android.gms.ads.AdError;
@@ -167,6 +169,20 @@ public static AdManagerAdRequest buildAdRequest(ReadableMap adRequestOptions) {
167169
}
168170
}
169171

172+
if (adRequestOptions.hasKey("publisherProvidedSignals")) {
173+
ReadableMap ppsMap = adRequestOptions.getMap("publisherProvidedSignals");
174+
ReadableMapKeySetIterator iterator = ppsMap.keySetIterator();
175+
while (iterator.hasNextKey()) {
176+
String key = iterator.nextKey();
177+
ReadableArray values = ppsMap.getArray(key);
178+
ArrayList<Integer> intValues = new ArrayList<>();
179+
for (int i = 0; i < values.size(); i++) {
180+
intValues.add(values.getInt(i));
181+
}
182+
extras.putIntegerArrayList(key, intValues);
183+
}
184+
}
185+
170186
builder.addNetworkExtrasBundle(AdMobAdapter.class, extras);
171187

172188
if (adRequestOptions.hasKey("keywords")) {

ios/RNGoogleMobileAds/RNGoogleMobileAdsCommon.mm

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,13 @@ + (GAMRequest *)buildAdRequest:(NSDictionary *)adRequestOptions {
6565
}
6666
}
6767

68+
if (adRequestOptions[@"publisherProvidedSignals"]) {
69+
NSDictionary *pps = adRequestOptions[@"publisherProvidedSignals"];
70+
for (NSString *key in pps) {
71+
extras[key] = pps[key];
72+
}
73+
}
74+
6875
GADExtras *networkExtras = [[GADExtras alloc] init];
6976
networkExtras.additionalParameters = extras;
7077
[request registerAdNetworkExtras:networkExtras];

src/types/RequestOptions.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,32 @@ export interface RequestOptions {
106106
* See [this article](https://support.google.com/admanager/answer/2880055) for more information.
107107
*/
108108
publisherProvidedId?: string;
109+
110+
/**
111+
* Publisher Provided Signals (PPS) for Google Ad Manager.
112+
*
113+
* PPS allows publishers to send IAB taxonomy signals to help ad buyers
114+
* evaluate and bid on inventory more effectively.
115+
*
116+
* Keys are IAB taxonomy names (e.g. `IAB_CONTENT_2_2`, `IAB_AUDIENCE_1_1`).
117+
* Values are arrays of taxonomy IDs as integers.
118+
*
119+
* On Android, values are passed as `putIntegerArrayList` in the extras Bundle.
120+
* On iOS, values are passed as `NSArray<NSNumber>` in GADExtras.
121+
*
122+
* @see https://developers.google.com/ad-manager/mobile-ads-sdk/android/targeting#publisher_provided_signals
123+
* @see https://developers.google.com/ad-manager/mobile-ads-sdk/ios/targeting#publisher_provided_signals
124+
*
125+
* #### Example
126+
*
127+
* ```js
128+
* await Interstitial.createForAdRequest('/12345/ad-unit', {
129+
* publisherProvidedSignals: {
130+
* IAB_CONTENT_2_2: [533, 483], // Soccer, Sports
131+
* IAB_AUDIENCE_1_1: [6, 284], // Sports & Fitness, Soccer
132+
* },
133+
* });
134+
* ```
135+
*/
136+
publisherProvidedSignals?: { [key: string]: number[] };
109137
}

src/validateAdRequestOptions.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,5 +136,19 @@ export function validateAdRequestOptions(options?: RequestOptions) {
136136
out.publisherProvidedId = options.publisherProvidedId;
137137
}
138138

139+
if (options.publisherProvidedSignals) {
140+
if (!isObject(options.publisherProvidedSignals)) {
141+
throw new Error("'options.publisherProvidedSignals' expected an object of key/value pairs");
142+
}
143+
144+
Object.entries(options.publisherProvidedSignals).forEach(([key, value]) => {
145+
if (!isArray(value)) {
146+
throw new Error(`'options.publisherProvidedSignals.${key}' expected an array of numbers`);
147+
}
148+
});
149+
150+
out.publisherProvidedSignals = options.publisherProvidedSignals;
151+
}
152+
139153
return out;
140154
}

0 commit comments

Comments
 (0)