@capacitor-community/admob 8.2.1 → 8.3.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 +1 -1
- package/android/build.gradle +5 -1
- package/android/src/main/java/com/getcapacitor/community/admob/AdMob.java +15 -0
- package/android/src/main/java/com/getcapacitor/community/admob/nativeads/NativeAdExecutor.kt +257 -9
- package/dist/esm/native-ads/native-ad-definitions.interface.d.ts +6 -0
- package/dist/esm/native-ads/native-ad-feed.d.ts +3 -3
- package/dist/esm/native-ads/native-ad-feed.js +107 -32
- package/dist/esm/native-ads/native-ad-feed.js.map +1 -1
- package/dist/esm/native-ads/native-ad-geometry.js +2 -1
- package/dist/esm/native-ads/native-ad-geometry.js.map +1 -1
- package/dist/esm/native-ads/native-ad-options.interface.d.ts +6 -2
- package/dist/esm/native-ads/native-ad-placement.interface.d.ts +2 -0
- package/dist/plugin.cjs.js +165 -33
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +165 -33
- package/dist/plugin.js.map +1 -1
- package/docs/native-ads.md +32 -6
- package/ios/Sources/AdMobPlugin/AppOpen/AppOpenAdPluginEvents.swift +3 -0
- package/ios/Sources/AdMobPlugin/Banner/BannerAdPluginEvents.swift +3 -0
- package/ios/Sources/AdMobPlugin/Banner/BannerAdSizeEnum.swift +3 -0
- package/ios/Sources/AdMobPlugin/Banner/BannerExecutor.swift +4 -4
- package/ios/Sources/AdMobPlugin/Helper/AuthorizationStatusEnum.swift +3 -0
- package/ios/Sources/AdMobPlugin/Interstitial/InterstitialAdPluginEvents.swift +3 -0
- package/ios/Sources/AdMobPlugin/Rewarded/RewardAdPluginEvents.swift +3 -0
- package/ios/Sources/AdMobPlugin/RewardedInterstitial/RewardInterstitialAdPluginEvents.swift +3 -0
- package/package.json +5 -4
package/dist/plugin.cjs.js
CHANGED
|
@@ -2,6 +2,64 @@
|
|
|
2
2
|
|
|
3
3
|
var core = require('@capacitor/core');
|
|
4
4
|
|
|
5
|
+
function* projectionAncestors(element) {
|
|
6
|
+
let current = element;
|
|
7
|
+
while (current) {
|
|
8
|
+
const root = current.getRootNode();
|
|
9
|
+
current = current.assignedSlot ?? current.parentElement ?? (root instanceof ShadowRoot ? root.host : null);
|
|
10
|
+
if (current)
|
|
11
|
+
yield current;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Current CSS visibility without the Engine's projection hiding rules.
|
|
16
|
+
* Use when an existing renderer decides whether its DOM source is authored hidden.
|
|
17
|
+
* Projection attributes are restored synchronously before returning; this performs
|
|
18
|
+
* a style read and may notify MutationObservers, so it is not a cached measurement.
|
|
19
|
+
*/
|
|
20
|
+
const getUnprojectedVisibility = (element) => {
|
|
21
|
+
const win = element.ownerDocument.defaultView;
|
|
22
|
+
if (!win)
|
|
23
|
+
return 'hidden';
|
|
24
|
+
const markers = [];
|
|
25
|
+
for (const node of [element, ...projectionAncestors(element)]) {
|
|
26
|
+
for (const attribute of Array.from(node.attributes)) {
|
|
27
|
+
if (attribute.name === 'data-native-ui-snapshot-source' ||
|
|
28
|
+
attribute.name === 'data-native-ui-snapshot-prepared' ||
|
|
29
|
+
attribute.name === 'data-native-ui-snapshot-content' ||
|
|
30
|
+
attribute.name.startsWith('data-native-ui-session-'))
|
|
31
|
+
markers.push({ node, name: attribute.name, value: attribute.value });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
for (const { node, name } of markers)
|
|
36
|
+
node.removeAttribute(name);
|
|
37
|
+
return win.getComputedStyle(element).visibility;
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
for (const { node, name, value } of markers)
|
|
41
|
+
node.setAttribute(name, value);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// The injected runtime and a plugin's bundled helper share the binding.
|
|
46
|
+
const placementKey = Symbol.for('webview-native-ui-engine.projection-placement');
|
|
47
|
+
const bindProjectionPlacement = (element, measure, invalidate = () => { }) => {
|
|
48
|
+
const host = element;
|
|
49
|
+
const state = { projected: false, measure, invalidate };
|
|
50
|
+
host[placementKey] = state;
|
|
51
|
+
return {
|
|
52
|
+
get projected() {
|
|
53
|
+
return host[placementKey] === state && state.projected;
|
|
54
|
+
},
|
|
55
|
+
dispose() {
|
|
56
|
+
if (host[placementKey] === state)
|
|
57
|
+
delete host[placementKey];
|
|
58
|
+
state.projected = false;
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
|
|
5
63
|
const NATIVE_AD_ELEMENT_NAME = 'capacitor-admob-native';
|
|
6
64
|
const defineNativeAdElement = (findFeed) => {
|
|
7
65
|
const runtime = globalThis;
|
|
@@ -94,7 +152,7 @@ const measureNativeAdSlot = (element, scrollElement) => {
|
|
|
94
152
|
const domRect = element.getBoundingClientRect();
|
|
95
153
|
if (!element.isConnected ||
|
|
96
154
|
style.display === 'none' ||
|
|
97
|
-
style.visibility === 'hidden' ||
|
|
155
|
+
(style.visibility === 'hidden' && getUnprojectedVisibility(element) === 'hidden') ||
|
|
98
156
|
Number.parseFloat(style.opacity || '1') === 0 ||
|
|
99
157
|
domRect.width <= 0 ||
|
|
100
158
|
domRect.height <= 0) {
|
|
@@ -189,12 +247,10 @@ let sessionSequence = 0;
|
|
|
189
247
|
const feeds = new Map();
|
|
190
248
|
const createSessionId = () => { var _a, _b, _c; return (_c = (_b = (_a = globalThis.crypto) === null || _a === void 0 ? void 0 : _a.randomUUID) === null || _b === void 0 ? void 0 : _b.call(_a)) !== null && _c !== void 0 ? _c : `${Date.now()}-${++sessionSequence}-${Math.random().toString(36).slice(2)}`; };
|
|
191
249
|
/**
|
|
192
|
-
*
|
|
250
|
+
* Manages native ad placement and lifecycle in a scrolling feed.
|
|
193
251
|
*
|
|
194
252
|
* A stable `slotKey`, rather than the element or array index, owns the native
|
|
195
253
|
* ad lifecycle. Virtual-scroller compatibility has not yet been validated.
|
|
196
|
-
*
|
|
197
|
-
* @experimental Native overlay gesture routing is still being validated on devices.
|
|
198
254
|
*/
|
|
199
255
|
class NativeAdFeed {
|
|
200
256
|
constructor(options) {
|
|
@@ -217,6 +273,15 @@ class NativeAdFeed {
|
|
|
217
273
|
if (this.destroyed) {
|
|
218
274
|
return;
|
|
219
275
|
}
|
|
276
|
+
if (event instanceof Event &&
|
|
277
|
+
event.type === 'load' &&
|
|
278
|
+
event.target instanceof HTMLImageElement &&
|
|
279
|
+
event.target.dataset['admobSnapshot'] !== undefined)
|
|
280
|
+
return;
|
|
281
|
+
if (!(event instanceof Event) || !['scroll', 'touchmove', 'wheel'].includes(event.type)) {
|
|
282
|
+
for (const state of this.slots.values())
|
|
283
|
+
state.lastPlacement = undefined;
|
|
284
|
+
}
|
|
220
285
|
if (!(this.nativeScrollElement &&
|
|
221
286
|
event instanceof Event &&
|
|
222
287
|
['scroll', 'touchmove', 'wheel'].includes(event.type))) {
|
|
@@ -245,6 +310,9 @@ class NativeAdFeed {
|
|
|
245
310
|
if (feeds.size >= MAX_ACTIVE_FEEDS) {
|
|
246
311
|
throw new Error(`At most ${MAX_ACTIVE_FEEDS} native ad feeds can be active`);
|
|
247
312
|
}
|
|
313
|
+
if (options.androidScrollSnapshot && core.Capacitor.getPlatform() !== 'android') {
|
|
314
|
+
throw new Error('androidScrollSnapshot requires Android');
|
|
315
|
+
}
|
|
248
316
|
this.options = Object.assign(Object.assign({}, options), { feedId: id });
|
|
249
317
|
this.nativeScrollElement = core.Capacitor.getPlatform() === 'ios' ? options.scrollElement : undefined;
|
|
250
318
|
if (typeof IntersectionObserver !== 'undefined') {
|
|
@@ -331,7 +399,7 @@ class NativeAdFeed {
|
|
|
331
399
|
return handle;
|
|
332
400
|
}
|
|
333
401
|
attach(slotKey, element) {
|
|
334
|
-
var _a, _b, _c;
|
|
402
|
+
var _a, _b, _c, _d, _e;
|
|
335
403
|
this.assertActive();
|
|
336
404
|
const normalizedKey = slotKey.trim();
|
|
337
405
|
if (!normalizedKey) {
|
|
@@ -358,30 +426,58 @@ class NativeAdFeed {
|
|
|
358
426
|
};
|
|
359
427
|
applySlotDefaults(element, (_a = this.options.template) !== null && _a !== void 0 ? _a : exports.NativeAdTemplate.Medium);
|
|
360
428
|
state.element = element;
|
|
429
|
+
if (this.options.androidScrollSnapshot) {
|
|
430
|
+
const owner = ((_b = state.projectionOwner) !== null && _b !== void 0 ? _b : (state.projectionOwner = `admob-${crypto.randomUUID()}`));
|
|
431
|
+
element.dataset['admobProjection'] = owner;
|
|
432
|
+
state.projectionSelector = `[data-admob-projection="${owner}"]`;
|
|
433
|
+
(_c = state.placementBinding) === null || _c === void 0 ? void 0 : _c.dispose();
|
|
434
|
+
state.placementBinding = bindProjectionPlacement(element, () => {
|
|
435
|
+
const placement = this.measurePlacement(state);
|
|
436
|
+
state.lastPlacement = placement;
|
|
437
|
+
// The reveal request applies this exact placement; keep batch deduplication in sync.
|
|
438
|
+
if (this.lastPlacements) {
|
|
439
|
+
const previous = JSON.parse(this.lastPlacements);
|
|
440
|
+
previous.placements = previous.placements.map((item) => item.slotKey === state.slotKey ? placement : item);
|
|
441
|
+
this.lastPlacements = JSON.stringify(previous);
|
|
442
|
+
}
|
|
443
|
+
return Object.assign(Object.assign({}, placement), { sessionId: this.sessionId, sequence: ++this.placementSequence });
|
|
444
|
+
}, () => {
|
|
445
|
+
if (state.element !== element || this.destroyed)
|
|
446
|
+
return;
|
|
447
|
+
state.lastPlacement = undefined;
|
|
448
|
+
this.lastPlacements = undefined;
|
|
449
|
+
this.schedulePlacementUpdate();
|
|
450
|
+
});
|
|
451
|
+
}
|
|
361
452
|
state.generation += 1;
|
|
362
453
|
state.lastUsed = Date.now();
|
|
363
454
|
this.slots.set(normalizedKey, state);
|
|
364
455
|
this.elementSlots.set(element, normalizedKey);
|
|
365
456
|
this.updateScrollRoots();
|
|
366
|
-
(
|
|
367
|
-
(
|
|
457
|
+
(_d = this.intersectionObserver) === null || _d === void 0 ? void 0 : _d.observe(element);
|
|
458
|
+
(_e = this.resizeObserver) === null || _e === void 0 ? void 0 : _e.observe(element);
|
|
368
459
|
this.scheduleEvaluation();
|
|
369
460
|
}
|
|
370
461
|
detach(element) {
|
|
371
|
-
var _a, _b;
|
|
462
|
+
var _a, _b, _c;
|
|
372
463
|
const slotKey = this.elementSlots.get(element);
|
|
373
464
|
if (!slotKey) {
|
|
374
465
|
return;
|
|
375
466
|
}
|
|
376
467
|
const state = this.slots.get(slotKey);
|
|
377
468
|
if ((state === null || state === void 0 ? void 0 : state.element) === element) {
|
|
469
|
+
(_a = state.placementBinding) === null || _a === void 0 ? void 0 : _a.dispose();
|
|
470
|
+
state.placementBinding = undefined;
|
|
471
|
+
state.lastPlacement = undefined;
|
|
472
|
+
if (state.projectionOwner)
|
|
473
|
+
delete element.dataset['admobProjection'];
|
|
378
474
|
state.element = undefined;
|
|
379
475
|
state.intersecting = false;
|
|
380
476
|
state.generation += 1;
|
|
381
477
|
state.lastUsed = Date.now();
|
|
382
478
|
}
|
|
383
|
-
(
|
|
384
|
-
(
|
|
479
|
+
(_b = this.intersectionObserver) === null || _b === void 0 ? void 0 : _b.unobserve(element);
|
|
480
|
+
(_c = this.resizeObserver) === null || _c === void 0 ? void 0 : _c.unobserve(element);
|
|
385
481
|
this.elementSlots.delete(element);
|
|
386
482
|
this.updateScrollRoots();
|
|
387
483
|
void this.requestPlacementUpdate();
|
|
@@ -403,6 +499,7 @@ class NativeAdFeed {
|
|
|
403
499
|
throw new Error('Native ad capacity is currently in use by visible slots');
|
|
404
500
|
}
|
|
405
501
|
await this.loadState(state);
|
|
502
|
+
await this.requestPlacementUpdate();
|
|
406
503
|
});
|
|
407
504
|
}
|
|
408
505
|
/** Immediately hides this feed's overlays until {@link resume} is called. */
|
|
@@ -423,6 +520,8 @@ class NativeAdFeed {
|
|
|
423
520
|
/** Hides stale overlays and remeasures after an application-driven layout change. */
|
|
424
521
|
async invalidateLayout() {
|
|
425
522
|
this.assertActive();
|
|
523
|
+
for (const state of this.slots.values())
|
|
524
|
+
state.lastPlacement = undefined;
|
|
426
525
|
this.layoutInvalidated = true;
|
|
427
526
|
this.scheduleEvaluation();
|
|
428
527
|
if (!(await this.requestPlacementUpdate())) {
|
|
@@ -507,7 +606,9 @@ class NativeAdFeed {
|
|
|
507
606
|
}
|
|
508
607
|
this.settleTimer = setTimeout(() => {
|
|
509
608
|
this.layoutInvalidated = false;
|
|
510
|
-
void this.runOperation(() =>
|
|
609
|
+
void this.runOperation(async () => {
|
|
610
|
+
await this.evaluate();
|
|
611
|
+
});
|
|
511
612
|
}, SCROLL_SETTLE_MS);
|
|
512
613
|
}
|
|
513
614
|
async evaluate() {
|
|
@@ -524,7 +625,7 @@ class NativeAdFeed {
|
|
|
524
625
|
.filter((state) => state.status === 'idle')
|
|
525
626
|
.slice(0, Math.max(0, MAX_NATIVE_ADS - occupied));
|
|
526
627
|
await Promise.allSettled(loadable.map((state) => this.loadState(state)));
|
|
527
|
-
|
|
628
|
+
await this.requestPlacementUpdate();
|
|
528
629
|
}
|
|
529
630
|
async loadState(state) {
|
|
530
631
|
var _a;
|
|
@@ -534,16 +635,7 @@ class NativeAdFeed {
|
|
|
534
635
|
state.status = 'loading';
|
|
535
636
|
const loadVersion = ++state.loadVersion;
|
|
536
637
|
try {
|
|
537
|
-
await bridge.loadNativeAd({
|
|
538
|
-
feedId: this.options.feedId,
|
|
539
|
-
sessionId: this.sessionId,
|
|
540
|
-
slotKey: state.slotKey,
|
|
541
|
-
template: (_a = this.options.template) !== null && _a !== void 0 ? _a : exports.NativeAdTemplate.Medium,
|
|
542
|
-
style: this.options.style,
|
|
543
|
-
adId: this.options.adId,
|
|
544
|
-
isTesting: this.options.isTesting,
|
|
545
|
-
npa: this.options.npa,
|
|
546
|
-
});
|
|
638
|
+
await bridge.loadNativeAd(Object.assign({ feedId: this.options.feedId, sessionId: this.sessionId, slotKey: state.slotKey, template: (_a = this.options.template) !== null && _a !== void 0 ? _a : exports.NativeAdTemplate.Medium, style: this.options.style, adId: this.options.adId, isTesting: this.options.isTesting, npa: this.options.npa }, (state.projectionSelector ? { projectionSelector: state.projectionSelector } : {})));
|
|
547
639
|
if (state.loadVersion === loadVersion) {
|
|
548
640
|
state.status = 'loaded';
|
|
549
641
|
state.lastUsed = Date.now();
|
|
@@ -577,6 +669,9 @@ class NativeAdFeed {
|
|
|
577
669
|
}
|
|
578
670
|
getVisibleStates() {
|
|
579
671
|
return [...this.slots.values()].filter((state) => {
|
|
672
|
+
var _a;
|
|
673
|
+
if (this.reuseProjectedPlacement(state))
|
|
674
|
+
return state.intersecting && !!((_a = state.lastPlacement) === null || _a === void 0 ? void 0 : _a.visible);
|
|
580
675
|
const measured = state.element && state.intersecting ? measureNativeAdSlot(state.element) : undefined;
|
|
581
676
|
return Boolean(measured && this.isSupportedSize(measured));
|
|
582
677
|
});
|
|
@@ -625,6 +720,7 @@ class NativeAdFeed {
|
|
|
625
720
|
return result;
|
|
626
721
|
}
|
|
627
722
|
async destroyResources() {
|
|
723
|
+
var _a;
|
|
628
724
|
await Promise.all([
|
|
629
725
|
...[...this.listenerHandles].map((handle) => handle.remove()),
|
|
630
726
|
...(this.nativeStarted && bridge
|
|
@@ -633,9 +729,39 @@ class NativeAdFeed {
|
|
|
633
729
|
]);
|
|
634
730
|
this.nativeStarted = false;
|
|
635
731
|
this.listenerHandles.clear();
|
|
732
|
+
for (const state of this.slots.values())
|
|
733
|
+
(_a = state.placementBinding) === null || _a === void 0 ? void 0 : _a.dispose();
|
|
636
734
|
this.slots.clear();
|
|
637
735
|
this.elementSlots.clear();
|
|
638
736
|
}
|
|
737
|
+
reuseProjectedPlacement(state) {
|
|
738
|
+
var _a, _b, _c;
|
|
739
|
+
return Boolean(((_a = state.placementBinding) === null || _a === void 0 ? void 0 : _a.projected) &&
|
|
740
|
+
((_b = state.lastPlacement) === null || _b === void 0 ? void 0 : _b.visible) &&
|
|
741
|
+
((_c = state.element) === null || _c === void 0 ? void 0 : _c.isConnected) &&
|
|
742
|
+
!this.paused &&
|
|
743
|
+
!this.destroyed &&
|
|
744
|
+
!document.hidden &&
|
|
745
|
+
state.status === 'loaded' &&
|
|
746
|
+
state.lastPlacement.generation === state.generation);
|
|
747
|
+
}
|
|
748
|
+
measurePlacement(state) {
|
|
749
|
+
const measured = state.element ? measureNativeAdSlot(state.element, this.nativeScrollElement) : undefined;
|
|
750
|
+
return {
|
|
751
|
+
feedId: this.options.feedId,
|
|
752
|
+
slotKey: state.slotKey,
|
|
753
|
+
generation: state.generation,
|
|
754
|
+
visible: Boolean(!this.paused &&
|
|
755
|
+
!this.destroyed &&
|
|
756
|
+
state.status === 'loaded' &&
|
|
757
|
+
(!this.layoutInvalidated || this.options.androidScrollSnapshot) &&
|
|
758
|
+
measured &&
|
|
759
|
+
this.isSupportedSize(measured) &&
|
|
760
|
+
!document.hidden),
|
|
761
|
+
rect: measured === null || measured === void 0 ? void 0 : measured.rect,
|
|
762
|
+
clipRect: measured === null || measured === void 0 ? void 0 : measured.clipRect,
|
|
763
|
+
};
|
|
764
|
+
}
|
|
639
765
|
requestPlacementUpdate(allowDirect = false) {
|
|
640
766
|
this.updateDirty = true;
|
|
641
767
|
this.placementAcknowledgementRequired || (this.placementAcknowledgementRequired = !allowDirect);
|
|
@@ -653,19 +779,15 @@ class NativeAdFeed {
|
|
|
653
779
|
this.updateDirty = false;
|
|
654
780
|
const allowDirect = !this.placementAcknowledgementRequired;
|
|
655
781
|
this.placementAcknowledgementRequired = false;
|
|
782
|
+
const retainedSlots = new Set();
|
|
656
783
|
const placements = [...this.slots.values()]
|
|
657
784
|
.filter((state) => state.status === 'loaded')
|
|
658
785
|
.map((state) => {
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
generation: state.generation,
|
|
665
|
-
visible,
|
|
666
|
-
rect: measured === null || measured === void 0 ? void 0 : measured.rect,
|
|
667
|
-
clipRect: measured === null || measured === void 0 ? void 0 : measured.clipRect,
|
|
668
|
-
};
|
|
786
|
+
if (this.reuseProjectedPlacement(state)) {
|
|
787
|
+
retainedSlots.add(state.slotKey);
|
|
788
|
+
return state.lastPlacement;
|
|
789
|
+
}
|
|
790
|
+
return (state.lastPlacement = this.measurePlacement(state));
|
|
669
791
|
});
|
|
670
792
|
const scrollContainer = this.nativeScrollElement
|
|
671
793
|
? measureNativeAdScrollContainer(this.nativeScrollElement)
|
|
@@ -675,7 +797,17 @@ class NativeAdFeed {
|
|
|
675
797
|
continue;
|
|
676
798
|
}
|
|
677
799
|
try {
|
|
678
|
-
const batch = Object.assign({ feedId: this.options.feedId, sessionId: this.sessionId, sequence: ++this.placementSequence, placements
|
|
800
|
+
const batch = Object.assign({ feedId: this.options.feedId, sessionId: this.sessionId, sequence: ++this.placementSequence, placements: placements.map((placement) => {
|
|
801
|
+
return retainedSlots.has(placement.slotKey)
|
|
802
|
+
? {
|
|
803
|
+
feedId: placement.feedId,
|
|
804
|
+
slotKey: placement.slotKey,
|
|
805
|
+
generation: placement.generation,
|
|
806
|
+
visible: true,
|
|
807
|
+
retain: true,
|
|
808
|
+
}
|
|
809
|
+
: placement;
|
|
810
|
+
}) }, (scrollContainer ? { scrollContainer } : {}));
|
|
679
811
|
const channel = core.Capacitor.getPlatform() === 'android'
|
|
680
812
|
? window
|
|
681
813
|
.capacitorAdMobPlacements
|