react-native-permission-gate 0.1.0 → 0.2.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/README.md +51 -1
- package/lib/module/adapters/reactNativePermissions.js +8 -3
- package/lib/module/adapters/reactNativePermissions.js.map +1 -1
- package/lib/module/core/queue.js +26 -0
- package/lib/module/core/queue.js.map +1 -0
- package/lib/module/hooks/usePermission.js +13 -2
- package/lib/module/hooks/usePermission.js.map +1 -1
- package/lib/module/hooks/usePermissions.js +10 -2
- package/lib/module/hooks/usePermissions.js.map +1 -1
- package/lib/module/imperative.js +14 -4
- package/lib/module/imperative.js.map +1 -1
- package/lib/module/index.js +4 -1
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/adapters/reactNativePermissions.d.ts.map +1 -1
- package/lib/typescript/src/context.d.ts +1 -1
- package/lib/typescript/src/context.d.ts.map +1 -1
- package/lib/typescript/src/core/queue.d.ts +15 -0
- package/lib/typescript/src/core/queue.d.ts.map +1 -0
- package/lib/typescript/src/hooks/usePermission.d.ts.map +1 -1
- package/lib/typescript/src/hooks/usePermissions.d.ts +1 -1
- package/lib/typescript/src/hooks/usePermissions.d.ts.map +1 -1
- package/lib/typescript/src/imperative.d.ts +10 -4
- package/lib/typescript/src/imperative.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +4 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +40 -11
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/adapters/reactNativePermissions.ts +12 -6
- package/src/context.tsx +1 -1
- package/src/core/queue.ts +29 -0
- package/src/hooks/usePermission.ts +21 -2
- package/src/hooks/usePermissions.ts +13 -2
- package/src/imperative.ts +18 -4
- package/src/index.tsx +7 -0
- package/src/types.ts +52 -11
package/README.md
CHANGED
|
@@ -17,6 +17,10 @@ const cam = usePermission('camera', { rationale: true });
|
|
|
17
17
|
{cam.isBlocked && <MyBlockedNotice onOpenSettings={cam.openSettings} />}
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
+
<p align="center">
|
|
21
|
+
<img src="docs/demo.gif" width="300" alt="Demo: rationale sheet, native prompt, blocked handling and multi-permission flow driven by permission-gate" />
|
|
22
|
+
</p>
|
|
23
|
+
|
|
20
24
|
---
|
|
21
25
|
|
|
22
26
|
## Why
|
|
@@ -24,6 +28,7 @@ const cam = usePermission('camera', { rationale: true });
|
|
|
24
28
|
- No baked-in modals or styles - it's headless, so you drive your own components off `status` / `phase`.
|
|
25
29
|
- A real, testable state machine (`idle → rationale → requesting → settled`) instead of ad-hoc booleans.
|
|
26
30
|
- One hook covers the whole flow: check, rationale pre-prompt, native prompt, blocked → Settings, and focus re-validation.
|
|
31
|
+
- Native prompts are serialized app-wide - concurrently mounted gates queue their OS dialogs instead of colliding.
|
|
27
32
|
- `request()` resolves with the final status, so it works imperatively (`await`) or declaratively (render on `phase`).
|
|
28
33
|
- The core has zero native dependencies, so your flows unit-test without a device.
|
|
29
34
|
- Full TypeScript with a normalized cross-platform status model.
|
|
@@ -38,6 +43,28 @@ npm install react-native-permission-gate react-native-permissions
|
|
|
38
43
|
|
|
39
44
|
`react-native-permissions` is a peer dependency and needs its own one-time native setup (iOS `Podfile` `setup_permissions` + `Info.plist` usage strings, Android manifest entries). Follow its [setup guide](https://github.com/zoontek/react-native-permissions#setup) - that's the only native step.
|
|
40
45
|
|
|
46
|
+
### Expo
|
|
47
|
+
|
|
48
|
+
Works with **Expo dev clients / prebuild** (not Expo Go, which can't load custom native modules). Configure the permission handlers through the `react-native-permissions` config plugin and rebuild your dev client:
|
|
49
|
+
|
|
50
|
+
```jsonc
|
|
51
|
+
// app.json
|
|
52
|
+
{
|
|
53
|
+
"expo": {
|
|
54
|
+
"plugins": [
|
|
55
|
+
[
|
|
56
|
+
"react-native-permissions",
|
|
57
|
+
{
|
|
58
|
+
"iosPermissions": ["Camera", "Microphone", "PhotoLibrary", "Notifications"]
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Remember to add the matching `NSxxxUsageDescription` strings via `expo.ios.infoPlist` (and Android permissions via `expo.android.permissions`).
|
|
67
|
+
|
|
41
68
|
## Quick start
|
|
42
69
|
|
|
43
70
|
```tsx
|
|
@@ -119,6 +146,7 @@ const controller = usePermission('camera', {
|
|
|
119
146
|
rationale: false, // gate the native prompt behind a `rationale` phase
|
|
120
147
|
revalidateOnFocus: true, // re-check when the app returns to the foreground
|
|
121
148
|
requestOnMount: false, // run the flow automatically on mount
|
|
149
|
+
notificationOptions: ['alert', 'sound', 'badge'], // iOS notifications: add 'provisional' for quiet delivery
|
|
122
150
|
onStatusChange: (s) => {}, // called when the resolved status changes
|
|
123
151
|
adapter, // override the backend (defaults to react-native-permissions)
|
|
124
152
|
});
|
|
@@ -131,14 +159,16 @@ Returns a `PermissionController`:
|
|
|
131
159
|
| `status` | `PermissionStatus` | Current OS truth |
|
|
132
160
|
| `phase` | `FlowPhase` | Current flow position |
|
|
133
161
|
| `isGranted` | `boolean` | `status === 'granted'` |
|
|
162
|
+
| `isLimited` | `boolean` | `status === 'limited'` (iOS partial access) |
|
|
134
163
|
| `isBlocked` | `boolean` | `status === 'blocked'` |
|
|
135
164
|
| `request()` | `() => Promise<PermissionStatus>` | Run the flow; resolves with the final status |
|
|
136
165
|
| `confirmRationale()` | `() => void` | Proceed from `rationale` to the native prompt |
|
|
137
166
|
| `cancelRationale()` | `() => void` | Abort `rationale`, back to `idle` |
|
|
138
167
|
| `openSettings()` | `() => Promise<void>` | Open this app's OS settings |
|
|
168
|
+
| `openLimitedPicker()` | `() => Promise<void>` | iOS: reopen the limited photo picker while `isLimited` |
|
|
139
169
|
| `refresh()` | `() => Promise<PermissionStatus>` | Re-check on demand |
|
|
140
170
|
|
|
141
|
-
Concurrent/double `request()` calls are de-duplicated - a double-tap never fires two native prompts or orphans a promise.
|
|
171
|
+
Concurrent/double `request()` calls are de-duplicated - a double-tap never fires two native prompts or orphans a promise. Beyond a single hook, **all native prompts are serialized through an app-wide queue**: if two gates mount on the same screen and both auto-request, the OS dialogs appear one after another instead of colliding. Imperative `requestPermission()` goes through the same queue.
|
|
142
172
|
|
|
143
173
|
### `usePermissions(names, options?)`
|
|
144
174
|
|
|
@@ -220,6 +250,26 @@ const adapter: PermissionAdapter = {
|
|
|
220
250
|
const { result } = renderHook(() => usePermission('camera', { adapter }));
|
|
221
251
|
```
|
|
222
252
|
|
|
253
|
+
## Roadmap
|
|
254
|
+
|
|
255
|
+
Actively maintained — this is what I'm planning, roughly in order. Open an issue if you want to influence the priorities, or grab one if you'd like to contribute.
|
|
256
|
+
|
|
257
|
+
- **Web adapter** — a `navigator.permissions`-backed adapter so the same hooks run on react-native-web (query where supported, graceful `unavailable` elsewhere).
|
|
258
|
+
- **Android "never asked" detection** — Android reports both *never asked* and *soft-denied* as the same state before requesting. An optional tiny storage adapter would let the library distinguish them, so you can decide whether to show a rationale on the very first ask.
|
|
259
|
+
- **Expo example app** — a second example using an Expo dev client + the `react-native-permissions` config plugin, so both workflows are covered end to end.
|
|
260
|
+
- **Per-Provider prompt queue** — the queue is currently an app-wide singleton, which is correct for a single OS prompt surface but untestable to override; making it injectable via `PermissionProvider` would help large apps and tests.
|
|
261
|
+
- **More aliases** — `siri`, `nearbyWifiDevices`, iOS `location accuracy` (full vs. reduced) are on the list; open an issue if you're missing one.
|
|
262
|
+
|
|
263
|
+
## Known limitations
|
|
264
|
+
|
|
265
|
+
Things I know about and consciously ship with — so you don't have to discover them the hard way:
|
|
266
|
+
|
|
267
|
+
- **Android can't distinguish "never asked" from "denied once"** before the first request — both surface as `requestable`. `blocked` is only detectable *after* a request settles. This is an OS constraint; the roadmap item above is the planned mitigation.
|
|
268
|
+
- **`notificationOptions` are iOS-only.** Android 13+ notification permission goes through the standard runtime-permission dialog and ignores them.
|
|
269
|
+
- **`limited` is iOS-specific** (photos/contacts partial access). On Android the closest concept — the Android 14+ partial photo picker — is not yet modeled.
|
|
270
|
+
- **Expo Go is not supported** (native module) — use a dev client or prebuild.
|
|
271
|
+
- **The rationale phase is UX-only.** The library doesn't consult Android's `shouldShowRequestPermissionRationale`; you decide when to gate with `rationale: true`. This is by design (headless, explicit), but it means the library won't auto-suggest when a rationale is appropriate.
|
|
272
|
+
|
|
223
273
|
## License
|
|
224
274
|
|
|
225
275
|
MIT © [Emre Cengiz](https://github.com/cengizemre)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import { Platform } from 'react-native';
|
|
4
|
-
import { PERMISSIONS, RESULTS, check, request, checkNotifications, requestNotifications, openSettings } from 'react-native-permissions';
|
|
4
|
+
import { PERMISSIONS, RESULTS, check, request, checkNotifications, requestNotifications, openSettings, openPhotoPicker } from 'react-native-permissions';
|
|
5
5
|
const IOS = {
|
|
6
6
|
camera: PERMISSIONS.IOS.CAMERA,
|
|
7
7
|
microphone: PERMISSIONS.IOS.MICROPHONE,
|
|
@@ -40,6 +40,7 @@ const ANDROID = {
|
|
|
40
40
|
// speechRecognition, appTrackingTransparency, faceId) → unavailable here.
|
|
41
41
|
};
|
|
42
42
|
const TABLE = Platform.OS === 'ios' ? IOS : Platform.OS === 'android' ? ANDROID : {};
|
|
43
|
+
const DEFAULT_NOTIFICATION_OPTIONS = ['alert', 'sound', 'badge'];
|
|
43
44
|
function toStatus(result) {
|
|
44
45
|
switch (result) {
|
|
45
46
|
case RESULTS.GRANTED:
|
|
@@ -79,11 +80,11 @@ export const reactNativePermissionsAdapter = {
|
|
|
79
80
|
if (!native) return 'unavailable';
|
|
80
81
|
return toStatus(await check(native));
|
|
81
82
|
},
|
|
82
|
-
async request(name) {
|
|
83
|
+
async request(name, options) {
|
|
83
84
|
if (name === 'notifications') {
|
|
84
85
|
const {
|
|
85
86
|
status
|
|
86
|
-
} = await requestNotifications(
|
|
87
|
+
} = await requestNotifications(options?.notificationOptions ?? [...DEFAULT_NOTIFICATION_OPTIONS]);
|
|
87
88
|
return toStatus(status);
|
|
88
89
|
}
|
|
89
90
|
const native = TABLE[name];
|
|
@@ -92,6 +93,10 @@ export const reactNativePermissionsAdapter = {
|
|
|
92
93
|
},
|
|
93
94
|
async openSettings() {
|
|
94
95
|
await openSettings();
|
|
96
|
+
},
|
|
97
|
+
async openLimitedPicker() {
|
|
98
|
+
if (Platform.OS !== 'ios') return;
|
|
99
|
+
await openPhotoPicker();
|
|
95
100
|
}
|
|
96
101
|
};
|
|
97
102
|
//# sourceMappingURL=reactNativePermissions.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["Platform","PERMISSIONS","RESULTS","check","request","checkNotifications","requestNotifications","openSettings","IOS","camera","CAMERA","microphone","MICROPHONE","photoLibrary","PHOTO_LIBRARY","photoLibraryAddOnly","PHOTO_LIBRARY_ADD_ONLY","mediaLibrary","MEDIA_LIBRARY","contacts","CONTACTS","calendar","CALENDARS","calendarWriteOnly","CALENDARS_WRITE_ONLY","reminders","REMINDERS","locationWhenInUse","LOCATION_WHEN_IN_USE","locationAlways","LOCATION_ALWAYS","bluetooth","BLUETOOTH","motion","MOTION","speechRecognition","SPEECH_RECOGNITION","appTrackingTransparency","APP_TRACKING_TRANSPARENCY","faceId","FACE_ID","ANDROID","RECORD_AUDIO","READ_MEDIA_IMAGES","storageReadAudio","READ_MEDIA_AUDIO","storageReadVideo","READ_MEDIA_VIDEO","READ_CONTACTS","READ_CALENDAR","WRITE_CALENDAR","ACCESS_FINE_LOCATION","ACCESS_BACKGROUND_LOCATION","BLUETOOTH_CONNECT","ACTIVITY_RECOGNITION","bodySensors","BODY_SENSORS","TABLE","OS","toStatus","result","GRANTED","LIMITED","BLOCKED","DENIED","UNAVAILABLE","reactNativePermissionsAdapter","isSupported","name","undefined","status","native"],"sourceRoot":"../../../src","sources":["adapters/reactNativePermissions.ts"],"mappings":";;AAAA,SAASA,QAAQ,QAAQ,cAAc;AACvC,SACEC,WAAW,EACXC,OAAO,EACPC,KAAK,EACLC,OAAO,EACPC,kBAAkB,EAClBC,oBAAoB,EACpBC,YAAY,
|
|
1
|
+
{"version":3,"names":["Platform","PERMISSIONS","RESULTS","check","request","checkNotifications","requestNotifications","openSettings","openPhotoPicker","IOS","camera","CAMERA","microphone","MICROPHONE","photoLibrary","PHOTO_LIBRARY","photoLibraryAddOnly","PHOTO_LIBRARY_ADD_ONLY","mediaLibrary","MEDIA_LIBRARY","contacts","CONTACTS","calendar","CALENDARS","calendarWriteOnly","CALENDARS_WRITE_ONLY","reminders","REMINDERS","locationWhenInUse","LOCATION_WHEN_IN_USE","locationAlways","LOCATION_ALWAYS","bluetooth","BLUETOOTH","motion","MOTION","speechRecognition","SPEECH_RECOGNITION","appTrackingTransparency","APP_TRACKING_TRANSPARENCY","faceId","FACE_ID","ANDROID","RECORD_AUDIO","READ_MEDIA_IMAGES","storageReadAudio","READ_MEDIA_AUDIO","storageReadVideo","READ_MEDIA_VIDEO","READ_CONTACTS","READ_CALENDAR","WRITE_CALENDAR","ACCESS_FINE_LOCATION","ACCESS_BACKGROUND_LOCATION","BLUETOOTH_CONNECT","ACTIVITY_RECOGNITION","bodySensors","BODY_SENSORS","TABLE","OS","DEFAULT_NOTIFICATION_OPTIONS","toStatus","result","GRANTED","LIMITED","BLOCKED","DENIED","UNAVAILABLE","reactNativePermissionsAdapter","isSupported","name","undefined","status","native","options","notificationOptions","openLimitedPicker"],"sourceRoot":"../../../src","sources":["adapters/reactNativePermissions.ts"],"mappings":";;AAAA,SAASA,QAAQ,QAAQ,cAAc;AACvC,SACEC,WAAW,EACXC,OAAO,EACPC,KAAK,EACLC,OAAO,EACPC,kBAAkB,EAClBC,oBAAoB,EACpBC,YAAY,EACZC,eAAe,QAGV,0BAA0B;AASjC,MAAMC,GAAc,GAAG;EACrBC,MAAM,EAAET,WAAW,CAACQ,GAAG,CAACE,MAAM;EAC9BC,UAAU,EAAEX,WAAW,CAACQ,GAAG,CAACI,UAAU;EACtCC,YAAY,EAAEb,WAAW,CAACQ,GAAG,CAACM,aAAa;EAC3CC,mBAAmB,EAAEf,WAAW,CAACQ,GAAG,CAACQ,sBAAsB;EAC3DC,YAAY,EAAEjB,WAAW,CAACQ,GAAG,CAACU,aAAa;EAC3CC,QAAQ,EAAEnB,WAAW,CAACQ,GAAG,CAACY,QAAQ;EAClCC,QAAQ,EAAErB,WAAW,CAACQ,GAAG,CAACc,SAAS;EACnCC,iBAAiB,EAAEvB,WAAW,CAACQ,GAAG,CAACgB,oBAAoB;EACvDC,SAAS,EAAEzB,WAAW,CAACQ,GAAG,CAACkB,SAAS;EACpCC,iBAAiB,EAAE3B,WAAW,CAACQ,GAAG,CAACoB,oBAAoB;EACvDC,cAAc,EAAE7B,WAAW,CAACQ,GAAG,CAACsB,eAAe;EAC/CC,SAAS,EAAE/B,WAAW,CAACQ,GAAG,CAACwB,SAAS;EACpCC,MAAM,EAAEjC,WAAW,CAACQ,GAAG,CAAC0B,MAAM;EAC9BC,iBAAiB,EAAEnC,WAAW,CAACQ,GAAG,CAAC4B,kBAAkB;EACrDC,uBAAuB,EAAErC,WAAW,CAACQ,GAAG,CAAC8B,yBAAyB;EAClEC,MAAM,EAAEvC,WAAW,CAACQ,GAAG,CAACgC;EACxB;EACA;AACF,CAAC;AAED,MAAMC,OAAkB,GAAG;EACzBhC,MAAM,EAAET,WAAW,CAACyC,OAAO,CAAC/B,MAAM;EAClCC,UAAU,EAAEX,WAAW,CAACyC,OAAO,CAACC,YAAY;EAC5C7B,YAAY,EAAEb,WAAW,CAACyC,OAAO,CAACE,iBAAiB;EACnDC,gBAAgB,EAAE5C,WAAW,CAACyC,OAAO,CAACI,gBAAgB;EACtDC,gBAAgB,EAAE9C,WAAW,CAACyC,OAAO,CAACM,gBAAgB;EACtD5B,QAAQ,EAAEnB,WAAW,CAACyC,OAAO,CAACO,aAAa;EAC3C3B,QAAQ,EAAErB,WAAW,CAACyC,OAAO,CAACQ,aAAa;EAC3C1B,iBAAiB,EAAEvB,WAAW,CAACyC,OAAO,CAACS,cAAc;EACrDvB,iBAAiB,EAAE3B,WAAW,CAACyC,OAAO,CAACU,oBAAoB;EAC3DtB,cAAc,EAAE7B,WAAW,CAACyC,OAAO,CAACW,0BAA0B;EAC9DrB,SAAS,EAAE/B,WAAW,CAACyC,OAAO,CAACY,iBAAiB;EAChDpB,MAAM,EAAEjC,WAAW,CAACyC,OAAO,CAACa,oBAAoB;EAChDC,WAAW,EAAEvD,WAAW,CAACyC,OAAO,CAACe;EACjC;EACA;AACF,CAAC;AAED,MAAMC,KAAgB,GACpB1D,QAAQ,CAAC2D,EAAE,KAAK,KAAK,GAAGlD,GAAG,GAAGT,QAAQ,CAAC2D,EAAE,KAAK,SAAS,GAAGjB,OAAO,GAAG,CAAC,CAAC;AAExE,MAAMkB,4BAA4B,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAU;AAEzE,SAASC,QAAQA,CAACC,MAA0B,EAAoB;EAC9D,QAAQA,MAAM;IACZ,KAAK5D,OAAO,CAAC6D,OAAO;MAClB,OAAO,SAAS;IAClB,KAAK7D,OAAO,CAAC8D,OAAO;MAClB,OAAO,SAAS;IAClB,KAAK9D,OAAO,CAAC+D,OAAO;MAClB,OAAO,SAAS;IAClB,KAAK/D,OAAO,CAACgE,MAAM;MACjB,OAAO,aAAa;IACtB,KAAKhE,OAAO,CAACiE,WAAW;IACxB;MACE,OAAO,aAAa;EACxB;AACF;;AAEA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMC,6BAAgD,GAAG;EAC9DC,WAAWA,CAACC,IAAI,EAAE;IAChB,IAAIA,IAAI,KAAK,eAAe,EAAE;MAC5B,OAAOtE,QAAQ,CAAC2D,EAAE,KAAK,KAAK,IAAI3D,QAAQ,CAAC2D,EAAE,KAAK,SAAS;IAC3D;IACA,OAAOD,KAAK,CAACY,IAAI,CAAC,KAAKC,SAAS;EAClC,CAAC;EAED,MAAMpE,KAAKA,CAACmE,IAAI,EAAE;IAChB,IAAIA,IAAI,KAAK,eAAe,EAAE;MAC5B,MAAM;QAAEE;MAAO,CAAC,GAAG,MAAMnE,kBAAkB,CAAC,CAAC;MAC7C,OAAOwD,QAAQ,CAACW,MAAM,CAAC;IACzB;IACA,MAAMC,MAAM,GAAGf,KAAK,CAACY,IAAI,CAAC;IAC1B,IAAI,CAACG,MAAM,EAAE,OAAO,aAAa;IACjC,OAAOZ,QAAQ,CAAC,MAAM1D,KAAK,CAACsE,MAAM,CAAC,CAAC;EACtC,CAAC;EAED,MAAMrE,OAAOA,CAACkE,IAAI,EAAEI,OAAO,EAAE;IAC3B,IAAIJ,IAAI,KAAK,eAAe,EAAE;MAC5B,MAAM;QAAEE;MAAO,CAAC,GAAG,MAAMlE,oBAAoB,CAC3CoE,OAAO,EAAEC,mBAAmB,IAAI,CAAC,GAAGf,4BAA4B,CAClE,CAAC;MACD,OAAOC,QAAQ,CAACW,MAAM,CAAC;IACzB;IACA,MAAMC,MAAM,GAAGf,KAAK,CAACY,IAAI,CAAC;IAC1B,IAAI,CAACG,MAAM,EAAE,OAAO,aAAa;IACjC,OAAOZ,QAAQ,CAAC,MAAMzD,OAAO,CAACqE,MAAM,CAAC,CAAC;EACxC,CAAC;EAED,MAAMlE,YAAYA,CAAA,EAAG;IACnB,MAAMA,YAAY,CAAC,CAAC;EACtB,CAAC;EAED,MAAMqE,iBAAiBA,CAAA,EAAG;IACxB,IAAI5E,QAAQ,CAAC2D,EAAE,KAAK,KAAK,EAAE;IAC3B,MAAMnD,eAAe,CAAC,CAAC;EACzB;AACF,CAAC","ignoreList":[]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Serializes native permission prompts. The OS can only present one permission
|
|
5
|
+
* dialog at a time — when several hooks/gates request concurrently (e.g. two
|
|
6
|
+
* `<PermissionGate requestOnMount>` mounted on the same screen), firing both
|
|
7
|
+
* native requests at once loses or interleaves prompts. Every adapter
|
|
8
|
+
* `request()` in this library is funneled through this queue so prompts are
|
|
9
|
+
* shown one after another, app-wide.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export function createPromptQueue() {
|
|
13
|
+
let tail = Promise.resolve();
|
|
14
|
+
return {
|
|
15
|
+
enqueue(task) {
|
|
16
|
+
// Run after the previous task settles, whether it resolved or rejected.
|
|
17
|
+
const run = tail.then(task, task);
|
|
18
|
+
tail = run.then(() => undefined, () => undefined);
|
|
19
|
+
return run;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** App-wide queue used by `usePermission` / `usePermissions`. */
|
|
25
|
+
export const promptQueue = createPromptQueue();
|
|
26
|
+
//# sourceMappingURL=queue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["createPromptQueue","tail","Promise","resolve","enqueue","task","run","then","undefined","promptQueue"],"sourceRoot":"../../../src","sources":["core/queue.ts"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAKA,OAAO,SAASA,iBAAiBA,CAAA,EAAgB;EAC/C,IAAIC,IAAsB,GAAGC,OAAO,CAACC,OAAO,CAAC,CAAC;EAC9C,OAAO;IACLC,OAAOA,CAACC,IAAI,EAAE;MACZ;MACA,MAAMC,GAAG,GAAGL,IAAI,CAACM,IAAI,CAACF,IAAI,EAAEA,IAAI,CAAC;MACjCJ,IAAI,GAAGK,GAAG,CAACC,IAAI,CACb,MAAMC,SAAS,EACf,MAAMA,SACR,CAAC;MACD,OAAOF,GAAG;IACZ;EACF,CAAC;AACH;;AAEA;AACA,OAAO,MAAMG,WAAW,GAAGT,iBAAiB,CAAC,CAAC","ignoreList":[]}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
|
4
4
|
import { AppState } from 'react-native';
|
|
5
5
|
import { reduce } from "../core/machine.js";
|
|
6
|
+
import { promptQueue } from "../core/queue.js";
|
|
6
7
|
import { PermissionContext } from "../context.js";
|
|
7
8
|
/**
|
|
8
9
|
* Headless permission-flow controller. Owns the state machine and wires its
|
|
@@ -14,6 +15,7 @@ export function usePermission(name, options = {}) {
|
|
|
14
15
|
const rationale = options.rationale ?? ctx.defaults.rationale ?? false;
|
|
15
16
|
const revalidateOnFocus = options.revalidateOnFocus ?? ctx.defaults.revalidateOnFocus ?? true;
|
|
16
17
|
const requestOnMount = options.requestOnMount ?? ctx.defaults.requestOnMount ?? false;
|
|
18
|
+
const notificationOptions = options.notificationOptions ?? ctx.defaults.notificationOptions;
|
|
17
19
|
const onStatusChange = options.onStatusChange;
|
|
18
20
|
const adapter = options.adapter ?? ctx.adapter;
|
|
19
21
|
const supported = adapter.isSupported(name);
|
|
@@ -29,6 +31,8 @@ export function usePermission(name, options = {}) {
|
|
|
29
31
|
stateRef.current = state;
|
|
30
32
|
const rationaleRef = useRef(rationale);
|
|
31
33
|
rationaleRef.current = rationale;
|
|
34
|
+
const notificationOptionsRef = useRef(notificationOptions);
|
|
35
|
+
notificationOptionsRef.current = notificationOptions;
|
|
32
36
|
const onStatusChangeRef = useRef(onStatusChange);
|
|
33
37
|
onStatusChangeRef.current = onStatusChange;
|
|
34
38
|
const requestOnMountRef = useRef(requestOnMount);
|
|
@@ -53,7 +57,11 @@ export function usePermission(name, options = {}) {
|
|
|
53
57
|
pendingRef.current = null;
|
|
54
58
|
pending?.resolve(effect.status);
|
|
55
59
|
} else if (effect.type === 'runRequest') {
|
|
56
|
-
|
|
60
|
+
// The app-wide queue serializes native prompts across every mounted
|
|
61
|
+
// hook/gate — the OS can only present one dialog at a time.
|
|
62
|
+
promptQueue.enqueue(() => adapter.request(name, {
|
|
63
|
+
notificationOptions: notificationOptionsRef.current
|
|
64
|
+
})).then(status => dispatchRef.current({
|
|
57
65
|
type: 'REQUEST_RESULT',
|
|
58
66
|
status
|
|
59
67
|
})).catch(() => dispatchRef.current({
|
|
@@ -101,6 +109,7 @@ export function usePermission(name, options = {}) {
|
|
|
101
109
|
const refreshRef = useRef(refresh);
|
|
102
110
|
refreshRef.current = refresh;
|
|
103
111
|
const openSettings = useCallback(() => adapter.openSettings(), [adapter]);
|
|
112
|
+
const openLimitedPicker = useCallback(() => adapter.openLimitedPicker?.() ?? Promise.resolve(), [adapter]);
|
|
104
113
|
|
|
105
114
|
// Initial check on mount and whenever the permission identity changes. On a
|
|
106
115
|
// change we reset to a neutral state first so a stale status never lingers.
|
|
@@ -151,12 +160,14 @@ export function usePermission(name, options = {}) {
|
|
|
151
160
|
status: state.status,
|
|
152
161
|
phase: state.phase,
|
|
153
162
|
isGranted: state.status === 'granted',
|
|
163
|
+
isLimited: state.status === 'limited',
|
|
154
164
|
isBlocked: state.status === 'blocked',
|
|
155
165
|
request,
|
|
156
166
|
confirmRationale,
|
|
157
167
|
cancelRationale,
|
|
158
168
|
openSettings,
|
|
169
|
+
openLimitedPicker,
|
|
159
170
|
refresh
|
|
160
|
-
}), [state.status, state.phase, request, confirmRationale, cancelRationale, openSettings, refresh]);
|
|
171
|
+
}), [state.status, state.phase, request, confirmRationale, cancelRationale, openSettings, openLimitedPicker, refresh]);
|
|
161
172
|
}
|
|
162
173
|
//# sourceMappingURL=usePermission.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["useCallback","useContext","useEffect","useMemo","useRef","useState","AppState","reduce","PermissionContext","usePermission","name","options","ctx","rationale","defaults","revalidateOnFocus","requestOnMount","onStatusChange","adapter","supported","isSupported","neutral","status","phase","state","setState","stateRef","current","rationaleRef","onStatusChangeRef","requestOnMountRef","pendingRef","mountedRef","dispatch","event","next","effects","effect","type","pending","resolve","request","then","dispatchRef","catch","promise","Promise","r","requestRef","confirmRationale","cancelRationale","refresh","check","refreshRef","openSettings","firstRunRef","reset","sub","addEventListener","remove","prevStatusRef","isGranted","isBlocked"],"sourceRoot":"../../../src","sources":["hooks/usePermission.ts"],"mappings":";;AAAA,SACEA,WAAW,EACXC,UAAU,EACVC,SAAS,EACTC,OAAO,EACPC,MAAM,EACNC,QAAQ,QACH,OAAO;AACd,SAASC,QAAQ,QAA6B,cAAc;AAC5D,SAASC,MAAM,QAA8C,oBAAiB;AAC9E,SAASC,iBAAiB,QAAQ,eAAY;AAa9C;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,aAAaA,CAC3BC,IAAoB,EACpBC,OAA6B,GAAG,CAAC,CAAC,EACZ;EACtB,MAAMC,GAAG,
|
|
1
|
+
{"version":3,"names":["useCallback","useContext","useEffect","useMemo","useRef","useState","AppState","reduce","promptQueue","PermissionContext","usePermission","name","options","ctx","rationale","defaults","revalidateOnFocus","requestOnMount","notificationOptions","onStatusChange","adapter","supported","isSupported","neutral","status","phase","state","setState","stateRef","current","rationaleRef","notificationOptionsRef","onStatusChangeRef","requestOnMountRef","pendingRef","mountedRef","dispatch","event","next","effects","effect","type","pending","resolve","enqueue","request","then","dispatchRef","catch","promise","Promise","r","requestRef","confirmRationale","cancelRationale","refresh","check","refreshRef","openSettings","openLimitedPicker","firstRunRef","reset","sub","addEventListener","remove","prevStatusRef","isGranted","isLimited","isBlocked"],"sourceRoot":"../../../src","sources":["hooks/usePermission.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
|
4
4
|
import { AppState } from 'react-native';
|
|
5
|
+
import { promptQueue } from "../core/queue.js";
|
|
5
6
|
import { PermissionContext } from "../context.js";
|
|
6
7
|
/**
|
|
7
8
|
* Aggregate variant of {@link usePermission} for permissions acquired together
|
|
8
9
|
* (e.g. camera + microphone for video). One shared rationale gate, one
|
|
9
|
-
* `request()`, one status map. The `names` array is treated as a stable set
|
|
10
|
+
* `request()`, one status map. The `names` array is treated as a stable set —
|
|
10
11
|
* pass a constant or memoized array.
|
|
11
12
|
*/
|
|
12
13
|
export function usePermissions(names, options = {}) {
|
|
@@ -14,6 +15,7 @@ export function usePermissions(names, options = {}) {
|
|
|
14
15
|
const rationale = options.rationale ?? ctx.defaults.rationale ?? false;
|
|
15
16
|
const revalidateOnFocus = options.revalidateOnFocus ?? ctx.defaults.revalidateOnFocus ?? true;
|
|
16
17
|
const requestOnMount = options.requestOnMount ?? ctx.defaults.requestOnMount ?? false;
|
|
18
|
+
const notificationOptions = options.notificationOptions ?? ctx.defaults.notificationOptions;
|
|
17
19
|
const adapter = options.adapter ?? ctx.adapter;
|
|
18
20
|
|
|
19
21
|
// Stable identity for the set so effects don't re-run on every new array.
|
|
@@ -34,6 +36,8 @@ export function usePermissions(names, options = {}) {
|
|
|
34
36
|
phaseRef.current = phase;
|
|
35
37
|
const rationaleRef = useRef(rationale);
|
|
36
38
|
rationaleRef.current = rationale;
|
|
39
|
+
const notificationOptionsRef = useRef(notificationOptions);
|
|
40
|
+
notificationOptionsRef.current = notificationOptions;
|
|
37
41
|
const requestOnMountRef = useRef(requestOnMount);
|
|
38
42
|
requestOnMountRef.current = requestOnMount;
|
|
39
43
|
const pendingRef = useRef(null);
|
|
@@ -53,7 +57,11 @@ export function usePermissions(names, options = {}) {
|
|
|
53
57
|
if (mountedRef.current) setPhase('requesting');
|
|
54
58
|
for (const name of list) {
|
|
55
59
|
try {
|
|
56
|
-
|
|
60
|
+
// Serialized through the app-wide queue: the batch's own prompts run
|
|
61
|
+
// one by one AND never collide with other mounted hooks/gates.
|
|
62
|
+
const status = await promptQueue.enqueue(() => adapter.request(name, {
|
|
63
|
+
notificationOptions: notificationOptionsRef.current
|
|
64
|
+
}));
|
|
57
65
|
commit({
|
|
58
66
|
...statusesRef.current,
|
|
59
67
|
[name]: status
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["useCallback","useContext","useEffect","useMemo","useRef","useState","AppState","PermissionContext","usePermissions","names","options","ctx","rationale","defaults","revalidateOnFocus","requestOnMount","adapter","key","join","buildInitial","map","name","isSupported","statuses","setStatuses","phase","setPhase","statusesRef","current","phaseRef","rationaleRef","requestOnMountRef","pendingRef","requestableRef","mountedRef","commit","next","settle","pending","resolve","runBatch","list","status","request","runBatchRef","promise","Promise","r","requestable","filter","length","requestRef","confirmRationale","cancelRationale","refresh","entries","all","check","refreshRef","openSettings","firstRunRef","then","sub","addEventListener","remove","allGranted","every","anyBlocked","some"],"sourceRoot":"../../../src","sources":["hooks/usePermissions.ts"],"mappings":";;AAAA,SACEA,WAAW,EACXC,UAAU,EACVC,SAAS,EACTC,OAAO,EACPC,MAAM,EACNC,QAAQ,QACH,OAAO;AACd,SAASC,QAAQ,QAA6B,cAAc;AAC5D,SAASC,iBAAiB,QAAQ,eAAY;AAwC9C;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,cAAcA,CAC5BC,KAAuB,EACvBC,OAA6B,GAAG,CAAC,CAAC,EACX;EACvB,MAAMC,GAAG,
|
|
1
|
+
{"version":3,"names":["useCallback","useContext","useEffect","useMemo","useRef","useState","AppState","promptQueue","PermissionContext","usePermissions","names","options","ctx","rationale","defaults","revalidateOnFocus","requestOnMount","notificationOptions","adapter","key","join","buildInitial","map","name","isSupported","statuses","setStatuses","phase","setPhase","statusesRef","current","phaseRef","rationaleRef","notificationOptionsRef","requestOnMountRef","pendingRef","requestableRef","mountedRef","commit","next","settle","pending","resolve","runBatch","list","status","enqueue","request","runBatchRef","promise","Promise","r","requestable","filter","length","requestRef","confirmRationale","cancelRationale","refresh","entries","all","check","refreshRef","openSettings","firstRunRef","then","sub","addEventListener","remove","allGranted","every","anyBlocked","some"],"sourceRoot":"../../../src","sources":["hooks/usePermissions.ts"],"mappings":"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","ignoreList":[]}
|
package/lib/module/imperative.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import { reactNativePermissionsAdapter } from "./adapters/reactNativePermissions.js";
|
|
4
|
+
import { promptQueue } from "./core/queue.js";
|
|
4
5
|
/**
|
|
5
6
|
* Imperative helpers for use outside React (services, sagas, utilities). These
|
|
6
|
-
* are the raw check/request/openSettings calls with no rationale UX
|
|
7
|
+
* are the raw check/request/openSettings calls with no rationale UX — for the
|
|
7
8
|
* full flow with pre-prompts, use {@link usePermission} or {@link PermissionGate}.
|
|
8
9
|
*/
|
|
9
10
|
|
|
@@ -12,13 +13,22 @@ export function checkPermission(name, adapter = reactNativePermissionsAdapter) {
|
|
|
12
13
|
return adapter.check(name);
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
/**
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Trigger the native OS prompt and resolve with the resulting status. Goes
|
|
18
|
+
* through the same app-wide prompt queue as the hooks, so imperative requests
|
|
19
|
+
* never collide with a mounted gate's prompt.
|
|
20
|
+
*/
|
|
21
|
+
export function requestPermission(name, adapter = reactNativePermissionsAdapter, options) {
|
|
22
|
+
return promptQueue.enqueue(() => adapter.request(name, options));
|
|
18
23
|
}
|
|
19
24
|
|
|
20
25
|
/** Open the OS settings screen for this app. */
|
|
21
26
|
export function openAppSettings(adapter = reactNativePermissionsAdapter) {
|
|
22
27
|
return adapter.openSettings();
|
|
23
28
|
}
|
|
29
|
+
|
|
30
|
+
/** iOS: reopen the limited photo picker. No-op if the adapter lacks support. */
|
|
31
|
+
export function openLimitedPicker(adapter = reactNativePermissionsAdapter) {
|
|
32
|
+
return adapter.openLimitedPicker?.() ?? Promise.resolve();
|
|
33
|
+
}
|
|
24
34
|
//# sourceMappingURL=imperative.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["reactNativePermissionsAdapter","checkPermission","name","adapter","check","requestPermission","request","openAppSettings","openSettings"],"sourceRoot":"../../src","sources":["imperative.ts"],"mappings":";;AAAA,SAASA,6BAA6B,QAAQ,sCAAmC;
|
|
1
|
+
{"version":3,"names":["reactNativePermissionsAdapter","promptQueue","checkPermission","name","adapter","check","requestPermission","options","enqueue","request","openAppSettings","openSettings","openLimitedPicker","Promise","resolve"],"sourceRoot":"../../src","sources":["imperative.ts"],"mappings":";;AAAA,SAASA,6BAA6B,QAAQ,sCAAmC;AACjF,SAASC,WAAW,QAAQ,iBAAc;AAQ1C;AACA;AACA;AACA;AACA;;AAEA;AACA,OAAO,SAASC,eAAeA,CAC7BC,IAAoB,EACpBC,OAA0B,GAAGJ,6BAA6B,EAC/B;EAC3B,OAAOI,OAAO,CAACC,KAAK,CAACF,IAAI,CAAC;AAC5B;;AAEA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASG,iBAAiBA,CAC/BH,IAAoB,EACpBC,OAA0B,GAAGJ,6BAA6B,EAC1DO,OAAwB,EACG;EAC3B,OAAON,WAAW,CAACO,OAAO,CAAC,MAAMJ,OAAO,CAACK,OAAO,CAACN,IAAI,EAAEI,OAAO,CAAC,CAAC;AAClE;;AAEA;AACA,OAAO,SAASG,eAAeA,CAC7BN,OAA0B,GAAGJ,6BAA6B,EAC3C;EACf,OAAOI,OAAO,CAACO,YAAY,CAAC,CAAC;AAC/B;;AAEA;AACA,OAAO,SAASC,iBAAiBA,CAC/BR,OAA0B,GAAGJ,6BAA6B,EAC3C;EACf,OAAOI,OAAO,CAACQ,iBAAiB,GAAG,CAAC,IAAIC,OAAO,CAACC,OAAO,CAAC,CAAC;AAC3D","ignoreList":[]}
|
package/lib/module/index.js
CHANGED
|
@@ -8,8 +8,11 @@ export { PermissionGate } from "./components/PermissionGate.js";
|
|
|
8
8
|
// Provider - optional shared adapter + default options.
|
|
9
9
|
export { PermissionProvider, PermissionContext } from "./context.js";
|
|
10
10
|
// Imperative helpers for use outside React.
|
|
11
|
-
export { checkPermission, requestPermission, openAppSettings } from "./imperative.js";
|
|
11
|
+
export { checkPermission, requestPermission, openAppSettings, openLimitedPicker } from "./imperative.js";
|
|
12
12
|
|
|
13
13
|
// Default adapter (override via options.adapter or PermissionProvider).
|
|
14
14
|
export { reactNativePermissionsAdapter } from "./adapters/reactNativePermissions.js";
|
|
15
|
+
|
|
16
|
+
// Prompt queue (app-wide native prompt serialization).
|
|
17
|
+
export { createPromptQueue, promptQueue } from "./core/queue.js";
|
|
15
18
|
//# sourceMappingURL=index.js.map
|
package/lib/module/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["usePermission","usePermissions","PermissionGate","PermissionProvider","PermissionContext","checkPermission","requestPermission","openAppSettings","reactNativePermissionsAdapter"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA;AACA,SAASA,aAAa,QAAQ,0BAAuB;AACrD,SAASC,cAAc,QAAQ,2BAAwB;AAMvD;AACA,SAASC,cAAc,QAAQ,gCAA6B;AAO5D;AACA,SAASC,kBAAkB,EAAEC,iBAAiB,QAAQ,cAAW;AAOjE;AACA,SACEC,eAAe,EACfC,iBAAiB,EACjBC,eAAe,
|
|
1
|
+
{"version":3,"names":["usePermission","usePermissions","PermissionGate","PermissionProvider","PermissionContext","checkPermission","requestPermission","openAppSettings","openLimitedPicker","reactNativePermissionsAdapter","createPromptQueue","promptQueue"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA;AACA,SAASA,aAAa,QAAQ,0BAAuB;AACrD,SAASC,cAAc,QAAQ,2BAAwB;AAMvD;AACA,SAASC,cAAc,QAAQ,gCAA6B;AAO5D;AACA,SAASC,kBAAkB,EAAEC,iBAAiB,QAAQ,cAAW;AAOjE;AACA,SACEC,eAAe,EACfC,iBAAiB,EACjBC,eAAe,EACfC,iBAAiB,QACZ,iBAAc;;AAErB;AACA,SAASC,6BAA6B,QAAQ,sCAAmC;;AAEjF;AACA,SAASC,iBAAiB,EAAEC,WAAW,QAAQ,iBAAc","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reactNativePermissions.d.ts","sourceRoot":"","sources":["../../../../src/adapters/reactNativePermissions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"reactNativePermissions.d.ts","sourceRoot":"","sources":["../../../../src/adapters/reactNativePermissions.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EACV,iBAAiB,EAGlB,MAAM,aAAU,CAAC;AAgElB;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,EAAE,iBAsC3C,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
2
|
import type { PermissionAdapter, UsePermissionOptions } from './types.js';
|
|
3
3
|
/** Options that can be defaulted for every hook/component under a provider. */
|
|
4
|
-
export type PermissionDefaults = Pick<UsePermissionOptions, 'rationale' | 'revalidateOnFocus' | 'requestOnMount'>;
|
|
4
|
+
export type PermissionDefaults = Pick<UsePermissionOptions, 'rationale' | 'revalidateOnFocus' | 'requestOnMount' | 'notificationOptions'>;
|
|
5
5
|
export interface PermissionContextValue {
|
|
6
6
|
adapter: PermissionAdapter;
|
|
7
7
|
defaults: PermissionDefaults;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/context.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAEtD,OAAO,KAAK,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,YAAS,CAAC;AAEvE,+EAA+E;AAC/E,MAAM,MAAM,kBAAkB,GAAG,IAAI,CACnC,oBAAoB,EACpB,WAAW,GAAG,mBAAmB,GAAG,gBAAgB,
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/context.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAEtD,OAAO,KAAK,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,YAAS,CAAC;AAEvE,+EAA+E;AAC/E,MAAM,MAAM,kBAAkB,GAAG,IAAI,CACnC,oBAAoB,EACpB,WAAW,GAAG,mBAAmB,GAAG,gBAAgB,GAAG,qBAAqB,CAC7E,CAAC;AAEF,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,iBAAiB,CAAC;IAC3B,QAAQ,EAAE,kBAAkB,CAAC;CAC9B;AAED,eAAO,MAAM,iBAAiB,iDAG5B,CAAC;AAEH,MAAM,WAAW,uBAAuB;IACtC,+CAA+C;IAC/C,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,OAAuC,EACvC,QAAa,EACb,QAAQ,GACT,EAAE,uBAAuB,+BAMzB"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serializes native permission prompts. The OS can only present one permission
|
|
3
|
+
* dialog at a time — when several hooks/gates request concurrently (e.g. two
|
|
4
|
+
* `<PermissionGate requestOnMount>` mounted on the same screen), firing both
|
|
5
|
+
* native requests at once loses or interleaves prompts. Every adapter
|
|
6
|
+
* `request()` in this library is funneled through this queue so prompts are
|
|
7
|
+
* shown one after another, app-wide.
|
|
8
|
+
*/
|
|
9
|
+
export interface PromptQueue {
|
|
10
|
+
enqueue<T>(task: () => Promise<T>): Promise<T>;
|
|
11
|
+
}
|
|
12
|
+
export declare function createPromptQueue(): PromptQueue;
|
|
13
|
+
/** App-wide queue used by `usePermission` / `usePermissions`. */
|
|
14
|
+
export declare const promptQueue: PromptQueue;
|
|
15
|
+
//# sourceMappingURL=queue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../../../../src/core/queue.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAChD;AAED,wBAAgB,iBAAiB,IAAI,WAAW,CAa/C;AAED,iEAAiE;AACjE,eAAO,MAAM,WAAW,aAAsB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePermission.d.ts","sourceRoot":"","sources":["../../../../src/hooks/usePermission.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"usePermission.d.ts","sourceRoot":"","sources":["../../../../src/hooks/usePermission.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EACV,oBAAoB,EACpB,cAAc,EAEd,oBAAoB,EACrB,MAAM,aAAU,CAAC;AAOlB;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,cAAc,EACpB,OAAO,GAAE,oBAAyB,GACjC,oBAAoB,CA+LtB"}
|
|
@@ -25,7 +25,7 @@ export interface PermissionsController {
|
|
|
25
25
|
/**
|
|
26
26
|
* Aggregate variant of {@link usePermission} for permissions acquired together
|
|
27
27
|
* (e.g. camera + microphone for video). One shared rationale gate, one
|
|
28
|
-
* `request()`, one status map. The `names` array is treated as a stable set
|
|
28
|
+
* `request()`, one status map. The `names` array is treated as a stable set —
|
|
29
29
|
* pass a constant or memoized array.
|
|
30
30
|
*/
|
|
31
31
|
export declare function usePermissions(names: PermissionName[], options?: UsePermissionOptions): PermissionsController;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePermissions.d.ts","sourceRoot":"","sources":["../../../../src/hooks/usePermissions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"usePermissions.d.ts","sourceRoot":"","sources":["../../../../src/hooks/usePermissions.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,SAAS,EACT,cAAc,EACd,gBAAgB,EAChB,oBAAoB,EACrB,MAAM,aAAU,CAAC;AAElB,MAAM,MAAM,mBAAmB,GAAG,OAAO,CACvC,MAAM,CAAC,cAAc,EAAE,gBAAgB,CAAC,CACzC,CAAC;AAEF,MAAM,WAAW,qBAAqB;IACpC,+CAA+C;IAC/C,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,0CAA0C;IAC1C,KAAK,EAAE,SAAS,CAAC;IACjB,+CAA+C;IAC/C,UAAU,EAAE,OAAO,CAAC;IACpB,sDAAsD;IACtD,UAAU,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,OAAO,EAAE,MAAM,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC5C,gEAAgE;IAChE,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,0CAA0C;IAC1C,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,YAAY,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,OAAO,EAAE,MAAM,OAAO,CAAC,mBAAmB,CAAC,CAAC;CAC7C;AAOD;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,cAAc,EAAE,EACvB,OAAO,GAAE,oBAAyB,GACjC,qBAAqB,CAgMvB"}
|
|
@@ -1,13 +1,19 @@
|
|
|
1
|
-
import type { PermissionAdapter, PermissionName, PermissionStatus } from './types.js';
|
|
1
|
+
import type { PermissionAdapter, PermissionName, PermissionStatus, RequestOptions } from './types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Imperative helpers for use outside React (services, sagas, utilities). These
|
|
4
|
-
* are the raw check/request/openSettings calls with no rationale UX
|
|
4
|
+
* are the raw check/request/openSettings calls with no rationale UX — for the
|
|
5
5
|
* full flow with pre-prompts, use {@link usePermission} or {@link PermissionGate}.
|
|
6
6
|
*/
|
|
7
7
|
/** Read current status without prompting. */
|
|
8
8
|
export declare function checkPermission(name: PermissionName, adapter?: PermissionAdapter): Promise<PermissionStatus>;
|
|
9
|
-
/**
|
|
10
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Trigger the native OS prompt and resolve with the resulting status. Goes
|
|
11
|
+
* through the same app-wide prompt queue as the hooks, so imperative requests
|
|
12
|
+
* never collide with a mounted gate's prompt.
|
|
13
|
+
*/
|
|
14
|
+
export declare function requestPermission(name: PermissionName, adapter?: PermissionAdapter, options?: RequestOptions): Promise<PermissionStatus>;
|
|
11
15
|
/** Open the OS settings screen for this app. */
|
|
12
16
|
export declare function openAppSettings(adapter?: PermissionAdapter): Promise<void>;
|
|
17
|
+
/** iOS: reopen the limited photo picker. No-op if the adapter lacks support. */
|
|
18
|
+
export declare function openLimitedPicker(adapter?: PermissionAdapter): Promise<void>;
|
|
13
19
|
//# sourceMappingURL=imperative.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"imperative.d.ts","sourceRoot":"","sources":["../../../src/imperative.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"imperative.d.ts","sourceRoot":"","sources":["../../../src/imperative.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EAChB,cAAc,EACf,MAAM,YAAS,CAAC;AAEjB;;;;GAIG;AAEH,6CAA6C;AAC7C,wBAAgB,eAAe,CAC7B,IAAI,EAAE,cAAc,EACpB,OAAO,GAAE,iBAAiD,GACzD,OAAO,CAAC,gBAAgB,CAAC,CAE3B;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,cAAc,EACpB,OAAO,GAAE,iBAAiD,EAC1D,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,gBAAgB,CAAC,CAE3B;AAED,gDAAgD;AAChD,wBAAgB,eAAe,CAC7B,OAAO,GAAE,iBAAiD,GACzD,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,gFAAgF;AAChF,wBAAgB,iBAAiB,CAC/B,OAAO,GAAE,iBAAiD,GACzD,OAAO,CAAC,IAAI,CAAC,CAEf"}
|
|
@@ -5,7 +5,9 @@ export { PermissionGate } from './components/PermissionGate.js';
|
|
|
5
5
|
export type { PermissionGateProps, RationaleControls, BlockedControls, } from './components/PermissionGate.js';
|
|
6
6
|
export { PermissionProvider, PermissionContext } from './context.js';
|
|
7
7
|
export type { PermissionProviderProps, PermissionContextValue, PermissionDefaults, } from './context.js';
|
|
8
|
-
export { checkPermission, requestPermission, openAppSettings, } from './imperative.js';
|
|
8
|
+
export { checkPermission, requestPermission, openAppSettings, openLimitedPicker, } from './imperative.js';
|
|
9
9
|
export { reactNativePermissionsAdapter } from './adapters/reactNativePermissions.js';
|
|
10
|
-
export
|
|
10
|
+
export { createPromptQueue, promptQueue } from './core/queue.js';
|
|
11
|
+
export type { PromptQueue } from './core/queue.js';
|
|
12
|
+
export type { PermissionName, PermissionStatus, FlowPhase, PermissionController, PermissionAdapter, UsePermissionOptions, NotificationOption, RequestOptions, } from './types.js';
|
|
11
13
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,0BAAuB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,2BAAwB,CAAC;AACxD,YAAY,EACV,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,2BAAwB,CAAC;AAGhC,OAAO,EAAE,cAAc,EAAE,MAAM,gCAA6B,CAAC;AAC7D,YAAY,EACV,mBAAmB,EACnB,iBAAiB,EACjB,eAAe,GAChB,MAAM,gCAA6B,CAAC;AAGrC,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,cAAW,CAAC;AAClE,YAAY,EACV,uBAAuB,EACvB,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,cAAW,CAAC;AAGnB,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,eAAe,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,0BAAuB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,2BAAwB,CAAC;AACxD,YAAY,EACV,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,2BAAwB,CAAC;AAGhC,OAAO,EAAE,cAAc,EAAE,MAAM,gCAA6B,CAAC;AAC7D,YAAY,EACV,mBAAmB,EACnB,iBAAiB,EACjB,eAAe,GAChB,MAAM,gCAA6B,CAAC;AAGrC,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,cAAW,CAAC;AAClE,YAAY,EACV,uBAAuB,EACvB,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,cAAW,CAAC;AAGnB,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,iBAAiB,GAClB,MAAM,iBAAc,CAAC;AAGtB,OAAO,EAAE,6BAA6B,EAAE,MAAM,sCAAmC,CAAC;AAGlF,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,iBAAc,CAAC;AAC9D,YAAY,EAAE,WAAW,EAAE,MAAM,iBAAc,CAAC;AAEhD,YAAY,EACV,cAAc,EACd,gBAAgB,EAChB,SAAS,EACT,oBAAoB,EACpB,iBAAiB,EACjB,oBAAoB,EACpB,kBAAkB,EAClB,cAAc,GACf,MAAM,YAAS,CAAC"}
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The OS-level truth about a permission, normalized across platforms.
|
|
3
3
|
*
|
|
4
|
-
* - `granted`
|
|
5
|
-
* - `limited`
|
|
6
|
-
* - `requestable`
|
|
7
|
-
* - `blocked`
|
|
8
|
-
* - `unavailable`
|
|
4
|
+
* - `granted` — access is allowed.
|
|
5
|
+
* - `limited` — partial access (iOS: e.g. selected photos / contacts).
|
|
6
|
+
* - `requestable` — not granted yet, but the OS prompt can still be shown.
|
|
7
|
+
* - `blocked` — denied and the OS will no longer prompt; only Settings helps.
|
|
8
|
+
* - `unavailable` — the capability does not exist on this device/build.
|
|
9
9
|
*/
|
|
10
10
|
export type PermissionStatus = 'granted' | 'limited' | 'requestable' | 'blocked' | 'unavailable';
|
|
11
11
|
/**
|
|
12
12
|
* Where the request flow currently sits. Independent from {@link PermissionStatus}:
|
|
13
13
|
* `status` is the OS truth, `phase` is the position in the UX state machine.
|
|
14
14
|
*
|
|
15
|
-
* - `idle`
|
|
16
|
-
* - `rationale`
|
|
17
|
-
* - `requesting`
|
|
18
|
-
* - `settled`
|
|
15
|
+
* - `idle` — nothing in flight.
|
|
16
|
+
* - `rationale` — pre-prompt is shown, awaiting the user's decision.
|
|
17
|
+
* - `requesting` — the native OS dialog is up.
|
|
18
|
+
* - `settled` — the flow finished; read `status` for the outcome.
|
|
19
19
|
*/
|
|
20
20
|
export type FlowPhase = 'idle' | 'rationale' | 'requesting' | 'settled';
|
|
21
21
|
/**
|
|
@@ -24,6 +24,17 @@ export type FlowPhase = 'idle' | 'rationale' | 'requesting' | 'settled';
|
|
|
24
24
|
* platform resolve to `unavailable`.
|
|
25
25
|
*/
|
|
26
26
|
export type PermissionName = 'camera' | 'microphone' | 'photoLibrary' | 'photoLibraryAddOnly' | 'mediaLibrary' | 'contacts' | 'calendar' | 'calendarWriteOnly' | 'reminders' | 'locationWhenInUse' | 'locationAlways' | 'notifications' | 'bluetooth' | 'motion' | 'speechRecognition' | 'appTrackingTransparency' | 'bodySensors' | 'storageReadAudio' | 'storageReadVideo' | 'faceId';
|
|
27
|
+
/**
|
|
28
|
+
* iOS notification-authorization options for `notifications` requests.
|
|
29
|
+
* `provisional` delivers quietly without a prompt (iOS 12+); `criticalAlert`
|
|
30
|
+
* requires a special Apple entitlement. Ignored on Android.
|
|
31
|
+
*/
|
|
32
|
+
export type NotificationOption = 'alert' | 'badge' | 'sound' | 'carPlay' | 'criticalAlert' | 'provisional' | 'providesAppSettings';
|
|
33
|
+
/** Extra context forwarded to {@link PermissionAdapter.request}. */
|
|
34
|
+
export interface RequestOptions {
|
|
35
|
+
/** Options for `notifications` requests. Default: `['alert', 'sound', 'badge']`. */
|
|
36
|
+
notificationOptions?: NotificationOption[];
|
|
37
|
+
}
|
|
27
38
|
/**
|
|
28
39
|
* Backend that performs the actual native permission calls. The core state
|
|
29
40
|
* machine is adapter-agnostic, which keeps it fully unit-testable without any
|
|
@@ -35,9 +46,15 @@ export interface PermissionAdapter {
|
|
|
35
46
|
/** Read current status without prompting. */
|
|
36
47
|
check(name: PermissionName): Promise<PermissionStatus>;
|
|
37
48
|
/** Trigger the native OS prompt and resolve with the resulting status. */
|
|
38
|
-
request(name: PermissionName): Promise<PermissionStatus>;
|
|
49
|
+
request(name: PermissionName, options?: RequestOptions): Promise<PermissionStatus>;
|
|
39
50
|
/** Open the OS settings screen for this app. */
|
|
40
51
|
openSettings(): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* iOS: reopen the limited photo-library picker so the user can adjust their
|
|
54
|
+
* selection while `photoLibrary` is `limited`. Optional — adapters without
|
|
55
|
+
* the capability may omit it.
|
|
56
|
+
*/
|
|
57
|
+
openLimitedPicker?(): Promise<void>;
|
|
41
58
|
}
|
|
42
59
|
export interface UsePermissionOptions {
|
|
43
60
|
/**
|
|
@@ -48,7 +65,7 @@ export interface UsePermissionOptions {
|
|
|
48
65
|
*/
|
|
49
66
|
rationale?: boolean;
|
|
50
67
|
/**
|
|
51
|
-
* Re-check status when the app returns to the foreground
|
|
68
|
+
* Re-check status when the app returns to the foreground — catches changes the
|
|
52
69
|
* user made in the OS Settings screen.
|
|
53
70
|
* @default true
|
|
54
71
|
*/
|
|
@@ -58,6 +75,11 @@ export interface UsePermissionOptions {
|
|
|
58
75
|
* @default false
|
|
59
76
|
*/
|
|
60
77
|
requestOnMount?: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* iOS notification-authorization options used when requesting `notifications`.
|
|
80
|
+
* @default ['alert', 'sound', 'badge']
|
|
81
|
+
*/
|
|
82
|
+
notificationOptions?: NotificationOption[];
|
|
61
83
|
/** Called whenever the resolved {@link PermissionStatus} changes. */
|
|
62
84
|
onStatusChange?: (status: PermissionStatus) => void;
|
|
63
85
|
/** Override the backend. Defaults to the `react-native-permissions` adapter. */
|
|
@@ -69,6 +91,8 @@ export interface PermissionController {
|
|
|
69
91
|
phase: FlowPhase;
|
|
70
92
|
/** Convenience: `status === 'granted'`. */
|
|
71
93
|
isGranted: boolean;
|
|
94
|
+
/** Convenience: `status === 'limited'` (iOS partial access). */
|
|
95
|
+
isLimited: boolean;
|
|
72
96
|
/** Convenience: `status === 'blocked'`. */
|
|
73
97
|
isBlocked: boolean;
|
|
74
98
|
/**
|
|
@@ -83,6 +107,11 @@ export interface PermissionController {
|
|
|
83
107
|
cancelRationale: () => void;
|
|
84
108
|
/** Open the OS settings screen for this app. */
|
|
85
109
|
openSettings: () => Promise<void>;
|
|
110
|
+
/**
|
|
111
|
+
* iOS: reopen the limited photo picker while `status === 'limited'`. No-op if
|
|
112
|
+
* the adapter doesn't support it.
|
|
113
|
+
*/
|
|
114
|
+
openLimitedPicker: () => Promise<void>;
|
|
86
115
|
/** Re-check status on demand (also happens automatically on focus). */
|
|
87
116
|
refresh: () => Promise<PermissionStatus>;
|
|
88
117
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,MAAM,gBAAgB,GAC1B,SAAS,GAAG,SAAS,GAAG,aAAa,GAAG,SAAS,GAAG,aAAa,CAAC;AAEpE;;;;;;;;GAQG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,WAAW,GAAG,YAAY,GAAG,SAAS,CAAC;AAExE;;;;GAIG;AACH,MAAM,MAAM,cAAc,GACtB,QAAQ,GACR,YAAY,GACZ,cAAc,GACd,qBAAqB,GACrB,cAAc,GACd,UAAU,GACV,UAAU,GACV,mBAAmB,GACnB,WAAW,GACX,mBAAmB,GACnB,gBAAgB,GAChB,eAAe,GACf,WAAW,GACX,QAAQ,GACR,mBAAmB,GACnB,yBAAyB,GACzB,aAAa,GACb,kBAAkB,GAClB,kBAAkB,GAClB,QAAQ,CAAC;AAEb;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,+DAA+D;IAC/D,WAAW,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC;IAC3C,6CAA6C;IAC7C,KAAK,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACvD,0EAA0E;IAC1E,OAAO,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,MAAM,gBAAgB,GAC1B,SAAS,GAAG,SAAS,GAAG,aAAa,GAAG,SAAS,GAAG,aAAa,CAAC;AAEpE;;;;;;;;GAQG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,WAAW,GAAG,YAAY,GAAG,SAAS,CAAC;AAExE;;;;GAIG;AACH,MAAM,MAAM,cAAc,GACtB,QAAQ,GACR,YAAY,GACZ,cAAc,GACd,qBAAqB,GACrB,cAAc,GACd,UAAU,GACV,UAAU,GACV,mBAAmB,GACnB,WAAW,GACX,mBAAmB,GACnB,gBAAgB,GAChB,eAAe,GACf,WAAW,GACX,QAAQ,GACR,mBAAmB,GACnB,yBAAyB,GACzB,aAAa,GACb,kBAAkB,GAClB,kBAAkB,GAClB,QAAQ,CAAC;AAEb;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAC1B,OAAO,GACP,OAAO,GACP,OAAO,GACP,SAAS,GACT,eAAe,GACf,aAAa,GACb,qBAAqB,CAAC;AAE1B,oEAAoE;AACpE,MAAM,WAAW,cAAc;IAC7B,oFAAoF;IACpF,mBAAmB,CAAC,EAAE,kBAAkB,EAAE,CAAC;CAC5C;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,+DAA+D;IAC/D,WAAW,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC;IAC3C,6CAA6C;IAC7C,KAAK,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACvD,0EAA0E;IAC1E,OAAO,CACL,IAAI,EAAE,cAAc,EACpB,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC7B,gDAAgD;IAChD,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B;;;;OAIG;IACH,iBAAiB,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC;AAED,MAAM,WAAW,oBAAoB;IACnC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAC3C,qEAAqE;IACrE,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC;IACpD,gFAAgF;IAChF,OAAO,CAAC,EAAE,iBAAiB,CAAC;CAC7B;AAED,iEAAiE;AACjE,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,gBAAgB,CAAC;IACzB,KAAK,EAAE,SAAS,CAAC;IACjB,2CAA2C;IAC3C,SAAS,EAAE,OAAO,CAAC;IACnB,gEAAgE;IAChE,SAAS,EAAE,OAAO,CAAC;IACnB,2CAA2C;IAC3C,SAAS,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,OAAO,EAAE,MAAM,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACzC,+DAA+D;IAC/D,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,wDAAwD;IACxD,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,gDAAgD;IAChD,YAAY,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC;;;OAGG;IACH,iBAAiB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,uEAAuE;IACvE,OAAO,EAAE,MAAM,OAAO,CAAC,gBAAgB,CAAC,CAAC;CAC1C"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-permission-gate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Headless permission flow orchestration for React Native. A state-machine hook, rationale gating, and a PermissionGate component. Bring your own UI.",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
checkNotifications,
|
|
8
8
|
requestNotifications,
|
|
9
9
|
openSettings,
|
|
10
|
+
openPhotoPicker,
|
|
10
11
|
type Permission,
|
|
11
12
|
type PermissionStatus as RNPermissionStatus,
|
|
12
13
|
} from 'react-native-permissions';
|
|
@@ -60,6 +61,8 @@ const ANDROID: NativeMap = {
|
|
|
60
61
|
const TABLE: NativeMap =
|
|
61
62
|
Platform.OS === 'ios' ? IOS : Platform.OS === 'android' ? ANDROID : {};
|
|
62
63
|
|
|
64
|
+
const DEFAULT_NOTIFICATION_OPTIONS = ['alert', 'sound', 'badge'] as const;
|
|
65
|
+
|
|
63
66
|
function toStatus(result: RNPermissionStatus): PermissionStatus {
|
|
64
67
|
switch (result) {
|
|
65
68
|
case RESULTS.GRANTED:
|
|
@@ -99,13 +102,11 @@ export const reactNativePermissionsAdapter: PermissionAdapter = {
|
|
|
99
102
|
return toStatus(await check(native));
|
|
100
103
|
},
|
|
101
104
|
|
|
102
|
-
async request(name) {
|
|
105
|
+
async request(name, options) {
|
|
103
106
|
if (name === 'notifications') {
|
|
104
|
-
const { status } = await requestNotifications(
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
'badge',
|
|
108
|
-
]);
|
|
107
|
+
const { status } = await requestNotifications(
|
|
108
|
+
options?.notificationOptions ?? [...DEFAULT_NOTIFICATION_OPTIONS]
|
|
109
|
+
);
|
|
109
110
|
return toStatus(status);
|
|
110
111
|
}
|
|
111
112
|
const native = TABLE[name];
|
|
@@ -116,4 +117,9 @@ export const reactNativePermissionsAdapter: PermissionAdapter = {
|
|
|
116
117
|
async openSettings() {
|
|
117
118
|
await openSettings();
|
|
118
119
|
},
|
|
120
|
+
|
|
121
|
+
async openLimitedPicker() {
|
|
122
|
+
if (Platform.OS !== 'ios') return;
|
|
123
|
+
await openPhotoPicker();
|
|
124
|
+
},
|
|
119
125
|
};
|
package/src/context.tsx
CHANGED
|
@@ -5,7 +5,7 @@ import type { PermissionAdapter, UsePermissionOptions } from './types';
|
|
|
5
5
|
/** Options that can be defaulted for every hook/component under a provider. */
|
|
6
6
|
export type PermissionDefaults = Pick<
|
|
7
7
|
UsePermissionOptions,
|
|
8
|
-
'rationale' | 'revalidateOnFocus' | 'requestOnMount'
|
|
8
|
+
'rationale' | 'revalidateOnFocus' | 'requestOnMount' | 'notificationOptions'
|
|
9
9
|
>;
|
|
10
10
|
|
|
11
11
|
export interface PermissionContextValue {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serializes native permission prompts. The OS can only present one permission
|
|
3
|
+
* dialog at a time — when several hooks/gates request concurrently (e.g. two
|
|
4
|
+
* `<PermissionGate requestOnMount>` mounted on the same screen), firing both
|
|
5
|
+
* native requests at once loses or interleaves prompts. Every adapter
|
|
6
|
+
* `request()` in this library is funneled through this queue so prompts are
|
|
7
|
+
* shown one after another, app-wide.
|
|
8
|
+
*/
|
|
9
|
+
export interface PromptQueue {
|
|
10
|
+
enqueue<T>(task: () => Promise<T>): Promise<T>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function createPromptQueue(): PromptQueue {
|
|
14
|
+
let tail: Promise<unknown> = Promise.resolve();
|
|
15
|
+
return {
|
|
16
|
+
enqueue(task) {
|
|
17
|
+
// Run after the previous task settles, whether it resolved or rejected.
|
|
18
|
+
const run = tail.then(task, task);
|
|
19
|
+
tail = run.then(
|
|
20
|
+
() => undefined,
|
|
21
|
+
() => undefined
|
|
22
|
+
);
|
|
23
|
+
return run;
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** App-wide queue used by `usePermission` / `usePermissions`. */
|
|
29
|
+
export const promptQueue = createPromptQueue();
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
} from 'react';
|
|
9
9
|
import { AppState, type AppStateStatus } from 'react-native';
|
|
10
10
|
import { reduce, type MachineEvent, type MachineState } from '../core/machine';
|
|
11
|
+
import { promptQueue } from '../core/queue';
|
|
11
12
|
import { PermissionContext } from '../context';
|
|
12
13
|
import type {
|
|
13
14
|
PermissionController,
|
|
@@ -37,6 +38,8 @@ export function usePermission(
|
|
|
37
38
|
options.revalidateOnFocus ?? ctx.defaults.revalidateOnFocus ?? true;
|
|
38
39
|
const requestOnMount =
|
|
39
40
|
options.requestOnMount ?? ctx.defaults.requestOnMount ?? false;
|
|
41
|
+
const notificationOptions =
|
|
42
|
+
options.notificationOptions ?? ctx.defaults.notificationOptions;
|
|
40
43
|
const onStatusChange = options.onStatusChange;
|
|
41
44
|
const adapter = options.adapter ?? ctx.adapter;
|
|
42
45
|
|
|
@@ -55,6 +58,8 @@ export function usePermission(
|
|
|
55
58
|
stateRef.current = state;
|
|
56
59
|
const rationaleRef = useRef(rationale);
|
|
57
60
|
rationaleRef.current = rationale;
|
|
61
|
+
const notificationOptionsRef = useRef(notificationOptions);
|
|
62
|
+
notificationOptionsRef.current = notificationOptions;
|
|
58
63
|
const onStatusChangeRef = useRef(onStatusChange);
|
|
59
64
|
onStatusChangeRef.current = onStatusChange;
|
|
60
65
|
const requestOnMountRef = useRef(requestOnMount);
|
|
@@ -79,8 +84,14 @@ export function usePermission(
|
|
|
79
84
|
pendingRef.current = null;
|
|
80
85
|
pending?.resolve(effect.status);
|
|
81
86
|
} else if (effect.type === 'runRequest') {
|
|
82
|
-
|
|
83
|
-
|
|
87
|
+
// The app-wide queue serializes native prompts across every mounted
|
|
88
|
+
// hook/gate — the OS can only present one dialog at a time.
|
|
89
|
+
promptQueue
|
|
90
|
+
.enqueue(() =>
|
|
91
|
+
adapter.request(name, {
|
|
92
|
+
notificationOptions: notificationOptionsRef.current,
|
|
93
|
+
})
|
|
94
|
+
)
|
|
84
95
|
.then((status) =>
|
|
85
96
|
dispatchRef.current({ type: 'REQUEST_RESULT', status })
|
|
86
97
|
)
|
|
@@ -133,6 +144,11 @@ export function usePermission(
|
|
|
133
144
|
|
|
134
145
|
const openSettings = useCallback(() => adapter.openSettings(), [adapter]);
|
|
135
146
|
|
|
147
|
+
const openLimitedPicker = useCallback(
|
|
148
|
+
() => adapter.openLimitedPicker?.() ?? Promise.resolve(),
|
|
149
|
+
[adapter]
|
|
150
|
+
);
|
|
151
|
+
|
|
136
152
|
// Initial check on mount and whenever the permission identity changes. On a
|
|
137
153
|
// change we reset to a neutral state first so a stale status never lingers.
|
|
138
154
|
const firstRunRef = useRef(true);
|
|
@@ -185,11 +201,13 @@ export function usePermission(
|
|
|
185
201
|
status: state.status,
|
|
186
202
|
phase: state.phase,
|
|
187
203
|
isGranted: state.status === 'granted',
|
|
204
|
+
isLimited: state.status === 'limited',
|
|
188
205
|
isBlocked: state.status === 'blocked',
|
|
189
206
|
request,
|
|
190
207
|
confirmRationale,
|
|
191
208
|
cancelRationale,
|
|
192
209
|
openSettings,
|
|
210
|
+
openLimitedPicker,
|
|
193
211
|
refresh,
|
|
194
212
|
}),
|
|
195
213
|
[
|
|
@@ -199,6 +217,7 @@ export function usePermission(
|
|
|
199
217
|
confirmRationale,
|
|
200
218
|
cancelRationale,
|
|
201
219
|
openSettings,
|
|
220
|
+
openLimitedPicker,
|
|
202
221
|
refresh,
|
|
203
222
|
]
|
|
204
223
|
);
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
useState,
|
|
8
8
|
} from 'react';
|
|
9
9
|
import { AppState, type AppStateStatus } from 'react-native';
|
|
10
|
+
import { promptQueue } from '../core/queue';
|
|
10
11
|
import { PermissionContext } from '../context';
|
|
11
12
|
import type {
|
|
12
13
|
FlowPhase,
|
|
@@ -50,7 +51,7 @@ interface Pending {
|
|
|
50
51
|
/**
|
|
51
52
|
* Aggregate variant of {@link usePermission} for permissions acquired together
|
|
52
53
|
* (e.g. camera + microphone for video). One shared rationale gate, one
|
|
53
|
-
* `request()`, one status map. The `names` array is treated as a stable set
|
|
54
|
+
* `request()`, one status map. The `names` array is treated as a stable set —
|
|
54
55
|
* pass a constant or memoized array.
|
|
55
56
|
*/
|
|
56
57
|
export function usePermissions(
|
|
@@ -63,6 +64,8 @@ export function usePermissions(
|
|
|
63
64
|
options.revalidateOnFocus ?? ctx.defaults.revalidateOnFocus ?? true;
|
|
64
65
|
const requestOnMount =
|
|
65
66
|
options.requestOnMount ?? ctx.defaults.requestOnMount ?? false;
|
|
67
|
+
const notificationOptions =
|
|
68
|
+
options.notificationOptions ?? ctx.defaults.notificationOptions;
|
|
66
69
|
const adapter = options.adapter ?? ctx.adapter;
|
|
67
70
|
|
|
68
71
|
// Stable identity for the set so effects don't re-run on every new array.
|
|
@@ -86,6 +89,8 @@ export function usePermissions(
|
|
|
86
89
|
phaseRef.current = phase;
|
|
87
90
|
const rationaleRef = useRef(rationale);
|
|
88
91
|
rationaleRef.current = rationale;
|
|
92
|
+
const notificationOptionsRef = useRef(notificationOptions);
|
|
93
|
+
notificationOptionsRef.current = notificationOptions;
|
|
89
94
|
const requestOnMountRef = useRef(requestOnMount);
|
|
90
95
|
requestOnMountRef.current = requestOnMount;
|
|
91
96
|
|
|
@@ -110,7 +115,13 @@ export function usePermissions(
|
|
|
110
115
|
if (mountedRef.current) setPhase('requesting');
|
|
111
116
|
for (const name of list) {
|
|
112
117
|
try {
|
|
113
|
-
|
|
118
|
+
// Serialized through the app-wide queue: the batch's own prompts run
|
|
119
|
+
// one by one AND never collide with other mounted hooks/gates.
|
|
120
|
+
const status = await promptQueue.enqueue(() =>
|
|
121
|
+
adapter.request(name, {
|
|
122
|
+
notificationOptions: notificationOptionsRef.current,
|
|
123
|
+
})
|
|
124
|
+
);
|
|
114
125
|
commit({ ...statusesRef.current, [name]: status });
|
|
115
126
|
} catch {
|
|
116
127
|
// Keep the previous status for this permission on failure.
|
package/src/imperative.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { reactNativePermissionsAdapter } from './adapters/reactNativePermissions';
|
|
2
|
+
import { promptQueue } from './core/queue';
|
|
2
3
|
import type {
|
|
3
4
|
PermissionAdapter,
|
|
4
5
|
PermissionName,
|
|
5
6
|
PermissionStatus,
|
|
7
|
+
RequestOptions,
|
|
6
8
|
} from './types';
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
11
|
* Imperative helpers for use outside React (services, sagas, utilities). These
|
|
10
|
-
* are the raw check/request/openSettings calls with no rationale UX
|
|
12
|
+
* are the raw check/request/openSettings calls with no rationale UX — for the
|
|
11
13
|
* full flow with pre-prompts, use {@link usePermission} or {@link PermissionGate}.
|
|
12
14
|
*/
|
|
13
15
|
|
|
@@ -19,12 +21,17 @@ export function checkPermission(
|
|
|
19
21
|
return adapter.check(name);
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Trigger the native OS prompt and resolve with the resulting status. Goes
|
|
26
|
+
* through the same app-wide prompt queue as the hooks, so imperative requests
|
|
27
|
+
* never collide with a mounted gate's prompt.
|
|
28
|
+
*/
|
|
23
29
|
export function requestPermission(
|
|
24
30
|
name: PermissionName,
|
|
25
|
-
adapter: PermissionAdapter = reactNativePermissionsAdapter
|
|
31
|
+
adapter: PermissionAdapter = reactNativePermissionsAdapter,
|
|
32
|
+
options?: RequestOptions
|
|
26
33
|
): Promise<PermissionStatus> {
|
|
27
|
-
return adapter.request(name);
|
|
34
|
+
return promptQueue.enqueue(() => adapter.request(name, options));
|
|
28
35
|
}
|
|
29
36
|
|
|
30
37
|
/** Open the OS settings screen for this app. */
|
|
@@ -33,3 +40,10 @@ export function openAppSettings(
|
|
|
33
40
|
): Promise<void> {
|
|
34
41
|
return adapter.openSettings();
|
|
35
42
|
}
|
|
43
|
+
|
|
44
|
+
/** iOS: reopen the limited photo picker. No-op if the adapter lacks support. */
|
|
45
|
+
export function openLimitedPicker(
|
|
46
|
+
adapter: PermissionAdapter = reactNativePermissionsAdapter
|
|
47
|
+
): Promise<void> {
|
|
48
|
+
return adapter.openLimitedPicker?.() ?? Promise.resolve();
|
|
49
|
+
}
|
package/src/index.tsx
CHANGED
|
@@ -27,11 +27,16 @@ export {
|
|
|
27
27
|
checkPermission,
|
|
28
28
|
requestPermission,
|
|
29
29
|
openAppSettings,
|
|
30
|
+
openLimitedPicker,
|
|
30
31
|
} from './imperative';
|
|
31
32
|
|
|
32
33
|
// Default adapter (override via options.adapter or PermissionProvider).
|
|
33
34
|
export { reactNativePermissionsAdapter } from './adapters/reactNativePermissions';
|
|
34
35
|
|
|
36
|
+
// Prompt queue (app-wide native prompt serialization).
|
|
37
|
+
export { createPromptQueue, promptQueue } from './core/queue';
|
|
38
|
+
export type { PromptQueue } from './core/queue';
|
|
39
|
+
|
|
35
40
|
export type {
|
|
36
41
|
PermissionName,
|
|
37
42
|
PermissionStatus,
|
|
@@ -39,4 +44,6 @@ export type {
|
|
|
39
44
|
PermissionController,
|
|
40
45
|
PermissionAdapter,
|
|
41
46
|
UsePermissionOptions,
|
|
47
|
+
NotificationOption,
|
|
48
|
+
RequestOptions,
|
|
42
49
|
} from './types';
|
package/src/types.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The OS-level truth about a permission, normalized across platforms.
|
|
3
3
|
*
|
|
4
|
-
* - `granted`
|
|
5
|
-
* - `limited`
|
|
6
|
-
* - `requestable`
|
|
7
|
-
* - `blocked`
|
|
8
|
-
* - `unavailable`
|
|
4
|
+
* - `granted` — access is allowed.
|
|
5
|
+
* - `limited` — partial access (iOS: e.g. selected photos / contacts).
|
|
6
|
+
* - `requestable` — not granted yet, but the OS prompt can still be shown.
|
|
7
|
+
* - `blocked` — denied and the OS will no longer prompt; only Settings helps.
|
|
8
|
+
* - `unavailable` — the capability does not exist on this device/build.
|
|
9
9
|
*/
|
|
10
10
|
export type PermissionStatus =
|
|
11
11
|
'granted' | 'limited' | 'requestable' | 'blocked' | 'unavailable';
|
|
@@ -14,10 +14,10 @@ export type PermissionStatus =
|
|
|
14
14
|
* Where the request flow currently sits. Independent from {@link PermissionStatus}:
|
|
15
15
|
* `status` is the OS truth, `phase` is the position in the UX state machine.
|
|
16
16
|
*
|
|
17
|
-
* - `idle`
|
|
18
|
-
* - `rationale`
|
|
19
|
-
* - `requesting`
|
|
20
|
-
* - `settled`
|
|
17
|
+
* - `idle` — nothing in flight.
|
|
18
|
+
* - `rationale` — pre-prompt is shown, awaiting the user's decision.
|
|
19
|
+
* - `requesting` — the native OS dialog is up.
|
|
20
|
+
* - `settled` — the flow finished; read `status` for the outcome.
|
|
21
21
|
*/
|
|
22
22
|
export type FlowPhase = 'idle' | 'rationale' | 'requesting' | 'settled';
|
|
23
23
|
|
|
@@ -48,6 +48,26 @@ export type PermissionName =
|
|
|
48
48
|
| 'storageReadVideo'
|
|
49
49
|
| 'faceId';
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* iOS notification-authorization options for `notifications` requests.
|
|
53
|
+
* `provisional` delivers quietly without a prompt (iOS 12+); `criticalAlert`
|
|
54
|
+
* requires a special Apple entitlement. Ignored on Android.
|
|
55
|
+
*/
|
|
56
|
+
export type NotificationOption =
|
|
57
|
+
| 'alert'
|
|
58
|
+
| 'badge'
|
|
59
|
+
| 'sound'
|
|
60
|
+
| 'carPlay'
|
|
61
|
+
| 'criticalAlert'
|
|
62
|
+
| 'provisional'
|
|
63
|
+
| 'providesAppSettings';
|
|
64
|
+
|
|
65
|
+
/** Extra context forwarded to {@link PermissionAdapter.request}. */
|
|
66
|
+
export interface RequestOptions {
|
|
67
|
+
/** Options for `notifications` requests. Default: `['alert', 'sound', 'badge']`. */
|
|
68
|
+
notificationOptions?: NotificationOption[];
|
|
69
|
+
}
|
|
70
|
+
|
|
51
71
|
/**
|
|
52
72
|
* Backend that performs the actual native permission calls. The core state
|
|
53
73
|
* machine is adapter-agnostic, which keeps it fully unit-testable without any
|
|
@@ -59,9 +79,18 @@ export interface PermissionAdapter {
|
|
|
59
79
|
/** Read current status without prompting. */
|
|
60
80
|
check(name: PermissionName): Promise<PermissionStatus>;
|
|
61
81
|
/** Trigger the native OS prompt and resolve with the resulting status. */
|
|
62
|
-
request(
|
|
82
|
+
request(
|
|
83
|
+
name: PermissionName,
|
|
84
|
+
options?: RequestOptions
|
|
85
|
+
): Promise<PermissionStatus>;
|
|
63
86
|
/** Open the OS settings screen for this app. */
|
|
64
87
|
openSettings(): Promise<void>;
|
|
88
|
+
/**
|
|
89
|
+
* iOS: reopen the limited photo-library picker so the user can adjust their
|
|
90
|
+
* selection while `photoLibrary` is `limited`. Optional — adapters without
|
|
91
|
+
* the capability may omit it.
|
|
92
|
+
*/
|
|
93
|
+
openLimitedPicker?(): Promise<void>;
|
|
65
94
|
}
|
|
66
95
|
|
|
67
96
|
export interface UsePermissionOptions {
|
|
@@ -73,7 +102,7 @@ export interface UsePermissionOptions {
|
|
|
73
102
|
*/
|
|
74
103
|
rationale?: boolean;
|
|
75
104
|
/**
|
|
76
|
-
* Re-check status when the app returns to the foreground
|
|
105
|
+
* Re-check status when the app returns to the foreground — catches changes the
|
|
77
106
|
* user made in the OS Settings screen.
|
|
78
107
|
* @default true
|
|
79
108
|
*/
|
|
@@ -83,6 +112,11 @@ export interface UsePermissionOptions {
|
|
|
83
112
|
* @default false
|
|
84
113
|
*/
|
|
85
114
|
requestOnMount?: boolean;
|
|
115
|
+
/**
|
|
116
|
+
* iOS notification-authorization options used when requesting `notifications`.
|
|
117
|
+
* @default ['alert', 'sound', 'badge']
|
|
118
|
+
*/
|
|
119
|
+
notificationOptions?: NotificationOption[];
|
|
86
120
|
/** Called whenever the resolved {@link PermissionStatus} changes. */
|
|
87
121
|
onStatusChange?: (status: PermissionStatus) => void;
|
|
88
122
|
/** Override the backend. Defaults to the `react-native-permissions` adapter. */
|
|
@@ -95,6 +129,8 @@ export interface PermissionController {
|
|
|
95
129
|
phase: FlowPhase;
|
|
96
130
|
/** Convenience: `status === 'granted'`. */
|
|
97
131
|
isGranted: boolean;
|
|
132
|
+
/** Convenience: `status === 'limited'` (iOS partial access). */
|
|
133
|
+
isLimited: boolean;
|
|
98
134
|
/** Convenience: `status === 'blocked'`. */
|
|
99
135
|
isBlocked: boolean;
|
|
100
136
|
/**
|
|
@@ -109,6 +145,11 @@ export interface PermissionController {
|
|
|
109
145
|
cancelRationale: () => void;
|
|
110
146
|
/** Open the OS settings screen for this app. */
|
|
111
147
|
openSettings: () => Promise<void>;
|
|
148
|
+
/**
|
|
149
|
+
* iOS: reopen the limited photo picker while `status === 'limited'`. No-op if
|
|
150
|
+
* the adapter doesn't support it.
|
|
151
|
+
*/
|
|
152
|
+
openLimitedPicker: () => Promise<void>;
|
|
112
153
|
/** Re-check status on demand (also happens automatically on focus). */
|
|
113
154
|
refresh: () => Promise<PermissionStatus>;
|
|
114
155
|
}
|