@buoy-gg/permissions 7.0.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +58 -0
- package/README.md +29 -0
- package/lib/commonjs/components/PermissionsBody.js +1 -0
- package/lib/commonjs/components/PermissionsChip.js +1 -0
- package/lib/commonjs/components/PermissionsToolView.js +1 -0
- package/lib/commonjs/engine/adapters/android.js +1 -0
- package/lib/commonjs/engine/adapters/expo.js +3 -0
- package/lib/commonjs/engine/adapters/rnPermissions.js +1 -0
- package/lib/commonjs/engine/adapters/wrap.js +1 -0
- package/lib/commonjs/engine/appSettings.js +1 -0
- package/lib/commonjs/engine/install.js +1 -0
- package/lib/commonjs/engine/install.web.js +1 -0
- package/lib/commonjs/engine/model.js +1 -0
- package/lib/commonjs/engine/persistence.js +1 -0
- package/lib/commonjs/engine/prompt.js +3 -0
- package/lib/commonjs/engine/readSavedSync.js +1 -0
- package/lib/commonjs/engine/readSavedSync.web.js +1 -0
- package/lib/commonjs/engine/store.js +1 -0
- package/lib/commonjs/index.js +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/preset.js +1 -0
- package/lib/commonjs/sync/permissionsSyncAdapter.js +1 -0
- package/lib/module/components/PermissionsBody.js +1 -0
- package/lib/module/components/PermissionsChip.js +1 -0
- package/lib/module/components/PermissionsToolView.js +1 -0
- package/lib/module/engine/adapters/android.js +1 -0
- package/lib/module/engine/adapters/expo.js +3 -0
- package/lib/module/engine/adapters/rnPermissions.js +1 -0
- package/lib/module/engine/adapters/wrap.js +1 -0
- package/lib/module/engine/appSettings.js +1 -0
- package/lib/module/engine/install.js +1 -0
- package/lib/module/engine/install.web.js +1 -0
- package/lib/module/engine/model.js +1 -0
- package/lib/module/engine/persistence.js +1 -0
- package/lib/module/engine/prompt.js +3 -0
- package/lib/module/engine/readSavedSync.js +1 -0
- package/lib/module/engine/readSavedSync.web.js +1 -0
- package/lib/module/engine/store.js +1 -0
- package/lib/module/index.js +1 -0
- package/lib/module/preset.js +1 -0
- package/lib/module/sync/permissionsSyncAdapter.js +1 -0
- package/lib/typescript/components/PermissionsBody.d.ts +2 -0
- package/lib/typescript/components/PermissionsChip.d.ts +7 -0
- package/lib/typescript/components/PermissionsToolView.d.ts +10 -0
- package/lib/typescript/engine/adapters/android.d.ts +13 -0
- package/lib/typescript/engine/adapters/expo.d.ts +60 -0
- package/lib/typescript/engine/adapters/rnPermissions.d.ts +16 -0
- package/lib/typescript/engine/adapters/wrap.d.ts +25 -0
- package/lib/typescript/engine/appSettings.d.ts +32 -0
- package/lib/typescript/engine/install.d.ts +3 -0
- package/lib/typescript/engine/install.web.d.ts +7 -0
- package/lib/typescript/engine/model.d.ts +115 -0
- package/lib/typescript/engine/persistence.d.ts +36 -0
- package/lib/typescript/engine/prompt.d.ts +26 -0
- package/lib/typescript/engine/readSavedSync.d.ts +9 -0
- package/lib/typescript/engine/readSavedSync.web.d.ts +4 -0
- package/lib/typescript/engine/store.d.ts +140 -0
- package/lib/typescript/index.d.ts +13 -0
- package/lib/typescript/preset.d.ts +45 -0
- package/lib/typescript/sync/permissionsSyncAdapter.d.ts +19 -0
- package/lib/web/index.mjs +1 -0
- package/package.json +110 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PermissionsSnapshot } from "../engine/store";
|
|
2
|
+
export type PermissionsActionHandler = (action: string, params?: Record<string, unknown>) => Promise<unknown>;
|
|
3
|
+
export interface PermissionsToolViewProps {
|
|
4
|
+
snapshot: PermissionsSnapshot | null;
|
|
5
|
+
onAction: PermissionsActionHandler;
|
|
6
|
+
/** Reload the app's JS so every screen asks again. Hidden when absent. */
|
|
7
|
+
onReload?: () => void;
|
|
8
|
+
}
|
|
9
|
+
export declare function PermissionsToolView({ snapshot, onAction, onReload }: PermissionsToolViewProps): import("react").JSX.Element;
|
|
10
|
+
//# sourceMappingURL=PermissionsToolView.d.ts.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React Native's PermissionsAndroid.
|
|
3
|
+
*
|
|
4
|
+
* It is one class instance shared by every import, and `check`, `request` and
|
|
5
|
+
* `requestMultiple` are prototype methods, so assigning them on the instance
|
|
6
|
+
* replaces them for every caller. On iOS the class only warns, so this runs
|
|
7
|
+
* on Android only.
|
|
8
|
+
*/
|
|
9
|
+
import { type PermissionId } from "../model";
|
|
10
|
+
/** The permission id for an Android permission string. */
|
|
11
|
+
export declare function androidPermissionId(permission: string): PermissionId;
|
|
12
|
+
export declare function installAndroidAdapter(permissionsAndroid: object | null | undefined): boolean;
|
|
13
|
+
//# sourceMappingURL=android.d.ts.map
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expo modules.
|
|
3
|
+
*
|
|
4
|
+
* Every Expo permission API ends in a native module method such as
|
|
5
|
+
* `ExpoLocation.getForegroundPermissionsAsync`. The JS wrappers
|
|
6
|
+
* (`Location.getForegroundPermissionsAsync`) and the `use…Permissions` hooks
|
|
7
|
+
* read that method at call time, so replacing it on the module object covers
|
|
8
|
+
* all of them. Nothing here imports an Expo package: modules are found by
|
|
9
|
+
* name on `globalThis.expo.modules`, and only the ones in the app's binary
|
|
10
|
+
* are touched.
|
|
11
|
+
*
|
|
12
|
+
* expo-maps and expo-calendar/next copy the method into their exports when
|
|
13
|
+
* they load, which is why the app should import `@buoy-gg/permissions`
|
|
14
|
+
* before its own code.
|
|
15
|
+
*/
|
|
16
|
+
import { type ExpoFlavor, type PermissionId } from "../model";
|
|
17
|
+
interface PermissionMethod {
|
|
18
|
+
get: string;
|
|
19
|
+
request: string;
|
|
20
|
+
permission: PermissionId;
|
|
21
|
+
flavor: ExpoFlavor;
|
|
22
|
+
/** Arguments the JS wrapper passes when the app gives none (for the real prompt). */
|
|
23
|
+
defaultArgs?: (platform: "ios" | "android") => unknown[];
|
|
24
|
+
}
|
|
25
|
+
interface GatedMethod {
|
|
26
|
+
method: string;
|
|
27
|
+
permission: PermissionId;
|
|
28
|
+
/** Error the native module rejects with, per platform. Absent = not enforced there. */
|
|
29
|
+
ios?: {
|
|
30
|
+
code: string;
|
|
31
|
+
reason: string;
|
|
32
|
+
};
|
|
33
|
+
android?: {
|
|
34
|
+
code: string;
|
|
35
|
+
reason: string;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
interface ExpoModuleSpec {
|
|
39
|
+
module: string;
|
|
40
|
+
library: string;
|
|
41
|
+
permissions: PermissionMethod[];
|
|
42
|
+
gated?: GatedMethod[];
|
|
43
|
+
}
|
|
44
|
+
/** Module names and methods, read from the Expo SDK 54/55 native sources. */
|
|
45
|
+
export declare const EXPO_MODULES: ExpoModuleSpec[];
|
|
46
|
+
/**
|
|
47
|
+
* Approximate location, the way iOS reduced accuracy reports it: the
|
|
48
|
+
* coordinate snapped to a grid about 2 km wide and an accuracy of 3 km.
|
|
49
|
+
*/
|
|
50
|
+
export declare function coarsenPosition<T>(position: T): T;
|
|
51
|
+
/** Wrap every Expo permission API in the app's binary. Returns the libraries found. */
|
|
52
|
+
export declare function installExpoAdapter(): string[];
|
|
53
|
+
/**
|
|
54
|
+
* Ask each module for its real state (without prompting) so the tool shows
|
|
55
|
+
* what the OS says before the app has asked. Calls the originals, so it
|
|
56
|
+
* doesn't add entries to the call log.
|
|
57
|
+
*/
|
|
58
|
+
export declare function refreshExpoRealStates(): Promise<void>;
|
|
59
|
+
export {};
|
|
60
|
+
//# sourceMappingURL=expo.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* react-native-permissions (5.x).
|
|
3
|
+
*
|
|
4
|
+
* Its JS calls the `RNPermissions` TurboModule at call time
|
|
5
|
+
* (`NativeModule.check(permission)`), and bridgeless TurboModules are cached
|
|
6
|
+
* objects with writable methods, so the native module object is the seam.
|
|
7
|
+
* The library's JS itself is never imported.
|
|
8
|
+
*
|
|
9
|
+
* Vocabulary: `unavailable | denied | blocked | granted | limited`. On iOS
|
|
10
|
+
* "never asked" reads as `denied` and a refusal as `blocked`.
|
|
11
|
+
*/
|
|
12
|
+
import { type PermissionId } from "../model";
|
|
13
|
+
/** The permission id for a react-native-permissions string, or null to pass it through. */
|
|
14
|
+
export declare function rnpPermissionId(permission: string): PermissionId | null;
|
|
15
|
+
export declare function installRnPermissionsAdapter(nativeModule: object | null | undefined): boolean;
|
|
16
|
+
//# sourceMappingURL=rnPermissions.d.ts.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replacing a method on a native module object, once.
|
|
3
|
+
*
|
|
4
|
+
* Expo modules (`globalThis.expo.modules.X`) and bridgeless TurboModules are
|
|
5
|
+
* cached per name, and their methods are ordinary writable properties, so a
|
|
6
|
+
* replacement is seen by every caller that reads the method at call time.
|
|
7
|
+
* The original is kept so a second install (Fast Refresh, a second import)
|
|
8
|
+
* wraps it again instead of wrapping the wrapper.
|
|
9
|
+
*/
|
|
10
|
+
declare const ORIGINAL = "__buoyPermissionsOriginal";
|
|
11
|
+
type AnyFn = ((...args: unknown[]) => unknown) & {
|
|
12
|
+
[ORIGINAL]?: AnyFn;
|
|
13
|
+
};
|
|
14
|
+
export declare function originalOf(target: object, method: string): AnyFn | null;
|
|
15
|
+
/**
|
|
16
|
+
* Replace `target[method]`. `make` gets the original, bound to the target.
|
|
17
|
+
* Returns false when the method is missing or the object refuses the write.
|
|
18
|
+
*/
|
|
19
|
+
export declare function wrapMethod(target: object, method: string, make: (original: (...args: unknown[]) => unknown) => (...args: unknown[]) => unknown): boolean;
|
|
20
|
+
/** An error shaped like the ones Expo modules reject with. */
|
|
21
|
+
export declare function codedError(code: string, message: string): Error & {
|
|
22
|
+
code: string;
|
|
23
|
+
};
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=wrap.d.ts.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type PermissionId } from "./model";
|
|
2
|
+
import type { AlertLike } from "./prompt";
|
|
3
|
+
type Emitter = {
|
|
4
|
+
emit(event: string, payload?: unknown): void;
|
|
5
|
+
};
|
|
6
|
+
export declare function configureAppSettings(options: {
|
|
7
|
+
emitter: Emitter | null;
|
|
8
|
+
alert: AlertLike | null;
|
|
9
|
+
}): void;
|
|
10
|
+
/** Leave for Settings, the way the platform reports it. */
|
|
11
|
+
export declare function simulateLeaveApp(): void;
|
|
12
|
+
/** Come back from Settings. iOS reports background (will-enter-foreground) then active. */
|
|
13
|
+
export declare function simulateReturnToApp(): void;
|
|
14
|
+
/**
|
|
15
|
+
* After a change from the tool: background → active, so screens that
|
|
16
|
+
* re-check permissions when the app comes back see the new state. Batched,
|
|
17
|
+
* so flipping several rows sends one round trip.
|
|
18
|
+
*/
|
|
19
|
+
export declare function notifyAppOfChange(): void;
|
|
20
|
+
/** The permission the app most likely sent the user to Settings for. */
|
|
21
|
+
export declare function settingsTarget(): PermissionId | null;
|
|
22
|
+
/**
|
|
23
|
+
* Show Buoy's stand-in for the Settings app. Resolves once the app is
|
|
24
|
+
* "back" (after the user picks, or leaves it as is).
|
|
25
|
+
*/
|
|
26
|
+
export declare function openSettingsStandIn(id: PermissionId, via: string): Promise<void>;
|
|
27
|
+
/** Intercept the ways an app opens its own Settings page. */
|
|
28
|
+
export declare function installSettingsIntercept(linking: object | null | undefined): void;
|
|
29
|
+
/** react-native-permissions' `openSettings(type)`. */
|
|
30
|
+
export declare function installRnpSettingsIntercept(nativeModule: object | null | undefined): void;
|
|
31
|
+
export {};
|
|
32
|
+
//# sourceMappingURL=appSettings.d.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web and Buoy Desktop render the Permissions panel but never patch
|
|
3
|
+
* permission APIs: the browser has its own permission model, and Desktop
|
|
4
|
+
* shows a phone's state over the socket.
|
|
5
|
+
*/
|
|
6
|
+
export declare function installPermissions(): boolean;
|
|
7
|
+
//# sourceMappingURL=install.web.d.ts.map
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The permission model: one canonical state per permission, which states each
|
|
3
|
+
* platform can produce, what a permission request does from each state, and
|
|
4
|
+
* how a state reads in each library's vocabulary.
|
|
5
|
+
*
|
|
6
|
+
* Pure. No React Native imports, so every rule here is unit tested directly.
|
|
7
|
+
*
|
|
8
|
+
* Canonical states:
|
|
9
|
+
* - `undetermined`: never asked. A request shows the system prompt.
|
|
10
|
+
* - `granted`: full access.
|
|
11
|
+
* - `limited`: partial access (selected photos or contacts, approximate
|
|
12
|
+
* location, provisional notifications).
|
|
13
|
+
* - `denied`: refused. On Android the app may ask once more. On iOS a refusal
|
|
14
|
+
* is final, so iOS `denied` reads the same as `blocked`.
|
|
15
|
+
* - `blocked`: refused for good (Android "don't ask again"). Only Settings
|
|
16
|
+
* can change it.
|
|
17
|
+
* - `restricted`: iOS parental controls or device management. The user can't
|
|
18
|
+
* change it either.
|
|
19
|
+
*/
|
|
20
|
+
export type PermissionState = "undetermined" | "granted" | "limited" | "denied" | "blocked" | "restricted";
|
|
21
|
+
export declare const PERMISSION_STATES: readonly PermissionState[];
|
|
22
|
+
export type Platform = "ios" | "android";
|
|
23
|
+
/** How the tool answers a request made from `undetermined` (or Android `denied`). */
|
|
24
|
+
export type RequestAnswer = "ask" | "allow" | "limited" | "deny";
|
|
25
|
+
export declare const REQUEST_ANSWERS: readonly RequestAnswer[];
|
|
26
|
+
/**
|
|
27
|
+
* An answer to a prompt. `once` is "Allow Once" / "Only this time": allowed
|
|
28
|
+
* now, back to not asked after the app restarts.
|
|
29
|
+
*/
|
|
30
|
+
export type PromptAnswer = Exclude<RequestAnswer, "ask"> | "once";
|
|
31
|
+
export type PermissionId = "location" | "locationBackground" | "camera" | "microphone" | "notifications" | "photos" | "contacts" | "calendar" | "reminders" | "tracking" | "motion" | "bluetooth" | `android:${string}`;
|
|
32
|
+
export interface PermissionInfo {
|
|
33
|
+
id: PermissionId;
|
|
34
|
+
label: string;
|
|
35
|
+
/** What `limited` means for this permission, per platform. Absent = no limited state. */
|
|
36
|
+
limited?: Partial<Record<Platform, string>>;
|
|
37
|
+
/** Only exists on this platform. */
|
|
38
|
+
only?: Platform;
|
|
39
|
+
/** Words for the prompt: "use your location", "access your camera". */
|
|
40
|
+
purpose: string;
|
|
41
|
+
}
|
|
42
|
+
export declare const KNOWN_PERMISSION_IDS: PermissionId[];
|
|
43
|
+
export declare function isPermissionId(value: unknown): value is PermissionId;
|
|
44
|
+
export declare function permissionInfo(id: PermissionId): PermissionInfo;
|
|
45
|
+
/** The states this permission can really be in on this platform, in picker order. */
|
|
46
|
+
export declare function supportedStates(id: PermissionId, platform: Platform): PermissionState[];
|
|
47
|
+
export declare function isSupported(id: PermissionId, platform: Platform, state: PermissionState): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* The state as the platform would really report it. iOS never reports
|
|
50
|
+
* "blocked" (a refusal is already final) and Android never reports
|
|
51
|
+
* "restricted". Unknown combinations fall back to the nearest real one.
|
|
52
|
+
*/
|
|
53
|
+
export declare function normalizeState(id: PermissionId, platform: Platform, state: PermissionState): PermissionState;
|
|
54
|
+
/** Can a request from this state show a prompt? */
|
|
55
|
+
export declare function canPrompt(platform: Platform, state: PermissionState): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* What a request does. Returns the state afterwards. `answer` is only used
|
|
58
|
+
* when a prompt would show; a `ask` answer must be resolved to a concrete one
|
|
59
|
+
* by the caller before calling this.
|
|
60
|
+
*/
|
|
61
|
+
export declare function stateAfterRequest(id: PermissionId, platform: Platform, state: PermissionState, answer: PromptAnswer): PermissionState;
|
|
62
|
+
/** The prompt's buttons, in the platform's order. */
|
|
63
|
+
export declare function promptChoices(id: PermissionId, platform: Platform): {
|
|
64
|
+
answer: PromptAnswer;
|
|
65
|
+
label: string;
|
|
66
|
+
}[];
|
|
67
|
+
/**
|
|
68
|
+
* The option names the Settings app uses for this permission, so Buoy's
|
|
69
|
+
* stand-in reads like the real page.
|
|
70
|
+
*/
|
|
71
|
+
export declare function settingsLabel(id: PermissionId, platform: Platform, state: PermissionState): string;
|
|
72
|
+
/** One sentence on what a state means for this permission, for the picker. */
|
|
73
|
+
export declare function stateDescription(id: PermissionId, platform: Platform, state: PermissionState): string;
|
|
74
|
+
export interface ExpoPermissionResponse {
|
|
75
|
+
status: "granted" | "denied" | "undetermined" | string;
|
|
76
|
+
granted?: boolean;
|
|
77
|
+
canAskAgain?: boolean;
|
|
78
|
+
expires?: "never" | number;
|
|
79
|
+
accessPrivileges?: "all" | "limited" | "none";
|
|
80
|
+
scope?: string;
|
|
81
|
+
ios?: Record<string, unknown> & {
|
|
82
|
+
status?: number;
|
|
83
|
+
scope?: string;
|
|
84
|
+
accuracy?: string;
|
|
85
|
+
};
|
|
86
|
+
android?: Record<string, unknown> & {
|
|
87
|
+
accuracy?: string;
|
|
88
|
+
importance?: number;
|
|
89
|
+
};
|
|
90
|
+
[key: string]: unknown;
|
|
91
|
+
}
|
|
92
|
+
/** An Expo `PermissionResponse` as a canonical state. */
|
|
93
|
+
export declare function fromExpo(response: unknown, platform: Platform): PermissionState | null;
|
|
94
|
+
export type RnpResult = "unavailable" | "denied" | "blocked" | "granted" | "limited";
|
|
95
|
+
/**
|
|
96
|
+
* A react-native-permissions result as a canonical state. The library reports
|
|
97
|
+
* "never asked" as `denied` on iOS, so `denied` there means undetermined.
|
|
98
|
+
*/
|
|
99
|
+
export declare function fromRnp(result: unknown, platform: Platform): PermissionState | null;
|
|
100
|
+
/** Which extra fields a module's PermissionResponse carries. */
|
|
101
|
+
export type ExpoFlavor = "plain" | "location" | "locationBackground" | "notifications" | "privileges";
|
|
102
|
+
/**
|
|
103
|
+
* The PermissionResponse an Expo module would return in this state. `base` is
|
|
104
|
+
* the module's real response, so fields the override doesn't concern (and
|
|
105
|
+
* fields added in newer SDKs) survive.
|
|
106
|
+
*/
|
|
107
|
+
export declare function toExpo(state: PermissionState, platform: Platform, flavor: ExpoFlavor, base?: unknown): ExpoPermissionResponse;
|
|
108
|
+
/** react-native-permissions' result for this state. */
|
|
109
|
+
export declare function toRnp(state: PermissionState, platform: Platform): RnpResult;
|
|
110
|
+
/** PermissionsAndroid.request's result for this state. */
|
|
111
|
+
export declare function toAndroidRequestResult(state: PermissionState): "granted" | "denied" | "never_ask_again";
|
|
112
|
+
/** PermissionsAndroid.check's result: is this exact Android permission held? */
|
|
113
|
+
export declare function toAndroidCheck(state: PermissionState, androidPermission: string): boolean;
|
|
114
|
+
export declare function stateLabel(id: PermissionId, platform: Platform, state: PermissionState): string;
|
|
115
|
+
//# sourceMappingURL=model.d.ts.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type PermissionId, type PermissionState, type RequestAnswer } from "./model";
|
|
2
|
+
export { readSavedSync } from "./readSavedSync";
|
|
3
|
+
export interface PermissionsSettings {
|
|
4
|
+
/** How a request from "not asked" is answered while overridden. */
|
|
5
|
+
requestAnswer: RequestAnswer;
|
|
6
|
+
/** Keep overrides across reloads and restarts. */
|
|
7
|
+
persist: boolean;
|
|
8
|
+
/** Show the floating chip while any override is on. */
|
|
9
|
+
showChip: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* After a change from the tool, send the app a background → active
|
|
12
|
+
* AppState change, the same thing a real trip to Settings produces, so
|
|
13
|
+
* screens that re-check on return pick up the new state.
|
|
14
|
+
*/
|
|
15
|
+
notifyApp: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Make calls that need a permission (current position, launch camera…)
|
|
18
|
+
* fail the way the native module fails when it is overridden to a refusal.
|
|
19
|
+
*/
|
|
20
|
+
enforce: boolean;
|
|
21
|
+
/** While overridden, "Open Settings" shows Buoy's stand-in instead of leaving the app. */
|
|
22
|
+
interceptSettings: boolean;
|
|
23
|
+
}
|
|
24
|
+
export declare const DEFAULT_SETTINGS: PermissionsSettings;
|
|
25
|
+
export interface SavedPermissions {
|
|
26
|
+
v: 1;
|
|
27
|
+
overrides: Partial<Record<PermissionId, PermissionState>>;
|
|
28
|
+
settings: PermissionsSettings;
|
|
29
|
+
}
|
|
30
|
+
export declare const PERMISSIONS_STORAGE_KEY: "@react_buoy_permissions_state";
|
|
31
|
+
export declare function parseSettings(raw: unknown): PermissionsSettings;
|
|
32
|
+
export declare function parseSaved(raw: string | null | undefined): SavedPermissions | null;
|
|
33
|
+
export declare function readSavedAsync(): Promise<SavedPermissions | null>;
|
|
34
|
+
/** Queue a write; the last call wins. */
|
|
35
|
+
export declare function writeSaved(saved: SavedPermissions): Promise<void>;
|
|
36
|
+
//# sourceMappingURL=persistence.d.ts.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Buoy's stand-in for the system permission prompt.
|
|
3
|
+
*
|
|
4
|
+
* While a permission is overridden, the real prompt must not show (it would
|
|
5
|
+
* change the device's real state), so a request from "not asked" shows this
|
|
6
|
+
* one instead: a native alert with the platform's own button labels. On iOS
|
|
7
|
+
* the message is the app's own purpose string when the Expo config has it,
|
|
8
|
+
* since that copy is part of what's being tested. A last line says the
|
|
9
|
+
* prompt is Buoy's, so nobody mistakes it for the real thing.
|
|
10
|
+
*/
|
|
11
|
+
import { type PermissionId, type PromptAnswer } from "./model";
|
|
12
|
+
export interface AlertButton {
|
|
13
|
+
text: string;
|
|
14
|
+
onPress?: () => void;
|
|
15
|
+
style?: "default" | "cancel" | "destructive";
|
|
16
|
+
}
|
|
17
|
+
export interface AlertLike {
|
|
18
|
+
alert(title: string, message?: string, buttons?: AlertButton[], options?: {
|
|
19
|
+
cancelable?: boolean;
|
|
20
|
+
onDismiss?: () => void;
|
|
21
|
+
}): void;
|
|
22
|
+
}
|
|
23
|
+
/** The app's purpose string for this permission, when its Expo config says. */
|
|
24
|
+
export declare function readPurposeString(id: PermissionId): string | null;
|
|
25
|
+
export declare function createAlertPrompter(alert: AlertLike): (id: PermissionId) => Promise<PromptAnswer>;
|
|
26
|
+
//# sourceMappingURL=prompt.d.ts.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read the saved permission overrides synchronously, from the file persistentStorage
|
|
3
|
+
* writes under Documents/react-buoy/, via expo-file-system's `File.textSync()`
|
|
4
|
+
* (SDK 54+). `undefined` means a synchronous read isn't possible here.
|
|
5
|
+
*/
|
|
6
|
+
import { type SavedPermissions } from "./persistence";
|
|
7
|
+
/** The saved permission overrides, read synchronously, or `undefined` if that's impossible here. */
|
|
8
|
+
export declare function readSavedSync(): SavedPermissions | null | undefined;
|
|
9
|
+
//# sourceMappingURL=readSavedSync.d.ts.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { SavedPermissions } from "./persistence";
|
|
2
|
+
/** Web and Buoy Desktop never patch permission APIs, so there is nothing to read early. */
|
|
3
|
+
export declare function readSavedSync(): SavedPermissions | null | undefined;
|
|
4
|
+
//# sourceMappingURL=readSavedSync.web.d.ts.map
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The permissions store: which permissions are overridden and to what, the
|
|
3
|
+
* settings, what the app has asked for, and a log of every call.
|
|
4
|
+
*
|
|
5
|
+
* Adapters (Expo, PermissionsAndroid, react-native-permissions) report calls
|
|
6
|
+
* here and ask it for the state to return. The tool screen, the sync adapter
|
|
7
|
+
* (Desktop, MCP, Ask Buoy) and the chip read the same snapshot.
|
|
8
|
+
*/
|
|
9
|
+
import { type PermissionId, type PermissionState, type Platform, type PromptAnswer } from "./model";
|
|
10
|
+
import { type PermissionsSettings } from "./persistence";
|
|
11
|
+
export type CallKind = "check" | "request" | "settings" | "gated";
|
|
12
|
+
export interface CallLogEntry {
|
|
13
|
+
id: number;
|
|
14
|
+
at: number;
|
|
15
|
+
/** "expo-location", "PermissionsAndroid", "react-native-permissions"… */
|
|
16
|
+
source: string;
|
|
17
|
+
method: string;
|
|
18
|
+
permission: PermissionId | null;
|
|
19
|
+
kind: CallKind;
|
|
20
|
+
/** What the OS said, when it was asked. */
|
|
21
|
+
real: PermissionState | null;
|
|
22
|
+
/** What the app received. */
|
|
23
|
+
returned: PermissionState | null;
|
|
24
|
+
overridden: boolean;
|
|
25
|
+
/** For a request that prompted: the answer given. */
|
|
26
|
+
answer?: PromptAnswer;
|
|
27
|
+
/** A gated call Buoy failed, or a call that threw. */
|
|
28
|
+
error?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface PermissionRow {
|
|
31
|
+
id: PermissionId;
|
|
32
|
+
label: string;
|
|
33
|
+
/** States the picker offers on this platform. */
|
|
34
|
+
states: PermissionState[];
|
|
35
|
+
override: PermissionState | null;
|
|
36
|
+
/** The OS's answer the last time anything asked. */
|
|
37
|
+
real: PermissionState | null;
|
|
38
|
+
/** What the app sees now: the override, else the real state. */
|
|
39
|
+
effective: PermissionState | null;
|
|
40
|
+
/** Libraries that can reach this permission in this app. */
|
|
41
|
+
sources: string[];
|
|
42
|
+
calls: number;
|
|
43
|
+
lastCallAt: number | null;
|
|
44
|
+
/** When the app last checked or requested it (not Settings trips or gated calls). */
|
|
45
|
+
lastReadAt: number | null;
|
|
46
|
+
/** When the tool last changed what the app sees (set or cleared). */
|
|
47
|
+
changedAt: number | null;
|
|
48
|
+
/**
|
|
49
|
+
* The app read this permission before the last change and hasn't read it
|
|
50
|
+
* since, so a screen may still show the old answer.
|
|
51
|
+
*/
|
|
52
|
+
stale: boolean;
|
|
53
|
+
/** Granted with "Allow Once": back to not asked after the app restarts. */
|
|
54
|
+
once: boolean;
|
|
55
|
+
/** The override grants access the OS hasn't: native calls can still fail. */
|
|
56
|
+
warning: string | null;
|
|
57
|
+
/** An installed library can show the real system prompt for this permission. */
|
|
58
|
+
canRequestReal: boolean;
|
|
59
|
+
}
|
|
60
|
+
/** Where the app runs, so a host (MCP, Desktop) can change the real OS state. */
|
|
61
|
+
export interface HostInfo {
|
|
62
|
+
isSimulator: boolean | null;
|
|
63
|
+
deviceName: string | null;
|
|
64
|
+
bundleId: string | null;
|
|
65
|
+
}
|
|
66
|
+
export interface PermissionsSnapshot {
|
|
67
|
+
platform: Platform;
|
|
68
|
+
host: HostInfo;
|
|
69
|
+
/** Adapters installed on this runtime, by library name. */
|
|
70
|
+
libraries: string[];
|
|
71
|
+
permissions: PermissionRow[];
|
|
72
|
+
overriddenCount: number;
|
|
73
|
+
settings: PermissionsSettings;
|
|
74
|
+
/** Newest first. */
|
|
75
|
+
log: CallLogEntry[];
|
|
76
|
+
}
|
|
77
|
+
type Prompter = (id: PermissionId) => Promise<PromptAnswer>;
|
|
78
|
+
type ChangeHook = (change: {
|
|
79
|
+
id: PermissionId | null;
|
|
80
|
+
from: "tool" | "request";
|
|
81
|
+
}) => void;
|
|
82
|
+
type RealRequester = () => Promise<PermissionState | null>;
|
|
83
|
+
/** Set the platform and host (install does this). */
|
|
84
|
+
export declare function configurePermissions(options: {
|
|
85
|
+
platform: Platform;
|
|
86
|
+
host?: HostInfo;
|
|
87
|
+
}): void;
|
|
88
|
+
/**
|
|
89
|
+
* Restore saved overrides. Synchronous when expo-file-system allows it, so a
|
|
90
|
+
* launch-time permission check already sees them. Idempotent.
|
|
91
|
+
*/
|
|
92
|
+
export declare function bootPermissions(): void;
|
|
93
|
+
/** Adapters register how to show the real system prompt for a permission. First one wins. */
|
|
94
|
+
export declare function registerRealRequester(id: PermissionId, fn: RealRequester): void;
|
|
95
|
+
export declare function canRequestReal(id: PermissionId): boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Show the real system prompt, whatever the override says, so the device
|
|
98
|
+
* grants what the override pretends. The system only prompts from "not
|
|
99
|
+
* asked"; after that it returns its answer without a prompt.
|
|
100
|
+
*/
|
|
101
|
+
export declare function requestRealPermission(idInput: unknown): Promise<PermissionsSnapshot>;
|
|
102
|
+
export declare function setPrompter(fn: Prompter | null): void;
|
|
103
|
+
export declare function onPermissionChange(hook: ChangeHook): () => void;
|
|
104
|
+
/** Adapters announce which libraries they found and which permissions each can reach. */
|
|
105
|
+
export declare function registerLibrary(library: string, permissions: PermissionId[]): void;
|
|
106
|
+
export declare function getPlatform(): Platform;
|
|
107
|
+
export declare function getSettings(): PermissionsSettings;
|
|
108
|
+
export declare function getOverride(id: PermissionId): PermissionState | null;
|
|
109
|
+
export declare function isOverridden(id: PermissionId): boolean;
|
|
110
|
+
export declare function anyOverride(): boolean;
|
|
111
|
+
/** Remember what the OS reported. */
|
|
112
|
+
export declare function noteReal(id: PermissionId, real: PermissionState | null, source?: string): void;
|
|
113
|
+
export declare function recordCall(entry: Omit<CallLogEntry, "id" | "at">): CallLogEntry;
|
|
114
|
+
/**
|
|
115
|
+
* A request for an overridden permission. Shows Buoy's prompt (or uses the
|
|
116
|
+
* preset answer) when the state allows a prompt, moves the override the way
|
|
117
|
+
* the OS would, and returns the new state.
|
|
118
|
+
*/
|
|
119
|
+
export declare function resolveRequest(id: PermissionId): Promise<{
|
|
120
|
+
state: PermissionState;
|
|
121
|
+
answer?: PromptAnswer;
|
|
122
|
+
}>;
|
|
123
|
+
/** Override a permission. The state must exist on this platform. */
|
|
124
|
+
export declare function setOverride(idInput: unknown, stateInput: unknown): PermissionsSnapshot;
|
|
125
|
+
/** Stop overriding one permission: the app sees the OS's answer again. */
|
|
126
|
+
export declare function clearOverride(idInput: unknown): PermissionsSnapshot;
|
|
127
|
+
/** Stop every override. */
|
|
128
|
+
export declare function resetPermissions(): PermissionsSnapshot;
|
|
129
|
+
/** The overrides as they are now, for putting them back after a reset (undo). */
|
|
130
|
+
export declare function getOverrides(): Partial<Record<PermissionId, PermissionState>>;
|
|
131
|
+
/** Replace every override at once: undo for a reset. */
|
|
132
|
+
export declare function restoreOverrides(overrides: Partial<Record<PermissionId, PermissionState>>): PermissionsSnapshot;
|
|
133
|
+
export declare function updatePermissionSettings(patch: Partial<Record<keyof PermissionsSettings, unknown>>): PermissionsSnapshot;
|
|
134
|
+
export declare function clearPermissionLog(): PermissionsSnapshot;
|
|
135
|
+
export declare function getPermissionsSnapshot(): PermissionsSnapshot;
|
|
136
|
+
export declare function subscribePermissions(listener: () => void): () => void;
|
|
137
|
+
/** For tests: forget everything. */
|
|
138
|
+
export declare function __resetPermissionsForTests(platform?: Platform): void;
|
|
139
|
+
export {};
|
|
140
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { permissionsToolPreset, createPermissionsTool } from "./preset";
|
|
2
|
+
export { PermissionsBody } from "./components/PermissionsBody";
|
|
3
|
+
export { PermissionsToolView } from "./components/PermissionsToolView";
|
|
4
|
+
export type { PermissionsToolViewProps, PermissionsActionHandler, } from "./components/PermissionsToolView";
|
|
5
|
+
export { PermissionsChip, openPermissionsChip, PERMISSIONS_CHIP_ID, } from "./components/PermissionsChip";
|
|
6
|
+
export { PermissionsIcon, PERMISSIONS_ICON_COLOR } from "@buoy-gg/shared-ui";
|
|
7
|
+
export { permissionsSyncAdapter } from "./sync/permissionsSyncAdapter";
|
|
8
|
+
export { installPermissions } from "./engine/install";
|
|
9
|
+
export { setOverride as setPermissionOverride, clearOverride as clearPermissionOverride, resetPermissions, updatePermissionSettings, getPermissionsSnapshot, subscribePermissions, isOverridden as isPermissionOverridden, getOverride as getPermissionOverride, } from "./engine/store";
|
|
10
|
+
export type { PermissionsSnapshot, PermissionRow, CallLogEntry, } from "./engine/store";
|
|
11
|
+
export type { PermissionsSettings } from "./engine/persistence";
|
|
12
|
+
export { supportedStates, stateLabel, permissionInfo, PERMISSION_STATES, KNOWN_PERMISSION_IDS, type PermissionId, type PermissionState, type RequestAnswer, } from "./engine/model";
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-configured Permissions tool for FloatingDevTools.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```tsx
|
|
6
|
+
* import { permissionsToolPreset } from '@buoy-gg/permissions';
|
|
7
|
+
*
|
|
8
|
+
* <FloatingDevTools apps={[permissionsToolPreset]} />
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
import { PermissionsBody } from "./components/PermissionsBody";
|
|
12
|
+
export declare const permissionsToolPreset: {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
description: string;
|
|
16
|
+
slot: "both";
|
|
17
|
+
color: string;
|
|
18
|
+
icon: ({ size, color }: {
|
|
19
|
+
size: number;
|
|
20
|
+
color?: string;
|
|
21
|
+
slot?: string;
|
|
22
|
+
}) => import("react").JSX.Element;
|
|
23
|
+
component: typeof PermissionsBody;
|
|
24
|
+
modal: {
|
|
25
|
+
subtitle: string;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
/** Create a custom Permissions tool configuration. */
|
|
29
|
+
export declare const createPermissionsTool: (options?: (Record<never, never> & import("@buoy-gg/shared-ui").ToolPresetOverrides) | undefined) => {
|
|
30
|
+
id: string;
|
|
31
|
+
name: string;
|
|
32
|
+
description: string;
|
|
33
|
+
slot: "both";
|
|
34
|
+
color: string;
|
|
35
|
+
icon: ({ size, color }: {
|
|
36
|
+
size: number;
|
|
37
|
+
color?: string;
|
|
38
|
+
slot?: string;
|
|
39
|
+
}) => import("react").JSX.Element;
|
|
40
|
+
component: typeof PermissionsBody;
|
|
41
|
+
modal: {
|
|
42
|
+
subtitle: string;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=preset.d.ts.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type PermissionsSnapshot } from "../engine/store";
|
|
2
|
+
export declare const permissionsSyncAdapter: import("@buoy-gg/shared-ui").SyncAdapter<PermissionsSnapshot, {
|
|
3
|
+
/** Current overrides, what the OS says, and the recent calls. Re-reads the OS first. */
|
|
4
|
+
getState(): Promise<PermissionsSnapshot>;
|
|
5
|
+
setOverride(params: unknown): PermissionsSnapshot;
|
|
6
|
+
clearOverride(params: unknown): PermissionsSnapshot;
|
|
7
|
+
/**
|
|
8
|
+
* Show the real system prompt for a permission, ignoring the override, so
|
|
9
|
+
* the device grants what the override pretends. Only prompts from "not asked".
|
|
10
|
+
*/
|
|
11
|
+
requestReal(params: unknown): Promise<PermissionsSnapshot>;
|
|
12
|
+
resetAll(): PermissionsSnapshot;
|
|
13
|
+
updateSettings(params: unknown): PermissionsSnapshot;
|
|
14
|
+
clearLog(): PermissionsSnapshot;
|
|
15
|
+
/** Send the app background → active, as a real return from Settings does. */
|
|
16
|
+
simulateReturnFromSettings(): PermissionsSnapshot;
|
|
17
|
+
}>;
|
|
18
|
+
export type PermissionsAction = keyof NonNullable<typeof permissionsSyncAdapter.actions>;
|
|
19
|
+
//# sourceMappingURL=permissionsSyncAdapter.d.ts.map
|