123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. // @flow
  2. import {NativeModules, PermissionsAndroid, Platform} from 'react-native';
  3. import AsyncStorage from '@react-native-community/async-storage';
  4. const NativeModule = NativeModules.ReactNativePermissions;
  5. export type PermissionStatus =
  6. | 'authorized'
  7. | 'denied'
  8. | 'restricted'
  9. | 'undetermined';
  10. export type Rationale = {
  11. title: string,
  12. message: string,
  13. buttonPositive?: string,
  14. buttonNegative?: string,
  15. buttonNeutral?: string,
  16. };
  17. const ASYNC_STORAGE_KEY = '@RNPermissions:didAskPermission:';
  18. const PERMISSIONS = Platform.select({
  19. ios: {
  20. backgroundRefresh: 'backgroundRefresh',
  21. bluetooth: 'bluetooth',
  22. camera: 'camera',
  23. contacts: 'contacts',
  24. event: 'event',
  25. location: 'location',
  26. mediaLibrary: 'mediaLibrary',
  27. microphone: 'microphone',
  28. motion: 'motion',
  29. notification: 'notification',
  30. photo: 'photo',
  31. reminder: 'reminder',
  32. speechRecognition: 'speechRecognition',
  33. },
  34. android: {
  35. callPhone: PermissionsAndroid.PERMISSIONS.CALL_PHONE,
  36. camera: PermissionsAndroid.PERMISSIONS.CAMERA,
  37. coarseLocation: PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION,
  38. contacts: PermissionsAndroid.PERMISSIONS.READ_CONTACTS,
  39. event: PermissionsAndroid.PERMISSIONS.READ_CALENDAR,
  40. location: PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
  41. microphone: PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
  42. photo: PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
  43. readSms: PermissionsAndroid.PERMISSIONS.READ_SMS,
  44. receiveSms: PermissionsAndroid.PERMISSIONS.RECEIVE_SMS,
  45. sendSms: PermissionsAndroid.PERMISSIONS.SEND_SMS,
  46. storage: PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
  47. },
  48. });
  49. const IOS_DEFAULT_OPTIONS = {
  50. location: 'whenInUse',
  51. notification: ['alert', 'badge', 'sound'],
  52. };
  53. const ANDROID_RESULTS = {
  54. granted: 'authorized',
  55. denied: 'denied',
  56. never_ask_again: 'restricted',
  57. };
  58. const setDidAskOnce = (permission: string) =>
  59. AsyncStorage.setItem(ASYNC_STORAGE_KEY + permission, 'true');
  60. const getDidAskOnce = (permission: string) =>
  61. AsyncStorage.getItem(ASYNC_STORAGE_KEY + permission).then(item => !!item);
  62. class ReactNativePermissions {
  63. canOpenSettings(): Promise<boolean> {
  64. return Platform.OS === 'ios'
  65. ? NativeModule.canOpenSettings().then(result => !!result)
  66. : Promise.resolve(false);
  67. }
  68. openSettings(): Promise<void> {
  69. return Platform.OS === 'ios'
  70. ? NativeModule.openSettings()
  71. : Promise.reject(new Error("'openSettings' is deprecated on android"));
  72. }
  73. getTypes(): string[] {
  74. return Object.keys(PERMISSIONS);
  75. }
  76. check = (
  77. permission: string,
  78. options?: string | {type?: string},
  79. ): Promise<PermissionStatus> => {
  80. if (!PERMISSIONS[permission]) {
  81. return Promise.reject(
  82. new Error(
  83. `ReactNativePermissions: ${permission} is not a valid permission type`,
  84. ),
  85. );
  86. }
  87. if (Platform.OS === 'ios') {
  88. let type = IOS_DEFAULT_OPTIONS[permission];
  89. if (typeof options === 'string') {
  90. type = options;
  91. } else if (options && options.type) {
  92. type = options.type;
  93. }
  94. return NativeModule.getPermissionStatus(permission, type);
  95. }
  96. if (Platform.OS === 'android') {
  97. return PermissionsAndroid.check(PERMISSIONS[permission]).then(granted => {
  98. if (granted) {
  99. return 'authorized';
  100. }
  101. return getDidAskOnce(permission).then(didAsk => {
  102. if (didAsk) {
  103. return NativeModules.PermissionsAndroid.shouldShowRequestPermissionRationale(
  104. PERMISSIONS[permission],
  105. ).then(shouldShow => (shouldShow ? 'denied' : 'restricted'));
  106. }
  107. return 'undetermined';
  108. });
  109. });
  110. }
  111. return Promise.resolve('restricted');
  112. };
  113. request = (
  114. permission: string,
  115. options?: string | {type?: string, rationale?: Rationale},
  116. ): Promise<PermissionStatus> => {
  117. if (!PERMISSIONS[permission]) {
  118. return Promise.reject(
  119. new Error(
  120. `ReactNativePermissions: ${permission} is not a valid permission type`,
  121. ),
  122. );
  123. }
  124. if (Platform.OS === 'ios') {
  125. if (permission == 'backgroundRefresh') {
  126. return Promise.reject(
  127. new Error(
  128. 'ReactNativePermissions: You cannot request backgroundRefresh',
  129. ),
  130. );
  131. }
  132. let type = IOS_DEFAULT_OPTIONS[permission];
  133. if (typeof options === 'string') {
  134. type = options;
  135. } else if (options && options.type) {
  136. type = options.type;
  137. }
  138. return NativeModule.requestPermission(permission, type);
  139. }
  140. if (Platform.OS === 'android') {
  141. let rationale: Rationale;
  142. if (typeof options === 'object' && options.rationale) {
  143. rationale = options.rationale;
  144. }
  145. return PermissionsAndroid.request(
  146. PERMISSIONS[permission],
  147. rationale,
  148. ).then(result => {
  149. // PermissionsAndroid.request() to native module resolves to boolean
  150. // rather than string if running on OS version prior to Android M
  151. if (typeof result === 'boolean') {
  152. return result ? 'authorized' : 'denied';
  153. }
  154. return setDidAskOnce(permission).then(() => ANDROID_RESULTS[result]);
  155. });
  156. }
  157. return Promise.resolve('restricted');
  158. };
  159. checkMultiple = (
  160. permissions: string[],
  161. ): Promise<{[permission: string]: PermissionStatus}> => {
  162. return Promise.all(
  163. permissions.map(permission => this.check(permission)),
  164. ).then(result =>
  165. result.reduce((acc, value, i) => {
  166. acc[permissions[i]] = value;
  167. return acc;
  168. }, {}),
  169. );
  170. };
  171. }
  172. export default new ReactNativePermissions();