Skip to content

Commit 8cf5432

Browse files
committed
feat: updated package to play-age-range-declaration
1 parent 22994a2 commit 8cf5432

40 files changed

+3164
-288
lines changed

.github/ISSUE_TEMPLATE/config.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
blank_issues_enabled: false
22
contact_links:
33
- name: Feature Request 💡
4-
url: https://github.com/Gautham495/react-native-play-age-signals/discussions/new?category=ideas
4+
url: https://github.com/Gautham495/react-native-play-age-range-declaration/discussions/new?category=ideas
55
about: If you have a feature request, please create a new discussion on GitHub.
66
- name: Discussions on GitHub 💬
7-
url: https://github.com/Gautham495/react-native-play-age-signals/discussions
7+
url: https://github.com/Gautham495/react-native-play-age-range-declaration/discussions
88
about: If this library works as promised but you need help, please ask questions there.

CONTRIBUTING.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ The [example app](/example/) demonstrates usage of the library. You need to run
2525

2626
It is configured to use the local version of the library, so any changes you make to the library's source code will be reflected in the example app. Changes to the library's JavaScript code will be reflected in the example app without a rebuild, but native code changes will require a rebuild of the example app.
2727

28-
If you want to use Android Studio or Xcode to edit the native code, you can open the `example/android` or `example/ios` directories respectively in those editors. To edit the Objective-C or Swift files, open `example/ios/PlayAgeSignalsExample.xcworkspace` in Xcode and find the source files at `Pods > Development Pods > react-native-play-age-signals`.
28+
If you want to use Android Studio or Xcode to edit the native code, you can open the `example/android` or `example/ios` directories respectively in those editors. To edit the Objective-C or Swift files, open `example/ios/PlayAgeRangeDeclarationExample.xcworkspace` in Xcode and find the source files at `Pods > Development Pods > react-native-play-age-range-declaration`.
2929

30-
To edit the Java or Kotlin files, open `example/android` in Android studio and find the source files at `react-native-play-age-signals` under `Android`.
30+
To edit the Java or Kotlin files, open `example/android` in Android studio and find the source files at `react-native-play-age-range-declaration` under `Android`.
3131

3232
You can use various commands from the root directory to work with the project.
3333

@@ -52,7 +52,7 @@ yarn example ios
5252
To confirm that the app is running with the new architecture, you can check the Metro logs for a message like this:
5353

5454
```sh
55-
Running "PlayAgeSignalsExample" with {"fabric":true,"initialProps":{"concurrentRoot":true},"rootTag":1}
55+
Running "PlayAgeRangeDeclarationExample" with {"fabric":true,"initialProps":{"concurrentRoot":true},"rootTag":1}
5656
```
5757

5858
Note the `"fabric":true` and `"concurrentRoot":true` properties.

README.md

Lines changed: 116 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,160 +1,197 @@
11
<a href="https://gauthamvijay.com">
22
<picture>
3-
<img alt="react-native-play-age-signals" src="./docs/img/banner.png" />
3+
<img alt="react-native-play-age-range-declaration" src="./docs/img/banner.png" />
44
</picture>
55
</a>
66

7-
# react-native-play-age-signals
7+
# react-native-play-age-range-declaration
88

9-
A **React Native TurboModule** that provides a bridge to the **Google Play Age Signals API**, enabling Android apps to query **user supervision and age verification status** when available via Play Services.
9+
A **React Native TurboModule** providing a unified API for **age-appropriate experiences** on mobile — bridging:
1010

11-
> [!IMPORTANT]
12-
>
13-
> - The Play Age Signals SDK (`com.google.android.play:age-signals`) is currently in **beta** and **not yet fully implemented** by Google.
14-
> - Calling `checkAgeSignals()` will currently throw: java.lang.UnsupportedOperationException: Not yet implemented
15-
> - This library is ready and production-safe — it will begin returning real data **automatically** once Google enables the API in upcoming Play Services updates.
11+
* 🟢 **Google Play Age Signals API** (Android)
12+
* 🔵 **Apple Declared Age Range API** (iOS 26+)
1613

1714
---
1815

1916
## 📦 Installation
2017

