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