@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/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
+ }