21-
```tsx
22-
npm install react-native-play-age-signals
18+
```bash
19+
npm install react-native-play-age-range-declaration
2320
```
2421

2522
### 🚀 Zero Config Setup
2623

27-
- **No manual native setup required.**
28-
This library uses **React Native TurboModules**, so it works out of the box with autolinking — even inside **Expo apps (custom dev clients)**.
24+
***Autolinking supported** (no manual native setup)
25+
* ✅ Works with **React Native New Architecture (TurboModules)**
26+
* ✅ Compatible with **Expo custom dev clients**
2927

3028
---
3129

32-
## 🔗 Reference Links
30+
## 🧠 Overview
3331

34-
- 📘 Google Developer Docs: [Play Age Signals](https://developer.android.com/google/play/age-signals/use-age-signals-api)
35-
- 🧾 Google Support: [Age-Appropriate Ads Requirements](https://support.google.com/googleplay/android-developer/answer/16569691)
32+
| Platform | API Used | Purpose |
33+
| ----------- | ------------------------------------------------------------ | -------------------------------------------------- |
34+
| **Android** | Play Age Signals API (`com.google.android.play:age-signals`) | Detect user supervision / verified status |
35+
| **iOS** | Declared Age Range API (`AgeRangeService.requestAgeRange`) | Get user’s declared age range (e.g., 13-15, 16-17) |
3636

3737
---
3838

39-
## 🇺🇸 Why this library exists
40-
41-
This module was built for **production use** in my work app (US-based), where new **state-level legislation** requires mobile apps to verify the user’s age before displaying certain content or ads.
42-
43-
To help other developers comply with these same laws, I’ve **open-sourced** it here.
39+
## ⚙️ Usage
4440

45-
> The **Google Play Age Signals API** will be the official, privacy-safe method to determine user supervision and age verification status across Android devices in the United States.
41+
```tsx
42+
import { getAgeData } from 'react-native-play-age-range-declaration';
43+
import { useEffect } from 'react';
44+
import { Text, View } from 'react-native';
4645

47-
---
46+
export default function App() {
47+
useEffect(() => {
48+
(async () => {
49+
try {
50+
const result = await getAgeData([13, 16, 18]); // iOS uses gates, Android ignores
51+
console.log('Age Signals:', result);
52+
} catch (err) {
53+
console.error('Failed to fetch Age Signals:', err);
54+
}
55+
})();
56+
}, []);
4857

49-
<picture>
50-
<img alt="Age Verification Bills in US States" src="./docs/img/bills-in-us.png" />
51-
</picture>
58+
return (
59+
<View>
60+
<Text>Check console for Play Age / Declared Range output</Text>
61+
</View>
62+
);
63+
}
64+
```
5265

5366
---
5467

55-
## 🧠 What It Does
68+
## 📱 Platform Outputs
5669

57-
Once the Play Services feature is live, the module will expose the following fields via `getPlayAgeSignals()`:
70+
### 🟢 **Android – Play Age Signals**
5871

59-
```
72+
```json
6073
{
61-
installId: string | null;
62-
userStatus: string | null;
63-
error?: string | null;
74+
"installId": "abcd-1234-efgh-5678",
75+
"userStatus": "SUPERVISED"
6476
}
6577
```
6678

67-
Example (future API result):
79+
### 🔵 **iOS – Declared Age Range**
6880

69-
```
81+
```json
7082
{
71-
"installId": "123e4567-e89b-12d3-a456-426614174000",
72-
"userStatus": "VERIFIED"
83+
"status": "sharing",
84+
"lowerBound": 16,
85+
"upperBound": 17
7386
}
7487
```
7588

7689
---
7790

78-
## ⚙️ Usage
91+
## 🧩 API Reference
7992

80-
```
81-
import { getPlayAgeSignals } from 'react-native-play-age-signals';
82-
import { useEffect } from 'react';
83-
import { Text, View } from 'react-native';
93+
### `getPlayAgeSignals(ageGates?: number[])`
8494

