@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
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @ankhorage/permissions
2
2
 
3
+ ## 0.2.4
4
+
5
+ ### Patch Changes
6
+
7
+ - f4b561b: Adopt the current Devtools policy and keep permission clients compatible with strict React and security linting.
8
+
9
+ ## 0.2.3
10
+
11
+ ### Patch Changes
12
+
13
+ - 8ee977d: Normalize blocked and partial Expo authorization states, project the Expo client dependency,
14
+ and add settings recovery with a stable failure contract.
15
+
3
16
  ## 0.2.2
4
17
 
5
18
  ### Patch Changes
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  # PERMISSIONS
5
5
 
6
- ![license: MIT](././paradox/badges/license.svg) ![npm: v0.2.0](././paradox/badges/npm.svg) ![runtime: bun](././paradox/badges/runtime.svg) ![typescript: strict](././paradox/badges/typescript.svg) ![eslint: checked](././paradox/badges/eslint.svg) ![prettier: checked](././paradox/badges/prettier.svg) ![build: checked](././paradox/badges/build.svg) ![tests: checked](././paradox/badges/tests.svg) ![docs: paradox](././paradox/badges/docs.svg)
6
+ ![license: MIT](././paradox/badges/license.svg) ![npm: v0.2.3](././paradox/badges/npm.svg) ![runtime: bun](././paradox/badges/runtime.svg) ![typescript: strict](././paradox/badges/typescript.svg) ![eslint: checked](././paradox/badges/eslint.svg) ![prettier: checked](././paradox/badges/prettier.svg) ![build: checked](././paradox/badges/build.svg) ![tests: checked](././paradox/badges/tests.svg) ![docs: paradox](././paradox/badges/docs.svg)
7
7
 
8
8
  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.
9
9
 
@@ -132,11 +132,35 @@ Fake clients make permission flows testable without native devices, browser
132
132
  prompts, simulators, or network access.
133
133
 
134
134
  Module: `src/testing/index.ts`
135
- Source: `src/testing/index.ts:55:1`
135
+ Source: `src/testing/index.ts:61:1`
136
136
  Related symbols: `FakePermissionClient`, `FakePermissionClientOptions`
137
137
 
138
138
  </details>
139
139
 
140
+ <details>
141
+ <summary>createPermissionClient</summary>
142
+
143
+ ```ts
144
+ createPermissionClient() => PermissionClient
145
+ ```
146
+
147
+ Creates a permission client backed by Expo SDK modules.
148
+
149
+ Denials that Expo cannot request again are normalized to `blocked`. Limited
150
+ media-library access and provisional or ephemeral iOS notification access
151
+ are normalized to usable `limited` states, where `granted` remains true.
152
+ The client requires `expo-linking` for settings recovery and rejects with a
153
+ stable error when the operating-system settings page cannot be opened.
154
+
155
+ Expo modules are loaded only when a client operation uses them. The package
156
+ root and Expo manifest metadata remain free of native module imports.
157
+
158
+ Module: `src/expo/client.ts`
159
+ Source: `src/expo/client.ts:42:1`
160
+ Related symbols: `PermissionClient`
161
+
162
+ </details>
163
+
140
164
  <details>
141
165
  <summary>createPermissionManager</summary>
142
166
 
@@ -168,7 +192,7 @@ Unsupported web APIs resolve to `status: 'unavailable'`. The adapter does not
168
192
  import DOM types and does not assume it is running in a browser.
169
193
 
170
194
  Module: `src/web/index.ts`
171
- Source: `src/web/index.ts:78:1`
195
+ Source: `src/web/index.ts:80:1`
172
196
  Related symbols: `PermissionClient`, `WebPermissionClientOptions`
173
197
 
174
198
  </details>
