react-native-permission-handler 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,158 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+ import { AppState } from "react-native";
3
+ import {
4
+ type PermissionStatus,
5
+ check,
6
+ checkNotifications,
7
+ openSettings,
8
+ request,
9
+ requestNotifications,
10
+ } from "react-native-permissions";
11
+ import { transition } from "../core/state-machine";
12
+ import type {
13
+ PermissionFlowState,
14
+ PermissionHandlerConfig,
15
+ PermissionHandlerResult,
16
+ } from "../types";
17
+
18
+ function isNotifications(
19
+ permission: PermissionHandlerConfig["permission"],
20
+ ): permission is "notifications" {
21
+ return permission === "notifications";
22
+ }
23
+
24
+ export function usePermissionHandler(config: PermissionHandlerConfig): PermissionHandlerResult {
25
+ const [flowState, setFlowState] = useState<PermissionFlowState>("idle");
26
+ const [nativeStatus, setNativeStatus] = useState<PermissionStatus | null>(null);
27
+ const isRequesting = useRef(false);
28
+ const waitingForSettings = useRef(false);
29
+ const appStateRef = useRef(AppState.currentState);
30
+
31
+ const { permission, autoCheck = true, onGrant, onDeny, onBlock, onSettingsReturn } = config;
32
+
33
+ const checkPermission = useCallback(async () => {
34
+ setFlowState((s) => transition(s, { type: "CHECK" }));
35
+ try {
36
+ let status: PermissionStatus;
37
+ if (isNotifications(permission)) {
38
+ const result = await checkNotifications();
39
+ status = result.status;
40
+ } else {
41
+ status = await check(permission);
42
+ }
43
+ setNativeStatus(status);
44
+ setFlowState((s) => {
45
+ const next = transition(s, { type: "CHECK_RESULT", status });
46
+ if (next === "granted" && s !== "granted") onGrant?.();
47
+ return next;
48
+ });
49
+ } catch {
50
+ setFlowState("idle");
51
+ }
52
+ }, [permission, onGrant]);
53
+
54
+ const requestPermission = useCallback(async () => {
55
+ if (isRequesting.current) return;
56
+ isRequesting.current = true;
57
+
58
+ setFlowState((s) => transition(s, { type: "PRE_PROMPT_CONFIRM" }));
59
+ try {
60
+ let status: PermissionStatus;
61
+ if (isNotifications(permission)) {
62
+ const result = await requestNotifications(["alert", "badge", "sound"]);
63
+ status = result.status;
64
+ } else {
65
+ status = await request(permission);
66
+ }
67
+ setNativeStatus(status);
68
+ setFlowState((s) => {
69
+ const next = transition(s, { type: "REQUEST_RESULT", status });
70
+ if (next === "granted") onGrant?.();
71
+ if (next === "denied") onDeny?.();
72
+ if (next === "blockedPrompt") onBlock?.();
73
+ return next;
74
+ });
75
+ } catch {
76
+ setFlowState("denied");
77
+ } finally {
78
+ isRequesting.current = false;
79
+ }
80
+ }, [permission, onGrant, onDeny, onBlock]);
81
+
82
+ const dismiss = useCallback(() => {
83
+ setFlowState((s) => transition(s, { type: "PRE_PROMPT_DISMISS" }));
84
+ onDeny?.();
85
+ }, [onDeny]);
86
+
87
+ const goToSettings = useCallback(async () => {
88
+ setFlowState((s) => transition(s, { type: "OPEN_SETTINGS" }));
89
+ waitingForSettings.current = true;
90
+ try {
91
+ await openSettings();
92
+ } catch {
93
+ waitingForSettings.current = false;
94
+ setFlowState("blockedPrompt");
95
+ }
96
+ }, []);
97
+
98
+ const recheckAfterSettings = useCallback(async () => {
99
+ setFlowState((s) => transition(s, { type: "SETTINGS_RETURN" }));
100
+ try {
101
+ let status: PermissionStatus;
102
+ if (isNotifications(permission)) {
103
+ const result = await checkNotifications();
104
+ status = result.status;
105
+ } else {
106
+ status = await check(permission);
107
+ }
108
+ setNativeStatus(status);
109
+ setFlowState((s) => {
110
+ const next = transition(s, { type: "RECHECK_RESULT", status });
111
+ if (next === "granted") onGrant?.();
112
+ onSettingsReturn?.(next === "granted");
113
+ return next;
114
+ });
115
+ } catch {
116
+ setFlowState("blockedPrompt");
117
+ }
118
+ }, [permission, onGrant, onSettingsReturn]);
119
+
120
+ // Auto-check on mount
121
+ // biome-ignore lint/correctness/useExhaustiveDependencies: intentional mount-only effect
122
+ useEffect(() => {
123
+ if (autoCheck) {
124
+ checkPermission();
125
+ }
126
+ }, []);
127
+
128
+ // AppState listener for settings return
129
+ useEffect(() => {
130
+ const subscription = AppState.addEventListener("change", (nextAppState) => {
131
+ if (
132
+ appStateRef.current.match(/inactive|background/) &&
133
+ nextAppState === "active" &&
134
+ waitingForSettings.current
135
+ ) {
136
+ waitingForSettings.current = false;
137
+ recheckAfterSettings();
138
+ }
139
+ appStateRef.current = nextAppState;
140
+ });
141
+ return () => subscription.remove();
142
+ }, [recheckAfterSettings]);
143
+
144
+ return {
145
+ state: flowState,
146
+ nativeStatus,
147
+ isGranted: flowState === "granted",
148
+ isDenied: flowState === "denied",
149
+ isBlocked:
150
+ flowState === "blocked" || flowState === "blockedPrompt" || flowState === "openingSettings",
151
+ isChecking: flowState === "checking" || flowState === "recheckingAfterSettings",
152
+ isUnavailable: flowState === "unavailable",
153
+ request: requestPermission,
154
+ check: checkPermission,
155
+ dismiss,
156
+ openSettings: goToSettings,
157
+ };
158
+ }
package/src/index.ts ADDED
@@ -0,0 +1,22 @@
1
+ export type {
2
+ BlockedPromptConfig,
3
+ MultiPermissionEntry,
4
+ MultiplePermissionsConfig,
5
+ MultiplePermissionsResult,
6
+ PermissionCallbacks,
7
+ PermissionFlowEvent,
8
+ PermissionFlowState,
9
+ PermissionHandlerConfig,
10
+ PermissionHandlerResult,
11
+ PrePromptConfig,
12
+ } from "./types";
13
+
14
+ export { transition } from "./core/state-machine";
15
+ export { usePermissionHandler } from "./hooks/use-permission-handler";
16
+ export { useMultiplePermissions } from "./hooks/use-multiple-permissions";
17
+ export { PermissionGate } from "./components/permission-gate";
18
+ export type { PermissionGateProps } from "./components/permission-gate";
19
+ export { DefaultPrePrompt } from "./components/default-pre-prompt";
20
+ export type { DefaultPrePromptProps } from "./components/default-pre-prompt";
21
+ export { DefaultBlockedPrompt } from "./components/default-blocked-prompt";
22
+ export type { DefaultBlockedPromptProps } from "./components/default-blocked-prompt";
package/src/types.ts ADDED
@@ -0,0 +1,114 @@
1
+ import type { Permission, PermissionStatus } from "react-native-permissions";
2
+
3
+ /**
4
+ * States of the permission flow state machine.
5
+ */
6
+ export type PermissionFlowState =
7
+ | "idle"
8
+ | "checking"
9
+ | "prePrompt"
10
+ | "requesting"
11
+ | "granted"
12
+ | "denied"
13
+ | "blocked"
14
+ | "blockedPrompt"
15
+ | "openingSettings"
16
+ | "recheckingAfterSettings"
17
+ | "unavailable";
18
+
19
+ /**
20
+ * Events that drive state transitions.
21
+ */
22
+ export type PermissionFlowEvent =
23
+ | { type: "CHECK" }
24
+ | { type: "CHECK_RESULT"; status: PermissionStatus }
25
+ | { type: "PRE_PROMPT_CONFIRM" }
26
+ | { type: "PRE_PROMPT_DISMISS" }
27
+ | { type: "REQUEST_RESULT"; status: PermissionStatus }
28
+ | { type: "OPEN_SETTINGS" }
29
+ | { type: "SETTINGS_RETURN" }
30
+ | { type: "RECHECK_RESULT"; status: PermissionStatus };
31
+
32
+ /**
33
+ * Configuration for the pre-prompt modal.
34
+ */
35
+ export interface PrePromptConfig {
36
+ title: string;
37
+ message: string;
38
+ confirmLabel?: string;
39
+ cancelLabel?: string;
40
+ }
41
+
42
+ /**
43
+ * Configuration for the blocked-prompt modal.
44
+ */
45
+ export interface BlockedPromptConfig {
46
+ title: string;
47
+ message: string;
48
+ settingsLabel?: string;
49
+ }
50
+
51
+ /**
52
+ * Callbacks for analytics and side effects.
53
+ */
54
+ export interface PermissionCallbacks {
55
+ onGrant?: () => void;
56
+ onDeny?: () => void;
57
+ onBlock?: () => void;
58
+ onSettingsReturn?: (granted: boolean) => void;
59
+ }
60
+
61
+ /**
62
+ * Configuration for usePermissionHandler.
63
+ */
64
+ export interface PermissionHandlerConfig extends PermissionCallbacks {
65
+ permission: Permission | "notifications";
66
+ prePrompt: PrePromptConfig;
67
+ blockedPrompt: BlockedPromptConfig;
68
+ autoCheck?: boolean;
69
+ recheckOnForeground?: boolean;
70
+ }
71
+
72
+ /**
73
+ * Return type of usePermissionHandler.
74
+ */
75
+ export interface PermissionHandlerResult {
76
+ state: PermissionFlowState;
77
+ nativeStatus: PermissionStatus | null;
78
+ isGranted: boolean;
79
+ isDenied: boolean;
80
+ isBlocked: boolean;
81
+ isChecking: boolean;
82
+ isUnavailable: boolean;
83
+ request: () => void;
84
+ check: () => void;
85
+ dismiss: () => void;
86
+ openSettings: () => void;
87
+ }
88
+
89
+ /**
90
+ * Configuration for a single permission within useMultiplePermissions.
91
+ */
92
+ export interface MultiPermissionEntry extends PermissionCallbacks {
93
+ permission: Permission | "notifications";
94
+ prePrompt: PrePromptConfig;
95
+ blockedPrompt: BlockedPromptConfig;
96
+ }
97
+
98
+ /**
99
+ * Configuration for useMultiplePermissions.
100
+ */
101
+ export interface MultiplePermissionsConfig {
102
+ permissions: MultiPermissionEntry[];
103
+ strategy: "sequential" | "parallel";
104
+ onAllGranted?: () => void;
105
+ }
106
+
107
+ /**
108
+ * Return type of useMultiplePermissions.
109
+ */
110
+ export interface MultiplePermissionsResult {
111
+ statuses: Record<string, PermissionFlowState>;
112
+ allGranted: boolean;
113
+ request: () => void;
114
+ }