85-
export default function App() {
86-
useEffect(() => {
87-
const fetchSignals = async () => {
88-
try {
89-
const result = await getPlayAgeSignals();
90-
console.log('Play Age Signals:', result);
91-
} catch (error) {
92-
console.error('Failed to fetch Play Age Signals:', error);
93-
}
94-
};
95+
**Returns:** `Promise<PlayAgeSignalsResult | DeclaredAgeRangeResult>`
9596

96-
fetchSignals();
97-
}, []);
97+
#### Android (`PlayAgeSignalsResult`)
9898

99-
return (
100-
<View>
101-
<Text>Check your logs for Play Age Signals output</Text>
102-
</View>
103-
);
104-
}
105-
```
99+
| Field | Type | Description | |
100+
| ------------ | ------- | ----------- | ---------------------------------------- |
101+
| `installId` | `string | null` | Install-specific identifier |
102+
| `userStatus` | `string | null` | `"SUPERVISED"`, `"VERIFIED"`, or similar |
103+
| `error` | `string | null` | Error message if API unavailable |
104+
105+
#### iOS (`DeclaredAgeRangeResult`)
106+
107+
| Field | Type | Description | |
108+
| ------------ | -------------------------------- | ----------------------------------- | ---------------------------- |
109+
| `status` | `'sharing' \| 'declinedSharing'` | Whether the user shared their range | |
110+
| `lowerBound` | `number | null` | Minimum declared age |
111+
| `upperBound` | `number | null` | Maximum declared age |
112+
| `error` | `string | null` | Error message if unavailable |
113+
114+
---
115+
116+
## 🧾 Real-World Use Case
117+
118+
This module enables developers to comply with **state-level digital safety laws** and **age-appropriate content regulations** in the U.S.
119+
120+
* Android → integrates with Play Age Signals (COPPA-compliant supervision data)
121+
* iOS → integrates with Declared Age Range API (WWDC 2025 privacy-preserving method)
122+
123+
---
124+
125+
## 🔗 Reference Links
126+
127+
* 📘 [Play Age Signals – Android Developers](https://developer.android.com/google/play/age-signals/use-age-signals-api)
128+
* 📘 [Declared Age Range – Apple Developer](https://developer.apple.com/documentation/declaredagerange)
129+
* 🧾 [U.S. Age Appropriate Design Laws (Overview)](https://support.google.com/googleplay/android-developer/answer/16569691)
106130

107131
---
108132

109133
## 🧩 Supported Platforms
110134

111-
| Platform | Status |
112-
| --------------------------------- | ------------------------------------- |
113-
| **Android** | ✅ Supported (pending SDK activation) |
114-
| **iOS** | 🚫 Not applicable (returns `null`) |
115-
| **Expo (Custom Dev Client)** | ✅ Works out of the box |
116-
| **AOSP Emulator (no Play Store)** | ⚠️ Not supported |
135+
| Platform | Status |
136+
| ---------------------------- | -------------------------------------- |
137+
| **Android** | ✅ Supported (SDK in beta) |
138+
| **iOS 26+** | ✅ Supported (Declared Age Range) |
139+
| **Expo (Custom Dev Client)** | ✅ Works out-of-the-box |
140+
| **AOSP Emulator** | ⚠️ Not supported (requires Play Store) |
117141

118142
---
119143

120-
## 🛠️ Under the Hood
144+
## 🧱 Under the Hood
121145

122-
This library uses:
146+
### Android
123147

124-
```
148+
Implemented in Kotlin using:
149+
150+
```kotlin
125151
AgeSignalsManagerFactory.create(context)
126152
.checkAgeSignals(AgeSignalsRequest.builder().build())
127153
```
128154

129-
to connect to the Play Services API through a native Kotlin TurboModule and forward results to JavaScript with full error propagation and stack traces.
155+
wrapped via a React Native TurboModule with full promise-based error handling.
156+
157+
### iOS
158+
159+
Implemented in Swift + Objective-C++ using:
160+
161+
```swift
162+
try await AgeRangeService.requestAgeRange(ageGates: [13, 16, 18])
163+
```
164+
165+
and bridged through `RCT_EXPORT_MODULE()`.
130166

131167
---
132168

133169
## 📅 Roadmap
134170

135-
- ✅ TurboModule bridge implementation
136-
- ✅ Error handling with full Java → JS stack propagation
137-
- 🚧 Awaiting Play Services rollout of working backend
138-
- 🔔 Auto-watch for new SDK versions via Dependabot and GitHub Actions
171+
* ✅ TurboModule bridge implementation (Android + iOS)
172+
* ✅ iOS Declared Age Range integration
173+
* ✅ TypeScript + Fabric compatibility
174+
* 🚧 Awaiting full rollout of Google Play Age Signals API
175+
* 🔔 Auto-update support for new SDK versions via GitHub Actions
139176

140177
---
141178

142179
## 🤝 Contributing
143180

144-
Pull requests are welcome — especially once Google enables this API!
181+
Pull requests are welcome once both APIs are live!
145182

