react-native-permission-handler 0.1.0 → 0.2.1
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 +212 -53
- package/dist/chunk-EU3KPRTI.mjs +81 -0
- package/dist/chunk-EU3KPRTI.mjs.map +1 -0
- package/dist/chunk-NFEGQTCC.mjs +27 -0
- package/dist/chunk-NFEGQTCC.mjs.map +1 -0
- package/dist/engines/expo.d.mts +18 -0
- package/dist/engines/expo.d.ts +18 -0
- package/dist/engines/expo.js +58 -0
- package/dist/engines/expo.js.map +1 -0
- package/dist/engines/expo.mjs +35 -0
- package/dist/engines/expo.mjs.map +1 -0
- package/dist/engines/rnp.d.mts +22 -0
- package/dist/engines/rnp.d.ts +22 -0
- package/dist/engines/rnp.js +83 -0
- package/dist/engines/rnp.js.map +1 -0
- package/dist/engines/rnp.mjs +12 -0
- package/dist/engines/rnp.mjs.map +1 -0
- package/dist/index.d.mts +7 -107
- package/dist/index.d.ts +7 -107
- package/dist/index.js +175 -76
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +98 -70
- package/dist/index.mjs.map +1 -1
- package/dist/types-QXyq8VnD.d.mts +122 -0
- package/dist/types-QXyq8VnD.d.ts +122 -0
- package/package.json +27 -2
- package/src/components/permission-gate.tsx +10 -3
- package/src/engines/expo.test.ts +122 -0
- package/src/engines/expo.ts +45 -0
- package/src/engines/resolve.test.ts +85 -0
- package/src/engines/resolve.ts +11 -0
- package/src/engines/rnp-fallback.ts +23 -0
- package/src/engines/rnp.test.ts +122 -0
- package/src/engines/rnp.ts +68 -0
- package/src/engines/use-engine.ts +10 -0
- package/src/hooks/use-multiple-permissions.test.ts +94 -54
- package/src/hooks/use-multiple-permissions.ts +52 -39
- package/src/hooks/use-permission-handler.test.ts +59 -49
- package/src/hooks/use-permission-handler.ts +11 -40
- package/src/index.ts +3 -0
- package/src/types.ts +20 -3
package/README.md
CHANGED
|
@@ -2,31 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
Smart permission UX flows for React Native. Pre-prompts, blocked handling, settings redirect, and foreground re-check — in one hook.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Works with [`react-native-permissions`](https://github.com/zoontek/react-native-permissions), [Expo modules](https://docs.expo.dev/guides/permissions/), or any custom permissions backend via the pluggable engine architecture.
|
|
6
6
|
|
|
7
7
|
## Why
|
|
8
8
|
|
|
9
|
-
Every React Native app that uses device features needs runtime permissions. The low-level check/request API is solved by `react-native-permissions
|
|
9
|
+
Every React Native app that uses device features needs runtime permissions. The low-level check/request API is solved by libraries like `react-native-permissions` or Expo modules. But the **UX flow** — pre-prompts, blocked state recovery, settings redirect, foreground re-check — is not. Every team builds the same 150+ lines of boilerplate for every permission, in every project.
|
|
10
10
|
|
|
11
|
-
This library handles the full flow in a single hook call.
|
|
11
|
+
This library handles the full flow in a single hook call, with any permissions backend.
|
|
12
12
|
|
|
13
13
|
## Quick Start
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
npm install react-native-permission-handler
|
|
16
|
+
npm install react-native-permission-handler
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
### With react-native-permissions (zero config)
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
If you have `react-native-permissions` installed, everything works out of the box — no engine configuration needed.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install react-native-permissions
|
|
25
|
+
```
|
|
22
26
|
|
|
23
27
|
```tsx
|
|
24
28
|
import { usePermissionHandler } from "react-native-permission-handler";
|
|
25
|
-
import {
|
|
29
|
+
import { Permissions } from "react-native-permission-handler/rnp";
|
|
26
30
|
|
|
27
31
|
function QRScannerScreen() {
|
|
28
32
|
const camera = usePermissionHandler({
|
|
29
|
-
permission:
|
|
33
|
+
permission: Permissions.CAMERA,
|
|
30
34
|
prePrompt: {
|
|
31
35
|
title: "Camera Access",
|
|
32
36
|
message: "We need your camera to scan QR codes.",
|
|
@@ -47,7 +51,72 @@ function QRScannerScreen() {
|
|
|
47
51
|
}
|
|
48
52
|
```
|
|
49
53
|
|
|
50
|
-
|
|
54
|
+
### With Expo modules
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npm install expo-camera expo-notifications # whichever modules you need
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
import { setDefaultEngine } from "react-native-permission-handler";
|
|
62
|
+
import { createExpoEngine } from "react-native-permission-handler/expo";
|
|
63
|
+
import * as Camera from "expo-camera";
|
|
64
|
+
import * as Notifications from "expo-notifications";
|
|
65
|
+
|
|
66
|
+
// Set once at app startup
|
|
67
|
+
setDefaultEngine(
|
|
68
|
+
createExpoEngine({
|
|
69
|
+
permissions: {
|
|
70
|
+
camera: Camera,
|
|
71
|
+
notifications: Notifications,
|
|
72
|
+
},
|
|
73
|
+
})
|
|
74
|
+
);
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Then use the hooks with plain string identifiers:
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
const camera = usePermissionHandler({
|
|
81
|
+
permission: "camera",
|
|
82
|
+
prePrompt: { title: "Camera", message: "We need camera access." },
|
|
83
|
+
blockedPrompt: { title: "Blocked", message: "Enable in Settings." },
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### With a custom engine
|
|
88
|
+
|
|
89
|
+
Implement the `PermissionEngine` interface to use any permissions backend:
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
import type { PermissionEngine } from "react-native-permission-handler";
|
|
93
|
+
|
|
94
|
+
const myEngine: PermissionEngine = {
|
|
95
|
+
check: async (permission) => { /* return "granted" | "denied" | "blocked" | "limited" | "unavailable" */ },
|
|
96
|
+
request: async (permission) => { /* request and return status */ },
|
|
97
|
+
openSettings: async () => { /* open app settings */ },
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// Use globally
|
|
101
|
+
setDefaultEngine(myEngine);
|
|
102
|
+
|
|
103
|
+
// Or per-hook
|
|
104
|
+
const camera = usePermissionHandler({
|
|
105
|
+
permission: "camera",
|
|
106
|
+
engine: myEngine,
|
|
107
|
+
// ...
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Engine Resolution
|
|
112
|
+
|
|
113
|
+
When a hook needs to call a permission API, it resolves the engine in this order:
|
|
114
|
+
|
|
115
|
+
1. **Config prop** — `engine` passed directly to the hook/component
|
|
116
|
+
2. **Global default** — set via `setDefaultEngine()`
|
|
117
|
+
3. **Auto-fallback** — lazily loads `react-native-permissions` if installed
|
|
118
|
+
|
|
119
|
+
If none of the above resolves, a clear error message explains the three options.
|
|
51
120
|
|
|
52
121
|
## State Machine
|
|
53
122
|
|
|
@@ -98,11 +167,11 @@ The core of the library is a pure state machine that drives the entire permissio
|
|
|
98
167
|
| `prePrompt` | Permission is requestable. Show a friendly explanation before the system dialog. |
|
|
99
168
|
| `requesting` | System permission dialog is showing. |
|
|
100
169
|
| `granted` | Permission is granted. Show the protected content. |
|
|
101
|
-
| `denied` | User dismissed the pre-prompt
|
|
170
|
+
| `denied` | User dismissed the pre-prompt or denied via system dialog. Still requestable. |
|
|
102
171
|
| `blocked` | Permission is permanently denied. Only Settings can fix it. |
|
|
103
172
|
| `blockedPrompt` | Showing the "go to Settings" prompt. |
|
|
104
|
-
| `openingSettings` | User tapped "Open Settings". Waiting for
|
|
105
|
-
| `recheckingAfterSettings` | App returned from Settings. Re-checking
|
|
173
|
+
| `openingSettings` | User tapped "Open Settings". Waiting for return. |
|
|
174
|
+
| `recheckingAfterSettings` | App returned from Settings. Re-checking status. |
|
|
106
175
|
| `unavailable` | Device doesn't support this feature. Terminal state. |
|
|
107
176
|
|
|
108
177
|
## API Reference
|
|
@@ -115,33 +184,31 @@ The main hook. Manages the full permission lifecycle.
|
|
|
115
184
|
|
|
116
185
|
```typescript
|
|
117
186
|
{
|
|
118
|
-
//
|
|
119
|
-
|
|
187
|
+
permission: string; // permission identifier (engine-specific)
|
|
188
|
+
engine?: PermissionEngine; // optional — overrides global/fallback
|
|
120
189
|
|
|
121
|
-
// Pre-prompt shown BEFORE the system dialog
|
|
122
190
|
prePrompt: {
|
|
123
191
|
title: string;
|
|
124
192
|
message: string;
|
|
125
|
-
confirmLabel?: string;
|
|
126
|
-
cancelLabel?: string;
|
|
193
|
+
confirmLabel?: string; // default: "Continue"
|
|
194
|
+
cancelLabel?: string; // default: "Not Now"
|
|
127
195
|
};
|
|
128
196
|
|
|
129
|
-
// Prompt shown when permission is permanently blocked
|
|
130
197
|
blockedPrompt: {
|
|
131
198
|
title: string;
|
|
132
199
|
message: string;
|
|
133
|
-
settingsLabel?: string;
|
|
200
|
+
settingsLabel?: string; // default: "Open Settings"
|
|
134
201
|
};
|
|
135
202
|
|
|
136
|
-
// Callbacks
|
|
203
|
+
// Callbacks
|
|
137
204
|
onGrant?: () => void;
|
|
138
205
|
onDeny?: () => void;
|
|
139
206
|
onBlock?: () => void;
|
|
140
207
|
onSettingsReturn?: (granted: boolean) => void;
|
|
141
208
|
|
|
142
209
|
// Options
|
|
143
|
-
autoCheck?: boolean;
|
|
144
|
-
recheckOnForeground?: boolean;
|
|
210
|
+
autoCheck?: boolean; // default: true — check on mount
|
|
211
|
+
recheckOnForeground?: boolean; // default: false
|
|
145
212
|
}
|
|
146
213
|
```
|
|
147
214
|
|
|
@@ -150,7 +217,7 @@ The main hook. Manages the full permission lifecycle.
|
|
|
150
217
|
```typescript
|
|
151
218
|
{
|
|
152
219
|
state: PermissionFlowState; // current state machine state
|
|
153
|
-
nativeStatus: PermissionStatus | null; //
|
|
220
|
+
nativeStatus: PermissionStatus | null; // status from the engine
|
|
154
221
|
|
|
155
222
|
// Convenience booleans
|
|
156
223
|
isGranted: boolean;
|
|
@@ -160,7 +227,7 @@ The main hook. Manages the full permission lifecycle.
|
|
|
160
227
|
isUnavailable: boolean;
|
|
161
228
|
|
|
162
229
|
// Actions
|
|
163
|
-
request: () => void; // confirm pre-prompt
|
|
230
|
+
request: () => void; // confirm pre-prompt -> fire system dialog
|
|
164
231
|
check: () => void; // manually re-check permission status
|
|
165
232
|
dismiss: () => void; // dismiss pre-prompt ("Not Now")
|
|
166
233
|
openSettings: () => void; // open app settings for blocked permissions
|
|
@@ -172,7 +239,7 @@ The main hook. Manages the full permission lifecycle.
|
|
|
172
239
|
```tsx
|
|
173
240
|
function CameraScreen() {
|
|
174
241
|
const camera = usePermissionHandler({
|
|
175
|
-
permission:
|
|
242
|
+
permission: Permissions.CAMERA,
|
|
176
243
|
prePrompt: {
|
|
177
244
|
title: "Camera Access",
|
|
178
245
|
message: "We need your camera to scan QR codes.",
|
|
@@ -221,7 +288,7 @@ Orchestrates flows for features needing multiple permissions (e.g., video call =
|
|
|
221
288
|
```typescript
|
|
222
289
|
{
|
|
223
290
|
permissions: Array<{
|
|
224
|
-
permission:
|
|
291
|
+
permission: string;
|
|
225
292
|
prePrompt: PrePromptConfig;
|
|
226
293
|
blockedPrompt: BlockedPromptConfig;
|
|
227
294
|
onGrant?: () => void;
|
|
@@ -233,6 +300,8 @@ Orchestrates flows for features needing multiple permissions (e.g., video call =
|
|
|
233
300
|
// sequential: ask one at a time, stop on denial/block
|
|
234
301
|
// parallel: check all at once, then request denied ones
|
|
235
302
|
|
|
303
|
+
engine?: PermissionEngine; // optional — overrides global/fallback
|
|
304
|
+
autoCheck?: boolean; // default: true — check all on mount
|
|
236
305
|
onAllGranted?: () => void;
|
|
237
306
|
}
|
|
238
307
|
```
|
|
@@ -254,12 +323,12 @@ function VideoCallScreen() {
|
|
|
254
323
|
const perms = useMultiplePermissions({
|
|
255
324
|
permissions: [
|
|
256
325
|
{
|
|
257
|
-
permission:
|
|
326
|
+
permission: Permissions.CAMERA,
|
|
258
327
|
prePrompt: { title: "Camera", message: "Needed for video." },
|
|
259
328
|
blockedPrompt: { title: "Camera Blocked", message: "Enable in Settings." },
|
|
260
329
|
},
|
|
261
330
|
{
|
|
262
|
-
permission:
|
|
331
|
+
permission: Permissions.MICROPHONE,
|
|
263
332
|
prePrompt: { title: "Microphone", message: "Needed for audio." },
|
|
264
333
|
blockedPrompt: { title: "Mic Blocked", message: "Enable in Settings." },
|
|
265
334
|
},
|
|
@@ -284,7 +353,8 @@ Declarative component that renders children only when permission is granted.
|
|
|
284
353
|
|
|
285
354
|
```typescript
|
|
286
355
|
{
|
|
287
|
-
permission:
|
|
356
|
+
permission: string;
|
|
357
|
+
engine?: PermissionEngine;
|
|
288
358
|
prePrompt: PrePromptConfig;
|
|
289
359
|
blockedPrompt: BlockedPromptConfig;
|
|
290
360
|
children: ReactNode; // shown when granted
|
|
@@ -294,7 +364,7 @@ Declarative component that renders children only when permission is granted.
|
|
|
294
364
|
onBlock?: () => void;
|
|
295
365
|
onSettingsReturn?: (granted: boolean) => void;
|
|
296
366
|
|
|
297
|
-
// Custom UI (optional
|
|
367
|
+
// Custom UI (optional -- default modals are used if omitted)
|
|
298
368
|
renderPrePrompt?: (props: {
|
|
299
369
|
config: PrePromptConfig;
|
|
300
370
|
onConfirm: () => void;
|
|
@@ -311,7 +381,7 @@ Declarative component that renders children only when permission is granted.
|
|
|
311
381
|
|
|
312
382
|
```tsx
|
|
313
383
|
<PermissionGate
|
|
314
|
-
permission={
|
|
384
|
+
permission={Permissions.CAMERA}
|
|
315
385
|
prePrompt={{ title: "Camera", message: "We need camera access." }}
|
|
316
386
|
blockedPrompt={{ title: "Blocked", message: "Enable in Settings." }}
|
|
317
387
|
fallback={<LoadingSpinner />}
|
|
@@ -360,47 +430,136 @@ The raw state machine function. For advanced use cases where you want to build y
|
|
|
360
430
|
import { transition } from "react-native-permission-handler";
|
|
361
431
|
|
|
362
432
|
const next = transition("prePrompt", { type: "PRE_PROMPT_CONFIRM" });
|
|
363
|
-
//
|
|
433
|
+
// -> "requesting"
|
|
364
434
|
```
|
|
365
435
|
|
|
366
436
|
Pure function, no side effects, no React dependency.
|
|
367
437
|
|
|
438
|
+
---
|
|
439
|
+
|
|
440
|
+
### `setDefaultEngine(engine)`
|
|
441
|
+
|
|
442
|
+
Set a global default `PermissionEngine` for all hooks and components. Call once at app startup.
|
|
443
|
+
|
|
444
|
+
```typescript
|
|
445
|
+
import { setDefaultEngine } from "react-native-permission-handler";
|
|
446
|
+
|
|
447
|
+
setDefaultEngine(myEngine);
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
---
|
|
451
|
+
|
|
452
|
+
### `createRNPEngine()`
|
|
453
|
+
|
|
454
|
+
Create an engine adapter for `react-native-permissions`. Handles notification routing internally.
|
|
455
|
+
|
|
456
|
+
```typescript
|
|
457
|
+
import { createRNPEngine } from "react-native-permission-handler/rnp";
|
|
458
|
+
|
|
459
|
+
const engine = createRNPEngine();
|
|
460
|
+
setDefaultEngine(engine);
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
You don't need to call this explicitly if `react-native-permissions` is installed — the library auto-creates it as a fallback.
|
|
464
|
+
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
### `Permissions` (cross-platform constants)
|
|
468
|
+
|
|
469
|
+
The RNP entry point also exports cross-platform permission constants that resolve to the correct platform-specific string via `Platform.select`:
|
|
470
|
+
|
|
471
|
+
```typescript
|
|
472
|
+
import { Permissions } from "react-native-permission-handler/rnp";
|
|
473
|
+
|
|
474
|
+
Permissions.CAMERA // ios: "ios.permission.CAMERA", android: "android.permission.CAMERA"
|
|
475
|
+
Permissions.MICROPHONE // ios: "ios.permission.MICROPHONE", android: "android.permission.RECORD_AUDIO"
|
|
476
|
+
Permissions.CONTACTS // ios: "ios.permission.CONTACTS", android: "android.permission.READ_CONTACTS"
|
|
477
|
+
Permissions.CALENDARS // ios: "ios.permission.CALENDARS", android: "android.permission.READ_CALENDAR"
|
|
478
|
+
Permissions.LOCATION_WHEN_IN_USE
|
|
479
|
+
Permissions.LOCATION_ALWAYS
|
|
480
|
+
Permissions.PHOTO_LIBRARY
|
|
481
|
+
Permissions.PHOTO_LIBRARY_ADD_ONLY
|
|
482
|
+
Permissions.BLUETOOTH
|
|
483
|
+
Permissions.NOTIFICATIONS // "notifications" (routed to notification-specific APIs by the engine)
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
For platform-specific permissions not in this map, pass the raw string directly (e.g., `"ios.permission.FACE_ID"`).
|
|
487
|
+
|
|
488
|
+
---
|
|
489
|
+
|
|
490
|
+
### `createExpoEngine(config)`
|
|
491
|
+
|
|
492
|
+
Create an engine adapter for Expo permission modules. Pass a map of permission keys to Expo modules.
|
|
493
|
+
|
|
494
|
+
```typescript
|
|
495
|
+
import { createExpoEngine } from "react-native-permission-handler/expo";
|
|
496
|
+
import * as Camera from "expo-camera";
|
|
497
|
+
import * as Location from "expo-location";
|
|
498
|
+
import * as Notifications from "expo-notifications";
|
|
499
|
+
|
|
500
|
+
const engine = createExpoEngine({
|
|
501
|
+
permissions: {
|
|
502
|
+
camera: Camera,
|
|
503
|
+
location: Location,
|
|
504
|
+
notifications: Notifications,
|
|
505
|
+
},
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
setDefaultEngine(engine);
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
The adapter maps Expo's `{ status, canAskAgain }` response to the library's `PermissionStatus`:
|
|
512
|
+
|
|
513
|
+
| Expo status | `canAskAgain` | Maps to |
|
|
514
|
+
|---|---|---|
|
|
515
|
+
| `"granted"` | — | `"granted"` |
|
|
516
|
+
| `"undetermined"` | — | `"denied"` |
|
|
517
|
+
| `"denied"` | `true` | `"denied"` |
|
|
518
|
+
| `"denied"` | `false` | `"blocked"` |
|
|
519
|
+
|
|
520
|
+
---
|
|
521
|
+
|
|
522
|
+
### `PermissionEngine` interface
|
|
523
|
+
|
|
524
|
+
Implement this to use any permissions backend:
|
|
525
|
+
|
|
526
|
+
```typescript
|
|
527
|
+
interface PermissionEngine {
|
|
528
|
+
check(permission: string): Promise<PermissionStatus>;
|
|
529
|
+
request(permission: string): Promise<PermissionStatus>;
|
|
530
|
+
openSettings(): Promise<void>;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
type PermissionStatus = "granted" | "denied" | "blocked" | "limited" | "unavailable";
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
The engine is responsible for:
|
|
537
|
+
- Mapping its native status values to the library's `PermissionStatus`
|
|
538
|
+
- Handling special cases like notifications internally
|
|
539
|
+
- Opening the correct settings screen
|
|
540
|
+
|
|
368
541
|
## Platform Notes
|
|
369
542
|
|
|
370
543
|
**iOS:**
|
|
371
|
-
- The system permission dialog can only be shown **once** per permission. If the user denies it, you can never show it again programmatically. This is why the pre-prompt is critical
|
|
544
|
+
- The system permission dialog can only be shown **once** per permission. If the user denies it, you can never show it again programmatically. This is why the pre-prompt is critical.
|
|
372
545
|
- `check()` returns `DENIED` for both "never asked" and "denied once" — both are still requestable.
|
|
373
546
|
- `BLOCKED` means the user denied via the system dialog or disabled the permission in Settings.
|
|
374
547
|
|
|
375
548
|
**Android:**
|
|
376
549
|
- After 2 denials, Android 11+ auto-blocks the permission. No more system dialogs.
|
|
377
|
-
- For notification permissions on Android 13+, `checkNotifications()` never returns `BLOCKED` — the
|
|
550
|
+
- For notification permissions on Android 13+, `checkNotifications()` never returns `BLOCKED` — the RNP engine handles this by using `requestNotifications()` for accurate status.
|
|
378
551
|
|
|
379
552
|
**Notifications:**
|
|
380
|
-
- Pass `"notifications"` as the permission identifier. The
|
|
553
|
+
- Pass `"notifications"` as the permission identifier. The RNP engine automatically routes to `checkNotifications`/`requestNotifications`. For Expo, map `"notifications"` to `expo-notifications` in the engine config.
|
|
381
554
|
|
|
382
555
|
## Requirements
|
|
383
556
|
|
|
384
557
|
- React Native >= 0.76
|
|
385
558
|
- React >= 18
|
|
386
|
-
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
```json
|
|
391
|
-
{
|
|
392
|
-
"plugins": [
|
|
393
|
-
[
|
|
394
|
-
"react-native-permissions",
|
|
395
|
-
{
|
|
396
|
-
"iosPermissions": ["Camera", "Microphone", "LocationWhenInUse"]
|
|
397
|
-
}
|
|
398
|
-
]
|
|
399
|
-
]
|
|
400
|
-
}
|
|
401
|
-
```
|
|
402
|
-
|
|
403
|
-
**Bare React Native:** Follow the [react-native-permissions setup guide](https://github.com/zoontek/react-native-permissions#setup).
|
|
559
|
+
- One of:
|
|
560
|
+
- `react-native-permissions` >= 4.0.0 (auto-detected, no config needed)
|
|
561
|
+
- Expo permission modules (use `createExpoEngine`)
|
|
562
|
+
- Custom `PermissionEngine` implementation
|
|
404
563
|
|
|
405
564
|
## License
|
|
406
565
|
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__esm,
|
|
3
|
+
__export
|
|
4
|
+
} from "./chunk-NFEGQTCC.mjs";
|
|
5
|
+
|
|
6
|
+
// src/engines/rnp.ts
|
|
7
|
+
var rnp_exports = {};
|
|
8
|
+
__export(rnp_exports, {
|
|
9
|
+
Permissions: () => Permissions,
|
|
10
|
+
createRNPEngine: () => createRNPEngine
|
|
11
|
+
});
|
|
12
|
+
import { Platform } from "react-native";
|
|
13
|
+
import {
|
|
14
|
+
check,
|
|
15
|
+
checkNotifications,
|
|
16
|
+
openSettings,
|
|
17
|
+
request,
|
|
18
|
+
requestNotifications
|
|
19
|
+
} from "react-native-permissions";
|
|
20
|
+
function p(ios, android) {
|
|
21
|
+
return Platform.select({ ios, android, default: ios }) ?? ios;
|
|
22
|
+
}
|
|
23
|
+
function createRNPEngine() {
|
|
24
|
+
return {
|
|
25
|
+
async check(permission) {
|
|
26
|
+
if (permission === "notifications") {
|
|
27
|
+
const result = await checkNotifications();
|
|
28
|
+
return result.status;
|
|
29
|
+
}
|
|
30
|
+
return await check(permission);
|
|
31
|
+
},
|
|
32
|
+
async request(permission) {
|
|
33
|
+
if (permission === "notifications") {
|
|
34
|
+
const result = await requestNotifications(["alert", "badge", "sound"]);
|
|
35
|
+
return result.status;
|
|
36
|
+
}
|
|
37
|
+
return await request(permission);
|
|
38
|
+
},
|
|
39
|
+
async openSettings() {
|
|
40
|
+
await openSettings();
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
var Permissions;
|
|
45
|
+
var init_rnp = __esm({
|
|
46
|
+
"src/engines/rnp.ts"() {
|
|
47
|
+
Permissions = {
|
|
48
|
+
CAMERA: p("ios.permission.CAMERA", "android.permission.CAMERA"),
|
|
49
|
+
MICROPHONE: p("ios.permission.MICROPHONE", "android.permission.RECORD_AUDIO"),
|
|
50
|
+
CONTACTS: p("ios.permission.CONTACTS", "android.permission.READ_CONTACTS"),
|
|
51
|
+
CALENDARS: p("ios.permission.CALENDARS", "android.permission.READ_CALENDAR"),
|
|
52
|
+
CALENDARS_WRITE_ONLY: p(
|
|
53
|
+
"ios.permission.CALENDARS_WRITE_ONLY",
|
|
54
|
+
"android.permission.WRITE_CALENDAR"
|
|
55
|
+
),
|
|
56
|
+
LOCATION_WHEN_IN_USE: p(
|
|
57
|
+
"ios.permission.LOCATION_WHEN_IN_USE",
|
|
58
|
+
"android.permission.ACCESS_FINE_LOCATION"
|
|
59
|
+
),
|
|
60
|
+
LOCATION_ALWAYS: p(
|
|
61
|
+
"ios.permission.LOCATION_ALWAYS",
|
|
62
|
+
"android.permission.ACCESS_BACKGROUND_LOCATION"
|
|
63
|
+
),
|
|
64
|
+
PHOTO_LIBRARY: p("ios.permission.PHOTO_LIBRARY", "android.permission.READ_MEDIA_IMAGES"),
|
|
65
|
+
PHOTO_LIBRARY_ADD_ONLY: p(
|
|
66
|
+
"ios.permission.PHOTO_LIBRARY_ADD_ONLY",
|
|
67
|
+
"android.permission.WRITE_EXTERNAL_STORAGE"
|
|
68
|
+
),
|
|
69
|
+
BLUETOOTH: p("ios.permission.BLUETOOTH", "android.permission.BLUETOOTH_CONNECT"),
|
|
70
|
+
NOTIFICATIONS: "notifications"
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
export {
|
|
76
|
+
Permissions,
|
|
77
|
+
createRNPEngine,
|
|
78
|
+
rnp_exports,
|
|
79
|
+
init_rnp
|
|
80
|
+
};
|
|
81
|
+
//# sourceMappingURL=chunk-EU3KPRTI.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/engines/rnp.ts"],"sourcesContent":["import { Platform } from \"react-native\";\nimport {\n type Permission,\n check,\n checkNotifications,\n openSettings,\n request,\n requestNotifications,\n} from \"react-native-permissions\";\nimport type { PermissionEngine, PermissionStatus } from \"../types\";\n\nfunction p(ios: string, android: string): string {\n return Platform.select({ ios, android, default: ios }) ?? ios;\n}\n\n/**\n * Cross-platform permission constants for use with the RNP engine.\n * Each resolves to the correct platform-specific string at runtime.\n */\nexport const Permissions = {\n CAMERA: p(\"ios.permission.CAMERA\", \"android.permission.CAMERA\"),\n MICROPHONE: p(\"ios.permission.MICROPHONE\", \"android.permission.RECORD_AUDIO\"),\n CONTACTS: p(\"ios.permission.CONTACTS\", \"android.permission.READ_CONTACTS\"),\n CALENDARS: p(\"ios.permission.CALENDARS\", \"android.permission.READ_CALENDAR\"),\n CALENDARS_WRITE_ONLY: p(\n \"ios.permission.CALENDARS_WRITE_ONLY\",\n \"android.permission.WRITE_CALENDAR\",\n ),\n LOCATION_WHEN_IN_USE: p(\n \"ios.permission.LOCATION_WHEN_IN_USE\",\n \"android.permission.ACCESS_FINE_LOCATION\",\n ),\n LOCATION_ALWAYS: p(\n \"ios.permission.LOCATION_ALWAYS\",\n \"android.permission.ACCESS_BACKGROUND_LOCATION\",\n ),\n PHOTO_LIBRARY: p(\"ios.permission.PHOTO_LIBRARY\", \"android.permission.READ_MEDIA_IMAGES\"),\n PHOTO_LIBRARY_ADD_ONLY: p(\n \"ios.permission.PHOTO_LIBRARY_ADD_ONLY\",\n \"android.permission.WRITE_EXTERNAL_STORAGE\",\n ),\n BLUETOOTH: p(\"ios.permission.BLUETOOTH\", \"android.permission.BLUETOOTH_CONNECT\"),\n NOTIFICATIONS: \"notifications\",\n} as const;\n\nexport function createRNPEngine(): PermissionEngine {\n return {\n async check(permission: string): Promise<PermissionStatus> {\n if (permission === \"notifications\") {\n const result = await checkNotifications();\n return result.status as PermissionStatus;\n }\n return (await check(permission as Permission)) as PermissionStatus;\n },\n\n async request(permission: string): Promise<PermissionStatus> {\n if (permission === \"notifications\") {\n const result = await requestNotifications([\"alert\", \"badge\", \"sound\"]);\n return result.status as PermissionStatus;\n }\n return (await request(permission as Permission)) as PermissionStatus;\n },\n\n async openSettings(): Promise<void> {\n await openSettings();\n },\n };\n}\n"],"mappings":";;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAAS,gBAAgB;AACzB;AAAA,EAEE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP,SAAS,EAAE,KAAa,SAAyB;AAC/C,SAAO,SAAS,OAAO,EAAE,KAAK,SAAS,SAAS,IAAI,CAAC,KAAK;AAC5D;AAgCO,SAAS,kBAAoC;AAClD,SAAO;AAAA,IACL,MAAM,MAAM,YAA+C;AACzD,UAAI,eAAe,iBAAiB;AAClC,cAAM,SAAS,MAAM,mBAAmB;AACxC,eAAO,OAAO;AAAA,MAChB;AACA,aAAQ,MAAM,MAAM,UAAwB;AAAA,IAC9C;AAAA,IAEA,MAAM,QAAQ,YAA+C;AAC3D,UAAI,eAAe,iBAAiB;AAClC,cAAM,SAAS,MAAM,qBAAqB,CAAC,SAAS,SAAS,OAAO,CAAC;AACrE,eAAO,OAAO;AAAA,MAChB;AACA,aAAQ,MAAM,QAAQ,UAAwB;AAAA,IAChD;AAAA,IAEA,MAAM,eAA8B;AAClC,YAAM,aAAa;AAAA,IACrB;AAAA,EACF;AACF;AAnEA,IAmBa;AAnBb;AAAA;AAmBO,IAAM,cAAc;AAAA,MACzB,QAAQ,EAAE,yBAAyB,2BAA2B;AAAA,MAC9D,YAAY,EAAE,6BAA6B,iCAAiC;AAAA,MAC5E,UAAU,EAAE,2BAA2B,kCAAkC;AAAA,MACzE,WAAW,EAAE,4BAA4B,kCAAkC;AAAA,MAC3E,sBAAsB;AAAA,QACpB;AAAA,QACA;AAAA,MACF;AAAA,MACA,sBAAsB;AAAA,QACpB;AAAA,QACA;AAAA,MACF;AAAA,MACA,iBAAiB;AAAA,QACf;AAAA,QACA;AAAA,MACF;AAAA,MACA,eAAe,EAAE,gCAAgC,sCAAsC;AAAA,MACvF,wBAAwB;AAAA,QACtB;AAAA,QACA;AAAA,MACF;AAAA,MACA,WAAW,EAAE,4BAA4B,sCAAsC;AAAA,MAC/E,eAAe;AAAA,IACjB;AAAA;AAAA;","names":[]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __esm = (fn, res) => function __init() {
|
|
6
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
7
|
+
};
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
__esm,
|
|
24
|
+
__export,
|
|
25
|
+
__toCommonJS
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=chunk-NFEGQTCC.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { P as PermissionEngine } from '../types-QXyq8VnD.mjs';
|
|
2
|
+
|
|
3
|
+
interface ExpoPermissionModule {
|
|
4
|
+
getPermissionsAsync: () => Promise<{
|
|
5
|
+
status: string;
|
|
6
|
+
canAskAgain: boolean;
|
|
7
|
+
}>;
|
|
8
|
+
requestPermissionsAsync: () => Promise<{
|
|
9
|
+
status: string;
|
|
10
|
+
canAskAgain: boolean;
|
|
11
|
+
}>;
|
|
12
|
+
}
|
|
13
|
+
interface ExpoEngineConfig {
|
|
14
|
+
permissions: Record<string, ExpoPermissionModule>;
|
|
15
|
+
}
|
|
16
|
+
declare function createExpoEngine(config: ExpoEngineConfig): PermissionEngine;
|
|
17
|
+
|
|
18
|
+
export { type ExpoEngineConfig, type ExpoPermissionModule, createExpoEngine };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { P as PermissionEngine } from '../types-QXyq8VnD.js';
|
|
2
|
+
|
|
3
|
+
interface ExpoPermissionModule {
|
|
4
|
+
getPermissionsAsync: () => Promise<{
|
|
5
|
+
status: string;
|
|
6
|
+
canAskAgain: boolean;
|
|
7
|
+
}>;
|
|
8
|
+
requestPermissionsAsync: () => Promise<{
|
|
9
|
+
status: string;
|
|
10
|
+
canAskAgain: boolean;
|
|
11
|
+
}>;
|
|
12
|
+
}
|
|
13
|
+
interface ExpoEngineConfig {
|
|
14
|
+
permissions: Record<string, ExpoPermissionModule>;
|
|
15
|
+
}
|
|
16
|
+
declare function createExpoEngine(config: ExpoEngineConfig): PermissionEngine;
|
|
17
|
+
|
|
18
|
+
export { type ExpoEngineConfig, type ExpoPermissionModule, createExpoEngine };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/engines/expo.ts
|
|
21
|
+
var expo_exports = {};
|
|
22
|
+
__export(expo_exports, {
|
|
23
|
+
createExpoEngine: () => createExpoEngine
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(expo_exports);
|
|
26
|
+
var import_react_native = require("react-native");
|
|
27
|
+
function mapExpoStatus(result) {
|
|
28
|
+
if (result.status === "granted") return "granted";
|
|
29
|
+
if (result.status === "undetermined") return "denied";
|
|
30
|
+
if (result.status === "denied") {
|
|
31
|
+
return result.canAskAgain ? "denied" : "blocked";
|
|
32
|
+
}
|
|
33
|
+
return "unavailable";
|
|
34
|
+
}
|
|
35
|
+
function createExpoEngine(config) {
|
|
36
|
+
return {
|
|
37
|
+
async check(permission) {
|
|
38
|
+
const mod = config.permissions[permission];
|
|
39
|
+
if (!mod) return "unavailable";
|
|
40
|
+
const result = await mod.getPermissionsAsync();
|
|
41
|
+
return mapExpoStatus(result);
|
|
42
|
+
},
|
|
43
|
+
async request(permission) {
|
|
44
|
+
const mod = config.permissions[permission];
|
|
45
|
+
if (!mod) return "unavailable";
|
|
46
|
+
const result = await mod.requestPermissionsAsync();
|
|
47
|
+
return mapExpoStatus(result);
|
|
48
|
+
},
|
|
49
|
+
async openSettings() {
|
|
50
|
+
await import_react_native.Linking.openSettings();
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
55
|
+
0 && (module.exports = {
|
|
56
|
+
createExpoEngine
|
|
57
|
+
});
|
|
58
|
+
//# sourceMappingURL=expo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/engines/expo.ts"],"sourcesContent":["import { Linking } from \"react-native\";\nimport type { PermissionEngine, PermissionStatus } from \"../types\";\n\nexport interface ExpoPermissionModule {\n getPermissionsAsync: () => Promise<{ status: string; canAskAgain: boolean }>;\n requestPermissionsAsync: () => Promise<{ status: string; canAskAgain: boolean }>;\n}\n\nexport interface ExpoEngineConfig {\n permissions: Record<string, ExpoPermissionModule>;\n}\n\nfunction mapExpoStatus(result: {\n status: string;\n canAskAgain: boolean;\n}): PermissionStatus {\n if (result.status === \"granted\") return \"granted\";\n if (result.status === \"undetermined\") return \"denied\";\n if (result.status === \"denied\") {\n return result.canAskAgain ? \"denied\" : \"blocked\";\n }\n return \"unavailable\";\n}\n\nexport function createExpoEngine(config: ExpoEngineConfig): PermissionEngine {\n return {\n async check(permission: string): Promise<PermissionStatus> {\n const mod = config.permissions[permission];\n if (!mod) return \"unavailable\";\n const result = await mod.getPermissionsAsync();\n return mapExpoStatus(result);\n },\n\n async request(permission: string): Promise<PermissionStatus> {\n const mod = config.permissions[permission];\n if (!mod) return \"unavailable\";\n const result = await mod.requestPermissionsAsync();\n return mapExpoStatus(result);\n },\n\n async openSettings(): Promise<void> {\n await Linking.openSettings();\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0BAAwB;AAYxB,SAAS,cAAc,QAGF;AACnB,MAAI,OAAO,WAAW,UAAW,QAAO;AACxC,MAAI,OAAO,WAAW,eAAgB,QAAO;AAC7C,MAAI,OAAO,WAAW,UAAU;AAC9B,WAAO,OAAO,cAAc,WAAW;AAAA,EACzC;AACA,SAAO;AACT;AAEO,SAAS,iBAAiB,QAA4C;AAC3E,SAAO;AAAA,IACL,MAAM,MAAM,YAA+C;AACzD,YAAM,MAAM,OAAO,YAAY,UAAU;AACzC,UAAI,CAAC,IAAK,QAAO;AACjB,YAAM,SAAS,MAAM,IAAI,oBAAoB;AAC7C,aAAO,cAAc,MAAM;AAAA,IAC7B;AAAA,IAEA,MAAM,QAAQ,YAA+C;AAC3D,YAAM,MAAM,OAAO,YAAY,UAAU;AACzC,UAAI,CAAC,IAAK,QAAO;AACjB,YAAM,SAAS,MAAM,IAAI,wBAAwB;AACjD,aAAO,cAAc,MAAM;AAAA,IAC7B;AAAA,IAEA,MAAM,eAA8B;AAClC,YAAM,4BAAQ,aAAa;AAAA,IAC7B;AAAA,EACF;AACF;","names":[]}
|