@@ -0,0 +1,2 @@
1
+ export { default } from '../ankh.provider.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { default } from '../ankh.provider.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC","sourcesContent":["export { default } from '../ankh.provider.js';\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"mediaLibrary.d.ts","sourceRoot":"","sources":["../../../src/expo/adapters/mediaLibrary.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAGvD,eAAO,MAAM,mBAAmB,EAAE,qBAsBjC,CAAC"}
1
+ {"version":3,"file":"mediaLibrary.d.ts","sourceRoot":"","sources":["../../../src/expo/adapters/mediaLibrary.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAWvD,eAAO,MAAM,mBAAmB,EAAE,qBAsBjC,CAAC"}
@@ -1,11 +1,12 @@
1
1
  import { Permission as KnownPermission } from '../../registry/permissions';
2
- import { createExpoPermissionState, createExpoUnavailableState } from '../permissionResponse';
2
+ import { createPermissionState } from '../../state/permissionState';
3
+ import { createExpoPermissionState, createExpoUnavailableState, } from '../permissionResponse';
3
4
  export const mediaLibraryAdapter = {
4
5
  async getStatus(permission) {
5
6
  try {
6
7
  const mod = await import('expo-media-library');
7
8
  const result = await mod.getPermissionsAsync(isWriteOnlyPermission(permission));
8
- return createExpoPermissionState(permission, result);
9
+ return createMediaLibraryPermissionState(permission, result);
9
10
  }
10
11
  catch (error) {
11
12
  return createExpoUnavailableState(permission, error);
@@ -15,13 +16,24 @@ export const mediaLibraryAdapter = {
15
16
  try {
16
17
  const mod = await import('expo-media-library');
17
18
  const result = await mod.requestPermissionsAsync(isWriteOnlyPermission(permission));
18
- return createExpoPermissionState(permission, result);
19
+ return createMediaLibraryPermissionState(permission, result);
19
20
  }
20
21
  catch (error) {
21
22
  return createExpoUnavailableState(permission, error);
22
23
  }
23
24
  },
24
25
  };
26
+ function createMediaLibraryPermissionState(permission, response) {
27
+ if (response.accessPrivileges !== 'limited') {
28
+ return createExpoPermissionState(permission, response);
29
+ }
30
+ return createPermissionState({
31
+ permission,
32
+ status: 'limited',
33
+ canAskAgain: response.canAskAgain,
34
+ reason: 'Media library access is limited to user-selected assets.',
35
+ });
36
+ }
25
37
  function isWriteOnlyPermission(permission) {
26
38
  return permission === KnownPermission.MediaLibraryWrite;
27
39
  }
@@ -1 +1 @@
1
- {"version":3,"file":"mediaLibrary.js","sourceRoot":"","sources":["../../../src/expo/adapters/mediaLibrary.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,IAAI,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAG3E,OAAO,EAAE,yBAAyB,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;AAE9F,MAAM,CAAC,MAAM,mBAAmB,GAA0B;IACxD,KAAK,CAAC,SAAS,CAAC,UAAsB;QACpC,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;YAC/C,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,mBAAmB,CAAC,qBAAqB,CAAC,UAAU,CAAC,CAAC,CAAC;YAEhF,OAAO,yBAAyB,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QACvD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,0BAA0B,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,UAAsB;QAClC,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;YAC/C,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,uBAAuB,CAAC,qBAAqB,CAAC,UAAU,CAAC,CAAC,CAAC;YAEpF,OAAO,yBAAyB,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QACvD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,0BAA0B,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;CACF,CAAC;AAEF,SAAS,qBAAqB,CAAC,UAAsB;IACnD,OAAO,UAAU,KAAK,eAAe,CAAC,iBAAiB,CAAC;AAC1D,CAAC","sourcesContent":["import type { Permission } from '../../registry/permissions';\nimport { Permission as KnownPermission } from '../../registry/permissions';\nimport type { PermissionState } from '../../state/permissionState';\nimport type { ExpoPermissionAdapter } from '../client';\nimport { createExpoPermissionState, createExpoUnavailableState } from '../permissionResponse';\n\nexport const mediaLibraryAdapter: ExpoPermissionAdapter = {\n async getStatus(permission: Permission): Promise<PermissionState> {\n try {\n const mod = await import('expo-media-library');\n const result = await mod.getPermissionsAsync(isWriteOnlyPermission(permission));\n\n return createExpoPermissionState(permission, result);\n } catch (error) {\n return createExpoUnavailableState(permission, error);\n }\n },\n\n async request(permission: Permission): Promise<PermissionState> {\n try {\n const mod = await import('expo-media-library');\n const result = await mod.requestPermissionsAsync(isWriteOnlyPermission(permission));\n\n return createExpoPermissionState(permission, result);\n } catch (error) {\n return createExpoUnavailableState(permission, error);\n }\n },\n};\n\nfunction isWriteOnlyPermission(permission: Permission): boolean {\n return permission === KnownPermission.MediaLibraryWrite;\n}\n"]}
1
+ {"version":3,"file":"mediaLibrary.js","sourceRoot":"","sources":["../../../src/expo/adapters/mediaLibrary.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,IAAI,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC3E,OAAO,EAAE,qBAAqB,EAAwB,MAAM,6BAA6B,CAAC;AAE1F,OAAO,EACL,yBAAyB,EACzB,0BAA0B,GAE3B,MAAM,uBAAuB,CAAC;AAM/B,MAAM,CAAC,MAAM,mBAAmB,GAA0B;IACxD,KAAK,CAAC,SAAS,CAAC,UAAsB;QACpC,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;YAC/C,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,mBAAmB,CAAC,qBAAqB,CAAC,UAAU,CAAC,CAAC,CAAC;YAEhF,OAAO,iCAAiC,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QAC/D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,0BAA0B,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,UAAsB;QAClC,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;YAC/C,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,uBAAuB,CAAC,qBAAqB,CAAC,UAAU,CAAC,CAAC,CAAC;YAEpF,OAAO,iCAAiC,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QAC/D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,0BAA0B,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;CACF,CAAC;AAEF,SAAS,iCAAiC,CACxC,UAAsB,EACtB,QAA4C;IAE5C,IAAI,QAAQ,CAAC,gBAAgB,KAAK,SAAS,EAAE,CAAC;QAC5C,OAAO,yBAAyB,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IACzD,CAAC;IAED,OAAO,qBAAqB,CAAC;QAC3B,UAAU;QACV,MAAM,EAAE,SAAS;QACjB,WAAW,EAAE,QAAQ,CAAC,WAAW;QACjC,MAAM,EAAE,0DAA0D;KACnE,CAAC,CAAC;AACL,CAAC;AAED,SAAS,qBAAqB,CAAC,UAAsB;IACnD,OAAO,UAAU,KAAK,eAAe,CAAC,iBAAiB,CAAC;AAC1D,CAAC","sourcesContent":["import type { Permission } from '../../registry/permissions';\nimport { Permission as KnownPermission } from '../../registry/permissions';\nimport { createPermissionState, type PermissionState } from '../../state/permissionState';\nimport type { ExpoPermissionAdapter } from '../client';\nimport {\n createExpoPermissionState,\n createExpoUnavailableState,\n type ExpoPermissionResponse,\n} from '../permissionResponse';\n\ninterface ExpoMediaLibraryPermissionResponse extends ExpoPermissionResponse {\n readonly accessPrivileges?: 'all' | 'limited' | 'none';\n}\n\nexport const mediaLibraryAdapter: ExpoPermissionAdapter = {\n async getStatus(permission: Permission): Promise<PermissionState> {\n try {\n const mod = await import('expo-media-library');\n const result = await mod.getPermissionsAsync(isWriteOnlyPermission(permission));\n\n return createMediaLibraryPermissionState(permission, result);\n } catch (error) {\n return createExpoUnavailableState(permission, error);\n }\n },\n\n async request(permission: Permission): Promise<PermissionState> {\n try {\n const mod = await import('expo-media-library');\n const result = await mod.requestPermissionsAsync(isWriteOnlyPermission(permission));\n\n return createMediaLibraryPermissionState(permission, result);\n } catch (error) {\n return createExpoUnavailableState(permission, error);\n }\n },\n};\n\nfunction createMediaLibraryPermissionState(\n permission: Permission,\n response: ExpoMediaLibraryPermissionResponse,\n): PermissionState {\n if (response.accessPrivileges !== 'limited') {\n return createExpoPermissionState(permission, response);\n }\n\n return createPermissionState({\n permission,\n status: 'limited',\n canAskAgain: response.canAskAgain,\n reason: 'Media library access is limited to user-selected assets.',\n });\n}\n\nfunction isWriteOnlyPermission(permission: Permission): boolean {\n return permission === KnownPermission.MediaLibraryWrite;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../../../src/expo/adapters/notifications.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAGvD,eAAO,MAAM,oBAAoB,EAAE,qBAsBlC,CAAC"}
1
+ {"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../../../src/expo/adapters/notifications.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAavD,eAAO,MAAM,oBAAoB,EAAE,qBAsBlC,CAAC"}
@@ -1,10 +1,11 @@
1
- import { createExpoPermissionState, createExpoUnavailableState } from '../permissionResponse';
1
+ import { createPermissionState } from '../../state/permissionState';
2
+ import { createExpoPermissionState, createExpoUnavailableState, } from '../permissionResponse';
2
3
  export const notificationsAdapter = {
3
4
  async getStatus(permission) {
4
5
  try {
5
6
  const mod = await import('expo-notifications');
6
7
  const result = await mod.getPermissionsAsync();
7
- return createExpoPermissionState(permission, result);
8
+ return createNotificationPermissionState(permission, result);
8
9
  }
9
10
  catch (error) {
10
11
  return createExpoUnavailableState(permission, error);
@@ -14,11 +15,39 @@ export const notificationsAdapter = {
14
15
  try {
15
16
  const mod = await import('expo-notifications');
16
17
  const result = await mod.requestPermissionsAsync();
17
- return createExpoPermissionState(permission, result);
18
+ return createNotificationPermissionState(permission, result);
18
19
  }
19
20
  catch (error) {
20
21
  return createExpoUnavailableState(permission, error);
21
22
  }
22
23
  },
23
24
  };
25
+ function createNotificationPermissionState(permission, response) {
26
+ switch (response.ios?.status) {
27
+ case 0:
28
+ return createNotificationState(permission, response, 'unknown', 'iOS notification authorization has not been determined.');
29
+ case 1:
30
+ return createNotificationState(permission, response, 'denied', 'iOS notification authorization is denied.');
31
+ case 2:
32
+ return createNotificationState(permission, response, 'granted');
33
+ case 3:
34
+ return createNotificationState(permission, response, 'limited', 'iOS notification authorization is provisional.');
35
+ case 4:
36
+ return createNotificationState(permission, response, 'limited', 'iOS notification authorization is ephemeral.');
37
+ default:
38
+ return createExpoPermissionState(permission, response);
39
+ }
40
+ }
41
+ function createNotificationState(permission, response, status, reason) {
42
+ const state = createExpoPermissionState(permission, {
43
+ status,
44
+ canAskAgain: response.canAskAgain,
45
+ });
46
+ return createPermissionState({
47
+ permission,
48
+ status: state.status,
49
+ canAskAgain: state.canAskAgain,
50
+ reason: state.reason ?? reason,
51
+ });
52
+ }
24
53
  //# sourceMappingURL=notifications.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"notifications.js","sourceRoot":"","sources":["../../../src/expo/adapters/notifications.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,yBAAyB,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;AAE9F,MAAM,CAAC,MAAM,oBAAoB,GAA0B;IACzD,KAAK,CAAC,SAAS,CAAC,UAAsB;QACpC,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;YAC/C,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,mBAAmB,EAAE,CAAC;YAE/C,OAAO,yBAAyB,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QACvD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,0BAA0B,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,UAAsB;QAClC,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;YAC/C,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,uBAAuB,EAAE,CAAC;YAEnD,OAAO,yBAAyB,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QACvD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,0BAA0B,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;CACF,CAAC","sourcesContent":["import type { Permission } from '../../registry/permissions';\nimport type { PermissionState } from '../../state/permissionState';\nimport type { ExpoPermissionAdapter } from '../client';\nimport { createExpoPermissionState, createExpoUnavailableState } from '../permissionResponse';\n\nexport const notificationsAdapter: ExpoPermissionAdapter = {\n async getStatus(permission: Permission): Promise<PermissionState> {\n try {\n const mod = await import('expo-notifications');\n const result = await mod.getPermissionsAsync();\n\n return createExpoPermissionState(permission, result);\n } catch (error) {\n return createExpoUnavailableState(permission, error);\n }\n },\n\n async request(permission: Permission): Promise<PermissionState> {\n try {\n const mod = await import('expo-notifications');\n const result = await mod.requestPermissionsAsync();\n\n return createExpoPermissionState(permission, result);\n } catch (error) {\n return createExpoUnavailableState(permission, error);\n }\n },\n};\n"]}
1
+ {"version":3,"file":"notifications.js","sourceRoot":"","sources":["../../../src/expo/adapters/notifications.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAwB,MAAM,6BAA6B,CAAC;AAE1F,OAAO,EACL,yBAAyB,EACzB,0BAA0B,GAE3B,MAAM,uBAAuB,CAAC;AAQ/B,MAAM,CAAC,MAAM,oBAAoB,GAA0B;IACzD,KAAK,CAAC,SAAS,CAAC,UAAsB;QACpC,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;YAC/C,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,mBAAmB,EAAE,CAAC;YAE/C,OAAO,iCAAiC,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QAC/D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,0BAA0B,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,UAAsB;QAClC,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;YAC/C,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,uBAAuB,EAAE,CAAC;YAEnD,OAAO,iCAAiC,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QAC/D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,0BAA0B,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;CACF,CAAC;AAEF,SAAS,iCAAiC,CACxC,UAAsB,EACtB,QAA4C;IAE5C,QAAQ,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC;QAC7B,KAAK,CAAC;YACJ,OAAO,uBAAuB,CAC5B,UAAU,EACV,QAAQ,EACR,SAAS,EACT,yDAAyD,CAC1D,CAAC;QACJ,KAAK,CAAC;YACJ,OAAO,uBAAuB,CAC5B,UAAU,EACV,QAAQ,EACR,QAAQ,EACR,2CAA2C,CAC5C,CAAC;QACJ,KAAK,CAAC;YACJ,OAAO,uBAAuB,CAAC,UAAU,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;QAClE,KAAK,CAAC;YACJ,OAAO,uBAAuB,CAC5B,UAAU,EACV,QAAQ,EACR,SAAS,EACT,gDAAgD,CACjD,CAAC;QACJ,KAAK,CAAC;YACJ,OAAO,uBAAuB,CAC5B,UAAU,EACV,QAAQ,EACR,SAAS,EACT,8CAA8C,CAC/C,CAAC;QACJ;YACE,OAAO,yBAAyB,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IAC3D,CAAC;AACH,CAAC;AAED,SAAS,uBAAuB,CAC9B,UAAsB,EACtB,QAA4C,EAC5C,MAAiC,EACjC,MAAe;IAEf,MAAM,KAAK,GAAG,yBAAyB,CAAC,UAAU,EAAE;QAClD,MAAM;QACN,WAAW,EAAE,QAAQ,CAAC,WAAW;KAClC,CAAC,CAAC;IAEH,OAAO,qBAAqB,CAAC;QAC3B,UAAU;QACV,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,MAAM;KAC/B,CAAC,CAAC;AACL,CAAC","sourcesContent":["import type { Permission } from '../../registry/permissions';\nimport { createPermissionState, type PermissionState } from '../../state/permissionState';\nimport type { ExpoPermissionAdapter } from '../client';\nimport {\n createExpoPermissionState,\n createExpoUnavailableState,\n type ExpoPermissionResponse,\n} from '../permissionResponse';\n\ninterface ExpoNotificationPermissionResponse extends ExpoPermissionResponse {\n readonly ios?: {\n readonly status?: number;\n };\n}\n\nexport const notificationsAdapter: ExpoPermissionAdapter = {\n async getStatus(permission: Permission): Promise<PermissionState> {\n try {\n const mod = await import('expo-notifications');\n const result = await mod.getPermissionsAsync();\n\n return createNotificationPermissionState(permission, result);\n } catch (error) {\n return createExpoUnavailableState(permission, error);\n }\n },\n\n async request(permission: Permission): Promise<PermissionState> {\n try {\n const mod = await import('expo-notifications');\n const result = await mod.requestPermissionsAsync();\n\n return createNotificationPermissionState(permission, result);\n } catch (error) {\n return createExpoUnavailableState(permission, error);\n }\n },\n};\n\nfunction createNotificationPermissionState(\n permission: Permission,\n response: ExpoNotificationPermissionResponse,\n): PermissionState {\n switch (response.ios?.status) {\n case 0:\n return createNotificationState(\n permission,\n response,\n 'unknown',\n 'iOS notification authorization has not been determined.',\n );\n case 1:\n return createNotificationState(\n permission,\n response,\n 'denied',\n 'iOS notification authorization is denied.',\n );\n case 2:\n return createNotificationState(permission, response, 'granted');\n case 3:\n return createNotificationState(\n permission,\n response,\n 'limited',\n 'iOS notification authorization is provisional.',\n );\n case 4:\n return createNotificationState(\n permission,\n response,\n 'limited',\n 'iOS notification authorization is ephemeral.',\n );\n default:\n return createExpoPermissionState(permission, response);\n }\n}\n\nfunction createNotificationState(\n permission: Permission,\n response: ExpoNotificationPermissionResponse,\n status: PermissionState['status'],\n reason?: string,\n): PermissionState {\n const state = createExpoPermissionState(permission, {\n status,\n canAskAgain: response.canAskAgain,\n });\n\n return createPermissionState({\n permission,\n status: state.status,\n canAskAgain: state.canAskAgain,\n reason: state.reason ?? reason,\n });\n}\n"]}
@@ -5,5 +5,19 @@ export interface ExpoPermissionAdapter {
5
5
  getStatus(permission: Permission): Promise<PermissionState>;
6
6
  request(permission: Permission): Promise<PermissionState>;
7
7
  }
8
+ /***
9
+ * Creates a permission client backed by Expo SDK modules.
10
+ *
11
+ * Denials that Expo cannot request again are normalized to `blocked`. Limited
12
+ * media-library access and provisional or ephemeral iOS notification access
13
+ * are normalized to usable `limited` states, where `granted` remains true.
14
+ * The client requires `expo-linking` for settings recovery and rejects with a
15
+ * stable error when the operating-system settings page cannot be opened.
16
+ *
17
+ * Expo modules are loaded only when a client operation uses them. The package
18
+ * root and Expo manifest metadata remain free of native module imports.
19
+ *
20
+ * @readme
21
+ */
8
22
  export declare function createPermissionClient(): PermissionClient;
9
23
  //# sourceMappingURL=client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/expo/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAQvF,MAAM,WAAW,qBAAqB;IACpC,SAAS,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IAC5D,OAAO,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;CAC3D;AAYD,wBAAgB,sBAAsB,IAAI,gBAAgB,CAsCzD"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/expo/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAUvF,MAAM,WAAW,qBAAqB;IACpC,SAAS,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IAC5D,OAAO,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;CAC3D;AAYD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,IAAI,gBAAgB,CAkDzD"}
@@ -6,47 +6,76 @@ import { mediaLibraryAdapter } from './adapters/mediaLibrary';
6
6
  import { microphoneAdapter } from './adapters/microphone';
7
7
  import { notificationsAdapter } from './adapters/notifications';
8
8
  import { EXPO_PERMISSION_SUPPORT } from './manifest';
9
- const adapters = {
10
- [Permission.Camera]: cameraAdapter,
11
- [Permission.MediaLibrary]: mediaLibraryAdapter,
12
- [Permission.MediaLibraryWrite]: mediaLibraryAdapter,
13
- [Permission.Microphone]: microphoneAdapter,
14
- [Permission.LocationForeground]: locationAdapter,
15
- [Permission.LocationBackground]: locationAdapter,
16
- [Permission.Notifications]: notificationsAdapter,
17
- };
9
+ const OPEN_SETTINGS_ERROR_MESSAGE = 'Unable to open application settings.';
10
+ const adapters = new Map([
11
+ [Permission.Camera, cameraAdapter],
12
+ [Permission.MediaLibrary, mediaLibraryAdapter],
13
+ [Permission.MediaLibraryWrite, mediaLibraryAdapter],
14
+ [Permission.Microphone, microphoneAdapter],
15
+ [Permission.LocationForeground, locationAdapter],
16
+ [Permission.LocationBackground, locationAdapter],
17
+ [Permission.Notifications, notificationsAdapter],
18
+ ]);
19
+ /***
20
+ * Creates a permission client backed by Expo SDK modules.
21
+ *
22
+ * Denials that Expo cannot request again are normalized to `blocked`. Limited
23
+ * media-library access and provisional or ephemeral iOS notification access
24
+ * are normalized to usable `limited` states, where `granted` remains true.
25
+ * The client requires `expo-linking` for settings recovery and rejects with a
26
+ * stable error when the operating-system settings page cannot be opened.
27
+ *
28
+ * Expo modules are loaded only when a client operation uses them. The package
29
+ * root and Expo manifest metadata remain free of native module imports.
30
+ *
31
+ * @readme
32
+ */
18
33
  export function createPermissionClient() {
19
34
  return {
20
35
  async getStatus(permission) {
21
- const metadata = EXPO_PERMISSION_SUPPORT[permission];
36
+ const metadata = getPermissionSupport(permission);
22
37
  if (metadata.support === 'unsupported' || metadata.support === 'notImplemented') {
23
38
  return createUnavailableSupportState(permission);
24
39
  }
25
40
  if (metadata.support === 'notRequired') {
26
41
  return createNotRequiredState(permission);
27
42
  }
28
- const adapter = adapters[permission];
43
+ const adapter = adapters.get(permission);
29
44
  if (!adapter) {
30
45
  throw new Error(`No adapter registered for permission: ${permission}`);
31
46
  }
32
47
  return adapter.getStatus(permission);
33
48
  },
34
49
  async request(permission) {
35
- const metadata = EXPO_PERMISSION_SUPPORT[permission];
50
+ const metadata = getPermissionSupport(permission);
36
51
  if (metadata.support === 'unsupported' || metadata.support === 'notImplemented') {
37
52
  return createUnavailableSupportState(permission);
38
53
  }
39
54
  if (metadata.support === 'notRequired') {
40
55
  return createNotRequiredState(permission);
41
56
  }
42
- const adapter = adapters[permission];
57
+ const adapter = adapters.get(permission);
43
58
  if (!adapter) {
44
59
  throw new Error(`No adapter registered for permission: ${permission}`);
45
60
  }
46
61
  return adapter.request(permission);
47
62
  },
63
+ async openSettings() {
64
+ try {
65
+ const linking = await import('expo-linking');
66
+ await linking.openSettings();
67
+ }
68
+ catch (error) {
69
+ throw new Error(OPEN_SETTINGS_ERROR_MESSAGE, {
70
+ cause: error,
71
+ });
72
+ }
73
+ },
48
74
  };
49
75
  }
76
+ function getPermissionSupport(permission) {
77
+ return Reflect.get(EXPO_PERMISSION_SUPPORT, permission);
78
+ }
50
79
  function createUnavailableSupportState(permission) {
51
80
  return createPermissionState({
52
81
  permission,
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/expo/client.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,qBAAqB,EAAwB,MAAM,0BAA0B,CAAC;AACvF,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAOrD,MAAM,QAAQ,GAAuD;IACnE,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,aAAa;IAClC,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,mBAAmB;IAC9C,CAAC,UAAU,CAAC,iBAAiB,CAAC,EAAE,mBAAmB;IACnD,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,iBAAiB;IAC1C,CAAC,UAAU,CAAC,kBAAkB,CAAC,EAAE,eAAe;IAChD,CAAC,UAAU,CAAC,kBAAkB,CAAC,EAAE,eAAe;IAChD,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,oBAAoB;CACjD,CAAC;AAEF,MAAM,UAAU,sBAAsB;IACpC,OAAO;QACL,KAAK,CAAC,SAAS,CAAC,UAAsB;YACpC,MAAM,QAAQ,GAAG,uBAAuB,CAAC,UAAU,CAAC,CAAC;YACrD,IAAI,QAAQ,CAAC,OAAO,KAAK,aAAa,IAAI,QAAQ,CAAC,OAAO,KAAK,gBAAgB,EAAE,CAAC;gBAChF,OAAO,6BAA6B,CAAC,UAAU,CAAC,CAAC;YACnD,CAAC;YAED,IAAI,QAAQ,CAAC,OAAO,KAAK,aAAa,EAAE,CAAC;gBACvC,OAAO,sBAAsB,CAAC,UAAU,CAAC,CAAC;YAC5C,CAAC;YAED,MAAM,OAAO,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC;YACrC,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,MAAM,IAAI,KAAK,CAAC,yCAAyC,UAAU,EAAE,CAAC,CAAC;YACzE,CAAC;YAED,OAAO,OAAO,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QACvC,CAAC;QAED,KAAK,CAAC,OAAO,CAAC,UAAsB;YAClC,MAAM,QAAQ,GAAG,uBAAuB,CAAC,UAAU,CAAC,CAAC;YACrD,IAAI,QAAQ,CAAC,OAAO,KAAK,aAAa,IAAI,QAAQ,CAAC,OAAO,KAAK,gBAAgB,EAAE,CAAC;gBAChF,OAAO,6BAA6B,CAAC,UAAU,CAAC,CAAC;YACnD,CAAC;YAED,IAAI,QAAQ,CAAC,OAAO,KAAK,aAAa,EAAE,CAAC;gBACvC,OAAO,sBAAsB,CAAC,UAAU,CAAC,CAAC;YAC5C,CAAC;YAED,MAAM,OAAO,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC;YACrC,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,MAAM,IAAI,KAAK,CAAC,yCAAyC,UAAU,EAAE,CAAC,CAAC;YACzE,CAAC;YAED,OAAO,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACrC,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,6BAA6B,CAAC,UAAsB;IAC3D,OAAO,qBAAqB,CAAC;QAC3B,UAAU;QACV,MAAM,EAAE,aAAa;QACrB,MAAM,EAAE,cAAc,UAAU,2CAA2C;KAC5E,CAAC,CAAC;AACL,CAAC;AAED,SAAS,sBAAsB,CAAC,UAAsB;IACpD,OAAO,qBAAqB,CAAC;QAC3B,UAAU;QACV,MAAM,EAAE,SAAS;KAClB,CAAC,CAAC;AACL,CAAC","sourcesContent":["import type { PermissionClient } from '../client/types';\nimport { Permission } from '../registry/permissions';\nimport { createPermissionState, type PermissionState } from '../state/permissionState';\nimport { cameraAdapter } from './adapters/camera';\nimport { locationAdapter } from './adapters/location';\nimport { mediaLibraryAdapter } from './adapters/mediaLibrary';\nimport { microphoneAdapter } from './adapters/microphone';\nimport { notificationsAdapter } from './adapters/notifications';\nimport { EXPO_PERMISSION_SUPPORT } from './manifest';\n\nexport interface ExpoPermissionAdapter {\n getStatus(permission: Permission): Promise<PermissionState>;\n request(permission: Permission): Promise<PermissionState>;\n}\n\nconst adapters: Partial<Record<Permission, ExpoPermissionAdapter>> = {\n [Permission.Camera]: cameraAdapter,\n [Permission.MediaLibrary]: mediaLibraryAdapter,\n [Permission.MediaLibraryWrite]: mediaLibraryAdapter,\n [Permission.Microphone]: microphoneAdapter,\n [Permission.LocationForeground]: locationAdapter,\n [Permission.LocationBackground]: locationAdapter,\n [Permission.Notifications]: notificationsAdapter,\n};\n\nexport function createPermissionClient(): PermissionClient {\n return {\n async getStatus(permission: Permission): Promise<PermissionState> {\n const metadata = EXPO_PERMISSION_SUPPORT[permission];\n if (metadata.support === 'unsupported' || metadata.support === 'notImplemented') {\n return createUnavailableSupportState(permission);\n }\n\n if (metadata.support === 'notRequired') {\n return createNotRequiredState(permission);\n }\n\n const adapter = adapters[permission];\n if (!adapter) {\n throw new Error(`No adapter registered for permission: ${permission}`);\n }\n\n return adapter.getStatus(permission);\n },\n\n async request(permission: Permission): Promise<PermissionState> {\n const metadata = EXPO_PERMISSION_SUPPORT[permission];\n if (metadata.support === 'unsupported' || metadata.support === 'notImplemented') {\n return createUnavailableSupportState(permission);\n }\n\n if (metadata.support === 'notRequired') {\n return createNotRequiredState(permission);\n }\n\n const adapter = adapters[permission];\n if (!adapter) {\n throw new Error(`No adapter registered for permission: ${permission}`);\n }\n\n return adapter.request(permission);\n },\n };\n}\n\nfunction createUnavailableSupportState(permission: Permission): PermissionState {\n return createPermissionState({\n permission,\n status: 'unavailable',\n reason: `Permission ${permission} is not implemented or supported on Expo.`,\n });\n}\n\nfunction createNotRequiredState(permission: Permission): PermissionState {\n return createPermissionState({\n permission,\n status: 'granted',\n });\n}\n"]}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/expo/client.ts"],"names":[],"mappings":"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","sourcesContent":["import type { PermissionClient } from '../client/types';\nimport { Permission } from '../registry/permissions';\nimport { createPermissionState, type PermissionState } from '../state/permissionState';\nimport { cameraAdapter } from './adapters/camera';\nimport { locationAdapter } from './adapters/location';\nimport { mediaLibraryAdapter } from './adapters/mediaLibrary';\nimport { microphoneAdapter } from './adapters/microphone';\nimport { notificationsAdapter } from './adapters/notifications';\nimport { EXPO_PERMISSION_SUPPORT } from './manifest';\n\nconst OPEN_SETTINGS_ERROR_MESSAGE = 'Unable to open application settings.';\n\nexport interface ExpoPermissionAdapter {\n getStatus(permission: Permission): Promise<PermissionState>;\n request(permission: Permission): Promise<PermissionState>;\n}\n\nconst adapters = new Map<Permission, ExpoPermissionAdapter>([\n [Permission.Camera, cameraAdapter],\n [Permission.MediaLibrary, mediaLibraryAdapter],\n [Permission.MediaLibraryWrite, mediaLibraryAdapter],\n [Permission.Microphone, microphoneAdapter],\n [Permission.LocationForeground, locationAdapter],\n [Permission.LocationBackground, locationAdapter],\n [Permission.Notifications, notificationsAdapter],\n]);\n\n/***\n * Creates a permission client backed by Expo SDK modules.\n *\n * Denials that Expo cannot request again are normalized to `blocked`. Limited\n * media-library access and provisional or ephemeral iOS notification access\n * are normalized to usable `limited` states, where `granted` remains true.\n * The client requires `expo-linking` for settings recovery and rejects with a\n * stable error when the operating-system settings page cannot be opened.\n *\n * Expo modules are loaded only when a client operation uses them. The package\n * root and Expo manifest metadata remain free of native module imports.\n *\n * @readme\n */\nexport function createPermissionClient(): PermissionClient {\n return {\n async getStatus(permission: Permission): Promise<PermissionState> {\n const metadata = getPermissionSupport(permission);\n if (metadata.support === 'unsupported' || metadata.support === 'notImplemented') {\n return createUnavailableSupportState(permission);\n }\n\n if (metadata.support === 'notRequired') {\n return createNotRequiredState(permission);\n }\n\n const adapter = adapters.get(permission);\n if (!adapter) {\n throw new Error(`No adapter registered for permission: ${permission}`);\n }\n\n return adapter.getStatus(permission);\n },\n\n async request(permission: Permission): Promise<PermissionState> {\n const metadata = getPermissionSupport(permission);\n if (metadata.support === 'unsupported' || metadata.support === 'notImplemented') {\n return createUnavailableSupportState(permission);\n }\n\n if (metadata.support === 'notRequired') {\n return createNotRequiredState(permission);\n }\n\n const adapter = adapters.get(permission);\n if (!adapter) {\n throw new Error(`No adapter registered for permission: ${permission}`);\n }\n\n return adapter.request(permission);\n },\n\n async openSettings(): Promise<void> {\n try {\n const linking = await import('expo-linking');\n\n await linking.openSettings();\n } catch (error) {\n throw new Error(OPEN_SETTINGS_ERROR_MESSAGE, {\n cause: error,\n });\n }\n },\n };\n}\n\nfunction getPermissionSupport(permission: Permission) {\n return Reflect.get(EXPO_PERMISSION_SUPPORT, permission);\n}\n\nfunction createUnavailableSupportState(permission: Permission): PermissionState {\n return createPermissionState({\n permission,\n status: 'unavailable',\n reason: `Permission ${permission} is not implemented or supported on Expo.`,\n });\n}\n\nfunction createNotRequiredState(permission: Permission): PermissionState {\n return createPermissionState({\n permission,\n status: 'granted',\n });\n}\n"]}
@@ -0,0 +1,54 @@
1
+ export interface MockPermissionResponse {
2
+ readonly status: string;
3
+ readonly canAskAgain?: boolean;
4
+ readonly accessPrivileges?: 'all' | 'limited' | 'none';
5
+ readonly ios?: {
6
+ readonly status?: number;
7
+ };
8
+ }
9
+ type CameraMockMode = 'topLevel' | 'cameraNamespace' | 'missingExports';
10
+ type AudioMockMode = 'topLevel' | 'audioModule' | 'missingExports';
11
+ export declare function permissionResponse(status: string, canAskAgain?: boolean): MockPermissionResponse;
12
+ export declare const cameraMock: {
13
+ mode: CameraMockMode;
14
+ getResponse: MockPermissionResponse;
15
+ requestResponse: MockPermissionResponse;
16
+ getError: string | undefined;
17
+ requestError: string | undefined;
18
+ };
19
+ export declare const audioMock: {
20
+ mode: AudioMockMode;
21
+ getResponse: MockPermissionResponse;
22
+ requestResponse: MockPermissionResponse;
23
+ getError: string | undefined;
24
+ requestError: string | undefined;
25
+ };
26
+ export declare const mediaLibraryMock: {
27
+ getResponse: MockPermissionResponse;
28
+ requestResponse: MockPermissionResponse;
29
+ getError: string | undefined;
30
+ requestError: string | undefined;
31
+ getCalls: boolean[];
32
+ requestCalls: boolean[];
33
+ };
34
+ export declare const locationMock: {
35
+ foregroundGetResponse: MockPermissionResponse;
36
+ foregroundRequestResponse: MockPermissionResponse;
37
+ backgroundGetResponse: MockPermissionResponse;
38
+ backgroundRequestResponse: MockPermissionResponse;
39
+ getCalls: string[];
40
+ requestCalls: string[];
41
+ };
42
+ export declare const notificationsMock: {
43
+ getResponse: MockPermissionResponse;
44
+ requestResponse: MockPermissionResponse;
45
+ getError: string | undefined;
46
+ requestError: string | undefined;
47
+ };
48
+ export declare const linkingMock: {
49
+ calls: number;
50
+ error: Error | undefined;
51
+ };
52
+ export declare function resetExpoMocks(): void;
53
+ export {};
54
+ //# sourceMappingURL=client.testSupport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.testSupport.d.ts","sourceRoot":"","sources":["../../src/expo/client.testSupport.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,KAAK,GAAG,SAAS,GAAG,MAAM,CAAC;IACvD,QAAQ,CAAC,GAAG,CAAC,EAAE;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7C;AAED,KAAK,cAAc,GAAG,UAAU,GAAG,iBAAiB,GAAG,gBAAgB,CAAC;AACxE,KAAK,aAAa,GAAG,UAAU,GAAG,aAAa,GAAG,gBAAgB,CAAC;AAEnE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,UAAO,GAAG,sBAAsB,CAE7F;AAED,eAAO,MAAM,UAAU;UACD,cAAc;;;cAGX,MAAM,GAAG,SAAS;kBACd,MAAM,GAAG,SAAS;CAC9C,CAAC;AACF,eAAO,MAAM,SAAS;UACA,aAAa;;;cAGV,MAAM,GAAG,SAAS;kBACd,MAAM,GAAG,SAAS;CAC9C,CAAC;AACF,eAAO,MAAM,gBAAgB;;;cAGJ,MAAM,GAAG,SAAS;kBACd,MAAM,GAAG,SAAS;cAC7B,OAAO,EAAE;kBACL,OAAO,EAAE;CAC9B,CAAC;AACF,eAAO,MAAM,YAAY;;;;;cAKP,MAAM,EAAE;kBACJ,MAAM,EAAE;CAC7B,CAAC;AACF,eAAO,MAAM,iBAAiB;;;cAGL,MAAM,GAAG,SAAS;kBACd,MAAM,GAAG,SAAS;CAC9C,CAAC;AACF,eAAO,MAAM,WAAW;;WAAmC,KAAK,GAAG,SAAS;CAAE,CAAC;AAsG/E,wBAAgB,cAAc,IAAI,IAAI,CAkBrC"}
@@ -0,0 +1,183 @@
1
+ import { mock } from 'bun:test';
2
+ export function permissionResponse(status, canAskAgain = true) {
3
+ return { status, canAskAgain };
4
+ }
5
+ export const cameraMock = {
6
+ mode: 'topLevel',
7
+ getResponse: permissionResponse('granted'),
8
+ requestResponse: permissionResponse('granted'),
9
+ getError: undefined,
10
+ requestError: undefined,
11
+ };
12
+ export const audioMock = {
13
+ mode: 'topLevel',
14
+ getResponse: permissionResponse('granted'),
15
+ requestResponse: permissionResponse('granted'),
16
+ getError: undefined,
17
+ requestError: undefined,
18
+ };
19
+ export const mediaLibraryMock = {
20
+ getResponse: permissionResponse('granted'),
21
+ requestResponse: permissionResponse('granted'),
22
+ getError: undefined,
23
+ requestError: undefined,
24
+ getCalls: [],
25
+ requestCalls: [],
26
+ };
27
+ export const locationMock = {
28
+ foregroundGetResponse: permissionResponse('granted'),
29
+ foregroundRequestResponse: permissionResponse('granted'),
30
+ backgroundGetResponse: permissionResponse('granted'),
31
+ backgroundRequestResponse: permissionResponse('granted'),
32
+ getCalls: [],
33
+ requestCalls: [],
34
+ };
35
+ export const notificationsMock = {
36
+ getResponse: permissionResponse('granted'),
37
+ requestResponse: permissionResponse('granted'),
38
+ getError: undefined,
39
+ requestError: undefined,
40
+ };
41
+ export const linkingMock = { calls: 0, error: undefined };
42
+ void mock.module('expo-camera', () => ({
43
+ get getCameraPermissionsAsync() {
44
+ if (cameraMock.mode !== 'topLevel')
45
+ return undefined;
46
+ return () => {
47
+ if (cameraMock.getError !== undefined)
48
+ throw new Error(cameraMock.getError);
49
+ return Promise.resolve(cameraMock.getResponse);
50
+ };
51
+ },
52
+ get requestCameraPermissionsAsync() {
53
+ if (cameraMock.mode !== 'topLevel')
54
+ return undefined;
55
+ return () => {
56
+ if (cameraMock.requestError !== undefined)
57
+ throw new Error(cameraMock.requestError);
58
+ return Promise.resolve(cameraMock.requestResponse);
59
+ };
60
+ },
61
+ get Camera() {
62
+ if (cameraMock.mode !== 'cameraNamespace')
63
+ return undefined;
64
+ return {
65
+ getCameraPermissionsAsync: () => Promise.resolve(cameraMock.getResponse),
66
+ requestCameraPermissionsAsync: () => Promise.resolve(cameraMock.requestResponse),
67
+ };
68
+ },
69
+ }));
70
+ void mock.module('expo-audio', () => ({
71
+ get getRecordingPermissionsAsync() {
72
+ if (audioMock.mode !== 'topLevel')
73
+ return undefined;
74
+ return () => {
75
+ if (audioMock.getError !== undefined)
76
+ throw new Error(audioMock.getError);
77
+ return Promise.resolve(audioMock.getResponse);
78
+ };
79
+ },
80
+ get requestRecordingPermissionsAsync() {
81
+ if (audioMock.mode !== 'topLevel')
82
+ return undefined;
83
+ return () => {
84
+ if (audioMock.requestError !== undefined)
85
+ throw new Error(audioMock.requestError);
86
+ return Promise.resolve(audioMock.requestResponse);
87
+ };
88
+ },
89
+ get AudioModule() {
90
+ if (audioMock.mode !== 'audioModule')
91
+ return undefined;
92
+ return {
93
+ getRecordingPermissionsAsync: () => Promise.resolve(audioMock.getResponse),
94
+ requestRecordingPermissionsAsync: () => Promise.resolve(audioMock.requestResponse),
95
+ };
96
+ },
97
+ }));
98
+ void mock.module('expo-media-library', () => ({
99
+ getPermissionsAsync: (writeOnly) => {
100
+ mediaLibraryMock.getCalls.push(writeOnly === true);
101
+ if (mediaLibraryMock.getError !== undefined)
102
+ throw new Error(mediaLibraryMock.getError);
103
+ return Promise.resolve(mediaLibraryMock.getResponse);
104
+ },
105
+ requestPermissionsAsync: (writeOnly) => {
106
+ mediaLibraryMock.requestCalls.push(writeOnly === true);
107
+ if (mediaLibraryMock.requestError !== undefined)
108
+ throw new Error(mediaLibraryMock.requestError);
109
+ return Promise.resolve(mediaLibraryMock.requestResponse);
110
+ },
111
+ }));
112
+ void mock.module('expo-location', () => ({
113
+ getForegroundPermissionsAsync: () => {
114
+ locationMock.getCalls.push('foreground');
115
+ return Promise.resolve(locationMock.foregroundGetResponse);
116
+ },
117
+ requestForegroundPermissionsAsync: () => {
118
+ locationMock.requestCalls.push('foreground');
119
+ return Promise.resolve(locationMock.foregroundRequestResponse);
120
+ },
121
+ getBackgroundPermissionsAsync: () => {
122
+ locationMock.getCalls.push('background');
123
+ return Promise.resolve(locationMock.backgroundGetResponse);
124
+ },
125
+ requestBackgroundPermissionsAsync: () => {
126
+ locationMock.requestCalls.push('background');
127
+ return Promise.resolve(locationMock.backgroundRequestResponse);
128
+ },
129
+ }));
130
+ void mock.module('expo-notifications', () => ({
131
+ getPermissionsAsync: () => {
132
+ if (notificationsMock.getError !== undefined)
133
+ throw new Error(notificationsMock.getError);
134
+ return Promise.resolve(notificationsMock.getResponse);
135
+ },
136
+ requestPermissionsAsync: () => {
137
+ if (notificationsMock.requestError !== undefined)
138
+ throw new Error(notificationsMock.requestError);
139
+ return Promise.resolve(notificationsMock.requestResponse);
140
+ },
141
+ }));
142
+ void mock.module('expo-linking', () => ({
143
+ openSettings: () => {
144
+ linkingMock.calls += 1;
145
+ if (linkingMock.error !== undefined)
146
+ throw linkingMock.error;
147
+ return Promise.resolve();
148
+ },
149
+ }));
150
+ export function resetExpoMocks() {
151
+ cameraMock.mode = 'topLevel';
152
+ cameraMock.getResponse = permissionResponse('granted');
153
+ cameraMock.requestResponse = permissionResponse('granted');
154
+ cameraMock.getError = undefined;
155
+ cameraMock.requestError = undefined;
156
+ audioMock.mode = 'topLevel';
157
+ audioMock.getResponse = permissionResponse('granted');
158
+ audioMock.requestResponse = permissionResponse('granted');
159
+ audioMock.getError = undefined;
160
+ audioMock.requestError = undefined;
161
+ resetCollectionMocks();
162
+ notificationsMock.getResponse = permissionResponse('granted');
163
+ notificationsMock.requestResponse = permissionResponse('granted');
164
+ notificationsMock.getError = undefined;
165
+ notificationsMock.requestError = undefined;
166
+ linkingMock.calls = 0;
167
+ linkingMock.error = undefined;
168
+ }
169
+ function resetCollectionMocks() {
170
+ mediaLibraryMock.getResponse = permissionResponse('granted');
171
+ mediaLibraryMock.requestResponse = permissionResponse('granted');
172
+ mediaLibraryMock.getError = undefined;
173
+ mediaLibraryMock.requestError = undefined;
174
+ mediaLibraryMock.getCalls.length = 0;
175
+ mediaLibraryMock.requestCalls.length = 0;
176
+ locationMock.foregroundGetResponse = permissionResponse('granted');
177
+ locationMock.foregroundRequestResponse = permissionResponse('granted');
178
+ locationMock.backgroundGetResponse = permissionResponse('granted');
179
+ locationMock.backgroundRequestResponse = permissionResponse('granted');
180
+ locationMock.getCalls.length = 0;
181
+ locationMock.requestCalls.length = 0;
182
+ }
183
+ //# sourceMappingURL=client.testSupport.js.map