@ankhorage/permissions 0.2.2 → 0.2.4

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.
Files changed (66) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +27 -3
  3. package/dist/cli/index.d.ts +2 -0
  4. package/dist/cli/index.d.ts.map +1 -0
  5. package/dist/cli/index.js +2 -0
  6. package/dist/cli/index.js.map +1 -0
  7. package/dist/expo/adapters/mediaLibrary.d.ts.map +1 -1
  8. package/dist/expo/adapters/mediaLibrary.js +15 -3
  9. package/dist/expo/adapters/mediaLibrary.js.map +1 -1
  10. package/dist/expo/adapters/notifications.d.ts.map +1 -1
  11. package/dist/expo/adapters/notifications.js +32 -3
  12. package/dist/expo/adapters/notifications.js.map +1 -1
  13. package/dist/expo/client.d.ts +14 -0
  14. package/dist/expo/client.d.ts.map +1 -1
  15. package/dist/expo/client.js +42 -13
  16. package/dist/expo/client.js.map +1 -1
  17. package/dist/expo/client.testSupport.d.ts +54 -0
  18. package/dist/expo/client.testSupport.d.ts.map +1 -0
  19. package/dist/expo/client.testSupport.js +183 -0
  20. package/dist/expo/client.testSupport.js.map +1 -0
  21. package/dist/expo/manifest.d.ts +5 -0
  22. package/dist/expo/manifest.d.ts.map +1 -1
  23. package/dist/expo/manifest.js +5 -0
  24. package/dist/expo/manifest.js.map +1 -1
  25. package/dist/expo/permissionResponse.d.ts.map +1 -1
  26. package/dist/expo/permissionResponse.js +16 -9
  27. package/dist/expo/permissionResponse.js.map +1 -1
  28. package/dist/react/index.d.ts.map +1 -1
  29. package/dist/react/index.js +16 -11
  30. package/dist/react/index.js.map +1 -1
  31. package/dist/state/permissionState.d.ts +2 -0
  32. package/dist/state/permissionState.d.ts.map +1 -1
  33. package/dist/state/permissionState.js +3 -1
  34. package/dist/state/permissionState.js.map +1 -1
  35. package/dist/testing/index.d.ts.map +1 -1
  36. package/dist/testing/index.js +27 -17
  37. package/dist/testing/index.js.map +1 -1
  38. package/dist/web/index.d.ts +1 -2
  39. package/dist/web/index.d.ts.map +1 -1
  40. package/dist/web/index.js +2 -76
  41. package/dist/web/index.js.map +1 -1
  42. package/dist/web/permissionStates.d.ts +10 -0
  43. package/dist/web/permissionStates.d.ts.map +1 -0
  44. package/dist/web/permissionStates.js +64 -0
  45. package/dist/web/permissionStates.js.map +1 -0
  46. package/package.json +22 -13
  47. package/src/cli/index.ts +1 -0
  48. package/src/expo/adapters/mediaLibrary.ts +28 -4
  49. package/src/expo/adapters/notifications.ts +73 -4
  50. package/src/expo/client.notifications.test.ts +106 -0
  51. package/src/expo/client.test.ts +109 -349
  52. package/src/expo/client.testSupport.ts +188 -0
  53. package/src/expo/client.ts +45 -13
  54. package/src/expo/manifest.test.ts +49 -47
  55. package/src/expo/manifest.ts +7 -5
  56. package/src/expo/modules.d.ts +18 -4
  57. package/src/expo/permissionResponse.ts +26 -9
  58. package/src/manager/createPermissionManager.test.ts +48 -50
  59. package/src/react/index.tsx +21 -13
  60. package/src/registry/permissions.test.ts +4 -3
  61. package/src/state/permissionState.test.ts +8 -1
  62. package/src/state/permissionState.ts +4 -7
  63. package/src/testing/index.ts +36 -25
  64. package/src/web/index.test.ts +149 -151
  65. package/src/web/index.ts +9 -102
  66. package/src/web/permissionStates.ts +83 -0
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/web/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { PermissionClient } from '../client/types';\nimport { assertKnownPermission, Permission } from '../registry/permissions';\nimport {\n createPermissionState,\n type PermissionState,\n type PermissionStatus,\n} from '../state/permissionState';\n\ntype BrowserPermissionName = 'camera' | 'microphone' | 'geolocation' | 'notifications';\ntype BrowserPermissionState = 'granted' | 'denied' | 'prompt';\ntype NotificationPermissionState = 'default' | 'denied' | 'granted';\n\ninterface BrowserPermissionStatus {\n readonly state: BrowserPermissionState;\n}\n\ninterface BrowserPermissions {\n query(descriptor: { readonly name: BrowserPermissionName }): Promise<BrowserPermissionStatus>;\n}\n\ninterface MediaStreamTrackLike {\n stop(): void;\n}\n\ninterface MediaStreamLike {\n getTracks(): readonly MediaStreamTrackLike[];\n}\n\ninterface MediaDevicesLike {\n getUserMedia(constraints: {\n readonly audio?: boolean;\n readonly video?: boolean;\n }): Promise<MediaStreamLike>;\n}\n\ninterface GeolocationLike {\n getCurrentPosition(\n success: () => void,\n error: () => void,\n options?: {\n readonly maximumAge?: number;\n readonly timeout?: number;\n },\n ): void;\n}\n\ninterface NavigatorLike {\n readonly permissions?: BrowserPermissions;\n readonly mediaDevices?: MediaDevicesLike;\n readonly geolocation?: GeolocationLike;\n}\n\ninterface NotificationLike {\n readonly permission: NotificationPermissionState;\n requestPermission(): Promise<NotificationPermissionState> | NotificationPermissionState;\n}\n\ninterface WebGlobalLike {\n readonly isSecureContext?: boolean;\n readonly navigator?: NavigatorLike;\n readonly Notification?: NotificationLike;\n}\n\n/***\n * Options for the browser permission adapter.\n */\nexport interface WebPermissionClientOptions {\n readonly global?: WebGlobalLike;\n}\n\n/***\n * Creates a browser permission client using guarded structural globals.\n *\n * @readme\n * Unsupported web APIs resolve to `status: 'unavailable'`. The adapter does not\n * import DOM types and does not assume it is running in a browser.\n */\nexport function createWebPermissionClient(\n options: WebPermissionClientOptions = {},\n): PermissionClient {\n const global = options.global ?? getDefaultGlobal();\n\n return {\n async getStatus(permission) {\n const knownPermission = assertKnownPermission(permission);\n\n switch (knownPermission) {\n case Permission.Notifications:\n return getNotificationStatus(knownPermission, global);\n case Permission.LocationForeground:\n return getPermissionApiStatus(\n knownPermission,\n global,\n 'geolocation',\n 'Geolocation is unavailable in this environment.',\n );\n case Permission.Camera:\n return getMediaStatus(knownPermission, global, 'camera');\n case Permission.Microphone:\n return getMediaStatus(knownPermission, global, 'microphone');\n default:\n return unavailable(\n knownPermission,\n 'This permission is not supported by the web adapter.',\n );\n }\n },\n\n async request(permission) {\n const knownPermission = assertKnownPermission(permission);\n\n switch (knownPermission) {\n case Permission.Notifications:\n return requestNotifications(knownPermission, global);\n case Permission.LocationForeground:\n return requestGeolocation(knownPermission, global);\n case Permission.Camera:\n return requestMedia(knownPermission, global, { video: true });\n case Permission.Microphone:\n return requestMedia(knownPermission, global, { audio: true });\n default:\n return unavailable(\n knownPermission,\n 'This permission is not supported by the web adapter.',\n );\n }\n },\n };\n}\n\nfunction getDefaultGlobal(): WebGlobalLike {\n return globalThis as WebGlobalLike;\n}\n\nfunction getNotificationStatus(permission: Permission, global: WebGlobalLike): PermissionState {\n if (global.Notification === undefined) {\n return unavailable(permission, 'Notification API is unavailable.');\n }\n\n return stateFromNotificationPermission(permission, global.Notification.permission);\n}\n\nasync function requestNotifications(\n permission: Permission,\n global: WebGlobalLike,\n): Promise<PermissionState> {\n if (global.Notification === undefined) {\n return unavailable(permission, 'Notification API is unavailable.');\n }\n\n try {\n const result = await global.Notification.requestPermission();\n\n return stateFromNotificationPermission(permission, result);\n } catch (error) {\n return unavailable(\n permission,\n `Notification permission request failed${formatErrorName(error)}.`,\n );\n }\n}\n\nasync function getPermissionApiStatus(\n permission: Permission,\n global: WebGlobalLike,\n name: BrowserPermissionName,\n unavailableReason: string,\n): Promise<PermissionState> {\n if (global.navigator?.permissions === undefined) {\n return unavailable(permission, unavailableReason);\n }\n\n try {\n const status = await global.navigator.permissions.query({ name });\n\n return stateFromBrowserPermission(permission, status.state);\n } catch {\n return unavailable(permission, unavailableReason);\n }\n}\n\nasync function getMediaStatus(\n permission: Permission,\n global: WebGlobalLike,\n name: BrowserPermissionName,\n): Promise<PermissionState> {\n if (global.navigator?.permissions !== undefined) {\n try {\n const status = await global.navigator.permissions.query({ name });\n\n return stateFromBrowserPermission(permission, status.state);\n } catch {\n return getMediaAvailability(permission, global);\n }\n }\n\n return getMediaAvailability(permission, global);\n}\n\nfunction getMediaAvailability(permission: Permission, global: WebGlobalLike): PermissionState {\n if (global.isSecureContext === false) {\n return createPermissionState({\n permission,\n status: 'blocked',\n canAskAgain: false,\n reason: 'Media permissions require a secure context.',\n });\n }\n\n if (global.navigator?.mediaDevices?.getUserMedia === undefined) {\n return unavailable(permission, 'Media devices API is unavailable.');\n }\n\n return createPermissionState({\n permission,\n status: 'unknown',\n canAskAgain: true,\n });\n}\n\nasync function requestGeolocation(\n permission: Permission,\n global: WebGlobalLike,\n): Promise<PermissionState> {\n if (global.isSecureContext === false) {\n return createPermissionState({\n permission,\n status: 'blocked',\n canAskAgain: false,\n reason: 'Geolocation requires a secure context.',\n });\n }\n\n if (global.navigator?.geolocation === undefined) {\n return unavailable(permission, 'Geolocation API is unavailable.');\n }\n\n return new Promise((resolve) => {\n global.navigator?.geolocation?.getCurrentPosition(\n () => {\n resolve(\n createPermissionState({\n permission,\n status: 'granted',\n canAskAgain: true,\n requestedAt: new Date(),\n }),\n );\n },\n () => {\n resolve(\n createPermissionState({\n permission,\n status: 'denied',\n canAskAgain: false,\n requestedAt: new Date(),\n }),\n );\n },\n { maximumAge: 0 },\n );\n });\n}\n\nasync function requestMedia(\n permission: Permission,\n global: WebGlobalLike,\n constraints: {\n readonly audio?: boolean;\n readonly video?: boolean;\n },\n): Promise<PermissionState> {\n const availability = getMediaAvailability(permission, global);\n\n if (availability.status !== 'unknown') {\n return availability;\n }\n\n try {\n const stream = await global.navigator?.mediaDevices?.getUserMedia(constraints);\n\n for (const track of stream?.getTracks() ?? []) {\n track.stop();\n }\n\n return createPermissionState({\n permission,\n status: 'granted',\n canAskAgain: true,\n requestedAt: new Date(),\n });\n } catch (error) {\n return stateFromMediaError(permission, error);\n }\n}\n\nfunction stateFromMediaError(permission: Permission, error: unknown): PermissionState {\n const errorName = getErrorName(error);\n const unavailableErrorNames = new Set([\n 'AbortError',\n 'NotFoundError',\n 'NotReadableError',\n 'OverconstrainedError',\n 'SecurityError',\n ]);\n\n if (errorName !== undefined && unavailableErrorNames.has(errorName)) {\n return createPermissionState({\n permission,\n status: 'unavailable',\n canAskAgain: false,\n requestedAt: new Date(),\n reason: `Media permission request failed: ${errorName}.`,\n });\n }\n\n return createPermissionState({\n permission,\n status: 'denied',\n canAskAgain: false,\n requestedAt: new Date(),\n reason:\n errorName === undefined\n ? 'Media permission request was denied.'\n : `Media permission request was denied: ${errorName}.`,\n });\n}\n\nfunction stateFromNotificationPermission(\n permission: Permission,\n status: NotificationPermissionState,\n): PermissionState {\n if (status === 'granted') {\n return createPermissionState({\n permission,\n status: 'granted',\n canAskAgain: true,\n });\n }\n\n if (status === 'denied') {\n return createPermissionState({\n permission,\n status: 'denied',\n canAskAgain: false,\n });\n }\n\n return createPermissionState({\n permission,\n status: 'unknown',\n canAskAgain: true,\n });\n}\n\nfunction stateFromBrowserPermission(\n permission: Permission,\n status: BrowserPermissionState,\n): PermissionState {\n const normalizedStatus: PermissionStatus = status === 'prompt' ? 'unknown' : status;\n\n return createPermissionState({\n permission,\n status: normalizedStatus,\n canAskAgain: status === 'prompt' || status === 'granted',\n });\n}\n\nfunction unavailable(permission: Permission, reason: string): PermissionState {\n return createPermissionState({\n permission,\n status: 'unavailable',\n canAskAgain: false,\n reason,\n });\n}\n\nfunction formatErrorName(error: unknown): string {\n const errorName = getErrorName(error);\n\n return errorName === undefined ? '' : `: ${errorName}`;\n}\n\nfunction getErrorName(error: unknown): string | undefined {\n if (typeof error !== 'object' || error === null || !('name' in error)) {\n return undefined;\n }\n\n return typeof error.name === 'string' && error.name.length > 0 ? error.name : undefined;\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/web/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { PermissionClient } from '../client/types';\nimport { assertKnownPermission, Permission } from '../registry/permissions';\nimport { createPermissionState, type PermissionState } from '../state/permissionState';\nimport {\n type BrowserPermissionState,\n formatErrorName,\n type NotificationPermissionState,\n stateFromBrowserPermission,\n stateFromMediaError,\n stateFromNotificationPermission,\n unavailable,\n} from './permissionStates';\n\ntype BrowserPermissionName = 'camera' | 'microphone' | 'geolocation' | 'notifications';\ninterface BrowserPermissionStatus {\n readonly state: BrowserPermissionState;\n}\n\ninterface BrowserPermissions {\n query(descriptor: { readonly name: BrowserPermissionName }): Promise<BrowserPermissionStatus>;\n}\n\ninterface MediaStreamTrackLike {\n stop(): void;\n}\n\ninterface MediaStreamLike {\n getTracks(): readonly MediaStreamTrackLike[];\n}\n\ninterface MediaDevicesLike {\n getUserMedia(constraints: {\n readonly audio?: boolean;\n readonly video?: boolean;\n }): Promise<MediaStreamLike>;\n}\n\ninterface GeolocationLike {\n getCurrentPosition(\n success: () => void,\n error: () => void,\n options?: {\n readonly maximumAge?: number;\n readonly timeout?: number;\n },\n ): void;\n}\n\ninterface NavigatorLike {\n readonly permissions?: BrowserPermissions;\n readonly mediaDevices?: MediaDevicesLike;\n readonly geolocation?: GeolocationLike;\n}\n\ninterface NotificationLike {\n readonly permission: NotificationPermissionState;\n requestPermission(): Promise<NotificationPermissionState> | NotificationPermissionState;\n}\n\ninterface WebGlobalLike {\n readonly isSecureContext?: boolean;\n readonly navigator?: NavigatorLike;\n readonly Notification?: NotificationLike;\n}\n\n/***\n * Options for the browser permission adapter.\n */\nexport interface WebPermissionClientOptions {\n readonly global?: WebGlobalLike;\n}\n\n/***\n * Creates a browser permission client using guarded structural globals.\n *\n * @readme\n * Unsupported web APIs resolve to `status: 'unavailable'`. The adapter does not\n * import DOM types and does not assume it is running in a browser.\n */\nexport function createWebPermissionClient(\n options: WebPermissionClientOptions = {},\n): PermissionClient {\n const global = options.global ?? getDefaultGlobal();\n\n return {\n async getStatus(permission) {\n const knownPermission = assertKnownPermission(permission);\n\n switch (knownPermission) {\n case Permission.Notifications:\n return getNotificationStatus(knownPermission, global);\n case Permission.LocationForeground:\n return getPermissionApiStatus(\n knownPermission,\n global,\n 'geolocation',\n 'Geolocation is unavailable in this environment.',\n );\n case Permission.Camera:\n return getMediaStatus(knownPermission, global, 'camera');\n case Permission.Microphone:\n return getMediaStatus(knownPermission, global, 'microphone');\n default:\n return unavailable(\n knownPermission,\n 'This permission is not supported by the web adapter.',\n );\n }\n },\n\n async request(permission) {\n const knownPermission = assertKnownPermission(permission);\n\n switch (knownPermission) {\n case Permission.Notifications:\n return requestNotifications(knownPermission, global);\n case Permission.LocationForeground:\n return requestGeolocation(knownPermission, global);\n case Permission.Camera:\n return requestMedia(knownPermission, global, { video: true });\n case Permission.Microphone:\n return requestMedia(knownPermission, global, { audio: true });\n default:\n return unavailable(\n knownPermission,\n 'This permission is not supported by the web adapter.',\n );\n }\n },\n };\n}\n\nfunction getDefaultGlobal(): WebGlobalLike {\n return globalThis as WebGlobalLike;\n}\n\nfunction getNotificationStatus(permission: Permission, global: WebGlobalLike): PermissionState {\n if (global.Notification === undefined) {\n return unavailable(permission, 'Notification API is unavailable.');\n }\n\n return stateFromNotificationPermission(permission, global.Notification.permission);\n}\n\nasync function requestNotifications(\n permission: Permission,\n global: WebGlobalLike,\n): Promise<PermissionState> {\n if (global.Notification === undefined) {\n return unavailable(permission, 'Notification API is unavailable.');\n }\n\n try {\n const result = await global.Notification.requestPermission();\n\n return stateFromNotificationPermission(permission, result);\n } catch (error) {\n return unavailable(\n permission,\n `Notification permission request failed${formatErrorName(error)}.`,\n );\n }\n}\n\nasync function getPermissionApiStatus(\n permission: Permission,\n global: WebGlobalLike,\n name: BrowserPermissionName,\n unavailableReason: string,\n): Promise<PermissionState> {\n if (global.navigator?.permissions === undefined) {\n return unavailable(permission, unavailableReason);\n }\n\n try {\n const status = await global.navigator.permissions.query({ name });\n\n return stateFromBrowserPermission(permission, status.state);\n } catch {\n return unavailable(permission, unavailableReason);\n }\n}\n\nasync function getMediaStatus(\n permission: Permission,\n global: WebGlobalLike,\n name: BrowserPermissionName,\n): Promise<PermissionState> {\n if (global.navigator?.permissions !== undefined) {\n try {\n const status = await global.navigator.permissions.query({ name });\n\n return stateFromBrowserPermission(permission, status.state);\n } catch {\n return getMediaAvailability(permission, global);\n }\n }\n\n return getMediaAvailability(permission, global);\n}\n\nfunction getMediaAvailability(permission: Permission, global: WebGlobalLike): PermissionState {\n if (global.isSecureContext === false) {\n return createPermissionState({\n permission,\n status: 'blocked',\n canAskAgain: false,\n reason: 'Media permissions require a secure context.',\n });\n }\n\n if (global.navigator?.mediaDevices?.getUserMedia === undefined) {\n return unavailable(permission, 'Media devices API is unavailable.');\n }\n\n return createPermissionState({\n permission,\n status: 'unknown',\n canAskAgain: true,\n });\n}\n\nasync function requestGeolocation(\n permission: Permission,\n global: WebGlobalLike,\n): Promise<PermissionState> {\n if (global.isSecureContext === false) {\n return createPermissionState({\n permission,\n status: 'blocked',\n canAskAgain: false,\n reason: 'Geolocation requires a secure context.',\n });\n }\n\n if (global.navigator?.geolocation === undefined) {\n return unavailable(permission, 'Geolocation API is unavailable.');\n }\n\n return new Promise((resolve) => {\n global.navigator?.geolocation?.getCurrentPosition(\n () => {\n resolve(\n createPermissionState({\n permission,\n status: 'granted',\n canAskAgain: true,\n requestedAt: new Date(),\n }),\n );\n },\n () => {\n resolve(\n createPermissionState({\n permission,\n status: 'denied',\n canAskAgain: false,\n requestedAt: new Date(),\n }),\n );\n },\n { maximumAge: 0 },\n );\n });\n}\n\nasync function requestMedia(\n permission: Permission,\n global: WebGlobalLike,\n constraints: {\n readonly audio?: boolean;\n readonly video?: boolean;\n },\n): Promise<PermissionState> {\n const availability = getMediaAvailability(permission, global);\n\n if (availability.status !== 'unknown') {\n return availability;\n }\n\n try {\n const stream = await global.navigator?.mediaDevices?.getUserMedia(constraints);\n\n for (const track of stream?.getTracks() ?? []) {\n track.stop();\n }\n\n return createPermissionState({\n permission,\n status: 'granted',\n canAskAgain: true,\n requestedAt: new Date(),\n });\n } catch (error) {\n return stateFromMediaError(permission, error);\n }\n}\n"]}
@@ -0,0 +1,10 @@
1
+ import type { Permission } from '../registry/permissions';
2
+ import { type PermissionState } from '../state/permissionState';
3
+ export type BrowserPermissionState = 'granted' | 'denied' | 'prompt';
4
+ export type NotificationPermissionState = 'default' | 'denied' | 'granted';
5
+ export declare function stateFromMediaError(permission: Permission, error: unknown): PermissionState;
6
+ export declare function stateFromNotificationPermission(permission: Permission, status: NotificationPermissionState): PermissionState;
7
+ export declare function stateFromBrowserPermission(permission: Permission, status: BrowserPermissionState): PermissionState;
8
+ export declare function unavailable(permission: Permission, reason: string): PermissionState;
9
+ export declare function formatErrorName(error: unknown): string;
10
+ //# sourceMappingURL=permissionStates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissionStates.d.ts","sourceRoot":"","sources":["../../src/web/permissionStates.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAEL,KAAK,eAAe,EAErB,MAAM,0BAA0B,CAAC;AAElC,MAAM,MAAM,sBAAsB,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAC;AACrE,MAAM,MAAM,2BAA2B,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE3E,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,GAAG,eAAe,CA4B3F;AAED,wBAAgB,+BAA+B,CAC7C,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,2BAA2B,GAClC,eAAe,CAQjB;AAED,wBAAgB,0BAA0B,CACxC,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,sBAAsB,GAC7B,eAAe,CAOjB;AAED,wBAAgB,WAAW,CAAC,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,eAAe,CAOnF;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAGtD"}
@@ -0,0 +1,64 @@
1
+ import { createPermissionState, } from '../state/permissionState';
2
+ export function stateFromMediaError(permission, error) {
3
+ const errorName = getErrorName(error);
4
+ const unavailableErrorNames = new Set([
5
+ 'AbortError',
6
+ 'NotFoundError',
7
+ 'NotReadableError',
8
+ 'OverconstrainedError',
9
+ 'SecurityError',
10
+ ]);
11
+ if (errorName !== undefined && unavailableErrorNames.has(errorName)) {
12
+ return createPermissionState({
13
+ permission,
14
+ status: 'unavailable',
15
+ canAskAgain: false,
16
+ requestedAt: new Date(),
17
+ reason: `Media permission request failed: ${errorName}.`,
18
+ });
19
+ }
20
+ return createPermissionState({
21
+ permission,
22
+ status: 'denied',
23
+ canAskAgain: false,
24
+ requestedAt: new Date(),
25
+ reason: errorName === undefined
26
+ ? 'Media permission request was denied.'
27
+ : `Media permission request was denied: ${errorName}.`,
28
+ });
29
+ }
30
+ export function stateFromNotificationPermission(permission, status) {
31
+ if (status === 'granted') {
32
+ return createPermissionState({ permission, status: 'granted', canAskAgain: true });
33
+ }
34
+ if (status === 'denied') {
35
+ return createPermissionState({ permission, status: 'denied', canAskAgain: false });
36
+ }
37
+ return createPermissionState({ permission, status: 'unknown', canAskAgain: true });
38
+ }
39
+ export function stateFromBrowserPermission(permission, status) {
40
+ const normalizedStatus = status === 'prompt' ? 'unknown' : status;
41
+ return createPermissionState({
42
+ permission,
43
+ status: normalizedStatus,
44
+ canAskAgain: status === 'prompt' || status === 'granted',
45
+ });
46
+ }
47
+ export function unavailable(permission, reason) {
48
+ return createPermissionState({
49
+ permission,
50
+ status: 'unavailable',
51
+ canAskAgain: false,
52
+ reason,
53
+ });
54
+ }
55
+ export function formatErrorName(error) {
56
+ const errorName = getErrorName(error);
57
+ return errorName === undefined ? '' : `: ${errorName}`;
58
+ }
59
+ function getErrorName(error) {
60
+ if (typeof error !== 'object' || error === null || !('name' in error))
61
+ return undefined;
62
+ return typeof error.name === 'string' && error.name.length > 0 ? error.name : undefined;
63
+ }
64
+ //# sourceMappingURL=permissionStates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissionStates.js","sourceRoot":"","sources":["../../src/web/permissionStates.ts"],"names":[],"mappings":"AACA,OAAO,EACL,qBAAqB,GAGtB,MAAM,0BAA0B,CAAC;AAKlC,MAAM,UAAU,mBAAmB,CAAC,UAAsB,EAAE,KAAc;IACxE,MAAM,SAAS,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;IACtC,MAAM,qBAAqB,GAAG,IAAI,GAAG,CAAC;QACpC,YAAY;QACZ,eAAe;QACf,kBAAkB;QAClB,sBAAsB;QACtB,eAAe;KAChB,CAAC,CAAC;IACH,IAAI,SAAS,KAAK,SAAS,IAAI,qBAAqB,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;QACpE,OAAO,qBAAqB,CAAC;YAC3B,UAAU;YACV,MAAM,EAAE,aAAa;YACrB,WAAW,EAAE,KAAK;YAClB,WAAW,EAAE,IAAI,IAAI,EAAE;YACvB,MAAM,EAAE,oCAAoC,SAAS,GAAG;SACzD,CAAC,CAAC;IACL,CAAC;IACD,OAAO,qBAAqB,CAAC;QAC3B,UAAU;QACV,MAAM,EAAE,QAAQ;QAChB,WAAW,EAAE,KAAK;QAClB,WAAW,EAAE,IAAI,IAAI,EAAE;QACvB,MAAM,EACJ,SAAS,KAAK,SAAS;YACrB,CAAC,CAAC,sCAAsC;YACxC,CAAC,CAAC,wCAAwC,SAAS,GAAG;KAC3D,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,+BAA+B,CAC7C,UAAsB,EACtB,MAAmC;IAEnC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,qBAAqB,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;IACrF,CAAC;IACD,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;QACxB,OAAO,qBAAqB,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC;IACrF,CAAC;IACD,OAAO,qBAAqB,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;AACrF,CAAC;AAED,MAAM,UAAU,0BAA0B,CACxC,UAAsB,EACtB,MAA8B;IAE9B,MAAM,gBAAgB,GAAqB,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC;IACpF,OAAO,qBAAqB,CAAC;QAC3B,UAAU;QACV,MAAM,EAAE,gBAAgB;QACxB,WAAW,EAAE,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,SAAS;KACzD,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,UAAsB,EAAE,MAAc;IAChE,OAAO,qBAAqB,CAAC;QAC3B,UAAU;QACV,MAAM,EAAE,aAAa;QACrB,WAAW,EAAE,KAAK;QAClB,MAAM;KACP,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAc;IAC5C,MAAM,SAAS,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;IACtC,OAAO,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,SAAS,EAAE,CAAC;AACzD,CAAC;AAED,SAAS,YAAY,CAAC,KAAc;IAClC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACxF,OAAO,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AAC1F,CAAC","sourcesContent":["import type { Permission } from '../registry/permissions';\nimport {\n createPermissionState,\n type PermissionState,\n type PermissionStatus,\n} from '../state/permissionState';\n\nexport type BrowserPermissionState = 'granted' | 'denied' | 'prompt';\nexport type NotificationPermissionState = 'default' | 'denied' | 'granted';\n\nexport function stateFromMediaError(permission: Permission, error: unknown): PermissionState {\n const errorName = getErrorName(error);\n const unavailableErrorNames = new Set([\n 'AbortError',\n 'NotFoundError',\n 'NotReadableError',\n 'OverconstrainedError',\n 'SecurityError',\n ]);\n if (errorName !== undefined && unavailableErrorNames.has(errorName)) {\n return createPermissionState({\n permission,\n status: 'unavailable',\n canAskAgain: false,\n requestedAt: new Date(),\n reason: `Media permission request failed: ${errorName}.`,\n });\n }\n return createPermissionState({\n permission,\n status: 'denied',\n canAskAgain: false,\n requestedAt: new Date(),\n reason:\n errorName === undefined\n ? 'Media permission request was denied.'\n : `Media permission request was denied: ${errorName}.`,\n });\n}\n\nexport function stateFromNotificationPermission(\n permission: Permission,\n status: NotificationPermissionState,\n): PermissionState {\n if (status === 'granted') {\n return createPermissionState({ permission, status: 'granted', canAskAgain: true });\n }\n if (status === 'denied') {\n return createPermissionState({ permission, status: 'denied', canAskAgain: false });\n }\n return createPermissionState({ permission, status: 'unknown', canAskAgain: true });\n}\n\nexport function stateFromBrowserPermission(\n permission: Permission,\n status: BrowserPermissionState,\n): PermissionState {\n const normalizedStatus: PermissionStatus = status === 'prompt' ? 'unknown' : status;\n return createPermissionState({\n permission,\n status: normalizedStatus,\n canAskAgain: status === 'prompt' || status === 'granted',\n });\n}\n\nexport function unavailable(permission: Permission, reason: string): PermissionState {\n return createPermissionState({\n permission,\n status: 'unavailable',\n canAskAgain: false,\n reason,\n });\n}\n\nexport function formatErrorName(error: unknown): string {\n const errorName = getErrorName(error);\n return errorName === undefined ? '' : `: ${errorName}`;\n}\n\nfunction getErrorName(error: unknown): string | undefined {\n if (typeof error !== 'object' || error === null || !('name' in error)) return undefined;\n return typeof error.name === 'string' && error.name.length > 0 ? error.name : undefined;\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ankhorage/permissions",
3
3
  "type": "module",
4
- "version": "0.2.2",
4
+ "version": "0.2.4",
5
5
  "description": "Cross-platform permission registry and runtime helpers for Expo, React Native, and React Native Web apps, with unified request/check APIs for camera, media, location, notifications, microphone, and other web/native capabilities.",
6
6
  "homepage": "https://github.com/ankhorage/permissions#readme",
7
7
  "bugs": {
@@ -39,8 +39,13 @@
39
39
  },
40
40
  "ankh": {
41
41
  "category": "permissions",
42
- "provider": null,
43
- "capabilities": []
42
+ "provider": "./dist/cli/index.js",
43
+ "capabilities": [
44
+ "permissions.inspect",
45
+ "permissions.check",
46
+ "permissions.request",
47
+ "permissions.manifest"
48
+ ]
44
49
  },
45
50
  "files": [
46
51
  "dist",
@@ -58,6 +63,11 @@
58
63
  "import": "./dist/index.js",
59
64
  "default": "./dist/index.js"
60
65
  },
66
+ "./cli": {
67
+ "types": "./dist/cli/index.d.ts",
68
+ "import": "./dist/cli/index.js",
69
+ "default": "./dist/cli/index.js"
70
+ },
61
71
  "./react": {
62
72
  "react-native": "./dist/react/index.js",
63
73
  "browser": "./dist/react/index.js",
@@ -103,11 +113,10 @@
103
113
  "access": "public"
104
114
  },
105
115
  "scripts": {
106
- "build": "rm -rf dist tsconfig.tsbuildinfo && bun x tsc -p tsconfig.json",
107
- "changeset": "changeset",
108
- "changeset:status": "changeset status --since=origin/main",
116
+ "build": "rm -rf dist tsconfig.tsbuildinfo tsconfig.build.tsbuildinfo && bun x tsc -p tsconfig.build.json",
117
+ "changeset": "ankhorage-changeset",
118
+ "changeset:status": "ankhorage-changeset status --since=origin/main",
109
119
  "docs": "paradox",
110
- "knip": "ankhorage-knip",
111
120
  "lint": "ankhorage-eslint . --max-warnings=0",
112
121
  "lint:fix": "ankhorage-eslint . --fix --max-warnings=0",
113
122
  "format": "ankhorage-prettier --write .",
@@ -115,17 +124,17 @@
115
124
  "prepack": "bun run build",
116
125
  "test": "bun test src",
117
126
  "typecheck": "bun x tsc --noEmit -p tsconfig.json",
118
- "version-packages": "changeset version"
127
+ "version-packages": "ankhorage-changeset version",
128
+ "knip:check": "ankhorage-knip"
119
129
  },
120
130
  "devDependencies": {
121
- "@ankhorage/devtools": "^1.0.6",
122
- "@ankhorage/paradox": "^0.1.10",
123
- "@changesets/cli": "^2.31.0",
124
- "@types/bun": "^1.3.13",
131
+ "@ankhorage/devtools": "^1.9.2",
132
+ "@ankhorage/paradox": "^0.1.24",
133
+ "@types/bun": "^1.4.0",
125
134
  "@types/node": "^25.6.0",
126
135
  "@types/react": "^19.2.7",
127
136
  "react": "^19.2.1",
128
137
  "typescript": "^5.9.3"
129
138
  },
130
- "packageManager": "bun@1.3.13"
139
+ "packageManager": "bun@1.4.0"
131
140
  }
