@ankhorage/permissions 0.2.3 → 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 (44) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +3 -3
  3. package/dist/expo/client.js +16 -13
  4. package/dist/expo/client.js.map +1 -1
  5. package/dist/expo/client.testSupport.d.ts +54 -0
  6. package/dist/expo/client.testSupport.d.ts.map +1 -0
  7. package/dist/expo/client.testSupport.js +183 -0
  8. package/dist/expo/client.testSupport.js.map +1 -0
  9. package/dist/expo/manifest.d.ts.map +1 -1
  10. package/dist/expo/manifest.js.map +1 -1
  11. package/dist/expo/permissionResponse.js +4 -4
  12. package/dist/expo/permissionResponse.js.map +1 -1
  13. package/dist/react/index.d.ts.map +1 -1
  14. package/dist/react/index.js +16 -11
  15. package/dist/react/index.js.map +1 -1
  16. package/dist/state/permissionState.d.ts.map +1 -1
  17. package/dist/state/permissionState.js.map +1 -1
  18. package/dist/testing/index.d.ts.map +1 -1
  19. package/dist/testing/index.js +27 -17
  20. package/dist/testing/index.js.map +1 -1
  21. package/dist/web/index.d.ts +1 -2
  22. package/dist/web/index.d.ts.map +1 -1
  23. package/dist/web/index.js +2 -76
  24. package/dist/web/index.js.map +1 -1
  25. package/dist/web/permissionStates.d.ts +10 -0
  26. package/dist/web/permissionStates.d.ts.map +1 -0
  27. package/dist/web/permissionStates.js +64 -0
  28. package/dist/web/permissionStates.js.map +1 -0
  29. package/package.json +10 -11
  30. package/src/expo/client.notifications.test.ts +106 -0
  31. package/src/expo/client.test.ts +110 -535
  32. package/src/expo/client.testSupport.ts +188 -0
  33. package/src/expo/client.ts +17 -13
  34. package/src/expo/manifest.test.ts +48 -50
  35. package/src/expo/manifest.ts +1 -5
  36. package/src/expo/permissionResponse.ts +4 -4
  37. package/src/manager/createPermissionManager.test.ts +48 -50
  38. package/src/react/index.tsx +21 -13
  39. package/src/registry/permissions.test.ts +4 -3
  40. package/src/state/permissionState.ts +1 -6
  41. package/src/testing/index.ts +36 -25
  42. package/src/web/index.test.ts +149 -151
  43. package/src/web/index.ts +9 -102
  44. package/src/web/permissionStates.ts +83 -0
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAE1D,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACtB,MAAM,0BAA0B,CAAC;AAElC;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAC/B,eAAe,GACf;IACE,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEN;;GAEG;AACH,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,uBAAuB,EAAE,CAAC;IAC5D,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,uBAAuB,EAAE,CAAC;IAC5D,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7C;AAED;;GAEG;AACH,MAAM,WAAW,oBAAqB,SAAQ,gBAAgB;IAC5D,QAAQ,CAAC,KAAK,EAAE,uBAAuB,GAAG,IAAI,CAAC;IAC/C,SAAS,CACP,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,gBAAgB,EACxB,OAAO,CAAC,EAAE;QACR,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;QAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;KAC1B,GACA,IAAI,CAAC;IACR,WAAW,IAAI,SAAS,eAAe,EAAE,CAAC;CAC3C;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,GAAE,2BAAgC,GACxC,oBAAoB,CA4EtB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAE1D,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACtB,MAAM,0BAA0B,CAAC;AAElC;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAC/B,eAAe,GACf;IACE,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEN;;GAEG;AACH,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,uBAAuB,EAAE,CAAC;IAC5D,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,uBAAuB,EAAE,CAAC;IAC5D,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7C;AAED;;GAEG;AACH,MAAM,WAAW,oBAAqB,SAAQ,gBAAgB;IAC5D,QAAQ,CAAC,KAAK,EAAE,uBAAuB,GAAG,IAAI,CAAC;IAC/C,SAAS,CACP,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,gBAAgB,EACxB,OAAO,CAAC,EAAE;QACR,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;QAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;KAC1B,GACA,IAAI,CAAC;IACR,WAAW,IAAI,SAAS,eAAe,EAAE,CAAC;CAC3C;AAQD;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,GAAE,2BAAgC,GACxC,oBAAoB,CAKtB"}
@@ -8,47 +8,60 @@ import { createPermissionState, } from '../state/permissionState';
8
8
  * prompts, simulators, or network access.