146-
- [Development workflow](CONTRIBUTING.md#development-workflow)
147-
- [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
148-
- [Code of conduct](CODE_OF_CONDUCT.md)
183+
* [Development Workflow](CONTRIBUTING.md#development-workflow)
184+
* [Sending a PR](CONTRIBUTING.md#sending-a-pull-request)
185+
* [Code of Conduct](CODE_OF_CONDUCT.md)
149186

150187
---
151188

152189
## 🪪 License
153190

154-
MIT © [Gautham Vijayan](https://gauthamvijay.com)
191+
MIT © [**Gautham Vijayan**](https://gauthamvijay.com)
155192

156193
---
157194

158-
Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
195+
Made with [**create-react-native-library**](https://github.com/callstack/react-native-builder-bob)
159196

160197
---

android/build.gradle

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
buildscript {
22
ext.getExtOrDefault = {name ->
3-
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['PlayAgeSignals_' + name]
3+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['PlayAgeRangeDeclaration_' + name]
44
}
55

66
repositories {
@@ -22,11 +22,11 @@ apply plugin: "kotlin-android"
2222
apply plugin: "com.facebook.react"
2323

2424
def getExtOrIntegerDefault(name) {
25-
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["PlayAgeSignals_" + name]).toInteger()
25+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["PlayAgeRangeDeclaration_" + name]).toInteger()
2626
}
2727

2828
android {
29-
namespace "com.gautham.playagesignals"
29+
namespace "com.gautham.playagerangedeclaration"
3030

3131
compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
3232

android/gradle.properties

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
PlayAgeSignals_kotlinVersion=2.0.21
2-
PlayAgeSignals_minSdkVersion=24
3-
PlayAgeSignals_targetSdkVersion=34
4-
PlayAgeSignals_compileSdkVersion=35
5-
PlayAgeSignals_ndkVersion=27.1.12297006
1+
PlayAgeRangeDeclaration_kotlinVersion=2.0.21
2+
PlayAgeRangeDeclaration_minSdkVersion=24
3+
PlayAgeRangeDeclaration_targetSdkVersion=34
4+
PlayAgeRangeDeclaration_compileSdkVersion=35
5+
PlayAgeRangeDeclaration_ndkVersion=27.1.12297006

android/src/main/java/com/playagesignals/PlayAgeSignalsModule.kt renamed to android/src/main/java/com/playagerangedeclaration/PlayAgeRangeDeclarationModule.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.gautham.playagesignals
1+
package com.gautham.playagerangedeclaration
22

33
import android.util.Log
44
import com.facebook.react.bridge.Arguments
@@ -11,21 +11,21 @@ import com.facebook.react.module.annotations.ReactModule
1111
import com.google.android.play.agesignals.AgeSignalsManagerFactory
1212
import com.google.android.play.agesignals.AgeSignalsRequest
1313

14-
@ReactModule(name = PlayAgeSignalsModule.NAME)
15-
class PlayAgeSignalsModule(
14+
@ReactModule(name = PlayAgeRangeDeclarationModule.NAME)
15+
class PlayAgeRangeDeclarationModule(
1616
private val reactContext: ReactApplicationContext
1717
) : NativeModule, TurboModule {
1818

1919
companion object {
20-
const val NAME = "PlayAgeSignals"
20+
const val NAME = "PlayAgeRangeDeclaration"
2121
}
2222

2323
override fun getName(): String = NAME
2424
override fun initialize() {}
2525
override fun invalidate() {}
2626

2727
@ReactMethod
28-
fun getPlayAgeSignals(promise: Promise) {
28+
fun getPlayAgeRangeDeclaration(promise: Promise) {
2929
try {
3030
val manager = AgeSignalsManagerFactory.create(reactContext.applicationContext)
3131
val request = AgeSignalsRequest.builder().build()
@@ -40,7 +40,7 @@ class PlayAgeSignalsModule(
4040
}
4141
.addOnFailureListener { e ->
4242
val msg = e.message ?: "Unknown error"
43-
Log.e("PlayAgeSignals", "Failed to fetch Age Signals: $msg", e)
43+
Log.e("PlayAgeRangeDeclaration", "Failed to fetch Age Signals: $msg", e)
4444

4545
// Reject so JS sees the *real* native exception
4646
promise.reject(
@@ -51,7 +51,7 @@ class PlayAgeSignalsModule(
5151
}
5252

5353
} catch (e: Exception) {
54-
Log.e("PlayAgeSignals", "Initialization error", e)
54+
Log.e("PlayAgeRangeDeclaration", "Initialization error", e)
5555
promise.reject("AGE_SIGNALS_INIT_ERROR", e.message, e)
5656
}
5757
}

0 commit comments

Comments
 (0)