@milkinteractive/react-native-age-range 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +325 -0
- package/StoreAgeSignalsNativeModules.podspec +25 -0
- package/android/build.gradle +81 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/AndroidManifestNew.xml +2 -0
- package/android/src/main/java/com/storeagesignalsnativemodules/StoreAgeSignalsNativeModulesModule.kt +193 -0
- package/android/src/main/java/com/storeagesignalsnativemodules/StoreAgeSignalsNativeModulesPackage.kt +17 -0
- package/ios/StoreAgeSignalsNativeModules-Bridging-Header.h +2 -0
- package/ios/StoreAgeSignalsNativeModules.h +4 -0
- package/ios/StoreAgeSignalsNativeModules.mm +73 -0
- package/ios/StoreAgeSignalsNativeModules.swift +180 -0
- package/lib/commonjs/index.js +121 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/module/index.js +114 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/src/index.d.ts +138 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/package.json +198 -0
- package/src/index.tsx +234 -0
package/package.json
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@milkinteractive/react-native-age-range",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A React Native Native Module (Legacy Architecture) that provides access to store-level age signals, including Android Play Age Range and iOS Declared Age, to assist with state-level age verification compliance (e.g., Texas).",
|
|
5
|
+
"source": "./src/index.tsx",
|
|
6
|
+
"main": "./lib/commonjs/index.js",
|
|
7
|
+
"module": "./lib/module/index.js",
|
|
8
|
+
"types": "./lib/typescript/src/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./lib/typescript/src/index.d.ts",
|
|
12
|
+
"import": "./lib/module/index.js",
|
|
13
|
+
"require": "./lib/commonjs/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./package.json": "./package.json"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"src",
|
|
19
|
+
"lib",
|
|
20
|
+
"android",
|
|
21
|
+
"ios",
|
|
22
|
+
"cpp",
|
|
23
|
+
"*.podspec",
|
|
24
|
+
"react-native.config.js",
|
|
25
|
+
"!ios/build",
|
|
26
|
+
"!android/build",
|
|
27
|
+
"!android/gradle",
|
|
28
|
+
"!android/gradlew",
|
|
29
|
+
"!android/gradlew.bat",
|
|
30
|
+
"!android/local.properties",
|
|
31
|
+
"!**/__tests__",
|
|
32
|
+
"!**/__fixtures__",
|
|
33
|
+
"!**/__mocks__",
|
|
34
|
+
"!**/.*"
|
|
35
|
+
],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"example": "yarn workspace @milkinteractive/react-native-age-range-example",
|
|
38
|
+
"test": "jest",
|
|
39
|
+
"typecheck": "tsc",
|
|
40
|
+
"lint": "eslint \"**/*.{js,ts,tsx}\"",
|
|
41
|
+
"clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib",
|
|
42
|
+
"prepare": "bob build",
|
|
43
|
+
"release": "release-it"
|
|
44
|
+
},
|
|
45
|
+
"keywords": [
|
|
46
|
+
"react-native",
|
|
47
|
+
"ios",
|
|
48
|
+
"android",
|
|
49
|
+
"age-verification",
|
|
50
|
+
"age-gate",
|
|
51
|
+
"texas-sb2420",
|
|
52
|
+
"parental-controls",
|
|
53
|
+
"coppa",
|
|
54
|
+
"compliance",
|
|
55
|
+
"google-play-age-signals",
|
|
56
|
+
"declared-age-range",
|
|
57
|
+
"app-store",
|
|
58
|
+
"child-safety",
|
|
59
|
+
"privacy",
|
|
60
|
+
"age-appropriate"
|
|
61
|
+
],
|
|
62
|
+
"repository": {
|
|
63
|
+
"type": "git",
|
|
64
|
+
"url": "git+https://github.com/milkinteractive/react-native-age-range.git"
|
|
65
|
+
},
|
|
66
|
+
"author": "Naga sai charan gubba <nagasaicharan7@gmail.com> (https://github.com/nagasaicharan)",
|
|
67
|
+
"contributors": [
|
|
68
|
+
"Christoph Eck <christoph@milkinteractive.ch>"
|
|
69
|
+
],
|
|
70
|
+
"license": "MIT",
|
|
71
|
+
"bugs": {
|
|
72
|
+
"url": "https://github.com/milkinteractive/react-native-age-range/issues"
|
|
73
|
+
},
|
|
74
|
+
"homepage": "https://github.com/milkinteractive/react-native-age-range#readme",
|
|
75
|
+
"publishConfig": {
|
|
76
|
+
"registry": "https://registry.npmjs.org/"
|
|
77
|
+
},
|
|
78
|
+
"devDependencies": {
|
|
79
|
+
"@commitlint/config-conventional": "^19.6.0",
|
|
80
|
+
"@evilmartians/lefthook": "^1.5.0",
|
|
81
|
+
"@react-native/eslint-config": "^0.73.1",
|
|
82
|
+
"@release-it/conventional-changelog": "^9.0.2",
|
|
83
|
+
"@types/jest": "^29.5.5",
|
|
84
|
+
"@types/react": "^19.1.1",
|
|
85
|
+
"commitlint": "^19.6.1",
|
|
86
|
+
"del-cli": "^5.1.0",
|
|
87
|
+
"eslint": "^8.51.0",
|
|
88
|
+
"eslint-config-prettier": "^9.0.0",
|
|
89
|
+
"eslint-plugin-prettier": "^5.0.1",
|
|
90
|
+
"jest": "^29.7.0",
|
|
91
|
+
"prettier": "^3.0.3",
|
|
92
|
+
"react": "19.1.1",
|
|
93
|
+
"react-native": "0.82.1",
|
|
94
|
+
"react-native-builder-bob": "0.38.0",
|
|
95
|
+
"release-it": "^17.10.0",
|
|
96
|
+
"turbo": "^1.10.7",
|
|
97
|
+
"typescript": "^5.2.2"
|
|
98
|
+
},
|
|
99
|
+
"peerDependencies": {
|
|
100
|
+
"react": "*",
|
|
101
|
+
"react-native": "*"
|
|
102
|
+
},
|
|
103
|
+
"workspaces": [
|
|
104
|
+
"example"
|
|
105
|
+
],
|
|
106
|
+
"packageManager": "yarn@3.6.1",
|
|
107
|
+
"jest": {
|
|
108
|
+
"preset": "react-native",
|
|
109
|
+
"modulePathIgnorePatterns": [
|
|
110
|
+
"<rootDir>/example/node_modules",
|
|
111
|
+
"<rootDir>/lib/"
|
|
112
|
+
]
|
|
113
|
+
},
|
|
114
|
+
"commitlint": {
|
|
115
|
+
"extends": [
|
|
116
|
+
"@commitlint/config-conventional"
|
|
117
|
+
]
|
|
118
|
+
},
|
|
119
|
+
"release-it": {
|
|
120
|
+
"git": {
|
|
121
|
+
"commitMessage": "chore: release ${version}",
|
|
122
|
+
"tagName": "v${version}"
|
|
123
|
+
},
|
|
124
|
+
"npm": {
|
|
125
|
+
"publish": true
|
|
126
|
+
},
|
|
127
|
+
"github": {
|
|
128
|
+
"release": true
|
|
129
|
+
},
|
|
130
|
+
"plugins": {
|
|
131
|
+
"@release-it/conventional-changelog": {
|
|
132
|
+
"preset": {
|
|
133
|
+
"name": "angular"
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
"eslintConfig": {
|
|
139
|
+
"root": true,
|
|
140
|
+
"extends": [
|
|
141
|
+
"@react-native",
|
|
142
|
+
"prettier"
|
|
143
|
+
],
|
|
144
|
+
"rules": {
|
|
145
|
+
"react/react-in-jsx-scope": "off",
|
|
146
|
+
"prettier/prettier": [
|
|
147
|
+
"error",
|
|
148
|
+
{
|
|
149
|
+
"quoteProps": "consistent",
|
|
150
|
+
"singleQuote": true,
|
|
151
|
+
"tabWidth": 2,
|
|
152
|
+
"trailingComma": "es5",
|
|
153
|
+
"useTabs": false
|
|
154
|
+
}
|
|
155
|
+
]
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
"eslintIgnore": [
|
|
159
|
+
"node_modules/",
|
|
160
|
+
"lib/"
|
|
161
|
+
],
|
|
162
|
+
"prettier": {
|
|
163
|
+
"quoteProps": "consistent",
|
|
164
|
+
"singleQuote": true,
|
|
165
|
+
"tabWidth": 2,
|
|
166
|
+
"trailingComma": "es5",
|
|
167
|
+
"useTabs": false
|
|
168
|
+
},
|
|
169
|
+
"react-native-builder-bob": {
|
|
170
|
+
"source": "src",
|
|
171
|
+
"output": "lib",
|
|
172
|
+
"targets": [
|
|
173
|
+
[
|
|
174
|
+
"module",
|
|
175
|
+
{
|
|
176
|
+
"esm": true
|
|
177
|
+
}
|
|
178
|
+
],
|
|
179
|
+
[
|
|
180
|
+
"commonjs",
|
|
181
|
+
{
|
|
182
|
+
"esm": true
|
|
183
|
+
}
|
|
184
|
+
],
|
|
185
|
+
[
|
|
186
|
+
"typescript",
|
|
187
|
+
{
|
|
188
|
+
"project": "tsconfig.build.json"
|
|
189
|
+
}
|
|
190
|
+
]
|
|
191
|
+
]
|
|
192
|
+
},
|
|
193
|
+
"create-react-native-library": {
|
|
194
|
+
"type": "legacy-module",
|
|
195
|
+
"languages": "kotlin-swift",
|
|
196
|
+
"version": "0.48.9"
|
|
197
|
+
}
|
|
198
|
+
}
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { NativeModules, Platform } from 'react-native';
|
|
2
|
+
|
|
3
|
+
const LINKING_ERROR =
|
|
4
|
+
`The package '@milkinteractive/react-native-age-range' doesn't seem to be linked. Make sure: \n\n` +
|
|
5
|
+
Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) +
|
|
6
|
+
'- You rebuilt the app after installing the package\n' +
|
|
7
|
+
'- You are not using Expo Go\n';
|
|
8
|
+
|
|
9
|
+
const StoreAgeSignalsNativeModules = NativeModules.StoreAgeSignalsNativeModules
|
|
10
|
+
? NativeModules.StoreAgeSignalsNativeModules
|
|
11
|
+
: new Proxy(
|
|
12
|
+
{},
|
|
13
|
+
{
|
|
14
|
+
get() {
|
|
15
|
+
throw new Error(LINKING_ERROR);
|
|
16
|
+
},
|
|
17
|
+
}
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
// Android Types
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* User verification status from Google Play Age Signals API.
|
|
24
|
+
* - OVER_AGE: User is verified as an adult (18+)
|
|
25
|
+
* - UNDER_AGE: User has a supervised account (child/teen)
|
|
26
|
+
* - UNDER_AGE_APPROVAL_PENDING: Supervised user, parent hasn't approved pending significant changes
|
|
27
|
+
* - UNDER_AGE_APPROVAL_DENIED: Supervised user, parent denied approval for significant changes
|
|
28
|
+
* - UNKNOWN: Status could not be determined
|
|
29
|
+
*/
|
|
30
|
+
export type AndroidUserStatus =
|
|
31
|
+
| 'OVER_AGE'
|
|
32
|
+
| 'UNDER_AGE'
|
|
33
|
+
| 'UNDER_AGE_APPROVAL_PENDING'
|
|
34
|
+
| 'UNDER_AGE_APPROVAL_DENIED'
|
|
35
|
+
| 'UNKNOWN';
|
|
36
|
+
|
|
37
|
+
export interface PlayAgeRangeStatusResult {
|
|
38
|
+
installId: string | null;
|
|
39
|
+
userStatus: AndroidUserStatus | null;
|
|
40
|
+
error: string | null;
|
|
41
|
+
/**
|
|
42
|
+
* The (inclusive) lower bound of a supervised user's age range.
|
|
43
|
+
* 0 to 18.
|
|
44
|
+
*/
|
|
45
|
+
ageLower?: number | null;
|
|
46
|
+
/**
|
|
47
|
+
* The (inclusive) upper bound of a supervised user's age range.
|
|
48
|
+
* 2 to 18.
|
|
49
|
+
*/
|
|
50
|
+
ageUpper?: number | null;
|
|
51
|
+
/**
|
|
52
|
+
* The effective from date of the most recent significant change that was approved.
|
|
53
|
+
* ISO string format.
|
|
54
|
+
*/
|
|
55
|
+
mostRecentApprovalDate?: string | null;
|
|
56
|
+
/**
|
|
57
|
+
* The numerical error code if the request failed.
|
|
58
|
+
* e.g., -1 (API_NOT_AVAILABLE), -2 (PLAY_STORE_NOT_FOUND), etc.
|
|
59
|
+
*/
|
|
60
|
+
errorCode?: number | null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// iOS Types
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* How the age range was declared/verified.
|
|
67
|
+
* - selfDeclared: User declared their own age
|
|
68
|
+
* - guardianDeclared: Guardian set the age (for children in iCloud family)
|
|
69
|
+
* - checkedByOtherMethod: Verified by another method
|
|
70
|
+
* - guardianCheckedByOtherMethod: Guardian verified by another method
|
|
71
|
+
* - governmentIDChecked: Verified via government ID
|
|
72
|
+
* - guardianGovernmentIDChecked: Guardian verified via government ID
|
|
73
|
+
* - paymentChecked: Verified via payment method (credit card)
|
|
74
|
+
* - guardianPaymentChecked: Guardian verified via payment method
|
|
75
|
+
*/
|
|
76
|
+
export type AgeRangeDeclarationType =
|
|
77
|
+
| 'selfDeclared'
|
|
78
|
+
| 'guardianDeclared'
|
|
79
|
+
| 'checkedByOtherMethod'
|
|
80
|
+
| 'guardianCheckedByOtherMethod'
|
|
81
|
+
| 'governmentIDChecked'
|
|
82
|
+
| 'guardianGovernmentIDChecked'
|
|
83
|
+
| 'paymentChecked'
|
|
84
|
+
| 'guardianPaymentChecked'
|
|
85
|
+
| 'unknown';
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Parental control settings active for the user.
|
|
89
|
+
*/
|
|
90
|
+
export interface ParentalControlsInfo {
|
|
91
|
+
/** Whether communication limits are enabled (e.g., contact restrictions) */
|
|
92
|
+
communicationLimits?: boolean;
|
|
93
|
+
/** Whether significant app changes require parental approval */
|
|
94
|
+
significantAppChangeApprovalRequired?: boolean;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface DeclaredAgeRangeResult {
|
|
98
|
+
status: 'sharing' | 'declined' | null;
|
|
99
|
+
lowerBound: number | null;
|
|
100
|
+
upperBound: number | null;
|
|
101
|
+
/**
|
|
102
|
+
* How the age range was declared/verified (iOS 26+).
|
|
103
|
+
* For children: always 'guardianDeclared'
|
|
104
|
+
* For teens in iCloud family: 'guardianDeclared'
|
|
105
|
+
* For teens not in family: 'selfDeclared'
|
|
106
|
+
* For adults: 'selfDeclared'
|
|
107
|
+
*/
|
|
108
|
+
ageRangeDeclaration?: AgeRangeDeclarationType | null;
|
|
109
|
+
/**
|
|
110
|
+
* Parental control settings active for the user (if under age of majority).
|
|
111
|
+
*/
|
|
112
|
+
parentalControls?: ParentalControlsInfo | null;
|
|
113
|
+
error: string | null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Eligibility Result
|
|
117
|
+
export interface DeclaredAgeEligibilityResult {
|
|
118
|
+
isEligible: boolean;
|
|
119
|
+
error: string | null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface AndroidAgeRangeConfig {
|
|
123
|
+
/**
|
|
124
|
+
* Enable mock mode to simulate results without calling Google Play API.
|
|
125
|
+
* Useful for development and testing.
|
|
126
|
+
*/
|
|
127
|
+
isMock?: boolean;
|
|
128
|
+
/**
|
|
129
|
+
* The status to return when isMock is true.
|
|
130
|
+
* Default: 'OVER_AGE'
|
|
131
|
+
*/
|
|
132
|
+
mockStatus?: AndroidUserStatus;
|
|
133
|
+
/**
|
|
134
|
+
* (Mock Only) Lower bound of the age range (e.g. 13).
|
|
135
|
+
* Relevant when mockStatus is 'UNDER_AGE' (Supervised).
|
|
136
|
+
*/
|
|
137
|
+
mockAgeLower?: number;
|
|
138
|
+
/**
|
|
139
|
+
* (Mock Only) Upper bound of the age range (e.g. 17).
|
|
140
|
+
* Relevant when mockStatus is 'UNDER_AGE' (Supervised).
|
|
141
|
+
*/
|
|
142
|
+
mockAgeUpper?: number;
|
|
143
|
+
/**
|
|
144
|
+
* (Mock Only) The numerical error code to throw.
|
|
145
|
+
* e.g., -1 for API_NOT_AVAILABLE.
|
|
146
|
+
*/
|
|
147
|
+
mockErrorCode?: number;
|
|
148
|
+
/**
|
|
149
|
+
* (Mock Only) ISO date string for most recent approval.
|
|
150
|
+
*/
|
|
151
|
+
mockMostRecentApprovalDate?: string;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Retrieves the age range declaration status from Google Play's Age Signals API.
|
|
156
|
+
* @platform android
|
|
157
|
+
*/
|
|
158
|
+
export function getAndroidPlayAgeRangeStatus(
|
|
159
|
+
config?: AndroidAgeRangeConfig
|
|
160
|
+
): Promise<PlayAgeRangeStatusResult> {
|
|
161
|
+
if (Platform.OS !== 'android') {
|
|
162
|
+
return Promise.resolve({
|
|
163
|
+
installId: null,
|
|
164
|
+
userStatus: null,
|
|
165
|
+
error: 'This method is only available on Android',
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return StoreAgeSignalsNativeModules.getAndroidPlayAgeRangeStatus(
|
|
169
|
+
config || {}
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Requests age range declaration from iOS Declared Age Range API.
|
|
175
|
+
* @platform ios
|
|
176
|
+
* @param firstThresholdAge First age threshold (e.g., 13)
|
|
177
|
+
* @param secondThresholdAge Second age threshold (e.g., 17)
|
|
178
|
+
* @param thirdThresholdAge Third age threshold (e.g., 21)
|
|
179
|
+
*/
|
|
180
|
+
export function requestIOSDeclaredAgeRange(
|
|
181
|
+
firstThresholdAge: number,
|
|
182
|
+
secondThresholdAge: number,
|
|
183
|
+
thirdThresholdAge: number
|
|
184
|
+
): Promise<DeclaredAgeRangeResult> {
|
|
185
|
+
if (Platform.OS !== 'ios') {
|
|
186
|
+
return Promise.resolve({
|
|
187
|
+
status: null,
|
|
188
|
+
lowerBound: null,
|
|
189
|
+
upperBound: null,
|
|
190
|
+
ageRangeDeclaration: null,
|
|
191
|
+
parentalControls: null,
|
|
192
|
+
error: 'This method is only available on iOS',
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
return StoreAgeSignalsNativeModules.requestIOSDeclaredAgeRange(
|
|
196
|
+
firstThresholdAge,
|
|
197
|
+
secondThresholdAge,
|
|
198
|
+
thirdThresholdAge
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Checks if the current user is eligible for age verification features on iOS.
|
|
204
|
+
* This determines if age checks need to be applied (e.g., user is in an applicable region like Texas).
|
|
205
|
+
* @platform ios
|
|
206
|
+
* @returns Promise<DeclaredAgeEligibilityResult> - Object containing isEligible boolean and error string
|
|
207
|
+
* @remarks Requires iOS 26.0+. Returns isEligible: false with error message if not available.
|
|
208
|
+
*/
|
|
209
|
+
export function isIOSEligibleForAgeFeatures(): Promise<DeclaredAgeEligibilityResult> {
|
|
210
|
+
if (Platform.OS !== 'ios') {
|
|
211
|
+
return Promise.resolve({
|
|
212
|
+
isEligible: false,
|
|
213
|
+
error: 'This method is only available on iOS',
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
return StoreAgeSignalsNativeModules.isEligibleForAgeFeatures();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Checks if the current user is eligible for age verification features on Android.
|
|
221
|
+
* This determines if age checks need to be applied (e.g., user is in an applicable region like Texas, Utah, Louisiana).
|
|
222
|
+
* @platform android
|
|
223
|
+
* @returns Promise<DeclaredAgeEligibilityResult> - Object containing isEligible boolean and error string
|
|
224
|
+
* @remarks Makes a lightweight API call to determine eligibility. Returns isEligible: false with error message if not available.
|
|
225
|
+
*/
|
|
226
|
+
export function isAndroidEligibleForAgeFeatures(): Promise<DeclaredAgeEligibilityResult> {
|
|
227
|
+
if (Platform.OS !== 'android') {
|
|
228
|
+
return Promise.resolve({
|
|
229
|
+
isEligible: false,
|
|
230
|
+
error: 'This method is only available on Android',
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
return StoreAgeSignalsNativeModules.isEligibleForAgeFeatures();
|
|
234
|
+
}
|