react-native-permission-handler 0.2.1 → 0.3.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 +107 -15
- package/dist/chunk-FJ23EV7L.mjs +127 -0
- package/dist/chunk-FJ23EV7L.mjs.map +1 -0
- package/dist/engines/expo.d.mts +24 -11
- package/dist/engines/expo.d.ts +24 -11
- package/dist/engines/expo.js +17 -8
- package/dist/engines/expo.js.map +1 -1
- package/dist/engines/expo.mjs +17 -8
- package/dist/engines/expo.mjs.map +1 -1
- package/dist/engines/rnp.d.mts +46 -3
- package/dist/engines/rnp.d.ts +46 -3
- package/dist/engines/rnp.js +47 -1
- package/dist/engines/rnp.js.map +1 -1
- package/dist/engines/rnp.mjs +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +212 -44
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +166 -44
- package/dist/index.mjs.map +1 -1
- package/dist/{types-QXyq8VnD.d.mts → types-DwqbbLGD.d.mts} +6 -0
- package/dist/{types-QXyq8VnD.d.ts → types-DwqbbLGD.d.ts} +6 -0
- package/package.json +1 -1
- package/src/core/debug-logger.test.ts +67 -0
- package/src/core/debug-logger.ts +29 -0
- package/src/core/with-timeout.test.ts +82 -0
- package/src/core/with-timeout.ts +34 -0
- package/src/engines/expo.test.ts +76 -0
- package/src/engines/expo.ts +44 -15
- package/src/engines/rnp.test.ts +41 -11
- package/src/engines/rnp.ts +50 -2
- package/src/hooks/use-multiple-permissions.ts +71 -29
- package/src/hooks/use-permission-handler.ts +60 -14
- package/src/types.ts +6 -0
- package/dist/chunk-EU3KPRTI.mjs +0 -81
- package/dist/chunk-EU3KPRTI.mjs.map +0 -1
package/src/engines/rnp.test.ts
CHANGED
|
@@ -100,23 +100,53 @@ describe("createRNPEngine", () => {
|
|
|
100
100
|
});
|
|
101
101
|
|
|
102
102
|
describe("Permissions constants", () => {
|
|
103
|
-
it("resolves to iOS strings (mocked platform)", () => {
|
|
103
|
+
it("resolves cross-platform permissions to iOS strings (mocked platform)", () => {
|
|
104
104
|
expect(Permissions.CAMERA).toBe("ios.permission.CAMERA");
|
|
105
105
|
expect(Permissions.MICROPHONE).toBe("ios.permission.MICROPHONE");
|
|
106
106
|
expect(Permissions.LOCATION_WHEN_IN_USE).toBe("ios.permission.LOCATION_WHEN_IN_USE");
|
|
107
|
+
expect(Permissions.MEDIA_LIBRARY).toBe("ios.permission.MEDIA_LIBRARY");
|
|
108
|
+
expect(Permissions.SPEECH_RECOGNITION).toBe("ios.permission.SPEECH_RECOGNITION");
|
|
109
|
+
expect(Permissions.MOTION).toBe("ios.permission.MOTION");
|
|
107
110
|
expect(Permissions.NOTIFICATIONS).toBe("notifications");
|
|
108
111
|
});
|
|
109
112
|
|
|
110
|
-
it("includes all
|
|
113
|
+
it("includes all cross-platform permissions", () => {
|
|
111
114
|
const keys = Object.keys(Permissions);
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
115
|
+
for (const key of [
|
|
116
|
+
"CAMERA",
|
|
117
|
+
"MICROPHONE",
|
|
118
|
+
"CONTACTS",
|
|
119
|
+
"CALENDARS",
|
|
120
|
+
"CALENDARS_WRITE_ONLY",
|
|
121
|
+
"LOCATION_WHEN_IN_USE",
|
|
122
|
+
"LOCATION_ALWAYS",
|
|
123
|
+
"PHOTO_LIBRARY",
|
|
124
|
+
"PHOTO_LIBRARY_ADD_ONLY",
|
|
125
|
+
"MEDIA_LIBRARY",
|
|
126
|
+
"BLUETOOTH",
|
|
127
|
+
"SPEECH_RECOGNITION",
|
|
128
|
+
"MOTION",
|
|
129
|
+
"NOTIFICATIONS",
|
|
130
|
+
]) {
|
|
131
|
+
expect(keys).toContain(key);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("includes iOS-only permissions", () => {
|
|
136
|
+
expect(Permissions.IOS.FACE_ID).toBe("ios.permission.FACE_ID");
|
|
137
|
+
expect(Permissions.IOS.APP_TRACKING_TRANSPARENCY).toBe(
|
|
138
|
+
"ios.permission.APP_TRACKING_TRANSPARENCY",
|
|
139
|
+
);
|
|
140
|
+
expect(Permissions.IOS.SIRI).toBe("ios.permission.SIRI");
|
|
141
|
+
expect(Permissions.IOS.REMINDERS).toBe("ios.permission.REMINDERS");
|
|
142
|
+
expect(Permissions.IOS.STOREKIT).toBe("ios.permission.STOREKIT");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("includes Android-only permissions", () => {
|
|
146
|
+
expect(Permissions.ANDROID.BODY_SENSORS).toBe("android.permission.BODY_SENSORS");
|
|
147
|
+
expect(Permissions.ANDROID.CALL_PHONE).toBe("android.permission.CALL_PHONE");
|
|
148
|
+
expect(Permissions.ANDROID.READ_SMS).toBe("android.permission.READ_SMS");
|
|
149
|
+
expect(Permissions.ANDROID.BLUETOOTH_SCAN).toBe("android.permission.BLUETOOTH_SCAN");
|
|
150
|
+
expect(Permissions.ANDROID.READ_MEDIA_VIDEO).toBe("android.permission.READ_MEDIA_VIDEO");
|
|
121
151
|
});
|
|
122
152
|
});
|
package/src/engines/rnp.ts
CHANGED
|
@@ -14,10 +14,11 @@ function p(ios: string, android: string): string {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* Cross-platform permission constants
|
|
18
|
-
*
|
|
17
|
+
* Cross-platform permission constants that resolve to the correct
|
|
18
|
+
* platform-specific string at runtime via Platform.select.
|
|
19
19
|
*/
|
|
20
20
|
export const Permissions = {
|
|
21
|
+
// Cross-platform
|
|
21
22
|
CAMERA: p("ios.permission.CAMERA", "android.permission.CAMERA"),
|
|
22
23
|
MICROPHONE: p("ios.permission.MICROPHONE", "android.permission.RECORD_AUDIO"),
|
|
23
24
|
CONTACTS: p("ios.permission.CONTACTS", "android.permission.READ_CONTACTS"),
|
|
@@ -39,8 +40,55 @@ export const Permissions = {
|
|
|
39
40
|
"ios.permission.PHOTO_LIBRARY_ADD_ONLY",
|
|
40
41
|
"android.permission.WRITE_EXTERNAL_STORAGE",
|
|
41
42
|
),
|
|
43
|
+
MEDIA_LIBRARY: p("ios.permission.MEDIA_LIBRARY", "android.permission.READ_MEDIA_AUDIO"),
|
|
42
44
|
BLUETOOTH: p("ios.permission.BLUETOOTH", "android.permission.BLUETOOTH_CONNECT"),
|
|
45
|
+
SPEECH_RECOGNITION: p("ios.permission.SPEECH_RECOGNITION", "android.permission.RECORD_AUDIO"),
|
|
46
|
+
MOTION: p("ios.permission.MOTION", "android.permission.ACTIVITY_RECOGNITION"),
|
|
43
47
|
NOTIFICATIONS: "notifications",
|
|
48
|
+
|
|
49
|
+
// iOS-only
|
|
50
|
+
IOS: {
|
|
51
|
+
APP_TRACKING_TRANSPARENCY: "ios.permission.APP_TRACKING_TRANSPARENCY",
|
|
52
|
+
FACE_ID: "ios.permission.FACE_ID",
|
|
53
|
+
REMINDERS: "ios.permission.REMINDERS",
|
|
54
|
+
SIRI: "ios.permission.SIRI",
|
|
55
|
+
STOREKIT: "ios.permission.STOREKIT",
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
// Android-only
|
|
59
|
+
ANDROID: {
|
|
60
|
+
ACCEPT_HANDOVER: "android.permission.ACCEPT_HANDOVER",
|
|
61
|
+
ACCESS_COARSE_LOCATION: "android.permission.ACCESS_COARSE_LOCATION",
|
|
62
|
+
ACCESS_MEDIA_LOCATION: "android.permission.ACCESS_MEDIA_LOCATION",
|
|
63
|
+
ADD_VOICEMAIL: "com.android.voicemail.permission.ADD_VOICEMAIL",
|
|
64
|
+
ANSWER_PHONE_CALLS: "android.permission.ANSWER_PHONE_CALLS",
|
|
65
|
+
BLUETOOTH_ADVERTISE: "android.permission.BLUETOOTH_ADVERTISE",
|
|
66
|
+
BLUETOOTH_SCAN: "android.permission.BLUETOOTH_SCAN",
|
|
67
|
+
BODY_SENSORS: "android.permission.BODY_SENSORS",
|
|
68
|
+
BODY_SENSORS_BACKGROUND: "android.permission.BODY_SENSORS_BACKGROUND",
|
|
69
|
+
CALL_PHONE: "android.permission.CALL_PHONE",
|
|
70
|
+
GET_ACCOUNTS: "android.permission.GET_ACCOUNTS",
|
|
71
|
+
NEARBY_WIFI_DEVICES: "android.permission.NEARBY_WIFI_DEVICES",
|
|
72
|
+
PROCESS_OUTGOING_CALLS: "android.permission.PROCESS_OUTGOING_CALLS",
|
|
73
|
+
READ_CALL_LOG: "android.permission.READ_CALL_LOG",
|
|
74
|
+
READ_EXTERNAL_STORAGE: "android.permission.READ_EXTERNAL_STORAGE",
|
|
75
|
+
READ_MEDIA_AUDIO: "android.permission.READ_MEDIA_AUDIO",
|
|
76
|
+
READ_MEDIA_IMAGES: "android.permission.READ_MEDIA_IMAGES",
|
|
77
|
+
READ_MEDIA_VIDEO: "android.permission.READ_MEDIA_VIDEO",
|
|
78
|
+
READ_MEDIA_VISUAL_USER_SELECTED: "android.permission.READ_MEDIA_VISUAL_USER_SELECTED",
|
|
79
|
+
READ_PHONE_NUMBERS: "android.permission.READ_PHONE_NUMBERS",
|
|
80
|
+
READ_PHONE_STATE: "android.permission.READ_PHONE_STATE",
|
|
81
|
+
READ_SMS: "android.permission.READ_SMS",
|
|
82
|
+
RECEIVE_MMS: "android.permission.RECEIVE_MMS",
|
|
83
|
+
RECEIVE_SMS: "android.permission.RECEIVE_SMS",
|
|
84
|
+
RECEIVE_WAP_PUSH: "android.permission.RECEIVE_WAP_PUSH",
|
|
85
|
+
SEND_SMS: "android.permission.SEND_SMS",
|
|
86
|
+
USE_SIP: "android.permission.USE_SIP",
|
|
87
|
+
UWB_RANGING: "android.permission.UWB_RANGING",
|
|
88
|
+
WRITE_CALL_LOG: "android.permission.WRITE_CALL_LOG",
|
|
89
|
+
WRITE_CONTACTS: "android.permission.WRITE_CONTACTS",
|
|
90
|
+
WRITE_EXTERNAL_STORAGE: "android.permission.WRITE_EXTERNAL_STORAGE",
|
|
91
|
+
},
|
|
44
92
|
} as const;
|
|
45
93
|
|
|
46
94
|
export function createRNPEngine(): PermissionEngine {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
import { createDebugLogger } from "../core/debug-logger";
|
|
3
|
+
import { PermissionTimeoutError, withTimeout } from "../core/with-timeout";
|
|
2
4
|
import { resolveEngine } from "../engines/use-engine";
|
|
3
5
|
import type {
|
|
4
6
|
MultiPermissionEntry,
|
|
@@ -37,7 +39,16 @@ export function useMultiplePermissions(
|
|
|
37
39
|
config: MultiplePermissionsConfig,
|
|
38
40
|
): MultiplePermissionsResult {
|
|
39
41
|
const engine = resolveEngine(config.engine);
|
|
40
|
-
const {
|
|
42
|
+
const {
|
|
43
|
+
permissions,
|
|
44
|
+
strategy,
|
|
45
|
+
autoCheck = true,
|
|
46
|
+
requestTimeout,
|
|
47
|
+
onTimeout,
|
|
48
|
+
debug,
|
|
49
|
+
onAllGranted,
|
|
50
|
+
} = config;
|
|
51
|
+
const logger = createDebugLogger(debug, "multi");
|
|
41
52
|
const [statuses, setStatuses] = useState<Record<string, PermissionFlowState>>(() => {
|
|
42
53
|
const initial: Record<string, PermissionFlowState> = {};
|
|
43
54
|
for (const entry of permissions) {
|
|
@@ -54,14 +65,17 @@ export function useMultiplePermissions(
|
|
|
54
65
|
isRunning.current = true;
|
|
55
66
|
|
|
56
67
|
const update = (key: string, state: PermissionFlowState) => {
|
|
57
|
-
setStatuses((prev) =>
|
|
68
|
+
setStatuses((prev) => {
|
|
69
|
+
logger.transition(prev[key] ?? "idle", state, key);
|
|
70
|
+
return { ...prev, [key]: state };
|
|
71
|
+
});
|
|
58
72
|
};
|
|
59
73
|
|
|
60
74
|
try {
|
|
61
75
|
if (strategy === "sequential") {
|
|
62
|
-
await runSequential(permissions, engine, update);
|
|
76
|
+
await runSequential(permissions, engine, update, requestTimeout, onTimeout);
|
|
63
77
|
} else {
|
|
64
|
-
await runParallel(permissions, engine, update);
|
|
78
|
+
await runParallel(permissions, engine, update, requestTimeout, onTimeout);
|
|
65
79
|
}
|
|
66
80
|
|
|
67
81
|
// Final check: are all granted?
|
|
@@ -79,7 +93,7 @@ export function useMultiplePermissions(
|
|
|
79
93
|
} finally {
|
|
80
94
|
isRunning.current = false;
|
|
81
95
|
}
|
|
82
|
-
}, [permissions, strategy, engine, onAllGranted]);
|
|
96
|
+
}, [permissions, strategy, engine, requestTimeout, onTimeout, logger, onAllGranted]);
|
|
83
97
|
|
|
84
98
|
// Auto-check on mount
|
|
85
99
|
// biome-ignore lint/correctness/useExhaustiveDependencies: intentional mount-only effect
|
|
@@ -112,6 +126,8 @@ async function runSequential(
|
|
|
112
126
|
permissions: MultiPermissionEntry[],
|
|
113
127
|
engine: PermissionEngine,
|
|
114
128
|
updateStatus: (key: string, state: PermissionFlowState) => void,
|
|
129
|
+
requestTimeout?: number,
|
|
130
|
+
onTimeout?: () => void,
|
|
115
131
|
): Promise<void> {
|
|
116
132
|
for (const entry of permissions) {
|
|
117
133
|
const key = permissionKey(entry);
|
|
@@ -138,19 +154,31 @@ async function runSequential(
|
|
|
138
154
|
|
|
139
155
|
// Denied — request it
|
|
140
156
|
updateStatus(key, "requesting");
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
157
|
+
try {
|
|
158
|
+
const requestPromise = engine.request(entry.permission);
|
|
159
|
+
const requestStatus = requestTimeout
|
|
160
|
+
? await withTimeout(requestPromise, requestTimeout, entry.permission)
|
|
161
|
+
: await requestPromise;
|
|
162
|
+
|
|
163
|
+
if (isGrantedStatus(requestStatus)) {
|
|
164
|
+
updateStatus(key, "granted");
|
|
165
|
+
entry.onGrant?.();
|
|
166
|
+
} else if (requestStatus === "blocked") {
|
|
167
|
+
updateStatus(key, "blockedPrompt");
|
|
168
|
+
entry.onBlock?.();
|
|
169
|
+
break;
|
|
170
|
+
} else {
|
|
171
|
+
updateStatus(key, "denied");
|
|
172
|
+
entry.onDeny?.();
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
} catch (err) {
|
|
176
|
+
if (err instanceof PermissionTimeoutError) {
|
|
177
|
+
onTimeout?.();
|
|
178
|
+
updateStatus(key, "blockedPrompt");
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
throw err;
|
|
154
182
|
}
|
|
155
183
|
}
|
|
156
184
|
}
|
|
@@ -159,6 +187,8 @@ async function runParallel(
|
|
|
159
187
|
permissions: MultiPermissionEntry[],
|
|
160
188
|
engine: PermissionEngine,
|
|
161
189
|
updateStatus: (key: string, state: PermissionFlowState) => void,
|
|
190
|
+
requestTimeout?: number,
|
|
191
|
+
onTimeout?: () => void,
|
|
162
192
|
): Promise<void> {
|
|
163
193
|
// Check all in parallel
|
|
164
194
|
const checkResults = await Promise.all(
|
|
@@ -193,17 +223,29 @@ async function runParallel(
|
|
|
193
223
|
}
|
|
194
224
|
|
|
195
225
|
updateStatus(key, "requesting");
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
226
|
+
try {
|
|
227
|
+
const requestPromise = engine.request(entry.permission);
|
|
228
|
+
const requestStatus = requestTimeout
|
|
229
|
+
? await withTimeout(requestPromise, requestTimeout, entry.permission)
|
|
230
|
+
: await requestPromise;
|
|
231
|
+
|
|
232
|
+
if (isGrantedStatus(requestStatus)) {
|
|
233
|
+
updateStatus(key, "granted");
|
|
234
|
+
entry.onGrant?.();
|
|
235
|
+
} else if (requestStatus === "blocked") {
|
|
236
|
+
updateStatus(key, "blockedPrompt");
|
|
237
|
+
entry.onBlock?.();
|
|
238
|
+
} else {
|
|
239
|
+
updateStatus(key, "denied");
|
|
240
|
+
entry.onDeny?.();
|
|
241
|
+
}
|
|
242
|
+
} catch (err) {
|
|
243
|
+
if (err instanceof PermissionTimeoutError) {
|
|
244
|
+
onTimeout?.();
|
|
245
|
+
updateStatus(key, "blockedPrompt");
|
|
246
|
+
} else {
|
|
247
|
+
throw err;
|
|
248
|
+
}
|
|
207
249
|
}
|
|
208
250
|
}
|
|
209
251
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
2
|
import { AppState } from "react-native";
|
|
3
|
+
import { createDebugLogger } from "../core/debug-logger";
|
|
3
4
|
import { transition } from "../core/state-machine";
|
|
5
|
+
import { PermissionTimeoutError, withTimeout } from "../core/with-timeout";
|
|
4
6
|
import { resolveEngine } from "../engines/use-engine";
|
|
5
7
|
import type {
|
|
6
8
|
PermissionFlowState,
|
|
@@ -17,52 +19,91 @@ export function usePermissionHandler(config: PermissionHandlerConfig): Permissio
|
|
|
17
19
|
const waitingForSettings = useRef(false);
|
|
18
20
|
const appStateRef = useRef(AppState.currentState);
|
|
19
21
|
|
|
20
|
-
const {
|
|
22
|
+
const {
|
|
23
|
+
permission,
|
|
24
|
+
autoCheck = true,
|
|
25
|
+
requestTimeout,
|
|
26
|
+
onTimeout,
|
|
27
|
+
debug,
|
|
28
|
+
onGrant,
|
|
29
|
+
onDeny,
|
|
30
|
+
onBlock,
|
|
31
|
+
onSettingsReturn,
|
|
32
|
+
} = config;
|
|
33
|
+
|
|
34
|
+
const logger = createDebugLogger(debug, permission);
|
|
21
35
|
|
|
22
36
|
const checkPermission = useCallback(async () => {
|
|
23
|
-
setFlowState((s) =>
|
|
37
|
+
setFlowState((s) => {
|
|
38
|
+
const next = transition(s, { type: "CHECK" });
|
|
39
|
+
logger.transition(s, next, "CHECK");
|
|
40
|
+
return next;
|
|
41
|
+
});
|
|
24
42
|
try {
|
|
25
43
|
const status = await engine.check(permission);
|
|
26
44
|
setNativeStatus(status);
|
|
27
45
|
setFlowState((s) => {
|
|
28
46
|
const next = transition(s, { type: "CHECK_RESULT", status });
|
|
47
|
+
logger.transition(s, next, `CHECK_RESULT:${status}`);
|
|
29
48
|
if (next === "granted" && s !== "granted") onGrant?.();
|
|
30
49
|
return next;
|
|
31
50
|
});
|
|
32
51
|
} catch {
|
|
33
52
|
setFlowState("idle");
|
|
34
53
|
}
|
|
35
|
-
}, [engine, permission, onGrant]);
|
|
54
|
+
}, [engine, permission, logger, onGrant]);
|
|
36
55
|
|
|
37
56
|
const requestPermission = useCallback(async () => {
|
|
38
57
|
if (isRequesting.current) return;
|
|
39
58
|
isRequesting.current = true;
|
|
40
59
|
|
|
41
|
-
setFlowState((s) =>
|
|
60
|
+
setFlowState((s) => {
|
|
61
|
+
const next = transition(s, { type: "PRE_PROMPT_CONFIRM" });
|
|
62
|
+
logger.transition(s, next, "PRE_PROMPT_CONFIRM");
|
|
63
|
+
return next;
|
|
64
|
+
});
|
|
42
65
|
try {
|
|
43
|
-
const
|
|
66
|
+
const requestPromise = engine.request(permission);
|
|
67
|
+
const status = requestTimeout
|
|
68
|
+
? await withTimeout(requestPromise, requestTimeout, permission)
|
|
69
|
+
: await requestPromise;
|
|
44
70
|
setNativeStatus(status);
|
|
45
71
|
setFlowState((s) => {
|
|
46
72
|
const next = transition(s, { type: "REQUEST_RESULT", status });
|
|
73
|
+
logger.transition(s, next, `REQUEST_RESULT:${status}`);
|
|
47
74
|
if (next === "granted") onGrant?.();
|
|
48
75
|
if (next === "denied") onDeny?.();
|
|
49
76
|
if (next === "blockedPrompt") onBlock?.();
|
|
50
77
|
return next;
|
|
51
78
|
});
|
|
52
|
-
} catch {
|
|
53
|
-
|
|
79
|
+
} catch (err) {
|
|
80
|
+
if (err instanceof PermissionTimeoutError) {
|
|
81
|
+
logger.info(`request timed out after ${requestTimeout}ms`);
|
|
82
|
+
onTimeout?.();
|
|
83
|
+
setFlowState("blockedPrompt");
|
|
84
|
+
} else {
|
|
85
|
+
setFlowState("denied");
|
|
86
|
+
}
|
|
54
87
|
} finally {
|
|
55
88
|
isRequesting.current = false;
|
|
56
89
|
}
|
|
57
|
-
}, [engine, permission, onGrant, onDeny, onBlock]);
|
|
90
|
+
}, [engine, permission, requestTimeout, onTimeout, logger, onGrant, onDeny, onBlock]);
|
|
58
91
|
|
|
59
92
|
const dismiss = useCallback(() => {
|
|
60
|
-
setFlowState((s) =>
|
|
93
|
+
setFlowState((s) => {
|
|
94
|
+
const next = transition(s, { type: "PRE_PROMPT_DISMISS" });
|
|
95
|
+
logger.transition(s, next, "PRE_PROMPT_DISMISS");
|
|
96
|
+
return next;
|
|
97
|
+
});
|
|
61
98
|
onDeny?.();
|
|
62
|
-
}, [onDeny]);
|
|
99
|
+
}, [logger, onDeny]);
|
|
63
100
|
|
|
64
101
|
const goToSettings = useCallback(async () => {
|
|
65
|
-
setFlowState((s) =>
|
|
102
|
+
setFlowState((s) => {
|
|
103
|
+
const next = transition(s, { type: "OPEN_SETTINGS" });
|
|
104
|
+
logger.transition(s, next, "OPEN_SETTINGS");
|
|
105
|
+
return next;
|
|
106
|
+
});
|
|
66
107
|
waitingForSettings.current = true;
|
|
67
108
|
try {
|
|
68
109
|
await engine.openSettings();
|
|
@@ -70,15 +111,20 @@ export function usePermissionHandler(config: PermissionHandlerConfig): Permissio
|
|
|
70
111
|
waitingForSettings.current = false;
|
|
71
112
|
setFlowState("blockedPrompt");
|
|
72
113
|
}
|
|
73
|
-
}, [engine]);
|
|
114
|
+
}, [engine, logger]);
|
|
74
115
|
|
|
75
116
|
const recheckAfterSettings = useCallback(async () => {
|
|
76
|
-
setFlowState((s) =>
|
|
117
|
+
setFlowState((s) => {
|
|
118
|
+
const next = transition(s, { type: "SETTINGS_RETURN" });
|
|
119
|
+
logger.transition(s, next, "SETTINGS_RETURN");
|
|
120
|
+
return next;
|
|
121
|
+
});
|
|
77
122
|
try {
|
|
78
123
|
const status = await engine.check(permission);
|
|
79
124
|
setNativeStatus(status);
|
|
80
125
|
setFlowState((s) => {
|
|
81
126
|
const next = transition(s, { type: "RECHECK_RESULT", status });
|
|
127
|
+
logger.transition(s, next, `RECHECK_RESULT:${status}`);
|
|
82
128
|
if (next === "granted") onGrant?.();
|
|
83
129
|
onSettingsReturn?.(next === "granted");
|
|
84
130
|
return next;
|
|
@@ -86,7 +132,7 @@ export function usePermissionHandler(config: PermissionHandlerConfig): Permissio
|
|
|
86
132
|
} catch {
|
|
87
133
|
setFlowState("blockedPrompt");
|
|
88
134
|
}
|
|
89
|
-
}, [engine, permission, onGrant, onSettingsReturn]);
|
|
135
|
+
}, [engine, permission, logger, onGrant, onSettingsReturn]);
|
|
90
136
|
|
|
91
137
|
// Auto-check on mount
|
|
92
138
|
// biome-ignore lint/correctness/useExhaustiveDependencies: intentional mount-only effect
|
package/src/types.ts
CHANGED
|
@@ -82,6 +82,9 @@ export interface PermissionHandlerConfig extends PermissionCallbacks {
|
|
|
82
82
|
blockedPrompt: BlockedPromptConfig;
|
|
83
83
|
autoCheck?: boolean;
|
|
84
84
|
recheckOnForeground?: boolean;
|
|
85
|
+
requestTimeout?: number;
|
|
86
|
+
onTimeout?: () => void;
|
|
87
|
+
debug?: boolean | ((msg: string) => void);
|
|
85
88
|
}
|
|
86
89
|
|
|
87
90
|
/**
|
|
@@ -118,6 +121,9 @@ export interface MultiplePermissionsConfig {
|
|
|
118
121
|
strategy: "sequential" | "parallel";
|
|
119
122
|
engine?: PermissionEngine;
|
|
120
123
|
autoCheck?: boolean;
|
|
124
|
+
requestTimeout?: number;
|
|
125
|
+
onTimeout?: () => void;
|
|
126
|
+
debug?: boolean | ((msg: string) => void);
|
|
121
127
|
onAllGranted?: () => void;
|
|
122
128
|
}
|
|
123
129
|
|
package/dist/chunk-EU3KPRTI.mjs
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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":[]}
|