@symbiote-native/age-range 0.1.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 +21 -0
- package/README.md +140 -0
- package/build/angular/index.d.ts +1 -0
- package/build/angular/index.js +2 -0
- package/build/core/age-range.d.ts +12 -0
- package/build/core/age-range.js +38 -0
- package/build/core/index.d.ts +2 -0
- package/build/core/index.js +1 -0
- package/build/core/native-module.d.ts +10 -0
- package/build/core/native-module.js +3 -0
- package/build/core/types.d.ts +50 -0
- package/build/core/types.js +3 -0
- package/build-ngc/angular/index.d.ts +1 -0
- package/build-ngc/angular/index.js +3 -0
- package/build-ngc/angular/index.js.map +1 -0
- package/build-ngc/core/age-range.d.ts +12 -0
- package/build-ngc/core/age-range.js +39 -0
- package/build-ngc/core/age-range.js.map +1 -0
- package/build-ngc/core/index.d.ts +2 -0
- package/build-ngc/core/index.js +2 -0
- package/build-ngc/core/index.js.map +1 -0
- package/build-ngc/core/native-module.d.ts +10 -0
- package/build-ngc/core/native-module.js +4 -0
- package/build-ngc/core/native-module.js.map +1 -0
- package/build-ngc/core/types.d.ts +50 -0
- package/build-ngc/core/types.js +4 -0
- package/build-ngc/core/types.js.map +1 -0
- package/native-link.json +12 -0
- package/package.json +150 -0
- package/src/angular/index.ts +2 -0
- package/src/core/age-range.ts +62 -0
- package/src/core/index.ts +15 -0
- package/src/core/native-module.ts +29 -0
- package/src/core/types.ts +64 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 A. Prokopenko
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# @symbiote-native/age-range
|
|
2
|
+
|
|
3
|
+
Show age-appropriate content, or meet an age-assurance law, without collecting a birth date
|
|
4
|
+
yourself: ask the platform. One API for every [SymbioteNative](../../README.md) adapter (React,
|
|
5
|
+
Vue, Svelte, Solid and Angular).
|
|
6
|
+
|
|
7
|
+
It wraps [`expo-age-range`](https://github.com/expo/expo/tree/main/packages/expo-age-range)
|
|
8
|
+
(Apple's Declared Age Range API on iOS 26+, and Google Play's Age Signals). Like
|
|
9
|
+
[`@symbiote-native/application`](../application), everything here is a one-shot async call (or a
|
|
10
|
+
single sync setter) with no per-instance state, so there is no hook, composable or service to wrap:
|
|
11
|
+
every adapter entry point is a plain re-export of the same `core`.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
**New app:**
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx @symbiote-native/cli new my-app --age-range
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Existing SymbioteNative app:**
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx @symbiote-native/cli add --age-range
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Either way: installs `@symbiote-native/age-range` and wires the native autolinking automatically,
|
|
28
|
+
see [`@symbiote-native/cli`](../cli).
|
|
29
|
+
|
|
30
|
+
<details>
|
|
31
|
+
<summary>Manual install (no CLI, wiring native autolinking by hand)</summary>
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install @symbiote-native/age-range
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`expo-age-range` and `expo-modules-core` come along as regular dependencies, pinned to exact
|
|
38
|
+
versions. Never install either yourself, and never add the `expo` meta-package to your project
|
|
39
|
+
(it bundles its own Metro/Babel pipeline, which conflicts with this project's own).
|
|
40
|
+
|
|
41
|
+
## Required one-time step: native autolinking wiring
|
|
42
|
+
|
|
43
|
+
Unlike a plain RN native module, `expo-age-range`'s native code is discovered by
|
|
44
|
+
`expo-modules-autolinking`, not RN's own `react-native.config.cjs` mechanism. This needs wiring
|
|
45
|
+
into the native host app **once**, covering this package and every other `expo-modules-core`
|
|
46
|
+
package with zero further changes:
|
|
47
|
+
|
|
48
|
+
| Platform | Touches |
|
|
49
|
+
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
50
|
+
| iOS | `ios/Podfile`, add `use_expo_modules!` |
|
|
51
|
+
| iOS | `AppDelegate.swift`, Expo's runtime-bootstrap hook |
|
|
52
|
+
| Android | `settings.gradle` / `app/build.gradle`, resolve and include the Expo Gradle projects |
|
|
53
|
+
| Android | `MainApplication.kt`, Expo's bootstrap hook, plus a hand-written native-module name map (there's no `expo` meta-package here to auto-generate one) |
|
|
54
|
+
|
|
55
|
+
Full mechanics live in the `symbiote-expo-native-module` project skill. Reference
|
|
56
|
+
implementation: `examples/expo-react/ios/Podfile` and
|
|
57
|
+
`examples/expo-react/android/app/src/main/java/com/canaryexpo/MainApplication.kt`.
|
|
58
|
+
|
|
59
|
+
No app-level permission strings are needed. Apple's age-range prompt and Google Play's Age
|
|
60
|
+
Signals consent screen are both system UI, carrying no `Info.plist`/manifest entry of their own.
|
|
61
|
+
|
|
62
|
+
**iOS needs one entitlement, added by hand.** Build with Xcode 26 or later and add
|
|
63
|
+
`com.apple.developer.declared-age-range` (`<true/>`) to your app's `.entitlements` file.
|
|
64
|
+
`native-link.json` does not generate it.
|
|
65
|
+
|
|
66
|
+
</details>
|
|
67
|
+
|
|
68
|
+
## Shape
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
src/core/ requestAgeRangeAsync / isEligibleForAgeFeaturesAsync /
|
|
72
|
+
showSignificantUpdateAcknowledgmentAsync / getRequiredRegulatoryFeaturesAsync /
|
|
73
|
+
requestAgeSignalsAccessAsync / setFakeAgeSignals, plus IAgeRangeRequest /
|
|
74
|
+
IAgeRangeResponse / IAgeSignalsStatus / IFakeAgeSignals / IAgeRangeRegulatoryFeature.
|
|
75
|
+
native-module.ts resolves the native module via expo-modules-core's
|
|
76
|
+
requireNativeModule.
|
|
77
|
+
src/angular/ @symbiote-native/age-range/angular, export * from '../core'
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`./react`, `./vue`, `./svelte`, and `./solid` are `exports`-map aliases straight onto `src/core/`,
|
|
81
|
+
no physical per-framework file, since there's nothing to subscribe to or clean up. `./angular`
|
|
82
|
+
stays a physical file/subpath since Angular ships through a separate `ngc`/AOT build (`build-ngc/`).
|
|
83
|
+
|
|
84
|
+
## API
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
requestAgeRangeAsync(options: IAgeRangeRequest): Promise<IAgeRangeResponse>
|
|
88
|
+
isEligibleForAgeFeaturesAsync(): Promise<boolean | null>
|
|
89
|
+
|
|
90
|
+
showSignificantUpdateAcknowledgmentAsync(updateDescription: string): Promise<void> // ios 26.4+, no-op elsewhere
|
|
91
|
+
getRequiredRegulatoryFeaturesAsync(): Promise<IAgeRangeRegulatoryFeature[] | null> // ios 26.4+, null elsewhere
|
|
92
|
+
|
|
93
|
+
requestAgeSignalsAccessAsync(): Promise<IAgeSignalsStatus | null> // android only, null elsewhere
|
|
94
|
+
setFakeAgeSignals(fake: IFakeAgeSignals | null): void // android only, no-op elsewhere
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import {
|
|
99
|
+
requestAgeRangeAsync,
|
|
100
|
+
isEligibleForAgeFeaturesAsync,
|
|
101
|
+
} from '@symbiote-native/age-range';
|
|
102
|
+
// or the framework-scoped entry points, identical surface, re-exported verbatim:
|
|
103
|
+
import { requestAgeRangeAsync } from '@symbiote-native/age-range/react';
|
|
104
|
+
import { requestAgeRangeAsync } from '@symbiote-native/age-range/vue';
|
|
105
|
+
import { requestAgeRangeAsync } from '@symbiote-native/age-range/angular';
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Notes
|
|
109
|
+
|
|
110
|
+
- **Recommended pattern**: call `isEligibleForAgeFeaturesAsync`/`getRequiredRegulatoryFeaturesAsync`
|
|
111
|
+
first and only prompt with `requestAgeRangeAsync` when the result says the user actually needs
|
|
112
|
+
it. Both resolve `null` where the OS can't answer (iOS below 26.2/26.4, Android, web), and
|
|
113
|
+
`null` means "unknown", never "not required".
|
|
114
|
+
- On Android, `requestAgeSignalsAccessAsync` must resolve `'SHARED'` before `requestAgeRangeAsync`
|
|
115
|
+
reports anything besides `null` fields. Play Age Signals requires the separate consent screen
|
|
116
|
+
first, unlike iOS where the consent is folded into `requestAgeRangeAsync` itself.
|
|
117
|
+
- `setFakeAgeSignals` only works in a debuggable build. The native side throws otherwise unless
|
|
118
|
+
the argument is `null` (which always goes back to real signals).
|
|
119
|
+
- **Test on a real device.** Simulators and emulators may not behave as expected; the canary
|
|
120
|
+
`AgeRangeScreen` in the `examples/expo-*` apps exercises every call.
|
|
121
|
+
|
|
122
|
+
## Common questions
|
|
123
|
+
|
|
124
|
+
- **iOS `IOS_ENTITLEMENT_ERROR`.** Add `com.apple.developer.declared-age-range`; the Simulator lacks the feature.
|
|
125
|
+
- **Xcode?** 26.0 or later.
|
|
126
|
+
- **Android `API_NOT_AVAILABLE` / `PLAY_STORE_NOT_FOUND` / `NETWORK_ERROR`.** Needs an up-to-date
|
|
127
|
+
Play Store, a network, and a Google Play install.
|
|
128
|
+
- **No range on Android.** Reported only while the user consents to share it.
|
|
129
|
+
|
|
130
|
+
Sources: [Expo docs: AgeRange](https://docs.expo.dev/versions/latest/sdk/age-range/),
|
|
131
|
+
[expo/expo#46365](https://github.com/expo/expo/issues/46365),
|
|
132
|
+
[AgeSignalsException](https://developer.android.com/google/play/age-signals/reference/com/google/android/play/agesignals/AgeSignalsException).
|
|
133
|
+
|
|
134
|
+
## Test it
|
|
135
|
+
|
|
136
|
+
No Fabric/Descriptor angle at all, every export here is a one-shot async function or a plain sync
|
|
137
|
+
setter, never a view or per-instance state. Tests inject a fake native-module object in place of
|
|
138
|
+
the real `requireNativeModule` resolution (`src/core/age-range.test.ts`, `vitest`), no
|
|
139
|
+
`installFabric()`, no ViewConfig. Native behavior itself is verified on-device (see the parent
|
|
140
|
+
[README](../../README.md) for the project's testing model).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../core';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { IAgeRangeRegulatoryFeature, IAgeRangeRequest, IAgeRangeResponse, IAgeSignalsStatus, IFakeAgeSignals } from './types';
|
|
2
|
+
/** Prompts the user to share their age range; the OS may cache the response for later requests */
|
|
3
|
+
export declare function requestAgeRangeAsync(options: IAgeRangeRequest): Promise<IAgeRangeResponse>;
|
|
4
|
+
export declare function isEligibleForAgeFeaturesAsync(): Promise<boolean | null>;
|
|
5
|
+
/** @platform ios, no-op elsewhere */
|
|
6
|
+
export declare function showSignificantUpdateAcknowledgmentAsync(updateDescription: string): Promise<void>;
|
|
7
|
+
/** @platform ios, `null` elsewhere means unknown rather than none required */
|
|
8
|
+
export declare function getRequiredRegulatoryFeaturesAsync(): Promise<IAgeRangeRegulatoryFeature[] | null>;
|
|
9
|
+
/** @platform android, `null` elsewhere means unknown rather than not shared */
|
|
10
|
+
export declare function requestAgeSignalsAccessAsync(): Promise<IAgeSignalsStatus | null>;
|
|
11
|
+
/** @platform android, no-op elsewhere; throws off a non-debuggable build unless `fake` is `null` */
|
|
12
|
+
export declare function setFakeAgeSignals(fake: IFakeAgeSignals | null): void;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Platform } from 'expo-modules-core';
|
|
2
|
+
import { expoAgeRange } from './native-module.js';
|
|
3
|
+
/** Prompts the user to share their age range; the OS may cache the response for later requests */
|
|
4
|
+
export async function requestAgeRangeAsync(options) {
|
|
5
|
+
return expoAgeRange.requestAgeRangeAsync(options);
|
|
6
|
+
}
|
|
7
|
+
export async function isEligibleForAgeFeaturesAsync() {
|
|
8
|
+
return expoAgeRange.isEligibleForAgeFeaturesAsync();
|
|
9
|
+
}
|
|
10
|
+
/** @platform ios, no-op elsewhere */
|
|
11
|
+
export async function showSignificantUpdateAcknowledgmentAsync(updateDescription) {
|
|
12
|
+
if (Platform.OS !== 'ios' ||
|
|
13
|
+
!expoAgeRange.showSignificantUpdateAcknowledgmentAsync) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
await expoAgeRange.showSignificantUpdateAcknowledgmentAsync(updateDescription);
|
|
17
|
+
}
|
|
18
|
+
/** @platform ios, `null` elsewhere means unknown rather than none required */
|
|
19
|
+
export async function getRequiredRegulatoryFeaturesAsync() {
|
|
20
|
+
if (Platform.OS !== 'ios' ||
|
|
21
|
+
!expoAgeRange.getRequiredRegulatoryFeaturesAsync) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
return expoAgeRange.getRequiredRegulatoryFeaturesAsync();
|
|
25
|
+
}
|
|
26
|
+
/** @platform android, `null` elsewhere means unknown rather than not shared */
|
|
27
|
+
export async function requestAgeSignalsAccessAsync() {
|
|
28
|
+
if (Platform.OS !== 'android' || !expoAgeRange.requestAgeSignalsAccessAsync) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
return expoAgeRange.requestAgeSignalsAccessAsync();
|
|
32
|
+
}
|
|
33
|
+
/** @platform android, no-op elsewhere; throws off a non-debuggable build unless `fake` is `null` */
|
|
34
|
+
export function setFakeAgeSignals(fake) {
|
|
35
|
+
if (Platform.OS !== 'android' || !expoAgeRange.setFakeAgeSignals)
|
|
36
|
+
return;
|
|
37
|
+
expoAgeRange.setFakeAgeSignals(fake);
|
|
38
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { getRequiredRegulatoryFeaturesAsync, isEligibleForAgeFeaturesAsync, requestAgeRangeAsync, requestAgeSignalsAccessAsync, setFakeAgeSignals, showSignificantUpdateAcknowledgmentAsync, } from './age-range';
|
|
2
|
+
export type { IAgeRangeRegulatoryFeature, IAgeRangeRequest, IAgeRangeResponse, IAgeSignalsStatus, IFakeAgeSignals, } from './types';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { getRequiredRegulatoryFeaturesAsync, isEligibleForAgeFeaturesAsync, requestAgeRangeAsync, requestAgeSignalsAccessAsync, setFakeAgeSignals, showSignificantUpdateAcknowledgmentAsync, } from './age-range.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { IAgeRangeRegulatoryFeature, IAgeRangeRequest, IAgeRangeResponse, IAgeSignalsStatus, IFakeAgeSignals } from './types';
|
|
2
|
+
export type INativeAgeRangeModule = {
|
|
3
|
+
requestAgeRangeAsync(options: IAgeRangeRequest): Promise<IAgeRangeResponse>;
|
|
4
|
+
isEligibleForAgeFeaturesAsync(): Promise<boolean | null>;
|
|
5
|
+
showSignificantUpdateAcknowledgmentAsync?(updateDescription: string): Promise<void>;
|
|
6
|
+
getRequiredRegulatoryFeaturesAsync?(): Promise<IAgeRangeRegulatoryFeature[] | null>;
|
|
7
|
+
requestAgeSignalsAccessAsync?(): Promise<IAgeSignalsStatus | null>;
|
|
8
|
+
setFakeAgeSignals?(fake: IFakeAgeSignals | null): void;
|
|
9
|
+
};
|
|
10
|
+
export declare const expoAgeRange: INativeAgeRangeModule;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** @platform ios */
|
|
2
|
+
export type IAgeRangeRequest = {
|
|
3
|
+
/** The required minimum age for your app */
|
|
4
|
+
threshold1: number;
|
|
5
|
+
threshold2?: number;
|
|
6
|
+
threshold3?: number;
|
|
7
|
+
};
|
|
8
|
+
export type IAgeRangeResponse = {
|
|
9
|
+
lowerBound: number | null;
|
|
10
|
+
upperBound: number | null;
|
|
11
|
+
/** @platform ios */
|
|
12
|
+
ageRangeDeclaration?: 'selfDeclared' | 'guardianDeclared' | 'confirmed' | null;
|
|
13
|
+
/** @platform ios */
|
|
14
|
+
activeParentalControls?: string[];
|
|
15
|
+
/** @platform android */
|
|
16
|
+
installId?: string | null;
|
|
17
|
+
/** @platform android */
|
|
18
|
+
ageRangeSource?: 'TIER_A' | 'TIER_B' | 'TIER_C' | 'TIER_D' | null;
|
|
19
|
+
/** @platform android */
|
|
20
|
+
significantChangeStatus?: 'APPROVED' | 'PENDING' | 'DECLINED' | null;
|
|
21
|
+
/** @platform android */
|
|
22
|
+
significantChangeApprovalDate?: number | null;
|
|
23
|
+
/** Deprecated, use `significantChangeApprovalDate`. @platform android */
|
|
24
|
+
mostRecentApprovalDate?: number | null;
|
|
25
|
+
};
|
|
26
|
+
/** @platform android */
|
|
27
|
+
export type IAgeSignalsStatus = 'SHARED' | 'NOT_SHARED' | 'VERIFICATION_REQUIRED';
|
|
28
|
+
/** @platform android, omitted fields report as `null` */
|
|
29
|
+
export type IFakeAgeSignals = {
|
|
30
|
+
lowerBound?: number | null;
|
|
31
|
+
upperBound?: number | null;
|
|
32
|
+
installId?: string | null;
|
|
33
|
+
ageRangeSource?: 'TIER_A' | 'TIER_B' | 'TIER_C' | 'TIER_D' | null;
|
|
34
|
+
significantChangeStatus?: 'APPROVED' | 'PENDING' | 'DECLINED' | null;
|
|
35
|
+
significantChangeApprovalDate?: number | null;
|
|
36
|
+
ageSignalsStatus?: IAgeSignalsStatus | null;
|
|
37
|
+
errorCode?: never;
|
|
38
|
+
} | {
|
|
39
|
+
/** https://developer.android.com/google/play/age-signals/handle-errors */
|
|
40
|
+
errorCode: number;
|
|
41
|
+
lowerBound?: never;
|
|
42
|
+
upperBound?: never;
|
|
43
|
+
installId?: never;
|
|
44
|
+
ageRangeSource?: never;
|
|
45
|
+
significantChangeStatus?: never;
|
|
46
|
+
significantChangeApprovalDate?: never;
|
|
47
|
+
ageSignalsStatus?: never;
|
|
48
|
+
};
|
|
49
|
+
/** @platform ios */
|
|
50
|
+
export type IAgeRangeRegulatoryFeature = 'declaredAgeRangeRequired' | 'significantAppChangeRequiresAdultNotification' | 'significantAppChangeRequiresParentalConsent';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../core';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/angular/index.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,cAAc,SAAS,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { IAgeRangeRegulatoryFeature, IAgeRangeRequest, IAgeRangeResponse, IAgeSignalsStatus, IFakeAgeSignals } from './types';
|
|
2
|
+
/** Prompts the user to share their age range; the OS may cache the response for later requests */
|
|
3
|
+
export declare function requestAgeRangeAsync(options: IAgeRangeRequest): Promise<IAgeRangeResponse>;
|
|
4
|
+
export declare function isEligibleForAgeFeaturesAsync(): Promise<boolean | null>;
|
|
5
|
+
/** @platform ios, no-op elsewhere */
|
|
6
|
+
export declare function showSignificantUpdateAcknowledgmentAsync(updateDescription: string): Promise<void>;
|
|
7
|
+
/** @platform ios, `null` elsewhere means unknown rather than none required */
|
|
8
|
+
export declare function getRequiredRegulatoryFeaturesAsync(): Promise<IAgeRangeRegulatoryFeature[] | null>;
|
|
9
|
+
/** @platform android, `null` elsewhere means unknown rather than not shared */
|
|
10
|
+
export declare function requestAgeSignalsAccessAsync(): Promise<IAgeSignalsStatus | null>;
|
|
11
|
+
/** @platform android, no-op elsewhere; throws off a non-debuggable build unless `fake` is `null` */
|
|
12
|
+
export declare function setFakeAgeSignals(fake: IFakeAgeSignals | null): void;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Platform } from 'expo-modules-core';
|
|
2
|
+
import { expoAgeRange } from './native-module';
|
|
3
|
+
/** Prompts the user to share their age range; the OS may cache the response for later requests */
|
|
4
|
+
export async function requestAgeRangeAsync(options) {
|
|
5
|
+
return expoAgeRange.requestAgeRangeAsync(options);
|
|
6
|
+
}
|
|
7
|
+
export async function isEligibleForAgeFeaturesAsync() {
|
|
8
|
+
return expoAgeRange.isEligibleForAgeFeaturesAsync();
|
|
9
|
+
}
|
|
10
|
+
/** @platform ios, no-op elsewhere */
|
|
11
|
+
export async function showSignificantUpdateAcknowledgmentAsync(updateDescription) {
|
|
12
|
+
if (Platform.OS !== 'ios' ||
|
|
13
|
+
!expoAgeRange.showSignificantUpdateAcknowledgmentAsync) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
await expoAgeRange.showSignificantUpdateAcknowledgmentAsync(updateDescription);
|
|
17
|
+
}
|
|
18
|
+
/** @platform ios, `null` elsewhere means unknown rather than none required */
|
|
19
|
+
export async function getRequiredRegulatoryFeaturesAsync() {
|
|
20
|
+
if (Platform.OS !== 'ios' ||
|
|
21
|
+
!expoAgeRange.getRequiredRegulatoryFeaturesAsync) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
return expoAgeRange.getRequiredRegulatoryFeaturesAsync();
|
|
25
|
+
}
|
|
26
|
+
/** @platform android, `null` elsewhere means unknown rather than not shared */
|
|
27
|
+
export async function requestAgeSignalsAccessAsync() {
|
|
28
|
+
if (Platform.OS !== 'android' || !expoAgeRange.requestAgeSignalsAccessAsync) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
return expoAgeRange.requestAgeSignalsAccessAsync();
|
|
32
|
+
}
|
|
33
|
+
/** @platform android, no-op elsewhere; throws off a non-debuggable build unless `fake` is `null` */
|
|
34
|
+
export function setFakeAgeSignals(fake) {
|
|
35
|
+
if (Platform.OS !== 'android' || !expoAgeRange.setFakeAgeSignals)
|
|
36
|
+
return;
|
|
37
|
+
expoAgeRange.setFakeAgeSignals(fake);
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=age-range.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"age-range.js","sourceRoot":"","sources":["../../src/core/age-range.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAS/C,kGAAkG;AAClG,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,OAAyB;IAEzB,OAAO,YAAY,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC;AACpD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,6BAA6B;IACjD,OAAO,YAAY,CAAC,6BAA6B,EAAE,CAAC;AACtD,CAAC;AAED,qCAAqC;AACrC,MAAM,CAAC,KAAK,UAAU,wCAAwC,CAC5D,iBAAyB;IAEzB,IACE,QAAQ,CAAC,EAAE,KAAK,KAAK;QACrB,CAAC,YAAY,CAAC,wCAAwC,EACtD,CAAC;QACD,OAAO;IACT,CAAC;IACD,MAAM,YAAY,CAAC,wCAAwC,CACzD,iBAAiB,CAClB,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,MAAM,CAAC,KAAK,UAAU,kCAAkC;IAGtD,IACE,QAAQ,CAAC,EAAE,KAAK,KAAK;QACrB,CAAC,YAAY,CAAC,kCAAkC,EAChD,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,YAAY,CAAC,kCAAkC,EAAE,CAAC;AAC3D,CAAC;AAED,+EAA+E;AAC/E,MAAM,CAAC,KAAK,UAAU,4BAA4B;IAChD,IAAI,QAAQ,CAAC,EAAE,KAAK,SAAS,IAAI,CAAC,YAAY,CAAC,4BAA4B,EAAE,CAAC;QAC5E,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,YAAY,CAAC,4BAA4B,EAAE,CAAC;AACrD,CAAC;AAED,oGAAoG;AACpG,MAAM,UAAU,iBAAiB,CAAC,IAA4B;IAC5D,IAAI,QAAQ,CAAC,EAAE,KAAK,SAAS,IAAI,CAAC,YAAY,CAAC,iBAAiB;QAAE,OAAO;IACzE,YAAY,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC;AACvC,CAAC"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { getRequiredRegulatoryFeaturesAsync, isEligibleForAgeFeaturesAsync, requestAgeRangeAsync, requestAgeSignalsAccessAsync, setFakeAgeSignals, showSignificantUpdateAcknowledgmentAsync, } from './age-range';
|
|
2
|
+
export type { IAgeRangeRegulatoryFeature, IAgeRangeRequest, IAgeRangeResponse, IAgeSignalsStatus, IFakeAgeSignals, } from './types';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kCAAkC,EAClC,6BAA6B,EAC7B,oBAAoB,EACpB,4BAA4B,EAC5B,iBAAiB,EACjB,wCAAwC,GACzC,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { IAgeRangeRegulatoryFeature, IAgeRangeRequest, IAgeRangeResponse, IAgeSignalsStatus, IFakeAgeSignals } from './types';
|
|
2
|
+
export type INativeAgeRangeModule = {
|
|
3
|
+
requestAgeRangeAsync(options: IAgeRangeRequest): Promise<IAgeRangeResponse>;
|
|
4
|
+
isEligibleForAgeFeaturesAsync(): Promise<boolean | null>;
|
|
5
|
+
showSignificantUpdateAcknowledgmentAsync?(updateDescription: string): Promise<void>;
|
|
6
|
+
getRequiredRegulatoryFeaturesAsync?(): Promise<IAgeRangeRegulatoryFeature[] | null>;
|
|
7
|
+
requestAgeSignalsAccessAsync?(): Promise<IAgeSignalsStatus | null>;
|
|
8
|
+
setFakeAgeSignals?(fake: IFakeAgeSignals | null): void;
|
|
9
|
+
};
|
|
10
|
+
export declare const expoAgeRange: INativeAgeRangeModule;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native-module.js","sourceRoot":"","sources":["../../src/core/native-module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AASxD,MAAM,0BAA0B,GAAG,cAAc,CAAC;AAiBlD,MAAM,CAAC,MAAM,YAAY,GAAG,mBAAmB,CAC7C,0BAA0B,CAC3B,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** @platform ios */
|
|
2
|
+
export type IAgeRangeRequest = {
|
|
3
|
+
/** The required minimum age for your app */
|
|
4
|
+
threshold1: number;
|
|
5
|
+
threshold2?: number;
|
|
6
|
+
threshold3?: number;
|
|
7
|
+
};
|
|
8
|
+
export type IAgeRangeResponse = {
|
|
9
|
+
lowerBound: number | null;
|
|
10
|
+
upperBound: number | null;
|
|
11
|
+
/** @platform ios */
|
|
12
|
+
ageRangeDeclaration?: 'selfDeclared' | 'guardianDeclared' | 'confirmed' | null;
|
|
13
|
+
/** @platform ios */
|
|
14
|
+
activeParentalControls?: string[];
|
|
15
|
+
/** @platform android */
|
|
16
|
+
installId?: string | null;
|
|
17
|
+
/** @platform android */
|
|
18
|
+
ageRangeSource?: 'TIER_A' | 'TIER_B' | 'TIER_C' | 'TIER_D' | null;
|
|
19
|
+
/** @platform android */
|
|
20
|
+
significantChangeStatus?: 'APPROVED' | 'PENDING' | 'DECLINED' | null;
|
|
21
|
+
/** @platform android */
|
|
22
|
+
significantChangeApprovalDate?: number | null;
|
|
23
|
+
/** Deprecated, use `significantChangeApprovalDate`. @platform android */
|
|
24
|
+
mostRecentApprovalDate?: number | null;
|
|
25
|
+
};
|
|
26
|
+
/** @platform android */
|
|
27
|
+
export type IAgeSignalsStatus = 'SHARED' | 'NOT_SHARED' | 'VERIFICATION_REQUIRED';
|
|
28
|
+
/** @platform android, omitted fields report as `null` */
|
|
29
|
+
export type IFakeAgeSignals = {
|
|
30
|
+
lowerBound?: number | null;
|
|
31
|
+
upperBound?: number | null;
|
|
32
|
+
installId?: string | null;
|
|
33
|
+
ageRangeSource?: 'TIER_A' | 'TIER_B' | 'TIER_C' | 'TIER_D' | null;
|
|
34
|
+
significantChangeStatus?: 'APPROVED' | 'PENDING' | 'DECLINED' | null;
|
|
35
|
+
significantChangeApprovalDate?: number | null;
|
|
36
|
+
ageSignalsStatus?: IAgeSignalsStatus | null;
|
|
37
|
+
errorCode?: never;
|
|
38
|
+
} | {
|
|
39
|
+
/** https://developer.android.com/google/play/age-signals/handle-errors */
|
|
40
|
+
errorCode: number;
|
|
41
|
+
lowerBound?: never;
|
|
42
|
+
upperBound?: never;
|
|
43
|
+
installId?: never;
|
|
44
|
+
ageRangeSource?: never;
|
|
45
|
+
significantChangeStatus?: never;
|
|
46
|
+
significantChangeApprovalDate?: never;
|
|
47
|
+
ageSignalsStatus?: never;
|
|
48
|
+
};
|
|
49
|
+
/** @platform ios */
|
|
50
|
+
export type IAgeRangeRegulatoryFeature = 'declaredAgeRangeRequired' | 'significantAppChangeRequiresAdultNotification' | 'significantAppChangeRequiresParentalConsent';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,+BAA+B"}
|
package/native-link.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@symbiote-native/age-range",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "expo-age-range wrapped for SymbioteNative — one framework-agnostic core, built once and reachable from the React, Vue, Svelte, Solid, and Angular adapters. Apple's Declared Age Range API and Google Play's Age Signals.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"react-native",
|
|
8
|
+
"symbiote-native",
|
|
9
|
+
"expo-age-range",
|
|
10
|
+
"react",
|
|
11
|
+
"vue",
|
|
12
|
+
"svelte",
|
|
13
|
+
"solid",
|
|
14
|
+
"angular",
|
|
15
|
+
"age-range",
|
|
16
|
+
"age-signals"
|
|
17
|
+
],
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/OneEyed1366/symbiote-native.git",
|
|
21
|
+
"directory": "packages/age-range"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://github.com/OneEyed1366/symbiote-native/tree/master/packages/age-range#readme",
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/OneEyed1366/symbiote-native/issues"
|
|
26
|
+
},
|
|
27
|
+
"author": "Andrey Prokopenko <psevdoproger@gmail.com>",
|
|
28
|
+
"type": "module",
|
|
29
|
+
"main": "./build/core/index.js",
|
|
30
|
+
"module": "./build/core/index.js",
|
|
31
|
+
"types": "./build/core/index.d.ts",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./build/core/index.d.ts",
|
|
35
|
+
"default": "./build/core/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./vue": {
|
|
38
|
+
"types": "./build/core/index.d.ts",
|
|
39
|
+
"default": "./build/core/index.js"
|
|
40
|
+
},
|
|
41
|
+
"./react": {
|
|
42
|
+
"types": "./build/core/index.d.ts",
|
|
43
|
+
"default": "./build/core/index.js"
|
|
44
|
+
},
|
|
45
|
+
"./svelte": {
|
|
46
|
+
"types": "./build/core/index.d.ts",
|
|
47
|
+
"default": "./build/core/index.js"
|
|
48
|
+
},
|
|
49
|
+
"./solid": {
|
|
50
|
+
"types": "./build/core/index.d.ts",
|
|
51
|
+
"default": "./build/core/index.js"
|
|
52
|
+
},
|
|
53
|
+
"./angular": {
|
|
54
|
+
"types": "./build-ngc/angular/index.d.ts",
|
|
55
|
+
"react-native": "./build-ngc/angular/index.js",
|
|
56
|
+
"default": "./src/angular/index.ts"
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"files": [
|
|
60
|
+
"src",
|
|
61
|
+
"build",
|
|
62
|
+
"build-ngc",
|
|
63
|
+
"native-link.json",
|
|
64
|
+
"!src/**/*.test.*",
|
|
65
|
+
"!src/**/*.spec.*",
|
|
66
|
+
"!src/**/*.detox.*"
|
|
67
|
+
],
|
|
68
|
+
"publishConfig": {
|
|
69
|
+
"access": "public"
|
|
70
|
+
},
|
|
71
|
+
"dependencies": {
|
|
72
|
+
"expo-age-range": "57.0.5",
|
|
73
|
+
"expo-modules-core": "57.0.20"
|
|
74
|
+
},
|
|
75
|
+
"peerDependencies": {
|
|
76
|
+
"@angular/core": ">=20",
|
|
77
|
+
"@vue/runtime-core": "^3.5.13",
|
|
78
|
+
"react": ">=19.0.0",
|
|
79
|
+
"react-native": ">=0.86",
|
|
80
|
+
"solid-js": ">=1.9.0",
|
|
81
|
+
"svelte": ">=5.56.0",
|
|
82
|
+
"vue": ">=3.5.0",
|
|
83
|
+
"@symbiote-native/angular": "^3.1.3",
|
|
84
|
+
"@symbiote-native/react": "^3.1.0",
|
|
85
|
+
"@symbiote-native/svelte": "^3.0.5",
|
|
86
|
+
"@symbiote-native/engine": "^1.4.0",
|
|
87
|
+
"@symbiote-native/vue": "^3.1.0",
|
|
88
|
+
"@symbiote-native/solid": "^3.0.5"
|
|
89
|
+
},
|
|
90
|
+
"peerDependenciesMeta": {
|
|
91
|
+
"@symbiote-native/angular": {
|
|
92
|
+
"optional": true
|
|
93
|
+
},
|
|
94
|
+
"@symbiote-native/react": {
|
|
95
|
+
"optional": true
|
|
96
|
+
},
|
|
97
|
+
"@symbiote-native/solid": {
|
|
98
|
+
"optional": true
|
|
99
|
+
},
|
|
100
|
+
"@symbiote-native/svelte": {
|
|
101
|
+
"optional": true
|
|
102
|
+
},
|
|
103
|
+
"@symbiote-native/vue": {
|
|
104
|
+
"optional": true
|
|
105
|
+
},
|
|
106
|
+
"@angular/core": {
|
|
107
|
+
"optional": true
|
|
108
|
+
},
|
|
109
|
+
"@vue/runtime-core": {
|
|
110
|
+
"optional": true
|
|
111
|
+
},
|
|
112
|
+
"react": {
|
|
113
|
+
"optional": true
|
|
114
|
+
},
|
|
115
|
+
"solid-js": {
|
|
116
|
+
"optional": true
|
|
117
|
+
},
|
|
118
|
+
"svelte": {
|
|
119
|
+
"optional": true
|
|
120
|
+
},
|
|
121
|
+
"vue": {
|
|
122
|
+
"optional": true
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
"devDependencies": {
|
|
126
|
+
"@angular/compiler": "~22.0.8",
|
|
127
|
+
"@angular/compiler-cli": "~22.0.8",
|
|
128
|
+
"@angular/core": "~22.0.8",
|
|
129
|
+
"@types/node": "^26.0.0",
|
|
130
|
+
"@types/react": "^19.2.0",
|
|
131
|
+
"@vue/runtime-core": "^3.5.13",
|
|
132
|
+
"react": "19.2.3",
|
|
133
|
+
"solid-js": "^1.9.14",
|
|
134
|
+
"svelte": "^5.56.0",
|
|
135
|
+
"typescript": "~6.0.0",
|
|
136
|
+
"@symbiote-native/engine": "1.4.0",
|
|
137
|
+
"@symbiote-native/test-utils": "0.4.5",
|
|
138
|
+
"@symbiote-native/angular": "3.1.3",
|
|
139
|
+
"@symbiote-native/solid": "3.0.5",
|
|
140
|
+
"@symbiote-native/react": "3.1.0",
|
|
141
|
+
"@symbiote-native/vue": "3.1.0",
|
|
142
|
+
"@symbiote-native/svelte": "3.0.5"
|
|
143
|
+
},
|
|
144
|
+
"scripts": {
|
|
145
|
+
"typecheck": "tsc --build",
|
|
146
|
+
"clean": "rm -rf build-ngc",
|
|
147
|
+
"ng:build": "pnpm run clean && ngc -p tsconfig.angular.json",
|
|
148
|
+
"format": "prettier --write \"src/**/*.{ts,tsx}\""
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Platform } from 'expo-modules-core';
|
|
2
|
+
import { expoAgeRange } from './native-module';
|
|
3
|
+
import type {
|
|
4
|
+
IAgeRangeRegulatoryFeature,
|
|
5
|
+
IAgeRangeRequest,
|
|
6
|
+
IAgeRangeResponse,
|
|
7
|
+
IAgeSignalsStatus,
|
|
8
|
+
IFakeAgeSignals,
|
|
9
|
+
} from './types';
|
|
10
|
+
|
|
11
|
+
/** Prompts the user to share their age range; the OS may cache the response for later requests */
|
|
12
|
+
export async function requestAgeRangeAsync(
|
|
13
|
+
options: IAgeRangeRequest,
|
|
14
|
+
): Promise<IAgeRangeResponse> {
|
|
15
|
+
return expoAgeRange.requestAgeRangeAsync(options);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function isEligibleForAgeFeaturesAsync(): Promise<boolean | null> {
|
|
19
|
+
return expoAgeRange.isEligibleForAgeFeaturesAsync();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** @platform ios, no-op elsewhere */
|
|
23
|
+
export async function showSignificantUpdateAcknowledgmentAsync(
|
|
24
|
+
updateDescription: string,
|
|
25
|
+
): Promise<void> {
|
|
26
|
+
if (
|
|
27
|
+
Platform.OS !== 'ios' ||
|
|
28
|
+
!expoAgeRange.showSignificantUpdateAcknowledgmentAsync
|
|
29
|
+
) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
await expoAgeRange.showSignificantUpdateAcknowledgmentAsync(
|
|
33
|
+
updateDescription,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** @platform ios, `null` elsewhere means unknown rather than none required */
|
|
38
|
+
export async function getRequiredRegulatoryFeaturesAsync(): Promise<
|
|
39
|
+
IAgeRangeRegulatoryFeature[] | null
|
|
40
|
+
> {
|
|
41
|
+
if (
|
|
42
|
+
Platform.OS !== 'ios' ||
|
|
43
|
+
!expoAgeRange.getRequiredRegulatoryFeaturesAsync
|
|
44
|
+
) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
return expoAgeRange.getRequiredRegulatoryFeaturesAsync();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** @platform android, `null` elsewhere means unknown rather than not shared */
|
|
51
|
+
export async function requestAgeSignalsAccessAsync(): Promise<IAgeSignalsStatus | null> {
|
|
52
|
+
if (Platform.OS !== 'android' || !expoAgeRange.requestAgeSignalsAccessAsync) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
return expoAgeRange.requestAgeSignalsAccessAsync();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** @platform android, no-op elsewhere; throws off a non-debuggable build unless `fake` is `null` */
|
|
59
|
+
export function setFakeAgeSignals(fake: IFakeAgeSignals | null): void {
|
|
60
|
+
if (Platform.OS !== 'android' || !expoAgeRange.setFakeAgeSignals) return;
|
|
61
|
+
expoAgeRange.setFakeAgeSignals(fake);
|
|
62
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export {
|
|
2
|
+
getRequiredRegulatoryFeaturesAsync,
|
|
3
|
+
isEligibleForAgeFeaturesAsync,
|
|
4
|
+
requestAgeRangeAsync,
|
|
5
|
+
requestAgeSignalsAccessAsync,
|
|
6
|
+
setFakeAgeSignals,
|
|
7
|
+
showSignificantUpdateAcknowledgmentAsync,
|
|
8
|
+
} from './age-range';
|
|
9
|
+
export type {
|
|
10
|
+
IAgeRangeRegulatoryFeature,
|
|
11
|
+
IAgeRangeRequest,
|
|
12
|
+
IAgeRangeResponse,
|
|
13
|
+
IAgeSignalsStatus,
|
|
14
|
+
IFakeAgeSignals,
|
|
15
|
+
} from './types';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { requireNativeModule } from 'expo-modules-core';
|
|
2
|
+
import type {
|
|
3
|
+
IAgeRangeRegulatoryFeature,
|
|
4
|
+
IAgeRangeRequest,
|
|
5
|
+
IAgeRangeResponse,
|
|
6
|
+
IAgeSignalsStatus,
|
|
7
|
+
IFakeAgeSignals,
|
|
8
|
+
} from './types';
|
|
9
|
+
|
|
10
|
+
const EXPO_AGE_RANGE_MODULE_NAME = 'ExpoAgeRange';
|
|
11
|
+
|
|
12
|
+
// The iOS-only and android-only methods stay optional here; age-range.ts checks Platform.OS
|
|
13
|
+
// before calling through, same convention as packages/application/src/core/native-module.ts
|
|
14
|
+
export type INativeAgeRangeModule = {
|
|
15
|
+
requestAgeRangeAsync(options: IAgeRangeRequest): Promise<IAgeRangeResponse>;
|
|
16
|
+
isEligibleForAgeFeaturesAsync(): Promise<boolean | null>;
|
|
17
|
+
showSignificantUpdateAcknowledgmentAsync?(
|
|
18
|
+
updateDescription: string,
|
|
19
|
+
): Promise<void>;
|
|
20
|
+
getRequiredRegulatoryFeaturesAsync?(): Promise<
|
|
21
|
+
IAgeRangeRegulatoryFeature[] | null
|
|
22
|
+
>;
|
|
23
|
+
requestAgeSignalsAccessAsync?(): Promise<IAgeSignalsStatus | null>;
|
|
24
|
+
setFakeAgeSignals?(fake: IFakeAgeSignals | null): void;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const expoAgeRange = requireNativeModule<INativeAgeRangeModule>(
|
|
28
|
+
EXPO_AGE_RANGE_MODULE_NAME,
|
|
29
|
+
);
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Ported verbatim from expo-age-range's ExpoAgeRange.types.ts (sdk-57), renamed with this
|
|
2
|
+
// repo's `I`-prefix convention
|
|
3
|
+
|
|
4
|
+
/** @platform ios */
|
|
5
|
+
export type IAgeRangeRequest = {
|
|
6
|
+
/** The required minimum age for your app */
|
|
7
|
+
threshold1: number;
|
|
8
|
+
threshold2?: number;
|
|
9
|
+
threshold3?: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type IAgeRangeResponse = {
|
|
13
|
+
lowerBound: number | null;
|
|
14
|
+
upperBound: number | null;
|
|
15
|
+
/** @platform ios */
|
|
16
|
+
ageRangeDeclaration?:
|
|
17
|
+
'selfDeclared' | 'guardianDeclared' | 'confirmed' | null;
|
|
18
|
+
/** @platform ios */
|
|
19
|
+
activeParentalControls?: string[];
|
|
20
|
+
/** @platform android */
|
|
21
|
+
installId?: string | null;
|
|
22
|
+
/** @platform android */
|
|
23
|
+
ageRangeSource?: 'TIER_A' | 'TIER_B' | 'TIER_C' | 'TIER_D' | null;
|
|
24
|
+
/** @platform android */
|
|
25
|
+
significantChangeStatus?: 'APPROVED' | 'PENDING' | 'DECLINED' | null;
|
|
26
|
+
/** @platform android */
|
|
27
|
+
significantChangeApprovalDate?: number | null;
|
|
28
|
+
/** Deprecated, use `significantChangeApprovalDate`. @platform android */
|
|
29
|
+
mostRecentApprovalDate?: number | null;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/** @platform android */
|
|
33
|
+
export type IAgeSignalsStatus =
|
|
34
|
+
'SHARED' | 'NOT_SHARED' | 'VERIFICATION_REQUIRED';
|
|
35
|
+
|
|
36
|
+
/** @platform android, omitted fields report as `null` */
|
|
37
|
+
export type IFakeAgeSignals =
|
|
38
|
+
| {
|
|
39
|
+
lowerBound?: number | null;
|
|
40
|
+
upperBound?: number | null;
|
|
41
|
+
installId?: string | null;
|
|
42
|
+
ageRangeSource?: 'TIER_A' | 'TIER_B' | 'TIER_C' | 'TIER_D' | null;
|
|
43
|
+
significantChangeStatus?: 'APPROVED' | 'PENDING' | 'DECLINED' | null;
|
|
44
|
+
significantChangeApprovalDate?: number | null;
|
|
45
|
+
ageSignalsStatus?: IAgeSignalsStatus | null;
|
|
46
|
+
errorCode?: never;
|
|
47
|
+
}
|
|
48
|
+
| {
|
|
49
|
+
/** https://developer.android.com/google/play/age-signals/handle-errors */
|
|
50
|
+
errorCode: number;
|
|
51
|
+
lowerBound?: never;
|
|
52
|
+
upperBound?: never;
|
|
53
|
+
installId?: never;
|
|
54
|
+
ageRangeSource?: never;
|
|
55
|
+
significantChangeStatus?: never;
|
|
56
|
+
significantChangeApprovalDate?: never;
|
|
57
|
+
ageSignalsStatus?: never;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/** @platform ios */
|
|
61
|
+
export type IAgeRangeRegulatoryFeature =
|
|
62
|
+
| 'declaredAgeRangeRequired'
|
|
63
|
+
| 'significantAppChangeRequiresAdultNotification'
|
|
64
|
+
| 'significantAppChangeRequiresParentalConsent';
|