index.js 5.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. // @flow
  2. import {
  3. AsyncStorage,
  4. NativeModules,
  5. PermissionsAndroid,
  6. Platform,
  7. } from "react-native";
  8. const { RNPermissions } = NativeModules;
  9. export const ANDROID_PERMISSIONS = {
  10. ...PermissionsAndroid.PERMISSIONS,
  11. // Dangerous permissions not included in PermissionsAndroid (which might be unavailable)
  12. ACCEPT_HANDOVER: "android.permission.ACCEPT_HANDOVER",
  13. // @TODO Add more of them (https://developer.android.com/reference/android/Manifest.permission)
  14. };
  15. // function keyMirror<O: {}>(obj: O): $ObjMapi<O, <K>(k: K) => K> {
  16. // return Object.keys(obj).reduce((acc, key) => ({ ...acc, [key]: key }), {});
  17. // }
  18. export const IOS_PERMISSIONS = {
  19. BLUETOOTH_PERIPHERICAL: "BLUETOOTH_PERIPHERICAL",
  20. CALENDARS: "CALENDARS",
  21. CAMERA: "CAMERA",
  22. CONTACTS: "CONTACTS",
  23. FACE_ID: "FACE_ID",
  24. LOCATION_ALWAYS: "LOCATION_ALWAYS",
  25. LOCATION_WHEN_IN_USE: "LOCATION_WHEN_IN_USE",
  26. MEDIA_LIBRARY: "MEDIA_LIBRARY",
  27. MICROPHONE: "MICROPHONE",
  28. MOTION: "MOTION",
  29. NOTIFICATIONS: "NOTIFICATIONS",
  30. PHOTO_LIBRARY: "PHOTO_LIBRARY",
  31. REMINDERS: "REMINDERS",
  32. SIRI: "SIRI",
  33. SPEECH_RECOGNITION: "SPEECH_RECOGNITION",
  34. STOREKIT: "STOREKIT",
  35. };
  36. export const RESULTS = {
  37. GRANTED: "granted",
  38. DENIED: "denied",
  39. NEVER_ASK_AGAIN: "never_ask_again",
  40. UNAVAILABLE: "unavailable",
  41. };
  42. export type Permission =
  43. | $Keys<typeof ANDROID_PERMISSIONS>
  44. | $Keys<typeof IOS_PERMISSIONS>;
  45. export type PermissionStatus = $Values<typeof RESULTS>;
  46. export type Rationale = {|
  47. title: string,
  48. message: string,
  49. buttonPositive: string,
  50. buttonNegative?: string,
  51. buttonNeutral?: string,
  52. |};
  53. export type NotificationOption =
  54. | "badge"
  55. | "sound"
  56. | "alert"
  57. | "carPlay"
  58. | "criticalAlert"
  59. | "provisional";
  60. export type RequestConfig = {|
  61. notificationOptions?: NotificationOption[],
  62. rationale?: Rationale,
  63. |};
  64. const requestedKey = "@RNPermissions:requested";
  65. const platformPermissions = Object.values(
  66. Platform.OS === "android" ? ANDROID_PERMISSIONS : IOS_PERMISSIONS,
  67. );
  68. function assertValidPermission(permission: string) {
  69. if (platformPermissions.includes(permission)) {
  70. return;
  71. }
  72. const bulletsList = `• ${platformPermissions.join("\n• ")}`;
  73. const alertSentence = `Invalid ${
  74. Platform.OS
  75. } permission "${permission}". Must be one of:\n\n`;
  76. throw new Error(`${alertSentence}${bulletsList}`);
  77. }
  78. async function getRequestedPermissions() {
  79. const requested = await AsyncStorage.getItem(requestedKey);
  80. return requested ? JSON.parse(requested) : [];
  81. }
  82. async function setRequestedPermission(permission: string) {
  83. const requested = await getRequestedPermissions();
  84. if (requested.includes(permission)) {
  85. return;
  86. }
  87. return await AsyncStorage.setItem(
  88. requestedKey,
  89. JSON.stringify([...requested, permission]),
  90. );
  91. }
  92. async function internalCheck(
  93. permission: Permission,
  94. ): Promise<PermissionStatus> {
  95. if (Platform.OS !== "android") {
  96. return RNPermissions.check(permission);
  97. }
  98. if (!(await RNPermissions.isPermissionAvailable(permission))) {
  99. return RESULTS.UNAVAILABLE;
  100. }
  101. if (await PermissionsAndroid.check(permission)) {
  102. return RESULTS.GRANTED;
  103. }
  104. if (!(await getRequestedPermissions()).includes(permission)) {
  105. return RESULTS.DENIED;
  106. }
  107. return (await NativeModules.PermissionsAndroid.shouldShowRequestPermissionRationale(
  108. permission,
  109. ))
  110. ? RESULTS.DENIED
  111. : RESULTS.NEVER_ASK_AGAIN;
  112. }
  113. async function internalRequest(
  114. permission: string,
  115. config: RequestConfig,
  116. ): Promise<PermissionStatus> {
  117. const { notificationOptions, rationale } = config;
  118. if (Platform.OS !== "android") {
  119. return RNPermissions.request(permission, { notificationOptions });
  120. }
  121. if (!(await RNPermissions.isPermissionAvailable(permission))) {
  122. return RESULTS.UNAVAILABLE;
  123. }
  124. const result = await PermissionsAndroid.request(permission, rationale);
  125. await setRequestedPermission(permission);
  126. return result;
  127. }
  128. export function openSettings(): Promise<boolean> {
  129. return RNPermissions.openSettings();
  130. }
  131. export function check(permission: Permission): Promise<PermissionStatus> {
  132. assertValidPermission(permission);
  133. return internalCheck(permission);
  134. }
  135. export function request(
  136. permission: string,
  137. config: RequestConfig = {},
  138. ): Promise<PermissionStatus> {
  139. assertValidPermission(permission);
  140. return internalRequest(permission, config);
  141. }
  142. export function requestMultiple(
  143. permissions: Permission[],
  144. ): Promise<{ [permission: Permission]: PermissionStatus }> {
  145. permissions.forEach(permission => {
  146. assertValidPermission(permission);
  147. });
  148. return (async () => {
  149. const result = {};
  150. if (Platform.OS !== "android") {
  151. for (let index = 0; index < permissions.length; index++) {
  152. const permission = permissions[index];
  153. result[permission] = await request(permission);
  154. }
  155. return result;
  156. }
  157. const available = [];
  158. for (let index = 0; index < permissions.length; index++) {
  159. const permission = permissions[index];
  160. // @TODO Do checks in parallel to improve performances
  161. if (await RNPermissions.isPermissionAvailable(permission)) {
  162. available.push(permission);
  163. } else {
  164. result[permission] = RESULTS.UNAVAILABLE;
  165. }
  166. }
  167. return PermissionsAndroid.requestMultiple(available).then(statuses => ({
  168. ...result,
  169. ...statuses,
  170. }));
  171. })();
  172. }