@chipmobilesdk/rn-ads 0.6.0 → 0.8.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 +139 -0
- package/README.md +4 -2
- package/lib/commonjs/config/resolveAdUnit.js +80 -0
- package/lib/commonjs/config/resolveAdUnit.js.map +1 -0
- package/lib/commonjs/config/types.js +138 -0
- package/lib/commonjs/config/types.js.map +1 -0
- package/lib/commonjs/config/validate.js +150 -0
- package/lib/commonjs/config/validate.js.map +1 -0
- package/lib/commonjs/core/adOriginatedReturn.js +119 -0
- package/lib/commonjs/core/adOriginatedReturn.js.map +1 -0
- package/lib/commonjs/core/adsInstance.js +129 -0
- package/lib/commonjs/core/adsInstance.js.map +1 -0
- package/lib/commonjs/core/consentFlow.js +154 -0
- package/lib/commonjs/core/consentFlow.js.map +1 -0
- package/lib/commonjs/core/enablement.js +64 -0
- package/lib/commonjs/core/enablement.js.map +1 -0
- package/lib/commonjs/core/fullScreenLock.js +48 -0
- package/lib/commonjs/core/fullScreenLock.js.map +1 -0
- package/lib/commonjs/core/gates.js +95 -0
- package/lib/commonjs/core/gates.js.map +1 -0
- package/lib/commonjs/core/initMachine.js +159 -0
- package/lib/commonjs/core/initMachine.js.map +1 -0
- package/lib/commonjs/core/instance/consent.js +155 -0
- package/lib/commonjs/core/instance/consent.js.map +1 -0
- package/lib/commonjs/core/instance/context.js +217 -0
- package/lib/commonjs/core/instance/context.js.map +1 -0
- package/lib/commonjs/core/instance/gate.js +194 -0
- package/lib/commonjs/core/instance/gate.js.map +1 -0
- package/lib/commonjs/core/instance/init.js +326 -0
- package/lib/commonjs/core/instance/init.js.map +1 -0
- package/lib/commonjs/core/instance/internals.js +115 -0
- package/lib/commonjs/core/instance/internals.js.map +1 -0
- package/lib/commonjs/core/instance/load.js +458 -0
- package/lib/commonjs/core/instance/load.js.map +1 -0
- package/lib/commonjs/core/instance/show.js +243 -0
- package/lib/commonjs/core/instance/show.js.map +1 -0
- package/lib/commonjs/core/instance/types.js +6 -0
- package/lib/commonjs/core/instance/types.js.map +1 -0
- package/lib/commonjs/core/placementMachine.js +116 -0
- package/lib/commonjs/core/placementMachine.js.map +1 -0
- package/lib/commonjs/core/privacyGate.js +93 -0
- package/lib/commonjs/core/privacyGate.js.map +1 -0
- package/lib/commonjs/core/requestThrottle.js +152 -0
- package/lib/commonjs/core/requestThrottle.js.map +1 -0
- package/lib/commonjs/core/retryPolicy.js +120 -0
- package/lib/commonjs/core/retryPolicy.js.map +1 -0
- package/lib/commonjs/core/rewardedSession.js +65 -0
- package/lib/commonjs/core/rewardedSession.js.map +1 -0
- package/lib/commonjs/core/timers.js +162 -0
- package/lib/commonjs/core/timers.js.map +1 -0
- package/lib/commonjs/diagnostics/correlation.js +33 -0
- package/lib/commonjs/diagnostics/correlation.js.map +1 -0
- package/lib/commonjs/diagnostics/errors.js +152 -0
- package/lib/commonjs/diagnostics/errors.js.map +1 -0
- package/lib/commonjs/diagnostics/events.js +132 -0
- package/lib/commonjs/diagnostics/events.js.map +1 -0
- package/lib/commonjs/engine/admobBanner.js +58 -0
- package/lib/commonjs/engine/admobBanner.js.map +1 -0
- package/lib/commonjs/engine/admobEngine.js +343 -0
- package/lib/commonjs/engine/admobEngine.js.map +1 -0
- package/lib/commonjs/engine/admobErrors.js +45 -0
- package/lib/commonjs/engine/admobErrors.js.map +1 -0
- package/lib/commonjs/engine/port.js +6 -0
- package/lib/commonjs/engine/port.js.map +1 -0
- package/lib/commonjs/engine/testAdUnits.js +61 -0
- package/lib/commonjs/engine/testAdUnits.js.map +1 -0
- package/lib/commonjs/index.js +218 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/react/AdsBanner.js +317 -0
- package/lib/commonjs/react/AdsBanner.js.map +1 -0
- package/lib/commonjs/react/AdsProvider.js +54 -0
- package/lib/commonjs/react/AdsProvider.js.map +1 -0
- package/lib/commonjs/react/hooks.js +63 -0
- package/lib/commonjs/react/hooks.js.map +1 -0
- package/lib/commonjs/testing/clock.js +76 -0
- package/lib/commonjs/testing/clock.js.map +1 -0
- package/lib/commonjs/testing/conformance.js +99 -0
- package/lib/commonjs/testing/conformance.js.map +1 -0
- package/lib/commonjs/testing/index.js +389 -0
- package/lib/commonjs/testing/index.js.map +1 -0
- package/lib/module/config/resolveAdUnit.js +75 -0
- package/lib/module/config/resolveAdUnit.js.map +1 -0
- package/lib/module/config/types.js +134 -0
- package/lib/module/config/types.js.map +1 -0
- package/lib/module/config/validate.js +146 -0
- package/lib/module/config/validate.js.map +1 -0
- package/lib/module/core/adOriginatedReturn.js +115 -0
- package/lib/module/core/adOriginatedReturn.js.map +1 -0
- package/lib/module/core/adsInstance.js +121 -0
- package/lib/module/core/adsInstance.js.map +1 -0
- package/lib/module/core/consentFlow.js +143 -0
- package/lib/module/core/consentFlow.js.map +1 -0
- package/lib/module/core/enablement.js +58 -0
- package/lib/module/core/enablement.js.map +1 -0
- package/lib/module/core/fullScreenLock.js +44 -0
- package/lib/module/core/fullScreenLock.js.map +1 -0
- package/lib/module/core/gates.js +91 -0
- package/lib/module/core/gates.js.map +1 -0
- package/lib/module/core/initMachine.js +152 -0
- package/lib/module/core/initMachine.js.map +1 -0
- package/lib/module/core/instance/consent.js +151 -0
- package/lib/module/core/instance/consent.js.map +1 -0
- package/lib/module/core/instance/context.js +212 -0
- package/lib/module/core/instance/context.js.map +1 -0
- package/lib/module/core/instance/gate.js +183 -0
- package/lib/module/core/instance/gate.js.map +1 -0
- package/lib/module/core/instance/init.js +321 -0
- package/lib/module/core/instance/init.js.map +1 -0
- package/lib/module/core/instance/internals.js +109 -0
- package/lib/module/core/instance/internals.js.map +1 -0
- package/lib/module/core/instance/load.js +452 -0
- package/lib/module/core/instance/load.js.map +1 -0
- package/lib/module/core/instance/show.js +238 -0
- package/lib/module/core/instance/show.js.map +1 -0
- package/lib/module/core/instance/types.js +4 -0
- package/lib/module/core/instance/types.js.map +1 -0
- package/lib/module/core/placementMachine.js +109 -0
- package/lib/module/core/placementMachine.js.map +1 -0
- package/lib/module/core/privacyGate.js +86 -0
- package/lib/module/core/privacyGate.js.map +1 -0
- package/lib/module/core/requestThrottle.js +146 -0
- package/lib/module/core/requestThrottle.js.map +1 -0
- package/lib/module/core/retryPolicy.js +113 -0
- package/lib/module/core/retryPolicy.js.map +1 -0
- package/lib/module/core/rewardedSession.js +61 -0
- package/lib/module/core/rewardedSession.js.map +1 -0
- package/lib/module/core/timers.js +150 -0
- package/lib/module/core/timers.js.map +1 -0
- package/lib/module/diagnostics/correlation.js +29 -0
- package/lib/module/diagnostics/correlation.js.map +1 -0
- package/lib/module/diagnostics/errors.js +143 -0
- package/lib/module/diagnostics/errors.js.map +1 -0
- package/lib/module/diagnostics/events.js +128 -0
- package/lib/module/diagnostics/events.js.map +1 -0
- package/lib/module/engine/admobBanner.js +52 -0
- package/lib/module/engine/admobBanner.js.map +1 -0
- package/lib/module/engine/admobEngine.js +334 -0
- package/lib/module/engine/admobEngine.js.map +1 -0
- package/lib/module/engine/admobErrors.js +41 -0
- package/lib/module/engine/admobErrors.js.map +1 -0
- package/lib/module/engine/port.js +4 -0
- package/lib/module/engine/port.js.map +1 -0
- package/lib/module/engine/testAdUnits.js +56 -0
- package/lib/module/engine/testAdUnits.js.map +1 -0
- package/lib/module/index.js +59 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/react/AdsBanner.js +312 -0
- package/lib/module/react/AdsBanner.js.map +1 -0
- package/lib/module/react/AdsProvider.js +48 -0
- package/lib/module/react/AdsProvider.js.map +1 -0
- package/lib/module/react/hooks.js +58 -0
- package/lib/module/react/hooks.js.map +1 -0
- package/lib/module/testing/clock.js +72 -0
- package/lib/module/testing/clock.js.map +1 -0
- package/lib/module/testing/conformance.js +93 -0
- package/lib/module/testing/conformance.js.map +1 -0
- package/lib/module/testing/index.js +366 -0
- package/lib/module/testing/index.js.map +1 -0
- package/lib/typescript/commonjs/config/resolveAdUnit.d.ts +43 -0
- package/lib/typescript/commonjs/config/resolveAdUnit.d.ts.map +1 -0
- package/lib/typescript/commonjs/config/types.d.ts +280 -0
- package/lib/typescript/commonjs/config/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/config/validate.d.ts +49 -0
- package/lib/typescript/commonjs/config/validate.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/adOriginatedReturn.d.ts +61 -0
- package/lib/typescript/commonjs/core/adOriginatedReturn.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/adsInstance.d.ts +33 -0
- package/lib/typescript/commonjs/core/adsInstance.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/consentFlow.d.ts +84 -0
- package/lib/typescript/commonjs/core/consentFlow.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/enablement.d.ts +74 -0
- package/lib/typescript/commonjs/core/enablement.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/fullScreenLock.d.ts +25 -0
- package/lib/typescript/commonjs/core/fullScreenLock.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/gates.d.ts +76 -0
- package/lib/typescript/commonjs/core/gates.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/initMachine.d.ts +66 -0
- package/lib/typescript/commonjs/core/initMachine.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/instance/consent.d.ts +4 -0
- package/lib/typescript/commonjs/core/instance/consent.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/instance/context.d.ts +118 -0
- package/lib/typescript/commonjs/core/instance/context.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/instance/gate.d.ts +38 -0
- package/lib/typescript/commonjs/core/instance/gate.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/instance/init.d.ts +13 -0
- package/lib/typescript/commonjs/core/instance/init.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/instance/internals.d.ts +60 -0
- package/lib/typescript/commonjs/core/instance/internals.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/instance/load.d.ts +9 -0
- package/lib/typescript/commonjs/core/instance/load.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/instance/show.d.ts +7 -0
- package/lib/typescript/commonjs/core/instance/show.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/instance/types.d.ts +240 -0
- package/lib/typescript/commonjs/core/instance/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/placementMachine.d.ts +55 -0
- package/lib/typescript/commonjs/core/placementMachine.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/privacyGate.d.ts +40 -0
- package/lib/typescript/commonjs/core/privacyGate.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/requestThrottle.d.ts +99 -0
- package/lib/typescript/commonjs/core/requestThrottle.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/retryPolicy.d.ts +66 -0
- package/lib/typescript/commonjs/core/retryPolicy.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/rewardedSession.d.ts +37 -0
- package/lib/typescript/commonjs/core/rewardedSession.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/timers.d.ts +135 -0
- package/lib/typescript/commonjs/core/timers.d.ts.map +1 -0
- package/lib/typescript/commonjs/diagnostics/correlation.d.ts +18 -0
- package/lib/typescript/commonjs/diagnostics/correlation.d.ts.map +1 -0
- package/lib/typescript/commonjs/diagnostics/errors.d.ts +59 -0
- package/lib/typescript/commonjs/diagnostics/errors.d.ts.map +1 -0
- package/lib/typescript/commonjs/diagnostics/events.d.ts +126 -0
- package/lib/typescript/commonjs/diagnostics/events.d.ts.map +1 -0
- package/lib/typescript/commonjs/engine/admobBanner.d.ts +10 -0
- package/lib/typescript/commonjs/engine/admobBanner.d.ts.map +1 -0
- package/lib/typescript/commonjs/engine/admobEngine.d.ts +71 -0
- package/lib/typescript/commonjs/engine/admobEngine.d.ts.map +1 -0
- package/lib/typescript/commonjs/engine/admobErrors.d.ts +9 -0
- package/lib/typescript/commonjs/engine/admobErrors.d.ts.map +1 -0
- package/lib/typescript/commonjs/engine/port.d.ts +171 -0
- package/lib/typescript/commonjs/engine/port.d.ts.map +1 -0
- package/lib/typescript/commonjs/engine/testAdUnits.d.ts +33 -0
- package/lib/typescript/commonjs/engine/testAdUnits.d.ts.map +1 -0
- package/lib/typescript/commonjs/index.d.ts +42 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/package.json +1 -0
- package/lib/typescript/commonjs/react/AdsBanner.d.ts +29 -0
- package/lib/typescript/commonjs/react/AdsBanner.d.ts.map +1 -0
- package/lib/typescript/commonjs/react/AdsProvider.d.ts +36 -0
- package/lib/typescript/commonjs/react/AdsProvider.d.ts.map +1 -0
- package/lib/typescript/commonjs/react/hooks.d.ts +27 -0
- package/lib/typescript/commonjs/react/hooks.d.ts.map +1 -0
- package/lib/typescript/commonjs/testing/clock.d.ts +29 -0
- package/lib/typescript/commonjs/testing/clock.d.ts.map +1 -0
- package/lib/typescript/commonjs/testing/conformance.d.ts +26 -0
- package/lib/typescript/commonjs/testing/conformance.d.ts.map +1 -0
- package/lib/typescript/commonjs/testing/index.d.ts +133 -0
- package/lib/typescript/commonjs/testing/index.d.ts.map +1 -0
- package/lib/typescript/module/config/resolveAdUnit.d.ts +43 -0
- package/lib/typescript/module/config/resolveAdUnit.d.ts.map +1 -0
- package/lib/typescript/module/config/types.d.ts +280 -0
- package/lib/typescript/module/config/types.d.ts.map +1 -0
- package/lib/typescript/module/config/validate.d.ts +49 -0
- package/lib/typescript/module/config/validate.d.ts.map +1 -0
- package/lib/typescript/module/core/adOriginatedReturn.d.ts +61 -0
- package/lib/typescript/module/core/adOriginatedReturn.d.ts.map +1 -0
- package/lib/typescript/module/core/adsInstance.d.ts +33 -0
- package/lib/typescript/module/core/adsInstance.d.ts.map +1 -0
- package/lib/typescript/module/core/consentFlow.d.ts +84 -0
- package/lib/typescript/module/core/consentFlow.d.ts.map +1 -0
- package/lib/typescript/module/core/enablement.d.ts +74 -0
- package/lib/typescript/module/core/enablement.d.ts.map +1 -0
- package/lib/typescript/module/core/fullScreenLock.d.ts +25 -0
- package/lib/typescript/module/core/fullScreenLock.d.ts.map +1 -0
- package/lib/typescript/module/core/gates.d.ts +76 -0
- package/lib/typescript/module/core/gates.d.ts.map +1 -0
- package/lib/typescript/module/core/initMachine.d.ts +66 -0
- package/lib/typescript/module/core/initMachine.d.ts.map +1 -0
- package/lib/typescript/module/core/instance/consent.d.ts +4 -0
- package/lib/typescript/module/core/instance/consent.d.ts.map +1 -0
- package/lib/typescript/module/core/instance/context.d.ts +118 -0
- package/lib/typescript/module/core/instance/context.d.ts.map +1 -0
- package/lib/typescript/module/core/instance/gate.d.ts +38 -0
- package/lib/typescript/module/core/instance/gate.d.ts.map +1 -0
- package/lib/typescript/module/core/instance/init.d.ts +13 -0
- package/lib/typescript/module/core/instance/init.d.ts.map +1 -0
- package/lib/typescript/module/core/instance/internals.d.ts +60 -0
- package/lib/typescript/module/core/instance/internals.d.ts.map +1 -0
- package/lib/typescript/module/core/instance/load.d.ts +9 -0
- package/lib/typescript/module/core/instance/load.d.ts.map +1 -0
- package/lib/typescript/module/core/instance/show.d.ts +7 -0
- package/lib/typescript/module/core/instance/show.d.ts.map +1 -0
- package/lib/typescript/module/core/instance/types.d.ts +240 -0
- package/lib/typescript/module/core/instance/types.d.ts.map +1 -0
- package/lib/typescript/module/core/placementMachine.d.ts +55 -0
- package/lib/typescript/module/core/placementMachine.d.ts.map +1 -0
- package/lib/typescript/module/core/privacyGate.d.ts +40 -0
- package/lib/typescript/module/core/privacyGate.d.ts.map +1 -0
- package/lib/typescript/module/core/requestThrottle.d.ts +99 -0
- package/lib/typescript/module/core/requestThrottle.d.ts.map +1 -0
- package/lib/typescript/module/core/retryPolicy.d.ts +66 -0
- package/lib/typescript/module/core/retryPolicy.d.ts.map +1 -0
- package/lib/typescript/module/core/rewardedSession.d.ts +37 -0
- package/lib/typescript/module/core/rewardedSession.d.ts.map +1 -0
- package/lib/typescript/module/core/timers.d.ts +135 -0
- package/lib/typescript/module/core/timers.d.ts.map +1 -0
- package/lib/typescript/module/diagnostics/correlation.d.ts +18 -0
- package/lib/typescript/module/diagnostics/correlation.d.ts.map +1 -0
- package/lib/typescript/module/diagnostics/errors.d.ts +59 -0
- package/lib/typescript/module/diagnostics/errors.d.ts.map +1 -0
- package/lib/typescript/module/diagnostics/events.d.ts +126 -0
- package/lib/typescript/module/diagnostics/events.d.ts.map +1 -0
- package/lib/typescript/module/engine/admobBanner.d.ts +10 -0
- package/lib/typescript/module/engine/admobBanner.d.ts.map +1 -0
- package/lib/typescript/module/engine/admobEngine.d.ts +71 -0
- package/lib/typescript/module/engine/admobEngine.d.ts.map +1 -0
- package/lib/typescript/module/engine/admobErrors.d.ts +9 -0
- package/lib/typescript/module/engine/admobErrors.d.ts.map +1 -0
- package/lib/typescript/module/engine/port.d.ts +171 -0
- package/lib/typescript/module/engine/port.d.ts.map +1 -0
- package/lib/typescript/module/engine/testAdUnits.d.ts +33 -0
- package/lib/typescript/module/engine/testAdUnits.d.ts.map +1 -0
- package/lib/typescript/module/index.d.ts +42 -0
- package/lib/typescript/module/index.d.ts.map +1 -0
- package/lib/typescript/module/package.json +1 -0
- package/lib/typescript/module/react/AdsBanner.d.ts +29 -0
- package/lib/typescript/module/react/AdsBanner.d.ts.map +1 -0
- package/lib/typescript/module/react/AdsProvider.d.ts +36 -0
- package/lib/typescript/module/react/AdsProvider.d.ts.map +1 -0
- package/lib/typescript/module/react/hooks.d.ts +27 -0
- package/lib/typescript/module/react/hooks.d.ts.map +1 -0
- package/lib/typescript/module/testing/clock.d.ts +29 -0
- package/lib/typescript/module/testing/clock.d.ts.map +1 -0
- package/lib/typescript/module/testing/conformance.d.ts +26 -0
- package/lib/typescript/module/testing/conformance.d.ts.map +1 -0
- package/lib/typescript/module/testing/index.d.ts +133 -0
- package/lib/typescript/module/testing/index.d.ts.map +1 -0
- package/package.json +48 -12
- package/skills/sdk-rn-ads/SKILL.md +2 -1
- package/src/core/adsInstance.ts +72 -1829
- package/src/core/consentFlow.ts +1 -20
- package/src/core/instance/consent.ts +162 -0
- package/src/core/instance/context.ts +328 -0
- package/src/core/instance/gate.ts +147 -0
- package/src/core/instance/init.ts +262 -0
- package/src/core/instance/internals.ts +138 -0
- package/src/core/instance/load.ts +426 -0
- package/src/core/instance/show.ts +202 -0
- package/src/core/instance/types.ts +198 -0
- package/src/core/requestThrottle.ts +11 -17
- package/src/diagnostics/errors.ts +24 -5
- package/src/engine/admobBanner.tsx +55 -0
- package/src/engine/admobEngine.ts +21 -12
- package/src/engine/admobErrors.ts +44 -0
- package/src/engine/port.ts +46 -1
- package/src/engine/testAdUnits.ts +0 -5
- package/src/react/AdsBanner.tsx +81 -80
- package/src/testing/clock.ts +75 -0
- package/src/testing/index.ts +43 -68
package/src/core/consentFlow.ts
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
import type { ConsentSnapshot, ConsentState, ConsentTestingOptions } from '../config/types';
|
|
29
29
|
import { AdsError } from '../diagnostics/errors';
|
|
30
|
-
import type {
|
|
30
|
+
import type { EngineConsentInfo, EngineConsentOptions } from '../engine/port';
|
|
31
31
|
import type { ConsentGateInput } from './gates';
|
|
32
32
|
|
|
33
33
|
export const INITIAL_CONSENT: ConsentSnapshot = {
|
|
@@ -161,22 +161,3 @@ export function needsForm(snapshot: ConsentSnapshot): boolean {
|
|
|
161
161
|
snapshot.isFormAvailable && (snapshot.state === 'required' || snapshot.state === 'undetermined')
|
|
162
162
|
);
|
|
163
163
|
}
|
|
164
|
-
|
|
165
|
-
export function createConsentPortGuard(port: ConsentPort | undefined): ConsentPort {
|
|
166
|
-
if (port) {
|
|
167
|
-
return port;
|
|
168
|
-
}
|
|
169
|
-
// An engine without a consent port cannot serve the capability. Reporting it
|
|
170
|
-
// as a normalized failure keeps FR-108 true — the app does not crash, it
|
|
171
|
-
// learns the flow is unavailable and can fall back to its own gate.
|
|
172
|
-
const unavailable = async (): Promise<EngineConsentInfo> => {
|
|
173
|
-
throw new Error('provider_missing');
|
|
174
|
-
};
|
|
175
|
-
return {
|
|
176
|
-
requestInfoUpdate: unavailable,
|
|
177
|
-
showFormIfRequired: unavailable,
|
|
178
|
-
showPrivacyOptionsForm: unavailable,
|
|
179
|
-
getInfo: unavailable,
|
|
180
|
-
reset: () => undefined,
|
|
181
|
-
};
|
|
182
|
-
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The optional consent capability (FR-101 – FR-113, FR-150 – FR-157).
|
|
3
|
+
*
|
|
4
|
+
* Every outcome feeds the gate through `reevaluate`, so the app never wires
|
|
5
|
+
* consent to requests by hand (FR-105), and a failure is never a grant
|
|
6
|
+
* (FR-106).
|
|
7
|
+
*/
|
|
8
|
+
import type { ConsentSnapshot } from '../../config/types';
|
|
9
|
+
import type { AdsError } from '../../diagnostics/errors';
|
|
10
|
+
import {
|
|
11
|
+
buildConsentOptions,
|
|
12
|
+
INITIAL_CONSENT,
|
|
13
|
+
needsForm,
|
|
14
|
+
normalizeConsent,
|
|
15
|
+
runConsentStep,
|
|
16
|
+
toConsentError,
|
|
17
|
+
} from '../consentFlow';
|
|
18
|
+
import type { InstanceContext } from './context';
|
|
19
|
+
import type { ConsentApi } from './types';
|
|
20
|
+
|
|
21
|
+
export function createConsentApi(
|
|
22
|
+
context: InstanceContext,
|
|
23
|
+
reevaluate: () => Promise<void>,
|
|
24
|
+
): ConsentApi {
|
|
25
|
+
const { state, engine, resolved } = context;
|
|
26
|
+
|
|
27
|
+
/** Applies an outcome unless the instance went away while it was pending. */
|
|
28
|
+
async function settle(next: ConsentSnapshot): Promise<ConsentSnapshot> {
|
|
29
|
+
if (state.disposed) {
|
|
30
|
+
return state.consent;
|
|
31
|
+
}
|
|
32
|
+
context.setConsent(next);
|
|
33
|
+
await reevaluate();
|
|
34
|
+
return next;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
async requestInfoUpdate() {
|
|
39
|
+
if (state.disposed) {
|
|
40
|
+
return state.consent;
|
|
41
|
+
}
|
|
42
|
+
// Marks the flow as in use. Until this is called the gate sees no consent
|
|
43
|
+
// input at all, which is how an app that supplies its own gate keeps
|
|
44
|
+
// 0.1.0 behavior without opting out of anything (FR-102).
|
|
45
|
+
state.consentStarted = true;
|
|
46
|
+
if (resolved.consentTesting?.resetOnStart) {
|
|
47
|
+
try {
|
|
48
|
+
engine.consent.reset();
|
|
49
|
+
} catch {
|
|
50
|
+
// A failed reset must not abort the update; the flow continues
|
|
51
|
+
// against whatever state is stored.
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const next = await runConsentStep(
|
|
55
|
+
() =>
|
|
56
|
+
engine.consent.requestInfoUpdate(
|
|
57
|
+
buildConsentOptions(
|
|
58
|
+
resolved.consentTesting,
|
|
59
|
+
state.currentPrivacy?.underAgeOfConsent,
|
|
60
|
+
resolved.testDeviceIds,
|
|
61
|
+
),
|
|
62
|
+
),
|
|
63
|
+
'consent information update failed',
|
|
64
|
+
);
|
|
65
|
+
return settle(next);
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
async showFormIfRequired() {
|
|
69
|
+
if (state.disposed) {
|
|
70
|
+
return state.consent;
|
|
71
|
+
}
|
|
72
|
+
state.consentStarted = true;
|
|
73
|
+
// Nothing to present is not an error: a region that does not require
|
|
74
|
+
// consent is the normal case (FR-107). Nor is a privacy options form
|
|
75
|
+
// already on screen — one provider form at a time.
|
|
76
|
+
if (!needsForm(state.consent) || state.privacyOptionsFormOpen) {
|
|
77
|
+
return state.consent;
|
|
78
|
+
}
|
|
79
|
+
state.consentFormsOpen += 1;
|
|
80
|
+
let next: ConsentSnapshot;
|
|
81
|
+
try {
|
|
82
|
+
next = await runConsentStep(
|
|
83
|
+
() => engine.consent.showFormIfRequired(),
|
|
84
|
+
'consent form failed',
|
|
85
|
+
);
|
|
86
|
+
} finally {
|
|
87
|
+
state.consentFormsOpen -= 1;
|
|
88
|
+
}
|
|
89
|
+
return settle(next);
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
async showPrivacyOptionsForm() {
|
|
93
|
+
// The refusals, most immovable first — the same reading order as the
|
|
94
|
+
// request gates, so the answer is the most useful one.
|
|
95
|
+
if (state.disposed) {
|
|
96
|
+
return { status: 'unavailable', reason: 'provider_missing' };
|
|
97
|
+
}
|
|
98
|
+
if (!engine.isAvailable()) {
|
|
99
|
+
return { status: 'unavailable', reason: 'unsupported_platform' };
|
|
100
|
+
}
|
|
101
|
+
if (state.privacyOptionsFormOpen || state.consentFormsOpen > 0) {
|
|
102
|
+
return { status: 'busy' };
|
|
103
|
+
}
|
|
104
|
+
if (state.consent.privacyOptionsRequirement === 'unknown') {
|
|
105
|
+
return { status: 'info_update_required' };
|
|
106
|
+
}
|
|
107
|
+
if (state.consent.privacyOptionsRequirement !== 'required') {
|
|
108
|
+
// The provider's verdict, not a region guess of ours (FR-150).
|
|
109
|
+
return { status: 'not_required' };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
state.privacyOptionsFormOpen = true;
|
|
113
|
+
let next: ConsentSnapshot;
|
|
114
|
+
let failure: AdsError | null = null;
|
|
115
|
+
try {
|
|
116
|
+
next = normalizeConsent(await engine.consent.showPrivacyOptionsForm());
|
|
117
|
+
} catch (cause) {
|
|
118
|
+
failure = toConsentError(cause, 'privacy options form failed');
|
|
119
|
+
// The form did not complete, so the stored decision is the provider's
|
|
120
|
+
// to report — ask rather than assume. Collapsing to `error` the way a
|
|
121
|
+
// failed first-time form does would revoke a consent the user never
|
|
122
|
+
// changed. If the provider cannot answer either, the gate closes: a
|
|
123
|
+
// failure is never consent (FR-106).
|
|
124
|
+
next = state.disposed
|
|
125
|
+
? state.consent
|
|
126
|
+
: await runConsentStep(
|
|
127
|
+
() => engine.consent.getInfo(),
|
|
128
|
+
'consent information could not be read after the privacy options form failed',
|
|
129
|
+
);
|
|
130
|
+
} finally {
|
|
131
|
+
state.privacyOptionsFormOpen = false;
|
|
132
|
+
}
|
|
133
|
+
// dispose() during the form: the form stays until the user closes it, but
|
|
134
|
+
// its outcome reaches nobody (FR-157).
|
|
135
|
+
if (state.disposed) {
|
|
136
|
+
return { status: 'unavailable', reason: 'provider_missing' };
|
|
137
|
+
}
|
|
138
|
+
await settle(next);
|
|
139
|
+
return failure ? { status: 'failed', error: failure } : { status: 'shown', consent: next };
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
getState() {
|
|
143
|
+
return state.consent;
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
subscribe: listener => context.consentListeners.add(listener),
|
|
147
|
+
|
|
148
|
+
async reset() {
|
|
149
|
+
// Config validation strips the testing tools in production, so this is
|
|
150
|
+
// inert there without a check here (FR-113).
|
|
151
|
+
if (state.disposed || !resolved.consentTesting) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
engine.consent.reset();
|
|
156
|
+
} catch {
|
|
157
|
+
// The stored state is the provider's; a failed reset leaves it as it was.
|
|
158
|
+
}
|
|
159
|
+
context.setConsent(INITIAL_CONSENT);
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
}
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What every part of an ads instance shares (ADS-10).
|
|
3
|
+
*
|
|
4
|
+
* 0.5.0 kept all of this as `let` bindings inside one 1,500-line closure, and
|
|
5
|
+
* each path that closed a gate re-implemented its own teardown — which is how
|
|
6
|
+
* ADS-01, ADS-02 and ADS-04 happened. Here the state is one object, the
|
|
7
|
+
* placement runtimes are one map, and the helpers that change them are defined
|
|
8
|
+
* once. The controllers (gate, init, load, show, consent, banner) each take this
|
|
9
|
+
* context and nothing else.
|
|
10
|
+
*/
|
|
11
|
+
import {
|
|
12
|
+
createListenerSet,
|
|
13
|
+
isDevBuild as detectDevBuild,
|
|
14
|
+
type ListenerSet,
|
|
15
|
+
} from '@chipmobilesdk/rn-core';
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
type AdFormat,
|
|
19
|
+
type AdsConfig,
|
|
20
|
+
type ConsentSnapshot,
|
|
21
|
+
type Enablement,
|
|
22
|
+
type InitStatus,
|
|
23
|
+
type PlacementDefinition,
|
|
24
|
+
type PlacementState,
|
|
25
|
+
type PrivacyOptions,
|
|
26
|
+
type Unsubscribe,
|
|
27
|
+
} from '../../config/types';
|
|
28
|
+
import { validateConfig, type ResolvedConfig } from '../../config/validate';
|
|
29
|
+
import { createCorrelationIssuer, type CorrelationIssuer } from '../../diagnostics/correlation';
|
|
30
|
+
import { AdsError } from '../../diagnostics/errors';
|
|
31
|
+
import { createEventEmitter, type AdEventEmitter } from '../../diagnostics/events';
|
|
32
|
+
import type { AdsEngine, EngineAdHandle } from '../../engine/port';
|
|
33
|
+
import { createAdOriginatedTracker, type AdOriginatedTracker } from '../adOriginatedReturn';
|
|
34
|
+
import { INITIAL_CONSENT } from '../consentFlow';
|
|
35
|
+
import { createFullScreenLock, type FullScreenLock } from '../fullScreenLock';
|
|
36
|
+
import { initialInit, initReducer, type InitSnapshot } from '../initMachine';
|
|
37
|
+
import { initialPlacement, placementReducer, type PlacementSnapshot } from '../placementMachine';
|
|
38
|
+
import { createRequestThrottle, type RequestThrottle } from '../requestThrottle';
|
|
39
|
+
import type { RewardedSession } from '../rewardedSession';
|
|
40
|
+
import { createTimerSet, type TimerSet } from '../timers';
|
|
41
|
+
import type { CreateAdsOptions, InitResult, LoadRefusal, LoadResult } from './types';
|
|
42
|
+
|
|
43
|
+
export interface PlacementRuntime {
|
|
44
|
+
snapshot: PlacementSnapshot;
|
|
45
|
+
/**
|
|
46
|
+
* The cycle's correlation id, held here rather than read from the snapshot:
|
|
47
|
+
* the reducer resets the snapshot on dismissal, and events emitted around
|
|
48
|
+
* that transition must still carry the id of the cycle they belong to.
|
|
49
|
+
*/
|
|
50
|
+
correlationId: string | null;
|
|
51
|
+
/**
|
|
52
|
+
* Whether this cycle already reported a dismissal. Format-independent — the
|
|
53
|
+
* rewarded session guards its own, but interstitials need the same guard and
|
|
54
|
+
* would otherwise report a repeated provider callback twice.
|
|
55
|
+
*/
|
|
56
|
+
dismissed: boolean;
|
|
57
|
+
handle: EngineAdHandle | null;
|
|
58
|
+
unsubscribeEngine: Unsubscribe | null;
|
|
59
|
+
inFlightLoad: Promise<LoadResult> | null;
|
|
60
|
+
/**
|
|
61
|
+
* Settles the in-flight load with a given result. Only a gate closing uses it
|
|
62
|
+
* (FR-153): the caller awaiting `load()` is told the refusal instead of
|
|
63
|
+
* waiting on a request that has been abandoned.
|
|
64
|
+
*/
|
|
65
|
+
abortLoad: ((result: LoadResult) => void) | null;
|
|
66
|
+
cancelLoadTimer: Unsubscribe | null;
|
|
67
|
+
cancelShowTimer: Unsubscribe | null;
|
|
68
|
+
rewarded: RewardedSession | null;
|
|
69
|
+
listeners: ListenerSet<[PlacementState]>;
|
|
70
|
+
/** Retries already spent in this load cycle. Reset when a new cycle starts. */
|
|
71
|
+
retryAttempts: number;
|
|
72
|
+
/** Cancels a scheduled retry. Called by disable, undecided, and dispose. */
|
|
73
|
+
cancelRetry: Unsubscribe | null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Everything about the instance that changes after creation. */
|
|
77
|
+
export interface InstanceState {
|
|
78
|
+
init: InitSnapshot;
|
|
79
|
+
enablement: Enablement;
|
|
80
|
+
appActive: boolean;
|
|
81
|
+
consent: ConsentSnapshot;
|
|
82
|
+
/** Whether the app ever used the consent flow; until then the gate ignores it (FR-102). */
|
|
83
|
+
consentStarted: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Consent forms currently on screen. A count rather than a flag because
|
|
86
|
+
* `showFormIfRequired` has never refused a concurrent call; only the privacy
|
|
87
|
+
* options form reads it, to report `busy`.
|
|
88
|
+
*/
|
|
89
|
+
consentFormsOpen: number;
|
|
90
|
+
privacyOptionsFormOpen: boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Whether the provider has been natively initialized, ever, by this instance.
|
|
93
|
+
*
|
|
94
|
+
* Tracked apart from the init status because the status can leave `ready` —
|
|
95
|
+
* a withdrawn consent moves it to `blocked` — while the native SDK stays
|
|
96
|
+
* initialized. Re-granting must resume, not initialize again (FR-022, FR-154).
|
|
97
|
+
*/
|
|
98
|
+
providerInitialized: boolean;
|
|
99
|
+
undecidedWarned: boolean;
|
|
100
|
+
cancelUndecidedTimer: Unsubscribe | null;
|
|
101
|
+
appliedPrivacy: PrivacyOptions | null;
|
|
102
|
+
currentPrivacy: PrivacyOptions | null;
|
|
103
|
+
inFlightInit: Promise<InitResult> | null;
|
|
104
|
+
disposed: boolean;
|
|
105
|
+
warnedSubstitution: boolean;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface InstanceContext {
|
|
109
|
+
readonly resolved: ResolvedConfig;
|
|
110
|
+
readonly engine: AdsEngine;
|
|
111
|
+
readonly now: () => number;
|
|
112
|
+
readonly events: AdEventEmitter;
|
|
113
|
+
readonly correlation: CorrelationIssuer;
|
|
114
|
+
/** FR-140. Account safety, not product policy — see core/requestThrottle.ts. */
|
|
115
|
+
readonly requestThrottle: RequestThrottle;
|
|
116
|
+
readonly timers: TimerSet;
|
|
117
|
+
readonly lock: FullScreenLock;
|
|
118
|
+
readonly adReturn: AdOriginatedTracker;
|
|
119
|
+
readonly state: InstanceState;
|
|
120
|
+
readonly placements: ReadonlyMap<string, PlacementRuntime>;
|
|
121
|
+
readonly statusListeners: ListenerSet<[InitStatus]>;
|
|
122
|
+
readonly consentListeners: ListenerSet<[ConsentSnapshot]>;
|
|
123
|
+
/** A placement's runtime and definition together, or null for an unknown key (ADS-18). */
|
|
124
|
+
placement(key: string): { runtime: PlacementRuntime; definition: PlacementDefinition } | null;
|
|
125
|
+
setInit(next: InitSnapshot): void;
|
|
126
|
+
setPlacement(key: string, next: PlacementSnapshot): void;
|
|
127
|
+
dispatchPlacement(key: string, event: Parameters<typeof placementReducer>[1]): void;
|
|
128
|
+
setConsent(next: ConsentSnapshot): void;
|
|
129
|
+
releasePlacementResources(key: string): void;
|
|
130
|
+
emitRefusal(
|
|
131
|
+
operation: 'initialize' | 'load' | 'show',
|
|
132
|
+
result: LoadRefusal,
|
|
133
|
+
placementKey?: string,
|
|
134
|
+
format?: AdFormat,
|
|
135
|
+
): void;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function createInstanceContext(
|
|
139
|
+
config: AdsConfig,
|
|
140
|
+
options: CreateAdsOptions,
|
|
141
|
+
defaultEngine: () => AdsEngine,
|
|
142
|
+
): InstanceContext {
|
|
143
|
+
const isDevBuild = options.isDevBuild ?? detectDevBuild();
|
|
144
|
+
const now = options.now ?? (() => Date.now());
|
|
145
|
+
|
|
146
|
+
// Throws on a structural fault or the FR-012 fail-closed case. Deliberately
|
|
147
|
+
// before anything else: a misconfigured instance should not exist at all.
|
|
148
|
+
const resolved = validateConfig(config, { isDevBuild });
|
|
149
|
+
const engine = options.engine ?? defaultEngine();
|
|
150
|
+
const events = createEventEmitter();
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Status, consent and placement listeners are consumer code. A failure is
|
|
154
|
+
* isolated from the rest and reported the way an event listener's is: as an
|
|
155
|
+
* `internal_error` event (FR-056).
|
|
156
|
+
*/
|
|
157
|
+
const listenerOptions = {
|
|
158
|
+
onListenerError(error: unknown) {
|
|
159
|
+
events.emit({
|
|
160
|
+
type: 'internal_error',
|
|
161
|
+
timestamp: now(),
|
|
162
|
+
error: new AdsError('internal_error', { message: 'consumer listener threw', cause: error }),
|
|
163
|
+
});
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const placements = new Map<string, PlacementRuntime>();
|
|
168
|
+
for (const key of resolved.placements.keys()) {
|
|
169
|
+
placements.set(key, {
|
|
170
|
+
snapshot: initialPlacement,
|
|
171
|
+
correlationId: null,
|
|
172
|
+
dismissed: false,
|
|
173
|
+
handle: null,
|
|
174
|
+
unsubscribeEngine: null,
|
|
175
|
+
inFlightLoad: null,
|
|
176
|
+
abortLoad: null,
|
|
177
|
+
cancelLoadTimer: null,
|
|
178
|
+
cancelShowTimer: null,
|
|
179
|
+
rewarded: null,
|
|
180
|
+
listeners: createListenerSet(listenerOptions),
|
|
181
|
+
retryAttempts: 0,
|
|
182
|
+
cancelRetry: null,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const state: InstanceState = {
|
|
187
|
+
/**
|
|
188
|
+
* An instance told it is `undecided` says so from the moment it exists, or
|
|
189
|
+
* it would report `not_started` until `initialize()` runs — which looks like
|
|
190
|
+
* a disabled instance at exactly the point an app decides whether to reserve
|
|
191
|
+
* the banner's space (FR-116). `disabled` keeps 0.1.0's `not_started`
|
|
192
|
+
* (BC-004).
|
|
193
|
+
*/
|
|
194
|
+
init:
|
|
195
|
+
resolved.enablement === 'undecided'
|
|
196
|
+
? initReducer(initialInit, { type: 'UNDECIDE' })
|
|
197
|
+
: initialInit,
|
|
198
|
+
enablement: resolved.enablement,
|
|
199
|
+
appActive: true,
|
|
200
|
+
consent: INITIAL_CONSENT,
|
|
201
|
+
consentStarted: false,
|
|
202
|
+
consentFormsOpen: 0,
|
|
203
|
+
privacyOptionsFormOpen: false,
|
|
204
|
+
providerInitialized: false,
|
|
205
|
+
undecidedWarned: false,
|
|
206
|
+
cancelUndecidedTimer: null,
|
|
207
|
+
appliedPrivacy: null,
|
|
208
|
+
currentPrivacy: null,
|
|
209
|
+
inFlightInit: null,
|
|
210
|
+
disposed: false,
|
|
211
|
+
warnedSubstitution: false,
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const statusListeners = createListenerSet<[InitStatus]>(listenerOptions);
|
|
215
|
+
const consentListeners = createListenerSet<[ConsentSnapshot]>(listenerOptions);
|
|
216
|
+
|
|
217
|
+
const context: InstanceContext = {
|
|
218
|
+
resolved,
|
|
219
|
+
engine,
|
|
220
|
+
now,
|
|
221
|
+
events,
|
|
222
|
+
correlation: createCorrelationIssuer(),
|
|
223
|
+
requestThrottle: createRequestThrottle(now),
|
|
224
|
+
timers: createTimerSet(options.scheduler),
|
|
225
|
+
lock: createFullScreenLock(),
|
|
226
|
+
adReturn: createAdOriginatedTracker(),
|
|
227
|
+
state,
|
|
228
|
+
placements,
|
|
229
|
+
statusListeners,
|
|
230
|
+
consentListeners,
|
|
231
|
+
|
|
232
|
+
placement(key) {
|
|
233
|
+
const runtime = placements.get(key);
|
|
234
|
+
const definition = resolved.placements.get(key);
|
|
235
|
+
return runtime && definition ? { runtime, definition } : null;
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
setInit(next) {
|
|
239
|
+
const previous = state.init;
|
|
240
|
+
state.init = next;
|
|
241
|
+
if (next === previous || next.status === previous.status) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
events.emit({
|
|
245
|
+
type: 'initialization',
|
|
246
|
+
timestamp: now(),
|
|
247
|
+
status: next.status,
|
|
248
|
+
retryable: next.error?.retryable,
|
|
249
|
+
});
|
|
250
|
+
statusListeners.notify(next.status);
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
setPlacement(key, next) {
|
|
254
|
+
const runtime = placements.get(key);
|
|
255
|
+
if (!runtime) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const changed = runtime.snapshot.state !== next.state;
|
|
259
|
+
runtime.snapshot = next;
|
|
260
|
+
if (changed) {
|
|
261
|
+
runtime.listeners.notify(next.state);
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
dispatchPlacement(key, event) {
|
|
266
|
+
const found = context.placement(key);
|
|
267
|
+
if (found) {
|
|
268
|
+
context.setPlacement(
|
|
269
|
+
key,
|
|
270
|
+
placementReducer(found.runtime.snapshot, event, found.definition.format),
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
setConsent(next) {
|
|
276
|
+
state.consent = next;
|
|
277
|
+
consentListeners.notify(next);
|
|
278
|
+
},
|
|
279
|
+
|
|
280
|
+
releasePlacementResources(key) {
|
|
281
|
+
const runtime = placements.get(key);
|
|
282
|
+
if (!runtime) {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
runtime.cancelLoadTimer?.();
|
|
286
|
+
runtime.cancelLoadTimer = null;
|
|
287
|
+
runtime.cancelShowTimer?.();
|
|
288
|
+
runtime.cancelShowTimer = null;
|
|
289
|
+
runtime.unsubscribeEngine?.();
|
|
290
|
+
runtime.unsubscribeEngine = null;
|
|
291
|
+
if (runtime.handle) {
|
|
292
|
+
engine.dispose(runtime.handle);
|
|
293
|
+
runtime.handle = null;
|
|
294
|
+
}
|
|
295
|
+
runtime.inFlightLoad = null;
|
|
296
|
+
runtime.abortLoad = null;
|
|
297
|
+
runtime.rewarded = null;
|
|
298
|
+
runtime.cancelRetry?.();
|
|
299
|
+
runtime.cancelRetry = null;
|
|
300
|
+
},
|
|
301
|
+
|
|
302
|
+
emitRefusal(operation, result, placementKey, format) {
|
|
303
|
+
if (result.status === 'disabled' || result.status === 'undecided') {
|
|
304
|
+
// `undecided` reuses the `disabled` event: for an observer of the
|
|
305
|
+
// stream the fact is the same — no request was made — and the
|
|
306
|
+
// distinction is carried by the typed result and the init status.
|
|
307
|
+
events.emit({ type: 'disabled', timestamp: now(), operation, placementKey, format });
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
events.emit({
|
|
311
|
+
type: 'unavailable',
|
|
312
|
+
timestamp: now(),
|
|
313
|
+
operation,
|
|
314
|
+
placementKey,
|
|
315
|
+
format,
|
|
316
|
+
reason: result.reason,
|
|
317
|
+
});
|
|
318
|
+
},
|
|
319
|
+
};
|
|
320
|
+
return context;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** App-open expires on its own, shorter window (FR-130). */
|
|
324
|
+
export function validityFor(context: InstanceContext, format: AdFormat): number {
|
|
325
|
+
return format === 'app_open'
|
|
326
|
+
? context.resolved.timeouts.appOpenValidity
|
|
327
|
+
: context.resolved.timeouts.adValidity;
|
|
328
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The request gate, evaluated in one place, and closed in one place (FR-140,
|
|
3
|
+
* ADS-10).
|
|
4
|
+
*
|
|
5
|
+
* Every path that closes the gate — ads turned off or undecided, the app's
|
|
6
|
+
* privacy input withdrawn, the consent flow withdrawn — goes through
|
|
7
|
+
* `closeGate`. In 0.5.0 each of those paths wrote its own teardown, and the one
|
|
8
|
+
* that forgot to cancel retries let a request reach the provider after consent
|
|
9
|
+
* was withdrawn (ADS-02).
|
|
10
|
+
*/
|
|
11
|
+
import { toGateInput } from '../consentFlow';
|
|
12
|
+
import { evaluateGates, type GateDecision } from '../gates';
|
|
13
|
+
import { initReducer } from '../initMachine';
|
|
14
|
+
import { evaluateGate } from '../privacyGate';
|
|
15
|
+
import type { InstanceContext } from './context';
|
|
16
|
+
import type { LoadRefusal } from './types';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Assembles the gate's inputs and defers the precedence entirely to
|
|
20
|
+
* `evaluateGates`: availability, then enablement, then the app's privacy input,
|
|
21
|
+
* then the consent flow. Nothing else compares gate state.
|
|
22
|
+
*
|
|
23
|
+
* The consent flow contributes `null` until the app opts into it, which is what
|
|
24
|
+
* makes FR-102 structural: with no flow started the gate sees only the app's
|
|
25
|
+
* own input and behaves exactly as 0.1.0 did.
|
|
26
|
+
*/
|
|
27
|
+
export function currentGate(context: InstanceContext): GateDecision {
|
|
28
|
+
const { engine, state } = context;
|
|
29
|
+
const appGate = evaluateGate(state.currentPrivacy);
|
|
30
|
+
return evaluateGates({
|
|
31
|
+
available: engine.isAvailable() ? { ok: true } : { ok: false, reason: 'unsupported_platform' },
|
|
32
|
+
enablement: state.enablement,
|
|
33
|
+
appGate: appGate.allowed ? { permitted: true } : { permitted: false, reason: appGate.reason },
|
|
34
|
+
consentGate: state.consentStarted ? toGateInput(state.consent) : null,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Maps a closed gate onto the refusal shape load, show, and the banner share. */
|
|
39
|
+
export function refusalFor(decision: Extract<GateDecision, { allowed: false }>): LoadRefusal {
|
|
40
|
+
switch (decision.kind) {
|
|
41
|
+
case 'unavailable':
|
|
42
|
+
return { status: 'unavailable', reason: decision.reason };
|
|
43
|
+
case 'disabled':
|
|
44
|
+
return { status: 'disabled' };
|
|
45
|
+
case 'undecided':
|
|
46
|
+
return { status: 'undecided' };
|
|
47
|
+
default:
|
|
48
|
+
return { status: 'blocked', reason: decision.reason };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** The refusal that applies now, or null when a request may go ahead. */
|
|
53
|
+
export function gateRefusal(context: InstanceContext): LoadRefusal | null {
|
|
54
|
+
const decision = currentGate(context);
|
|
55
|
+
return decision.allowed ? null : refusalFor(decision);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Cancels every pending retry, everywhere (FR-137). */
|
|
59
|
+
export function cancelAllRetries(context: InstanceContext): void {
|
|
60
|
+
for (const runtime of context.placements.values()) {
|
|
61
|
+
runtime.cancelRetry?.();
|
|
62
|
+
runtime.cancelRetry = null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Stops every request a gate that just closed forbids (FR-125, FR-153, FR-159).
|
|
68
|
+
*
|
|
69
|
+
* In-flight loads are abandoned and their resources released, a loaded ad that
|
|
70
|
+
* may no longer be shown is released with them, and an ad already on screen is
|
|
71
|
+
* left alone — the provider offers no safe way to close it.
|
|
72
|
+
*
|
|
73
|
+
* An abandoned load *settles*: its caller gets `refusal`, the same answer a load
|
|
74
|
+
* started now would get, and a handle the provider hands back afterwards is
|
|
75
|
+
* disposed rather than adopted.
|
|
76
|
+
*/
|
|
77
|
+
function haltPlacements(context: InstanceContext, refusal: LoadRefusal): void {
|
|
78
|
+
for (const [key, runtime] of context.placements) {
|
|
79
|
+
if (runtime.snapshot.state === 'showing') {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const abort = runtime.abortLoad;
|
|
83
|
+
context.releasePlacementResources(key);
|
|
84
|
+
context.dispatchPlacement(key, { type: 'RESET' });
|
|
85
|
+
if (abort) {
|
|
86
|
+
context.emitRefusal('load', refusal, key, context.resolved.placements.get(key)?.format);
|
|
87
|
+
abort(refusal);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The one teardown for a gate that closed to `refusal`: retries cancelled,
|
|
94
|
+
* placements halted, and the init status moved to match.
|
|
95
|
+
*/
|
|
96
|
+
export function closeGate(
|
|
97
|
+
context: InstanceContext,
|
|
98
|
+
refusal: Exclude<LoadRefusal, { status: 'unavailable' }>,
|
|
99
|
+
): void {
|
|
100
|
+
cancelAllRetries(context);
|
|
101
|
+
haltPlacements(context, refusal);
|
|
102
|
+
const { init } = context.state;
|
|
103
|
+
switch (refusal.status) {
|
|
104
|
+
case 'disabled':
|
|
105
|
+
context.setInit(initReducer(init, { type: 'DISABLE' }));
|
|
106
|
+
return;
|
|
107
|
+
case 'undecided':
|
|
108
|
+
context.setInit(initReducer(init, { type: 'UNDECIDE' }));
|
|
109
|
+
return;
|
|
110
|
+
default:
|
|
111
|
+
context.setInit(initReducer(init, { type: 'BLOCK', reason: refusal.reason }));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Warns once if the app leaves `undecided` unresolved (FR-119).
|
|
117
|
+
*
|
|
118
|
+
* Once, not repeatedly: a warning that fires every ten seconds becomes noise.
|
|
119
|
+
* And it only warns — resolving the state would mean guessing the business
|
|
120
|
+
* decision FR-120 forbids the package from knowing.
|
|
121
|
+
*/
|
|
122
|
+
export function armUndecidedWarning(context: InstanceContext): void {
|
|
123
|
+
const { state, timers, resolved, events, now } = context;
|
|
124
|
+
if (state.enablement !== 'undecided' || state.undecidedWarned || state.cancelUndecidedTimer) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const armedAt = now();
|
|
128
|
+
state.cancelUndecidedTimer = timers.after(resolved.timeouts.undecidedWarning, () => {
|
|
129
|
+
state.cancelUndecidedTimer = null;
|
|
130
|
+
if (state.disposed || state.enablement !== 'undecided' || state.undecidedWarned) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
state.undecidedWarned = true;
|
|
134
|
+
events.emit({
|
|
135
|
+
type: 'config_warning',
|
|
136
|
+
timestamp: now(),
|
|
137
|
+
warning: 'enablement_undecided_timeout',
|
|
138
|
+
placementKeys: [],
|
|
139
|
+
elapsedMs: now() - armedAt,
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function disarmUndecidedWarning(context: InstanceContext): void {
|
|
145
|
+
context.state.cancelUndecidedTimer?.();
|
|
146
|
+
context.state.cancelUndecidedTimer = null;
|
|
147
|
+
}
|