@@ -0,0 +1 @@
1
+ export { default } from '../ankh.provider.js';
@@ -1,8 +1,16 @@
1
1
  import type { Permission } from '../../registry/permissions';
2
2
  import { Permission as KnownPermission } from '../../registry/permissions';
3
- import type { PermissionState } from '../../state/permissionState';
3
+ import { createPermissionState, type PermissionState } from '../../state/permissionState';
4
4
  import type { ExpoPermissionAdapter } from '../client';
5
- import { createExpoPermissionState, createExpoUnavailableState } from '../permissionResponse';
5
+ import {
6
+ createExpoPermissionState,
7
+ createExpoUnavailableState,
8
+ type ExpoPermissionResponse,
9
+ } from '../permissionResponse';
10
+
11
+ interface ExpoMediaLibraryPermissionResponse extends ExpoPermissionResponse {
12
+ readonly accessPrivileges?: 'all' | 'limited' | 'none';
13
+ }
6
14
 
7
15
  export const mediaLibraryAdapter: ExpoPermissionAdapter = {
8
16
  async getStatus(permission: Permission): Promise<PermissionState> {
@@ -10,7 +18,7 @@ export const mediaLibraryAdapter: ExpoPermissionAdapter = {
10
18
  const mod = await import('expo-media-library');
11
19
  const result = await mod.getPermissionsAsync(isWriteOnlyPermission(permission));
12
20
 
13
- return createExpoPermissionState(permission, result);
21
+ return createMediaLibraryPermissionState(permission, result);
14
22
  } catch (error) {
15
23
  return createExpoUnavailableState(permission, error);
16
24
  }
@@ -21,13 +29,29 @@ export const mediaLibraryAdapter: ExpoPermissionAdapter = {
21
29
  const mod = await import('expo-media-library');
22
30
  const result = await mod.requestPermissionsAsync(isWriteOnlyPermission(permission));
23
31
 
24
- return createExpoPermissionState(permission, result);
32
+ return createMediaLibraryPermissionState(permission, result);
25
33
  } catch (error) {
26
34
  return createExpoUnavailableState(permission, error);
27
35
  }
28
36
  },
29
37
  };
