@codeleap/permissions 6.3.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Permission.d.ts +80 -0
- package/dist/Permission.d.ts.map +1 -0
- package/dist/PermissionsManager.d.ts +82 -0
- package/dist/PermissionsManager.d.ts.map +1 -0
- package/dist/globals.d.ts +38 -0
- package/dist/globals.d.ts.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/types.d.ts +17 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +24 -9
- package/src/Permission.ts +39 -10
- package/src/PermissionsManager.ts +32 -19
- package/src/globals.ts +32 -0
- package/src/types.ts +8 -0
- package/package.json.bak +0 -26
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { PermissionOptions } from './types';
|
|
2
|
+
import { PermissionConfig, PermissionStatus } from './globals';
|
|
3
|
+
/**
|
|
4
|
+
* Represents a single named permission with persistent reactive state.
|
|
5
|
+
*
|
|
6
|
+
* State is backed by `globalState` and persisted under the key
|
|
7
|
+
* `"permissions-<name>"`, so the last-known status survives page reloads or
|
|
8
|
+
* app restarts without an extra async check.
|
|
9
|
+
*
|
|
10
|
+
* The status machine has one sticky transition: once a permission reaches
|
|
11
|
+
* `"blocked"` it will never regress to `"denied"` via {@link check} — because
|
|
12
|
+
* on most platforms a blocked permission can only be cleared through the OS
|
|
13
|
+
* settings screen, not by a subsequent API call.
|
|
14
|
+
*/
|
|
15
|
+
export declare class Permission {
|
|
16
|
+
/** The name of the permission. */
|
|
17
|
+
name: string;
|
|
18
|
+
private state;
|
|
19
|
+
private checkStatus;
|
|
20
|
+
private requestStatus;
|
|
21
|
+
/**
|
|
22
|
+
* Set to `true` to enable verbose permission logs via `@codeleap/logger`.
|
|
23
|
+
* Applies to every `Permission` instance; toggling at runtime takes effect
|
|
24
|
+
* immediately.
|
|
25
|
+
*/
|
|
26
|
+
static logsEnabled: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Converts a raw status string into a structured descriptor object.
|
|
29
|
+
* Prefer this over equality checks scattered across call sites so that
|
|
30
|
+
* adding a new status variant only requires updating this method.
|
|
31
|
+
*/
|
|
32
|
+
static is(status: PermissionStatus): {
|
|
33
|
+
value: null;
|
|
34
|
+
isGranted: boolean;
|
|
35
|
+
isLimited: boolean;
|
|
36
|
+
isDenied: boolean;
|
|
37
|
+
isBlocked: boolean;
|
|
38
|
+
};
|
|
39
|
+
/** Configuration object associated with the permission. */
|
|
40
|
+
config: PermissionConfig;
|
|
41
|
+
/**
|
|
42
|
+
* Snapshot of the persisted status. Returns `null` until the first
|
|
43
|
+
* `check()` or `request()` resolves.
|
|
44
|
+
*/
|
|
45
|
+
get value(): null;
|
|
46
|
+
get isGranted(): boolean;
|
|
47
|
+
get isLimited(): boolean;
|
|
48
|
+
get isDenied(): boolean;
|
|
49
|
+
get isBlocked(): boolean;
|
|
50
|
+
constructor(options: PermissionOptions<PermissionConfig>);
|
|
51
|
+
private log;
|
|
52
|
+
/**
|
|
53
|
+
* Directly overwrites the stored status without going through the native
|
|
54
|
+
* check or request flow. Useful when the status is already known from an
|
|
55
|
+
* external source (e.g. a push-notification token registration callback).
|
|
56
|
+
*/
|
|
57
|
+
set(newStatus: PermissionStatus): void;
|
|
58
|
+
/**
|
|
59
|
+
* React hook that subscribes a component to this permission's reactive
|
|
60
|
+
* state. Re-renders whenever the status changes.
|
|
61
|
+
*/
|
|
62
|
+
use(): null;
|
|
63
|
+
/**
|
|
64
|
+
* Queries the native status without showing a user-facing prompt.
|
|
65
|
+
*
|
|
66
|
+
* If the platform returns `"denied"` but the stored value is already
|
|
67
|
+
* `"blocked"`, the `"blocked"` value is preserved. This guards against
|
|
68
|
+
* platforms that report a previously-blocked permission as merely denied on
|
|
69
|
+
* subsequent queries, which would incorrectly allow a re-request attempt.
|
|
70
|
+
*/
|
|
71
|
+
check(): Promise<null>;
|
|
72
|
+
/**
|
|
73
|
+
* Triggers an interactive permission prompt (may show a system dialog) and
|
|
74
|
+
* updates stored state when the result differs from the current value.
|
|
75
|
+
* Only call this in direct response to a user action; invoking it
|
|
76
|
+
* speculatively or on mount will fail silently on most platforms.
|
|
77
|
+
*/
|
|
78
|
+
request(): Promise<null>;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=Permission.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Permission.d.ts","sourceRoot":"","sources":["../src/Permission.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAC3C,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAI9D;;;;;;;;;;;GAWG;AACH,qBAAa,UAAU;IACrB,kCAAkC;IAC3B,IAAI,EAAE,MAAM,CAAA;IAEnB,OAAO,CAAC,KAAK,CAA+B;IAE5C,OAAO,CAAC,WAAW,CAAiC;IAEpD,OAAO,CAAC,aAAa,CAAiC;IAEtD;;;;OAIG;IACH,MAAM,CAAC,WAAW,EAAE,OAAO,CAAQ;IAEnC;;;;OAIG;IACH,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,gBAAgB;;;;;;;IAUlC,2DAA2D;IACpD,MAAM,EAAE,gBAAgB,CAAA;IAE/B;;;OAGG;IACH,IAAI,KAAK,SAER;IAED,IAAI,SAAS,YAEZ;IAED,IAAI,SAAS,YAEZ;IAED,IAAI,QAAQ,YAEX;IAED,IAAI,SAAS,YAEZ;gBAEW,OAAO,EAAE,iBAAiB,CAAC,gBAAgB,CAAC;IAWxD,OAAO,CAAC,GAAG;IAKX;;;;OAIG;IACH,GAAG,CAAC,SAAS,EAAE,gBAAgB;IAI/B;;;OAGG;IACH,GAAG;IAIH;;;;;;;OAOG;IACG,KAAK;IAkBX;;;;;OAKG;IACG,OAAO;CAYd"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Permission } from './Permission';
|
|
2
|
+
import { PermissionOptions } from './types';
|
|
3
|
+
import { PermissionStatus, PermissionConfig } from './globals';
|
|
4
|
+
/**
|
|
5
|
+
* Registry and coordinator for a fixed set of named permissions.
|
|
6
|
+
*
|
|
7
|
+
* Instantiating this class immediately calls `checkAll()` in the background so
|
|
8
|
+
* that every permission's cached status is refreshed before the first render.
|
|
9
|
+
* The `requester` callback is the single authoritative place to implement any
|
|
10
|
+
* pre-prompt logic (e.g. showing an educational interstitial before the OS
|
|
11
|
+
* dialog appears).
|
|
12
|
+
*/
|
|
13
|
+
export declare class PermissionsManager<P extends string> {
|
|
14
|
+
private requester;
|
|
15
|
+
permissions: Record<P, Permission>;
|
|
16
|
+
private get keys();
|
|
17
|
+
/**
|
|
18
|
+
* Snapshot of all registered permissions' current statuses. Values reflect
|
|
19
|
+
* whatever is in persistent state at the moment of access — call
|
|
20
|
+
* `checkAll()` first if you need fresh data from the platform.
|
|
21
|
+
*/
|
|
22
|
+
get values(): Record<P, null>;
|
|
23
|
+
private forEach;
|
|
24
|
+
constructor(requester: (permission: Permission) => Promise<PermissionStatus>, permissions: Record<P, Omit<PermissionOptions<PermissionConfig>, 'name'>>);
|
|
25
|
+
private log;
|
|
26
|
+
/**
|
|
27
|
+
* React hook that subscribes a component to the reactive state of a single
|
|
28
|
+
* named permission. Re-renders whenever that permission's status changes.
|
|
29
|
+
*/
|
|
30
|
+
use(permissionName: P): null;
|
|
31
|
+
/**
|
|
32
|
+
* Requests a single permission through the manager's `requester` callback.
|
|
33
|
+
* The result passes through {@link Permission.is} so callers get a
|
|
34
|
+
* structured descriptor rather than a raw string.
|
|
35
|
+
*
|
|
36
|
+
* Note: this bypasses `Permission.request()` on the individual instance —
|
|
37
|
+
* the `requester` callback owns the full flow, including any pre-prompt UI.
|
|
38
|
+
*/
|
|
39
|
+
request(permissionName: P): Promise<{
|
|
40
|
+
value: null;
|
|
41
|
+
isGranted: boolean;
|
|
42
|
+
isLimited: boolean;
|
|
43
|
+
isDenied: boolean;
|
|
44
|
+
isBlocked: boolean;
|
|
45
|
+
}>;
|
|
46
|
+
/**
|
|
47
|
+
* Silently queries a single permission's current status (no system dialog).
|
|
48
|
+
* Returns a structured descriptor via {@link Permission.is}.
|
|
49
|
+
*/
|
|
50
|
+
check(permissionName: P): Promise<{
|
|
51
|
+
value: null;
|
|
52
|
+
isGranted: boolean;
|
|
53
|
+
isLimited: boolean;
|
|
54
|
+
isDenied: boolean;
|
|
55
|
+
isBlocked: boolean;
|
|
56
|
+
}>;
|
|
57
|
+
/**
|
|
58
|
+
* Requests multiple permissions sequentially. Requests are serial, not
|
|
59
|
+
* parallel, to avoid triggering concurrent system dialogs which may be
|
|
60
|
+
* rejected or silently dropped on some platforms.
|
|
61
|
+
*/
|
|
62
|
+
requestMany<K extends P>(permissionsNames: K[]): Promise<Record<K, ReturnType<typeof Permission.is>>>;
|
|
63
|
+
/**
|
|
64
|
+
* Silently queries multiple permissions sequentially. Serial execution
|
|
65
|
+
* ensures consistent ordering and avoids race conditions in the underlying
|
|
66
|
+
* state store.
|
|
67
|
+
*/
|
|
68
|
+
checkMany<K extends P>(permissionsNames: K[]): Promise<Record<K, ReturnType<typeof Permission.is>>>;
|
|
69
|
+
/**
|
|
70
|
+
* Silently refreshes every registered permission. Called automatically
|
|
71
|
+
* during construction so that cached statuses are up to date before the
|
|
72
|
+
* first render without requiring the caller to await initialization.
|
|
73
|
+
*/
|
|
74
|
+
checkAll(): Promise<Record<P, {
|
|
75
|
+
value: null;
|
|
76
|
+
isGranted: boolean;
|
|
77
|
+
isLimited: boolean;
|
|
78
|
+
isDenied: boolean;
|
|
79
|
+
isBlocked: boolean;
|
|
80
|
+
}>>;
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=PermissionsManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PermissionsManager.d.ts","sourceRoot":"","sources":["../src/PermissionsManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAC3C,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAG9D;;;;;;;;GAQG;AACH,qBAAa,kBAAkB,CAAC,CAAC,SAAS,MAAM;IAC9C,OAAO,CAAC,SAAS,CAAuD;IAExE,WAAW,EAAE,MAAM,CAAC,CAAC,EAAE,UAAU,CAAC,CAA8B;IAEhE,OAAO,KAAK,IAAI,GAEf;IAED;;;;OAIG;IACH,IAAI,MAAM,oBAQT;YAEa,OAAO;gBAQnB,SAAS,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,OAAO,CAAC,gBAAgB,CAAC,EAChE,WAAW,EAAE,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC,CAAC;IAkB3E,OAAO,CAAC,GAAG;IAKX;;;OAGG;IACH,GAAG,CAAC,cAAc,EAAE,CAAC;IAIrB;;;;;;;OAOG;IACG,OAAO,CAAC,cAAc,EAAE,CAAC;;;;;;;IAM/B;;;OAGG;IACG,KAAK,CAAC,cAAc,EAAE,CAAC;;;;;;;IAM7B;;;;OAIG;IACG,WAAW,CAAC,CAAC,SAAS,CAAC,EAAE,gBAAgB,EAAE,CAAC,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC;IAU3G;;;;OAIG;IACG,SAAS,CAAC,CAAC,SAAS,CAAC,EAAE,gBAAgB,EAAE,CAAC,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC;IAUzG;;;;OAIG;IACG,QAAQ;;;;;;;CAIf"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extend this interface via module augmentation in the consuming app to attach
|
|
3
|
+
* platform-specific metadata (e.g. Android rationale strings, iOS usage
|
|
4
|
+
* descriptions) to every registered permission.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* declare module '@codeleap/permissions' {
|
|
8
|
+
* interface PermissionConfig {
|
|
9
|
+
* rationale: string
|
|
10
|
+
* }
|
|
11
|
+
* }
|
|
12
|
+
*/
|
|
13
|
+
export interface PermissionConfig {
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Extend this interface via module augmentation to register the set of valid
|
|
17
|
+
* permission statuses for the target platform. The keys become the union type
|
|
18
|
+
* used throughout the package.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* declare module '@codeleap/permissions' {
|
|
22
|
+
* interface Status {
|
|
23
|
+
* granted: never
|
|
24
|
+
* denied: never
|
|
25
|
+
* blocked: never
|
|
26
|
+
* limited: never
|
|
27
|
+
* }
|
|
28
|
+
* }
|
|
29
|
+
*/
|
|
30
|
+
export interface Status {
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Union of every key registered in {@link Status} plus `null`.
|
|
34
|
+
* `null` means the status has not yet been resolved (e.g. before the first
|
|
35
|
+
* `check()` call completes).
|
|
36
|
+
*/
|
|
37
|
+
export type PermissionStatus = keyof Status | null;
|
|
38
|
+
//# sourceMappingURL=globals.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"globals.d.ts","sourceRoot":"","sources":["../src/globals.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,gBAAgB;CAEhC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,MAAM;CAEtB;AAED;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG,MAAM,MAAM,GAAG,IAAI,CAAA"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAA;AACpC,cAAc,cAAc,CAAA;AAC5B,cAAc,SAAS,CAAA;AACvB,cAAc,WAAW,CAAA"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { AnyRecord } from '@codeleap/types';
|
|
2
|
+
import { PermissionStatus } from './globals';
|
|
3
|
+
/**
|
|
4
|
+
* Construction options for a single {@link Permission} instance.
|
|
5
|
+
*
|
|
6
|
+
* `check` and `request` are intentionally separate callbacks because many
|
|
7
|
+
* platforms distinguish a silent status query (no UI) from an interactive
|
|
8
|
+
* prompt (may show a system dialog). Provide both even if the underlying API
|
|
9
|
+
* is the same — the manager calls them at different points in the flow.
|
|
10
|
+
*/
|
|
11
|
+
export type PermissionOptions<Config extends AnyRecord> = {
|
|
12
|
+
name: string;
|
|
13
|
+
config: Config;
|
|
14
|
+
check: () => Promise<PermissionStatus>;
|
|
15
|
+
request: () => Promise<PermissionStatus>;
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAC3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAE5C;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,CAAC,MAAM,SAAS,SAAS,IAAI;IACxD,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,OAAO,CAAC,gBAAgB,CAAC,CAAA;IACtC,OAAO,EAAE,MAAM,OAAO,CAAC,gBAAgB,CAAC,CAAA;CACzC,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codeleap/permissions",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0",
|
|
4
4
|
"main": "src/index.ts",
|
|
5
|
+
"types": "dist/index.d.ts",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"source": "./src/index.ts",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"src"
|
|
17
|
+
],
|
|
5
18
|
"license": "UNLICENSED",
|
|
6
19
|
"repository": {
|
|
7
20
|
"url": "https://github.com/codeleap-uk/internal-libs-monorepo.git",
|
|
@@ -9,18 +22,20 @@
|
|
|
9
22
|
"directory": "packages/permissions"
|
|
10
23
|
},
|
|
11
24
|
"devDependencies": {
|
|
12
|
-
"@codeleap/config": "
|
|
13
|
-
"@codeleap/
|
|
14
|
-
"@codeleap/
|
|
25
|
+
"@codeleap/config": "7.0.0",
|
|
26
|
+
"@codeleap/logger": "7.0.0",
|
|
27
|
+
"@codeleap/store": "7.0.0",
|
|
28
|
+
"@codeleap/types": "7.0.0",
|
|
15
29
|
"ts-node-dev": "1.1.8"
|
|
16
30
|
},
|
|
17
31
|
"scripts": {
|
|
18
|
-
"build": "
|
|
32
|
+
"build": "tsc --build tsconfig.build.json",
|
|
33
|
+
"typecheck": "bun tsc --noEmit -p ./tsconfig.json"
|
|
19
34
|
},
|
|
20
35
|
"peerDependencies": {
|
|
21
|
-
"@codeleap/store": "
|
|
22
|
-
"@codeleap/types": "
|
|
23
|
-
"@codeleap/logger": "
|
|
36
|
+
"@codeleap/store": "7.0.0",
|
|
37
|
+
"@codeleap/types": "7.0.0",
|
|
38
|
+
"@codeleap/logger": "7.0.0",
|
|
24
39
|
"typescript": "5.5.2"
|
|
25
40
|
}
|
|
26
|
-
}
|
|
41
|
+
}
|
package/src/Permission.ts
CHANGED
|
@@ -4,6 +4,18 @@ import { PermissionConfig, PermissionStatus } from './globals'
|
|
|
4
4
|
import { TypeGuards } from '@codeleap/types'
|
|
5
5
|
import { logger } from '@codeleap/logger'
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Represents a single named permission with persistent reactive state.
|
|
9
|
+
*
|
|
10
|
+
* State is backed by `globalState` and persisted under the key
|
|
11
|
+
* `"permissions-<name>"`, so the last-known status survives page reloads or
|
|
12
|
+
* app restarts without an extra async check.
|
|
13
|
+
*
|
|
14
|
+
* The status machine has one sticky transition: once a permission reaches
|
|
15
|
+
* `"blocked"` it will never regress to `"denied"` via {@link check} — because
|
|
16
|
+
* on most platforms a blocked permission can only be cleared through the OS
|
|
17
|
+
* settings screen, not by a subsequent API call.
|
|
18
|
+
*/
|
|
7
19
|
export class Permission {
|
|
8
20
|
/** The name of the permission. */
|
|
9
21
|
public name: string
|
|
@@ -14,8 +26,18 @@ export class Permission {
|
|
|
14
26
|
|
|
15
27
|
private requestStatus: () => Promise<PermissionStatus>
|
|
16
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Set to `true` to enable verbose permission logs via `@codeleap/logger`.
|
|
31
|
+
* Applies to every `Permission` instance; toggling at runtime takes effect
|
|
32
|
+
* immediately.
|
|
33
|
+
*/
|
|
17
34
|
static logsEnabled: boolean = false
|
|
18
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Converts a raw status string into a structured descriptor object.
|
|
38
|
+
* Prefer this over equality checks scattered across call sites so that
|
|
39
|
+
* adding a new status variant only requires updating this method.
|
|
40
|
+
*/
|
|
19
41
|
static is(status: PermissionStatus) {
|
|
20
42
|
return {
|
|
21
43
|
value: status,
|
|
@@ -30,8 +52,8 @@ export class Permission {
|
|
|
30
52
|
public config: PermissionConfig
|
|
31
53
|
|
|
32
54
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
55
|
+
* Snapshot of the persisted status. Returns `null` until the first
|
|
56
|
+
* `check()` or `request()` resolves.
|
|
35
57
|
*/
|
|
36
58
|
get value() {
|
|
37
59
|
return this.state.get()
|
|
@@ -70,24 +92,29 @@ export class Permission {
|
|
|
70
92
|
}
|
|
71
93
|
|
|
72
94
|
/**
|
|
73
|
-
*
|
|
74
|
-
*
|
|
95
|
+
* Directly overwrites the stored status without going through the native
|
|
96
|
+
* check or request flow. Useful when the status is already known from an
|
|
97
|
+
* external source (e.g. a push-notification token registration callback).
|
|
75
98
|
*/
|
|
76
99
|
set(newStatus: PermissionStatus) {
|
|
77
100
|
this.state.set(newStatus)
|
|
78
101
|
}
|
|
79
102
|
|
|
80
103
|
/**
|
|
81
|
-
* React hook
|
|
82
|
-
*
|
|
104
|
+
* React hook that subscribes a component to this permission's reactive
|
|
105
|
+
* state. Re-renders whenever the status changes.
|
|
83
106
|
*/
|
|
84
107
|
use() {
|
|
85
108
|
return this.state.use()
|
|
86
109
|
}
|
|
87
110
|
|
|
88
111
|
/**
|
|
89
|
-
*
|
|
90
|
-
*
|
|
112
|
+
* Queries the native status without showing a user-facing prompt.
|
|
113
|
+
*
|
|
114
|
+
* If the platform returns `"denied"` but the stored value is already
|
|
115
|
+
* `"blocked"`, the `"blocked"` value is preserved. This guards against
|
|
116
|
+
* platforms that report a previously-blocked permission as merely denied on
|
|
117
|
+
* subsequent queries, which would incorrectly allow a re-request attempt.
|
|
91
118
|
*/
|
|
92
119
|
async check() {
|
|
93
120
|
let status = await this.checkStatus()
|
|
@@ -108,8 +135,10 @@ export class Permission {
|
|
|
108
135
|
}
|
|
109
136
|
|
|
110
137
|
/**
|
|
111
|
-
*
|
|
112
|
-
*
|
|
138
|
+
* Triggers an interactive permission prompt (may show a system dialog) and
|
|
139
|
+
* updates stored state when the result differs from the current value.
|
|
140
|
+
* Only call this in direct response to a user action; invoking it
|
|
141
|
+
* speculatively or on mount will fail silently on most platforms.
|
|
113
142
|
*/
|
|
114
143
|
async request() {
|
|
115
144
|
const status = await this.requestStatus()
|
|
@@ -3,6 +3,15 @@ import { PermissionOptions } from './types'
|
|
|
3
3
|
import { PermissionStatus, PermissionConfig } from './globals'
|
|
4
4
|
import { logger } from '@codeleap/logger'
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Registry and coordinator for a fixed set of named permissions.
|
|
8
|
+
*
|
|
9
|
+
* Instantiating this class immediately calls `checkAll()` in the background so
|
|
10
|
+
* that every permission's cached status is refreshed before the first render.
|
|
11
|
+
* The `requester` callback is the single authoritative place to implement any
|
|
12
|
+
* pre-prompt logic (e.g. showing an educational interstitial before the OS
|
|
13
|
+
* dialog appears).
|
|
14
|
+
*/
|
|
6
15
|
export class PermissionsManager<P extends string> {
|
|
7
16
|
private requester: (permission: Permission) => Promise<PermissionStatus>
|
|
8
17
|
|
|
@@ -13,14 +22,15 @@ export class PermissionsManager<P extends string> {
|
|
|
13
22
|
}
|
|
14
23
|
|
|
15
24
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
25
|
+
* Snapshot of all registered permissions' current statuses. Values reflect
|
|
26
|
+
* whatever is in persistent state at the moment of access — call
|
|
27
|
+
* `checkAll()` first if you need fresh data from the platform.
|
|
18
28
|
*/
|
|
19
29
|
get values() {
|
|
20
30
|
const values = {} as Record<P, PermissionStatus>
|
|
21
31
|
|
|
22
32
|
this.forEach(permission => {
|
|
23
|
-
values[permission.name] = permission.value
|
|
33
|
+
values[permission.name as P] = permission.value
|
|
24
34
|
})
|
|
25
35
|
|
|
26
36
|
return values
|
|
@@ -59,17 +69,20 @@ export class PermissionsManager<P extends string> {
|
|
|
59
69
|
}
|
|
60
70
|
|
|
61
71
|
/**
|
|
62
|
-
* React hook
|
|
63
|
-
*
|
|
72
|
+
* React hook that subscribes a component to the reactive state of a single
|
|
73
|
+
* named permission. Re-renders whenever that permission's status changes.
|
|
64
74
|
*/
|
|
65
75
|
use(permissionName: P) {
|
|
66
76
|
return this.permissions[permissionName].use()
|
|
67
77
|
}
|
|
68
78
|
|
|
69
79
|
/**
|
|
70
|
-
* Requests a
|
|
71
|
-
*
|
|
72
|
-
*
|
|
80
|
+
* Requests a single permission through the manager's `requester` callback.
|
|
81
|
+
* The result passes through {@link Permission.is} so callers get a
|
|
82
|
+
* structured descriptor rather than a raw string.
|
|
83
|
+
*
|
|
84
|
+
* Note: this bypasses `Permission.request()` on the individual instance —
|
|
85
|
+
* the `requester` callback owns the full flow, including any pre-prompt UI.
|
|
73
86
|
*/
|
|
74
87
|
async request(permissionName: P) {
|
|
75
88
|
const permission = this.permissions[permissionName]
|
|
@@ -78,9 +91,8 @@ export class PermissionsManager<P extends string> {
|
|
|
78
91
|
}
|
|
79
92
|
|
|
80
93
|
/**
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
* @returns The current permission status.
|
|
94
|
+
* Silently queries a single permission's current status (no system dialog).
|
|
95
|
+
* Returns a structured descriptor via {@link Permission.is}.
|
|
84
96
|
*/
|
|
85
97
|
async check(permissionName: P) {
|
|
86
98
|
const permission = this.permissions[permissionName]
|
|
@@ -89,9 +101,9 @@ export class PermissionsManager<P extends string> {
|
|
|
89
101
|
}
|
|
90
102
|
|
|
91
103
|
/**
|
|
92
|
-
* Requests multiple permissions
|
|
93
|
-
*
|
|
94
|
-
*
|
|
104
|
+
* Requests multiple permissions sequentially. Requests are serial, not
|
|
105
|
+
* parallel, to avoid triggering concurrent system dialogs which may be
|
|
106
|
+
* rejected or silently dropped on some platforms.
|
|
95
107
|
*/
|
|
96
108
|
async requestMany<K extends P>(permissionsNames: K[]): Promise<Record<K, ReturnType<typeof Permission.is>>> {
|
|
97
109
|
const status = {} as Record<K, ReturnType<typeof Permission.is>>
|
|
@@ -104,9 +116,9 @@ export class PermissionsManager<P extends string> {
|
|
|
104
116
|
}
|
|
105
117
|
|
|
106
118
|
/**
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
119
|
+
* Silently queries multiple permissions sequentially. Serial execution
|
|
120
|
+
* ensures consistent ordering and avoids race conditions in the underlying
|
|
121
|
+
* state store.
|
|
110
122
|
*/
|
|
111
123
|
async checkMany<K extends P>(permissionsNames: K[]): Promise<Record<K, ReturnType<typeof Permission.is>>> {
|
|
112
124
|
const status = {} as Record<K, ReturnType<typeof Permission.is>>
|
|
@@ -119,8 +131,9 @@ export class PermissionsManager<P extends string> {
|
|
|
119
131
|
}
|
|
120
132
|
|
|
121
133
|
/**
|
|
122
|
-
*
|
|
123
|
-
*
|
|
134
|
+
* Silently refreshes every registered permission. Called automatically
|
|
135
|
+
* during construction so that cached statuses are up to date before the
|
|
136
|
+
* first render without requiring the caller to await initialization.
|
|
124
137
|
*/
|
|
125
138
|
async checkAll() {
|
|
126
139
|
this.log('checkAll')
|
package/src/globals.ts
CHANGED
|
@@ -1,10 +1,42 @@
|
|
|
1
1
|
|
|
2
|
+
/**
|
|
3
|
+
* Extend this interface via module augmentation in the consuming app to attach
|
|
4
|
+
* platform-specific metadata (e.g. Android rationale strings, iOS usage
|
|
5
|
+
* descriptions) to every registered permission.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* declare module '@codeleap/permissions' {
|
|
9
|
+
* interface PermissionConfig {
|
|
10
|
+
* rationale: string
|
|
11
|
+
* }
|
|
12
|
+
* }
|
|
13
|
+
*/
|
|
2
14
|
export interface PermissionConfig {
|
|
3
15
|
|
|
4
16
|
}
|
|
5
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Extend this interface via module augmentation to register the set of valid
|
|
20
|
+
* permission statuses for the target platform. The keys become the union type
|
|
21
|
+
* used throughout the package.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* declare module '@codeleap/permissions' {
|
|
25
|
+
* interface Status {
|
|
26
|
+
* granted: never
|
|
27
|
+
* denied: never
|
|
28
|
+
* blocked: never
|
|
29
|
+
* limited: never
|
|
30
|
+
* }
|
|
31
|
+
* }
|
|
32
|
+
*/
|
|
6
33
|
export interface Status {
|
|
7
34
|
|
|
8
35
|
}
|
|
9
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Union of every key registered in {@link Status} plus `null`.
|
|
39
|
+
* `null` means the status has not yet been resolved (e.g. before the first
|
|
40
|
+
* `check()` call completes).
|
|
41
|
+
*/
|
|
10
42
|
export type PermissionStatus = keyof Status | null
|
package/src/types.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { AnyRecord } from '@codeleap/types'
|
|
2
2
|
import { PermissionStatus } from './globals'
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Construction options for a single {@link Permission} instance.
|
|
6
|
+
*
|
|
7
|
+
* `check` and `request` are intentionally separate callbacks because many
|
|
8
|
+
* platforms distinguish a silent status query (no UI) from an interactive
|
|
9
|
+
* prompt (may show a system dialog). Provide both even if the underlying API
|
|
10
|
+
* is the same — the manager calls them at different points in the flow.
|
|
11
|
+
*/
|
|
4
12
|
export type PermissionOptions<Config extends AnyRecord> = {
|
|
5
13
|
name: string
|
|
6
14
|
config: Config
|
package/package.json.bak
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@codeleap/permissions",
|
|
3
|
-
"version": "6.3.0",
|
|
4
|
-
"main": "src/index.ts",
|
|
5
|
-
"license": "UNLICENSED",
|
|
6
|
-
"repository": {
|
|
7
|
-
"url": "https://github.com/codeleap-uk/internal-libs-monorepo.git",
|
|
8
|
-
"type": "git",
|
|
9
|
-
"directory": "packages/permissions"
|
|
10
|
-
},
|
|
11
|
-
"devDependencies": {
|
|
12
|
-
"@codeleap/config": "workspace:*",
|
|
13
|
-
"@codeleap/store": "workspace:*",
|
|
14
|
-
"@codeleap/types": "workspace:*",
|
|
15
|
-
"ts-node-dev": "1.1.8"
|
|
16
|
-
},
|
|
17
|
-
"scripts": {
|
|
18
|
-
"build": "echo 'No build needed'"
|
|
19
|
-
},
|
|
20
|
-
"peerDependencies": {
|
|
21
|
-
"@codeleap/store": "workspace:*",
|
|
22
|
-
"@codeleap/types": "workspace:*",
|
|
23
|
-
"@codeleap/logger": "workspace:*",
|
|
24
|
-
"typescript": "5.5.2"
|
|
25
|
-
}
|
|
26
|
-
}
|