Skip to content
Back to skills

Admob Ads And Consent

ASecurity

Builds AdMob ads behind a port - banners, interstitials, rewarded, UMP consent then ATT, pacing, ADS_MODE test IDs. Use when touching ads, consent, ATT, ad IDs or rewarded perks. Not for purchases (in-app-purchases).

  • 3 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 18, 2026
developmenttypescriptrustgoshellreactnodetestingapi

Works with

  • cli
  • api

Security analysis

A92/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 19 files and shows the line behind each finding

Scanned October 18, 2026

npx -y skills add zakariaf/ReactNative-Skills --skill admob-ads-and-consent --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Admob Ads And Consent?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Admob Ads And Consent
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/zakariaf-admob-ads-and-consent/badge)](https://www.skillsdirectory.com/skills/zakariaf-admob-ads-and-consent)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: admob-ads-and-consent
description: Builds AdMob ads behind a port - banners, interstitials, rewarded, UMP consent then ATT, pacing, ADS_MODE test IDs. Use when touching ads, consent, ATT, ad IDs or rewarded perks. Not for purchases (in-app-purchases).
---

# AdMob ads and consent

Makes an Expo app show Google AdMob ads that never interrupt a focused task: Google's consent and
Apple's tracking prompt come first, every "may an ad appear now?" answer comes from pure, tested
functions, testing builds only ever see Google's test ads, iOS and Android each get their own IDs, and
two zero-dependency checkers prove it on the real code.

## Rules that must hold

1. **Ads never interrupt a focused task.** Banners only on the screens contracts/ads.json `bannerSlots`
   declares (browsing screens, never a form, editor, dialog, paywall or onboarding), and only while that
   screen is focused (`useBannerSlot(screen, facts, useIsFocused())`: stacks and tabs keep it mounted
   under pushed screens); interstitials only from the press handler that leaves a natural break the user
   just finished, never on mount, app start or during onboarding; rewarded ads only on the user's tap
   behind a clear "Watch an ad to ..." label; the AdsPort's show, initialize and preload calls only in
   `ad-moments.ts` and `ad-gate.ts`. *Why:* an interruption costs the user's trust, and Google's policies
   punish ads that surprise or invite accidental taps.
2. **Every ad decision comes from `ad-policy.ts` / `rewarded-offer.ts`** (pure; numbers from
   `src/config/ad-pacing.ts`, at or above contracts/ads.json `pacingFloors`, default 3 breaks before the
   first interstitial, then 3 minutes and 2 breaks between two), and every finished natural break is
   saved with `recordBreak` where it finishes. *Why:* only pure rules can be tested one by one, the
   numbers are configuration, and a history that never counts a break allows exactly one interstitial.
3. **Consent, then Apple's prompt, before any ad request, through the consent moment, and none at all
   when ads are off.** UMP refreshes at every launch; where Google's form is required, the app's optional
   primer and then the form; once `canRequestAds`, ATT through `ConsentPort.requestTracking()` (iOS only,
   while not-determined, the app active); then `setRequestConfiguration` + `initialize` + preload. Only
   after onboarding, online, not Premium, over a focused ad screen. `AdsProvider` (through
   `consent-moment-flow.ts`) is the only runner of `prepareAds`; banners are allowed only after the ATT
   answer and a successful `initialize`, and the adapter mounts its native banner only after it. The
   ConsentPort comes from `createConsentPort(adsMode, () => createAdmobConsentAdapter(...))`, so an
   `ADS_MODE=off` build never calls UMP or ATT. *Why:* Google requires consent (EEA, UK, Switzerland, US
   states), Apple's guideline 5.1.2(i) requires the ATT answer before IDFA use, the SDK may preload at
   initialisation, and a UMP update is a network request that would fail the network audit of off builds.
4. **Testing builds never see real ads; real IDs reach the runtime only in production + live builds.**
   `resolveAdsMode` decides: development and preview -> `test` (or `off`), production -> `live` (or
   `off`); the other pairs throw. IDs live only in `src/config/admob-ids.ts`, read only by
   `app.config.ts`; `admobPluginOptions` always gives both platforms an app ID (Google's sample unless
   live); `adsExtra` writes units only in live builds; the template's placeholder IDs are noted and
   never reach a store build. `expo.extra` is embedded at build time, or replaced by the running EAS
   Update's manifest, so `eas update` runs with the channel's environment and the app reads it through
   `readAdsExtraForBinary(Application.applicationId)`, which turns ads off for a manifest made for
   another variant's app id. *Why:* real ads in a testing build risk the AdMob account; a missing app ID
   stops the app at launch.
5. **`react-native-google-mobile-ads` is exactly 17.2.0 and is imported only by `admob-ads-adapter.ts`,
   `admob-consent-adapter.ts` and the test-only `admob-consent-debug-adapter.ts`**, with the classic
   create/load/show API (no v17 hooks or pools), and the adapters are created only inside
   `createAdsPort` / `createConsentPort` in the composition root. *Why:* an upgrade touches three files,
   pacing stays in pure code, and pools preload (on Android initialise) before consent.
6. **The reward is granted only on `EARNED_REWARD`, and the AdsPort reports a `RewardedStatus`
   (`'loading' | 'ready' | 'unavailable'`), never a loaded flag.** `rewardedOffer` gives a perk the user
   waits on `'loading'` and `'hidden'` only when no ad can come, so a flow waiting on it moves on. *Why:*
   with a flag, "not loaded" means both "still loading" and "cannot come", and a waiting user is stranded.
7. **Pause media around every fullscreen ad (`runFullscreenAd` with the one `MediaLifecycle`), resume in
   `finally`, and make every show settle** on `CLOSED`, on `ERROR` (phase `show`) and on a rejected
   `show()`. *Why:* iOS does not background the app for these ads, so nothing else pauses audio or
   timers, and a show that never settles leaves media suspended and the handler stuck.
8. **Ads are silent and their failures are values.** Never an error, spinner or "please connect" for
   ads; failures go to the error log; branch on `error.phase` then `error.reason`, never `error.code`;
   `initialize()` returns a Result; the banner slot has zero height until an ad has loaded. *Why:* an ad
   is never what the user came for, and an escaped rejection is dropped silently in release builds.
9. **The audience is declared and enforced, and tracking follows Apple's rules.** contracts/ads.json
   `audience`: `general` -> the declared `maxAdContentRating` (default PG), no child tags;
   `child-directed` -> G, both child tags, Android's `AD_ID` permission blocked, and no ATT at all (no
   tracking package, plugin, purpose text or adapter; `tracking: null`). General audience:
   `expo-tracking-transparency` is imported only by `expo-tracking-consent-adapter.ts`, handed to the
   consent adapter as `tracking`; its plugin entry gets the source text and every language's catalog has
   it; the AdMob plugin gets no `userTrackingUsageDescription`; the console publishes no IDFA explainer.
   Declined, restricted or unavailable tracking still serves ads, and nothing is gated on the answer.
   *Why:* the rating must match the console, apps for children may not send device data to third
   parties (App Review 1.3, Play Families), a missing purpose text crashes, and 5.1.2(i) forbids gating
   on ATT.
