From 4a95c3d0288c8676a83a30e3975d4efe614c639c Mon Sep 17 00:00:00 2001 From: Mike Hardy Date: Fri, 4 Sep 2026 09:28:59 -0500 Subject: [PATCH 01/11] build(repo): add empty GAM adapter package shells --- packages/applovin/package.json | 24 +++++++++++++++ packages/facebook/package.json | 24 +++++++++++++++ packages/inmobi/package.json | 24 +++++++++++++++ packages/mintegral/package.json | 24 +++++++++++++++ packages/moloco/package.json | 24 +++++++++++++++ packages/pangle/package.json | 24 +++++++++++++++ packages/unity/package.json | 24 +++++++++++++++ packages/vungle/package.json | 24 +++++++++++++++ packages/yandex/package.json | 24 +++++++++++++++ yarn.lock | 54 +++++++++++++++++++++++++++++++++ 10 files changed, 270 insertions(+) create mode 100644 packages/applovin/package.json create mode 100644 packages/facebook/package.json create mode 100644 packages/inmobi/package.json create mode 100644 packages/mintegral/package.json create mode 100644 packages/moloco/package.json create mode 100644 packages/pangle/package.json create mode 100644 packages/unity/package.json create mode 100644 packages/vungle/package.json create mode 100644 packages/yandex/package.json diff --git a/packages/applovin/package.json b/packages/applovin/package.json new file mode 100644 index 00000000..e805fdae --- /dev/null +++ b/packages/applovin/package.json @@ -0,0 +1,24 @@ +{ + "name": "@react-native-google-mobile-ads/applovin", + "version": "16.5.0", + "description": "Empty package shell for the AppLovin Google Mobile Ads (GAM) mediation adapter. Product adapter content is out of scope for this shell (see A-0 / ADAPT-02).", + "author": "Invertase (http://invertase.io)", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/invertase/react-native-google-mobile-ads", + "directory": "packages/applovin" + }, + "keywords": [ + "react-native", + "google-mobile-ads", + "admob", + "mediation", + "gam", + "applovin" + ], + "files": [], + "publishConfig": { + "access": "public" + } +} diff --git a/packages/facebook/package.json b/packages/facebook/package.json new file mode 100644 index 00000000..2b032a3f --- /dev/null +++ b/packages/facebook/package.json @@ -0,0 +1,24 @@ +{ + "name": "@react-native-google-mobile-ads/facebook", + "version": "16.5.0", + "description": "Empty package shell for the Meta Audience Network (Facebook) Google Mobile Ads (GAM) mediation adapter. Product adapter content is out of scope for this shell (see A-0 / ADAPT-02).", + "author": "Invertase (http://invertase.io)", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/invertase/react-native-google-mobile-ads", + "directory": "packages/facebook" + }, + "keywords": [ + "react-native", + "google-mobile-ads", + "admob", + "mediation", + "gam", + "facebook" + ], + "files": [], + "publishConfig": { + "access": "public" + } +} diff --git a/packages/inmobi/package.json b/packages/inmobi/package.json new file mode 100644 index 00000000..1a5ea55f --- /dev/null +++ b/packages/inmobi/package.json @@ -0,0 +1,24 @@ +{ + "name": "@react-native-google-mobile-ads/inmobi", + "version": "16.5.0", + "description": "Empty package shell for the InMobi Google Mobile Ads (GAM) mediation adapter. Product adapter content is out of scope for this shell (see A-0 / ADAPT-02).", + "author": "Invertase (http://invertase.io)", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/invertase/react-native-google-mobile-ads", + "directory": "packages/inmobi" + }, + "keywords": [ + "react-native", + "google-mobile-ads", + "admob", + "mediation", + "gam", + "inmobi" + ], + "files": [], + "publishConfig": { + "access": "public" + } +} diff --git a/packages/mintegral/package.json b/packages/mintegral/package.json new file mode 100644 index 00000000..1fb773d8 --- /dev/null +++ b/packages/mintegral/package.json @@ -0,0 +1,24 @@ +{ + "name": "@react-native-google-mobile-ads/mintegral", + "version": "16.5.0", + "description": "Empty package shell for the Mintegral Google Mobile Ads (GAM) mediation adapter. Product adapter content is out of scope for this shell (see A-0 / ADAPT-02).", + "author": "Invertase (http://invertase.io)", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/invertase/react-native-google-mobile-ads", + "directory": "packages/mintegral" + }, + "keywords": [ + "react-native", + "google-mobile-ads", + "admob", + "mediation", + "gam", + "mintegral" + ], + "files": [], + "publishConfig": { + "access": "public" + } +} diff --git a/packages/moloco/package.json b/packages/moloco/package.json new file mode 100644 index 00000000..27e3efa5 --- /dev/null +++ b/packages/moloco/package.json @@ -0,0 +1,24 @@ +{ + "name": "@react-native-google-mobile-ads/moloco", + "version": "16.5.0", + "description": "Empty package shell for the Moloco Google Mobile Ads (GAM) mediation adapter. Product adapter content is out of scope for this shell (see A-0 / ADAPT-02).", + "author": "Invertase (http://invertase.io)", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/invertase/react-native-google-mobile-ads", + "directory": "packages/moloco" + }, + "keywords": [ + "react-native", + "google-mobile-ads", + "admob", + "mediation", + "gam", + "moloco" + ], + "files": [], + "publishConfig": { + "access": "public" + } +} diff --git a/packages/pangle/package.json b/packages/pangle/package.json new file mode 100644 index 00000000..1c5acbe5 --- /dev/null +++ b/packages/pangle/package.json @@ -0,0 +1,24 @@ +{ + "name": "@react-native-google-mobile-ads/pangle", + "version": "16.5.0", + "description": "Empty package shell for the Pangle (ByteDance) Google Mobile Ads (GAM) mediation adapter. Product adapter content is out of scope for this shell (see A-0 / ADAPT-02).", + "author": "Invertase (http://invertase.io)", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/invertase/react-native-google-mobile-ads", + "directory": "packages/pangle" + }, + "keywords": [ + "react-native", + "google-mobile-ads", + "admob", + "mediation", + "gam", + "pangle" + ], + "files": [], + "publishConfig": { + "access": "public" + } +} diff --git a/packages/unity/package.json b/packages/unity/package.json new file mode 100644 index 00000000..02ff3bb7 --- /dev/null +++ b/packages/unity/package.json @@ -0,0 +1,24 @@ +{ + "name": "@react-native-google-mobile-ads/unity", + "version": "16.5.0", + "description": "Empty package shell for the Unity Ads Google Mobile Ads (GAM) mediation adapter. Product adapter content is out of scope for this shell (see A-0 / ADAPT-02).", + "author": "Invertase (http://invertase.io)", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/invertase/react-native-google-mobile-ads", + "directory": "packages/unity" + }, + "keywords": [ + "react-native", + "google-mobile-ads", + "admob", + "mediation", + "gam", + "unity" + ], + "files": [], + "publishConfig": { + "access": "public" + } +} diff --git a/packages/vungle/package.json b/packages/vungle/package.json new file mode 100644 index 00000000..bf9fd880 --- /dev/null +++ b/packages/vungle/package.json @@ -0,0 +1,24 @@ +{ + "name": "@react-native-google-mobile-ads/vungle", + "version": "16.5.0", + "description": "Empty package shell for the Liftoff / Vungle Google Mobile Ads (GAM) mediation adapter. Product adapter content is out of scope for this shell (see A-0 / ADAPT-02).", + "author": "Invertase (http://invertase.io)", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/invertase/react-native-google-mobile-ads", + "directory": "packages/vungle" + }, + "keywords": [ + "react-native", + "google-mobile-ads", + "admob", + "mediation", + "gam", + "vungle" + ], + "files": [], + "publishConfig": { + "access": "public" + } +} diff --git a/packages/yandex/package.json b/packages/yandex/package.json new file mode 100644 index 00000000..b8d8ed51 --- /dev/null +++ b/packages/yandex/package.json @@ -0,0 +1,24 @@ +{ + "name": "@react-native-google-mobile-ads/yandex", + "version": "16.5.0", + "description": "Empty package shell for the Yandex Google Mobile Ads (GAM) mediation adapter. Product adapter content is out of scope for this shell (see A-0 / ADAPT-02).", + "author": "Invertase (http://invertase.io)", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/invertase/react-native-google-mobile-ads", + "directory": "packages/yandex" + }, + "keywords": [ + "react-native", + "google-mobile-ads", + "admob", + "mediation", + "gam", + "yandex" + ], + "files": [], + "publishConfig": { + "access": "public" + } +} diff --git a/yarn.lock b/yarn.lock index 04e8ca4a..d0bd2b3b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6009,6 +6009,60 @@ __metadata: languageName: node linkType: hard +"@react-native-google-mobile-ads/applovin@workspace:packages/applovin": + version: 0.0.0-use.local + resolution: "@react-native-google-mobile-ads/applovin@workspace:packages/applovin" + languageName: unknown + linkType: soft + +"@react-native-google-mobile-ads/facebook@workspace:packages/facebook": + version: 0.0.0-use.local + resolution: "@react-native-google-mobile-ads/facebook@workspace:packages/facebook" + languageName: unknown + linkType: soft + +"@react-native-google-mobile-ads/inmobi@workspace:packages/inmobi": + version: 0.0.0-use.local + resolution: "@react-native-google-mobile-ads/inmobi@workspace:packages/inmobi" + languageName: unknown + linkType: soft + +"@react-native-google-mobile-ads/mintegral@workspace:packages/mintegral": + version: 0.0.0-use.local + resolution: "@react-native-google-mobile-ads/mintegral@workspace:packages/mintegral" + languageName: unknown + linkType: soft + +"@react-native-google-mobile-ads/moloco@workspace:packages/moloco": + version: 0.0.0-use.local + resolution: "@react-native-google-mobile-ads/moloco@workspace:packages/moloco" + languageName: unknown + linkType: soft + +"@react-native-google-mobile-ads/pangle@workspace:packages/pangle": + version: 0.0.0-use.local + resolution: "@react-native-google-mobile-ads/pangle@workspace:packages/pangle" + languageName: unknown + linkType: soft + +"@react-native-google-mobile-ads/unity@workspace:packages/unity": + version: 0.0.0-use.local + resolution: "@react-native-google-mobile-ads/unity@workspace:packages/unity" + languageName: unknown + linkType: soft + +"@react-native-google-mobile-ads/vungle@workspace:packages/vungle": + version: 0.0.0-use.local + resolution: "@react-native-google-mobile-ads/vungle@workspace:packages/vungle" + languageName: unknown + linkType: soft + +"@react-native-google-mobile-ads/yandex@workspace:packages/yandex": + version: 0.0.0-use.local + resolution: "@react-native-google-mobile-ads/yandex@workspace:packages/yandex" + languageName: unknown + linkType: soft + "@react-native/assets-registry@npm:0.83.1": version: 0.83.1 resolution: "@react-native/assets-registry@npm:0.83.1" From db15950743f1fb1a6ffb33dbf90eb8bc995c4483 Mon Sep 17 00:00:00 2001 From: Mike Hardy Date: Fri, 4 Sep 2026 09:40:19 -0500 Subject: [PATCH 02/11] feat(adapters): add GAM adapter package template --- jest.config.js | 1 + packages/_template/LICENSE | 32 +++++ packages/_template/README.md | 60 +++++++++ .../RNGoogleMobileAdsAdapter.podspec | 39 ++++++ .../__tests__/adapterTemplate.test.ts | 20 +++ packages/_template/android/build.gradle | 78 +++++++++++ .../android/src/main/AndroidManifest.xml | 6 + .../template/AdapterPackageMarker.java | 13 ++ packages/_template/app.plugin.js | 1 + .../RNGoogleMobileAdsAdapterTemplateMarker.m | 12 ++ packages/_template/package.json | 126 ++++++++++++++++++ packages/_template/plugin/src/index.ts | 51 +++++++ packages/_template/plugin/tsconfig.json | 9 ++ .../_template/plugin/tsconfig.tsbuildinfo | 1 + packages/_template/react-native.config.js | 12 ++ packages/_template/src/index.ts | 27 ++++ packages/_template/tsconfig.json | 5 + yarn.lock | 66 +++++++++ 18 files changed, 559 insertions(+) create mode 100644 packages/_template/LICENSE create mode 100644 packages/_template/README.md create mode 100644 packages/_template/RNGoogleMobileAdsAdapter.podspec create mode 100644 packages/_template/__tests__/adapterTemplate.test.ts create mode 100644 packages/_template/android/build.gradle create mode 100644 packages/_template/android/src/main/AndroidManifest.xml create mode 100644 packages/_template/android/src/main/java/io/invertase/googlemobileads/adapters/template/AdapterPackageMarker.java create mode 100644 packages/_template/app.plugin.js create mode 100644 packages/_template/ios/RNGoogleMobileAdsAdapterTemplateMarker.m create mode 100644 packages/_template/package.json create mode 100644 packages/_template/plugin/src/index.ts create mode 100644 packages/_template/plugin/tsconfig.json create mode 100644 packages/_template/plugin/tsconfig.tsbuildinfo create mode 100644 packages/_template/react-native.config.js create mode 100644 packages/_template/src/index.ts create mode 100644 packages/_template/tsconfig.json diff --git a/jest.config.js b/jest.config.js index 109669f2..d64f96fe 100644 --- a/jest.config.js +++ b/jest.config.js @@ -3,6 +3,7 @@ module.exports = { modulePathIgnorePatterns: [ '/RNGoogleMobileAdsExample/node_modules', '/packages/core/lib/', + '/packages/_template/lib/', ], setupFiles: ['./jest.setup.ts'], testPathIgnorePatterns: ['/RNGoogleMobileAdsExample'], diff --git a/packages/_template/LICENSE b/packages/_template/LICENSE new file mode 100644 index 00000000..db4814fc --- /dev/null +++ b/packages/_template/LICENSE @@ -0,0 +1,32 @@ +Apache-2.0 License +------------------ + +Copyright (c) 2021-present Invertase Limited + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this library except in compliance with the License. + +You may obtain a copy of the Apache-2.0 License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +Creative Commons Attribution 3.0 License +---------------------------------------- + +Copyright (c) 2016-present Invertase Limited + +Documentation and other instructional materials provided for this project +(including on a separate documentation repository or it's documentation website) are +licensed under the Creative Commons Attribution 3.0 License. Code samples/blocks +contained therein are licensed under the Apache License, Version 2.0 (the "License"), as above. + +You may obtain a copy of the Creative Commons Attribution 3.0 License at + + https://creativecommons.org/licenses/by/3.0/ diff --git a/packages/_template/README.md b/packages/_template/README.md new file mode 100644 index 00000000..7ce7f583 --- /dev/null +++ b/packages/_template/README.md @@ -0,0 +1,60 @@ +# GAM adapter package template + +**Private scaffold** (`@invertase/rngma-gam-adapter-template`). Character network packages copy from here; this directory is **not** a publishable `@react-native-google-mobile-ads/` package. + +Scope: + +- Scoped package layout + bob/exports +- Android Gradle hook for `com.google.ads.mediation:` +- CocoaPods hook for `GoogleMobileAdsMediation*` +- Optional Expo plugin slice (SKAdNetwork merge only — app IDs stay in core) +- Public JS surface: `nativeAdapterClassName` + `networkSlug` only (no ad APIs) + +Out of scope: MAX / CloudX / Yandex-as-host packages; filling AppLovin/Meta/… product SDK pins (add one network package per commit); core public JS API changes. + +## Layout + +```text +packages/_template/ + package.json # private; gamAdapter + sdkVersions placeholders + src/index.ts # nativeAdapterClassName / networkSlug + android/build.gradle # api mediation artifact from package.json + android/src/main/... # empty manifest + package marker + ios/*.m # pod source marker + RNGoogleMobileAdsAdapter.podspec + app.plugin.js + plugin/ # optional Expo SKAdNetwork merge + react-native.config.js # autolinking (no TurboModule) + __tests__/ # template surface unit tests + README.md # this file +``` + +## Instantiating a network package + +1. Copy this tree to `packages//` (or fill an existing empty network shell under `packages/`). +2. Set `private` → omit / `false`, rename package to `@react-native-google-mobile-ads/`. +3. Replace every `__PLACEHOLDER__` token: + +| Token | Example (AppLovin GAM adapter — verify at ship time) | +| ----- | ---------------------------------------------------- | +| `__NETWORK__` | `applovin` | +| `__ANDROID_ARTIFACT__` | `applovin` → artifact `com.google.ads.mediation:applovin` | +| `__ANDROID_MEDIATION_VERSION__` | vendor pin from Google mediation release notes | +| `__ANDROID_ADAPTER_CLASS__` | `com.google.ads.mediation.applovin.AppLovinMediationAdapter` | +| `__IOS_MEDIATION_POD__` | `GoogleMobileAdsMediationAppLovin` | +| `__IOS_MEDIATION_VERSION__` | vendor pin | +| `__IOS_ADAPTER_CLASS__` | `GADMediationAdapterAppLovin` | + +4. Rename podspec / `s.name` / Android `namespace` / Java package / Expo plugin export to the network. +5. Update `repository.directory`, keywords, README (Gradle/pod FQCN for GAM UI paste). +6. Do **not** add MAX host, CloudX host, or Yandex-as-host SDKs. +7. Peer-depend on `react-native-google-mobile-ads` only; never import core internals. + +Empty shells under `packages/{applovin,facebook,…}/` stay metadata-only until each network is filled in its own commit. + +## Build + +```bash +yarn workspace @invertase/rngma-gam-adapter-template prepare +``` + +Root `yarn prepare` (Lerna) also builds this private package when present. diff --git a/packages/_template/RNGoogleMobileAdsAdapter.podspec b/packages/_template/RNGoogleMobileAdsAdapter.podspec new file mode 100644 index 00000000..e501ec55 --- /dev/null +++ b/packages/_template/RNGoogleMobileAdsAdapter.podspec @@ -0,0 +1,39 @@ +require 'json' + +package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) +gam_adapter = package['gamAdapter'] +mediation_pod = gam_adapter['iosPod'] +mediation_version = package['sdkVersions']['ios']['googleMobileAdsMediation'] + +Pod::Spec.new do |s| + # When instantiating: rename file + s.name to RNGoogleMobileAdsAdapter + # e.g. RNGoogleMobileAdsAdapterApplovin.podspec / RNGoogleMobileAdsAdapterApplovin + s.name = "RNGoogleMobileAdsAdapterTemplate" + s.version = package["version"] + s.summary = package["description"] + s.description = package["description"] + s.homepage = "https://github.com/invertase/react-native-google-mobile-ads" + s.license = package["license"] + s.authors = "Invertase Limited" + s.source = { + :git => "#{package["repository"]["url"]}.git", + :tag => "v#{s.version}" + } + s.platforms = { :ios => "12.0" } + s.source_files = "ios/**/*.{h,m,mm,swift}" + + if respond_to?(:install_modules_dependencies, true) + install_modules_dependencies(s) + else + s.dependency "React-Core" + end + + # Google-published GAM mediation adapter pod. Replace __IOS_MEDIATION_POD__ / + # __IOS_MEDIATION_VERSION__ when instantiating. Core ships Google-Mobile-Ads-SDK. + if mediation_pod && !mediation_pod.include?("__") && + mediation_version && !mediation_version.include?("__") + s.dependency mediation_pod, mediation_version + else + Pod::UI.puts "#{s.name}: placeholder mediation pod/version — replace tokens before app use (A-0 template)." + end +end diff --git a/packages/_template/__tests__/adapterTemplate.test.ts b/packages/_template/__tests__/adapterTemplate.test.ts new file mode 100644 index 00000000..e90be08e --- /dev/null +++ b/packages/_template/__tests__/adapterTemplate.test.ts @@ -0,0 +1,20 @@ +import { nativeAdapterClassName, networkSlug } from '../src'; + +describe('A-0 GAM adapter template public surface', () => { + it('exports networkSlug placeholder for copy-replace', () => { + expect(networkSlug).toBe('__NETWORK__'); + }); + + it('exports nativeAdapterClassName placeholders (API design §5.3)', () => { + expect(nativeAdapterClassName).toEqual({ + android: '__ANDROID_ADAPTER_CLASS__', + ios: '__IOS_ADAPTER_CLASS__', + }); + }); + + it('does not invent JS ad APIs on the template surface', () => { + // eslint-disable-next-line @typescript-eslint/no-require-imports + const surface = require('../src') as Record; + expect(Object.keys(surface).sort()).toEqual(['nativeAdapterClassName', 'networkSlug']); + }); +}); diff --git a/packages/_template/android/build.gradle b/packages/_template/android/build.gradle new file mode 100644 index 00000000..c145aee9 --- /dev/null +++ b/packages/_template/android/build.gradle @@ -0,0 +1,78 @@ +import groovy.json.JsonSlurper + +buildscript { + ext.getExtOrDefault = { name, fallback -> + return rootProject.ext.has(name) ? rootProject.ext.get(name) : fallback + } + + // AGP is only required when opening this android/ folder stand-alone. + if (project == rootProject) { + repositories { + google() + mavenCentral() + } + dependencies { + classpath("com.android.tools.build:gradle:8.1.1") + } + } +} + +apply plugin: "com.android.library" + +def packageJson = new JsonSlurper().parseText(file("${projectDir}/../package.json").text) +def gamAdapter = packageJson.gamAdapter +def sdkVersions = packageJson.sdkVersions.android +def mediationArtifact = "${gamAdapter.androidArtifact}:${sdkVersions.googleMobileAdsMediation}" + +android { + namespace "io.invertase.googlemobileads.adapters.template" + + compileSdkVersion sdkVersions.compileSdk as Integer + + defaultConfig { + minSdkVersion sdkVersions.minSdk as Integer + targetSdkVersion sdkVersions.targetSdk as Integer + buildConfigField "String", "GAM_ADAPTER_NETWORK", "\"${gamAdapter.network}\"" + buildConfigField "String", "GAM_ADAPTER_ANDROID_CLASS", "\"${gamAdapter.androidAdapterClassName}\"" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + buildFeatures { + buildConfig true + } + + sourceSets { + main { + java.srcDirs = ["src/main/java"] + } + } +} + +repositories { + google() + mavenCentral() +} + +dependencies { + // Google-published GAM mediation adapter (same coordinates for classic + Next-Gen). + // Replace __ANDROID_ARTIFACT__ / __ANDROID_MEDIATION_VERSION__ when instantiating. + // Core GMA is provided by react-native-google-mobile-ads; do not re-pin it here. + api mediationArtifact +} + +// Guard: fail fast if this private template is linked without token replacement. +afterEvaluate { + if (gamAdapter.network == "__NETWORK__" || + "${sdkVersions.googleMobileAdsMediation}".contains("__") || + "${gamAdapter.androidArtifact}".contains("__")) { + logger.warn( + "[rngma-gam-adapter-template] Placeholder tokens remain in package.json. " + + "Copy packages/_template → packages// and replace __PLACEHOLDER__ values " + + "before depending on this package in an app." + ) + } +} diff --git a/packages/_template/android/src/main/AndroidManifest.xml b/packages/_template/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..b1dd18ee --- /dev/null +++ b/packages/_template/android/src/main/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + diff --git a/packages/_template/android/src/main/java/io/invertase/googlemobileads/adapters/template/AdapterPackageMarker.java b/packages/_template/android/src/main/java/io/invertase/googlemobileads/adapters/template/AdapterPackageMarker.java new file mode 100644 index 00000000..5e1cbe72 --- /dev/null +++ b/packages/_template/android/src/main/java/io/invertase/googlemobileads/adapters/template/AdapterPackageMarker.java @@ -0,0 +1,13 @@ +package io.invertase.googlemobileads.adapters.template; + +/** + * Package marker so the Android library module has a compilable source root. + * Network packages do not expose a TurboModule — mediation is linked via Gradle. + * + * When copying the template, rename this package path to match + * {@code io.invertase.googlemobileads.adapters.} and update + * {@code android.namespace} in {@code build.gradle}. + */ +public final class AdapterPackageMarker { + private AdapterPackageMarker() {} +} diff --git a/packages/_template/app.plugin.js b/packages/_template/app.plugin.js new file mode 100644 index 00000000..3c7d11b6 --- /dev/null +++ b/packages/_template/app.plugin.js @@ -0,0 +1 @@ +module.exports = require('./plugin/build'); diff --git a/packages/_template/ios/RNGoogleMobileAdsAdapterTemplateMarker.m b/packages/_template/ios/RNGoogleMobileAdsAdapterTemplateMarker.m new file mode 100644 index 00000000..d2d2440a --- /dev/null +++ b/packages/_template/ios/RNGoogleMobileAdsAdapterTemplateMarker.m @@ -0,0 +1,12 @@ +#import + +/** + * Package marker so the podspec has a compilable source file. + * Mediation is linked via the GoogleMobileAdsMediation* pod dependency. + * Rename/remove when instantiating if the network package needs no ObjC. + */ +@interface RNGoogleMobileAdsAdapterTemplateMarker : NSObject +@end + +@implementation RNGoogleMobileAdsAdapterTemplateMarker +@end diff --git a/packages/_template/package.json b/packages/_template/package.json new file mode 100644 index 00000000..d55a1a82 --- /dev/null +++ b/packages/_template/package.json @@ -0,0 +1,126 @@ +{ + "name": "@invertase/rngma-gam-adapter-template", + "version": "16.5.0", + "private": true, + "description": "A-0 private scaffold for @react-native-google-mobile-ads/ GAM mediation adapter packages. Copy to packages// and replace __PLACEHOLDER__ tokens (see README). Not a publishable network package.", + "author": "Invertase (http://invertase.io)", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/invertase/react-native-google-mobile-ads", + "directory": "packages/_template" + }, + "keywords": [ + "react-native", + "google-mobile-ads", + "admob", + "mediation", + "gam", + "adapter-template" + ], + "main": "lib/commonjs/index.js", + "module": "lib/module/index.js", + "react-native": "src/index.ts", + "types": "lib/typescript/commonjs/index.d.ts", + "exports": { + ".": { + "react-native": { + "types": "./lib/typescript/module/index.d.ts", + "default": "./src/index.ts" + }, + "source": { + "types": "./lib/typescript/module/index.d.ts", + "default": "./src/index.ts" + }, + "import": { + "types": "./lib/typescript/module/index.d.ts", + "default": "./lib/module/index.js" + }, + "require": { + "types": "./lib/typescript/commonjs/index.d.ts", + "default": "./lib/commonjs/index.js" + }, + "default": "./lib/commonjs/index.js" + }, + "./app.plugin": "./app.plugin.js", + "./app.plugin.js": "./app.plugin.js", + "./package.json": "./package.json" + }, + "files": [ + "/android/", + "/ios/", + "/lib/", + "/plugin/", + "/src/", + "/LICENSE", + "/RNGoogleMobileAdsAdapter.podspec", + "/app.plugin.js", + "/react-native.config.js" + ], + "gamAdapter": { + "network": "__NETWORK__", + "androidArtifact": "com.google.ads.mediation:__ANDROID_ARTIFACT__", + "androidAdapterClassName": "__ANDROID_ADAPTER_CLASS__", + "iosPod": "__IOS_MEDIATION_POD__", + "iosAdapterClassName": "__IOS_ADAPTER_CLASS__" + }, + "sdkVersions": { + "ios": { + "googleMobileAdsMediation": "__IOS_MEDIATION_VERSION__" + }, + "android": { + "minSdk": 23, + "targetSdk": 34, + "compileSdk": 34, + "buildTools": "34.0.0", + "googleMobileAdsMediation": "__ANDROID_MEDIATION_VERSION__" + } + }, + "react-native-builder-bob": { + "source": "src", + "output": "lib", + "targets": [ + [ + "commonjs", + { + "esm": true + } + ], + [ + "module", + { + "esm": true + } + ], + "typescript" + ] + }, + "scripts": { + "prepare": "yarn build && yarn build:plugin", + "build": "bob build", + "build:clean": "rimraf android/build && rimraf ios/build && rimraf lib && rimraf plugin/build", + "build:plugin": "yarn tsc --build plugin" + }, + "peerDependencies": { + "@expo/config-plugins": ">=5.0.0", + "expo": ">=47.0.0", + "react": "*", + "react-native": "*", + "react-native-google-mobile-ads": "*" + }, + "peerDependenciesMeta": { + "@expo/config-plugins": { + "optional": true + }, + "expo": { + "optional": true + } + }, + "devDependencies": { + "@expo/config-plugins": "^54.0.4", + "react-native-builder-bob": "^0.40.13", + "react-native-google-mobile-ads": "workspace:*", + "rimraf": "^6.0.1", + "typescript": "^5.9.3" + } +} diff --git a/packages/_template/plugin/src/index.ts b/packages/_template/plugin/src/index.ts new file mode 100644 index 00000000..a8220e98 --- /dev/null +++ b/packages/_template/plugin/src/index.ts @@ -0,0 +1,51 @@ +import { ConfigPlugin, withInfoPlist, withPlugins } from '@expo/config-plugins'; + +type PluginParameters = { + /** + * Network-specific SKAdNetwork identifiers to merge into Info.plist. + * Core's Expo plugin owns app IDs; adapters only add mediation network rows. + */ + skAdNetworkItems?: string[]; +}; + +const withAdapterSkAdNetworkItems: ConfigPlugin = ( + config, + skAdNetworkItems, +) => { + if (skAdNetworkItems === undefined || skAdNetworkItems.length === 0) { + return config; + } + + return withInfoPlist(config, config => { + config.modResults.SKAdNetworkItems = config.modResults.SKAdNetworkItems ?? []; + + const existingIdentifiers = config.modResults.SKAdNetworkItems.map( + (item: { SKAdNetworkIdentifier: string }) => item.SKAdNetworkIdentifier, + ); + + const missingIdentifiers = skAdNetworkItems.filter( + identifier => !existingIdentifiers.includes(identifier), + ); + + config.modResults.SKAdNetworkItems.push( + ...missingIdentifiers.map(identifier => ({ + SKAdNetworkIdentifier: identifier, + })), + ); + + return config; + }); +}; + +/** + * Optional Expo config plugin for a GAM adapter package. + * Does not set GMA app IDs (core plugin). Replace export name when instantiating. + */ +const withRNGoogleMobileAdsAdapterTemplate: ConfigPlugin = ( + config, + { skAdNetworkItems } = {}, +) => { + return withPlugins(config, [[withAdapterSkAdNetworkItems, skAdNetworkItems]]); +}; + +export default withRNGoogleMobileAdsAdapterTemplate; diff --git a/packages/_template/plugin/tsconfig.json b/packages/_template/plugin/tsconfig.json new file mode 100644 index 00000000..354bddb4 --- /dev/null +++ b/packages/_template/plugin/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "expo-module-scripts/tsconfig.plugin", + "compilerOptions": { + "outDir": "build", + "rootDir": "src" + }, + "include": ["./src"], + "exclude": ["**/__mocks__/*", "**/__tests__/*"] +} diff --git a/packages/_template/plugin/tsconfig.tsbuildinfo b/packages/_template/plugin/tsconfig.tsbuildinfo new file mode 100644 index 00000000..0597b1c4 --- /dev/null +++ b/packages/_template/plugin/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./src/index.ts"],"version":"5.9.3"} \ No newline at end of file diff --git a/packages/_template/react-native.config.js b/packages/_template/react-native.config.js new file mode 100644 index 00000000..382dade8 --- /dev/null +++ b/packages/_template/react-native.config.js @@ -0,0 +1,12 @@ +/** + * Autolinking entry for the GAM adapter package. + * Adapters ship native mediation artifacts only — no TurboModule / Fabric view. + */ +module.exports = { + dependency: { + platforms: { + android: {}, + ios: {}, + }, + }, +}; diff --git a/packages/_template/src/index.ts b/packages/_template/src/index.ts new file mode 100644 index 00000000..0f228bf8 --- /dev/null +++ b/packages/_template/src/index.ts @@ -0,0 +1,27 @@ +/** + * A-0 GAM adapter package public surface. + * + * Scoped network packages export adapter class-name constants for GAM UI paste / + * docs only. They do **not** add JS ad APIs — core `initialize()` adapter + * statuses remain the discovery signal (API design §5.3). + * + * When copying this template to `packages//`, replace every + * `__PLACEHOLDER__` token (see README). + */ + +export type NativeAdapterClassName = { + android: string; + ios: string; +}; + +/** Network slug (`applovin`, `facebook`, …). Replace `__NETWORK__` when instantiating. */ +export const networkSlug = '__NETWORK__' as const; + +/** + * Fully-qualified native mediation adapter class names for AdMob / GAM console paste. + * Replace `__ANDROID_ADAPTER_CLASS__` / `__IOS_ADAPTER_CLASS__` per vendor docs. + */ +export const nativeAdapterClassName: NativeAdapterClassName = { + android: '__ANDROID_ADAPTER_CLASS__', + ios: '__IOS_ADAPTER_CLASS__', +}; diff --git a/packages/_template/tsconfig.json b/packages/_template/tsconfig.json new file mode 100644 index 00000000..a841e12a --- /dev/null +++ b/packages/_template/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.packages.base.json", + "include": ["src"], + "exclude": ["node_modules", "**/*.spec.ts", "**/*.test.ts"] +} diff --git a/yarn.lock b/yarn.lock index d0bd2b3b..86df7516 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3407,6 +3407,28 @@ __metadata: languageName: node linkType: hard +"@expo/config-plugins@npm:^54.0.4": + version: 54.0.5 + resolution: "@expo/config-plugins@npm:54.0.5" + dependencies: + "@expo/config-types": "npm:^54.0.10" + "@expo/json-file": "npm:~10.0.16" + "@expo/plist": "npm:^0.4.9" + "@expo/sdk-runtime-versions": "npm:^1.0.0" + chalk: "npm:^4.1.2" + debug: "npm:^4.3.5" + getenv: "npm:^2.0.0" + glob: "npm:^13.0.0" + resolve-from: "npm:^5.0.0" + semver: "npm:^7.5.4" + slash: "npm:^3.0.0" + slugify: "npm:^1.6.6" + xcode: "npm:^3.0.1" + xml2js: "npm:0.6.0" + checksum: 10/8670d4e73761d2b010a8e390223fb53dd1f36e65262855795583f9bbdb33892ef1f9483bac10f6769b3c17e42d03c3296136cefc496d374b8e343b68c1039297 + languageName: node + linkType: hard + "@expo/config-plugins@npm:~54.0.4": version: 54.0.4 resolution: "@expo/config-plugins@npm:54.0.4" @@ -3544,6 +3566,16 @@ __metadata: languageName: node linkType: hard +"@expo/json-file@npm:~10.0.16": + version: 10.0.16 + resolution: "@expo/json-file@npm:10.0.16" + dependencies: + "@babel/code-frame": "npm:~7.10.4" + json5: "npm:^2.2.3" + checksum: 10/1ba48ee886cf9d9129be54ff903c28c27302dbc5fc7b301bce64b7a00d9d08b850f3febffa919860bc53be8e5bc92c1ae7fcf1ef508a9004950bfd7581200a90 + languageName: node + linkType: hard + "@expo/metro-config@npm:54.0.14, @expo/metro-config@npm:~54.0.14": version: 54.0.14 resolution: "@expo/metro-config@npm:54.0.14" @@ -3645,6 +3677,17 @@ __metadata: languageName: node linkType: hard +"@expo/plist@npm:^0.4.9": + version: 0.4.9 + resolution: "@expo/plist@npm:0.4.9" + dependencies: + "@xmldom/xmldom": "npm:^0.8.8" + base64-js: "npm:^1.2.3" + xmlbuilder: "npm:^15.1.1" + checksum: 10/8c8d396e1a4cb8bb718fb2e7674395ea9659420fd2c08814933d14620d70e052def311835396036b5b8e60ed204aef6190f335a78b9626bf1812b3e90e04fb86 + languageName: node + linkType: hard + "@expo/prebuild-config@npm:^54.0.8": version: 54.0.8 resolution: "@expo/prebuild-config@npm:54.0.8" @@ -4329,6 +4372,29 @@ __metadata: languageName: unknown linkType: soft +"@invertase/rngma-gam-adapter-template@workspace:packages/_template": + version: 0.0.0-use.local + resolution: "@invertase/rngma-gam-adapter-template@workspace:packages/_template" + dependencies: + "@expo/config-plugins": "npm:^54.0.4" + react-native-builder-bob: "npm:^0.40.13" + react-native-google-mobile-ads: "workspace:*" + rimraf: "npm:^6.0.1" + typescript: "npm:^5.9.3" + peerDependencies: + "@expo/config-plugins": ">=5.0.0" + expo: ">=47.0.0" + react: "*" + react-native: "*" + react-native-google-mobile-ads: "*" + peerDependenciesMeta: + "@expo/config-plugins": + optional: true + expo: + optional: true + languageName: unknown + linkType: soft + "@invertase/rngma-testing@portal:./modules/rngma-testing::locator=RNGoogleMobileAdsExample%40workspace%3ARNGoogleMobileAdsExample": version: 0.0.0-use.local resolution: "@invertase/rngma-testing@portal:./modules/rngma-testing::locator=RNGoogleMobileAdsExample%40workspace%3ARNGoogleMobileAdsExample" From 1a4ce6d7eb4fea7892c86a9f16c207344b45373f Mon Sep 17 00:00:00 2001 From: Mike Hardy Date: Fri, 4 Sep 2026 09:47:28 -0500 Subject: [PATCH 03/11] feat(adapters): add applovin GAM mediation package --- jest.config.js | 1 + packages/applovin/LICENSE | 32 ++++++ packages/applovin/README.md | 66 +++++++++++ .../RNGoogleMobileAdsAdapterApplovin.podspec | 33 ++++++ .../__tests__/applovinAdapter.test.ts | 20 ++++ packages/applovin/android/build.gradle | 66 +++++++++++ .../android/src/main/AndroidManifest.xml | 6 + .../applovin/AdapterPackageMarker.java | 10 ++ packages/applovin/app.plugin.js | 1 + .../RNGoogleMobileAdsAdapterApplovinMarker.m | 11 ++ packages/applovin/package.json | 108 +++++++++++++++++- packages/applovin/plugin/src/index.ts | 52 +++++++++ packages/applovin/plugin/tsconfig.json | 9 ++ packages/applovin/plugin/tsconfig.tsbuildinfo | 1 + packages/applovin/react-native.config.js | 12 ++ packages/applovin/src/index.ts | 29 +++++ packages/applovin/tsconfig.json | 5 + yarn.lock | 17 +++ 18 files changed, 477 insertions(+), 2 deletions(-) create mode 100644 packages/applovin/LICENSE create mode 100644 packages/applovin/README.md create mode 100644 packages/applovin/RNGoogleMobileAdsAdapterApplovin.podspec create mode 100644 packages/applovin/__tests__/applovinAdapter.test.ts create mode 100644 packages/applovin/android/build.gradle create mode 100644 packages/applovin/android/src/main/AndroidManifest.xml create mode 100644 packages/applovin/android/src/main/java/io/invertase/googlemobileads/adapters/applovin/AdapterPackageMarker.java create mode 100644 packages/applovin/app.plugin.js create mode 100644 packages/applovin/ios/RNGoogleMobileAdsAdapterApplovinMarker.m create mode 100644 packages/applovin/plugin/src/index.ts create mode 100644 packages/applovin/plugin/tsconfig.json create mode 100644 packages/applovin/plugin/tsconfig.tsbuildinfo create mode 100644 packages/applovin/react-native.config.js create mode 100644 packages/applovin/src/index.ts create mode 100644 packages/applovin/tsconfig.json diff --git a/jest.config.js b/jest.config.js index d64f96fe..e19eaaed 100644 --- a/jest.config.js +++ b/jest.config.js @@ -4,6 +4,7 @@ module.exports = { '/RNGoogleMobileAdsExample/node_modules', '/packages/core/lib/', '/packages/_template/lib/', + '/packages/applovin/lib/', ], setupFiles: ['./jest.setup.ts'], testPathIgnorePatterns: ['/RNGoogleMobileAdsExample'], diff --git a/packages/applovin/LICENSE b/packages/applovin/LICENSE new file mode 100644 index 00000000..db4814fc --- /dev/null +++ b/packages/applovin/LICENSE @@ -0,0 +1,32 @@ +Apache-2.0 License +------------------ + +Copyright (c) 2021-present Invertase Limited + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this library except in compliance with the License. + +You may obtain a copy of the Apache-2.0 License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +Creative Commons Attribution 3.0 License +---------------------------------------- + +Copyright (c) 2016-present Invertase Limited + +Documentation and other instructional materials provided for this project +(including on a separate documentation repository or it's documentation website) are +licensed under the Creative Commons Attribution 3.0 License. Code samples/blocks +contained therein are licensed under the Apache License, Version 2.0 (the "License"), as above. + +You may obtain a copy of the Creative Commons Attribution 3.0 License at + + https://creativecommons.org/licenses/by/3.0/ diff --git a/packages/applovin/README.md b/packages/applovin/README.md new file mode 100644 index 00000000..2aeaeef4 --- /dev/null +++ b/packages/applovin/README.md @@ -0,0 +1,66 @@ +# `@react-native-google-mobile-ads/applovin` + +AppLovin **Google Ad Manager / AdMob mediation** adapter package for [`react-native-google-mobile-ads`](https://github.com/invertase/react-native-google-mobile-ads). + +This package links Google’s official AppLovin mediation adapter on Android and iOS. It does **not** ship AppLovin MAX, CloudX, or any JS ad APIs — use core `initialize()` / adapter status for discovery. + +## Install + +```bash +yarn add @react-native-google-mobile-ads/applovin +# peer: react-native-google-mobile-ads +``` + +Autolinking pulls in the native mediation artifacts. Rebuild the app after install. + +## Native adapter class names (GAM / AdMob UI) + +| Platform | Class name | +| -------- | ---------- | +| Android | `com.google.ads.mediation.applovin.AppLovinMediationAdapter` | +| iOS | `GADMediationAdapterAppLovin` | + +Also exported from JS: + +```ts +import { + nativeAdapterClassName, + networkSlug, +} from '@react-native-google-mobile-ads/applovin'; +``` + +## Mediation dependencies (pinned in this package) + +| Platform | Coordinate | Version pin | +| -------- | ---------- | ----------- | +| Android | `com.google.ads.mediation:applovin` | `13.6.4.1` | +| iOS | CocoaPods `GoogleMobileAdsMediationAppLovin` | `13.6.4.0` | + +Citations (verify at upgrade time): + +- Android: https://developers.google.com/admob/android/mediation/applovin +- iOS: https://developers.google.com/admob/ios/mediation/applovin +- CocoaPods: https://cocoapods.org/pods/GoogleMobileAdsMediationAppLovin + +Core continues to own `play-services-ads` / `Google-Mobile-Ads-SDK`. This package does not re-pin the GMA SDK. + +## Expo (optional) + +```js +// app.json / app.config.js plugins +[ + '@react-native-google-mobile-ads/applovin', + { + // Pass AppLovin SKAdNetwork IDs from AppLovin’s current docs + skAdNetworkItems: [/* ... */], + }, +] +``` + +App IDs stay in the core Expo plugin. + +## Out of scope + +- AppLovin MAX host SDK +- CloudX / other non-GAM hosts +- Fyber/DT Exchange (no Google GAM adapter — do not invent) diff --git a/packages/applovin/RNGoogleMobileAdsAdapterApplovin.podspec b/packages/applovin/RNGoogleMobileAdsAdapterApplovin.podspec new file mode 100644 index 00000000..c712dc35 --- /dev/null +++ b/packages/applovin/RNGoogleMobileAdsAdapterApplovin.podspec @@ -0,0 +1,33 @@ +require 'json' + +package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) +gam_adapter = package['gamAdapter'] +mediation_pod = gam_adapter['iosPod'] +mediation_version = package['sdkVersions']['ios']['googleMobileAdsMediation'] + +Pod::Spec.new do |s| + s.name = "RNGoogleMobileAdsAdapterApplovin" + s.version = package["version"] + s.summary = package["description"] + s.description = package["description"] + s.homepage = "https://github.com/invertase/react-native-google-mobile-ads" + s.license = package["license"] + s.authors = "Invertase Limited" + s.source = { + :git => "#{package["repository"]["url"]}.git", + :tag => "v#{s.version}" + } + s.platforms = { :ios => "12.0" } + s.source_files = "ios/**/*.{h,m,mm,swift}" + + if respond_to?(:install_modules_dependencies, true) + install_modules_dependencies(s) + else + s.dependency "React-Core" + end + + # Google-published AppLovin GAM mediation adapter. + # https://developers.google.com/admob/ios/mediation/applovin + # https://cocoapods.org/pods/GoogleMobileAdsMediationAppLovin + s.dependency mediation_pod, mediation_version +end diff --git a/packages/applovin/__tests__/applovinAdapter.test.ts b/packages/applovin/__tests__/applovinAdapter.test.ts new file mode 100644 index 00000000..add4777b --- /dev/null +++ b/packages/applovin/__tests__/applovinAdapter.test.ts @@ -0,0 +1,20 @@ +import { nativeAdapterClassName, networkSlug } from '../src'; + +describe('@react-native-google-mobile-ads/applovin public surface', () => { + it('exports networkSlug applovin', () => { + expect(networkSlug).toBe('applovin'); + }); + + it('exports documented GAM mediation adapter class names', () => { + expect(nativeAdapterClassName).toEqual({ + android: 'com.google.ads.mediation.applovin.AppLovinMediationAdapter', + ios: 'GADMediationAdapterAppLovin', + }); + }); + + it('does not invent JS ad APIs on the package surface', () => { + // eslint-disable-next-line @typescript-eslint/no-require-imports + const surface = require('../src') as Record; + expect(Object.keys(surface).sort()).toEqual(['nativeAdapterClassName', 'networkSlug']); + }); +}); diff --git a/packages/applovin/android/build.gradle b/packages/applovin/android/build.gradle new file mode 100644 index 00000000..92041110 --- /dev/null +++ b/packages/applovin/android/build.gradle @@ -0,0 +1,66 @@ +import groovy.json.JsonSlurper + +buildscript { + ext.getExtOrDefault = { name, fallback -> + return rootProject.ext.has(name) ? rootProject.ext.get(name) : fallback + } + + // AGP is only required when opening this android/ folder stand-alone. + if (project == rootProject) { + repositories { + google() + mavenCentral() + } + dependencies { + classpath("com.android.tools.build:gradle:8.1.1") + } + } +} + +apply plugin: "com.android.library" + +def packageJson = new JsonSlurper().parseText(file("${projectDir}/../package.json").text) +def gamAdapter = packageJson.gamAdapter +def sdkVersions = packageJson.sdkVersions.android +def mediationArtifact = "${gamAdapter.androidArtifact}:${sdkVersions.googleMobileAdsMediation}" + +android { + namespace "io.invertase.googlemobileads.adapters.applovin" + + compileSdkVersion sdkVersions.compileSdk as Integer + + defaultConfig { + minSdkVersion sdkVersions.minSdk as Integer + targetSdkVersion sdkVersions.targetSdk as Integer + buildConfigField "String", "GAM_ADAPTER_NETWORK", "\"${gamAdapter.network}\"" + buildConfigField "String", "GAM_ADAPTER_ANDROID_CLASS", "\"${gamAdapter.androidAdapterClassName}\"" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + buildFeatures { + buildConfig true + } + + sourceSets { + main { + java.srcDirs = ["src/main/java"] + } + } +} + +repositories { + google() + mavenCentral() +} + +dependencies { + // Google-published AppLovin GAM mediation adapter (classic + Next-Gen share coordinates). + // Pin: package.json sdkVersions.android.googleMobileAdsMediation (docs: 13.6.4.1 as of package publish; verify against current docs/Maven). + // Core GMA is provided by react-native-google-mobile-ads; do not re-pin it here. + // https://developers.google.com/admob/android/mediation/applovin + api mediationArtifact +} diff --git a/packages/applovin/android/src/main/AndroidManifest.xml b/packages/applovin/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..b1dd18ee --- /dev/null +++ b/packages/applovin/android/src/main/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + diff --git a/packages/applovin/android/src/main/java/io/invertase/googlemobileads/adapters/applovin/AdapterPackageMarker.java b/packages/applovin/android/src/main/java/io/invertase/googlemobileads/adapters/applovin/AdapterPackageMarker.java new file mode 100644 index 00000000..13a9d020 --- /dev/null +++ b/packages/applovin/android/src/main/java/io/invertase/googlemobileads/adapters/applovin/AdapterPackageMarker.java @@ -0,0 +1,10 @@ +package io.invertase.googlemobileads.adapters.applovin; + +/** + * Package marker so the Android library module has a compilable source root. + * This package does not expose a TurboModule — mediation is linked via Gradle + * ({@code com.google.ads.mediation:applovin}). + */ +public final class AdapterPackageMarker { + private AdapterPackageMarker() {} +} diff --git a/packages/applovin/app.plugin.js b/packages/applovin/app.plugin.js new file mode 100644 index 00000000..3c7d11b6 --- /dev/null +++ b/packages/applovin/app.plugin.js @@ -0,0 +1 @@ +module.exports = require('./plugin/build'); diff --git a/packages/applovin/ios/RNGoogleMobileAdsAdapterApplovinMarker.m b/packages/applovin/ios/RNGoogleMobileAdsAdapterApplovinMarker.m new file mode 100644 index 00000000..adc57f05 --- /dev/null +++ b/packages/applovin/ios/RNGoogleMobileAdsAdapterApplovinMarker.m @@ -0,0 +1,11 @@ +#import + +/** + * Package marker so the podspec has a compilable source file. + * Mediation is linked via the GoogleMobileAdsMediationAppLovin pod dependency. + */ +@interface RNGoogleMobileAdsAdapterApplovinMarker : NSObject +@end + +@implementation RNGoogleMobileAdsAdapterApplovinMarker +@end diff --git a/packages/applovin/package.json b/packages/applovin/package.json index e805fdae..6237679e 100644 --- a/packages/applovin/package.json +++ b/packages/applovin/package.json @@ -1,7 +1,7 @@ { "name": "@react-native-google-mobile-ads/applovin", "version": "16.5.0", - "description": "Empty package shell for the AppLovin Google Mobile Ads (GAM) mediation adapter. Product adapter content is out of scope for this shell (see A-0 / ADAPT-02).", + "description": "AppLovin Google Mobile Ads (GAM) mediation adapter for react-native-google-mobile-ads. Links the official Google mediation adapter on Android and iOS; does not add JS ad APIs.", "author": "Invertase (http://invertase.io)", "license": "Apache-2.0", "repository": { @@ -17,8 +17,112 @@ "gam", "applovin" ], - "files": [], + "main": "lib/commonjs/index.js", + "module": "lib/module/index.js", + "react-native": "src/index.ts", + "types": "lib/typescript/commonjs/index.d.ts", + "exports": { + ".": { + "react-native": { + "types": "./lib/typescript/module/index.d.ts", + "default": "./src/index.ts" + }, + "source": { + "types": "./lib/typescript/module/index.d.ts", + "default": "./src/index.ts" + }, + "import": { + "types": "./lib/typescript/module/index.d.ts", + "default": "./lib/module/index.js" + }, + "require": { + "types": "./lib/typescript/commonjs/index.d.ts", + "default": "./lib/commonjs/index.js" + }, + "default": "./lib/commonjs/index.js" + }, + "./app.plugin": "./app.plugin.js", + "./app.plugin.js": "./app.plugin.js", + "./package.json": "./package.json" + }, + "files": [ + "/android/", + "/ios/", + "/lib/", + "/plugin/", + "/src/", + "/LICENSE", + "/RNGoogleMobileAdsAdapterApplovin.podspec", + "/app.plugin.js", + "/react-native.config.js" + ], "publishConfig": { "access": "public" + }, + "gamAdapter": { + "network": "applovin", + "androidArtifact": "com.google.ads.mediation:applovin", + "androidAdapterClassName": "com.google.ads.mediation.applovin.AppLovinMediationAdapter", + "iosPod": "GoogleMobileAdsMediationAppLovin", + "iosAdapterClassName": "GADMediationAdapterAppLovin" + }, + "sdkVersions": { + "ios": { + "googleMobileAdsMediation": "13.6.4.0" + }, + "android": { + "minSdk": 23, + "targetSdk": 34, + "compileSdk": 34, + "buildTools": "34.0.0", + "googleMobileAdsMediation": "13.6.4.1" + } + }, + "react-native-builder-bob": { + "source": "src", + "output": "lib", + "targets": [ + [ + "commonjs", + { + "esm": true + } + ], + [ + "module", + { + "esm": true + } + ], + "typescript" + ] + }, + "scripts": { + "prepare": "yarn build && yarn build:plugin", + "build": "bob build", + "build:clean": "rimraf android/build && rimraf ios/build && rimraf lib && rimraf plugin/build", + "build:plugin": "yarn tsc --build plugin" + }, + "peerDependencies": { + "@expo/config-plugins": ">=5.0.0", + "expo": ">=47.0.0", + "react": "*", + "react-native": "*", + "react-native-google-mobile-ads": "*" + }, + "peerDependenciesMeta": { + "@expo/config-plugins": { + "optional": true + }, + "expo": { + "optional": true + } + }, + "devDependencies": { + "@expo/config-plugins": "^54.0.4", + "react-native-builder-bob": "^0.40.13", + "react-native-google-mobile-ads": "workspace:*", + "rimraf": "^6.0.1", + "typescript": "^5.9.3" } } diff --git a/packages/applovin/plugin/src/index.ts b/packages/applovin/plugin/src/index.ts new file mode 100644 index 00000000..71694941 --- /dev/null +++ b/packages/applovin/plugin/src/index.ts @@ -0,0 +1,52 @@ +import { ConfigPlugin, withInfoPlist, withPlugins } from '@expo/config-plugins'; + +type PluginParameters = { + /** + * AppLovin-related SKAdNetwork identifiers to merge into Info.plist. + * Core's Expo plugin owns app IDs; this adapter only merges mediation network rows. + * Pass identifiers from AppLovin's current SKAdNetwork documentation at integrate time. + */ + skAdNetworkItems?: string[]; +}; + +const withAdapterSkAdNetworkItems: ConfigPlugin = ( + config, + skAdNetworkItems, +) => { + if (skAdNetworkItems === undefined || skAdNetworkItems.length === 0) { + return config; + } + + return withInfoPlist(config, config => { + config.modResults.SKAdNetworkItems = config.modResults.SKAdNetworkItems ?? []; + + const existingIdentifiers = config.modResults.SKAdNetworkItems.map( + (item: { SKAdNetworkIdentifier: string }) => item.SKAdNetworkIdentifier, + ); + + const missingIdentifiers = skAdNetworkItems.filter( + identifier => !existingIdentifiers.includes(identifier), + ); + + config.modResults.SKAdNetworkItems.push( + ...missingIdentifiers.map(identifier => ({ + SKAdNetworkIdentifier: identifier, + })), + ); + + return config; + }); +}; + +/** + * Optional Expo config plugin for `@react-native-google-mobile-ads/applovin`. + * Does not set GMA app IDs (core plugin). + */ +const withRNGoogleMobileAdsAdapterApplovin: ConfigPlugin = ( + config, + { skAdNetworkItems } = {}, +) => { + return withPlugins(config, [[withAdapterSkAdNetworkItems, skAdNetworkItems]]); +}; + +export default withRNGoogleMobileAdsAdapterApplovin; diff --git a/packages/applovin/plugin/tsconfig.json b/packages/applovin/plugin/tsconfig.json new file mode 100644 index 00000000..354bddb4 --- /dev/null +++ b/packages/applovin/plugin/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "expo-module-scripts/tsconfig.plugin", + "compilerOptions": { + "outDir": "build", + "rootDir": "src" + }, + "include": ["./src"], + "exclude": ["**/__mocks__/*", "**/__tests__/*"] +} diff --git a/packages/applovin/plugin/tsconfig.tsbuildinfo b/packages/applovin/plugin/tsconfig.tsbuildinfo new file mode 100644 index 00000000..0597b1c4 --- /dev/null +++ b/packages/applovin/plugin/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./src/index.ts"],"version":"5.9.3"} \ No newline at end of file diff --git a/packages/applovin/react-native.config.js b/packages/applovin/react-native.config.js new file mode 100644 index 00000000..382dade8 --- /dev/null +++ b/packages/applovin/react-native.config.js @@ -0,0 +1,12 @@ +/** + * Autolinking entry for the GAM adapter package. + * Adapters ship native mediation artifacts only — no TurboModule / Fabric view. + */ +module.exports = { + dependency: { + platforms: { + android: {}, + ios: {}, + }, + }, +}; diff --git a/packages/applovin/src/index.ts b/packages/applovin/src/index.ts new file mode 100644 index 00000000..57cf719c --- /dev/null +++ b/packages/applovin/src/index.ts @@ -0,0 +1,29 @@ +/** + * AppLovin GAM mediation adapter package public surface. + * + * Exports adapter class-name constants for GAM UI paste / docs only. Does **not** + * add JS ad APIs — core `initialize()` adapter statuses remain the discovery + * signal (API design §5.3). + * + * Native mediation is linked via Google's published adapter: + * - Android: `com.google.ads.mediation:applovin` + * - iOS: `GoogleMobileAdsMediationAppLovin` + */ + +export type NativeAdapterClassName = { + android: string; + ios: string; +}; + +/** Network slug for this Character GAM adapter package. */ +export const networkSlug = 'applovin' as const; + +/** + * Fully-qualified native mediation adapter class names for AdMob / GAM console paste. + * @see https://developers.google.com/admob/android/mediation/applovin + * @see https://developers.google.com/admob/ios/mediation/applovin + */ +export const nativeAdapterClassName: NativeAdapterClassName = { + android: 'com.google.ads.mediation.applovin.AppLovinMediationAdapter', + ios: 'GADMediationAdapterAppLovin', +}; diff --git a/packages/applovin/tsconfig.json b/packages/applovin/tsconfig.json new file mode 100644 index 00000000..a841e12a --- /dev/null +++ b/packages/applovin/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.packages.base.json", + "include": ["src"], + "exclude": ["node_modules", "**/*.spec.ts", "**/*.test.ts"] +} diff --git a/yarn.lock b/yarn.lock index 86df7516..ffab913f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6078,6 +6078,23 @@ __metadata: "@react-native-google-mobile-ads/applovin@workspace:packages/applovin": version: 0.0.0-use.local resolution: "@react-native-google-mobile-ads/applovin@workspace:packages/applovin" + dependencies: + "@expo/config-plugins": "npm:^54.0.4" + react-native-builder-bob: "npm:^0.40.13" + react-native-google-mobile-ads: "workspace:*" + rimraf: "npm:^6.0.1" + typescript: "npm:^5.9.3" + peerDependencies: + "@expo/config-plugins": ">=5.0.0" + expo: ">=47.0.0" + react: "*" + react-native: "*" + react-native-google-mobile-ads: "*" + peerDependenciesMeta: + "@expo/config-plugins": + optional: true + expo: + optional: true languageName: unknown linkType: soft From d16a160131c6394af9d574d22012d5ab0b79e442 Mon Sep 17 00:00:00 2001 From: Mike Hardy Date: Fri, 4 Sep 2026 09:59:00 -0500 Subject: [PATCH 04/11] feat(adapters): add facebook GAM mediation package --- jest.config.js | 2 + packages/facebook/LICENSE | 32 +++++ packages/facebook/README.md | 68 +++++++++++ .../RNGoogleMobileAdsAdapterFacebook.podspec | 35 ++++++ .../__tests__/facebookAdapter.test.ts | 20 ++++ packages/facebook/android/build.gradle | 66 +++++++++++ .../android/src/main/AndroidManifest.xml | 6 + .../facebook/AdapterPackageMarker.java | 10 ++ packages/facebook/app.plugin.js | 1 + .../RNGoogleMobileAdsAdapterFacebookMarker.m | 11 ++ packages/facebook/package.json | 112 +++++++++++++++++- packages/facebook/plugin/src/index.ts | 52 ++++++++ packages/facebook/plugin/tsconfig.json | 9 ++ packages/facebook/plugin/tsconfig.tsbuildinfo | 1 + packages/facebook/react-native.config.js | 12 ++ packages/facebook/src/index.ts | 29 +++++ packages/facebook/tsconfig.json | 5 + yarn.lock | 17 +++ 18 files changed, 485 insertions(+), 3 deletions(-) create mode 100644 packages/facebook/LICENSE create mode 100644 packages/facebook/README.md create mode 100644 packages/facebook/RNGoogleMobileAdsAdapterFacebook.podspec create mode 100644 packages/facebook/__tests__/facebookAdapter.test.ts create mode 100644 packages/facebook/android/build.gradle create mode 100644 packages/facebook/android/src/main/AndroidManifest.xml create mode 100644 packages/facebook/android/src/main/java/io/invertase/googlemobileads/adapters/facebook/AdapterPackageMarker.java create mode 100644 packages/facebook/app.plugin.js create mode 100644 packages/facebook/ios/RNGoogleMobileAdsAdapterFacebookMarker.m create mode 100644 packages/facebook/plugin/src/index.ts create mode 100644 packages/facebook/plugin/tsconfig.json create mode 100644 packages/facebook/plugin/tsconfig.tsbuildinfo create mode 100644 packages/facebook/react-native.config.js create mode 100644 packages/facebook/src/index.ts create mode 100644 packages/facebook/tsconfig.json diff --git a/jest.config.js b/jest.config.js index e19eaaed..ed1bd46e 100644 --- a/jest.config.js +++ b/jest.config.js @@ -5,7 +5,9 @@ module.exports = { '/packages/core/lib/', '/packages/_template/lib/', '/packages/applovin/lib/', + '/packages/facebook/lib/', ], + setupFiles: ['./jest.setup.ts'], testPathIgnorePatterns: ['/RNGoogleMobileAdsExample'], testRegex: '(/__tests__/.*\\.(test|spec))\\.[jt]sx?$', diff --git a/packages/facebook/LICENSE b/packages/facebook/LICENSE new file mode 100644 index 00000000..db4814fc --- /dev/null +++ b/packages/facebook/LICENSE @@ -0,0 +1,32 @@ +Apache-2.0 License +------------------ + +Copyright (c) 2021-present Invertase Limited + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this library except in compliance with the License. + +You may obtain a copy of the Apache-2.0 License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +Creative Commons Attribution 3.0 License +---------------------------------------- + +Copyright (c) 2016-present Invertase Limited + +Documentation and other instructional materials provided for this project +(including on a separate documentation repository or it's documentation website) are +licensed under the Creative Commons Attribution 3.0 License. Code samples/blocks +contained therein are licensed under the Apache License, Version 2.0 (the "License"), as above. + +You may obtain a copy of the Creative Commons Attribution 3.0 License at + + https://creativecommons.org/licenses/by/3.0/ diff --git a/packages/facebook/README.md b/packages/facebook/README.md new file mode 100644 index 00000000..759226bf --- /dev/null +++ b/packages/facebook/README.md @@ -0,0 +1,68 @@ +# `@react-native-google-mobile-ads/facebook` + +Meta Audience Network (Facebook) **Google Ad Manager / AdMob mediation** adapter package for [`react-native-google-mobile-ads`](https://github.com/invertase/react-native-google-mobile-ads). + +This package links Google’s official Meta Audience Network mediation adapter on Android and iOS. It does **not** ship AppLovin MAX, CloudX, or any JS ad APIs — use core `initialize()` / adapter status for discovery. + +## Install + +```bash +yarn add @react-native-google-mobile-ads/facebook +# peer: react-native-google-mobile-ads +``` + +Autolinking pulls in the native mediation artifacts. Rebuild the app after install. + +## Native adapter class names (GAM / AdMob UI) + +| Platform | Class name | +| -------- | ---------- | +| Android | `com.google.ads.mediation.facebook.FacebookMediationAdapter` | +| iOS | `GADMediationAdapterFacebook` | + +Also exported from JS: + +```ts +import { + nativeAdapterClassName, + networkSlug, +} from '@react-native-google-mobile-ads/facebook'; +``` + +## Mediation dependencies (pinned in this package) + +| Platform | Coordinate | Version pin | +| -------- | ---------- | ----------- | +| Android | `com.google.ads.mediation:facebook` | `6.22.0.0` | +| iOS | CocoaPods `GoogleMobileAdsMediationFacebook` | `6.22.0.0` | + +Citations (verify at upgrade time): + +- Android: https://developers.google.com/admob/android/mediation/meta +- iOS: https://developers.google.com/admob/ios/mediation/meta +- CocoaPods: https://cocoapods.org/pods/GoogleMobileAdsMediationFacebook + +Core continues to own `play-services-ads` / `Google-Mobile-Ads-SDK`. This package does not re-pin the GMA SDK. + +iOS platform floor is **15.0** (required by Meta Audience Network mediation adapter `6.22.0.0`). + +## Expo (optional) + +```js +// app.json / app.config.js plugins +[ + '@react-native-google-mobile-ads/facebook', + { + // Pass Meta Audience Network SKAdNetwork IDs from Meta’s current docs + skAdNetworkItems: [/* ... */], + }, +] +``` + +App IDs stay in the core Expo plugin. + +## Out of scope + +- AppLovin MAX host SDK +- CloudX / other non-GAM hosts +- Fyber/DT Exchange (no Google GAM adapter — do not invent) diff --git a/packages/facebook/RNGoogleMobileAdsAdapterFacebook.podspec b/packages/facebook/RNGoogleMobileAdsAdapterFacebook.podspec new file mode 100644 index 00000000..f93e1149 --- /dev/null +++ b/packages/facebook/RNGoogleMobileAdsAdapterFacebook.podspec @@ -0,0 +1,35 @@ +require 'json' + +package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) +gam_adapter = package['gamAdapter'] +mediation_pod = gam_adapter['iosPod'] +mediation_version = package['sdkVersions']['ios']['googleMobileAdsMediation'] + +Pod::Spec.new do |s| + s.name = "RNGoogleMobileAdsAdapterFacebook" + s.version = package["version"] + s.summary = package["description"] + s.description = package["description"] + s.homepage = "https://github.com/invertase/react-native-google-mobile-ads" + s.license = package["license"] + s.authors = "Invertase Limited" + s.source = { + :git => "#{package["repository"]["url"]}.git", + :tag => "v#{s.version}" + } + # Meta Audience Network mediation adapter 6.22.0.0 requires iOS 15.0+. + # https://developers.google.com/admob/ios/mediation/meta + s.platforms = { :ios => "15.0" } + s.source_files = "ios/**/*.{h,m,mm,swift}" + + if respond_to?(:install_modules_dependencies, true) + install_modules_dependencies(s) + else + s.dependency "React-Core" + end + + # Google-published Meta Audience Network GAM mediation adapter. + # https://developers.google.com/admob/ios/mediation/meta + # https://cocoapods.org/pods/GoogleMobileAdsMediationFacebook + s.dependency mediation_pod, mediation_version +end diff --git a/packages/facebook/__tests__/facebookAdapter.test.ts b/packages/facebook/__tests__/facebookAdapter.test.ts new file mode 100644 index 00000000..dabe1c35 --- /dev/null +++ b/packages/facebook/__tests__/facebookAdapter.test.ts @@ -0,0 +1,20 @@ +import { nativeAdapterClassName, networkSlug } from '../src'; + +describe('@react-native-google-mobile-ads/facebook public surface', () => { + it('exports networkSlug facebook', () => { + expect(networkSlug).toBe('facebook'); + }); + + it('exports documented GAM mediation adapter class names', () => { + expect(nativeAdapterClassName).toEqual({ + android: 'com.google.ads.mediation.facebook.FacebookMediationAdapter', + ios: 'GADMediationAdapterFacebook', + }); + }); + + it('does not invent JS ad APIs on the package surface', () => { + // eslint-disable-next-line @typescript-eslint/no-require-imports + const surface = require('../src') as Record; + expect(Object.keys(surface).sort()).toEqual(['nativeAdapterClassName', 'networkSlug']); + }); +}); diff --git a/packages/facebook/android/build.gradle b/packages/facebook/android/build.gradle new file mode 100644 index 00000000..1d52ef16 --- /dev/null +++ b/packages/facebook/android/build.gradle @@ -0,0 +1,66 @@ +import groovy.json.JsonSlurper + +buildscript { + ext.getExtOrDefault = { name, fallback -> + return rootProject.ext.has(name) ? rootProject.ext.get(name) : fallback + } + + // AGP is only required when opening this android/ folder stand-alone. + if (project == rootProject) { + repositories { + google() + mavenCentral() + } + dependencies { + classpath("com.android.tools.build:gradle:8.1.1") + } + } +} + +apply plugin: "com.android.library" + +def packageJson = new JsonSlurper().parseText(file("${projectDir}/../package.json").text) +def gamAdapter = packageJson.gamAdapter +def sdkVersions = packageJson.sdkVersions.android +def mediationArtifact = "${gamAdapter.androidArtifact}:${sdkVersions.googleMobileAdsMediation}" + +android { + namespace "io.invertase.googlemobileads.adapters.facebook" + + compileSdkVersion sdkVersions.compileSdk as Integer + + defaultConfig { + minSdkVersion sdkVersions.minSdk as Integer + targetSdkVersion sdkVersions.targetSdk as Integer + buildConfigField "String", "GAM_ADAPTER_NETWORK", "\"${gamAdapter.network}\"" + buildConfigField "String", "GAM_ADAPTER_ANDROID_CLASS", "\"${gamAdapter.androidAdapterClassName}\"" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + buildFeatures { + buildConfig true + } + + sourceSets { + main { + java.srcDirs = ["src/main/java"] + } + } +} + +repositories { + google() + mavenCentral() +} + +dependencies { + // Google-published Meta Audience Network GAM mediation adapter. + // Pin: package.json sdkVersions.android.googleMobileAdsMediation (docs: 6.22.0.0 as of package publish; verify against current docs/Maven). + // Core GMA is provided by react-native-google-mobile-ads; do not re-pin it here. + // https://developers.google.com/admob/android/mediation/meta + api mediationArtifact +} diff --git a/packages/facebook/android/src/main/AndroidManifest.xml b/packages/facebook/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..b1dd18ee --- /dev/null +++ b/packages/facebook/android/src/main/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + diff --git a/packages/facebook/android/src/main/java/io/invertase/googlemobileads/adapters/facebook/AdapterPackageMarker.java b/packages/facebook/android/src/main/java/io/invertase/googlemobileads/adapters/facebook/AdapterPackageMarker.java new file mode 100644 index 00000000..f4ad9664 --- /dev/null +++ b/packages/facebook/android/src/main/java/io/invertase/googlemobileads/adapters/facebook/AdapterPackageMarker.java @@ -0,0 +1,10 @@ +package io.invertase.googlemobileads.adapters.facebook; + +/** + * Package marker so the Android library module has a compilable source root. + * This package does not expose a TurboModule — mediation is linked via Gradle + * ({@code com.google.ads.mediation:facebook}). + */ +public final class AdapterPackageMarker { + private AdapterPackageMarker() {} +} diff --git a/packages/facebook/app.plugin.js b/packages/facebook/app.plugin.js new file mode 100644 index 00000000..3c7d11b6 --- /dev/null +++ b/packages/facebook/app.plugin.js @@ -0,0 +1 @@ +module.exports = require('./plugin/build'); diff --git a/packages/facebook/ios/RNGoogleMobileAdsAdapterFacebookMarker.m b/packages/facebook/ios/RNGoogleMobileAdsAdapterFacebookMarker.m new file mode 100644 index 00000000..3f97ca72 --- /dev/null +++ b/packages/facebook/ios/RNGoogleMobileAdsAdapterFacebookMarker.m @@ -0,0 +1,11 @@ +#import + +/** + * Package marker so the podspec has a compilable source file. + * Mediation is linked via the GoogleMobileAdsMediationFacebook pod dependency. + */ +@interface RNGoogleMobileAdsAdapterFacebookMarker : NSObject +@end + +@implementation RNGoogleMobileAdsAdapterFacebookMarker +@end diff --git a/packages/facebook/package.json b/packages/facebook/package.json index 2b032a3f..8e45ef09 100644 --- a/packages/facebook/package.json +++ b/packages/facebook/package.json @@ -1,7 +1,7 @@ { "name": "@react-native-google-mobile-ads/facebook", "version": "16.5.0", - "description": "Empty package shell for the Meta Audience Network (Facebook) Google Mobile Ads (GAM) mediation adapter. Product adapter content is out of scope for this shell (see A-0 / ADAPT-02).", + "description": "Meta Audience Network (Facebook) Google Mobile Ads (GAM) mediation adapter for react-native-google-mobile-ads. Links the official Google mediation adapter on Android and iOS; does not add JS ad APIs.", "author": "Invertase (http://invertase.io)", "license": "Apache-2.0", "repository": { @@ -15,10 +15,116 @@ "admob", "mediation", "gam", - "facebook" + "facebook", + "meta", + "audience-network" + ], + "main": "lib/commonjs/index.js", + "module": "lib/module/index.js", + "react-native": "src/index.ts", + "types": "lib/typescript/commonjs/index.d.ts", + "exports": { + ".": { + "react-native": { + "types": "./lib/typescript/module/index.d.ts", + "default": "./src/index.ts" + }, + "source": { + "types": "./lib/typescript/module/index.d.ts", + "default": "./src/index.ts" + }, + "import": { + "types": "./lib/typescript/module/index.d.ts", + "default": "./lib/module/index.js" + }, + "require": { + "types": "./lib/typescript/commonjs/index.d.ts", + "default": "./lib/commonjs/index.js" + }, + "default": "./lib/commonjs/index.js" + }, + "./app.plugin": "./app.plugin.js", + "./app.plugin.js": "./app.plugin.js", + "./package.json": "./package.json" + }, + "files": [ + "/android/", + "/ios/", + "/lib/", + "/plugin/", + "/src/", + "/LICENSE", + "/RNGoogleMobileAdsAdapterFacebook.podspec", + "/app.plugin.js", + "/react-native.config.js" ], - "files": [], "publishConfig": { "access": "public" + }, + "gamAdapter": { + "network": "facebook", + "androidArtifact": "com.google.ads.mediation:facebook", + "androidAdapterClassName": "com.google.ads.mediation.facebook.FacebookMediationAdapter", + "iosPod": "GoogleMobileAdsMediationFacebook", + "iosAdapterClassName": "GADMediationAdapterFacebook" + }, + "sdkVersions": { + "ios": { + "googleMobileAdsMediation": "6.22.0.0" + }, + "android": { + "minSdk": 23, + "targetSdk": 34, + "compileSdk": 34, + "buildTools": "34.0.0", + "googleMobileAdsMediation": "6.22.0.0" + } + }, + "react-native-builder-bob": { + "source": "src", + "output": "lib", + "targets": [ + [ + "commonjs", + { + "esm": true + } + ], + [ + "module", + { + "esm": true + } + ], + "typescript" + ] + }, + "scripts": { + "prepare": "yarn build && yarn build:plugin", + "build": "bob build", + "build:clean": "rimraf android/build && rimraf ios/build && rimraf lib && rimraf plugin/build", + "build:plugin": "yarn tsc --build plugin" + }, + "peerDependencies": { + "@expo/config-plugins": ">=5.0.0", + "expo": ">=47.0.0", + "react": "*", + "react-native": "*", + "react-native-google-mobile-ads": "*" + }, + "peerDependenciesMeta": { + "@expo/config-plugins": { + "optional": true + }, + "expo": { + "optional": true + } + }, + "devDependencies": { + "@expo/config-plugins": "^54.0.4", + "react-native-builder-bob": "^0.40.13", + "react-native-google-mobile-ads": "workspace:*", + "rimraf": "^6.0.1", + "typescript": "^5.9.3" } } diff --git a/packages/facebook/plugin/src/index.ts b/packages/facebook/plugin/src/index.ts new file mode 100644 index 00000000..f831d5d7 --- /dev/null +++ b/packages/facebook/plugin/src/index.ts @@ -0,0 +1,52 @@ +import { ConfigPlugin, withInfoPlist, withPlugins } from '@expo/config-plugins'; + +type PluginParameters = { + /** + * Meta Audience Network SKAdNetwork identifiers to merge into Info.plist. + * Core's Expo plugin owns app IDs; this adapter only merges mediation network rows. + * Pass identifiers from Meta's current SKAdNetwork documentation at integrate time. + */ + skAdNetworkItems?: string[]; +}; + +const withAdapterSkAdNetworkItems: ConfigPlugin = ( + config, + skAdNetworkItems, +) => { + if (skAdNetworkItems === undefined || skAdNetworkItems.length === 0) { + return config; + } + + return withInfoPlist(config, config => { + config.modResults.SKAdNetworkItems = config.modResults.SKAdNetworkItems ?? []; + + const existingIdentifiers = config.modResults.SKAdNetworkItems.map( + (item: { SKAdNetworkIdentifier: string }) => item.SKAdNetworkIdentifier, + ); + + const missingIdentifiers = skAdNetworkItems.filter( + identifier => !existingIdentifiers.includes(identifier), + ); + + config.modResults.SKAdNetworkItems.push( + ...missingIdentifiers.map(identifier => ({ + SKAdNetworkIdentifier: identifier, + })), + ); + + return config; + }); +}; + +/** + * Optional Expo config plugin for `@react-native-google-mobile-ads/facebook`. + * Does not set GMA app IDs (core plugin). + */ +const withRNGoogleMobileAdsAdapterFacebook: ConfigPlugin = ( + config, + { skAdNetworkItems } = {}, +) => { + return withPlugins(config, [[withAdapterSkAdNetworkItems, skAdNetworkItems]]); +}; + +export default withRNGoogleMobileAdsAdapterFacebook; diff --git a/packages/facebook/plugin/tsconfig.json b/packages/facebook/plugin/tsconfig.json new file mode 100644 index 00000000..354bddb4 --- /dev/null +++ b/packages/facebook/plugin/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "expo-module-scripts/tsconfig.plugin", + "compilerOptions": { + "outDir": "build", + "rootDir": "src" + }, + "include": ["./src"], + "exclude": ["**/__mocks__/*", "**/__tests__/*"] +} diff --git a/packages/facebook/plugin/tsconfig.tsbuildinfo b/packages/facebook/plugin/tsconfig.tsbuildinfo new file mode 100644 index 00000000..0597b1c4 --- /dev/null +++ b/packages/facebook/plugin/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./src/index.ts"],"version":"5.9.3"} \ No newline at end of file diff --git a/packages/facebook/react-native.config.js b/packages/facebook/react-native.config.js new file mode 100644 index 00000000..382dade8 --- /dev/null +++ b/packages/facebook/react-native.config.js @@ -0,0 +1,12 @@ +/** + * Autolinking entry for the GAM adapter package. + * Adapters ship native mediation artifacts only — no TurboModule / Fabric view. + */ +module.exports = { + dependency: { + platforms: { + android: {}, + ios: {}, + }, + }, +}; diff --git a/packages/facebook/src/index.ts b/packages/facebook/src/index.ts new file mode 100644 index 00000000..ff4dcf34 --- /dev/null +++ b/packages/facebook/src/index.ts @@ -0,0 +1,29 @@ +/** + * Meta Audience Network (Facebook) GAM mediation adapter package public surface. + * + * Exports adapter class-name constants for GAM UI paste / docs only. Does **not** + * add JS ad APIs — core `initialize()` adapter statuses remain the discovery + * signal (API design §5.3). + * + * Native mediation is linked via Google's published adapter: + * - Android: `com.google.ads.mediation:facebook` + * - iOS: `GoogleMobileAdsMediationFacebook` + */ + +export type NativeAdapterClassName = { + android: string; + ios: string; +}; + +/** Network slug for this Character GAM adapter package. */ +export const networkSlug = 'facebook' as const; + +/** + * Fully-qualified native mediation adapter class names for AdMob / GAM console paste. + * @see https://developers.google.com/admob/android/mediation/meta + * @see https://developers.google.com/admob/ios/mediation/meta + */ +export const nativeAdapterClassName: NativeAdapterClassName = { + android: 'com.google.ads.mediation.facebook.FacebookMediationAdapter', + ios: 'GADMediationAdapterFacebook', +}; diff --git a/packages/facebook/tsconfig.json b/packages/facebook/tsconfig.json new file mode 100644 index 00000000..a841e12a --- /dev/null +++ b/packages/facebook/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.packages.base.json", + "include": ["src"], + "exclude": ["node_modules", "**/*.spec.ts", "**/*.test.ts"] +} diff --git a/yarn.lock b/yarn.lock index ffab913f..526ea65f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6101,6 +6101,23 @@ __metadata: "@react-native-google-mobile-ads/facebook@workspace:packages/facebook": version: 0.0.0-use.local resolution: "@react-native-google-mobile-ads/facebook@workspace:packages/facebook" + dependencies: + "@expo/config-plugins": "npm:^54.0.4" + react-native-builder-bob: "npm:^0.40.13" + react-native-google-mobile-ads: "workspace:*" + rimraf: "npm:^6.0.1" + typescript: "npm:^5.9.3" + peerDependencies: + "@expo/config-plugins": ">=5.0.0" + expo: ">=47.0.0" + react: "*" + react-native: "*" + react-native-google-mobile-ads: "*" + peerDependenciesMeta: + "@expo/config-plugins": + optional: true + expo: + optional: true languageName: unknown linkType: soft From ace30c61df725820dff1c537a5533c20bbb64ae2 Mon Sep 17 00:00:00 2001 From: Mike Hardy Date: Fri, 4 Sep 2026 10:05:34 -0500 Subject: [PATCH 05/11] feat(adapters): add unity GAM mediation package --- jest.config.js | 1 + packages/unity/LICENSE | 32 +++++ packages/unity/README.md | 68 +++++++++++ .../RNGoogleMobileAdsAdapterUnity.podspec | 35 ++++++ packages/unity/__tests__/unityAdapter.test.ts | 20 ++++ packages/unity/android/build.gradle | 66 +++++++++++ .../android/src/main/AndroidManifest.xml | 6 + .../adapters/unity/AdapterPackageMarker.java | 10 ++ packages/unity/app.plugin.js | 1 + .../ios/RNGoogleMobileAdsAdapterUnityMarker.m | 11 ++ packages/unity/package.json | 111 +++++++++++++++++- packages/unity/plugin/src/index.ts | 52 ++++++++ packages/unity/plugin/tsconfig.json | 9 ++ packages/unity/plugin/tsconfig.tsbuildinfo | 1 + packages/unity/react-native.config.js | 12 ++ packages/unity/src/index.ts | 29 +++++ packages/unity/tsconfig.json | 5 + yarn.lock | 17 +++ 18 files changed, 483 insertions(+), 3 deletions(-) create mode 100644 packages/unity/LICENSE create mode 100644 packages/unity/README.md create mode 100644 packages/unity/RNGoogleMobileAdsAdapterUnity.podspec create mode 100644 packages/unity/__tests__/unityAdapter.test.ts create mode 100644 packages/unity/android/build.gradle create mode 100644 packages/unity/android/src/main/AndroidManifest.xml create mode 100644 packages/unity/android/src/main/java/io/invertase/googlemobileads/adapters/unity/AdapterPackageMarker.java create mode 100644 packages/unity/app.plugin.js create mode 100644 packages/unity/ios/RNGoogleMobileAdsAdapterUnityMarker.m create mode 100644 packages/unity/plugin/src/index.ts create mode 100644 packages/unity/plugin/tsconfig.json create mode 100644 packages/unity/plugin/tsconfig.tsbuildinfo create mode 100644 packages/unity/react-native.config.js create mode 100644 packages/unity/src/index.ts create mode 100644 packages/unity/tsconfig.json diff --git a/jest.config.js b/jest.config.js index ed1bd46e..1dc1f986 100644 --- a/jest.config.js +++ b/jest.config.js @@ -6,6 +6,7 @@ module.exports = { '/packages/_template/lib/', '/packages/applovin/lib/', '/packages/facebook/lib/', + '/packages/unity/lib/', ], setupFiles: ['./jest.setup.ts'], diff --git a/packages/unity/LICENSE b/packages/unity/LICENSE new file mode 100644 index 00000000..db4814fc --- /dev/null +++ b/packages/unity/LICENSE @@ -0,0 +1,32 @@ +Apache-2.0 License +------------------ + +Copyright (c) 2021-present Invertase Limited + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this library except in compliance with the License. + +You may obtain a copy of the Apache-2.0 License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +Creative Commons Attribution 3.0 License +---------------------------------------- + +Copyright (c) 2016-present Invertase Limited + +Documentation and other instructional materials provided for this project +(including on a separate documentation repository or it's documentation website) are +licensed under the Creative Commons Attribution 3.0 License. Code samples/blocks +contained therein are licensed under the Apache License, Version 2.0 (the "License"), as above. + +You may obtain a copy of the Creative Commons Attribution 3.0 License at + + https://creativecommons.org/licenses/by/3.0/ diff --git a/packages/unity/README.md b/packages/unity/README.md new file mode 100644 index 00000000..62132601 --- /dev/null +++ b/packages/unity/README.md @@ -0,0 +1,68 @@ +# `@react-native-google-mobile-ads/unity` + +Unity Ads **Google Ad Manager / AdMob mediation** adapter package for [`react-native-google-mobile-ads`](https://github.com/invertase/react-native-google-mobile-ads). + +This package links Google’s official Unity Ads mediation adapter on Android and iOS. It does **not** ship AppLovin MAX, CloudX, or any JS ad APIs — use core `initialize()` / adapter status for discovery. + +## Install + +```bash +yarn add @react-native-google-mobile-ads/unity +# peer: react-native-google-mobile-ads +``` + +Autolinking pulls in the native mediation artifacts. Rebuild the app after install. + +## Native adapter class names (GAM / AdMob UI) + +| Platform | Class name | +| -------- | ---------- | +| Android | `com.google.ads.mediation.unity.UnityMediationAdapter` | +| iOS | `GADMediationAdapterUnity` | + +Also exported from JS: + +```ts +import { + nativeAdapterClassName, + networkSlug, +} from '@react-native-google-mobile-ads/unity'; +``` + +## Mediation dependencies (pinned in this package) + +| Platform | Coordinate | Version pin | +| -------- | ---------- | ----------- | +| Android | `com.google.ads.mediation:unity` | `4.20.0.1` | +| iOS | CocoaPods `GoogleMobileAdsMediationUnity` | `4.20.0.0` | + +Citations (verify at upgrade time): + +- Android (GAM): https://developers.google.com/ad-manager/mobile-ads-sdk/android/mediation/unity +- iOS (GAM): https://developers.google.com/ad-manager/mobile-ads-sdk/ios/mediation/unity +- CocoaPods: https://cocoapods.org/pods/GoogleMobileAdsMediationUnity + +Core continues to own `play-services-ads` / `Google-Mobile-Ads-SDK`. This package does not re-pin the GMA SDK. + +iOS platform floor is **13.0** (Unity Ads mediation guide / adapter changelog since `4.16.0.0`). + +## Expo (optional) + +```js +// app.json / app.config.js plugins +[ + '@react-native-google-mobile-ads/unity', + { + // Pass Unity Ads SKAdNetwork IDs from Unity’s current docs + skAdNetworkItems: [/* ... */], + }, +] +``` + +App IDs stay in the core Expo plugin. + +## Out of scope + +- AppLovin MAX host SDK +- CloudX / other non-GAM hosts +- Fyber/DT Exchange (no Google GAM adapter — do not invent) diff --git a/packages/unity/RNGoogleMobileAdsAdapterUnity.podspec b/packages/unity/RNGoogleMobileAdsAdapterUnity.podspec new file mode 100644 index 00000000..5392a54f --- /dev/null +++ b/packages/unity/RNGoogleMobileAdsAdapterUnity.podspec @@ -0,0 +1,35 @@ +require 'json' + +package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) +gam_adapter = package['gamAdapter'] +mediation_pod = gam_adapter['iosPod'] +mediation_version = package['sdkVersions']['ios']['googleMobileAdsMediation'] + +Pod::Spec.new do |s| + s.name = "RNGoogleMobileAdsAdapterUnity" + s.version = package["version"] + s.summary = package["description"] + s.description = package["description"] + s.homepage = "https://github.com/invertase/react-native-google-mobile-ads" + s.license = package["license"] + s.authors = "Invertase Limited" + s.source = { + :git => "#{package["repository"]["url"]}.git", + :tag => "v#{s.version}" + } + # Unity Ads mediation adapter requires iOS 13.0+ (guide + changelog since 4.16.0.0). + # https://developers.google.com/ad-manager/mobile-ads-sdk/ios/mediation/unity + s.platforms = { :ios => "13.0" } + s.source_files = "ios/**/*.{h,m,mm,swift}" + + if respond_to?(:install_modules_dependencies, true) + install_modules_dependencies(s) + else + s.dependency "React-Core" + end + + # Google-published Unity Ads GAM mediation adapter. + # https://developers.google.com/ad-manager/mobile-ads-sdk/ios/mediation/unity + # https://cocoapods.org/pods/GoogleMobileAdsMediationUnity + s.dependency mediation_pod, mediation_version +end diff --git a/packages/unity/__tests__/unityAdapter.test.ts b/packages/unity/__tests__/unityAdapter.test.ts new file mode 100644 index 00000000..aab42b0c --- /dev/null +++ b/packages/unity/__tests__/unityAdapter.test.ts @@ -0,0 +1,20 @@ +import { nativeAdapterClassName, networkSlug } from '../src'; + +describe('@react-native-google-mobile-ads/unity public surface', () => { + it('exports networkSlug unity', () => { + expect(networkSlug).toBe('unity'); + }); + + it('exports documented GAM mediation adapter class names', () => { + expect(nativeAdapterClassName).toEqual({ + android: 'com.google.ads.mediation.unity.UnityMediationAdapter', + ios: 'GADMediationAdapterUnity', + }); + }); + + it('does not invent JS ad APIs on the package surface', () => { + // eslint-disable-next-line @typescript-eslint/no-require-imports + const surface = require('../src') as Record; + expect(Object.keys(surface).sort()).toEqual(['nativeAdapterClassName', 'networkSlug']); + }); +}); diff --git a/packages/unity/android/build.gradle b/packages/unity/android/build.gradle new file mode 100644 index 00000000..93ca1caf --- /dev/null +++ b/packages/unity/android/build.gradle @@ -0,0 +1,66 @@ +import groovy.json.JsonSlurper + +buildscript { + ext.getExtOrDefault = { name, fallback -> + return rootProject.ext.has(name) ? rootProject.ext.get(name) : fallback + } + + // AGP is only required when opening this android/ folder stand-alone. + if (project == rootProject) { + repositories { + google() + mavenCentral() + } + dependencies { + classpath("com.android.tools.build:gradle:8.1.1") + } + } +} + +apply plugin: "com.android.library" + +def packageJson = new JsonSlurper().parseText(file("${projectDir}/../package.json").text) +def gamAdapter = packageJson.gamAdapter +def sdkVersions = packageJson.sdkVersions.android +def mediationArtifact = "${gamAdapter.androidArtifact}:${sdkVersions.googleMobileAdsMediation}" + +android { + namespace "io.invertase.googlemobileads.adapters.unity" + + compileSdkVersion sdkVersions.compileSdk as Integer + + defaultConfig { + minSdkVersion sdkVersions.minSdk as Integer + targetSdkVersion sdkVersions.targetSdk as Integer + buildConfigField "String", "GAM_ADAPTER_NETWORK", "\"${gamAdapter.network}\"" + buildConfigField "String", "GAM_ADAPTER_ANDROID_CLASS", "\"${gamAdapter.androidAdapterClassName}\"" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + buildFeatures { + buildConfig true + } + + sourceSets { + main { + java.srcDirs = ["src/main/java"] + } + } +} + +repositories { + google() + mavenCentral() +} + +dependencies { + // Google-published Unity Ads GAM mediation adapter. + // Pin: package.json sdkVersions.android.googleMobileAdsMediation (docs: 4.20.0.1 as of package publish; verify against current docs/Maven). + // Core GMA is provided by react-native-google-mobile-ads; do not re-pin it here. + // https://developers.google.com/ad-manager/mobile-ads-sdk/android/mediation/unity + api mediationArtifact +} diff --git a/packages/unity/android/src/main/AndroidManifest.xml b/packages/unity/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..b1dd18ee --- /dev/null +++ b/packages/unity/android/src/main/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + diff --git a/packages/unity/android/src/main/java/io/invertase/googlemobileads/adapters/unity/AdapterPackageMarker.java b/packages/unity/android/src/main/java/io/invertase/googlemobileads/adapters/unity/AdapterPackageMarker.java new file mode 100644 index 00000000..39a939a7 --- /dev/null +++ b/packages/unity/android/src/main/java/io/invertase/googlemobileads/adapters/unity/AdapterPackageMarker.java @@ -0,0 +1,10 @@ +package io.invertase.googlemobileads.adapters.unity; + +/** + * Package marker so the Android library module has a compilable source root. + * This package does not expose a TurboModule — mediation is linked via Gradle + * ({@code com.google.ads.mediation:unity}). + */ +public final class AdapterPackageMarker { + private AdapterPackageMarker() {} +} diff --git a/packages/unity/app.plugin.js b/packages/unity/app.plugin.js new file mode 100644 index 00000000..3c7d11b6 --- /dev/null +++ b/packages/unity/app.plugin.js @@ -0,0 +1 @@ +module.exports = require('./plugin/build'); diff --git a/packages/unity/ios/RNGoogleMobileAdsAdapterUnityMarker.m b/packages/unity/ios/RNGoogleMobileAdsAdapterUnityMarker.m new file mode 100644 index 00000000..e732661f --- /dev/null +++ b/packages/unity/ios/RNGoogleMobileAdsAdapterUnityMarker.m @@ -0,0 +1,11 @@ +#import + +/** + * Package marker so the podspec has a compilable source file. + * Mediation is linked via the GoogleMobileAdsMediationUnity pod dependency. + */ +@interface RNGoogleMobileAdsAdapterUnityMarker : NSObject +@end + +@implementation RNGoogleMobileAdsAdapterUnityMarker +@end diff --git a/packages/unity/package.json b/packages/unity/package.json index 02ff3bb7..851e75bb 100644 --- a/packages/unity/package.json +++ b/packages/unity/package.json @@ -1,7 +1,7 @@ { "name": "@react-native-google-mobile-ads/unity", "version": "16.5.0", - "description": "Empty package shell for the Unity Ads Google Mobile Ads (GAM) mediation adapter. Product adapter content is out of scope for this shell (see A-0 / ADAPT-02).", + "description": "Unity Ads Google Mobile Ads (GAM) mediation adapter for react-native-google-mobile-ads. Links the official Google mediation adapter on Android and iOS; does not add JS ad APIs.", "author": "Invertase (http://invertase.io)", "license": "Apache-2.0", "repository": { @@ -15,10 +15,115 @@ "admob", "mediation", "gam", - "unity" + "unity", + "unity-ads" + ], + "main": "lib/commonjs/index.js", + "module": "lib/module/index.js", + "react-native": "src/index.ts", + "types": "lib/typescript/commonjs/index.d.ts", + "exports": { + ".": { + "react-native": { + "types": "./lib/typescript/module/index.d.ts", + "default": "./src/index.ts" + }, + "source": { + "types": "./lib/typescript/module/index.d.ts", + "default": "./src/index.ts" + }, + "import": { + "types": "./lib/typescript/module/index.d.ts", + "default": "./lib/module/index.js" + }, + "require": { + "types": "./lib/typescript/commonjs/index.d.ts", + "default": "./lib/commonjs/index.js" + }, + "default": "./lib/commonjs/index.js" + }, + "./app.plugin": "./app.plugin.js", + "./app.plugin.js": "./app.plugin.js", + "./package.json": "./package.json" + }, + "files": [ + "/android/", + "/ios/", + "/lib/", + "/plugin/", + "/src/", + "/LICENSE", + "/RNGoogleMobileAdsAdapterUnity.podspec", + "/app.plugin.js", + "/react-native.config.js" ], - "files": [], "publishConfig": { "access": "public" + }, + "gamAdapter": { + "network": "unity", + "androidArtifact": "com.google.ads.mediation:unity", + "androidAdapterClassName": "com.google.ads.mediation.unity.UnityMediationAdapter", + "iosPod": "GoogleMobileAdsMediationUnity", + "iosAdapterClassName": "GADMediationAdapterUnity" + }, + "sdkVersions": { + "ios": { + "googleMobileAdsMediation": "4.20.0.0" + }, + "android": { + "minSdk": 23, + "targetSdk": 34, + "compileSdk": 34, + "buildTools": "34.0.0", + "googleMobileAdsMediation": "4.20.0.1" + } + }, + "react-native-builder-bob": { + "source": "src", + "output": "lib", + "targets": [ + [ + "commonjs", + { + "esm": true + } + ], + [ + "module", + { + "esm": true + } + ], + "typescript" + ] + }, + "scripts": { + "prepare": "yarn build && yarn build:plugin", + "build": "bob build", + "build:clean": "rimraf android/build && rimraf ios/build && rimraf lib && rimraf plugin/build", + "build:plugin": "yarn tsc --build plugin" + }, + "peerDependencies": { + "@expo/config-plugins": ">=5.0.0", + "expo": ">=47.0.0", + "react": "*", + "react-native": "*", + "react-native-google-mobile-ads": "*" + }, + "peerDependenciesMeta": { + "@expo/config-plugins": { + "optional": true + }, + "expo": { + "optional": true + } + }, + "devDependencies": { + "@expo/config-plugins": "^54.0.4", + "react-native-builder-bob": "^0.40.13", + "react-native-google-mobile-ads": "workspace:*", + "rimraf": "^6.0.1", + "typescript": "^5.9.3" } } diff --git a/packages/unity/plugin/src/index.ts b/packages/unity/plugin/src/index.ts new file mode 100644 index 00000000..5bf8a9ae --- /dev/null +++ b/packages/unity/plugin/src/index.ts @@ -0,0 +1,52 @@ +import { ConfigPlugin, withInfoPlist, withPlugins } from '@expo/config-plugins'; + +type PluginParameters = { + /** + * Unity Ads SKAdNetwork identifiers to merge into Info.plist. + * Core's Expo plugin owns app IDs; this adapter only merges mediation network rows. + * Pass identifiers from Unity Ads's current SKAdNetwork documentation at integrate time. + */ + skAdNetworkItems?: string[]; +}; + +const withAdapterSkAdNetworkItems: ConfigPlugin = ( + config, + skAdNetworkItems, +) => { + if (skAdNetworkItems === undefined || skAdNetworkItems.length === 0) { + return config; + } + + return withInfoPlist(config, config => { + config.modResults.SKAdNetworkItems = config.modResults.SKAdNetworkItems ?? []; + + const existingIdentifiers = config.modResults.SKAdNetworkItems.map( + (item: { SKAdNetworkIdentifier: string }) => item.SKAdNetworkIdentifier, + ); + + const missingIdentifiers = skAdNetworkItems.filter( + identifier => !existingIdentifiers.includes(identifier), + ); + + config.modResults.SKAdNetworkItems.push( + ...missingIdentifiers.map(identifier => ({ + SKAdNetworkIdentifier: identifier, + })), + ); + + return config; + }); +}; + +/** + * Optional Expo config plugin for `@react-native-google-mobile-ads/unity`. + * Does not set GMA app IDs (core plugin). + */ +const withRNGoogleMobileAdsAdapterUnity: ConfigPlugin = ( + config, + { skAdNetworkItems } = {}, +) => { + return withPlugins(config, [[withAdapterSkAdNetworkItems, skAdNetworkItems]]); +}; + +export default withRNGoogleMobileAdsAdapterUnity; diff --git a/packages/unity/plugin/tsconfig.json b/packages/unity/plugin/tsconfig.json new file mode 100644 index 00000000..354bddb4 --- /dev/null +++ b/packages/unity/plugin/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "expo-module-scripts/tsconfig.plugin", + "compilerOptions": { + "outDir": "build", + "rootDir": "src" + }, + "include": ["./src"], + "exclude": ["**/__mocks__/*", "**/__tests__/*"] +} diff --git a/packages/unity/plugin/tsconfig.tsbuildinfo b/packages/unity/plugin/tsconfig.tsbuildinfo new file mode 100644 index 00000000..0597b1c4 --- /dev/null +++ b/packages/unity/plugin/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./src/index.ts"],"version":"5.9.3"} \ No newline at end of file diff --git a/packages/unity/react-native.config.js b/packages/unity/react-native.config.js new file mode 100644 index 00000000..382dade8 --- /dev/null +++ b/packages/unity/react-native.config.js @@ -0,0 +1,12 @@ +/** + * Autolinking entry for the GAM adapter package. + * Adapters ship native mediation artifacts only — no TurboModule / Fabric view. + */ +module.exports = { + dependency: { + platforms: { + android: {}, + ios: {}, + }, + }, +}; diff --git a/packages/unity/src/index.ts b/packages/unity/src/index.ts new file mode 100644 index 00000000..c4de4d01 --- /dev/null +++ b/packages/unity/src/index.ts @@ -0,0 +1,29 @@ +/** + * Unity Ads GAM mediation adapter package public surface. + * + * Exports adapter class-name constants for GAM UI paste / docs only. Does **not** + * add JS ad APIs — core `initialize()` adapter statuses remain the discovery + * signal (API design §5.3). + * + * Native mediation is linked via Google's published adapter: + * - Android: `com.google.ads.mediation:unity` + * - iOS: `GoogleMobileAdsMediationUnity` + */ + +export type NativeAdapterClassName = { + android: string; + ios: string; +}; + +/** Network slug for this Character GAM adapter package. */ +export const networkSlug = 'unity' as const; + +/** + * Fully-qualified native mediation adapter class names for AdMob / GAM console paste. + * @see https://developers.google.com/ad-manager/mobile-ads-sdk/android/mediation/unity + * @see https://developers.google.com/ad-manager/mobile-ads-sdk/ios/mediation/unity + */ +export const nativeAdapterClassName: NativeAdapterClassName = { + android: 'com.google.ads.mediation.unity.UnityMediationAdapter', + ios: 'GADMediationAdapterUnity', +}; diff --git a/packages/unity/tsconfig.json b/packages/unity/tsconfig.json new file mode 100644 index 00000000..a841e12a --- /dev/null +++ b/packages/unity/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.packages.base.json", + "include": ["src"], + "exclude": ["node_modules", "**/*.spec.ts", "**/*.test.ts"] +} diff --git a/yarn.lock b/yarn.lock index 526ea65f..f5b6951c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6148,6 +6148,23 @@ __metadata: "@react-native-google-mobile-ads/unity@workspace:packages/unity": version: 0.0.0-use.local resolution: "@react-native-google-mobile-ads/unity@workspace:packages/unity" + dependencies: + "@expo/config-plugins": "npm:^54.0.4" + react-native-builder-bob: "npm:^0.40.13" + react-native-google-mobile-ads: "workspace:*" + rimraf: "npm:^6.0.1" + typescript: "npm:^5.9.3" + peerDependencies: + "@expo/config-plugins": ">=5.0.0" + expo: ">=47.0.0" + react: "*" + react-native: "*" + react-native-google-mobile-ads: "*" + peerDependenciesMeta: + "@expo/config-plugins": + optional: true + expo: + optional: true languageName: unknown linkType: soft From 0fd524d1ec57b1088c3cdca0b1678e33a93fb4d9 Mon Sep 17 00:00:00 2001 From: Mike Hardy Date: Fri, 4 Sep 2026 10:12:32 -0500 Subject: [PATCH 06/11] feat(adapters): add pangle GAM mediation package --- jest.config.js | 1 + packages/pangle/LICENSE | 32 +++++ packages/pangle/README.md | 78 ++++++++++++ .../RNGoogleMobileAdsAdapterPangle.podspec | 35 ++++++ .../pangle/__tests__/pangleAdapter.test.ts | 20 ++++ packages/pangle/android/build.gradle | 72 ++++++++++++ .../android/src/main/AndroidManifest.xml | 6 + .../adapters/pangle/AdapterPackageMarker.java | 10 ++ packages/pangle/app.plugin.js | 1 + .../RNGoogleMobileAdsAdapterPangleMarker.m | 11 ++ packages/pangle/package.json | 111 +++++++++++++++++- packages/pangle/plugin/src/index.ts | 52 ++++++++ packages/pangle/plugin/tsconfig.json | 9 ++ packages/pangle/plugin/tsconfig.tsbuildinfo | 1 + packages/pangle/react-native.config.js | 12 ++ packages/pangle/src/index.ts | 29 +++++ packages/pangle/tsconfig.json | 5 + yarn.lock | 17 +++ 18 files changed, 499 insertions(+), 3 deletions(-) create mode 100644 packages/pangle/LICENSE create mode 100644 packages/pangle/README.md create mode 100644 packages/pangle/RNGoogleMobileAdsAdapterPangle.podspec create mode 100644 packages/pangle/__tests__/pangleAdapter.test.ts create mode 100644 packages/pangle/android/build.gradle create mode 100644 packages/pangle/android/src/main/AndroidManifest.xml create mode 100644 packages/pangle/android/src/main/java/io/invertase/googlemobileads/adapters/pangle/AdapterPackageMarker.java create mode 100644 packages/pangle/app.plugin.js create mode 100644 packages/pangle/ios/RNGoogleMobileAdsAdapterPangleMarker.m create mode 100644 packages/pangle/plugin/src/index.ts create mode 100644 packages/pangle/plugin/tsconfig.json create mode 100644 packages/pangle/plugin/tsconfig.tsbuildinfo create mode 100644 packages/pangle/react-native.config.js create mode 100644 packages/pangle/src/index.ts create mode 100644 packages/pangle/tsconfig.json diff --git a/jest.config.js b/jest.config.js index 1dc1f986..d907cadc 100644 --- a/jest.config.js +++ b/jest.config.js @@ -7,6 +7,7 @@ module.exports = { '/packages/applovin/lib/', '/packages/facebook/lib/', '/packages/unity/lib/', + '/packages/pangle/lib/', ], setupFiles: ['./jest.setup.ts'], diff --git a/packages/pangle/LICENSE b/packages/pangle/LICENSE new file mode 100644 index 00000000..db4814fc --- /dev/null +++ b/packages/pangle/LICENSE @@ -0,0 +1,32 @@ +Apache-2.0 License +------------------ + +Copyright (c) 2021-present Invertase Limited + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this library except in compliance with the License. + +You may obtain a copy of the Apache-2.0 License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +Creative Commons Attribution 3.0 License +---------------------------------------- + +Copyright (c) 2016-present Invertase Limited + +Documentation and other instructional materials provided for this project +(including on a separate documentation repository or it's documentation website) are +licensed under the Creative Commons Attribution 3.0 License. Code samples/blocks +contained therein are licensed under the Apache License, Version 2.0 (the "License"), as above. + +You may obtain a copy of the Creative Commons Attribution 3.0 License at + + https://creativecommons.org/licenses/by/3.0/ diff --git a/packages/pangle/README.md b/packages/pangle/README.md new file mode 100644 index 00000000..50900931 --- /dev/null +++ b/packages/pangle/README.md @@ -0,0 +1,78 @@ +# `@react-native-google-mobile-ads/pangle` + +Pangle (ByteDance) **Google Ad Manager / AdMob mediation** adapter package for [`react-native-google-mobile-ads`](https://github.com/invertase/react-native-google-mobile-ads). + +This package links Google’s official Pangle mediation adapter on Android and iOS. It does **not** ship AppLovin MAX, CloudX, or any JS ad APIs — use core `initialize()` / adapter status for discovery. + +## Install + +```bash +yarn add @react-native-google-mobile-ads/pangle +# peer: react-native-google-mobile-ads +``` + +Autolinking pulls in the native mediation artifacts. Rebuild the app after install. + +### Android Maven repository (required) + +Google’s Pangle GAM adapter depends on `com.pangle.global:pag-sdk`, which is hosted on ByteDance Maven (not Maven Central). This package adds that repository in its Android module. If your app uses `dependencyResolutionManagement` / `FAIL_ON_PROJECT_REPOS`, also add the repo in your project `settings.gradle(.kts)` as shown in Google’s guide: + +```kotlin +maven { + url = uri("https://artifact.bytedance.com/repository/pangle/") +} +``` + +## Native adapter class names (GAM / AdMob UI) + +| Platform | Class name | +| -------- | ---------- | +| Android | `com.google.ads.mediation.pangle.PangleMediationAdapter` | +| iOS | `GADMediationAdapterPangle` | + +Also exported from JS: + +```ts +import { + nativeAdapterClassName, + networkSlug, +} from '@react-native-google-mobile-ads/pangle'; +``` + +## Mediation dependencies (pinned in this package) + +| Platform | Coordinate | Version pin | +| -------- | ---------- | ----------- | +| Android | `com.google.ads.mediation:pangle` | `8.2.0.4.0` | +| iOS | CocoaPods `GoogleMobileAdsMediationPangle` | `8.2.1.0.0` | + +Citations (verify at upgrade time): + +- Android (GAM): https://developers.google.com/ad-manager/mobile-ads-sdk/android/mediation/pangle +- iOS (GAM): https://developers.google.com/ad-manager/mobile-ads-sdk/ios/mediation/pangle +- CocoaPods: https://cocoapods.org/pods/GoogleMobileAdsMediationPangle + +Core continues to own `play-services-ads` / `Google-Mobile-Ads-SDK`. This package does not re-pin the GMA SDK. + +iOS platform floor is **13.0** (Pangle mediation guide prerequisites / CocoaPods podspec). + +## Expo (optional) + +```js +// app.json / app.config.js plugins +[ + '@react-native-google-mobile-ads/pangle', + { + // Pass Pangle SKAdNetwork IDs from Pangle’s current docs + skAdNetworkItems: [/* ... */], + }, +] +``` + +App IDs stay in the core Expo plugin. + +## Out of scope + +- AppLovin MAX host SDK +- CloudX / other non-GAM hosts +- Fyber/DT Exchange (no Google GAM adapter — do not invent) diff --git a/packages/pangle/RNGoogleMobileAdsAdapterPangle.podspec b/packages/pangle/RNGoogleMobileAdsAdapterPangle.podspec new file mode 100644 index 00000000..2da4edf1 --- /dev/null +++ b/packages/pangle/RNGoogleMobileAdsAdapterPangle.podspec @@ -0,0 +1,35 @@ +require 'json' + +package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) +gam_adapter = package['gamAdapter'] +mediation_pod = gam_adapter['iosPod'] +mediation_version = package['sdkVersions']['ios']['googleMobileAdsMediation'] + +Pod::Spec.new do |s| + s.name = "RNGoogleMobileAdsAdapterPangle" + s.version = package["version"] + s.summary = package["description"] + s.description = package["description"] + s.homepage = "https://github.com/invertase/react-native-google-mobile-ads" + s.license = package["license"] + s.authors = "Invertase Limited" + s.source = { + :git => "#{package["repository"]["url"]}.git", + :tag => "v#{s.version}" + } + # Pangle mediation adapter requires iOS 13.0+ (guide prerequisites + CocoaPods podspec). + # https://developers.google.com/ad-manager/mobile-ads-sdk/ios/mediation/pangle + s.platforms = { :ios => "13.0" } + s.source_files = "ios/**/*.{h,m,mm,swift}" + + if respond_to?(:install_modules_dependencies, true) + install_modules_dependencies(s) + else + s.dependency "React-Core" + end + + # Google-published Pangle GAM mediation adapter. + # https://developers.google.com/ad-manager/mobile-ads-sdk/ios/mediation/pangle + # https://cocoapods.org/pods/GoogleMobileAdsMediationPangle + s.dependency mediation_pod, mediation_version +end diff --git a/packages/pangle/__tests__/pangleAdapter.test.ts b/packages/pangle/__tests__/pangleAdapter.test.ts new file mode 100644 index 00000000..3fc096da --- /dev/null +++ b/packages/pangle/__tests__/pangleAdapter.test.ts @@ -0,0 +1,20 @@ +import { nativeAdapterClassName, networkSlug } from '../src'; + +describe('@react-native-google-mobile-ads/pangle public surface', () => { + it('exports networkSlug pangle', () => { + expect(networkSlug).toBe('pangle'); + }); + + it('exports documented GAM mediation adapter class names', () => { + expect(nativeAdapterClassName).toEqual({ + android: 'com.google.ads.mediation.pangle.PangleMediationAdapter', + ios: 'GADMediationAdapterPangle', + }); + }); + + it('does not invent JS ad APIs on the package surface', () => { + // eslint-disable-next-line @typescript-eslint/no-require-imports + const surface = require('../src') as Record; + expect(Object.keys(surface).sort()).toEqual(['nativeAdapterClassName', 'networkSlug']); + }); +}); diff --git a/packages/pangle/android/build.gradle b/packages/pangle/android/build.gradle new file mode 100644 index 00000000..53c1e7e7 --- /dev/null +++ b/packages/pangle/android/build.gradle @@ -0,0 +1,72 @@ +import groovy.json.JsonSlurper + +buildscript { + ext.getExtOrDefault = { name, fallback -> + return rootProject.ext.has(name) ? rootProject.ext.get(name) : fallback + } + + // AGP is only required when opening this android/ folder stand-alone. + if (project == rootProject) { + repositories { + google() + mavenCentral() + } + dependencies { + classpath("com.android.tools.build:gradle:8.1.1") + } + } +} + +apply plugin: "com.android.library" + +def packageJson = new JsonSlurper().parseText(file("${projectDir}/../package.json").text) +def gamAdapter = packageJson.gamAdapter +def sdkVersions = packageJson.sdkVersions.android +def mediationArtifact = "${gamAdapter.androidArtifact}:${sdkVersions.googleMobileAdsMediation}" + +android { + namespace "io.invertase.googlemobileads.adapters.pangle" + + compileSdkVersion sdkVersions.compileSdk as Integer + + defaultConfig { + minSdkVersion sdkVersions.minSdk as Integer + targetSdkVersion sdkVersions.targetSdk as Integer + buildConfigField "String", "GAM_ADAPTER_NETWORK", "\"${gamAdapter.network}\"" + buildConfigField "String", "GAM_ADAPTER_ANDROID_CLASS", "\"${gamAdapter.androidAdapterClassName}\"" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + buildFeatures { + buildConfig true + } + + sourceSets { + main { + java.srcDirs = ["src/main/java"] + } + } +} + +repositories { + google() + mavenCentral() + // Pangle SDK (`com.pangle.global:pag-sdk`) is hosted on ByteDance Maven — required by + // Google's GAM Pangle mediation guide Step 3. + // https://developers.google.com/ad-manager/mobile-ads-sdk/android/mediation/pangle + maven { + url = uri("https://artifact.bytedance.com/repository/pangle/") + } +} + +dependencies { + // Google-published Pangle GAM mediation adapter. + // Pin: package.json sdkVersions.android.googleMobileAdsMediation (docs: 8.2.0.4.0 as of package publish; verify against current docs/Maven). + // Core GMA is provided by react-native-google-mobile-ads; do not re-pin it here. + // https://developers.google.com/ad-manager/mobile-ads-sdk/android/mediation/pangle + api mediationArtifact +} diff --git a/packages/pangle/android/src/main/AndroidManifest.xml b/packages/pangle/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..b1dd18ee --- /dev/null +++ b/packages/pangle/android/src/main/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + diff --git a/packages/pangle/android/src/main/java/io/invertase/googlemobileads/adapters/pangle/AdapterPackageMarker.java b/packages/pangle/android/src/main/java/io/invertase/googlemobileads/adapters/pangle/AdapterPackageMarker.java new file mode 100644 index 00000000..0ccbad90 --- /dev/null +++ b/packages/pangle/android/src/main/java/io/invertase/googlemobileads/adapters/pangle/AdapterPackageMarker.java @@ -0,0 +1,10 @@ +package io.invertase.googlemobileads.adapters.pangle; + +/** + * Package marker so the Android library module has a compilable source root. + * This package does not expose a TurboModule — mediation is linked via Gradle + * ({@code com.google.ads.mediation:pangle}). + */ +public final class AdapterPackageMarker { + private AdapterPackageMarker() {} +} diff --git a/packages/pangle/app.plugin.js b/packages/pangle/app.plugin.js new file mode 100644 index 00000000..3c7d11b6 --- /dev/null +++ b/packages/pangle/app.plugin.js @@ -0,0 +1 @@ +module.exports = require('./plugin/build'); diff --git a/packages/pangle/ios/RNGoogleMobileAdsAdapterPangleMarker.m b/packages/pangle/ios/RNGoogleMobileAdsAdapterPangleMarker.m new file mode 100644 index 00000000..dfb434d3 --- /dev/null +++ b/packages/pangle/ios/RNGoogleMobileAdsAdapterPangleMarker.m @@ -0,0 +1,11 @@ +#import + +/** + * Package marker so the podspec has a compilable source file. + * Mediation is linked via the GoogleMobileAdsMediationPangle pod dependency. + */ +@interface RNGoogleMobileAdsAdapterPangleMarker : NSObject +@end + +@implementation RNGoogleMobileAdsAdapterPangleMarker +@end diff --git a/packages/pangle/package.json b/packages/pangle/package.json index 1c5acbe5..c09c5914 100644 --- a/packages/pangle/package.json +++ b/packages/pangle/package.json @@ -1,7 +1,7 @@ { "name": "@react-native-google-mobile-ads/pangle", "version": "16.5.0", - "description": "Empty package shell for the Pangle (ByteDance) Google Mobile Ads (GAM) mediation adapter. Product adapter content is out of scope for this shell (see A-0 / ADAPT-02).", + "description": "Pangle (ByteDance) Google Mobile Ads (GAM) mediation adapter for react-native-google-mobile-ads. Links the official Google mediation adapter on Android and iOS; does not add JS ad APIs.", "author": "Invertase (http://invertase.io)", "license": "Apache-2.0", "repository": { @@ -15,10 +15,115 @@ "admob", "mediation", "gam", - "pangle" + "pangle", + "bytedance" + ], + "main": "lib/commonjs/index.js", + "module": "lib/module/index.js", + "react-native": "src/index.ts", + "types": "lib/typescript/commonjs/index.d.ts", + "exports": { + ".": { + "react-native": { + "types": "./lib/typescript/module/index.d.ts", + "default": "./src/index.ts" + }, + "source": { + "types": "./lib/typescript/module/index.d.ts", + "default": "./src/index.ts" + }, + "import": { + "types": "./lib/typescript/module/index.d.ts", + "default": "./lib/module/index.js" + }, + "require": { + "types": "./lib/typescript/commonjs/index.d.ts", + "default": "./lib/commonjs/index.js" + }, + "default": "./lib/commonjs/index.js" + }, + "./app.plugin": "./app.plugin.js", + "./app.plugin.js": "./app.plugin.js", + "./package.json": "./package.json" + }, + "files": [ + "/android/", + "/ios/", + "/lib/", + "/plugin/", + "/src/", + "/LICENSE", + "/RNGoogleMobileAdsAdapterPangle.podspec", + "/app.plugin.js", + "/react-native.config.js" ], - "files": [], "publishConfig": { "access": "public" + }, + "gamAdapter": { + "network": "pangle", + "androidArtifact": "com.google.ads.mediation:pangle", + "androidAdapterClassName": "com.google.ads.mediation.pangle.PangleMediationAdapter", + "iosPod": "GoogleMobileAdsMediationPangle", + "iosAdapterClassName": "GADMediationAdapterPangle" + }, + "sdkVersions": { + "ios": { + "googleMobileAdsMediation": "8.2.1.0.0" + }, + "android": { + "minSdk": 23, + "targetSdk": 34, + "compileSdk": 34, + "buildTools": "34.0.0", + "googleMobileAdsMediation": "8.2.0.4.0" + } + }, + "react-native-builder-bob": { + "source": "src", + "output": "lib", + "targets": [ + [ + "commonjs", + { + "esm": true + } + ], + [ + "module", + { + "esm": true + } + ], + "typescript" + ] + }, + "scripts": { + "prepare": "yarn build && yarn build:plugin", + "build": "bob build", + "build:clean": "rimraf android/build && rimraf ios/build && rimraf lib && rimraf plugin/build", + "build:plugin": "yarn tsc --build plugin" + }, + "peerDependencies": { + "@expo/config-plugins": ">=5.0.0", + "expo": ">=47.0.0", + "react": "*", + "react-native": "*", + "react-native-google-mobile-ads": "*" + }, + "peerDependenciesMeta": { + "@expo/config-plugins": { + "optional": true + }, + "expo": { + "optional": true + } + }, + "devDependencies": { + "@expo/config-plugins": "^54.0.4", + "react-native-builder-bob": "^0.40.13", + "react-native-google-mobile-ads": "workspace:*", + "rimraf": "^6.0.1", + "typescript": "^5.9.3" } } diff --git a/packages/pangle/plugin/src/index.ts b/packages/pangle/plugin/src/index.ts new file mode 100644 index 00000000..cd6aa367 --- /dev/null +++ b/packages/pangle/plugin/src/index.ts @@ -0,0 +1,52 @@ +import { ConfigPlugin, withInfoPlist, withPlugins } from '@expo/config-plugins'; + +type PluginParameters = { + /** + * Pangle SKAdNetwork identifiers to merge into Info.plist. + * Core's Expo plugin owns app IDs; this adapter only merges mediation network rows. + * Pass identifiers from Pangle's current SKAdNetwork documentation at integrate time. + */ + skAdNetworkItems?: string[]; +}; + +const withAdapterSkAdNetworkItems: ConfigPlugin = ( + config, + skAdNetworkItems, +) => { + if (skAdNetworkItems === undefined || skAdNetworkItems.length === 0) { + return config; + } + + return withInfoPlist(config, config => { + config.modResults.SKAdNetworkItems = config.modResults.SKAdNetworkItems ?? []; + + const existingIdentifiers = config.modResults.SKAdNetworkItems.map( + (item: { SKAdNetworkIdentifier: string }) => item.SKAdNetworkIdentifier, + ); + + const missingIdentifiers = skAdNetworkItems.filter( + identifier => !existingIdentifiers.includes(identifier), + ); + + config.modResults.SKAdNetworkItems.push( + ...missingIdentifiers.map(identifier => ({ + SKAdNetworkIdentifier: identifier, + })), + ); + + return config; + }); +}; + +/** + * Optional Expo config plugin for `@react-native-google-mobile-ads/pangle`. + * Does not set GMA app IDs (core plugin). + */ +const withRNGoogleMobileAdsAdapterPangle: ConfigPlugin = ( + config, + { skAdNetworkItems } = {}, +) => { + return withPlugins(config, [[withAdapterSkAdNetworkItems, skAdNetworkItems]]); +}; + +export default withRNGoogleMobileAdsAdapterPangle; diff --git a/packages/pangle/plugin/tsconfig.json b/packages/pangle/plugin/tsconfig.json new file mode 100644 index 00000000..354bddb4 --- /dev/null +++ b/packages/pangle/plugin/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "expo-module-scripts/tsconfig.plugin", + "compilerOptions": { + "outDir": "build", + "rootDir": "src" + }, + "include": ["./src"], + "exclude": ["**/__mocks__/*", "**/__tests__/*"] +} diff --git a/packages/pangle/plugin/tsconfig.tsbuildinfo b/packages/pangle/plugin/tsconfig.tsbuildinfo new file mode 100644 index 00000000..0597b1c4 --- /dev/null +++ b/packages/pangle/plugin/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./src/index.ts"],"version":"5.9.3"} \ No newline at end of file diff --git a/packages/pangle/react-native.config.js b/packages/pangle/react-native.config.js new file mode 100644 index 00000000..382dade8 --- /dev/null +++ b/packages/pangle/react-native.config.js @@ -0,0 +1,12 @@ +/** + * Autolinking entry for the GAM adapter package. + * Adapters ship native mediation artifacts only — no TurboModule / Fabric view. + */ +module.exports = { + dependency: { + platforms: { + android: {}, + ios: {}, + }, + }, +}; diff --git a/packages/pangle/src/index.ts b/packages/pangle/src/index.ts new file mode 100644 index 00000000..fa30d3b6 --- /dev/null +++ b/packages/pangle/src/index.ts @@ -0,0 +1,29 @@ +/** + * Pangle (ByteDance) GAM mediation adapter package public surface. + * + * Exports adapter class-name constants for GAM UI paste / docs only. Does **not** + * add JS ad APIs — core `initialize()` adapter statuses remain the discovery + * signal (API design §5.3). + * + * Native mediation is linked via Google's published adapter: + * - Android: `com.google.ads.mediation:pangle` + * - iOS: `GoogleMobileAdsMediationPangle` + */ + +export type NativeAdapterClassName = { + android: string; + ios: string; +}; + +/** Network slug for this Character GAM adapter package. */ +export const networkSlug = 'pangle' as const; + +/** + * Fully-qualified native mediation adapter class names for AdMob / GAM console paste. + * @see https://developers.google.com/ad-manager/mobile-ads-sdk/android/mediation/pangle + * @see https://developers.google.com/ad-manager/mobile-ads-sdk/ios/mediation/pangle + */ +export const nativeAdapterClassName: NativeAdapterClassName = { + android: 'com.google.ads.mediation.pangle.PangleMediationAdapter', + ios: 'GADMediationAdapterPangle', +}; diff --git a/packages/pangle/tsconfig.json b/packages/pangle/tsconfig.json new file mode 100644 index 00000000..a841e12a --- /dev/null +++ b/packages/pangle/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.packages.base.json", + "include": ["src"], + "exclude": ["node_modules", "**/*.spec.ts", "**/*.test.ts"] +} diff --git a/yarn.lock b/yarn.lock index f5b6951c..9f60178a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6142,6 +6142,23 @@ __metadata: "@react-native-google-mobile-ads/pangle@workspace:packages/pangle": version: 0.0.0-use.local resolution: "@react-native-google-mobile-ads/pangle@workspace:packages/pangle" + dependencies: + "@expo/config-plugins": "npm:^54.0.4" + react-native-builder-bob: "npm:^0.40.13" + react-native-google-mobile-ads: "workspace:*" + rimraf: "npm:^6.0.1" + typescript: "npm:^5.9.3" + peerDependencies: + "@expo/config-plugins": ">=5.0.0" + expo: ">=47.0.0" + react: "*" + react-native: "*" + react-native-google-mobile-ads: "*" + peerDependenciesMeta: + "@expo/config-plugins": + optional: true + expo: + optional: true languageName: unknown linkType: soft From 60456071fae733ca37ef0107585fa82e14e7476d Mon Sep 17 00:00:00 2001 From: Mike Hardy Date: Fri, 4 Sep 2026 10:21:18 -0500 Subject: [PATCH 07/11] feat(adapters): add vungle GAM mediation package --- jest.config.js | 1 + packages/vungle/LICENSE | 32 +++++ packages/vungle/README.md | 69 +++++++++++ .../RNGoogleMobileAdsAdapterVungle.podspec | 35 ++++++ .../vungle/__tests__/vungleAdapter.test.ts | 20 ++++ packages/vungle/android/build.gradle | 66 +++++++++++ .../android/src/main/AndroidManifest.xml | 6 + .../adapters/vungle/AdapterPackageMarker.java | 10 ++ packages/vungle/app.plugin.js | 1 + .../RNGoogleMobileAdsAdapterVungleMarker.m | 11 ++ packages/vungle/package.json | 112 +++++++++++++++++- packages/vungle/plugin/src/index.ts | 52 ++++++++ packages/vungle/plugin/tsconfig.json | 9 ++ packages/vungle/plugin/tsconfig.tsbuildinfo | 1 + packages/vungle/react-native.config.js | 12 ++ packages/vungle/src/index.ts | 29 +++++ packages/vungle/tsconfig.json | 5 + yarn.lock | 17 +++ 18 files changed, 485 insertions(+), 3 deletions(-) create mode 100644 packages/vungle/LICENSE create mode 100644 packages/vungle/README.md create mode 100644 packages/vungle/RNGoogleMobileAdsAdapterVungle.podspec create mode 100644 packages/vungle/__tests__/vungleAdapter.test.ts create mode 100644 packages/vungle/android/build.gradle create mode 100644 packages/vungle/android/src/main/AndroidManifest.xml create mode 100644 packages/vungle/android/src/main/java/io/invertase/googlemobileads/adapters/vungle/AdapterPackageMarker.java create mode 100644 packages/vungle/app.plugin.js create mode 100644 packages/vungle/ios/RNGoogleMobileAdsAdapterVungleMarker.m create mode 100644 packages/vungle/plugin/src/index.ts create mode 100644 packages/vungle/plugin/tsconfig.json create mode 100644 packages/vungle/plugin/tsconfig.tsbuildinfo create mode 100644 packages/vungle/react-native.config.js create mode 100644 packages/vungle/src/index.ts create mode 100644 packages/vungle/tsconfig.json diff --git a/jest.config.js b/jest.config.js index d907cadc..a8e87c0a 100644 --- a/jest.config.js +++ b/jest.config.js @@ -8,6 +8,7 @@ module.exports = { '/packages/facebook/lib/', '/packages/unity/lib/', '/packages/pangle/lib/', + '/packages/vungle/lib/', ], setupFiles: ['./jest.setup.ts'], diff --git a/packages/vungle/LICENSE b/packages/vungle/LICENSE new file mode 100644 index 00000000..db4814fc --- /dev/null +++ b/packages/vungle/LICENSE @@ -0,0 +1,32 @@ +Apache-2.0 License +------------------ + +Copyright (c) 2021-present Invertase Limited + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this library except in compliance with the License. + +You may obtain a copy of the Apache-2.0 License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +Creative Commons Attribution 3.0 License +---------------------------------------- + +Copyright (c) 2016-present Invertase Limited + +Documentation and other instructional materials provided for this project +(including on a separate documentation repository or it's documentation website) are +licensed under the Creative Commons Attribution 3.0 License. Code samples/blocks +contained therein are licensed under the Apache License, Version 2.0 (the "License"), as above. + +You may obtain a copy of the Creative Commons Attribution 3.0 License at + + https://creativecommons.org/licenses/by/3.0/ diff --git a/packages/vungle/README.md b/packages/vungle/README.md new file mode 100644 index 00000000..e394a1e3 --- /dev/null +++ b/packages/vungle/README.md @@ -0,0 +1,69 @@ +# `@react-native-google-mobile-ads/vungle` + +Liftoff Monetize (formerly Vungle) **Google Ad Manager / AdMob mediation** adapter package for [`react-native-google-mobile-ads`](https://github.com/invertase/react-native-google-mobile-ads). + +This package links Google’s official Liftoff Monetize / Vungle mediation adapter on Android and iOS. It does **not** ship AppLovin MAX, CloudX, or any JS ad APIs — use core `initialize()` / adapter status for discovery. + +## Install + +```bash +yarn add @react-native-google-mobile-ads/vungle +# peer: react-native-google-mobile-ads +``` + +Autolinking pulls in the native mediation artifacts. Rebuild the app after install. + +## Native adapter class names (GAM / AdMob UI) + +| Platform | Class name | +| -------- | ---------- | +| Android | `com.google.ads.mediation.vungle.VungleMediationAdapter` | +| iOS | `GADMediationAdapterVungle` | + +Also exported from JS: + +```ts +import { + nativeAdapterClassName, + networkSlug, +} from '@react-native-google-mobile-ads/vungle'; +``` + +## Mediation dependencies (pinned in this package) + +| Platform | Coordinate | Version pin | +| -------- | ---------- | ----------- | +| Android | `com.google.ads.mediation:vungle` | `7.7.8.0` | +| iOS | CocoaPods `GoogleMobileAdsMediationVungle` | `7.7.7.0` | + +Citations (verify at upgrade time): + +- Android (GAM): https://developers.google.com/ad-manager/mobile-ads-sdk/android/mediation/liftoff-monetize +- iOS (GAM): https://developers.google.com/ad-manager/mobile-ads-sdk/ios/mediation/liftoff-monetize +- Google Maven: `com.google.ads.mediation:vungle` (`latest` / `release` = `7.7.8.0`) +- CocoaPods: https://cocoapods.org/pods/GoogleMobileAdsMediationVungle + +Core continues to own `play-services-ads` / `Google-Mobile-Ads-SDK`. This package does not re-pin the GMA SDK. + +iOS platform floor is **13.0** (Liftoff Monetize mediation guide prerequisites / CocoaPods podspec). + +## Expo (optional) + +```js +// app.json / app.config.js plugins +[ + '@react-native-google-mobile-ads/vungle', + { + // Pass Liftoff / Vungle SKAdNetwork IDs from Liftoff’s current docs + skAdNetworkItems: [/* ... */], + }, +] +``` + +App IDs stay in the core Expo plugin. + +## Out of scope + +- AppLovin MAX host SDK +- CloudX / other non-GAM hosts +- Fyber/DT Exchange (no Google GAM adapter — do not invent) diff --git a/packages/vungle/RNGoogleMobileAdsAdapterVungle.podspec b/packages/vungle/RNGoogleMobileAdsAdapterVungle.podspec new file mode 100644 index 00000000..1e475c06 --- /dev/null +++ b/packages/vungle/RNGoogleMobileAdsAdapterVungle.podspec @@ -0,0 +1,35 @@ +require 'json' + +package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) +gam_adapter = package['gamAdapter'] +mediation_pod = gam_adapter['iosPod'] +mediation_version = package['sdkVersions']['ios']['googleMobileAdsMediation'] + +Pod::Spec.new do |s| + s.name = "RNGoogleMobileAdsAdapterVungle" + s.version = package["version"] + s.summary = package["description"] + s.description = package["description"] + s.homepage = "https://github.com/invertase/react-native-google-mobile-ads" + s.license = package["license"] + s.authors = "Invertase Limited" + s.source = { + :git => "#{package["repository"]["url"]}.git", + :tag => "v#{s.version}" + } + # Liftoff Monetize mediation adapter requires iOS 13.0+ (guide prerequisites + CocoaPods podspec). + # https://developers.google.com/ad-manager/mobile-ads-sdk/ios/mediation/liftoff-monetize + s.platforms = { :ios => "13.0" } + s.source_files = "ios/**/*.{h,m,mm,swift}" + + if respond_to?(:install_modules_dependencies, true) + install_modules_dependencies(s) + else + s.dependency "React-Core" + end + + # Google-published Liftoff Monetize (Vungle) GAM mediation adapter. + # https://developers.google.com/ad-manager/mobile-ads-sdk/ios/mediation/liftoff-monetize + # https://cocoapods.org/pods/GoogleMobileAdsMediationVungle + s.dependency mediation_pod, mediation_version +end diff --git a/packages/vungle/__tests__/vungleAdapter.test.ts b/packages/vungle/__tests__/vungleAdapter.test.ts new file mode 100644 index 00000000..207c799d --- /dev/null +++ b/packages/vungle/__tests__/vungleAdapter.test.ts @@ -0,0 +1,20 @@ +import { nativeAdapterClassName, networkSlug } from '../src'; + +describe('@react-native-google-mobile-ads/vungle public surface', () => { + it('exports networkSlug vungle', () => { + expect(networkSlug).toBe('vungle'); + }); + + it('exports documented GAM mediation adapter class names', () => { + expect(nativeAdapterClassName).toEqual({ + android: 'com.google.ads.mediation.vungle.VungleMediationAdapter', + ios: 'GADMediationAdapterVungle', + }); + }); + + it('does not invent JS ad APIs on the package surface', () => { + // eslint-disable-next-line @typescript-eslint/no-require-imports + const surface = require('../src') as Record; + expect(Object.keys(surface).sort()).toEqual(['nativeAdapterClassName', 'networkSlug']); + }); +}); diff --git a/packages/vungle/android/build.gradle b/packages/vungle/android/build.gradle new file mode 100644 index 00000000..d1c6531a --- /dev/null +++ b/packages/vungle/android/build.gradle @@ -0,0 +1,66 @@ +import groovy.json.JsonSlurper + +buildscript { + ext.getExtOrDefault = { name, fallback -> + return rootProject.ext.has(name) ? rootProject.ext.get(name) : fallback + } + + // AGP is only required when opening this android/ folder stand-alone. + if (project == rootProject) { + repositories { + google() + mavenCentral() + } + dependencies { + classpath("com.android.tools.build:gradle:8.1.1") + } + } +} + +apply plugin: "com.android.library" + +def packageJson = new JsonSlurper().parseText(file("${projectDir}/../package.json").text) +def gamAdapter = packageJson.gamAdapter +def sdkVersions = packageJson.sdkVersions.android +def mediationArtifact = "${gamAdapter.androidArtifact}:${sdkVersions.googleMobileAdsMediation}" + +android { + namespace "io.invertase.googlemobileads.adapters.vungle" + + compileSdkVersion sdkVersions.compileSdk as Integer + + defaultConfig { + minSdkVersion sdkVersions.minSdk as Integer + targetSdkVersion sdkVersions.targetSdk as Integer + buildConfigField "String", "GAM_ADAPTER_NETWORK", "\"${gamAdapter.network}\"" + buildConfigField "String", "GAM_ADAPTER_ANDROID_CLASS", "\"${gamAdapter.androidAdapterClassName}\"" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + buildFeatures { + buildConfig true + } + + sourceSets { + main { + java.srcDirs = ["src/main/java"] + } + } +} + +repositories { + google() + mavenCentral() +} + +dependencies { + // Google-published Liftoff Monetize (Vungle) GAM mediation adapter. + // Pin: package.json sdkVersions.android.googleMobileAdsMediation (docs/Maven: 7.7.8.0 as of package publish; verify against current docs/Maven). + // Core GMA is provided by react-native-google-mobile-ads; do not re-pin it here. + // https://developers.google.com/ad-manager/mobile-ads-sdk/android/mediation/liftoff-monetize + api mediationArtifact +} diff --git a/packages/vungle/android/src/main/AndroidManifest.xml b/packages/vungle/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..b1dd18ee --- /dev/null +++ b/packages/vungle/android/src/main/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + diff --git a/packages/vungle/android/src/main/java/io/invertase/googlemobileads/adapters/vungle/AdapterPackageMarker.java b/packages/vungle/android/src/main/java/io/invertase/googlemobileads/adapters/vungle/AdapterPackageMarker.java new file mode 100644 index 00000000..a7d9f935 --- /dev/null +++ b/packages/vungle/android/src/main/java/io/invertase/googlemobileads/adapters/vungle/AdapterPackageMarker.java @@ -0,0 +1,10 @@ +package io.invertase.googlemobileads.adapters.vungle; + +/** + * Package marker so the Android library module has a compilable source root. + * This package does not expose a TurboModule — mediation is linked via Gradle + * ({@code com.google.ads.mediation:vungle}). + */ +public final class AdapterPackageMarker { + private AdapterPackageMarker() {} +} diff --git a/packages/vungle/app.plugin.js b/packages/vungle/app.plugin.js new file mode 100644 index 00000000..3c7d11b6 --- /dev/null +++ b/packages/vungle/app.plugin.js @@ -0,0 +1 @@ +module.exports = require('./plugin/build'); diff --git a/packages/vungle/ios/RNGoogleMobileAdsAdapterVungleMarker.m b/packages/vungle/ios/RNGoogleMobileAdsAdapterVungleMarker.m new file mode 100644 index 00000000..d54a245e --- /dev/null +++ b/packages/vungle/ios/RNGoogleMobileAdsAdapterVungleMarker.m @@ -0,0 +1,11 @@ +#import + +/** + * Package marker so the podspec has a compilable source file. + * Mediation is linked via the GoogleMobileAdsMediationVungle pod dependency. + */ +@interface RNGoogleMobileAdsAdapterVungleMarker : NSObject +@end + +@implementation RNGoogleMobileAdsAdapterVungleMarker +@end diff --git a/packages/vungle/package.json b/packages/vungle/package.json index bf9fd880..dd0eeca2 100644 --- a/packages/vungle/package.json +++ b/packages/vungle/package.json @@ -1,7 +1,7 @@ { "name": "@react-native-google-mobile-ads/vungle", "version": "16.5.0", - "description": "Empty package shell for the Liftoff / Vungle Google Mobile Ads (GAM) mediation adapter. Product adapter content is out of scope for this shell (see A-0 / ADAPT-02).", + "description": "Liftoff Monetize (Vungle) Google Mobile Ads (GAM) mediation adapter for react-native-google-mobile-ads. Links the official Google mediation adapter on Android and iOS; does not add JS ad APIs.", "author": "Invertase (http://invertase.io)", "license": "Apache-2.0", "repository": { @@ -15,10 +15,116 @@ "admob", "mediation", "gam", - "vungle" + "vungle", + "liftoff", + "liftoff-monetize" + ], + "main": "lib/commonjs/index.js", + "module": "lib/module/index.js", + "react-native": "src/index.ts", + "types": "lib/typescript/commonjs/index.d.ts", + "exports": { + ".": { + "react-native": { + "types": "./lib/typescript/module/index.d.ts", + "default": "./src/index.ts" + }, + "source": { + "types": "./lib/typescript/module/index.d.ts", + "default": "./src/index.ts" + }, + "import": { + "types": "./lib/typescript/module/index.d.ts", + "default": "./lib/module/index.js" + }, + "require": { + "types": "./lib/typescript/commonjs/index.d.ts", + "default": "./lib/commonjs/index.js" + }, + "default": "./lib/commonjs/index.js" + }, + "./app.plugin": "./app.plugin.js", + "./app.plugin.js": "./app.plugin.js", + "./package.json": "./package.json" + }, + "files": [ + "/android/", + "/ios/", + "/lib/", + "/plugin/", + "/src/", + "/LICENSE", + "/RNGoogleMobileAdsAdapterVungle.podspec", + "/app.plugin.js", + "/react-native.config.js" ], - "files": [], "publishConfig": { "access": "public" + }, + "gamAdapter": { + "network": "vungle", + "androidArtifact": "com.google.ads.mediation:vungle", + "androidAdapterClassName": "com.google.ads.mediation.vungle.VungleMediationAdapter", + "iosPod": "GoogleMobileAdsMediationVungle", + "iosAdapterClassName": "GADMediationAdapterVungle" + }, + "sdkVersions": { + "ios": { + "googleMobileAdsMediation": "7.7.7.0" + }, + "android": { + "minSdk": 23, + "targetSdk": 34, + "compileSdk": 34, + "buildTools": "34.0.0", + "googleMobileAdsMediation": "7.7.8.0" + } + }, + "react-native-builder-bob": { + "source": "src", + "output": "lib", + "targets": [ + [ + "commonjs", + { + "esm": true + } + ], + [ + "module", + { + "esm": true + } + ], + "typescript" + ] + }, + "scripts": { + "prepare": "yarn build && yarn build:plugin", + "build": "bob build", + "build:clean": "rimraf android/build && rimraf ios/build && rimraf lib && rimraf plugin/build", + "build:plugin": "yarn tsc --build plugin" + }, + "peerDependencies": { + "@expo/config-plugins": ">=5.0.0", + "expo": ">=47.0.0", + "react": "*", + "react-native": "*", + "react-native-google-mobile-ads": "*" + }, + "peerDependenciesMeta": { + "@expo/config-plugins": { + "optional": true + }, + "expo": { + "optional": true + } + }, + "devDependencies": { + "@expo/config-plugins": "^54.0.4", + "react-native-builder-bob": "^0.40.13", + "react-native-google-mobile-ads": "workspace:*", + "rimraf": "^6.0.1", + "typescript": "^5.9.3" } } diff --git a/packages/vungle/plugin/src/index.ts b/packages/vungle/plugin/src/index.ts new file mode 100644 index 00000000..5fcbe654 --- /dev/null +++ b/packages/vungle/plugin/src/index.ts @@ -0,0 +1,52 @@ +import { ConfigPlugin, withInfoPlist, withPlugins } from '@expo/config-plugins'; + +type PluginParameters = { + /** + * Liftoff Monetize (Vungle) SKAdNetwork identifiers to merge into Info.plist. + * Core's Expo plugin owns app IDs; this adapter only merges mediation network rows. + * Pass identifiers from Liftoff's current SKAdNetwork documentation at integrate time. + */ + skAdNetworkItems?: string[]; +}; + +const withAdapterSkAdNetworkItems: ConfigPlugin = ( + config, + skAdNetworkItems, +) => { + if (skAdNetworkItems === undefined || skAdNetworkItems.length === 0) { + return config; + } + + return withInfoPlist(config, config => { + config.modResults.SKAdNetworkItems = config.modResults.SKAdNetworkItems ?? []; + + const existingIdentifiers = config.modResults.SKAdNetworkItems.map( + (item: { SKAdNetworkIdentifier: string }) => item.SKAdNetworkIdentifier, + ); + + const missingIdentifiers = skAdNetworkItems.filter( + identifier => !existingIdentifiers.includes(identifier), + ); + + config.modResults.SKAdNetworkItems.push( + ...missingIdentifiers.map(identifier => ({ + SKAdNetworkIdentifier: identifier, + })), + ); + + return config; + }); +}; + +/** + * Optional Expo config plugin for `@react-native-google-mobile-ads/vungle`. + * Does not set GMA app IDs (core plugin). + */ +const withRNGoogleMobileAdsAdapterVungle: ConfigPlugin = ( + config, + { skAdNetworkItems } = {}, +) => { + return withPlugins(config, [[withAdapterSkAdNetworkItems, skAdNetworkItems]]); +}; + +export default withRNGoogleMobileAdsAdapterVungle; diff --git a/packages/vungle/plugin/tsconfig.json b/packages/vungle/plugin/tsconfig.json new file mode 100644 index 00000000..354bddb4 --- /dev/null +++ b/packages/vungle/plugin/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "expo-module-scripts/tsconfig.plugin", + "compilerOptions": { + "outDir": "build", + "rootDir": "src" + }, + "include": ["./src"], + "exclude": ["**/__mocks__/*", "**/__tests__/*"] +} diff --git a/packages/vungle/plugin/tsconfig.tsbuildinfo b/packages/vungle/plugin/tsconfig.tsbuildinfo new file mode 100644 index 00000000..0597b1c4 --- /dev/null +++ b/packages/vungle/plugin/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./src/index.ts"],"version":"5.9.3"} \ No newline at end of file diff --git a/packages/vungle/react-native.config.js b/packages/vungle/react-native.config.js new file mode 100644 index 00000000..382dade8 --- /dev/null +++ b/packages/vungle/react-native.config.js @@ -0,0 +1,12 @@ +/** + * Autolinking entry for the GAM adapter package. + * Adapters ship native mediation artifacts only — no TurboModule / Fabric view. + */ +module.exports = { + dependency: { + platforms: { + android: {}, + ios: {}, + }, + }, +}; diff --git a/packages/vungle/src/index.ts b/packages/vungle/src/index.ts new file mode 100644 index 00000000..1c445a1c --- /dev/null +++ b/packages/vungle/src/index.ts @@ -0,0 +1,29 @@ +/** + * Liftoff Monetize (Vungle) GAM mediation adapter package public surface. + * + * Exports adapter class-name constants for GAM UI paste / docs only. Does **not** + * add JS ad APIs — core `initialize()` adapter statuses remain the discovery + * signal (API design §5.3). + * + * Native mediation is linked via Google's published adapter: + * - Android: `com.google.ads.mediation:vungle` + * - iOS: `GoogleMobileAdsMediationVungle` + */ + +export type NativeAdapterClassName = { + android: string; + ios: string; +}; + +/** Network slug for this Character GAM adapter package. */ +export const networkSlug = 'vungle' as const; + +/** + * Fully-qualified native mediation adapter class names for AdMob / GAM console paste. + * @see https://developers.google.com/ad-manager/mobile-ads-sdk/android/mediation/liftoff-monetize + * @see https://developers.google.com/ad-manager/mobile-ads-sdk/ios/mediation/liftoff-monetize + */ +export const nativeAdapterClassName: NativeAdapterClassName = { + android: 'com.google.ads.mediation.vungle.VungleMediationAdapter', + ios: 'GADMediationAdapterVungle', +}; diff --git a/packages/vungle/tsconfig.json b/packages/vungle/tsconfig.json new file mode 100644 index 00000000..a841e12a --- /dev/null +++ b/packages/vungle/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.packages.base.json", + "include": ["src"], + "exclude": ["node_modules", "**/*.spec.ts", "**/*.test.ts"] +} diff --git a/yarn.lock b/yarn.lock index 9f60178a..0a2e9dfb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6188,6 +6188,23 @@ __metadata: "@react-native-google-mobile-ads/vungle@workspace:packages/vungle": version: 0.0.0-use.local resolution: "@react-native-google-mobile-ads/vungle@workspace:packages/vungle" + dependencies: + "@expo/config-plugins": "npm:^54.0.4" + react-native-builder-bob: "npm:^0.40.13" + react-native-google-mobile-ads: "workspace:*" + rimraf: "npm:^6.0.1" + typescript: "npm:^5.9.3" + peerDependencies: + "@expo/config-plugins": ">=5.0.0" + expo: ">=47.0.0" + react: "*" + react-native: "*" + react-native-google-mobile-ads: "*" + peerDependenciesMeta: + "@expo/config-plugins": + optional: true + expo: + optional: true languageName: unknown linkType: soft From 17f474c2cc5bf6b74b1ae71bdd0eadcc31e843a4 Mon Sep 17 00:00:00 2001 From: Mike Hardy Date: Fri, 4 Sep 2026 10:28:46 -0500 Subject: [PATCH 08/11] feat(adapters): add moloco GAM mediation package --- jest.config.js | 1 + packages/moloco/LICENSE | 32 ++++++ packages/moloco/README.md | 69 +++++++++++ .../RNGoogleMobileAdsAdapterMoloco.podspec | 35 ++++++ .../moloco/__tests__/molocoAdapter.test.ts | 20 ++++ packages/moloco/android/build.gradle | 66 +++++++++++ .../android/src/main/AndroidManifest.xml | 6 + .../adapters/moloco/AdapterPackageMarker.java | 10 ++ packages/moloco/app.plugin.js | 1 + .../RNGoogleMobileAdsAdapterMolocoMarker.m | 11 ++ packages/moloco/package.json | 108 +++++++++++++++++- packages/moloco/plugin/src/index.ts | 52 +++++++++ packages/moloco/plugin/tsconfig.json | 9 ++ packages/moloco/plugin/tsconfig.tsbuildinfo | 1 + packages/moloco/react-native.config.js | 12 ++ packages/moloco/src/index.ts | 29 +++++ packages/moloco/tsconfig.json | 5 + yarn.lock | 17 +++ 18 files changed, 482 insertions(+), 2 deletions(-) create mode 100644 packages/moloco/LICENSE create mode 100644 packages/moloco/README.md create mode 100644 packages/moloco/RNGoogleMobileAdsAdapterMoloco.podspec create mode 100644 packages/moloco/__tests__/molocoAdapter.test.ts create mode 100644 packages/moloco/android/build.gradle create mode 100644 packages/moloco/android/src/main/AndroidManifest.xml create mode 100644 packages/moloco/android/src/main/java/io/invertase/googlemobileads/adapters/moloco/AdapterPackageMarker.java create mode 100644 packages/moloco/app.plugin.js create mode 100644 packages/moloco/ios/RNGoogleMobileAdsAdapterMolocoMarker.m create mode 100644 packages/moloco/plugin/src/index.ts create mode 100644 packages/moloco/plugin/tsconfig.json create mode 100644 packages/moloco/plugin/tsconfig.tsbuildinfo create mode 100644 packages/moloco/react-native.config.js create mode 100644 packages/moloco/src/index.ts create mode 100644 packages/moloco/tsconfig.json diff --git a/jest.config.js b/jest.config.js index a8e87c0a..d6568923 100644 --- a/jest.config.js +++ b/jest.config.js @@ -9,6 +9,7 @@ module.exports = { '/packages/unity/lib/', '/packages/pangle/lib/', '/packages/vungle/lib/', + '/packages/moloco/lib/', ], setupFiles: ['./jest.setup.ts'], diff --git a/packages/moloco/LICENSE b/packages/moloco/LICENSE new file mode 100644 index 00000000..db4814fc --- /dev/null +++ b/packages/moloco/LICENSE @@ -0,0 +1,32 @@ +Apache-2.0 License +------------------ + +Copyright (c) 2021-present Invertase Limited + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this library except in compliance with the License. + +You may obtain a copy of the Apache-2.0 License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +Creative Commons Attribution 3.0 License +---------------------------------------- + +Copyright (c) 2016-present Invertase Limited + +Documentation and other instructional materials provided for this project +(including on a separate documentation repository or it's documentation website) are +licensed under the Creative Commons Attribution 3.0 License. Code samples/blocks +contained therein are licensed under the Apache License, Version 2.0 (the "License"), as above. + +You may obtain a copy of the Creative Commons Attribution 3.0 License at + + https://creativecommons.org/licenses/by/3.0/ diff --git a/packages/moloco/README.md b/packages/moloco/README.md new file mode 100644 index 00000000..435d8e45 --- /dev/null +++ b/packages/moloco/README.md @@ -0,0 +1,69 @@ +# `@react-native-google-mobile-ads/moloco` + +Moloco **Google Ad Manager / AdMob mediation** adapter package for [`react-native-google-mobile-ads`](https://github.com/invertase/react-native-google-mobile-ads). + +This package links Google’s official Moloco mediation adapter on Android and iOS. It does **not** ship AppLovin MAX, CloudX, or any JS ad APIs — use core `initialize()` / adapter status for discovery. + +## Install + +```bash +yarn add @react-native-google-mobile-ads/moloco +# peer: react-native-google-mobile-ads +``` + +Autolinking pulls in the native mediation artifacts. Rebuild the app after install. + +## Native adapter class names (GAM / AdMob UI) + +| Platform | Class name | +| -------- | ---------- | +| Android | `com.google.ads.mediation.moloco.MolocoMediationAdapter` | +| iOS | `GADMediationAdapterMoloco` | + +Also exported from JS: + +```ts +import { + nativeAdapterClassName, + networkSlug, +} from '@react-native-google-mobile-ads/moloco'; +``` + +## Mediation dependencies (pinned in this package) + +| Platform | Coordinate | Version pin | +| -------- | ---------- | ----------- | +| Android | `com.google.ads.mediation:moloco` | `4.12.0.0` | +| iOS | CocoaPods `GoogleMobileAdsMediationMoloco` | `4.10.0.0` | + +Citations (verify at upgrade time): + +- Android (GAM): https://developers.google.com/ad-manager/mobile-ads-sdk/android/mediation/moloco +- iOS (GAM): https://developers.google.com/ad-manager/mobile-ads-sdk/ios/mediation/moloco +- Google Maven: `com.google.ads.mediation:moloco` (`latest` / `release` = `4.12.0.0`) +- CocoaPods: https://cocoapods.org/pods/GoogleMobileAdsMediationMoloco + +Core continues to own `play-services-ads` / `Google-Mobile-Ads-SDK`. This package does not re-pin the GMA SDK. + +iOS platform floor is **13.0** (Moloco mediation guide prerequisites / CocoaPods podspec). Android minSdk is **23** (guide prerequisites). + +## Expo (optional) + +```js +// app.json / app.config.js plugins +[ + '@react-native-google-mobile-ads/moloco', + { + // Pass Moloco SKAdNetwork IDs from Moloco’s current docs + skAdNetworkItems: [/* ... */], + }, +] +``` + +App IDs stay in the core Expo plugin. + +## Out of scope + +- AppLovin MAX host SDK +- CloudX / other non-GAM hosts +- Fyber/DT Exchange (no Google GAM adapter — do not invent) diff --git a/packages/moloco/RNGoogleMobileAdsAdapterMoloco.podspec b/packages/moloco/RNGoogleMobileAdsAdapterMoloco.podspec new file mode 100644 index 00000000..c828586f --- /dev/null +++ b/packages/moloco/RNGoogleMobileAdsAdapterMoloco.podspec @@ -0,0 +1,35 @@ +require 'json' + +package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) +gam_adapter = package['gamAdapter'] +mediation_pod = gam_adapter['iosPod'] +mediation_version = package['sdkVersions']['ios']['googleMobileAdsMediation'] + +Pod::Spec.new do |s| + s.name = "RNGoogleMobileAdsAdapterMoloco" + s.version = package["version"] + s.summary = package["description"] + s.description = package["description"] + s.homepage = "https://github.com/invertase/react-native-google-mobile-ads" + s.license = package["license"] + s.authors = "Invertase Limited" + s.source = { + :git => "#{package["repository"]["url"]}.git", + :tag => "v#{s.version}" + } + # Moloco mediation adapter requires iOS 13.0+ (guide prerequisites + CocoaPods podspec). + # https://developers.google.com/ad-manager/mobile-ads-sdk/ios/mediation/moloco + s.platforms = { :ios => "13.0" } + s.source_files = "ios/**/*.{h,m,mm,swift}" + + if respond_to?(:install_modules_dependencies, true) + install_modules_dependencies(s) + else + s.dependency "React-Core" + end + + # Google-published Moloco GAM mediation adapter. + # https://developers.google.com/ad-manager/mobile-ads-sdk/ios/mediation/moloco + # https://cocoapods.org/pods/GoogleMobileAdsMediationMoloco + s.dependency mediation_pod, mediation_version +end diff --git a/packages/moloco/__tests__/molocoAdapter.test.ts b/packages/moloco/__tests__/molocoAdapter.test.ts new file mode 100644 index 00000000..2759ab38 --- /dev/null +++ b/packages/moloco/__tests__/molocoAdapter.test.ts @@ -0,0 +1,20 @@ +import { nativeAdapterClassName, networkSlug } from '../src'; + +describe('@react-native-google-mobile-ads/moloco public surface', () => { + it('exports networkSlug moloco', () => { + expect(networkSlug).toBe('moloco'); + }); + + it('exports documented GAM mediation adapter class names', () => { + expect(nativeAdapterClassName).toEqual({ + android: 'com.google.ads.mediation.moloco.MolocoMediationAdapter', + ios: 'GADMediationAdapterMoloco', + }); + }); + + it('does not invent JS ad APIs on the package surface', () => { + // eslint-disable-next-line @typescript-eslint/no-require-imports + const surface = require('../src') as Record; + expect(Object.keys(surface).sort()).toEqual(['nativeAdapterClassName', 'networkSlug']); + }); +}); diff --git a/packages/moloco/android/build.gradle b/packages/moloco/android/build.gradle new file mode 100644 index 00000000..7d38de5f --- /dev/null +++ b/packages/moloco/android/build.gradle @@ -0,0 +1,66 @@ +import groovy.json.JsonSlurper + +buildscript { + ext.getExtOrDefault = { name, fallback -> + return rootProject.ext.has(name) ? rootProject.ext.get(name) : fallback + } + + // AGP is only required when opening this android/ folder stand-alone. + if (project == rootProject) { + repositories { + google() + mavenCentral() + } + dependencies { + classpath("com.android.tools.build:gradle:8.1.1") + } + } +} + +apply plugin: "com.android.library" + +def packageJson = new JsonSlurper().parseText(file("${projectDir}/../package.json").text) +def gamAdapter = packageJson.gamAdapter +def sdkVersions = packageJson.sdkVersions.android +def mediationArtifact = "${gamAdapter.androidArtifact}:${sdkVersions.googleMobileAdsMediation}" + +android { + namespace "io.invertase.googlemobileads.adapters.moloco" + + compileSdkVersion sdkVersions.compileSdk as Integer + + defaultConfig { + minSdkVersion sdkVersions.minSdk as Integer + targetSdkVersion sdkVersions.targetSdk as Integer + buildConfigField "String", "GAM_ADAPTER_NETWORK", "\"${gamAdapter.network}\"" + buildConfigField "String", "GAM_ADAPTER_ANDROID_CLASS", "\"${gamAdapter.androidAdapterClassName}\"" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + buildFeatures { + buildConfig true + } + + sourceSets { + main { + java.srcDirs = ["src/main/java"] + } + } +} + +repositories { + google() + mavenCentral() +} + +dependencies { + // Google-published Moloco GAM mediation adapter. + // Pin: package.json sdkVersions.android.googleMobileAdsMediation (docs/Maven: 4.12.0.0 as of package publish; verify against current docs/Maven). + // Core GMA is provided by react-native-google-mobile-ads; do not re-pin it here. + // https://developers.google.com/ad-manager/mobile-ads-sdk/android/mediation/moloco + api mediationArtifact +} diff --git a/packages/moloco/android/src/main/AndroidManifest.xml b/packages/moloco/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..b1dd18ee --- /dev/null +++ b/packages/moloco/android/src/main/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + diff --git a/packages/moloco/android/src/main/java/io/invertase/googlemobileads/adapters/moloco/AdapterPackageMarker.java b/packages/moloco/android/src/main/java/io/invertase/googlemobileads/adapters/moloco/AdapterPackageMarker.java new file mode 100644 index 00000000..6c484ce4 --- /dev/null +++ b/packages/moloco/android/src/main/java/io/invertase/googlemobileads/adapters/moloco/AdapterPackageMarker.java @@ -0,0 +1,10 @@ +package io.invertase.googlemobileads.adapters.moloco; + +/** + * Package marker so the Android library module has a compilable source root. + * This package does not expose a TurboModule — mediation is linked via Gradle + * ({@code com.google.ads.mediation:moloco}). + */ +public final class AdapterPackageMarker { + private AdapterPackageMarker() {} +} diff --git a/packages/moloco/app.plugin.js b/packages/moloco/app.plugin.js new file mode 100644 index 00000000..3c7d11b6 --- /dev/null +++ b/packages/moloco/app.plugin.js @@ -0,0 +1 @@ +module.exports = require('./plugin/build'); diff --git a/packages/moloco/ios/RNGoogleMobileAdsAdapterMolocoMarker.m b/packages/moloco/ios/RNGoogleMobileAdsAdapterMolocoMarker.m new file mode 100644 index 00000000..9751637a --- /dev/null +++ b/packages/moloco/ios/RNGoogleMobileAdsAdapterMolocoMarker.m @@ -0,0 +1,11 @@ +#import + +/** + * Package marker so the podspec has a compilable source file. + * Mediation is linked via the GoogleMobileAdsMediationMoloco pod dependency. + */ +@interface RNGoogleMobileAdsAdapterMolocoMarker : NSObject +@end + +@implementation RNGoogleMobileAdsAdapterMolocoMarker +@end diff --git a/packages/moloco/package.json b/packages/moloco/package.json index 27e3efa5..774cb77d 100644 --- a/packages/moloco/package.json +++ b/packages/moloco/package.json @@ -1,7 +1,7 @@ { "name": "@react-native-google-mobile-ads/moloco", "version": "16.5.0", - "description": "Empty package shell for the Moloco Google Mobile Ads (GAM) mediation adapter. Product adapter content is out of scope for this shell (see A-0 / ADAPT-02).", + "description": "Moloco Google Mobile Ads (GAM) mediation adapter for react-native-google-mobile-ads. Links the official Google mediation adapter on Android and iOS; does not add JS ad APIs.", "author": "Invertase (http://invertase.io)", "license": "Apache-2.0", "repository": { @@ -17,8 +17,112 @@ "gam", "moloco" ], - "files": [], + "main": "lib/commonjs/index.js", + "module": "lib/module/index.js", + "react-native": "src/index.ts", + "types": "lib/typescript/commonjs/index.d.ts", + "exports": { + ".": { + "react-native": { + "types": "./lib/typescript/module/index.d.ts", + "default": "./src/index.ts" + }, + "source": { + "types": "./lib/typescript/module/index.d.ts", + "default": "./src/index.ts" + }, + "import": { + "types": "./lib/typescript/module/index.d.ts", + "default": "./lib/module/index.js" + }, + "require": { + "types": "./lib/typescript/commonjs/index.d.ts", + "default": "./lib/commonjs/index.js" + }, + "default": "./lib/commonjs/index.js" + }, + "./app.plugin": "./app.plugin.js", + "./app.plugin.js": "./app.plugin.js", + "./package.json": "./package.json" + }, + "files": [ + "/android/", + "/ios/", + "/lib/", + "/plugin/", + "/src/", + "/LICENSE", + "/RNGoogleMobileAdsAdapterMoloco.podspec", + "/app.plugin.js", + "/react-native.config.js" + ], "publishConfig": { "access": "public" + }, + "gamAdapter": { + "network": "moloco", + "androidArtifact": "com.google.ads.mediation:moloco", + "androidAdapterClassName": "com.google.ads.mediation.moloco.MolocoMediationAdapter", + "iosPod": "GoogleMobileAdsMediationMoloco", + "iosAdapterClassName": "GADMediationAdapterMoloco" + }, + "sdkVersions": { + "ios": { + "googleMobileAdsMediation": "4.10.0.0" + }, + "android": { + "minSdk": 23, + "targetSdk": 34, + "compileSdk": 34, + "buildTools": "34.0.0", + "googleMobileAdsMediation": "4.12.0.0" + } + }, + "react-native-builder-bob": { + "source": "src", + "output": "lib", + "targets": [ + [ + "commonjs", + { + "esm": true + } + ], + [ + "module", + { + "esm": true + } + ], + "typescript" + ] + }, + "scripts": { + "prepare": "yarn build && yarn build:plugin", + "build": "bob build", + "build:clean": "rimraf android/build && rimraf ios/build && rimraf lib && rimraf plugin/build", + "build:plugin": "yarn tsc --build plugin" + }, + "peerDependencies": { + "@expo/config-plugins": ">=5.0.0", + "expo": ">=47.0.0", + "react": "*", + "react-native": "*", + "react-native-google-mobile-ads": "*" + }, + "peerDependenciesMeta": { + "@expo/config-plugins": { + "optional": true + }, + "expo": { + "optional": true + } + }, + "devDependencies": { + "@expo/config-plugins": "^54.0.4", + "react-native-builder-bob": "^0.40.13", + "react-native-google-mobile-ads": "workspace:*", + "rimraf": "^6.0.1", + "typescript": "^5.9.3" } } diff --git a/packages/moloco/plugin/src/index.ts b/packages/moloco/plugin/src/index.ts new file mode 100644 index 00000000..6b08b933 --- /dev/null +++ b/packages/moloco/plugin/src/index.ts @@ -0,0 +1,52 @@ +import { ConfigPlugin, withInfoPlist, withPlugins } from '@expo/config-plugins'; + +type PluginParameters = { + /** + * Moloco SKAdNetwork identifiers to merge into Info.plist. + * Core's Expo plugin owns app IDs; this adapter only merges mediation network rows. + * Pass identifiers from Moloco's current SKAdNetwork documentation at integrate time. + */ + skAdNetworkItems?: string[]; +}; + +const withAdapterSkAdNetworkItems: ConfigPlugin = ( + config, + skAdNetworkItems, +) => { + if (skAdNetworkItems === undefined || skAdNetworkItems.length === 0) { + return config; + } + + return withInfoPlist(config, config => { + config.modResults.SKAdNetworkItems = config.modResults.SKAdNetworkItems ?? []; + + const existingIdentifiers = config.modResults.SKAdNetworkItems.map( + (item: { SKAdNetworkIdentifier: string }) => item.SKAdNetworkIdentifier, + ); + + const missingIdentifiers = skAdNetworkItems.filter( + identifier => !existingIdentifiers.includes(identifier), + ); + + config.modResults.SKAdNetworkItems.push( + ...missingIdentifiers.map(identifier => ({ + SKAdNetworkIdentifier: identifier, + })), + ); + + return config; + }); +}; + +/** + * Optional Expo config plugin for `@react-native-google-mobile-ads/moloco`. + * Does not set GMA app IDs (core plugin). + */ +const withRNGoogleMobileAdsAdapterMoloco: ConfigPlugin = ( + config, + { skAdNetworkItems } = {}, +) => { + return withPlugins(config, [[withAdapterSkAdNetworkItems, skAdNetworkItems]]); +}; + +export default withRNGoogleMobileAdsAdapterMoloco; diff --git a/packages/moloco/plugin/tsconfig.json b/packages/moloco/plugin/tsconfig.json new file mode 100644 index 00000000..354bddb4 --- /dev/null +++ b/packages/moloco/plugin/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "expo-module-scripts/tsconfig.plugin", + "compilerOptions": { + "outDir": "build", + "rootDir": "src" + }, + "include": ["./src"], + "exclude": ["**/__mocks__/*", "**/__tests__/*"] +} diff --git a/packages/moloco/plugin/tsconfig.tsbuildinfo b/packages/moloco/plugin/tsconfig.tsbuildinfo new file mode 100644 index 00000000..0597b1c4 --- /dev/null +++ b/packages/moloco/plugin/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./src/index.ts"],"version":"5.9.3"} \ No newline at end of file diff --git a/packages/moloco/react-native.config.js b/packages/moloco/react-native.config.js new file mode 100644 index 00000000..382dade8 --- /dev/null +++ b/packages/moloco/react-native.config.js @@ -0,0 +1,12 @@ +/** + * Autolinking entry for the GAM adapter package. + * Adapters ship native mediation artifacts only — no TurboModule / Fabric view. + */ +module.exports = { + dependency: { + platforms: { + android: {}, + ios: {}, + }, + }, +}; diff --git a/packages/moloco/src/index.ts b/packages/moloco/src/index.ts new file mode 100644 index 00000000..8db43d47 --- /dev/null +++ b/packages/moloco/src/index.ts @@ -0,0 +1,29 @@ +/** + * Moloco GAM mediation adapter package public surface. + * + * Exports adapter class-name constants for GAM UI paste / docs only. Does **not** + * add JS ad APIs — core `initialize()` adapter statuses remain the discovery + * signal (API design §5.3). + * + * Native mediation is linked via Google's published adapter: + * - Android: `com.google.ads.mediation:moloco` + * - iOS: `GoogleMobileAdsMediationMoloco` + */ + +export type NativeAdapterClassName = { + android: string; + ios: string; +}; + +/** Network slug for this Character GAM adapter package. */ +export const networkSlug = 'moloco' as const; + +/** + * Fully-qualified native mediation adapter class names for AdMob / GAM console paste. + * @see https://developers.google.com/ad-manager/mobile-ads-sdk/android/mediation/moloco + * @see https://developers.google.com/ad-manager/mobile-ads-sdk/ios/mediation/moloco + */ +export const nativeAdapterClassName: NativeAdapterClassName = { + android: 'com.google.ads.mediation.moloco.MolocoMediationAdapter', + ios: 'GADMediationAdapterMoloco', +}; diff --git a/packages/moloco/tsconfig.json b/packages/moloco/tsconfig.json new file mode 100644 index 00000000..a841e12a --- /dev/null +++ b/packages/moloco/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.packages.base.json", + "include": ["src"], + "exclude": ["node_modules", "**/*.spec.ts", "**/*.test.ts"] +} diff --git a/yarn.lock b/yarn.lock index 0a2e9dfb..46ee1ab3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6136,6 +6136,23 @@ __metadata: "@react-native-google-mobile-ads/moloco@workspace:packages/moloco": version: 0.0.0-use.local resolution: "@react-native-google-mobile-ads/moloco@workspace:packages/moloco" + dependencies: + "@expo/config-plugins": "npm:^54.0.4" + react-native-builder-bob: "npm:^0.40.13" + react-native-google-mobile-ads: "workspace:*" + rimraf: "npm:^6.0.1" + typescript: "npm:^5.9.3" + peerDependencies: + "@expo/config-plugins": ">=5.0.0" + expo: ">=47.0.0" + react: "*" + react-native: "*" + react-native-google-mobile-ads: "*" + peerDependenciesMeta: + "@expo/config-plugins": + optional: true + expo: + optional: true languageName: unknown linkType: soft From e612eda981a2f7f54b7612d590d01f15fd8dfab1 Mon Sep 17 00:00:00 2001 From: Mike Hardy Date: Fri, 4 Sep 2026 10:35:17 -0500 Subject: [PATCH 09/11] feat(adapters): add mintegral GAM mediation package --- jest.config.js | 1 + packages/mintegral/LICENSE | 32 ++++++ packages/mintegral/README.md | 80 +++++++++++++ .../RNGoogleMobileAdsAdapterMintegral.podspec | 35 ++++++ .../__tests__/mintegralAdapter.test.ts | 20 ++++ packages/mintegral/android/build.gradle | 72 ++++++++++++ .../android/src/main/AndroidManifest.xml | 6 + .../mintegral/AdapterPackageMarker.java | 10 ++ packages/mintegral/app.plugin.js | 1 + .../RNGoogleMobileAdsAdapterMintegralMarker.m | 11 ++ packages/mintegral/package.json | 108 +++++++++++++++++- packages/mintegral/plugin/src/index.ts | 52 +++++++++ packages/mintegral/plugin/tsconfig.json | 9 ++ .../mintegral/plugin/tsconfig.tsbuildinfo | 1 + packages/mintegral/react-native.config.js | 12 ++ packages/mintegral/src/index.ts | 29 +++++ packages/mintegral/tsconfig.json | 5 + yarn.lock | 17 +++ 18 files changed, 499 insertions(+), 2 deletions(-) create mode 100644 packages/mintegral/LICENSE create mode 100644 packages/mintegral/README.md create mode 100644 packages/mintegral/RNGoogleMobileAdsAdapterMintegral.podspec create mode 100644 packages/mintegral/__tests__/mintegralAdapter.test.ts create mode 100644 packages/mintegral/android/build.gradle create mode 100644 packages/mintegral/android/src/main/AndroidManifest.xml create mode 100644 packages/mintegral/android/src/main/java/io/invertase/googlemobileads/adapters/mintegral/AdapterPackageMarker.java create mode 100644 packages/mintegral/app.plugin.js create mode 100644 packages/mintegral/ios/RNGoogleMobileAdsAdapterMintegralMarker.m create mode 100644 packages/mintegral/plugin/src/index.ts create mode 100644 packages/mintegral/plugin/tsconfig.json create mode 100644 packages/mintegral/plugin/tsconfig.tsbuildinfo create mode 100644 packages/mintegral/react-native.config.js create mode 100644 packages/mintegral/src/index.ts create mode 100644 packages/mintegral/tsconfig.json diff --git a/jest.config.js b/jest.config.js index d6568923..25ea61f3 100644 --- a/jest.config.js +++ b/jest.config.js @@ -10,6 +10,7 @@ module.exports = { '/packages/pangle/lib/', '/packages/vungle/lib/', '/packages/moloco/lib/', + '/packages/mintegral/lib/', ], setupFiles: ['./jest.setup.ts'], diff --git a/packages/mintegral/LICENSE b/packages/mintegral/LICENSE new file mode 100644 index 00000000..db4814fc --- /dev/null +++ b/packages/mintegral/LICENSE @@ -0,0 +1,32 @@ +Apache-2.0 License +------------------ + +Copyright (c) 2021-present Invertase Limited + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this library except in compliance with the License. + +You may obtain a copy of the Apache-2.0 License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +Creative Commons Attribution 3.0 License +---------------------------------------- + +Copyright (c) 2016-present Invertase Limited + +Documentation and other instructional materials provided for this project +(including on a separate documentation repository or it's documentation website) are +licensed under the Creative Commons Attribution 3.0 License. Code samples/blocks +contained therein are licensed under the Apache License, Version 2.0 (the "License"), as above. + +You may obtain a copy of the Creative Commons Attribution 3.0 License at + + https://creativecommons.org/licenses/by/3.0/ diff --git a/packages/mintegral/README.md b/packages/mintegral/README.md new file mode 100644 index 00000000..b5863e97 --- /dev/null +++ b/packages/mintegral/README.md @@ -0,0 +1,80 @@ +# `@react-native-google-mobile-ads/mintegral` + +Mintegral **Google Ad Manager / AdMob mediation** adapter package for [`react-native-google-mobile-ads`](https://github.com/invertase/react-native-google-mobile-ads). + +This package links Google’s official Mintegral mediation adapter on Android and iOS. It does **not** ship AppLovin MAX, CloudX, or any JS ad APIs — use core `initialize()` / adapter status for discovery. + +## Install + +```bash +yarn add @react-native-google-mobile-ads/mintegral +# peer: react-native-google-mobile-ads +``` + +Autolinking pulls in the native mediation artifacts. Rebuild the app after install. + +### Android Maven repository (required) + +Google’s Mintegral GAM adapter depends on `com.mbridge.msdk.oversea:mbridge_android_sdk`, which is hosted on Mintegral’s Maven (not Maven Central). This package adds that repository in its Android module. If your app uses `dependencyResolutionManagement` / `FAIL_ON_PROJECT_REPOS`, also add the repo in your project `settings.gradle(.kts)` as shown in Google’s guide: + +```kotlin +maven { + url = uri("https://dl-maven-android.mintegral.com/repository/mbridge_android_sdk_oversea") +} +``` + +## Native adapter class names (GAM / AdMob UI) + +| Platform | Class name | +| -------- | ---------- | +| Android | `com.google.ads.mediation.mintegral.MintegralMediationAdapter` | +| iOS | `GADMediationAdapterMintegral` | + +Also exported from JS: + +```ts +import { + nativeAdapterClassName, + networkSlug, +} from '@react-native-google-mobile-ads/mintegral'; +``` + +## Mediation dependencies (pinned in this package) + +| Platform | Coordinate | Version pin | +| -------- | ---------- | ----------- | +| Android | `com.google.ads.mediation:mintegral` | `17.1.81.0` | +| iOS | CocoaPods `GoogleMobileAdsMediationMintegral` | `8.1.7.0` | + +Citations (verify at upgrade time): + +- Android (GAM): https://developers.google.com/ad-manager/mobile-ads-sdk/android/mediation/mintegral +- iOS (GAM): https://developers.google.com/ad-manager/mobile-ads-sdk/ios/mediation/mintegral +- Google Maven: `com.google.ads.mediation:mintegral` (`latest` / `release` = `17.1.81.0`) +- CocoaPods: https://cocoapods.org/pods/GoogleMobileAdsMediationMintegral +- Mintegral Android Maven: `https://dl-maven-android.mintegral.com/repository/mbridge_android_sdk_oversea` + +Core continues to own `play-services-ads` / `Google-Mobile-Ads-SDK`. This package does not re-pin the GMA SDK. + +iOS platform floor is **13.0** (Mintegral mediation guide prerequisites / CocoaPods podspec). Android minSdk is **23** (guide prerequisites). + +## Expo (optional) + +```js +// app.json / app.config.js plugins +[ + '@react-native-google-mobile-ads/mintegral', + { + // Pass Mintegral SKAdNetwork IDs from Mintegral’s current docs + skAdNetworkItems: [/* ... */], + }, +] +``` + +App IDs stay in the core Expo plugin. + +## Out of scope + +- AppLovin MAX host SDK +- CloudX / other non-GAM hosts +- Fyber/DT Exchange (no Google GAM adapter — do not invent) diff --git a/packages/mintegral/RNGoogleMobileAdsAdapterMintegral.podspec b/packages/mintegral/RNGoogleMobileAdsAdapterMintegral.podspec new file mode 100644 index 00000000..1d8f19de --- /dev/null +++ b/packages/mintegral/RNGoogleMobileAdsAdapterMintegral.podspec @@ -0,0 +1,35 @@ +require 'json' + +package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) +gam_adapter = package['gamAdapter'] +mediation_pod = gam_adapter['iosPod'] +mediation_version = package['sdkVersions']['ios']['googleMobileAdsMediation'] + +Pod::Spec.new do |s| + s.name = "RNGoogleMobileAdsAdapterMintegral" + s.version = package["version"] + s.summary = package["description"] + s.description = package["description"] + s.homepage = "https://github.com/invertase/react-native-google-mobile-ads" + s.license = package["license"] + s.authors = "Invertase Limited" + s.source = { + :git => "#{package["repository"]["url"]}.git", + :tag => "v#{s.version}" + } + # Mintegral mediation adapter requires iOS 13.0+ (guide prerequisites + CocoaPods podspec). + # https://developers.google.com/ad-manager/mobile-ads-sdk/ios/mediation/mintegral + s.platforms = { :ios => "13.0" } + s.source_files = "ios/**/*.{h,m,mm,swift}" + + if respond_to?(:install_modules_dependencies, true) + install_modules_dependencies(s) + else + s.dependency "React-Core" + end + + # Google-published Mintegral GAM mediation adapter. + # https://developers.google.com/ad-manager/mobile-ads-sdk/ios/mediation/mintegral + # https://cocoapods.org/pods/GoogleMobileAdsMediationMintegral + s.dependency mediation_pod, mediation_version +end diff --git a/packages/mintegral/__tests__/mintegralAdapter.test.ts b/packages/mintegral/__tests__/mintegralAdapter.test.ts new file mode 100644 index 00000000..522e26b6 --- /dev/null +++ b/packages/mintegral/__tests__/mintegralAdapter.test.ts @@ -0,0 +1,20 @@ +import { nativeAdapterClassName, networkSlug } from '../src'; + +describe('@react-native-google-mobile-ads/mintegral public surface', () => { + it('exports networkSlug mintegral', () => { + expect(networkSlug).toBe('mintegral'); + }); + + it('exports documented GAM mediation adapter class names', () => { + expect(nativeAdapterClassName).toEqual({ + android: 'com.google.ads.mediation.mintegral.MintegralMediationAdapter', + ios: 'GADMediationAdapterMintegral', + }); + }); + + it('does not invent JS ad APIs on the package surface', () => { + // eslint-disable-next-line @typescript-eslint/no-require-imports + const surface = require('../src') as Record; + expect(Object.keys(surface).sort()).toEqual(['nativeAdapterClassName', 'networkSlug']); + }); +}); diff --git a/packages/mintegral/android/build.gradle b/packages/mintegral/android/build.gradle new file mode 100644 index 00000000..b76339ce --- /dev/null +++ b/packages/mintegral/android/build.gradle @@ -0,0 +1,72 @@ +import groovy.json.JsonSlurper + +buildscript { + ext.getExtOrDefault = { name, fallback -> + return rootProject.ext.has(name) ? rootProject.ext.get(name) : fallback + } + + // AGP is only required when opening this android/ folder stand-alone. + if (project == rootProject) { + repositories { + google() + mavenCentral() + } + dependencies { + classpath("com.android.tools.build:gradle:8.1.1") + } + } +} + +apply plugin: "com.android.library" + +def packageJson = new JsonSlurper().parseText(file("${projectDir}/../package.json").text) +def gamAdapter = packageJson.gamAdapter +def sdkVersions = packageJson.sdkVersions.android +def mediationArtifact = "${gamAdapter.androidArtifact}:${sdkVersions.googleMobileAdsMediation}" + +android { + namespace "io.invertase.googlemobileads.adapters.mintegral" + + compileSdkVersion sdkVersions.compileSdk as Integer + + defaultConfig { + minSdkVersion sdkVersions.minSdk as Integer + targetSdkVersion sdkVersions.targetSdk as Integer + buildConfigField "String", "GAM_ADAPTER_NETWORK", "\"${gamAdapter.network}\"" + buildConfigField "String", "GAM_ADAPTER_ANDROID_CLASS", "\"${gamAdapter.androidAdapterClassName}\"" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + buildFeatures { + buildConfig true + } + + sourceSets { + main { + java.srcDirs = ["src/main/java"] + } + } +} + +repositories { + google() + mavenCentral() + // Mintegral SDK (`com.mbridge.msdk.oversea:mbridge_android_sdk`) is hosted on + // Mintegral's Maven — required by Google's GAM Mintegral mediation guide Step 3. + // https://developers.google.com/ad-manager/mobile-ads-sdk/android/mediation/mintegral + maven { + url = uri("https://dl-maven-android.mintegral.com/repository/mbridge_android_sdk_oversea") + } +} + +dependencies { + // Google-published Mintegral GAM mediation adapter. + // Pin: package.json sdkVersions.android.googleMobileAdsMediation (docs/Maven: 17.1.81.0 as of package publish; verify against current docs/Maven). + // Core GMA is provided by react-native-google-mobile-ads; do not re-pin it here. + // https://developers.google.com/ad-manager/mobile-ads-sdk/android/mediation/mintegral + api mediationArtifact +} diff --git a/packages/mintegral/android/src/main/AndroidManifest.xml b/packages/mintegral/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..b1dd18ee --- /dev/null +++ b/packages/mintegral/android/src/main/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + diff --git a/packages/mintegral/android/src/main/java/io/invertase/googlemobileads/adapters/mintegral/AdapterPackageMarker.java b/packages/mintegral/android/src/main/java/io/invertase/googlemobileads/adapters/mintegral/AdapterPackageMarker.java new file mode 100644 index 00000000..44145e99 --- /dev/null +++ b/packages/mintegral/android/src/main/java/io/invertase/googlemobileads/adapters/mintegral/AdapterPackageMarker.java @@ -0,0 +1,10 @@ +package io.invertase.googlemobileads.adapters.mintegral; + +/** + * Package marker so the Android library module has a compilable source root. + * This package does not expose a TurboModule — mediation is linked via Gradle + * ({@code com.google.ads.mediation:mintegral}). + */ +public final class AdapterPackageMarker { + private AdapterPackageMarker() {} +} diff --git a/packages/mintegral/app.plugin.js b/packages/mintegral/app.plugin.js new file mode 100644 index 00000000..3c7d11b6 --- /dev/null +++ b/packages/mintegral/app.plugin.js @@ -0,0 +1 @@ +module.exports = require('./plugin/build'); diff --git a/packages/mintegral/ios/RNGoogleMobileAdsAdapterMintegralMarker.m b/packages/mintegral/ios/RNGoogleMobileAdsAdapterMintegralMarker.m new file mode 100644 index 00000000..94d6a952 --- /dev/null +++ b/packages/mintegral/ios/RNGoogleMobileAdsAdapterMintegralMarker.m @@ -0,0 +1,11 @@ +#import + +/** + * Package marker so the podspec has a compilable source file. + * Mediation is linked via the GoogleMobileAdsMediationMintegral pod dependency. + */ +@interface RNGoogleMobileAdsAdapterMintegralMarker : NSObject +@end + +@implementation RNGoogleMobileAdsAdapterMintegralMarker +@end diff --git a/packages/mintegral/package.json b/packages/mintegral/package.json index 1fb773d8..ef329d42 100644 --- a/packages/mintegral/package.json +++ b/packages/mintegral/package.json @@ -1,7 +1,7 @@ { "name": "@react-native-google-mobile-ads/mintegral", "version": "16.5.0", - "description": "Empty package shell for the Mintegral Google Mobile Ads (GAM) mediation adapter. Product adapter content is out of scope for this shell (see A-0 / ADAPT-02).", + "description": "Mintegral Google Mobile Ads (GAM) mediation adapter for react-native-google-mobile-ads. Links the official Google mediation adapter on Android and iOS; does not add JS ad APIs.", "author": "Invertase (http://invertase.io)", "license": "Apache-2.0", "repository": { @@ -17,8 +17,112 @@ "gam", "mintegral" ], - "files": [], + "main": "lib/commonjs/index.js", + "module": "lib/module/index.js", + "react-native": "src/index.ts", + "types": "lib/typescript/commonjs/index.d.ts", + "exports": { + ".": { + "react-native": { + "types": "./lib/typescript/module/index.d.ts", + "default": "./src/index.ts" + }, + "source": { + "types": "./lib/typescript/module/index.d.ts", + "default": "./src/index.ts" + }, + "import": { + "types": "./lib/typescript/module/index.d.ts", + "default": "./lib/module/index.js" + }, + "require": { + "types": "./lib/typescript/commonjs/index.d.ts", + "default": "./lib/commonjs/index.js" + }, + "default": "./lib/commonjs/index.js" + }, + "./app.plugin": "./app.plugin.js", + "./app.plugin.js": "./app.plugin.js", + "./package.json": "./package.json" + }, + "files": [ + "/android/", + "/ios/", + "/lib/", + "/plugin/", + "/src/", + "/LICENSE", + "/RNGoogleMobileAdsAdapterMintegral.podspec", + "/app.plugin.js", + "/react-native.config.js" + ], "publishConfig": { "access": "public" + }, + "gamAdapter": { + "network": "mintegral", + "androidArtifact": "com.google.ads.mediation:mintegral", + "androidAdapterClassName": "com.google.ads.mediation.mintegral.MintegralMediationAdapter", + "iosPod": "GoogleMobileAdsMediationMintegral", + "iosAdapterClassName": "GADMediationAdapterMintegral" + }, + "sdkVersions": { + "ios": { + "googleMobileAdsMediation": "8.1.7.0" + }, + "android": { + "minSdk": 23, + "targetSdk": 34, + "compileSdk": 34, + "buildTools": "34.0.0", + "googleMobileAdsMediation": "17.1.81.0" + } + }, + "react-native-builder-bob": { + "source": "src", + "output": "lib", + "targets": [ + [ + "commonjs", + { + "esm": true + } + ], + [ + "module", + { + "esm": true + } + ], + "typescript" + ] + }, + "scripts": { + "prepare": "yarn build && yarn build:plugin", + "build": "bob build", + "build:clean": "rimraf android/build && rimraf ios/build && rimraf lib && rimraf plugin/build", + "build:plugin": "yarn tsc --build plugin" + }, + "peerDependencies": { + "@expo/config-plugins": ">=5.0.0", + "expo": ">=47.0.0", + "react": "*", + "react-native": "*", + "react-native-google-mobile-ads": "*" + }, + "peerDependenciesMeta": { + "@expo/config-plugins": { + "optional": true + }, + "expo": { + "optional": true + } + }, + "devDependencies": { + "@expo/config-plugins": "^54.0.4", + "react-native-builder-bob": "^0.40.13", + "react-native-google-mobile-ads": "workspace:*", + "rimraf": "^6.0.1", + "typescript": "^5.9.3" } } diff --git a/packages/mintegral/plugin/src/index.ts b/packages/mintegral/plugin/src/index.ts new file mode 100644 index 00000000..7b4c5fb3 --- /dev/null +++ b/packages/mintegral/plugin/src/index.ts @@ -0,0 +1,52 @@ +import { ConfigPlugin, withInfoPlist, withPlugins } from '@expo/config-plugins'; + +type PluginParameters = { + /** + * Mintegral SKAdNetwork identifiers to merge into Info.plist. + * Core's Expo plugin owns app IDs; this adapter only merges mediation network rows. + * Pass identifiers from Mintegral's current SKAdNetwork documentation at integrate time. + */ + skAdNetworkItems?: string[]; +}; + +const withAdapterSkAdNetworkItems: ConfigPlugin = ( + config, + skAdNetworkItems, +) => { + if (skAdNetworkItems === undefined || skAdNetworkItems.length === 0) { + return config; + } + + return withInfoPlist(config, config => { + config.modResults.SKAdNetworkItems = config.modResults.SKAdNetworkItems ?? []; + + const existingIdentifiers = config.modResults.SKAdNetworkItems.map( + (item: { SKAdNetworkIdentifier: string }) => item.SKAdNetworkIdentifier, + ); + + const missingIdentifiers = skAdNetworkItems.filter( + identifier => !existingIdentifiers.includes(identifier), + ); + + config.modResults.SKAdNetworkItems.push( + ...missingIdentifiers.map(identifier => ({ + SKAdNetworkIdentifier: identifier, + })), + ); + + return config; + }); +}; + +/** + * Optional Expo config plugin for `@react-native-google-mobile-ads/mintegral`. + * Does not set GMA app IDs (core plugin). + */ +const withRNGoogleMobileAdsAdapterMintegral: ConfigPlugin = ( + config, + { skAdNetworkItems } = {}, +) => { + return withPlugins(config, [[withAdapterSkAdNetworkItems, skAdNetworkItems]]); +}; + +export default withRNGoogleMobileAdsAdapterMintegral; diff --git a/packages/mintegral/plugin/tsconfig.json b/packages/mintegral/plugin/tsconfig.json new file mode 100644 index 00000000..354bddb4 --- /dev/null +++ b/packages/mintegral/plugin/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "expo-module-scripts/tsconfig.plugin", + "compilerOptions": { + "outDir": "build", + "rootDir": "src" + }, + "include": ["./src"], + "exclude": ["**/__mocks__/*", "**/__tests__/*"] +} diff --git a/packages/mintegral/plugin/tsconfig.tsbuildinfo b/packages/mintegral/plugin/tsconfig.tsbuildinfo new file mode 100644 index 00000000..0597b1c4 --- /dev/null +++ b/packages/mintegral/plugin/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./src/index.ts"],"version":"5.9.3"} \ No newline at end of file diff --git a/packages/mintegral/react-native.config.js b/packages/mintegral/react-native.config.js new file mode 100644 index 00000000..382dade8 --- /dev/null +++ b/packages/mintegral/react-native.config.js @@ -0,0 +1,12 @@ +/** + * Autolinking entry for the GAM adapter package. + * Adapters ship native mediation artifacts only — no TurboModule / Fabric view. + */ +module.exports = { + dependency: { + platforms: { + android: {}, + ios: {}, + }, + }, +}; diff --git a/packages/mintegral/src/index.ts b/packages/mintegral/src/index.ts new file mode 100644 index 00000000..ad81322f --- /dev/null +++ b/packages/mintegral/src/index.ts @@ -0,0 +1,29 @@ +/** + * Mintegral GAM mediation adapter package public surface. + * + * Exports adapter class-name constants for GAM UI paste / docs only. Does **not** + * add JS ad APIs — core `initialize()` adapter statuses remain the discovery + * signal (API design §5.3). + * + * Native mediation is linked via Google's published adapter: + * - Android: `com.google.ads.mediation:mintegral` + * - iOS: `GoogleMobileAdsMediationMintegral` + */ + +export type NativeAdapterClassName = { + android: string; + ios: string; +}; + +/** Network slug for this Character GAM adapter package. */ +export const networkSlug = 'mintegral' as const; + +/** + * Fully-qualified native mediation adapter class names for AdMob / GAM console paste. + * @see https://developers.google.com/ad-manager/mobile-ads-sdk/android/mediation/mintegral + * @see https://developers.google.com/ad-manager/mobile-ads-sdk/ios/mediation/mintegral + */ +export const nativeAdapterClassName: NativeAdapterClassName = { + android: 'com.google.ads.mediation.mintegral.MintegralMediationAdapter', + ios: 'GADMediationAdapterMintegral', +}; diff --git a/packages/mintegral/tsconfig.json b/packages/mintegral/tsconfig.json new file mode 100644 index 00000000..a841e12a --- /dev/null +++ b/packages/mintegral/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.packages.base.json", + "include": ["src"], + "exclude": ["node_modules", "**/*.spec.ts", "**/*.test.ts"] +} diff --git a/yarn.lock b/yarn.lock index 46ee1ab3..e405845e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6130,6 +6130,23 @@ __metadata: "@react-native-google-mobile-ads/mintegral@workspace:packages/mintegral": version: 0.0.0-use.local resolution: "@react-native-google-mobile-ads/mintegral@workspace:packages/mintegral" + dependencies: + "@expo/config-plugins": "npm:^54.0.4" + react-native-builder-bob: "npm:^0.40.13" + react-native-google-mobile-ads: "workspace:*" + rimraf: "npm:^6.0.1" + typescript: "npm:^5.9.3" + peerDependencies: + "@expo/config-plugins": ">=5.0.0" + expo: ">=47.0.0" + react: "*" + react-native: "*" + react-native-google-mobile-ads: "*" + peerDependenciesMeta: + "@expo/config-plugins": + optional: true + expo: + optional: true languageName: unknown linkType: soft From 921ecfc58ef8ae7e1b03bd1212d2196c16642a64 Mon Sep 17 00:00:00 2001 From: Mike Hardy Date: Fri, 4 Sep 2026 10:41:50 -0500 Subject: [PATCH 10/11] feat(adapters): add inmobi GAM mediation package --- jest.config.js | 1 + packages/inmobi/LICENSE | 32 ++++++ packages/inmobi/README.md | 70 ++++++++++++ .../RNGoogleMobileAdsAdapterInMobi.podspec | 35 ++++++ .../inmobi/__tests__/inmobiAdapter.test.ts | 20 ++++ packages/inmobi/android/build.gradle | 67 +++++++++++ .../android/src/main/AndroidManifest.xml | 6 + .../adapters/inmobi/AdapterPackageMarker.java | 10 ++ packages/inmobi/app.plugin.js | 1 + .../RNGoogleMobileAdsAdapterInMobiMarker.m | 11 ++ packages/inmobi/package.json | 108 +++++++++++++++++- packages/inmobi/plugin/src/index.ts | 52 +++++++++ packages/inmobi/plugin/tsconfig.json | 9 ++ packages/inmobi/plugin/tsconfig.tsbuildinfo | 1 + packages/inmobi/react-native.config.js | 12 ++ packages/inmobi/src/index.ts | 29 +++++ packages/inmobi/tsconfig.json | 5 + yarn.lock | 17 +++ 18 files changed, 484 insertions(+), 2 deletions(-) create mode 100644 packages/inmobi/LICENSE create mode 100644 packages/inmobi/README.md create mode 100644 packages/inmobi/RNGoogleMobileAdsAdapterInMobi.podspec create mode 100644 packages/inmobi/__tests__/inmobiAdapter.test.ts create mode 100644 packages/inmobi/android/build.gradle create mode 100644 packages/inmobi/android/src/main/AndroidManifest.xml create mode 100644 packages/inmobi/android/src/main/java/io/invertase/googlemobileads/adapters/inmobi/AdapterPackageMarker.java create mode 100644 packages/inmobi/app.plugin.js create mode 100644 packages/inmobi/ios/RNGoogleMobileAdsAdapterInMobiMarker.m create mode 100644 packages/inmobi/plugin/src/index.ts create mode 100644 packages/inmobi/plugin/tsconfig.json create mode 100644 packages/inmobi/plugin/tsconfig.tsbuildinfo create mode 100644 packages/inmobi/react-native.config.js create mode 100644 packages/inmobi/src/index.ts create mode 100644 packages/inmobi/tsconfig.json diff --git a/jest.config.js b/jest.config.js index 25ea61f3..670ff1df 100644 --- a/jest.config.js +++ b/jest.config.js @@ -11,6 +11,7 @@ module.exports = { '/packages/vungle/lib/', '/packages/moloco/lib/', '/packages/mintegral/lib/', + '/packages/inmobi/lib/', ], setupFiles: ['./jest.setup.ts'], diff --git a/packages/inmobi/LICENSE b/packages/inmobi/LICENSE new file mode 100644 index 00000000..db4814fc --- /dev/null +++ b/packages/inmobi/LICENSE @@ -0,0 +1,32 @@ +Apache-2.0 License +------------------ + +Copyright (c) 2021-present Invertase Limited + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this library except in compliance with the License. + +You may obtain a copy of the Apache-2.0 License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +Creative Commons Attribution 3.0 License +---------------------------------------- + +Copyright (c) 2016-present Invertase Limited + +Documentation and other instructional materials provided for this project +(including on a separate documentation repository or it's documentation website) are +licensed under the Creative Commons Attribution 3.0 License. Code samples/blocks +contained therein are licensed under the Apache License, Version 2.0 (the "License"), as above. + +You may obtain a copy of the Creative Commons Attribution 3.0 License at + + https://creativecommons.org/licenses/by/3.0/ diff --git a/packages/inmobi/README.md b/packages/inmobi/README.md new file mode 100644 index 00000000..cabba642 --- /dev/null +++ b/packages/inmobi/README.md @@ -0,0 +1,70 @@ +# `@react-native-google-mobile-ads/inmobi` + +InMobi **Google Ad Manager / AdMob mediation** adapter package for [`react-native-google-mobile-ads`](https://github.com/invertase/react-native-google-mobile-ads). + +This package links Google’s official InMobi mediation adapter on Android and iOS. It does **not** ship AppLovin MAX, CloudX, or any JS ad APIs — use core `initialize()` / adapter status for discovery. + +## Install + +```bash +yarn add @react-native-google-mobile-ads/inmobi +# peer: react-native-google-mobile-ads +``` + +Autolinking pulls in the native mediation artifacts. Rebuild the app after install. + +## Native adapter class names (GAM / AdMob UI) + +| Platform | Class name | +| -------- | ---------- | +| Android | `com.google.ads.mediation.inmobi.InMobiMediationAdapter` | +| iOS | `GADMediationAdapterInMobi` | + +Also exported from JS: + +```ts +import { + nativeAdapterClassName, + networkSlug, +} from '@react-native-google-mobile-ads/inmobi'; +``` + +## Mediation dependencies (pinned in this package) + +| Platform | Coordinate | Version pin | +| -------- | ---------- | ----------- | +| Android | `com.google.ads.mediation:inmobi` | `11.4.1.1` | +| iOS | CocoaPods `GoogleMobileAdsMediationInMobi` | `11.4.1.0` | + +Citations (verify at upgrade time): + +- Android (GAM): https://developers.google.com/ad-manager/mobile-ads-sdk/android/mediation/inmobi +- iOS (GAM): https://developers.google.com/ad-manager/mobile-ads-sdk/ios/mediation/inmobi +- Google Maven: `com.google.ads.mediation:inmobi` (`latest` / `release` = `11.4.1.1`) +- CocoaPods: https://cocoapods.org/pods/GoogleMobileAdsMediationInMobi +- Android host SDK: `com.inmobi.monetization:inmobi-ads-kotlin` on Maven Central (pulled transitively) + +Core continues to own `play-services-ads` / `Google-Mobile-Ads-SDK`. This package does not re-pin the GMA SDK. + +iOS platform floor is **13.0** (InMobi mediation guide prerequisites / CocoaPods podspec). Android minSdk is **23** (guide prerequisites). + +## Expo (optional) + +```js +// app.json / app.config.js plugins +[ + '@react-native-google-mobile-ads/inmobi', + { + // Pass InMobi SKAdNetwork IDs from InMobi’s current docs + skAdNetworkItems: [/* ... */], + }, +] +``` + +App IDs stay in the core Expo plugin. + +## Out of scope + +- AppLovin MAX host SDK +- CloudX / other non-GAM hosts +- Fyber/DT Exchange (no Google GAM adapter — do not invent) diff --git a/packages/inmobi/RNGoogleMobileAdsAdapterInMobi.podspec b/packages/inmobi/RNGoogleMobileAdsAdapterInMobi.podspec new file mode 100644 index 00000000..df65bece --- /dev/null +++ b/packages/inmobi/RNGoogleMobileAdsAdapterInMobi.podspec @@ -0,0 +1,35 @@ +require 'json' + +package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) +gam_adapter = package['gamAdapter'] +mediation_pod = gam_adapter['iosPod'] +mediation_version = package['sdkVersions']['ios']['googleMobileAdsMediation'] + +Pod::Spec.new do |s| + s.name = "RNGoogleMobileAdsAdapterInMobi" + s.version = package["version"] + s.summary = package["description"] + s.description = package["description"] + s.homepage = "https://github.com/invertase/react-native-google-mobile-ads" + s.license = package["license"] + s.authors = "Invertase Limited" + s.source = { + :git => "#{package["repository"]["url"]}.git", + :tag => "v#{s.version}" + } + # InMobi mediation adapter requires iOS 13.0+ (guide prerequisites + CocoaPods podspec). + # https://developers.google.com/ad-manager/mobile-ads-sdk/ios/mediation/inmobi + s.platforms = { :ios => "13.0" } + s.source_files = "ios/**/*.{h,m,mm,swift}" + + if respond_to?(:install_modules_dependencies, true) + install_modules_dependencies(s) + else + s.dependency "React-Core" + end + + # Google-published InMobi GAM mediation adapter. + # https://developers.google.com/ad-manager/mobile-ads-sdk/ios/mediation/inmobi + # https://cocoapods.org/pods/GoogleMobileAdsMediationInMobi + s.dependency mediation_pod, mediation_version +end diff --git a/packages/inmobi/__tests__/inmobiAdapter.test.ts b/packages/inmobi/__tests__/inmobiAdapter.test.ts new file mode 100644 index 00000000..5b84cc92 --- /dev/null +++ b/packages/inmobi/__tests__/inmobiAdapter.test.ts @@ -0,0 +1,20 @@ +import { nativeAdapterClassName, networkSlug } from '../src'; + +describe('@react-native-google-mobile-ads/inmobi public surface', () => { + it('exports networkSlug inmobi', () => { + expect(networkSlug).toBe('inmobi'); + }); + + it('exports documented GAM mediation adapter class names', () => { + expect(nativeAdapterClassName).toEqual({ + android: 'com.google.ads.mediation.inmobi.InMobiMediationAdapter', + ios: 'GADMediationAdapterInMobi', + }); + }); + + it('does not invent JS ad APIs on the package surface', () => { + // eslint-disable-next-line @typescript-eslint/no-require-imports + const surface = require('../src') as Record; + expect(Object.keys(surface).sort()).toEqual(['nativeAdapterClassName', 'networkSlug']); + }); +}); diff --git a/packages/inmobi/android/build.gradle b/packages/inmobi/android/build.gradle new file mode 100644 index 00000000..2ca0933a --- /dev/null +++ b/packages/inmobi/android/build.gradle @@ -0,0 +1,67 @@ +import groovy.json.JsonSlurper + +buildscript { + ext.getExtOrDefault = { name, fallback -> + return rootProject.ext.has(name) ? rootProject.ext.get(name) : fallback + } + + // AGP is only required when opening this android/ folder stand-alone. + if (project == rootProject) { + repositories { + google() + mavenCentral() + } + dependencies { + classpath("com.android.tools.build:gradle:8.1.1") + } + } +} + +apply plugin: "com.android.library" + +def packageJson = new JsonSlurper().parseText(file("${projectDir}/../package.json").text) +def gamAdapter = packageJson.gamAdapter +def sdkVersions = packageJson.sdkVersions.android +def mediationArtifact = "${gamAdapter.androidArtifact}:${sdkVersions.googleMobileAdsMediation}" + +android { + namespace "io.invertase.googlemobileads.adapters.inmobi" + + compileSdkVersion sdkVersions.compileSdk as Integer + + defaultConfig { + minSdkVersion sdkVersions.minSdk as Integer + targetSdkVersion sdkVersions.targetSdk as Integer + buildConfigField "String", "GAM_ADAPTER_NETWORK", "\"${gamAdapter.network}\"" + buildConfigField "String", "GAM_ADAPTER_ANDROID_CLASS", "\"${gamAdapter.androidAdapterClassName}\"" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + buildFeatures { + buildConfig true + } + + sourceSets { + main { + java.srcDirs = ["src/main/java"] + } + } +} + +repositories { + google() + mavenCentral() +} + +dependencies { + // Google-published InMobi GAM mediation adapter. + // Pin: package.json sdkVersions.android.googleMobileAdsMediation (docs/Maven: 11.4.1.1 as of package publish; verify against current docs/Maven). + // Core GMA is provided by react-native-google-mobile-ads; do not re-pin it here. + // InMobi SDK host (`com.inmobi.monetization:inmobi-ads-kotlin`) is on Maven Central. + // https://developers.google.com/ad-manager/mobile-ads-sdk/android/mediation/inmobi + api mediationArtifact +} diff --git a/packages/inmobi/android/src/main/AndroidManifest.xml b/packages/inmobi/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..b1dd18ee --- /dev/null +++ b/packages/inmobi/android/src/main/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + diff --git a/packages/inmobi/android/src/main/java/io/invertase/googlemobileads/adapters/inmobi/AdapterPackageMarker.java b/packages/inmobi/android/src/main/java/io/invertase/googlemobileads/adapters/inmobi/AdapterPackageMarker.java new file mode 100644 index 00000000..32e55646 --- /dev/null +++ b/packages/inmobi/android/src/main/java/io/invertase/googlemobileads/adapters/inmobi/AdapterPackageMarker.java @@ -0,0 +1,10 @@ +package io.invertase.googlemobileads.adapters.inmobi; + +/** + * Package marker so the Android library module has a compilable source root. + * This package does not expose a TurboModule — mediation is linked via Gradle + * ({@code com.google.ads.mediation:inmobi}). + */ +public final class AdapterPackageMarker { + private AdapterPackageMarker() {} +} diff --git a/packages/inmobi/app.plugin.js b/packages/inmobi/app.plugin.js new file mode 100644 index 00000000..3c7d11b6 --- /dev/null +++ b/packages/inmobi/app.plugin.js @@ -0,0 +1 @@ +module.exports = require('./plugin/build'); diff --git a/packages/inmobi/ios/RNGoogleMobileAdsAdapterInMobiMarker.m b/packages/inmobi/ios/RNGoogleMobileAdsAdapterInMobiMarker.m new file mode 100644 index 00000000..3d3a3392 --- /dev/null +++ b/packages/inmobi/ios/RNGoogleMobileAdsAdapterInMobiMarker.m @@ -0,0 +1,11 @@ +#import + +/** + * Package marker so the podspec has a compilable source file. + * Mediation is linked via the GoogleMobileAdsMediationInMobi pod dependency. + */ +@interface RNGoogleMobileAdsAdapterInMobiMarker : NSObject +@end + +@implementation RNGoogleMobileAdsAdapterInMobiMarker +@end diff --git a/packages/inmobi/package.json b/packages/inmobi/package.json index 1a5ea55f..8348800f 100644 --- a/packages/inmobi/package.json +++ b/packages/inmobi/package.json @@ -1,7 +1,7 @@ { "name": "@react-native-google-mobile-ads/inmobi", "version": "16.5.0", - "description": "Empty package shell for the InMobi Google Mobile Ads (GAM) mediation adapter. Product adapter content is out of scope for this shell (see A-0 / ADAPT-02).", + "description": "InMobi Google Mobile Ads (GAM) mediation adapter for react-native-google-mobile-ads. Links the official Google mediation adapter on Android and iOS; does not add JS ad APIs.", "author": "Invertase (http://invertase.io)", "license": "Apache-2.0", "repository": { @@ -17,8 +17,112 @@ "gam", "inmobi" ], - "files": [], + "main": "lib/commonjs/index.js", + "module": "lib/module/index.js", + "react-native": "src/index.ts", + "types": "lib/typescript/commonjs/index.d.ts", + "exports": { + ".": { + "react-native": { + "types": "./lib/typescript/module/index.d.ts", + "default": "./src/index.ts" + }, + "source": { + "types": "./lib/typescript/module/index.d.ts", + "default": "./src/index.ts" + }, + "import": { + "types": "./lib/typescript/module/index.d.ts", + "default": "./lib/module/index.js" + }, + "require": { + "types": "./lib/typescript/commonjs/index.d.ts", + "default": "./lib/commonjs/index.js" + }, + "default": "./lib/commonjs/index.js" + }, + "./app.plugin": "./app.plugin.js", + "./app.plugin.js": "./app.plugin.js", + "./package.json": "./package.json" + }, + "files": [ + "/android/", + "/ios/", + "/lib/", + "/plugin/", + "/src/", + "/LICENSE", + "/RNGoogleMobileAdsAdapterInMobi.podspec", + "/app.plugin.js", + "/react-native.config.js" + ], "publishConfig": { "access": "public" + }, + "gamAdapter": { + "network": "inmobi", + "androidArtifact": "com.google.ads.mediation:inmobi", + "androidAdapterClassName": "com.google.ads.mediation.inmobi.InMobiMediationAdapter", + "iosPod": "GoogleMobileAdsMediationInMobi", + "iosAdapterClassName": "GADMediationAdapterInMobi" + }, + "sdkVersions": { + "ios": { + "googleMobileAdsMediation": "11.4.1.0" + }, + "android": { + "minSdk": 23, + "targetSdk": 34, + "compileSdk": 34, + "buildTools": "34.0.0", + "googleMobileAdsMediation": "11.4.1.1" + } + }, + "react-native-builder-bob": { + "source": "src", + "output": "lib", + "targets": [ + [ + "commonjs", + { + "esm": true + } + ], + [ + "module", + { + "esm": true + } + ], + "typescript" + ] + }, + "scripts": { + "prepare": "yarn build && yarn build:plugin", + "build": "bob build", + "build:clean": "rimraf android/build && rimraf ios/build && rimraf lib && rimraf plugin/build", + "build:plugin": "yarn tsc --build plugin" + }, + "peerDependencies": { + "@expo/config-plugins": ">=5.0.0", + "expo": ">=47.0.0", + "react": "*", + "react-native": "*", + "react-native-google-mobile-ads": "*" + }, + "peerDependenciesMeta": { + "@expo/config-plugins": { + "optional": true + }, + "expo": { + "optional": true + } + }, + "devDependencies": { + "@expo/config-plugins": "^54.0.4", + "react-native-builder-bob": "^0.40.13", + "react-native-google-mobile-ads": "workspace:*", + "rimraf": "^6.0.1", + "typescript": "^5.9.3" } } diff --git a/packages/inmobi/plugin/src/index.ts b/packages/inmobi/plugin/src/index.ts new file mode 100644 index 00000000..187f87ee --- /dev/null +++ b/packages/inmobi/plugin/src/index.ts @@ -0,0 +1,52 @@ +import { ConfigPlugin, withInfoPlist, withPlugins } from '@expo/config-plugins'; + +type PluginParameters = { + /** + * InMobi SKAdNetwork identifiers to merge into Info.plist. + * Core's Expo plugin owns app IDs; this adapter only merges mediation network rows. + * Pass identifiers from InMobi's current SKAdNetwork documentation at integrate time. + */ + skAdNetworkItems?: string[]; +}; + +const withAdapterSkAdNetworkItems: ConfigPlugin = ( + config, + skAdNetworkItems, +) => { + if (skAdNetworkItems === undefined || skAdNetworkItems.length === 0) { + return config; + } + + return withInfoPlist(config, config => { + config.modResults.SKAdNetworkItems = config.modResults.SKAdNetworkItems ?? []; + + const existingIdentifiers = config.modResults.SKAdNetworkItems.map( + (item: { SKAdNetworkIdentifier: string }) => item.SKAdNetworkIdentifier, + ); + + const missingIdentifiers = skAdNetworkItems.filter( + identifier => !existingIdentifiers.includes(identifier), + ); + + config.modResults.SKAdNetworkItems.push( + ...missingIdentifiers.map(identifier => ({ + SKAdNetworkIdentifier: identifier, + })), + ); + + return config; + }); +}; + +/** + * Optional Expo config plugin for `@react-native-google-mobile-ads/inmobi`. + * Does not set GMA app IDs (core plugin). + */ +const withRNGoogleMobileAdsAdapterInMobi: ConfigPlugin = ( + config, + { skAdNetworkItems } = {}, +) => { + return withPlugins(config, [[withAdapterSkAdNetworkItems, skAdNetworkItems]]); +}; + +export default withRNGoogleMobileAdsAdapterInMobi; diff --git a/packages/inmobi/plugin/tsconfig.json b/packages/inmobi/plugin/tsconfig.json new file mode 100644 index 00000000..354bddb4 --- /dev/null +++ b/packages/inmobi/plugin/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "expo-module-scripts/tsconfig.plugin", + "compilerOptions": { + "outDir": "build", + "rootDir": "src" + }, + "include": ["./src"], + "exclude": ["**/__mocks__/*", "**/__tests__/*"] +} diff --git a/packages/inmobi/plugin/tsconfig.tsbuildinfo b/packages/inmobi/plugin/tsconfig.tsbuildinfo new file mode 100644 index 00000000..0597b1c4 --- /dev/null +++ b/packages/inmobi/plugin/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./src/index.ts"],"version":"5.9.3"} \ No newline at end of file diff --git a/packages/inmobi/react-native.config.js b/packages/inmobi/react-native.config.js new file mode 100644 index 00000000..382dade8 --- /dev/null +++ b/packages/inmobi/react-native.config.js @@ -0,0 +1,12 @@ +/** + * Autolinking entry for the GAM adapter package. + * Adapters ship native mediation artifacts only — no TurboModule / Fabric view. + */ +module.exports = { + dependency: { + platforms: { + android: {}, + ios: {}, + }, + }, +}; diff --git a/packages/inmobi/src/index.ts b/packages/inmobi/src/index.ts new file mode 100644 index 00000000..feeed9cc --- /dev/null +++ b/packages/inmobi/src/index.ts @@ -0,0 +1,29 @@ +/** + * InMobi GAM mediation adapter package public surface. + * + * Exports adapter class-name constants for GAM UI paste / docs only. Does **not** + * add JS ad APIs — core `initialize()` adapter statuses remain the discovery + * signal (API design §5.3). + * + * Native mediation is linked via Google's published adapter: + * - Android: `com.google.ads.mediation:inmobi` + * - iOS: `GoogleMobileAdsMediationInMobi` + */ + +export type NativeAdapterClassName = { + android: string; + ios: string; +}; + +/** Network slug for this Character GAM adapter package. */ +export const networkSlug = 'inmobi' as const; + +/** + * Fully-qualified native mediation adapter class names for AdMob / GAM console paste. + * @see https://developers.google.com/ad-manager/mobile-ads-sdk/android/mediation/inmobi + * @see https://developers.google.com/ad-manager/mobile-ads-sdk/ios/mediation/inmobi + */ +export const nativeAdapterClassName: NativeAdapterClassName = { + android: 'com.google.ads.mediation.inmobi.InMobiMediationAdapter', + ios: 'GADMediationAdapterInMobi', +}; diff --git a/packages/inmobi/tsconfig.json b/packages/inmobi/tsconfig.json new file mode 100644 index 00000000..a841e12a --- /dev/null +++ b/packages/inmobi/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.packages.base.json", + "include": ["src"], + "exclude": ["node_modules", "**/*.spec.ts", "**/*.test.ts"] +} diff --git a/yarn.lock b/yarn.lock index e405845e..669467b0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6124,6 +6124,23 @@ __metadata: "@react-native-google-mobile-ads/inmobi@workspace:packages/inmobi": version: 0.0.0-use.local resolution: "@react-native-google-mobile-ads/inmobi@workspace:packages/inmobi" + dependencies: + "@expo/config-plugins": "npm:^54.0.4" + react-native-builder-bob: "npm:^0.40.13" + react-native-google-mobile-ads: "workspace:*" + rimraf: "npm:^6.0.1" + typescript: "npm:^5.9.3" + peerDependencies: + "@expo/config-plugins": ">=5.0.0" + expo: ">=47.0.0" + react: "*" + react-native: "*" + react-native-google-mobile-ads: "*" + peerDependenciesMeta: + "@expo/config-plugins": + optional: true + expo: + optional: true languageName: unknown linkType: soft From 75d3c9a0da96090645b96fe195ba3b8681eb5edc Mon Sep 17 00:00:00 2001 From: Mike Hardy Date: Fri, 4 Sep 2026 10:53:43 -0500 Subject: [PATCH 11/11] feat(adapters): add yandex GAM mediation package --- jest.config.js | 1 + packages/yandex/LICENSE | 32 +++++ packages/yandex/README.md | 96 ++++++++++++++ .../RNGoogleMobileAdsAdapterYandex.podspec | 37 ++++++ .../yandex/__tests__/yandexAdapter.test.ts | 25 ++++ packages/yandex/android/build.gradle | 65 ++++++++++ .../android/src/main/AndroidManifest.xml | 6 + .../adapters/yandex/AdapterPackageMarker.java | 10 ++ packages/yandex/app.plugin.js | 1 + .../RNGoogleMobileAdsAdapterYandexMarker.m | 12 ++ packages/yandex/package.json | 117 +++++++++++++++++- packages/yandex/plugin/src/index.ts | 52 ++++++++ packages/yandex/plugin/tsconfig.json | 9 ++ packages/yandex/plugin/tsconfig.tsbuildinfo | 1 + packages/yandex/react-native.config.js | 13 ++ packages/yandex/src/index.ts | 51 ++++++++ packages/yandex/tsconfig.json | 5 + yarn.lock | 17 +++ 18 files changed, 548 insertions(+), 2 deletions(-) create mode 100644 packages/yandex/LICENSE create mode 100644 packages/yandex/README.md create mode 100644 packages/yandex/RNGoogleMobileAdsAdapterYandex.podspec create mode 100644 packages/yandex/__tests__/yandexAdapter.test.ts create mode 100644 packages/yandex/android/build.gradle create mode 100644 packages/yandex/android/src/main/AndroidManifest.xml create mode 100644 packages/yandex/android/src/main/java/io/invertase/googlemobileads/adapters/yandex/AdapterPackageMarker.java create mode 100644 packages/yandex/app.plugin.js create mode 100644 packages/yandex/ios/RNGoogleMobileAdsAdapterYandexMarker.m create mode 100644 packages/yandex/plugin/src/index.ts create mode 100644 packages/yandex/plugin/tsconfig.json create mode 100644 packages/yandex/plugin/tsconfig.tsbuildinfo create mode 100644 packages/yandex/react-native.config.js create mode 100644 packages/yandex/src/index.ts create mode 100644 packages/yandex/tsconfig.json diff --git a/jest.config.js b/jest.config.js index 670ff1df..991bcdc8 100644 --- a/jest.config.js +++ b/jest.config.js @@ -12,6 +12,7 @@ module.exports = { '/packages/moloco/lib/', '/packages/mintegral/lib/', '/packages/inmobi/lib/', + '/packages/yandex/lib/', ], setupFiles: ['./jest.setup.ts'], diff --git a/packages/yandex/LICENSE b/packages/yandex/LICENSE new file mode 100644 index 00000000..db4814fc --- /dev/null +++ b/packages/yandex/LICENSE @@ -0,0 +1,32 @@ +Apache-2.0 License +------------------ + +Copyright (c) 2021-present Invertase Limited + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this library except in compliance with the License. + +You may obtain a copy of the Apache-2.0 License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +Creative Commons Attribution 3.0 License +---------------------------------------- + +Copyright (c) 2016-present Invertase Limited + +Documentation and other instructional materials provided for this project +(including on a separate documentation repository or it's documentation website) are +licensed under the Creative Commons Attribution 3.0 License. Code samples/blocks +contained therein are licensed under the Apache License, Version 2.0 (the "License"), as above. + +You may obtain a copy of the Creative Commons Attribution 3.0 License at + + https://creativecommons.org/licenses/by/3.0/ diff --git a/packages/yandex/README.md b/packages/yandex/README.md new file mode 100644 index 00000000..d2ffd2a8 --- /dev/null +++ b/packages/yandex/README.md @@ -0,0 +1,96 @@ +# `@react-native-google-mobile-ads/yandex` + +Yandex **Google Ad Manager / AdMob mediation** adapter package for [`react-native-google-mobile-ads`](https://github.com/invertase/react-native-google-mobile-ads). + +## Platform disposition: iOS-only + +Google does **not** publish an open-source / versioned Yandex mediation adapter: + +- No CocoaPods `GoogleMobileAdsMediationYandex` ([CocoaPods 404](https://cocoapods.org/pods/GoogleMobileAdsMediationYandex)) +- No Maven `com.google.ads.mediation:yandex` (Google Maven 404) +- Yandex is absent from AdMob / GAM [choose ad sources](https://developers.google.com/admob/ios/choose-networks) lists +- Google’s `/mediation/yandex` guide URLs return 404 (AdMob + GAM, Android + iOS) + +Character inventory treats the **Yandex GAM adapter as iOS-focused** (Android none), consistent with the Google publication gaps above. + +This package therefore ships **iOS only**, linking Yandex’s own published AdMob custom-event adapters — **not** Yandex Mobile Mediation as a host (`GoogleYandexMobileAdsAdapters` / MAX / CloudX are out of scope). + +Yandex does publish an Android AdMob adapter (`com.yandex.ads.adapter:admob-mobileads` on Maven Central). It is **not** linked here because Google has no official Android Yandex GAM mediation artifact and Character lists Android coverage for this network as none. + +## Install + +```bash +yarn add @react-native-google-mobile-ads/yandex +# peer: react-native-google-mobile-ads +``` + +Autolinking pulls in the iOS mediation pod. Rebuild the app after install. Android autolinking is disabled for this package. + +## Native adapter class names (AdMob / GAM UI — custom events) + +Yandex is added to AdMob / GAM mediation as a **custom event** (not a single `GADMediationAdapter*` class). Paste the class name for the ad format: + +| Platform | Format | Class name | +| -------- | ------ | ---------- | +| iOS | Banner | `YMAAdMobCustomEventBanner` | +| iOS | Interstitial | `YMAAdMobCustomEventInterstitial` | +| iOS | Rewarded | `YMAAdMobCustomEventRewarded` | +| iOS | Native | `YMAAdMobCustomEventNative` | +| Android | — | **not shipped** | + +Also exported from JS: + +```ts +import { + nativeAdapterClassName, + networkSlug, +} from '@react-native-google-mobile-ads/yandex'; + +// nativeAdapterClassName.android === null +// nativeAdapterClassName.ios.banner === 'YMAAdMobCustomEventBanner' +``` + +Custom-event parameter JSON (Yandex Ad Unit ID) must still be configured in the AdMob / GAM UI per Yandex’s guide, e.g. `{"adUnitId": "R-M-XXXXXX-X"}`. + +## Mediation dependencies (pinned in this package) + +| Platform | Coordinate | Version pin | +| -------- | ---------- | ----------- | +| iOS | CocoaPods `YandexMobileAdsAdMobAdapters` | `8.4.0.0` | +| Android | — | **not linked** | + +Citations (verify at upgrade time): + +- iOS AdMob (Yandex → Google mediation): https://ads.yandex.com/helpcenter/en/dev/ios/admob-third (`pod 'YandexMobileAdsAdMobAdapters', '8.0.0.0'` example; pin uses current CocoaPods Trunk latest) +- CocoaPods: https://cocoapods.org/pods/YandexMobileAdsAdMobAdapters (Trunk latest `8.4.0.0` as of 2026-09-04) +- CocoaPods Specs podspec `8.4.0.0`: depends on `YandexMobileAds ~> 8.4.0`, `Google-Mobile-Ads-SDK ~> 13.6.0`, `platforms.ios` `13.0` +- Android AdMob adapter exists at Maven Central but is **not** used here: https://central.sonatype.com/artifact/com.yandex.ads.adapter/admob-mobileads (`8.4.0.0`); Yandex guide: https://ads.yandex.com/helpcenter/en/dev/android/admob-third +- Do **not** use `GoogleYandexMobileAdsAdapters` — that enables Google **inside Yandex Mobile Mediation** (Yandex-as-host), which is out of RNGMA adapter scope + +Core continues to own `play-services-ads` / `Google-Mobile-Ads-SDK`. This package does not re-pin the GMA SDK. + +**GMA compatibility note:** `YandexMobileAdsAdMobAdapters@8.4.0.0` requires `Google-Mobile-Ads-SDK ~> 13.6.0`. Core currently pins iOS GMA `13.5.0` — apps that enable this adapter may need a core GMA bump or supported override (same class of pin tension documented for other networks in Character intake). + +iOS platform floor is **13.0** (Yandex AdMob adapters podspec). + +## Expo (optional) + +```js +// app.json / app.config.js plugins +[ + '@react-native-google-mobile-ads/yandex', + { + // Pass Yandex SKAdNetwork IDs from Yandex’s current docs + skAdNetworkItems: [/* ... */], + }, +] +``` + +App IDs stay in the core Expo plugin. + +## Out of scope + +- Yandex Mobile Mediation **host** SDK (`YandexMobileAdsMediation`, `GoogleYandexMobileAdsAdapters`) +- AppLovin MAX host SDK +- CloudX / other non-GAM hosts +- Inventing a Google-published Android Yandex GAM artifact diff --git a/packages/yandex/RNGoogleMobileAdsAdapterYandex.podspec b/packages/yandex/RNGoogleMobileAdsAdapterYandex.podspec new file mode 100644 index 00000000..005e6349 --- /dev/null +++ b/packages/yandex/RNGoogleMobileAdsAdapterYandex.podspec @@ -0,0 +1,37 @@ +require 'json' + +package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) +gam_adapter = package['gamAdapter'] +mediation_pod = gam_adapter['iosPod'] +mediation_version = package['sdkVersions']['ios']['googleMobileAdsMediation'] + +Pod::Spec.new do |s| + s.name = "RNGoogleMobileAdsAdapterYandex" + s.version = package["version"] + s.summary = package["description"] + s.description = package["description"] + s.homepage = "https://github.com/invertase/react-native-google-mobile-ads" + s.license = package["license"] + s.authors = "Invertase Limited" + s.source = { + :git => "#{package["repository"]["url"]}.git", + :tag => "v#{s.version}" + } + # Yandex AdMob adapters require iOS 13.0+ (CocoaPods podspec platforms.ios). + # https://ads.yandex.com/helpcenter/en/dev/ios/admob-third + # https://cocoapods.org/pods/YandexMobileAdsAdMobAdapters + s.platforms = { :ios => "13.0" } + s.source_files = "ios/**/*.{h,m,mm,swift}" + + if respond_to?(:install_modules_dependencies, true) + install_modules_dependencies(s) + else + s.dependency "React-Core" + end + + # Yandex-published AdMob custom-event adapters (Google does not publish + # GoogleMobileAdsMediationYandex). Not GoogleYandexMobileAdsAdapters (Yandex-as-host). + # https://ads.yandex.com/helpcenter/en/dev/ios/admob-third + # https://cocoapods.org/pods/YandexMobileAdsAdMobAdapters + s.dependency mediation_pod, mediation_version +end diff --git a/packages/yandex/__tests__/yandexAdapter.test.ts b/packages/yandex/__tests__/yandexAdapter.test.ts new file mode 100644 index 00000000..de4d5d43 --- /dev/null +++ b/packages/yandex/__tests__/yandexAdapter.test.ts @@ -0,0 +1,25 @@ +import { nativeAdapterClassName, networkSlug } from '../src'; + +describe('@react-native-google-mobile-ads/yandex public surface', () => { + it('exports networkSlug yandex', () => { + expect(networkSlug).toBe('yandex'); + }); + + it('exports documented iOS AdMob custom-event class names and null Android', () => { + expect(nativeAdapterClassName).toEqual({ + android: null, + ios: { + banner: 'YMAAdMobCustomEventBanner', + interstitial: 'YMAAdMobCustomEventInterstitial', + rewarded: 'YMAAdMobCustomEventRewarded', + native: 'YMAAdMobCustomEventNative', + }, + }); + }); + + it('does not invent JS ad APIs on the package surface', () => { + // eslint-disable-next-line @typescript-eslint/no-require-imports + const surface = require('../src') as Record; + expect(Object.keys(surface).sort()).toEqual(['nativeAdapterClassName', 'networkSlug']); + }); +}); diff --git a/packages/yandex/android/build.gradle b/packages/yandex/android/build.gradle new file mode 100644 index 00000000..d240d34e --- /dev/null +++ b/packages/yandex/android/build.gradle @@ -0,0 +1,65 @@ +import groovy.json.JsonSlurper + +buildscript { + ext.getExtOrDefault = { name, fallback -> + return rootProject.ext.has(name) ? rootProject.ext.get(name) : fallback + } + + // AGP is only required when opening this android/ folder stand-alone. + if (project == rootProject) { + repositories { + google() + mavenCentral() + } + dependencies { + classpath("com.android.tools.build:gradle:8.1.1") + } + } +} + +apply plugin: "com.android.library" + +def packageJson = new JsonSlurper().parseText(file("${projectDir}/../package.json").text) +def gamAdapter = packageJson.gamAdapter +def sdkVersions = packageJson.sdkVersions.android + +android { + // A-0 layout stub only: this package is iOS-only (see package.json gamAdapter.platforms). + // Google publishes no com.google.ads.mediation:yandex; Android autolinking is disabled. + namespace "io.invertase.googlemobileads.adapters.yandex" + + compileSdkVersion sdkVersions.compileSdk as Integer + + defaultConfig { + minSdkVersion sdkVersions.minSdk as Integer + targetSdkVersion sdkVersions.targetSdk as Integer + buildConfigField "String", "GAM_ADAPTER_NETWORK", "\"${gamAdapter.network}\"" + buildConfigField "String", "GAM_ADAPTER_ANDROID_SHIPPED", "\"false\"" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + buildFeatures { + buildConfig true + } + + sourceSets { + main { + java.srcDirs = ["src/main/java"] + } + } +} + +repositories { + google() + mavenCentral() +} + +dependencies { + // Intentionally empty: no Android Yandex GAM mediation artifact is linked. + // Yandex publishes com.yandex.ads.adapter:admob-mobileads, but Google has no + // official Android Yandex GAM adapter and Character inventory is iOS-only. +} diff --git a/packages/yandex/android/src/main/AndroidManifest.xml b/packages/yandex/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..0d186fd4 --- /dev/null +++ b/packages/yandex/android/src/main/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + diff --git a/packages/yandex/android/src/main/java/io/invertase/googlemobileads/adapters/yandex/AdapterPackageMarker.java b/packages/yandex/android/src/main/java/io/invertase/googlemobileads/adapters/yandex/AdapterPackageMarker.java new file mode 100644 index 00000000..6573b861 --- /dev/null +++ b/packages/yandex/android/src/main/java/io/invertase/googlemobileads/adapters/yandex/AdapterPackageMarker.java @@ -0,0 +1,10 @@ +package io.invertase.googlemobileads.adapters.yandex; + +/** + * Package marker so the Android library module has a compilable source root. + * This package does not expose a TurboModule and does not link + * an Android mediation artifact (iOS-only Yandex AdMob adapters). + */ +public final class AdapterPackageMarker { + private AdapterPackageMarker() {} +} diff --git a/packages/yandex/app.plugin.js b/packages/yandex/app.plugin.js new file mode 100644 index 00000000..3c7d11b6 --- /dev/null +++ b/packages/yandex/app.plugin.js @@ -0,0 +1 @@ +module.exports = require('./plugin/build'); diff --git a/packages/yandex/ios/RNGoogleMobileAdsAdapterYandexMarker.m b/packages/yandex/ios/RNGoogleMobileAdsAdapterYandexMarker.m new file mode 100644 index 00000000..4cfd5a22 --- /dev/null +++ b/packages/yandex/ios/RNGoogleMobileAdsAdapterYandexMarker.m @@ -0,0 +1,12 @@ +#import + +/** + * Package marker so the podspec has a compilable source file. + * Mediation is linked via the YandexMobileAdsAdMobAdapters pod dependency + * (Yandex-published AdMob custom-event adapters; iOS-only). + */ +@interface RNGoogleMobileAdsAdapterYandexMarker : NSObject +@end + +@implementation RNGoogleMobileAdsAdapterYandexMarker +@end diff --git a/packages/yandex/package.json b/packages/yandex/package.json index b8d8ed51..cb669f54 100644 --- a/packages/yandex/package.json +++ b/packages/yandex/package.json @@ -1,7 +1,7 @@ { "name": "@react-native-google-mobile-ads/yandex", "version": "16.5.0", - "description": "Empty package shell for the Yandex Google Mobile Ads (GAM) mediation adapter. Product adapter content is out of scope for this shell (see A-0 / ADAPT-02).", + "description": "Yandex Google Mobile Ads (GAM/AdMob) mediation adapter for react-native-google-mobile-ads. Links Yandex’s published AdMob custom-event adapters on iOS only; does not add JS ad APIs. Not Yandex-as-host / MAX / CloudX.", "author": "Invertase (http://invertase.io)", "license": "Apache-2.0", "repository": { @@ -17,8 +17,121 @@ "gam", "yandex" ], - "files": [], + "main": "lib/commonjs/index.js", + "module": "lib/module/index.js", + "react-native": "src/index.ts", + "types": "lib/typescript/commonjs/index.d.ts", + "exports": { + ".": { + "react-native": { + "types": "./lib/typescript/module/index.d.ts", + "default": "./src/index.ts" + }, + "source": { + "types": "./lib/typescript/module/index.d.ts", + "default": "./src/index.ts" + }, + "import": { + "types": "./lib/typescript/module/index.d.ts", + "default": "./lib/module/index.js" + }, + "require": { + "types": "./lib/typescript/commonjs/index.d.ts", + "default": "./lib/commonjs/index.js" + }, + "default": "./lib/commonjs/index.js" + }, + "./app.plugin": "./app.plugin.js", + "./app.plugin.js": "./app.plugin.js", + "./package.json": "./package.json" + }, + "files": [ + "/android/", + "/ios/", + "/lib/", + "/plugin/", + "/src/", + "/LICENSE", + "/RNGoogleMobileAdsAdapterYandex.podspec", + "/app.plugin.js", + "/react-native.config.js" + ], "publishConfig": { "access": "public" + }, + "gamAdapter": { + "network": "yandex", + "platforms": [ + "ios" + ], + "androidArtifact": null, + "androidAdapterClassName": null, + "iosPod": "YandexMobileAdsAdMobAdapters", + "iosAdapterClassNames": { + "banner": "YMAAdMobCustomEventBanner", + "interstitial": "YMAAdMobCustomEventInterstitial", + "rewarded": "YMAAdMobCustomEventRewarded", + "native": "YMAAdMobCustomEventNative" + }, + "publisher": "yandex", + "integration": "custom-event" + }, + "sdkVersions": { + "ios": { + "googleMobileAdsMediation": "8.4.0.0" + }, + "android": { + "minSdk": 23, + "targetSdk": 34, + "compileSdk": 34, + "buildTools": "34.0.0" + } + }, + "react-native-builder-bob": { + "source": "src", + "output": "lib", + "targets": [ + [ + "commonjs", + { + "esm": true + } + ], + [ + "module", + { + "esm": true + } + ], + "typescript" + ] + }, + "scripts": { + "prepare": "yarn build && yarn build:plugin", + "build": "bob build", + "build:clean": "rimraf android/build && rimraf ios/build && rimraf lib && rimraf plugin/build", + "build:plugin": "yarn tsc --build plugin" + }, + "peerDependencies": { + "@expo/config-plugins": ">=5.0.0", + "expo": ">=47.0.0", + "react": "*", + "react-native": "*", + "react-native-google-mobile-ads": "*" + }, + "peerDependenciesMeta": { + "@expo/config-plugins": { + "optional": true + }, + "expo": { + "optional": true + } + }, + "devDependencies": { + "@expo/config-plugins": "^54.0.4", + "react-native-builder-bob": "^0.40.13", + "react-native-google-mobile-ads": "workspace:*", + "rimraf": "^6.0.1", + "typescript": "^5.9.3" } } diff --git a/packages/yandex/plugin/src/index.ts b/packages/yandex/plugin/src/index.ts new file mode 100644 index 00000000..cc89fb1f --- /dev/null +++ b/packages/yandex/plugin/src/index.ts @@ -0,0 +1,52 @@ +import { ConfigPlugin, withInfoPlist, withPlugins } from '@expo/config-plugins'; + +type PluginParameters = { + /** + * Yandex SKAdNetwork identifiers to merge into Info.plist. + * Core's Expo plugin owns app IDs; this adapter only merges mediation network rows. + * Pass identifiers from Yandex’s current SKAdNetwork documentation at integrate time. + */ + skAdNetworkItems?: string[]; +}; + +const withAdapterSkAdNetworkItems: ConfigPlugin = ( + config, + skAdNetworkItems, +) => { + if (skAdNetworkItems === undefined || skAdNetworkItems.length === 0) { + return config; + } + + return withInfoPlist(config, config => { + config.modResults.SKAdNetworkItems = config.modResults.SKAdNetworkItems ?? []; + + const existingIdentifiers = config.modResults.SKAdNetworkItems.map( + (item: { SKAdNetworkIdentifier: string }) => item.SKAdNetworkIdentifier, + ); + + const missingIdentifiers = skAdNetworkItems.filter( + identifier => !existingIdentifiers.includes(identifier), + ); + + config.modResults.SKAdNetworkItems.push( + ...missingIdentifiers.map(identifier => ({ + SKAdNetworkIdentifier: identifier, + })), + ); + + return config; + }); +}; + +/** + * Optional Expo config plugin for `@react-native-google-mobile-ads/yandex`. + * Does not set GMA app IDs (core plugin). + */ +const withRNGoogleMobileAdsAdapterYandex: ConfigPlugin = ( + config, + { skAdNetworkItems } = {}, +) => { + return withPlugins(config, [[withAdapterSkAdNetworkItems, skAdNetworkItems]]); +}; + +export default withRNGoogleMobileAdsAdapterYandex; diff --git a/packages/yandex/plugin/tsconfig.json b/packages/yandex/plugin/tsconfig.json new file mode 100644 index 00000000..354bddb4 --- /dev/null +++ b/packages/yandex/plugin/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "expo-module-scripts/tsconfig.plugin", + "compilerOptions": { + "outDir": "build", + "rootDir": "src" + }, + "include": ["./src"], + "exclude": ["**/__mocks__/*", "**/__tests__/*"] +} diff --git a/packages/yandex/plugin/tsconfig.tsbuildinfo b/packages/yandex/plugin/tsconfig.tsbuildinfo new file mode 100644 index 00000000..0597b1c4 --- /dev/null +++ b/packages/yandex/plugin/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./src/index.ts"],"version":"5.9.3"} \ No newline at end of file diff --git a/packages/yandex/react-native.config.js b/packages/yandex/react-native.config.js new file mode 100644 index 00000000..525be3c4 --- /dev/null +++ b/packages/yandex/react-native.config.js @@ -0,0 +1,13 @@ +/** + * Autolinking entry for the Yandex GAM adapter package. + * iOS-only: Android is disabled (Google has no official Android Yandex GAM artifact; + * Character inventory is iOS-focused). No TurboModule / Fabric view. + */ +module.exports = { + dependency: { + platforms: { + android: null, + ios: {}, + }, + }, +}; diff --git a/packages/yandex/src/index.ts b/packages/yandex/src/index.ts new file mode 100644 index 00000000..0d7773c2 --- /dev/null +++ b/packages/yandex/src/index.ts @@ -0,0 +1,51 @@ +/** + * Yandex GAM / AdMob mediation adapter package public surface. + * + * Exports adapter class-name constants for AdMob / GAM UI paste / docs only. + * Does **not** add JS ad APIs — core `initialize()` adapter statuses remain the + * discovery signal (API design §5.3). + * + * **iOS-only:** Google does not publish `GoogleMobileAdsMediationYandex` / + * `com.google.ads.mediation:yandex`. Character’s GAM adapter inventory is + * iOS-focused. This package links Yandex’s published AdMob custom-event pod + * `YandexMobileAdsAdMobAdapters` (not Yandex-as-host / `GoogleYandexMobileAdsAdapters`). + * + * @see https://ads.yandex.com/helpcenter/en/dev/ios/admob-third + * @see https://cocoapods.org/pods/YandexMobileAdsAdMobAdapters + */ + +/** Per-format iOS custom-event class names for AdMob / GAM console paste. */ +export type YandexIosCustomEventClassNames = { + banner: string; + interstitial: string; + rewarded: string; + native: string; +}; + +/** + * Native adapter class names for this package. + * `android` is `null` — no Android mediation artifact is linked (see README). + */ +export type NativeAdapterClassName = { + android: null; + ios: YandexIosCustomEventClassNames; +}; + +/** Network slug for this Character GAM adapter package. */ +export const networkSlug = 'yandex' as const; + +/** + * Fully-qualified native mediation adapter class names for AdMob / GAM console. + * Yandex integrates as **custom events** (not a single `GADMediationAdapter*` class). + * + * @see https://ads.yandex.com/helpcenter/en/dev/ios/admob-third + */ +export const nativeAdapterClassName: NativeAdapterClassName = { + android: null, + ios: { + banner: 'YMAAdMobCustomEventBanner', + interstitial: 'YMAAdMobCustomEventInterstitial', + rewarded: 'YMAAdMobCustomEventRewarded', + native: 'YMAAdMobCustomEventNative', + }, +}; diff --git a/packages/yandex/tsconfig.json b/packages/yandex/tsconfig.json new file mode 100644 index 00000000..a841e12a --- /dev/null +++ b/packages/yandex/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.packages.base.json", + "include": ["src"], + "exclude": ["node_modules", "**/*.spec.ts", "**/*.test.ts"] +} diff --git a/yarn.lock b/yarn.lock index 669467b0..c22396f7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6262,6 +6262,23 @@ __metadata: "@react-native-google-mobile-ads/yandex@workspace:packages/yandex": version: 0.0.0-use.local resolution: "@react-native-google-mobile-ads/yandex@workspace:packages/yandex" + dependencies: + "@expo/config-plugins": "npm:^54.0.4" + react-native-builder-bob: "npm:^0.40.13" + react-native-google-mobile-ads: "workspace:*" + rimraf: "npm:^6.0.1" + typescript: "npm:^5.9.3" + peerDependencies: + "@expo/config-plugins": ">=5.0.0" + expo: ">=47.0.0" + react: "*" + react-native: "*" + react-native-google-mobile-ads: "*" + peerDependenciesMeta: + "@expo/config-plugins": + optional: true + expo: + optional: true languageName: unknown linkType: soft