30
38
 
39
+ function createMediaLibraryPermissionState(
40
+ permission: Permission,
41
+ response: ExpoMediaLibraryPermissionResponse,
42
+ ): PermissionState {
43
+ if (response.accessPrivileges !== 'limited') {
44
+ return createExpoPermissionState(permission, response);
45
+ }
46
+
47
+ return createPermissionState({
48
+ permission,
49
+ status: 'limited',
50
+ canAskAgain: response.canAskAgain,
51
+ reason: 'Media library access is limited to user-selected assets.',
52
+ });
53
+ }
54
+
31
55
  function isWriteOnlyPermission(permission: Permission): boolean {
32
56
  return permission === KnownPermission.MediaLibraryWrite;
33
57
  }
@@ -1,7 +1,17 @@
1
1
  import type { Permission } from '../../registry/permissions';
2
- import type { PermissionState } from '../../state/permissionState';
2
+ import { createPermissionState, type PermissionState } from '../../state/permissionState';
3
3
  import type { ExpoPermissionAdapter } from '../client';
4
- import { createExpoPermissionState, createExpoUnavailableState } from '../permissionResponse';
4
+ import {
5
+ createExpoPermissionState,
6
+ createExpoUnavailableState,
7
+ type ExpoPermissionResponse,
8
+ } from '../permissionResponse';
9
+
10
+ interface ExpoNotificationPermissionResponse extends ExpoPermissionResponse {
11
+ readonly ios?: {
12
+ readonly status?: number;
13
+ };
14
+ }
5
15
 
