react-native-purchases-admob 0.0.0-stage → 10.15.1
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/LICENSE +20 -0
- package/README.md +91 -2
- package/lib/commonjs/index.js +446 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/state.js +92 -0
- package/lib/commonjs/state.js.map +1 -0
- package/lib/commonjs/tracker.js +161 -0
- package/lib/commonjs/tracker.js.map +1 -0
- package/lib/module/index.js +439 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/state.js +82 -0
- package/lib/module/state.js.map +1 -0
- package/lib/module/tracker.js +150 -0
- package/lib/module/tracker.js.map +1 -0
- package/lib/typescript/src/index.d.ts +230 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/state.d.ts +31 -0
- package/lib/typescript/src/state.d.ts.map +1 -0
- package/lib/typescript/src/tracker.d.ts +17 -0
- package/lib/typescript/src/tracker.d.ts.map +1 -0
- package/package.json +109 -4
- package/src/index.ts +623 -0
- package/src/state.ts +109 -0
- package/src/tracker.ts +200 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,623 @@
|
|
|
1
|
+
import { useRef } from "react";
|
|
2
|
+
import Purchases, { AdFormat } from "react-native-purchases";
|
|
3
|
+
import type { RewardVerificationResult } from "react-native-purchases";
|
|
4
|
+
import {
|
|
5
|
+
AdEventType,
|
|
6
|
+
AppOpenAd,
|
|
7
|
+
InterstitialAd,
|
|
8
|
+
NativeAd,
|
|
9
|
+
NativeAdEventType,
|
|
10
|
+
RewardedAd,
|
|
11
|
+
RewardedAdEventType,
|
|
12
|
+
RewardedInterstitialAd,
|
|
13
|
+
} from "react-native-google-mobile-ads";
|
|
14
|
+
import type {
|
|
15
|
+
AdShowOptions,
|
|
16
|
+
BannerAdProps,
|
|
17
|
+
PaidEvent,
|
|
18
|
+
RequestOptions,
|
|
19
|
+
ResponseInfo,
|
|
20
|
+
RewardedAdReward,
|
|
21
|
+
} from "react-native-google-mobile-ads";
|
|
22
|
+
|
|
23
|
+
import {
|
|
24
|
+
generateImpressionId,
|
|
25
|
+
setState,
|
|
26
|
+
stateFor,
|
|
27
|
+
TrackedAdState,
|
|
28
|
+
} from "./state";
|
|
29
|
+
import { AdMobTracker, logWarning } from "./tracker";
|
|
30
|
+
|
|
31
|
+
export type { TrackedAdState } from "./state";
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The structural shape of a `react-native-google-mobile-ads` full-screen ad
|
|
35
|
+
* (`InterstitialAd`, `AppOpenAd`, `RewardedAd`, `RewardedInterstitialAd`).
|
|
36
|
+
* Typed structurally so the adapter works across the library's per-class
|
|
37
|
+
* listener generics and duplicate type installations.
|
|
38
|
+
*/
|
|
39
|
+
export interface TrackableAd {
|
|
40
|
+
readonly adUnitId: string;
|
|
41
|
+
readonly loaded: boolean;
|
|
42
|
+
readonly responseInfo: ResponseInfo | null;
|
|
43
|
+
load(): void;
|
|
44
|
+
destroy(): void;
|
|
45
|
+
addAdEventsListener(
|
|
46
|
+
listener: (event: { type: string; payload?: unknown }) => void
|
|
47
|
+
): () => void;
|
|
48
|
+
addAdEventListener(
|
|
49
|
+
type: string,
|
|
50
|
+
// tslint:disable-next-line:no-any
|
|
51
|
+
listener: (payload: any) => void
|
|
52
|
+
): () => void;
|
|
53
|
+
show(showOptions?: AdShowOptions): Promise<void>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface TrackingOptions {
|
|
57
|
+
/** Free-form label for where in your app the ad is shown. */
|
|
58
|
+
placement?: string | null;
|
|
59
|
+
/** Passed through to `createForAdRequest`. */
|
|
60
|
+
requestOptions?: RequestOptions;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface RewardedTrackingOptions extends TrackingOptions {
|
|
64
|
+
/**
|
|
65
|
+
* Attach a RevenueCat reward-verification token to AdMob's server-side
|
|
66
|
+
* verification options. The token has to exist before the ad request is
|
|
67
|
+
* created, so the adapter generates the impression id itself and uses it for
|
|
68
|
+
* every tracking event of this ad. Tokens are single-use: create a new ad
|
|
69
|
+
* for each verified show instead of calling `load()` again on this one.
|
|
70
|
+
*/
|
|
71
|
+
enableRewardVerification?: boolean;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface ShowOptions {
|
|
75
|
+
/** Overrides the load-time placement for this impression's events. */
|
|
76
|
+
placement?: string | null;
|
|
77
|
+
showOptions?: AdShowOptions;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export type BannerAdTrackingProps = BannerAdProps & {
|
|
81
|
+
placement?: string | null;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export type RewardVerificationStartedCallback = () => void;
|
|
85
|
+
export type RewardVerificationCompletedCallback = (
|
|
86
|
+
result: RewardVerificationResult
|
|
87
|
+
) => void;
|
|
88
|
+
|
|
89
|
+
export interface ShowWithRewardVerificationOptions extends ShowOptions {
|
|
90
|
+
onUserEarnedReward?: (reward: RewardedAdReward) => void;
|
|
91
|
+
onRewardVerificationStarted?: RewardVerificationStartedCallback;
|
|
92
|
+
onRewardVerificationCompleted: RewardVerificationCompletedCallback;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const failedResult: RewardVerificationResult = {
|
|
96
|
+
failed: true,
|
|
97
|
+
moreRewards: [],
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
function formatOf(ad: TrackableAd): AdFormat | null {
|
|
101
|
+
if (ad instanceof InterstitialAd) {
|
|
102
|
+
return AdFormat.interstitial;
|
|
103
|
+
}
|
|
104
|
+
if (ad instanceof RewardedInterstitialAd) {
|
|
105
|
+
return AdFormat.rewardedInterstitial;
|
|
106
|
+
}
|
|
107
|
+
if (ad instanceof RewardedAd) {
|
|
108
|
+
return AdFormat.rewarded;
|
|
109
|
+
}
|
|
110
|
+
if (ad instanceof AppOpenAd) {
|
|
111
|
+
return AdFormat.appOpen;
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Attaches RevenueCat tracking to an already created full-screen ad. Listeners
|
|
118
|
+
* are additive, so your own `addAdEventListener` calls keep working.
|
|
119
|
+
*/
|
|
120
|
+
function trackAd(
|
|
121
|
+
ad: TrackableAd,
|
|
122
|
+
adFormat: AdFormat,
|
|
123
|
+
placement: string | null | undefined,
|
|
124
|
+
pinnedImpressionId: string | null = null
|
|
125
|
+
): TrackedAdState {
|
|
126
|
+
const state = setState(
|
|
127
|
+
ad,
|
|
128
|
+
new TrackedAdState(adFormat, ad.adUnitId, placement, pinnedImpressionId)
|
|
129
|
+
);
|
|
130
|
+
const rewarded =
|
|
131
|
+
ad instanceof RewardedAd || ad instanceof RewardedInterstitialAd;
|
|
132
|
+
|
|
133
|
+
ad.addAdEventsListener(({ type, payload }) => {
|
|
134
|
+
switch (type) {
|
|
135
|
+
case AdEventType.LOADED:
|
|
136
|
+
case RewardedAdEventType.LOADED:
|
|
137
|
+
if (type === AdEventType.LOADED && rewarded) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
state.loadedOnce = true;
|
|
141
|
+
state.updateFromResponse(ad.responseInfo);
|
|
142
|
+
AdMobTracker.trackLoaded(state);
|
|
143
|
+
return;
|
|
144
|
+
case AdEventType.ERROR: {
|
|
145
|
+
const error = payload as
|
|
146
|
+
| { phase?: string; reason?: string }
|
|
147
|
+
| undefined;
|
|
148
|
+
if (error?.phase === "show") {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
AdMobTracker.trackFailedToLoad(
|
|
152
|
+
ad.adUnitId,
|
|
153
|
+
adFormat,
|
|
154
|
+
state.loadPlacement,
|
|
155
|
+
error?.reason
|
|
156
|
+
);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
case AdEventType.IMPRESSION:
|
|
160
|
+
AdMobTracker.trackDisplayed(state);
|
|
161
|
+
return;
|
|
162
|
+
case AdEventType.CLICKED:
|
|
163
|
+
AdMobTracker.trackOpened(state);
|
|
164
|
+
return;
|
|
165
|
+
case AdEventType.PAID:
|
|
166
|
+
AdMobTracker.trackRevenue(state, payload as PaidEvent);
|
|
167
|
+
return;
|
|
168
|
+
case RewardedAdEventType.EARNED_REWARD:
|
|
169
|
+
// With verification enabled the core SDK reports earning from the poll.
|
|
170
|
+
if (!state.clientTransactionId) {
|
|
171
|
+
AdMobTracker.trackRewardEarnedUnverified(state);
|
|
172
|
+
}
|
|
173
|
+
return;
|
|
174
|
+
default:
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
return state;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function createRewarded<T extends TrackableAd>(
|
|
182
|
+
create: (requestOptions: RequestOptions) => T,
|
|
183
|
+
adFormat: AdFormat,
|
|
184
|
+
options: RewardedTrackingOptions
|
|
185
|
+
): Promise<T> {
|
|
186
|
+
let requestOptions = options.requestOptions ?? {};
|
|
187
|
+
let pinnedImpressionId: string | null = null;
|
|
188
|
+
let clientTransactionId: string | null = null;
|
|
189
|
+
|
|
190
|
+
if (options.enableRewardVerification) {
|
|
191
|
+
pinnedImpressionId = generateImpressionId();
|
|
192
|
+
try {
|
|
193
|
+
const token = await Purchases.generateRewardVerificationToken(
|
|
194
|
+
pinnedImpressionId
|
|
195
|
+
);
|
|
196
|
+
clientTransactionId = token.clientTransactionId;
|
|
197
|
+
requestOptions = {
|
|
198
|
+
...requestOptions,
|
|
199
|
+
serverSideVerificationOptions: {
|
|
200
|
+
userId: token.appUserID,
|
|
201
|
+
customData: token.customData,
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
} catch (e) {
|
|
205
|
+
logWarning(`Could not enable reward verification: ${e}`);
|
|
206
|
+
pinnedImpressionId = null;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const ad = create(requestOptions);
|
|
211
|
+
const state = trackAd(ad, adFormat, options.placement, pinnedImpressionId);
|
|
212
|
+
state.clientTransactionId = clientTransactionId;
|
|
213
|
+
if (pinnedImpressionId) {
|
|
214
|
+
preventReload(ad, state);
|
|
215
|
+
}
|
|
216
|
+
return ad;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* The SSV token travels with the request options, which the library reuses
|
|
221
|
+
* and attaches natively on every successful load, so a reload after the ad
|
|
222
|
+
* has loaded would send the already used token to AdMob. Shadow `load` on
|
|
223
|
+
* the instance so it is ignored once the ad has loaded; a retry after a load
|
|
224
|
+
* error never consumed the token and still goes through.
|
|
225
|
+
*/
|
|
226
|
+
function preventReload(ad: TrackableAd, state: TrackedAdState): void {
|
|
227
|
+
const load = ad.load.bind(ad);
|
|
228
|
+
ad.load = () => {
|
|
229
|
+
if (state.loadedOnce) {
|
|
230
|
+
logWarning(
|
|
231
|
+
"Reward verification tokens are single-use; load() was ignored. " +
|
|
232
|
+
"Create a new ad through createRewardedAd for each verified show."
|
|
233
|
+
);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
load();
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function trackedBannerProps(
|
|
241
|
+
state: TrackedAdState,
|
|
242
|
+
props: BannerAdProps
|
|
243
|
+
): BannerAdProps {
|
|
244
|
+
return {
|
|
245
|
+
...props,
|
|
246
|
+
onAdLoaded: (dimensions) => {
|
|
247
|
+
state.updateFromResponse(dimensions.responseInfo);
|
|
248
|
+
AdMobTracker.trackLoaded(state);
|
|
249
|
+
props.onAdLoaded?.(dimensions);
|
|
250
|
+
},
|
|
251
|
+
onAdFailedToLoad: (error) => {
|
|
252
|
+
AdMobTracker.trackFailedToLoad(
|
|
253
|
+
props.unitId,
|
|
254
|
+
AdFormat.banner,
|
|
255
|
+
state.loadPlacement,
|
|
256
|
+
error.reason
|
|
257
|
+
);
|
|
258
|
+
props.onAdFailedToLoad?.(error);
|
|
259
|
+
},
|
|
260
|
+
onAdImpression: () => {
|
|
261
|
+
AdMobTracker.trackDisplayed(state);
|
|
262
|
+
props.onAdImpression?.();
|
|
263
|
+
},
|
|
264
|
+
onAdClicked: () => {
|
|
265
|
+
AdMobTracker.trackOpened(state);
|
|
266
|
+
props.onAdClicked?.();
|
|
267
|
+
},
|
|
268
|
+
onPaid: (paid) => {
|
|
269
|
+
AdMobTracker.trackRevenue(state, paid);
|
|
270
|
+
props.onPaid?.(paid);
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Returns `BannerAd` props that report to RevenueCat, forwarding to the
|
|
277
|
+
* props you pass in. Spread the result into `<BannerAd {...props} />`.
|
|
278
|
+
* The tracking state lives for the lifetime of the calling component, so
|
|
279
|
+
* rerenders keep the impression id of the banner that loaded.
|
|
280
|
+
*
|
|
281
|
+
* @example
|
|
282
|
+
* ```tsx
|
|
283
|
+
* function HomeBanner() {
|
|
284
|
+
* const bannerProps = useTrackingBannerAdProps({
|
|
285
|
+
* unitId,
|
|
286
|
+
* size: BannerAdSize.BANNER,
|
|
287
|
+
* placement: "home_bottom",
|
|
288
|
+
* });
|
|
289
|
+
* return <BannerAd {...bannerProps} />;
|
|
290
|
+
* }
|
|
291
|
+
* ```
|
|
292
|
+
*/
|
|
293
|
+
export function useTrackingBannerAdProps(
|
|
294
|
+
props: BannerAdTrackingProps
|
|
295
|
+
): BannerAdProps {
|
|
296
|
+
const { placement, ...rest } = props;
|
|
297
|
+
const state = useRef<TrackedAdState | null>(null);
|
|
298
|
+
if (state.current === null) {
|
|
299
|
+
state.current = new TrackedAdState(
|
|
300
|
+
AdFormat.banner,
|
|
301
|
+
props.unitId,
|
|
302
|
+
placement
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
return trackedBannerProps(state.current, rest);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function prepareShow(
|
|
309
|
+
ad: TrackableAd,
|
|
310
|
+
placement?: string | null
|
|
311
|
+
): TrackedAdState | undefined {
|
|
312
|
+
const state = stateFor(ad);
|
|
313
|
+
if (!state) {
|
|
314
|
+
logWarning(
|
|
315
|
+
"This ad was not created through RevenueCatAdMob; it will not be tracked."
|
|
316
|
+
);
|
|
317
|
+
return undefined;
|
|
318
|
+
}
|
|
319
|
+
if (placement != null) {
|
|
320
|
+
state.showPlacement = placement;
|
|
321
|
+
}
|
|
322
|
+
return state;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Entry point of the RevenueCat AdMob adapter for React Native.
|
|
327
|
+
*
|
|
328
|
+
* Create full-screen ads through `create...` instead of
|
|
329
|
+
* `createForAdRequest`, then `load()` and show them through
|
|
330
|
+
* `showAndTrack` / `showWithRewardVerification`. Banner components spread
|
|
331
|
+
* `useTrackingBannerAdProps(...)`; native ads go through `trackNativeAd`.
|
|
332
|
+
*/
|
|
333
|
+
export const RevenueCatAdMob = {
|
|
334
|
+
/**
|
|
335
|
+
* Creates an interstitial ad whose loaded, displayed, opened, revenue and
|
|
336
|
+
* failed-to-load events are reported to RevenueCat. Use it in place of
|
|
337
|
+
* `InterstitialAd.createForAdRequest`, then `load()` and show it through
|
|
338
|
+
* `showAndTrack`.
|
|
339
|
+
*
|
|
340
|
+
* @example
|
|
341
|
+
* ```typescript
|
|
342
|
+
* const interstitial = RevenueCatAdMob.createInterstitialAd(adUnitId, {
|
|
343
|
+
* placement: "level_complete",
|
|
344
|
+
* });
|
|
345
|
+
* interstitial.addAdEventListener(AdEventType.LOADED, () => setReady(true));
|
|
346
|
+
* interstitial.load();
|
|
347
|
+
* // Later
|
|
348
|
+
* await RevenueCatAdMob.showAndTrack(interstitial);
|
|
349
|
+
* ```
|
|
350
|
+
*/
|
|
351
|
+
createInterstitialAd(
|
|
352
|
+
adUnitId: string,
|
|
353
|
+
options: TrackingOptions = {}
|
|
354
|
+
): InterstitialAd {
|
|
355
|
+
const ad = InterstitialAd.createForAdRequest(
|
|
356
|
+
adUnitId,
|
|
357
|
+
options.requestOptions
|
|
358
|
+
);
|
|
359
|
+
trackAd(ad, AdFormat.interstitial, options.placement);
|
|
360
|
+
return ad;
|
|
361
|
+
},
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Creates an app open ad tracked by RevenueCat. Use it in place of
|
|
365
|
+
* `AppOpenAd.createForAdRequest`, then `load()` and show it through
|
|
366
|
+
* `showAndTrack`.
|
|
367
|
+
*
|
|
368
|
+
* @example
|
|
369
|
+
* ```typescript
|
|
370
|
+
* const appOpen = RevenueCatAdMob.createAppOpenAd(adUnitId, {
|
|
371
|
+
* placement: "cold_start",
|
|
372
|
+
* });
|
|
373
|
+
* appOpen.load();
|
|
374
|
+
* // When the app comes to the foreground
|
|
375
|
+
* await RevenueCatAdMob.showAndTrack(appOpen);
|
|
376
|
+
* ```
|
|
377
|
+
*/
|
|
378
|
+
createAppOpenAd(adUnitId: string, options: TrackingOptions = {}): AppOpenAd {
|
|
379
|
+
const ad = AppOpenAd.createForAdRequest(adUnitId, options.requestOptions);
|
|
380
|
+
trackAd(ad, AdFormat.appOpen, options.placement);
|
|
381
|
+
return ad;
|
|
382
|
+
},
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Creates a rewarded ad tracked by RevenueCat. Use it in place of
|
|
386
|
+
* `RewardedAd.createForAdRequest`. With `enableRewardVerification` the
|
|
387
|
+
* adapter requests a verification token first, which is why this resolves
|
|
388
|
+
* asynchronously; show such ads through `showWithRewardVerification`.
|
|
389
|
+
*
|
|
390
|
+
* @example
|
|
391
|
+
* ```typescript
|
|
392
|
+
* const rewarded = await RevenueCatAdMob.createRewardedAd(adUnitId, {
|
|
393
|
+
* placement: "extra_life",
|
|
394
|
+
* enableRewardVerification: true,
|
|
395
|
+
* });
|
|
396
|
+
* rewarded.load();
|
|
397
|
+
* // Later
|
|
398
|
+
* await RevenueCatAdMob.showWithRewardVerification(rewarded, {
|
|
399
|
+
* onRewardVerificationCompleted: (result) => {
|
|
400
|
+
* if (!result.failed) grant(result.reward);
|
|
401
|
+
* },
|
|
402
|
+
* });
|
|
403
|
+
* ```
|
|
404
|
+
*/
|
|
405
|
+
createRewardedAd(
|
|
406
|
+
adUnitId: string,
|
|
407
|
+
options: RewardedTrackingOptions = {}
|
|
408
|
+
): Promise<RewardedAd> {
|
|
409
|
+
return createRewarded(
|
|
410
|
+
(requestOptions) =>
|
|
411
|
+
RewardedAd.createForAdRequest(adUnitId, requestOptions),
|
|
412
|
+
AdFormat.rewarded,
|
|
413
|
+
options
|
|
414
|
+
);
|
|
415
|
+
},
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Creates a rewarded interstitial ad tracked by RevenueCat. Use it in place
|
|
419
|
+
* of `RewardedInterstitialAd.createForAdRequest`; otherwise identical to
|
|
420
|
+
* `createRewardedAd`.
|
|
421
|
+
*
|
|
422
|
+
* @example
|
|
423
|
+
* ```typescript
|
|
424
|
+
* const ad = await RevenueCatAdMob.createRewardedInterstitialAd(adUnitId, {
|
|
425
|
+
* placement: "chapter_end",
|
|
426
|
+
* });
|
|
427
|
+
* ad.load();
|
|
428
|
+
* // Later
|
|
429
|
+
* await RevenueCatAdMob.showAndTrack(ad);
|
|
430
|
+
* ```
|
|
431
|
+
*/
|
|
432
|
+
createRewardedInterstitialAd(
|
|
433
|
+
adUnitId: string,
|
|
434
|
+
options: RewardedTrackingOptions = {}
|
|
435
|
+
): Promise<RewardedInterstitialAd> {
|
|
436
|
+
return createRewarded(
|
|
437
|
+
(requestOptions) =>
|
|
438
|
+
RewardedInterstitialAd.createForAdRequest(adUnitId, requestOptions),
|
|
439
|
+
AdFormat.rewardedInterstitial,
|
|
440
|
+
options
|
|
441
|
+
);
|
|
442
|
+
},
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Attaches tracking to a full-screen ad you created yourself. Call before
|
|
446
|
+
* `load()`. Reward verification is only available for ads created through
|
|
447
|
+
* `createRewardedAd` / `createRewardedInterstitialAd`.
|
|
448
|
+
*/
|
|
449
|
+
trackAd(ad: TrackableAd, options: TrackingOptions = {}): void {
|
|
450
|
+
const adFormat = formatOf(ad);
|
|
451
|
+
if (!adFormat) {
|
|
452
|
+
logWarning("Unsupported ad type; it will not be tracked.");
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
trackAd(ad, adFormat, options.placement);
|
|
456
|
+
},
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Shows a tracked full-screen ad, optionally overriding its placement.
|
|
460
|
+
*
|
|
461
|
+
* @example
|
|
462
|
+
* ```typescript
|
|
463
|
+
* await RevenueCatAdMob.showAndTrack(interstitial, {
|
|
464
|
+
* placement: "level_complete_retry",
|
|
465
|
+
* });
|
|
466
|
+
* ```
|
|
467
|
+
*/
|
|
468
|
+
showAndTrack(ad: TrackableAd, options: ShowOptions = {}): Promise<void> {
|
|
469
|
+
prepareShow(ad, options.placement);
|
|
470
|
+
return ad.show(options.showOptions);
|
|
471
|
+
},
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Shows a rewarded ad and verifies the earned reward with RevenueCat.
|
|
475
|
+
* `onRewardVerificationCompleted` is called at most once per show, after
|
|
476
|
+
* the user earns the reward; it is not called when the ad is dismissed or
|
|
477
|
+
* fails before a reward. It receives a failed result when the ad was
|
|
478
|
+
* created without `enableRewardVerification`.
|
|
479
|
+
*
|
|
480
|
+
* @example
|
|
481
|
+
* ```typescript
|
|
482
|
+
* await RevenueCatAdMob.showWithRewardVerification(rewarded, {
|
|
483
|
+
* onRewardVerificationStarted: () => showSpinner(),
|
|
484
|
+
* onRewardVerificationCompleted: (result) => {
|
|
485
|
+
* if (result.failed) return showError();
|
|
486
|
+
* grant(result.reward);
|
|
487
|
+
* },
|
|
488
|
+
* });
|
|
489
|
+
* ```
|
|
490
|
+
*/
|
|
491
|
+
showWithRewardVerification(
|
|
492
|
+
ad: TrackableAd,
|
|
493
|
+
options: ShowWithRewardVerificationOptions
|
|
494
|
+
): Promise<void> {
|
|
495
|
+
const state = prepareShow(ad, options.placement);
|
|
496
|
+
if (state) {
|
|
497
|
+
state.rewardVerificationDelivered = false;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const unsubscribe = ad.addAdEventListener(
|
|
501
|
+
RewardedAdEventType.EARNED_REWARD,
|
|
502
|
+
(reward: RewardedAdReward) => {
|
|
503
|
+
options.onUserEarnedReward?.(reward);
|
|
504
|
+
options.onRewardVerificationStarted?.();
|
|
505
|
+
|
|
506
|
+
const deliver = (result: RewardVerificationResult) => {
|
|
507
|
+
if (state?.rewardVerificationDelivered) {
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
if (state) {
|
|
511
|
+
state.rewardVerificationDelivered = true;
|
|
512
|
+
}
|
|
513
|
+
options.onRewardVerificationCompleted(result);
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
const clientTransactionId = state?.clientTransactionId;
|
|
517
|
+
if (!state || !clientTransactionId) {
|
|
518
|
+
logWarning(
|
|
519
|
+
"Reward earned but reward verification was not enabled for this ad. " +
|
|
520
|
+
"Create it with enableRewardVerification: true."
|
|
521
|
+
);
|
|
522
|
+
deliver(failedResult);
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
Purchases.pollRewardVerification(
|
|
526
|
+
clientTransactionId,
|
|
527
|
+
state.trackingMetadata
|
|
528
|
+
)
|
|
529
|
+
.then(deliver)
|
|
530
|
+
.catch((e) => {
|
|
531
|
+
logWarning(`Reward verification failed: ${e}`);
|
|
532
|
+
deliver(failedResult);
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
);
|
|
536
|
+
const unsubscribeAll = () => {
|
|
537
|
+
unsubscribe();
|
|
538
|
+
unsubscribeOnClose();
|
|
539
|
+
unsubscribeOnError();
|
|
540
|
+
};
|
|
541
|
+
const unsubscribeOnClose = ad.addAdEventListener(
|
|
542
|
+
AdEventType.CLOSED,
|
|
543
|
+
unsubscribeAll
|
|
544
|
+
);
|
|
545
|
+
const unsubscribeOnError = ad.addAdEventListener(
|
|
546
|
+
AdEventType.ERROR,
|
|
547
|
+
unsubscribeAll
|
|
548
|
+
);
|
|
549
|
+
const shown = ad.show(options.showOptions);
|
|
550
|
+
// A rejected show never emits CLOSED, so the listeners would outlive it.
|
|
551
|
+
shown.catch(unsubscribeAll);
|
|
552
|
+
return shown;
|
|
553
|
+
},
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Returns `BannerAd` props that report to RevenueCat, forwarding to the
|
|
557
|
+
* props you pass in. Spread the result into `<BannerAd {...props} />`.
|
|
558
|
+
* Each call starts a new tracking state, so call it once per banner
|
|
559
|
+
* instance (for example in a class component's constructor), never inside
|
|
560
|
+
* `render`. Function components should use `useTrackingBannerAdProps` instead.
|
|
561
|
+
*
|
|
562
|
+
* @example
|
|
563
|
+
* ```tsx
|
|
564
|
+
* class HomeBanner extends React.Component {
|
|
565
|
+
* bannerProps = RevenueCatAdMob.trackingBannerAdProps({
|
|
566
|
+
* unitId,
|
|
567
|
+
* size: BannerAdSize.BANNER,
|
|
568
|
+
* placement: "home_bottom",
|
|
569
|
+
* });
|
|
570
|
+
* render() {
|
|
571
|
+
* return <BannerAd {...this.bannerProps} />;
|
|
572
|
+
* }
|
|
573
|
+
* }
|
|
574
|
+
* ```
|
|
575
|
+
*/
|
|
576
|
+
trackingBannerAdProps(props: BannerAdTrackingProps): BannerAdProps {
|
|
577
|
+
const { placement, ...rest } = props;
|
|
578
|
+
return trackedBannerProps(
|
|
579
|
+
new TrackedAdState(AdFormat.banner, props.unitId, placement),
|
|
580
|
+
rest
|
|
581
|
+
);
|
|
582
|
+
},
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Tracks a loaded native ad. `NativeAd.createForAdRequest` resolves with a
|
|
586
|
+
* loaded ad, so the loaded event is reported immediately.
|
|
587
|
+
*
|
|
588
|
+
* @example
|
|
589
|
+
* ```typescript
|
|
590
|
+
* const nativeAd = await NativeAd.createForAdRequest(adUnitId);
|
|
591
|
+
* RevenueCatAdMob.trackNativeAd(nativeAd, { placement: "feed" });
|
|
592
|
+
* ```
|
|
593
|
+
*/
|
|
594
|
+
trackNativeAd(
|
|
595
|
+
ad: NativeAd,
|
|
596
|
+
options: { placement?: string | null } = {}
|
|
597
|
+
): void {
|
|
598
|
+
const state = setState(
|
|
599
|
+
ad,
|
|
600
|
+
new TrackedAdState(AdFormat.nativeAd, ad.adUnitId, options.placement)
|
|
601
|
+
);
|
|
602
|
+
state.updateFromResponse(
|
|
603
|
+
ad.responseInfo ?? ({ responseId: ad.responseId } as ResponseInfo)
|
|
604
|
+
);
|
|
605
|
+
AdMobTracker.trackLoaded(state);
|
|
606
|
+
ad.addAdEventListener(NativeAdEventType.IMPRESSION, () =>
|
|
607
|
+
AdMobTracker.trackDisplayed(state)
|
|
608
|
+
);
|
|
609
|
+
ad.addAdEventListener(NativeAdEventType.CLICKED, () =>
|
|
610
|
+
AdMobTracker.trackOpened(state)
|
|
611
|
+
);
|
|
612
|
+
ad.addAdEventListener(NativeAdEventType.PAID, (paid) =>
|
|
613
|
+
AdMobTracker.trackRevenue(state, paid as unknown as PaidEvent)
|
|
614
|
+
);
|
|
615
|
+
},
|
|
616
|
+
|
|
617
|
+
/** The impression id the adapter uses for a tracked ad, once known. */
|
|
618
|
+
impressionIdOf(ad: object): string | null {
|
|
619
|
+
return stateFor(ad)?.impressionId ?? null;
|
|
620
|
+
},
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
export default RevenueCatAdMob;
|