10. **The SKAdNetwork list is Google's full list of `xxxxxxxxxx.skadnetwork` identifiers, generated and
    checked (`--check`) before every release.** *Why:* the plugin copies only what it gets, and Google
    changes the list.
11. **Consent reset, the debug geography and the Ad Inspector exist only in test-only code** (the
    debug adapter and debug options reached through the test-only entry). *Why:* they change consent
    behaviour for real users.
12. **The developer does the AdMob console steps; ask and wait, never invent IDs or publish anything.**
    *Why:* the console is the developer's account, and an invented ID or a published setting reaches real
    users.

## Workflow

1. **Install.** Read [references/setup-ids-and-modes.md](references/setup-ids-and-modes.md):
   `npm install -E react-native-google-mobile-ads@17.2.0` and
   `npx expo install expo-tracking-transparency expo-network expo-constants expo-application` (a
   child-directed app leaves out `expo-tracking-transparency`).
2. **Copy the templates** at the paths their first lines name (never rewrite them from memory):
   `templates/src/services/ads/`, `templates/src/services/consent/admob-consent-debug-adapter.ts` (+ test),
   `templates/src/config/{ads-config,admob-ids,ad-pacing,skadnetwork-ids}.ts` (+ tests),
   `templates/src/hooks/`, `templates/src/ui/ad-banner-slot.tsx` (+ test), `templates/scripts/ads/`,
   `templates/contracts/ads.json` (+ schema; fill the banner screens, audience and floors). The synced
   canonical files (`src/services/consent/*`, `src/services/connectivity/*`, `src/config/app-variant.ts`,
   `src/core/result.ts`, the two root mocks) are copied only when the app lacks them; a child-directed
   app deletes `expo-tracking-consent-adapter.ts` (+ tests) and the tracking mock.
