@chipmobilesdk/rn-ads 0.5.1 → 0.7.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
@@ -1,5 +1,183 @@
1
1
  # @chipmobilesdk/rn-ads
2
2
 
3
+ ## 0.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - The ads instance is split by responsibility, and the banner host's wiring is off the
8
+ public type. ([#19](https://github.com/operationd8979/chip-mobile-sdk/pull/19))
9
+
10
+ - `AdsInstance` no longer has an `internals` field. It was undocumented, but any
11
+ consumer could use it to emit `impression` or `reward_earned` into the event stream
12
+ used for revenue reconciliation. `AdsBanner` now reaches an instance's wiring through
13
+ a private registry, so a banner given an instance the app wrote by hand renders
14
+ nothing instead of throwing.
15
+ - Banner events carry the instance's clock (`createAds({ now })`), like full-screen
16
+ events, and resolving a banner during render no longer mints a correlation id.
17
+ - `allTestAdUnits()` moved from the engine module to `@chipmobilesdk/rn-ads/testing`.
18
+ - Rewards from AdMob go through the one `normalizeReward`; an unused consent port guard
19
+ and the throttle's unused `reset`/`lastRequestAt` are gone.
20
+ - Internally, `createAds` is now a 142-line composition root over `core/instance/`
21
+ (gate, init, load, show, consent, internals), down from one 1,899-line closure. Every
22
+ path that closes the request gate goes through one teardown.
23
+
24
+ - Four fixes to how ads behave around throttling, retries and show timeouts. ([#19](https://github.com/operationd8979/chip-mobile-sdk/pull/19))
25
+
26
+ - A banner refused by the request throttle now tries again by itself once the window
27
+ passes. Before, it stayed empty until it was remounted, so a banner on a screen the
28
+ user returned to within a minute never filled.
29
+ - A load retry waits at least as long as the placement's `minRequestIntervalMs`
30
+ requires, so a retry policy cannot request faster than the floor allows. The
31
+ `retry_scheduled` event reports the wait actually used.
32
+ - A request AdMob refuses outright (an invalid request or ad unit, a missing app id)
33
+ is the new non-retryable `provider_rejected` code instead of a retryable
34
+ `provider_error`, so a retry policy no longer spends billable requests on it. Engines
35
+ may classify a failure with the new optional `kind` on the `failed` event.
36
+ - A show that times out and then opens anyway is reported once, not twice. The late
37
+ ad holds the full-screen lock until it is dismissed, and its reward and dismissal
38
+ still reach the app.
39
+
40
+ - The banner renders through the engine port. `AdsEngine` has an optional `banner`
41
+ capability (`isAvailable()` and a `Component` with neutral props: `adUnitId`,
42
+ `personalizedAds`, `onLoaded`, `onFailed`, `onImpression`, `onClicked`), and the AdMob
43
+ adapter supplies the adaptive `BannerAd` there. `AdsBanner` no longer loads the AdMob
44
+ bridge itself, so a custom or future iOS engine provides its own banner, and the fake
45
+ engine from `./testing` renders one in tests without a mocked native module. An engine
46
+ without `banner` keeps working; its banners report `unavailable`. ([#19](https://github.com/operationd8979/chip-mobile-sdk/pull/19))
47
+
48
+ ### Patch Changes
49
+
50
+ - Internal: no module in these packages is over 500 lines any more. The camera controller's session I/O moved to `sessionRuntime` and its contract to `contract.ts`; the Play adapter's shape translation to `iapShapes`; the purchase flow's failure mapping and its receipt-validation hook to modules of their own; the AI client's quota request to `quotaRequest`; the in-memory adapter's attachments and the fake ads engine's clock likewise. Behaviour is unchanged — the splits are by responsibility, and every package's suite passes as it stood. ([#19](https://github.com/operationd8979/chip-mobile-sdk/pull/19))
51
+
52
+ The repository also measures what it promised: `npm run perf` prints the median of nine runs of the deterministic hot paths against written budgets (recurrence expansion, theme construction, translation lookup, chart geometry, filter compilation), the demo app has a Performance screen reporting startup phases, time to interactive and the same benchmarks on Hermes, and `npm run check:file-size` keeps the limit from drifting. The budgets and the measured figures are recorded in `docs/standards.md` §VI.
53
+
54
+ ## 0.6.0
55
+
56
+ ### Minor Changes
57
+
58
+ - One diagnostic stream: install a sink with `installSdkSink` from
59
+ `@chipmobilesdk/rn-core` and it receives what every package observes (events,
60
+ failures, warnings) as `SdkDiagnostic`s, redacted. Listeners are notified through
61
+ rn-core, so a listener that throws no longer stops the others nor fails the call that
62
+ notified, and its error is reported rather than swallowed. ([#17](https://github.com/operationd8979/chip-mobile-sdk/pull/17))
63
+
64
+ - rn-ads: a banner's auto-refresh is a new cycle, so its `loaded` and `impression`
65
+ events are no longer deduplicated away; the dedupe memory is bounded. A throwing
66
+ status, consent or placement listener is reported as an `internal_error` event.
67
+ - rn-i18n: new `onDiagnostic` config receives every diagnostic (`I18nDiagnostic`,
68
+ with a stable `code`); without it, warnings go to `console.warn` in development
69
+ builds only. The warn-once memory is bounded.
70
+ - rn-chart: now depends on rn-core for its diagnostics.
71
+ - rn-payment: status, entitlement and pending listeners report a throw to the logger
72
+ instead of swallowing it.
73
+
74
+ - The package's errors extend `SdkError` from the new `@chipmobilesdk/rn-core`
75
+ dependency: `isSdkError` recognises them alongside every other package's, and each
76
+ carries `pkg`, `code` and `retryable`. Names and codes are unchanged. ([#17](https://github.com/operationd8979/chip-mobile-sdk/pull/17))
77
+
78
+ - rn-ai and rn-camera: `retryable` is the new name of `recoverable`, which stays as a
79
+ deprecated alias.
80
+ - rn-auth: `retryable` is true for `network-unavailable`. `redact` also removes URLs,
81
+ e-mail addresses and UUIDs.
82
+ - rn-ads: `redact` also removes JWTs and e-mail addresses, and reduces an `AdsError`
83
+ to its code.
84
+ - rn-ai: an error message quoted from the gateway is pattern-redacted.
85
+ - rn-camera: `redact` also removes JWTs, e-mail addresses and UUIDs.
86
+ - rn-theme: `ThemeConfigError` has `code: 'invalid_config'` and `ThemeContrastError`
87
+ `code: 'contrast'` (new `ThemeErrorCode` type).
88
+ - rn-i18n: `I18nConfigError` has `code: 'invalid_config'` and `UnitConversionError`
89
+ `code: 'unit_conversion'` (new `I18nErrorCode` type).
90
+
91
+ - Timers, retries and bounds now come from `@chipmobilesdk/rn-core`, and the drift
92
+ between the copies is fixed. ([#17](https://github.com/operationd8979/chip-mobile-sdk/pull/17))
93
+
94
+ - rn-ads: a retry policy out of bounds throws `retry_out_of_bounds`, as documented,
95
+ instead of `timeout_out_of_bounds`; a placement's `minRequestIntervalMs` out of
96
+ bounds throws `invalid_config`.
97
+ - rn-payment: backoff validates `initialDelayMs` (100 ms to 60 s, not above
98
+ `maxDelayMs`) and `factor` (1 to 10); `factor: 0` used to retry at once, forever.
99
+ Every timeout, backoff or interval out of bounds now throws the new
100
+ `PAYMENT_INVALID_CONFIG` instead of `PAYMENT_INVALID_CATALOG`, which is about
101
+ products.
102
+ - rn-ai: concurrent calls share one behavior-config request, so a cold start makes
103
+ one instead of one per call. A silent retry after a config reload waits
104
+ `recovery.retryBackoffMs`, which was parsed but never applied.
105
+
106
+ - Native peers load through `lazyPeer` from `@chipmobilesdk/rn-core`, on first use,
107
+ and a missing one is each package's own typed error. ([#17](https://github.com/operationd8979/chip-mobile-sdk/pull/17))
108
+
109
+ - rn-auth: creating `auth` no longer loads react-native-app-auth or
110
+ react-native-keychain; each loads on first use. New entrypoints
111
+ `@chipmobilesdk/rn-auth/app-auth` and `/keychain`. The default platform comes from
112
+ rn-core.
113
+ - rn-camera: a preview whose VisionCamera feed cannot load fails the session with
114
+ `cameraUnavailable` (new `controller.reportSurfaceUnavailable()`), instead of
115
+ showing black with no error. A missing VisionCamera module is `cameraUnavailable`.
116
+ - rn-notification: `createMmkvStore` reports a fallback to memory as
117
+ `STATE_STORE_DEGRADED` (to the SDK sinks and a new `onDegraded` option) and
118
+ exposes `durable`.
119
+ - rn-payment: an iOS app validates its catalog against the iOS product ids; the
120
+ default platform used to be Android everywhere. A missing react-native-iap is
121
+ `PAYMENT_UNAVAILABLE`.
122
+ - rn-theme: `buildThemes` runs without React Native (new `hairlineWidth` option) and
123
+ importing the package no longer loads Unistyles; `applyThemeMode` loads it on
124
+ first use.
125
+ - rn-ads, rn-local-db: the same loading, with the same error codes as before.
126
+
127
+ - Hooks read through `useSyncExternalStore` (rn-core's `useExternalStore`) and
128
+ re-render only when what they return changes. ([#17](https://github.com/operationd8979/chip-mobile-sdk/pull/17))
129
+
130
+ - rn-auth: `useProfile` and `useAccessTier` no longer re-render for a status change,
131
+ and the provider's context no longer changes on every transition. New
132
+ `useAuthSelector`. `auth.getState()` returns the same frozen object until the state
133
+ changes, instead of a copy per call.
134
+ - rn-payment: `useEntitlement` re-renders only when its own entitlement changes; the
135
+ engine keeps unchanged entitlement objects across syncs and does not notify for a
136
+ sync that changed nothing.
137
+ - rn-ads: `usePlacementState` and `useAdsStatus` read tear-free; `useAdsEvents` keeps
138
+ one subscription when given an inline listener.
139
+ - rn-i18n: `useTranslation` returns the same `t` until the locale changes, and
140
+ `useLocale`'s `setLocale` is stable.
141
+ - rn-ai: new `useAiBehaviorState`, with `client.subscribe` and a stable
142
+ `client.getBehaviorState()`.
143
+
144
+ - Port conformance suites: one list of checks that a package's fake and its real adapter
145
+ both pass, so a test written against the fake proves something about the device. ([#17](https://github.com/operationd8979/chip-mobile-sdk/pull/17))
146
+
147
+ - rn-core: `@chipmobilesdk/rn-core/testing` adds `runChecks`, `expectConformance`,
148
+ `ensure` and `createFaultRegistry` (deterministic fault injection by named point).
149
+ - rn-camera: a `./testing` entrypoint with `runPermissionPortConformance`,
150
+ `runCameraEngineConformance` and the test adapter, which stays on the main
151
+ entrypoint too until the next major. `createTestCamera(...).prompts()` counts system
152
+ prompts.
153
+ - rn-payment: `runProviderPortConformance` in `./testing`, passed by the fake store and
154
+ the Play adapter.
155
+ - rn-ads: `runAdsEngineConformance` in `./testing`, passed by the fake engine and the
156
+ AdMob adapter.
157
+ - rn-auth: a `./testing` entrypoint with `createMemorySecureStore`,
158
+ `runSecureTokenStoreConformance` and `runOidcAuthClientConformance`, passed by the
159
+ in-memory doubles and the keychain and app-auth adapters.
160
+
161
+ ### Patch Changes
162
+
163
+ - One app-lifecycle source, and a startup that needs no extra frame. ([#17](https://github.com/operationd8979/chip-mobile-sdk/pull/17))
164
+
165
+ - rn-core: `appLifecycle()`, a shared, reference-counted `AppState` subscription that
166
+ reports changes only (`active`, `inactive`, `background`), with `onForeground` for a
167
+ return from the background and `createManualLifecycle` in `./testing`.
168
+ - rn-camera, rn-ads and rn-notification hear the lifecycle through it instead of
169
+ subscribing to `AppState` each, so an app holds one native listener. The camera
170
+ controller's core layer no longer imports `react-native`.
171
+ - rn-payment: `syncOnForeground(engine)` and `<PaymentProvider syncOnForeground>` call
172
+ `handleForeground()` on each return from the background. Off by default; calling
173
+ `handleForeground()` yourself works as before.
174
+ - rn-i18n: `i18n.initSync()` resolves the locale before the first render when the
175
+ storage adapter has the new optional `getItemSync`, and returns false otherwise.
176
+ `localeStorageFromKeyValue(port)` builds such an adapter from rn-core's
177
+ `KeyValuePort` (MMKV).
178
+ - Updated dependencies ([#17](https://github.com/operationd8979/chip-mobile-sdk/pull/17), [#17](https://github.com/operationd8979/chip-mobile-sdk/pull/17), [#17](https://github.com/operationd8979/chip-mobile-sdk/pull/17)):
179
+ - @chipmobilesdk/rn-core@0.1.0
180
+
3
181
  ## 0.5.1
4
182
 
5
183
  ### Patch Changes
package/README.md CHANGED
@@ -268,10 +268,14 @@ ads.subscribeEvents(event => {
268
268
 
269
269
  **Ordering and exactly-once** are documented and tested. Within one `correlationId`, `loaded` / `display_started` / `impression` / `dismissed` / `reward_earned` occur at most once; `click` may repeat. After `dispose()`, zero events are emitted — a late provider callback is dropped.
270
270
 
271
- **Errors** carry a stable `code`, a log-safe `message`, the `operation`, a `retryable` flag, and a redacted `cause`. Codes are public API; renaming one is a breaking change.
271
+ **Errors** carry a stable `code`, a log-safe `message`, the `operation`, a `retryable` flag, and a redacted `cause`. Codes are public API; renaming one is a breaking change. `AdsError` extends `SdkError` from `@chipmobilesdk/rn-core`, so `isSdkError(error)` recognises it alongside every other package's errors, `error.pkg` names this package, and `error.redactedCause` (when present) is the underlying failure reduced to log-safe text.
272
+
273
+ A config value outside its band names its own field and code: a timeout or validity window is `timeout_out_of_bounds`, a retry policy `retry_out_of_bounds`, a placement's `minRequestIntervalMs` `invalid_config`.
272
274
 
273
275
  Retryable: `no_fill` `network_error` `load_timeout` `show_timeout` `ad_expired` `not_ready` `busy` `consumed` `app_inactive` `provider_error`
274
- Not retryable: `invalid_config` `duplicate_placement_key` `missing_ad_unit_id` `format_mismatch` `placement_not_found` `production_in_dev_build` `timeout_out_of_bounds` `unsupported_platform` `provider_missing` `disabled` `consent_required` `consent_undetermined` `requires_reinitialization` `internal_error`
276
+ Not retryable: `invalid_config` `duplicate_placement_key` `missing_ad_unit_id` `format_mismatch` `placement_not_found` `production_in_dev_build` `timeout_out_of_bounds` `retry_out_of_bounds` `unsupported_platform` `provider_missing` `disabled` `consent_required` `consent_undetermined` `requires_reinitialization` `provider_rejected` `internal_error`
277
+
278
+ `provider_rejected` (0.7.0) means AdMob refused the request itself — an invalid request or ad unit, a missing app id — so a retry policy does not spend more requests on it. Before, those were `provider_error` and were retried.
275
279
 
276
280
  Attach any logger or analytics you already use. The package depends on none:
277
281
 
@@ -741,6 +745,11 @@ engine.failConsentInfoRead(); // the re-read after a failure r
741
745
  engine.consentCalls // includes 'showPrivacyOptionsForm'
742
746
  ```
743
747
 
748
+ `runAdsEngineConformance(harness)` from `./testing` holds the checks every `AdsEngine`
749
+ must pass — initialization, `loaded` reaching a subscriber and nothing after it
750
+ unsubscribes, a reward and a dismissal, one handle per ad, and a dispose that is safe
751
+ twice. The package runs it against the fake engine and the AdMob adapter.
752
+
744
753
  On device, verify with Google's official test ads only. `npm run android`, then exercise each `Ads · …` screen in the demo app.
745
754
 
746
755
  ---
@@ -797,7 +806,7 @@ On device, verify with Google's official test ads only. `npm run android`, then
797
806
  Adding iOS contributes exactly three things and changes **nothing** you have already written:
798
807
 
799
808
  1. **`PlacementDefinition.iosAdUnitId`** — the optional field already exists in v1's type, so existing Android-only catalogs stay valid.
800
- 2. **A second engine adapter** behind the existing `AdsEngine` port. The port names no platform and no provider; that is what makes this additive.
809
+ 2. **A second engine adapter** behind the existing `AdsEngine` port. The port names no platform and no provider; that is what makes this additive. The banner is part of the port too (`AdsEngine.banner`, since 0.7.0): an adapter supplies its own banner component, which receives neutral props (`adUnitId`, `personalizedAds`, `onLoaded`, `onFailed`, `onImpression`, `onClicked`), and `AdsBanner` renders whatever the engine offers.
801
810
  3. **iOS entries in the private test-ad-unit table.**
802
811
 
803
812
  Your `createAds`, `initialize`, `load`, `show`, `AdsBanner`, and every subscription keep their current signatures. The exported state, event, and error models do not change.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chipmobilesdk/rn-ads",
3
- "version": "0.5.1",
3
+ "version": "0.7.0",
4
4
  "private": false,
5
5
  "license": "UNLICENSED",
6
6
  "main": "src/index.ts",
@@ -31,9 +31,12 @@
31
31
  ],
32
32
  "sideEffects": false,
33
33
  "scripts": {
34
- "typecheck": "tsc -p tsconfig.json --noEmit",
34
+ "typecheck": "tsc -p tsconfig.json && tsc -p tsconfig.test.json",
35
35
  "pack:check": "npm pack --dry-run"
36
36
  },
37
+ "dependencies": {
38
+ "@chipmobilesdk/rn-core": "^0.1.0"
39
+ },
37
40
  "peerDependencies": {
38
41
  "react": ">=19",
39
42
  "react-native": ">=0.85",
@@ -173,8 +173,9 @@ policy; everything else returns a typed result rather than throwing) · `AdsProv
173
173
  `SHOW_TIMEOUT_BOUNDS`, `AD_VALIDITY_BOUNDS`, `APP_OPEN_VALIDITY_BOUNDS`, `RETRY_BOUNDS`,
174
174
  `UNDECIDED_WARNING_BOUNDS`, `DEFAULT_BANNER_MIN_REQUEST_INTERVAL_MS`,
175
175
  `DEFAULT_FULL_SCREEN_MIN_REQUEST_INTERVAL_MS`, `MIN_REQUEST_INTERVAL_BOUNDS`,
176
- `defaultMinRequestIntervalMs` · `createFakeAdsEngine`, `createTestClock` from
177
- `@chipmobilesdk/rn-ads/testing` (test doubles only).
176
+ `defaultMinRequestIntervalMs` · `createFakeAdsEngine`, `createTestClock`,
177
+ `runAdsEngineConformance`, `allTestAdUnits` from `@chipmobilesdk/rn-ads/testing` (test
178
+ doubles only).
178
179
 
179
180
  Instance: `initialize`, `updatePrivacy`, `setEnabled`, `setEnablement`, `setAppActive`,
180
181
  `consent`, `getStatus`, `subscribeStatus`, `load`, `show`, `getPlacementState`,
@@ -146,10 +146,12 @@ export function validateConfig(config: AdsConfig, options: ValidateOptions): Res
146
146
  key,
147
147
  placement.minRequestIntervalMs === undefined
148
148
  ? defaultMinRequestIntervalMs(placement.format)
149
- : checkBand(
149
+ : // A request floor, not a timeout: its own code (ADS-16).
150
+ checkBand(
150
151
  `placements["${key}"].minRequestIntervalMs`,
151
152
  placement.minRequestIntervalMs,
152
153
  MIN_REQUEST_INTERVAL_BOUNDS,
154
+ 'invalid_config',
153
155
  ),
154
156
  );
155
157
  }