6
16
  export const notificationsAdapter: ExpoPermissionAdapter = {
7
17
  async getStatus(permission: Permission): Promise<PermissionState> {
@@ -9,7 +19,7 @@ export const notificationsAdapter: ExpoPermissionAdapter = {
9
19
  const mod = await import('expo-notifications');
10
20
  const result = await mod.getPermissionsAsync();
11
21
 
12
- return createExpoPermissionState(permission, result);
22
+ return createNotificationPermissionState(permission, result);
13
23
  } catch (error) {
14
24
  return createExpoUnavailableState(permission, error);
15
25
  }
@@ -20,9 +30,68 @@ export const notificationsAdapter: ExpoPermissionAdapter = {
20
30
  const mod = await import('expo-notifications');
21
31
  const result = await mod.requestPermissionsAsync();
22
32
 
23
- return createExpoPermissionState(permission, result);
33
+ return createNotificationPermissionState(permission, result);
24
34
  } catch (error) {
25
35
  return createExpoUnavailableState(permission, error);
26
36
  }
27
37
  },
28
38
  };
39
+
40
+ function createNotificationPermissionState(
41
+ permission: Permission,
42
+ response: ExpoNotificationPermissionResponse,
43
+ ): PermissionState {
44
+ switch (response.ios?.status) {
45
+ case 0:
46
+ return createNotificationState(
47
+ permission,
48
+ response,
49
+ 'unknown',
50
+ 'iOS notification authorization has not been determined.',
51
+ );
52
+ case 1:
53
+ return createNotificationState(
54
+ permission,
55
+ response,
56
+ 'denied',
57
+ 'iOS notification authorization is denied.',
58
+ );
59
+ case 2:
60
+ return createNotificationState(permission, response, 'granted');
61
+ case 3:
62
+ return createNotificationState(
63
+ permission,
64
+ response,
65
+ 'limited',
66
+ 'iOS notification authorization is provisional.',
67
+ );
68
+ case 4:
69
+ return createNotificationState(
70
+ permission,
71
+ response,
72
+ 'limited',
73
+ 'iOS notification authorization is ephemeral.',
74
+ );
75
+ default:
76
+ return createExpoPermissionState(permission, response);
77
+ }
78
+ }
79
+
80
+ function createNotificationState(
81
+ permission: Permission,
82
+ response: ExpoNotificationPermissionResponse,
83
+ status: PermissionState['status'],
84
+ reason?: string,
85
+ ): PermissionState {
86
+ const state = createExpoPermissionState(permission, {
87
+ status,
88
+ canAskAgain: response.canAskAgain,
89
+ });
90
+
91
+ return createPermissionState({
92
+ permission,
93
+ status: state.status,
94
+ canAskAgain: state.canAskAgain,
95
+ reason: state.reason ?? reason,
96
+ });
97
+ }
@@ -0,0 +1,106 @@
1
+ import { beforeEach, expect, test } from 'bun:test';
2
+
3
+ import { Permission } from '../registry/permissions';
4
+ import { createPermissionClient } from './client';
5
+ import {
6
+ audioMock,
7
+ cameraMock,
8
+ linkingMock,
9
+ type MockPermissionResponse,
10
+ notificationsMock,
11
+ resetExpoMocks,
12
+ } from './client.testSupport';
13
+
14
+ const IOS_AUTHORIZATION_CASES = [
15
+ [0, true, 'unknown', false, 'iOS notification authorization has not been determined.'],
16
+ [1, true, 'denied', false, 'iOS notification authorization is denied.'],
17
+ [1, false, 'blocked', false, 'Permission is blocked. Open system settings to change it.'],
18
+ [2, false, 'granted', true, undefined],
19
+ [3, false, 'limited', true, 'iOS notification authorization is provisional.'],
20
+ [4, false, 'limited', true, 'iOS notification authorization is ephemeral.'],
21
+ ] as const;
22
+
23
+ beforeEach(resetExpoMocks);
24
+
25
+ test('Expo client uses granular iOS notification authorization', async () => {
26
+ const client = createPermissionClient();
27
+ for (const [iosStatus, canAskAgain, status, granted, reason] of IOS_AUTHORIZATION_CASES) {
28
+ const response: MockPermissionResponse = {
29
+ status: iosStatus === 2 ? 'denied' : 'granted',
30
+ canAskAgain,
31
+ ios: { status: iosStatus },
32
+ };
33
+ notificationsMock.getResponse = response;
34
+ notificationsMock.requestResponse = response;
35
+ for (const state of [
36
+ await client.getStatus(Permission.Notifications),
37
+ await client.request(Permission.Notifications),
38
+ ]) {
39
+ expect(state).toMatchObject({
40
+ permission: Permission.Notifications,
41
+ status,
42
+ granted,
43
+ canAskAgain,
44
+ reason,
45
+ });
46
+ }
47
+ }
48
+ });
49
+
50
+ test('Expo client opens native application settings', async () => {
51
+ const client = createPermissionClient();
52
+ if (client.openSettings === undefined) throw new Error('Expected openSettings.');
53
+ await client.openSettings();
54
+ expect(linkingMock.calls).toBe(1);
55
+ });
56
+
57
+ test('Expo client preserves the native settings failure cause', async () => {
58
+ const cause = new Error('native settings unavailable');
59
+ linkingMock.error = cause;
60
+ const error = await captureOpenSettingsErrorAsync();
61
+ expect(error.message).toBe('Unable to open application settings.');
62
+ expect(error.cause).toBe(cause);
63
+ });
64
+
65
+ test('Expo client uses the stable settings error on web', async () => {
66
+ const cause = new Error('openSettings is unavailable on web');
67
+ cause.name = 'UnavailabilityError';
68
+ linkingMock.error = cause;
69
+ const error = await captureOpenSettingsErrorAsync();
70
+ expect(error.message).toBe('Unable to open application settings.');
71
+ expect(error.cause).toBe(cause);
72
+ });
73
+
74
+ test('Expo client returns unavailable when a camera call fails', async () => {
75
+ cameraMock.getError = 'camera bridge unavailable';
76
+ const state = await createPermissionClient().getStatus(Permission.Camera);
77
+ expect(state).toMatchObject({
78
+ permission: Permission.Camera,
79
+ status: 'unavailable',
80
+ granted: false,
81
+ reason: 'camera bridge unavailable',
82
+ });
83
+ });
84
+
85
+ test('Expo client returns unavailable when an audio request fails', async () => {
86
+ audioMock.requestError = 'microphone bridge unavailable';
87
+ const state = await createPermissionClient().request(Permission.Microphone);
88
+ expect(state).toMatchObject({
89
+ permission: Permission.Microphone,
90
+ status: 'unavailable',
91
+ granted: false,
92
+ reason: 'microphone bridge unavailable',
93
+ });
94
+ });
95
+
96
+ async function captureOpenSettingsErrorAsync(): Promise<Error> {
97
+ const client = createPermissionClient();
98
+ if (client.openSettings === undefined) throw new Error('Expected openSettings.');
99
+ try {
100
+ await client.openSettings();
101
+ } catch (error) {
102
+ if (error instanceof Error) return error;
103
+ throw new Error('Opening settings must reject with an Error.', { cause: error });
104
+ }
105
+ throw new Error('Opening settings must reject.');
106
+ }