3. **Wire `app.config.ts`** as [examples/app-config-ads.ts](examples/app-config-ads.ts) shows:
   `resolveAdsMode`, the two plugin entries, `extra: { ...adsExtra(adsMode, ADMOB_IDS) }`, the tracking
   text in every language's `locales` (none of the tracking lines for a child-directed app). With EAS the
   next build applies it; locally rerun `npx expo prebuild --clean`. Refresh the native config snapshot
   (expo-prebuild-and-config-plugins' checker with `--write`: `APPLICATION_ID` and
   `DELAY_APP_MEASUREMENT_INIT` change) and commit it with the plugin change. Publish every later
   `eas update` with the channel's environment (`--environment <env>`, `ADS_MODE` unset).
4. **Compose the ports** in the composition root ([examples/create-device-services.ts](examples/create-device-services.ts)):
   add `ads`, `consent`, `connectivity`, `fullscreenAd` to `Services`, `createDeviceServices` and
   `createTestServices` (architecture-and-boundaries' port recipe), with
   `readAdsExtraForBinary(Application.applicationId)` and the consent adapter's `tracking`.
5. **Mount the consent moment.** Read [references/consent-flow.md](references/consent-flow.md); render
   `AdsRoot` ([examples/ads-root.tsx](examples/ads-root.tsx)) inside `RootStack` around `AppStack`, with
   the app's Premium, onboarding and saved consent facts and, optionally, a primer
   ([examples/consent-primer.tsx](examples/consent-primer.tsx)). Show the "Ad privacy choices" entry only
   while required: the settings model returns `adPrivacy`
   ([examples/use-settings-model.ts](examples/use-settings-model.ts)), the row is a pure view
   ([examples/ad-privacy-row.tsx](examples/ad-privacy-row.tsx)).
6. **Place the ads.** Read [references/ad-policy.md](references/ad-policy.md) and
   [references/placements-and-lifecycle.md](references/placements-and-lifecycle.md); follow
   [examples/use-tasks-model.ts](examples/use-tasks-model.ts) and [examples/tasks-screen.tsx](examples/tasks-screen.tsx):
   `useBannerSlot('<screen>', facts, useIsFocused())` in the model hook and `AdBannerSlot` in the view;
   `recordBreak` where a break finishes; `showInterstitialIfDue` in the press handler that leaves it;
   `useRewardedOffer` and `earnRewardedPerk` for rewards; the `fullscreenAd` gate for every fullscreen ad.
7. **Run the tests**: `npx jest src/services/ads src/services/consent src/config src/hooks src/ui scripts/ads --ci --maxWorkers=2`.
8. **Run the checkers**: `node ${CLAUDE_SKILL_DIR}/scripts/check-ads.mjs .` and
   `node ${CLAUDE_SKILL_DIR}/scripts/check-ad-behaviour.mjs .` (multi-package: add `--app apps/<app>`).
   Fix every `FAIL` line (file, rule, fix) and rerun until both print `RESULT: PASS`.
9. **Before a release**: `node scripts/ads/refresh-skadnetwork.ts --check`; the ads smoke test by hand on
   an `ADS_MODE=test` build, on a named iOS simulator and a named Android emulator
   ([references/console-privacy-troubleshooting.md](references/console-privacy-troubleshooting.md),
   `templates/maestro/ads-smoke/`; declare the folder in `contracts/naming.json` `extraE2eFolders` as
   `{ "folder": "ads-smoke", "why": "by-hand ads smoke on ADS_MODE=test builds; CI never runs it" }`,
   so naming-conventions' checkers pass); then hand off the App Privacy and Data safety answers
   (privacy-and-store-declarations).
10. **Tell the developer**, in plain words, which console steps are still open (account, apps and units,
    the GDPR message, blocking controls before the first store build; store links and `app-ads.txt`
    after the first release) and what each needs from them.

## Definition of done

- [ ] The SDK is exactly 17.2.0 and imported only by the three adapter files; `expo-tracking-transparency`
      is installed and imported only by `expo-tracking-consent-adapter.ts` (general audience) or absent
      with `tracking: null` (child-directed); the root mocks exist.
- [ ] Every template is in place and its Jest suite passes; the consent folder equals the canonical copy.
- [ ] The AdMob plugin gets `admobPluginOptions(...)`; `expo.extra` comes from `adsExtra(...)`; no AdMob
      ID is typed in app code and no app code imports `admob-ids.ts`; the app reads it through
      `readAdsExtraForBinary(Application.applicationId)`; the native config snapshot is refreshed.
- [ ] Consent: launch refresh, the primer (if any) then Google's form, then Apple's prompt while
      not-determined (general audience), after onboarding, over a focused ad screen, before the first
      ad; nothing initialised or loaded without `canRequestAds`; banners only after the ATT answer and
      a successful `initialize`; declined, restricted or unavailable tracking still initializes;
      `ADS_MODE=off` never calls UMP or ATT; the privacy entry shows only when required.
- [ ] The purpose text: the plugin entry, every language's catalog, the localised writer; no IDFA
      explainer in the console; the audience profile matches the adapter and the console rating.
- [ ] Every finished break is recorded once; banners only on the declared slots with their testIDs and
      a policy-computed `isAllowed`, only while focused; interstitials only from press handlers through
      `showInterstitialIfDue`; rewards only on `EARNED_REWARD`; media is suspended during fullscreen ads.
- [ ] Offline, Premium, no consent and `ADS_MODE=off` each show no ad and no message.
- [ ] `refresh-skadnetwork.ts --check` passes before a release; the ads smoke flows passed on iOS and
      Android (or the report says they are owed); `contracts/naming.json` declares `ads-smoke`; the
      developer has done the console steps.
- [ ] Expected only: `SKIP src/i18n/catalogs [att-plugin] not yet created (i18n-strings-and-catalogs creates it)`
      before the app has catalogs; the placeholder-IDs line (informational, not a problem) until the
      developer gives the real IDs (never in a release report); a consent-only app's note that the ads
      rules do not apply yet; and `NOT APPLICABLE` in an app without the SDK. A multi-package repo runs
      each app with `--app <dir>`.
- [ ] `node ${CLAUDE_SKILL_DIR}/scripts/check-ads.mjs .` prints `RESULT: PASS`
- [ ] `node ${CLAUDE_SKILL_DIR}/scripts/check-ad-behaviour.mjs .` prints `RESULT: PASS`

## Anti-patterns

- **Calling `showInterstitialIfDue` from an effect of the next screen or on app resume** (an
  `AppState` listener), directly or through a helper. The ad covers what the user just opened. Only the
  press handler that leaves the break calls it (check-ads `ad-in-effect`).
- **"Upgrading" the child tags to `ageRestrictedTreatment`.** In react-native-google-mobile-ads 17.2.0 Android drops it silently;
  keep the deprecated tags (references/consent-flow.md, Audience; check-ads `content-rating`).
- **"Upgrading" the adapter to `useInterstitialAd`, `useRewardedAd` or ad pools.** Hooks tie ads to a
  component; pools preload (and on Android initialise the SDK) before consent. Keep the classic API.
- **Initialising the SDK at app start "to be ready"** (`createAdsPort(...).initialize()`). It may preload
  ads before consent; `prepareAds` is the only caller of `initialize` (check-ads `port-calls`).
- **`deps.ads.showInterstitial()` straight from a press handler.** It skips the pacing policy, the media
  pause and the history; call `showInterstitialIfDue` (check-ads `port-calls`).
- **Holding the ad moment while the banner screen is merely mounted.** A pushed editor gets the primer,
  the form and ATT; pass `useIsFocused()` to `useBannerSlot`.
- **`readAdsExtra()` in the composition root, or `eas update` with whatever environment the shell has.**
  An update made for another variant changes the ads of installed builds; use `readAdsExtraForBinary`
  and `--environment`.
- **ATT in a child-directed app.** Kids Category apps do not implement it; no package, no purpose text,
  `tracking: null`.
- **`createAdmobConsentAdapter(...)` or `createAdmobAdsAdapter(...)` outside the factories.** An
  `ADS_MODE=off` build then asks Google UMP at launch and the network audit fails.
- **Typing test unit IDs or the sample publisher ID in code.** Use `units: 'test'` (the adapter maps
  `TestIds.*`) and `GOOGLE_SAMPLE_APP_IDS` in `ads-config.ts`.
- **`npx expo install react-native-google-mobile-ads`.** It writes a caret; use `npm install -E`.
- **One AdMob app for both platforms, or iOS units on Android.** They are separate AdMob apps; the
  factory picks this platform's units.
- **Hiding an ad failure behind a message, a retry button or a spinner.** Ads are silent; log and move on.
- **Granting a reward on `CLOSED`, or a pressable "Watch an ad" with nothing loaded.** Use
  `rewardedOffer`: `'loading'` busy, `'hidden'` gone.
- **Wrapping `AdBannerSlot` in a frame that renders nothing until an ad has loaded.** The native banner
  must be mounted to load; pass the frame as `loadedStyle`.
- **Asking ATT at launch, before Google's form, or from a screen.** Apple's prompt comes through
  `ConsentPort.requestTracking()` inside `prepareAds`, over a focused ad screen only.
- **Hiding a feature, a reward or Premium behind "Allow".** 5.1.2(i) forbids gating on the ATT answer.
- **Publishing an IDFA explainer in the AdMob console or setting the AdMob plugin's
  `userTrackingUsageDescription`.** UMP runs a second ATT flow, and the text gets two writers.
- **Proving ATT with a flow that starts with `launchApp`.** Maestro grants every permission; launch with
  `xcrun simctl launch <udid> <bundle id>`.
- **Changing pacing by editing `ad-policy.ts`, or lowering a number below its floor "for testing".**
  Change `src/config/ad-pacing.ts`; a testing build already serves test ads.
- **Putting `AdsConsent.reset()` or the Ad Inspector in the consent adapter.** They belong to the
  test-only debug adapter.
- **Fixing a failing checker by weakening a Jest test or the fixture.** The behaviour check runs the real
  modules; fix the module.

## Files in this skill

| File | What it is | Read/run when |
|---|---|---|
| [references/setup-ids-and-modes.md](references/setup-ids-and-modes.md) | Versions, install, the variant x ads-mode matrix, IDs, plugin options, test IDs, the ports per mode, SKAdNetwork, multi-package, re-verify | Workflow steps 1-4 |
| [references/consent-flow.md](references/consent-flow.md) | UMP and ATT rules with sources, Android, the audience profiles, the canonical consent folder, the sequence, the consent moment, the privacy entry, test-only tools | Workflow step 5 |
| [references/ad-policy.md](references/ad-policy.md) | The default pacing, the pure policy, floors, the history, rewarded offers, interpretations, tests | Workflow step 6; before changing any ad rule |
| [references/placements-and-lifecycle.md](references/placements-and-lifecycle.md) | AdsPort and adapter facts, the banner slot, natural breaks, rewards, pausing media on iOS and Android, offline, React Navigation | Workflow step 6 |
| [references/console-privacy-troubleshooting.md](references/console-privacy-troubleshooting.md) | The developer's console steps, App Privacy and Data safety facts, the smoke test, root mocks, error and symptom tables | Workflow steps 9-10; when an ad misbehaves |
| [examples/app-config-ads.ts](examples/app-config-ads.ts) | The ads lines of `app.config.ts` | Workflow step 3 |
| [examples/create-device-services.ts](examples/create-device-services.ts) | The composition root with the ads, consent, connectivity and media gate ports | Workflow step 4 |
| [examples/ads-root.tsx](examples/ads-root.tsx) | Mounting `AdsProvider` with the app's facts | Workflow step 5 |
| [examples/consent-primer.tsx](examples/consent-primer.tsx) | An optional consent primer before Google's form | Workflow step 5 |
| [examples/use-tasks-model.ts](examples/use-tasks-model.ts) | A model hook with the banner, a natural break, an interstitial and a rewarded offer | Workflow step 6 |
| [examples/tasks-screen.tsx](examples/tasks-screen.tsx) | The banner slot at the bottom of a pure view | Workflow step 6 |
| [examples/use-settings-model.ts](examples/use-settings-model.ts) | The settings model's `adPrivacy` (reads the consent answer and the port) | Workflow step 5 |
| [examples/ad-privacy-row.tsx](examples/ad-privacy-row.tsx) | The "Ad privacy choices" entry, a pure view | Workflow step 5 |
| `templates/src/services/ads/` | Port, AdMob adapter, fake, factory, read-ads-extra, policy, history, rewarded offer, gate, consent moment flow, moments, fullscreen gate, AdsProvider, its context and hook, with tests | Workflow step 2 |
| `templates/src/services/consent/` | ConsentPort, the AdMob consent adapter (+ iOS and Android tests), the expo-tracking-transparency ATT adapter (+ iOS and Android tests; general audience only), the factory, the fake (+ tests): synced canonical files this skill owns; the test-only debug adapter (+ test) | Workflow step 2 |
| `templates/src/services/connectivity/` | The ConnectivityPort, expo-network adapter, reachability hub, fake (synced; networking-and-server-state owns them) | Workflow step 2 |
| `templates/src/config/` | `ads-config.ts`, `admob-ids.ts`, `ad-pacing.ts` (+ tests), `skadnetwork-ids.ts` (Google's 50 IDs, 2026-10-10); `app-variant.ts` (+ test, synced) | Workflow steps 2-3 |
| `templates/src/hooks/` | `useAdContext`, `useBannerSlot`, `useRewardedOffer` (+ tests) | Workflow step 6 |
| `templates/src/ui/` | `ad-banner-slot.tsx` (collapsed until loaded) and its test | Workflow step 6 |
| `templates/src/core/` | `result.ts` (`Result`, `ok`, `err`, `attemptAsync`) and its test (synced; error-handling-typed-results owns them) | Workflow step 2 |
| `templates/scripts/ads/` | `refresh-skadnetwork.ts` and the tested `skadnetwork-list.ts` | Workflow step 9 |
| `templates/contracts/` | `ads.json` (the app's ads contract) and `ads.schema.json` (every key documented) | Workflow step 2 |
| `templates/maestro/ads-smoke/` | Six Maestro flows of the by-hand smoke test (they land in `.maestro/ads-smoke/`) | Workflow step 9 |
| `templates/__mocks__/` | The SDK and tracking root mocks (synced; do not edit here) | Workflow step 2 |
| `assets/admob-facts.json` | Version, adapter allowlists, banned names, ID formats, SKAdNetwork facts, default floors, required files | Read by the checkers; update on an SDK upgrade |
| `scripts/check-ads.mjs` | Static checks: files, pin, imports, SDK API, factories, test-only tools, IDs, ATT order/plugin/text/wiring per audience, plugin and extra wiring, the binary guard, SKAdNetwork, audience, pacing, placements, effects, port calls, breaks, the consent moment and focus, the smoke folder, free allowances | Workflow step 8 |
| `scripts/check-ad-behaviour.mjs` | Runs the app's modules against the decision tables and the adapters and factories against scripted stand-ins | Workflow step 8 |
| `scripts/lib/ads-facts.mjs` | The facts, the layout paths, the contract reader and the scanned sources | Only to change a checker |
| `scripts/lib/consent-rules.mjs` | check-ads' consent, ATT and SDK-boundary rules | Only to change a checker |
| `scripts/lib/ads-rules.mjs` | check-ads' ads-module, wiring and placement rules | Only to change a checker |
| `scripts/lib/consent-scenarios.mjs` | check-ad-behaviour's consent adapter, tracking and factory scenarios | Only to change a checker |
| `scripts/lib/ads-scenarios.mjs` | check-ad-behaviour's policy, offer, gate, flow, moments, config, adapter, factory and extra scenarios | Only to change a checker |
| `scripts/lib/load-ts.mjs` | Loads the app's TypeScript (type stripping, tsconfig aliases) with the stand-ins | Only to change a checker |
| `scripts/lib/repo-scan.mjs` | File lists (check-lib's repo walk) and import and call helpers | Only to change a checker |
| `scripts/lib/stubs/` | Scripted stand-ins of `react-native-google-mobile-ads` 17.2.0, `expo-tracking-transparency`, `react-native`, `react` and `expo-constants` (update on an SDK upgrade) | Only to change a checker |
| `scripts/lib/app-modules.mjs` | Imports the app's pure TypeScript (synced; do not edit here) | Never by hand |
| `scripts/check-lib.mjs` | Shared script helper (synced; do not edit here) | Never by hand |
| `scripts/selftest.mjs` | Proves both checkers: the shipped templates and examples pass, every planted bug fails for its rule | After changing a checker, a template or a fixture |
| `assets/shared.json` | The shared files copied into this skill | When adding a shared file |
| `tests/` | The base app (with silent planted findings under `.agents/skills/`, `skills/` and a vendored folder) and the overlay cases per checker | When adding a rule |

## Related skills

- `in-app-purchases` - the Premium entitlement that removes every ad.
- `privacy-and-store-declarations` - privacy manifests, App Privacy and Data safety answers, the network audit.
- `architecture-and-boundaries` - ports, adapters, the composition root and SDK classification.
- `networking-and-server-state` - the ConnectivityPort the ads read.
- `debug-menu-and-test-hooks` - the test-only gate, the debug link and simulated offline.
- `settings-and-preferences` - the look of the "Ad privacy choices" row.
- `i18n-strings-and-catalogs` - the tracking purpose text in every language.
- `persistence-and-migrations` - where the ad history and the consent answer are saved.
- `app-variants-and-environment` - the variants `resolveAdsMode` builds on.
- `audio-and-haptics-feedback` - audio that follows the fullscreen gate.
- `navigation-and-routing`, `app-startup-and-bootstrap` - where `AdsProvider` sits in the root tree.
- `e2e-maestro`, `ios-simulator-build` - running the smoke flows on named devices.
- `ios-release-testflight`, `android-release-play` - the release gates that refuse the placeholder or
  sample AdMob app ID in a store build.
- `expo-prebuild-and-config-plugins` - the native config snapshot the plugin change refreshes.
- `ota-updates` - publishing EAS Updates with the channel's environment.
- `naming-conventions` - the `extraE2eFolders` entry of the smoke flows.
- `dependency-management` - pinning and upgrading the SDK.
- `analytics-and-consent` - analytics has its own consent; it shares only the ATT half (`TrackingConsent`).

Files in this skill

  • SKILL.md26.1 KB
  • assets/admob-facts.json4.8 KB
  • assets/shared.json2.5 KB
  • examples/ad-privacy-row.tsx1.1 KB
  • examples/ads-root.tsx2.1 KB
  • examples/app-config-ads.ts1.8 KB
  • examples/consent-primer.tsx1 KB
  • examples/create-device-services.ts2.5 KB
  • examples/tasks-screen.tsx949 B
  • examples/use-settings-model.ts1 KB
  • examples/use-tasks-model.ts3.2 KB
  • references/ad-policy.md11.2 KB
  • references/consent-flow.md24.7 KB
  • references/console-privacy-troubleshooting.md14.6 KB
  • references/placements-and-lifecycle.md13 KB
  • references/setup-ids-and-modes.md17.9 KB
  • scripts/check-ad-behaviour.mjs6.7 KB
  • scripts/check-ads.mjs7.7 KB
  • scripts/lib/ads-facts.mjs4.1 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.