@chipmobilesdk/rn-ads 0.2.1 → 0.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/CHANGELOG.md CHANGED
@@ -6,6 +6,52 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
6
6
 
7
7
  The event catalog, error codes, state names, and result unions are **public API**. Renaming or removing any of them is a major change.
8
8
 
9
+ ## [0.3.0] — 2026-10-10
10
+
11
+ Additive minor release closing one gap that surfaced when `0.2.x` met an app placing a banner inside a view the user swipes between. It is a **capability** gap: the app could not close it itself without reaching past the package, which is the thing this package exists to prevent.
12
+
13
+ **Upgrading requires one change, compile-time only**, plus one behaviour change worth reading: see [Changed](#changed-1).
14
+
15
+ ### Added
16
+
17
+ - **Per-placement request throttling** (`minRequestIntervalMs`). A minimum interval between provider *requests* for one placement.
18
+
19
+ The pattern it exists for: a banner mounted inside a view the user navigates between — a day in a calendar, a tab, a row scrolling in and out of a list — is unmounted and remounted by React on every navigation, and **each remount is a billable request**. Ten quick swipes were ten requests in a few seconds. Ad networks read that as invalid traffic, and the penalty is account-level and not recoverable by shipping a fix.
20
+
21
+ That is why this belongs in the package rather than in a README note. It sits in the same class as the test-ad substitution already enforced in non-production builds: a safety property the library guarantees rather than documents and hopes each app remembers.
22
+
23
+ - **Format-aware defaults.** `60 000 ms` for a banner — AdMob's own documented floor for banner auto-refresh, so the number is the provider's rather than invented. `0` (off) for `interstitial`, `rewarded` and `app_open`: those are loaded by an explicit call, not mounted by a renderer, so they never had the problem, and a default floor would break an app that legitimately loads three times in two minutes.
24
+ - **Per-placement override**, bounds `0`–`600 000 ms`, validated eagerly at `createAds` and naming the offending placement — so a misconfiguration fails at construction rather than by refusing every request in production, which is the one moment nobody is watching for a configuration error.
25
+ - **Rotation is exempt.** A width re-adaptation inside one mount is the same banner the placement already requested; refusing it would leave a rotated screen with no banner for up to a minute. The slot is charged once per *mount*, not per request.
26
+ - **A concurrent duplicate load is exempt.** Those callers already join the in-flight request, and charging the second would turn an existing guarantee into a refusal.
27
+ - **Clock-robust.** A backwards clock — a manual time change, an NTP correction — fails open rather than locking a placement out until the clock catches up. The throttle guards against bursts, and a backwards clock is not a burst.
28
+
29
+ - `PlacementState` gains `'throttled'`, and `LoadResult` gains `{ status: 'throttled', retryAfterMs }`. Deliberately distinct from `'failed'` and from a no-fill: no-fill means the network had nothing to give, this means we did not ask. An app that conflates them will misdiagnose the first time a placement goes quiet.
30
+
31
+ - `request_throttled` event, carrying `retryAfterMs` and the `minRequestIntervalMs` that refused it, so the cause is readable from the event stream alone. No `load_started` accompanies it — a refused request is not a cycle, and the stream must not claim a billable request that never happened.
32
+
33
+ - Exported `DEFAULT_BANNER_MIN_REQUEST_INTERVAL_MS`, `DEFAULT_FULL_SCREEN_MIN_REQUEST_INTERVAL_MS`, `MIN_REQUEST_INTERVAL_BOUNDS`, `defaultMinRequestIntervalMs`, so an app reads the floor it is bound by instead of discovering it from a `throttled` state in production.
34
+
35
+ ### Changed
36
+
37
+ - **A banner placement now has a 60s floor between requests.** If your banner lives inside a list row, a tab, or a view the user swipes between, it will report `'throttled'` after a quick navigation where `0.2.x` silently issued another request. That silent request was the defect; this is the fix.
38
+
39
+ Before reaching for `minRequestIntervalMs: 0`, consider the architectural answer: a banner kept **outside** the subtree that remounts stays mounted across navigation, keeps its ad, and costs one request. The throttle is a safety net for when that is not possible.
40
+
41
+ - Three unions gained a member, so an exhaustive `switch` over any of them needs the new case or a `default`:
42
+
43
+ | Union | New member |
44
+ |---|---|
45
+ | `PlacementState` | `'throttled'` |
46
+ | `LoadResult` | `{ status: 'throttled', retryAfterMs }` |
47
+ | `AdEventType` | `'request_throttled'` |
48
+
49
+ - Full-screen formats are unchanged: their default floor is `0`, so request counts for `interstitial`, `rewarded` and `app_open` are byte-identical to `0.2.x` unless you configure a floor.
50
+
51
+ ### Not added, deliberately
52
+
53
+ - **No frequency cap, and none planned.** This release limits how often a placement may *ask*; how often an app *shows* remains entirely the app's. Pacing has no correct default — one app counts items created, another counts screens opened, another counts minutes — so shipping one would make the package own policy that belongs to the app. For "an interstitial every third save", count the saves in the app and call `show` when the rule says so.
54
+
9
55
  ## [0.2.0] — 2026-09-13
10
56
 
11
57
  Additive minor release closing five gaps that surfaced when `0.1.0` met an app selling an ad-removal tier. Four of the five were gaps in **capability** — an app could not patch them itself without importing the provider directly, which is the one thing this package exists to prevent.