9
9
  */
10
10
  export function createFakePermissionClient(options = {}) {
11
- const now = options.now ?? (() => new Date());
12
- const states = new Map();
13
- const requestStates = new Map();
11
+ const context = createFakePermissionContext(options);
12
+ const client = createFakePermissionOperations(context, options.openSettings);
13
+ if (options.openSettings === undefined)
14
+ delete client.openSettings;
15
+ return client;
16
+ }
17
+ function createFakePermissionContext(options) {
18
+ const context = {
19
+ now: options.now ?? (() => new Date()),
20
+ states: new Map(),
21
+ requestStates: new Map(),
22
+ };
14
23
  for (const permission of PERMISSIONS) {
15
- states.set(permission, createPermissionState({
24
+ context.states.set(permission, createPermissionState({
16
25
  permission,
17
26
  status: 'unknown',
18
27
  canAskAgain: true,
19
28
  }));
20
29
  }
21
30
  for (const state of options.initialStates ?? []) {
22
- states.set(state.permission, normalizeSeed(state));
31
+ context.states.set(state.permission, normalizeSeed(state));
23
32
  }
24
33
  for (const state of options.requestStates ?? []) {
25
- requestStates.set(state.permission, normalizeSeed(state));
34
+ context.requestStates.set(state.permission, normalizeSeed(state));
26
35
  }
36
+ return context;
37
+ }
38
+ function createFakePermissionOperations(context, openSettings) {
27
39
  const client = {
28
40
  getStatus(permission) {
29
- return Promise.resolve(getStoredState(states, assertKnownPermission(permission)));
41
+ return Promise.resolve(getStoredState(context.states, assertKnownPermission(permission)));
30
42
  },
31
43
  request(permission) {
32
44
  const knownPermission = assertKnownPermission(permission);
33
- const requestState = requestStates.get(knownPermission) ?? getStoredState(states, knownPermission);
45
+ const requestState = context.requestStates.get(knownPermission) ??
46
+ getStoredState(context.states, knownPermission);
34
47
  const nextState = createPermissionState({
35
48
  permission: knownPermission,
36
49
  status: requestState.status,
37
50
  canAskAgain: requestState.canAskAgain,
38
- requestedAt: now(),
51
+ requestedAt: context.now(),
39
52
  reason: requestState.reason,
40
53
  });
41
- states.set(knownPermission, nextState);
54
+ context.states.set(knownPermission, nextState);
42
55
  return Promise.resolve(nextState);
43
56
  },
44
- openSettings: options.openSettings,
57
+ openSettings,
45
58
  setState(state) {
46
59
  const normalizedState = normalizeSeed(state);
47
- states.set(normalizedState.permission, normalizedState);
60
+ context.states.set(normalizedState.permission, normalizedState);
48
61
  },
49
62
  setStatus(permission, status, statusOptions = {}) {
50
63
  const knownPermission = assertKnownPermission(permission);
51
- states.set(knownPermission, createPermissionState({
64
+ context.states.set(knownPermission, createPermissionState({
52
65
  permission: knownPermission,
53
66
  status,
54
67
  canAskAgain: statusOptions.canAskAgain,
@@ -56,12 +69,9 @@ export function createFakePermissionClient(options = {}) {
56
69
  }));
57
70
  },
58
71
  getSnapshot() {
59
- return PERMISSIONS.map((permission) => getStoredState(states, permission));
72
+ return PERMISSIONS.map((permission) => getStoredState(context.states, permission));
60
73
  },
61
74
  };
62
- if (options.openSettings === undefined) {
63
- delete client.openSettings;
64
- }
65
75
  return client;
66
76
  }
67
77
  function normalizeSeed(seed) {
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC7E,OAAO,EACL,qBAAqB,GAGtB,MAAM,0BAA0B,CAAC;AAwClC;;;;;;GAMG;AACH,MAAM,UAAU,0BAA0B,CACxC,UAAuC,EAAE;IAEzC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;IAC9C,MAAM,MAAM,GAAG,IAAI,GAAG,EAA+B,CAAC;IACtD,MAAM,aAAa,GAAG,IAAI,GAAG,EAA+B,CAAC;IAE7D,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACrC,MAAM,CAAC,GAAG,CACR,UAAU,EACV,qBAAqB,CAAC;YACpB,UAAU;YACV,MAAM,EAAE,SAAS;YACjB,WAAW,EAAE,IAAI;SAClB,CAAC,CACH,CAAC;IACJ,CAAC;IAED,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,aAAa,IAAI,EAAE,EAAE,CAAC;QAChD,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,EAAE,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC;IACrD,CAAC;IAED,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,aAAa,IAAI,EAAE,EAAE,CAAC;QAChD,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,EAAE,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC;IAC5D,CAAC;IAED,MAAM,MAAM,GAAyB;QACnC,SAAS,CAAC,UAAU;YAClB,OAAO,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,MAAM,EAAE,qBAAqB,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QACpF,CAAC;QAED,OAAO,CAAC,UAAU;YAChB,MAAM,eAAe,GAAG,qBAAqB,CAAC,UAAU,CAAC,CAAC;YAC1D,MAAM,YAAY,GAChB,aAAa,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,cAAc,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;YAChF,MAAM,SAAS,GAAG,qBAAqB,CAAC;gBACtC,UAAU,EAAE,eAAe;gBAC3B,MAAM,EAAE,YAAY,CAAC,MAAM;gBAC3B,WAAW,EAAE,YAAY,CAAC,WAAW;gBACrC,WAAW,EAAE,GAAG,EAAE;gBAClB,MAAM,EAAE,YAAY,CAAC,MAAM;aAC5B,CAAC,CAAC;YAEH,MAAM,CAAC,GAAG,CAAC,eAAe,EAAE,SAAS,CAAC,CAAC;YAEvC,OAAO,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QACpC,CAAC;QAED,YAAY,EAAE,OAAO,CAAC,YAAY;QAElC,QAAQ,CAAC,KAAK;YACZ,MAAM,eAAe,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;YAC7C,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;QAC1D,CAAC;QAED,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,aAAa,GAAG,EAAE;YAC9C,MAAM,eAAe,GAAG,qBAAqB,CAAC,UAAU,CAAC,CAAC;YAC1D,MAAM,CAAC,GAAG,CACR,eAAe,EACf,qBAAqB,CAAC;gBACpB,UAAU,EAAE,eAAe;gBAC3B,MAAM;gBACN,WAAW,EAAE,aAAa,CAAC,WAAW;gBACtC,MAAM,EAAE,aAAa,CAAC,MAAM;aAC7B,CAAC,CACH,CAAC;QACJ,CAAC;QAED,WAAW;YACT,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,cAAc,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC;QAC7E,CAAC;KACF,CAAC;IAEF,IAAI,OAAO,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;QACvC,OAAO,MAAM,CAAC,YAAY,CAAC;IAC7B,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,aAAa,CAAC,IAA6B;IAClD,OAAO,qBAAqB,CAAC;QAC3B,UAAU,EAAE,qBAAqB,CAAC,IAAI,CAAC,UAAU,CAAC;QAClD,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,WAAW,EAAE,aAAa,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS;QACjE,MAAM,EAAE,IAAI,CAAC,MAAM;KACpB,CAAC,CAAC;AACL,CAAC;AAED,SAAS,cAAc,CACrB,MAAgD,EAChD,UAAsB;IAEtB,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IAErC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,OAAO,qBAAqB,CAAC;YAC3B,UAAU;YACV,MAAM,EAAE,SAAS;YACjB,WAAW,EAAE,IAAI;SAClB,CAAC,CAAC;IACL,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC","sourcesContent":["import type { PermissionClient } from '../client/types';\nimport type { Permission } from '../registry/permissions';\nimport { assertKnownPermission, PERMISSIONS } from '../registry/permissions';\nimport {\n createPermissionState,\n type PermissionState,\n type PermissionStatus,\n} from '../state/permissionState';\n\n/***\n * Seed format accepted by the fake client.\n */\nexport type FakePermissionStateSeed =\n | PermissionState\n | {\n readonly permission: Permission;\n readonly status: PermissionStatus;\n readonly canAskAgain?: boolean;\n readonly reason?: string;\n };\n\n/***\n * Options for deterministic fake clients.\n */\nexport interface FakePermissionClientOptions {\n readonly initialStates?: readonly FakePermissionStateSeed[];\n readonly requestStates?: readonly FakePermissionStateSeed[];\n readonly now?: () => Date;\n readonly openSettings?: () => Promise<void>;\n}\n\n/***\n * Test client that stores permission states in memory.\n */\nexport interface FakePermissionClient extends PermissionClient {\n setState(state: FakePermissionStateSeed): void;\n setStatus(\n permission: Permission,\n status: PermissionStatus,\n options?: {\n readonly canAskAgain?: boolean;\n readonly reason?: string;\n },\n ): void;\n getSnapshot(): readonly PermissionState[];\n}\n\n/***\n * Creates a deterministic in-memory client for tests and examples.\n *\n * @readme\n * Fake clients make permission flows testable without native devices, browser\n * prompts, simulators, or network access.\n */\nexport function createFakePermissionClient(\n options: FakePermissionClientOptions = {},\n): FakePermissionClient {\n const now = options.now ?? (() => new Date());\n const states = new Map<Permission, PermissionState>();\n const requestStates = new Map<Permission, PermissionState>();\n\n for (const permission of PERMISSIONS) {\n states.set(\n permission,\n createPermissionState({\n permission,\n status: 'unknown',\n canAskAgain: true,\n }),\n );\n }\n\n for (const state of options.initialStates ?? []) {\n states.set(state.permission, normalizeSeed(state));\n }\n\n for (const state of options.requestStates ?? []) {\n requestStates.set(state.permission, normalizeSeed(state));\n }\n\n const client: FakePermissionClient = {\n getStatus(permission) {\n return Promise.resolve(getStoredState(states, assertKnownPermission(permission)));\n },\n\n request(permission) {\n const knownPermission = assertKnownPermission(permission);\n const requestState =\n requestStates.get(knownPermission) ?? getStoredState(states, knownPermission);\n const nextState = createPermissionState({\n permission: knownPermission,\n status: requestState.status,\n canAskAgain: requestState.canAskAgain,\n requestedAt: now(),\n reason: requestState.reason,\n });\n\n states.set(knownPermission, nextState);\n\n return Promise.resolve(nextState);\n },\n\n openSettings: options.openSettings,\n\n setState(state) {\n const normalizedState = normalizeSeed(state);\n states.set(normalizedState.permission, normalizedState);\n },\n\n setStatus(permission, status, statusOptions = {}) {\n const knownPermission = assertKnownPermission(permission);\n states.set(\n knownPermission,\n createPermissionState({\n permission: knownPermission,\n status,\n canAskAgain: statusOptions.canAskAgain,\n reason: statusOptions.reason,\n }),\n );\n },\n\n getSnapshot() {\n return PERMISSIONS.map((permission) => getStoredState(states, permission));\n },\n };\n\n if (options.openSettings === undefined) {\n delete client.openSettings;\n }\n\n return client;\n}\n\nfunction normalizeSeed(seed: FakePermissionStateSeed): PermissionState {\n return createPermissionState({\n permission: assertKnownPermission(seed.permission),\n status: seed.status,\n canAskAgain: seed.canAskAgain,\n requestedAt: 'requestedAt' in seed ? seed.requestedAt : undefined,\n reason: seed.reason,\n });\n}\n\nfunction getStoredState(\n states: ReadonlyMap<Permission, PermissionState>,\n permission: Permission,\n): PermissionState {\n const state = states.get(permission);\n\n if (state === undefined) {\n return createPermissionState({\n permission,\n status: 'unknown',\n canAskAgain: true,\n });\n }\n\n return state;\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { PermissionClient } from '../client/types';\nimport type { Permission } from '../registry/permissions';\nimport { assertKnownPermission, PERMISSIONS } from '../registry/permissions';\nimport {\n createPermissionState,\n type PermissionState,\n type PermissionStatus,\n} from '../state/permissionState';\n\n/***\n * Seed format accepted by the fake client.\n */\nexport type FakePermissionStateSeed =\n | PermissionState\n | {\n readonly permission: Permission;\n readonly status: PermissionStatus;\n readonly canAskAgain?: boolean;\n readonly reason?: string;\n };\n\n/***\n * Options for deterministic fake clients.\n */\nexport interface FakePermissionClientOptions {\n readonly initialStates?: readonly FakePermissionStateSeed[];\n readonly requestStates?: readonly FakePermissionStateSeed[];\n readonly now?: () => Date;\n readonly openSettings?: () => Promise<void>;\n}\n\n/***\n * Test client that stores permission states in memory.\n */\nexport interface FakePermissionClient extends PermissionClient {\n setState(state: FakePermissionStateSeed): void;\n setStatus(\n permission: Permission,\n status: PermissionStatus,\n options?: {\n readonly canAskAgain?: boolean;\n readonly reason?: string;\n },\n ): void;\n getSnapshot(): readonly PermissionState[];\n}\n\ninterface FakePermissionContext {\n readonly now: () => Date;\n readonly requestStates: Map<Permission, PermissionState>;\n readonly states: Map<Permission, PermissionState>;\n}\n\n/***\n * Creates a deterministic in-memory client for tests and examples.\n *\n * @readme\n * Fake clients make permission flows testable without native devices, browser\n * prompts, simulators, or network access.\n */\nexport function createFakePermissionClient(\n options: FakePermissionClientOptions = {},\n): FakePermissionClient {\n const context = createFakePermissionContext(options);\n const client = createFakePermissionOperations(context, options.openSettings);\n if (options.openSettings === undefined) delete client.openSettings;\n return client;\n}\n\nfunction createFakePermissionContext(options: FakePermissionClientOptions): FakePermissionContext {\n const context: FakePermissionContext = {\n now: options.now ?? (() => new Date()),\n states: new Map<Permission, PermissionState>(),\n requestStates: new Map<Permission, PermissionState>(),\n };\n\n for (const permission of PERMISSIONS) {\n context.states.set(\n permission,\n createPermissionState({\n permission,\n status: 'unknown',\n canAskAgain: true,\n }),\n );\n }\n\n for (const state of options.initialStates ?? []) {\n context.states.set(state.permission, normalizeSeed(state));\n }\n\n for (const state of options.requestStates ?? []) {\n context.requestStates.set(state.permission, normalizeSeed(state));\n }\n return context;\n}\n\nfunction createFakePermissionOperations(\n context: FakePermissionContext,\n openSettings: (() => Promise<void>) | undefined,\n): FakePermissionClient {\n const client: FakePermissionClient = {\n getStatus(permission) {\n return Promise.resolve(getStoredState(context.states, assertKnownPermission(permission)));\n },\n request(permission) {\n const knownPermission = assertKnownPermission(permission);\n const requestState =\n context.requestStates.get(knownPermission) ??\n getStoredState(context.states, knownPermission);\n const nextState = createPermissionState({\n permission: knownPermission,\n status: requestState.status,\n canAskAgain: requestState.canAskAgain,\n requestedAt: context.now(),\n reason: requestState.reason,\n });\n\n context.states.set(knownPermission, nextState);\n return Promise.resolve(nextState);\n },\n openSettings,\n setState(state) {\n const normalizedState = normalizeSeed(state);\n context.states.set(normalizedState.permission, normalizedState);\n },\n setStatus(permission, status, statusOptions = {}) {\n const knownPermission = assertKnownPermission(permission);\n context.states.set(\n knownPermission,\n createPermissionState({\n permission: knownPermission,\n status,\n canAskAgain: statusOptions.canAskAgain,\n reason: statusOptions.reason,\n }),\n );\n },\n getSnapshot() {\n return PERMISSIONS.map((permission) => getStoredState(context.states, permission));\n },\n };\n return client;\n}\n\nfunction normalizeSeed(seed: FakePermissionStateSeed): PermissionState {\n return createPermissionState({\n permission: assertKnownPermission(seed.permission),\n status: seed.status,\n canAskAgain: seed.canAskAgain,\n requestedAt: 'requestedAt' in seed ? seed.requestedAt : undefined,\n reason: seed.reason,\n });\n}\n\nfunction getStoredState(\n states: ReadonlyMap<Permission, PermissionState>,\n permission: Permission,\n): PermissionState {\n const state = states.get(permission);\n\n if (state === undefined) {\n return createPermissionState({\n permission,\n status: 'unknown',\n canAskAgain: true,\n });\n }\n\n return state;\n}\n"]}
@@ -1,7 +1,6 @@
1
1
  import type { PermissionClient } from '../client/types';
2
+ import { type BrowserPermissionState, type NotificationPermissionState } from './permissionStates';
2
3
  type BrowserPermissionName = 'camera' | 'microphone' | 'geolocation' | 'notifications';
3
- type BrowserPermissionState = 'granted' | 'denied' | 'prompt';
4
- type NotificationPermissionState = 'default' | 'denied' | 'granted';
5
4
  interface BrowserPermissionStatus {
6
5
  readonly state: BrowserPermissionState;
7
6
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/web/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAQxD,KAAK,qBAAqB,GAAG,QAAQ,GAAG,YAAY,GAAG,aAAa,GAAG,eAAe,CAAC;AACvF,KAAK,sBAAsB,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAC9D,KAAK,2BAA2B,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEpE,UAAU,uBAAuB;IAC/B,QAAQ,CAAC,KAAK,EAAE,sBAAsB,CAAC;CACxC;AAED,UAAU,kBAAkB;IAC1B,KAAK,CAAC,UAAU,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAA;KAAE,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;CAC/F;AAED,UAAU,oBAAoB;IAC5B,IAAI,IAAI,IAAI,CAAC;CACd;AAED,UAAU,eAAe;IACvB,SAAS,IAAI,SAAS,oBAAoB,EAAE,CAAC;CAC9C;AAED,UAAU,gBAAgB;IACxB,YAAY,CAAC,WAAW,EAAE;QACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;KAC1B,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;CAC9B;AAED,UAAU,eAAe;IACvB,kBAAkB,CAChB,OAAO,EAAE,MAAM,IAAI,EACnB,KAAK,EAAE,MAAM,IAAI,EACjB,OAAO,CAAC,EAAE;QACR,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAC7B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;KAC3B,GACA,IAAI,CAAC;CACT;AAED,UAAU,aAAa;IACrB,QAAQ,CAAC,WAAW,CAAC,EAAE,kBAAkB,CAAC;IAC1C,QAAQ,CAAC,YAAY,CAAC,EAAE,gBAAgB,CAAC;IACzC,QAAQ,CAAC,WAAW,CAAC,EAAE,eAAe,CAAC;CACxC;AAED,UAAU,gBAAgB;IACxB,QAAQ,CAAC,UAAU,EAAE,2BAA2B,CAAC;IACjD,iBAAiB,IAAI,OAAO,CAAC,2BAA2B,CAAC,GAAG,2BAA2B,CAAC;CACzF;AAED,UAAU,aAAa;IACrB,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IACnC,QAAQ,CAAC,SAAS,CAAC,EAAE,aAAa,CAAC;IACnC,QAAQ,CAAC,YAAY,CAAC,EAAE,gBAAgB,CAAC;CAC1C;AAED;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;CACjC;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,GAAE,0BAA+B,GACvC,gBAAgB,CAiDlB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/web/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAGxD,OAAO,EACL,KAAK,sBAAsB,EAE3B,KAAK,2BAA2B,EAKjC,MAAM,oBAAoB,CAAC;AAE5B,KAAK,qBAAqB,GAAG,QAAQ,GAAG,YAAY,GAAG,aAAa,GAAG,eAAe,CAAC;AACvF,UAAU,uBAAuB;IAC/B,QAAQ,CAAC,KAAK,EAAE,sBAAsB,CAAC;CACxC;AAED,UAAU,kBAAkB;IAC1B,KAAK,CAAC,UAAU,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAA;KAAE,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;CAC/F;AAED,UAAU,oBAAoB;IAC5B,IAAI,IAAI,IAAI,CAAC;CACd;AAED,UAAU,eAAe;IACvB,SAAS,IAAI,SAAS,oBAAoB,EAAE,CAAC;CAC9C;AAED,UAAU,gBAAgB;IACxB,YAAY,CAAC,WAAW,EAAE;QACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;KAC1B,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;CAC9B;AAED,UAAU,eAAe;IACvB,kBAAkB,CAChB,OAAO,EAAE,MAAM,IAAI,EACnB,KAAK,EAAE,MAAM,IAAI,EACjB,OAAO,CAAC,EAAE;QACR,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAC7B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;KAC3B,GACA,IAAI,CAAC;CACT;AAED,UAAU,aAAa;IACrB,QAAQ,CAAC,WAAW,CAAC,EAAE,kBAAkB,CAAC;IAC1C,QAAQ,CAAC,YAAY,CAAC,EAAE,gBAAgB,CAAC;IACzC,QAAQ,CAAC,WAAW,CAAC,EAAE,eAAe,CAAC;CACxC;AAED,UAAU,gBAAgB;IACxB,QAAQ,CAAC,UAAU,EAAE,2BAA2B,CAAC;IACjD,iBAAiB,IAAI,OAAO,CAAC,2BAA2B,CAAC,GAAG,2BAA2B,CAAC;CACzF;AAED,UAAU,aAAa;IACrB,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IACnC,QAAQ,CAAC,SAAS,CAAC,EAAE,aAAa,CAAC;IACnC,QAAQ,CAAC,YAAY,CAAC,EAAE,gBAAgB,CAAC;CAC1C;AAED;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;CACjC;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,GAAE,0BAA+B,GACvC,gBAAgB,CAiDlB"}
package/dist/web/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { assertKnownPermission, Permission } from '../registry/permissions';
2
- import { createPermissionState, } from '../state/permissionState';
2
+ import { createPermissionState } from '../state/permissionState';
3
+ import { formatErrorName, stateFromBrowserPermission, stateFromMediaError, stateFromNotificationPermission, unavailable, } from './permissionStates';
3
4
  /***
4
5
  * Creates a browser permission client using guarded structural globals.
5
6
  *
@@ -156,79 +157,4 @@ async function requestMedia(permission, global, constraints) {
156
157
  return stateFromMediaError(permission, error);
157
158
  }
158
159
  }
159
- function stateFromMediaError(permission, error) {
160
- const errorName = getErrorName(error);
161
- const unavailableErrorNames = new Set([
162
- 'AbortError',
163
- 'NotFoundError',
164
- 'NotReadableError',
165
- 'OverconstrainedError',
166
- 'SecurityError',
167
- ]);
168
- if (errorName !== undefined && unavailableErrorNames.has(errorName)) {
169
- return createPermissionState({
170
- permission,
171
- status: 'unavailable',
172
- canAskAgain: false,
173
- requestedAt: new Date(),
174
- reason: `Media permission request failed: ${errorName}.`,
175
- });
176
- }
177
- return createPermissionState({
178
- permission,
179
- status: 'denied',
180
- canAskAgain: false,
181
- requestedAt: new Date(),
182
- reason: errorName === undefined
183
- ? 'Media permission request was denied.'
184
- : `Media permission request was denied: ${errorName}.`,
185
- });
186
- }
187
- function stateFromNotificationPermission(permission, status) {
188
- if (status === 'granted') {
189
- return createPermissionState({
190
- permission,
191
- status: 'granted',
192
- canAskAgain: true,
193
- });
194
- }
195
- if (status === 'denied') {
196
- return createPermissionState({
197
- permission,
198
- status: 'denied',
199
- canAskAgain: false,
200
- });
201
- }
202
- return createPermissionState({
203
- permission,
204
- status: 'unknown',
205
- canAskAgain: true,
206
- });
207
- }
208
- function stateFromBrowserPermission(permission, status) {
209
- const normalizedStatus = status === 'prompt' ? 'unknown' : status;
210
- return createPermissionState({
211
- permission,
212
- status: normalizedStatus,
213
- canAskAgain: status === 'prompt' || status === 'granted',
214
- });
215
- }
216
- function unavailable(permission, reason) {
217
- return createPermissionState({
218
- permission,
219
- status: 'unavailable',
220
- canAskAgain: false,
221
- reason,
222
- });
223
- }
224
- function formatErrorName(error) {
225
- const errorName = getErrorName(error);
226
- return errorName === undefined ? '' : `: ${errorName}`;
227
- }
228
- function getErrorName(error) {
229
- if (typeof error !== 'object' || error === null || !('name' in error)) {
230
- return undefined;
231
- }
232
- return typeof error.name === 'string' && error.name.length > 0 ? error.name : undefined;
233
- }
234
160
  //# sourceMappingURL=index.js.map
@@ -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.3",
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": {
@@ -113,11 +113,10 @@
113
113
  "access": "public"
114
114
  },
115
115
  "scripts": {
116
- "build": "rm -rf dist tsconfig.tsbuildinfo && bun x tsc -p tsconfig.json",
117
- "changeset": "changeset",
118
- "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",
119
119
  "docs": "paradox",
120
- "knip": "ankhorage-knip",
121
120
  "lint": "ankhorage-eslint . --max-warnings=0",
122
121
  "lint:fix": "ankhorage-eslint . --fix --max-warnings=0",
123
122
  "format": "ankhorage-prettier --write .",
@@ -125,17 +124,17 @@
125
124
  "prepack": "bun run build",
126
125
  "test": "bun test src",
127
126
  "typecheck": "bun x tsc --noEmit -p tsconfig.json",
128
- "version-packages": "changeset version"
127
+ "version-packages": "ankhorage-changeset version",
128
+ "knip:check": "ankhorage-knip"
129
129
  },
130
130
  "devDependencies": {
131
- "@ankhorage/devtools": "^1.0.6",
132
- "@ankhorage/paradox": "^0.1.10",
133
- "@changesets/cli": "^2.31.0",
134
- "@types/bun": "^1.3.13",
131
+ "@ankhorage/devtools": "^1.9.2",
132
+ "@ankhorage/paradox": "^0.1.24",
133
+ "@types/bun": "^1.4.0",
135
134
  "@types/node": "^25.6.0",
136
135
  "@types/react": "^19.2.7",
137
136
  "react": "^19.2.1",
138
137
  "typescript": "^5.9.3"
139
138
  },
140
- "packageManager": "bun@1.3.13"
139
+ "packageManager": "bun@1.4.0"
141
140
  }
@@ -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
+ }