@nativescript-community/perms
Version:
An unified permissions API for NativeScript on iOS and Android.
433 lines • 18 kB
JavaScript
import { Application, Trace, Utils } from '@nativescript/core';
import { getBoolean, setBoolean } from '@nativescript/core/application-settings';
import { SDK_VERSION } from '@nativescript/core/utils';
import { CLog, CLogTypes, Status } from './index.common';
export * from './index.common';
const MARSHMALLOW = 23;
const ANDROIDQ = 29;
const ANDROIDS = 31;
const ANDROID13 = 33;
const NativePermissionsTypes = [
'location',
'camera',
'mediaLocation',
'microphone',
'contacts',
'event',
'storage',
'photo',
'video',
'audio',
'callPhone',
'readSms',
'receiveSms',
'bluetoothScan',
'bluetoothConnect',
'bluetooth'
];
function getNativePermissions(permission, options) {
if (Trace.isEnabled()) {
CLog(CLogTypes.info, 'getNativePermissions', permission, options);
}
switch (permission) {
case 'location': {
const result = [];
const theOptions = options;
if ((theOptions?.coarse ?? true) !== false) {
result.push('android.permission.ACCESS_COARSE_LOCATION');
}
if ((theOptions?.precise ?? true) !== false) {
result.push('android.permission.ACCESS_FINE_LOCATION');
}
if (SDK_VERSION >= ANDROIDQ && (theOptions?.background ?? false) === true) {
result.push('android.permission.ACCESS_BACKGROUND_LOCATION');
}
return result;
}
case 'camera': {
return ['android.permission.CAMERA'];
}
case 'mediaLocation': {
if (SDK_VERSION >= ANDROIDQ) {
return ['android.permission.ACCESS_MEDIA_LOCATION'];
}
break;
}
case 'microphone': {
return ['android.permission.RECORD_AUDIO'];
}
case 'contacts': {
return ['android.permission.READ_CONTACTS'];
}
case 'event': {
return ['android.permission.READ_CALENDAR'];
}
case 'storage': {
const result = [];
const theOptions = options;
// const manage = options?.manage?? true;
if ((theOptions?.read ?? true) !== false) {
result.push('android.permission.READ_EXTERNAL_STORAGE');
}
if ((theOptions?.write ?? true) !== false) {
result.push('android.permission.WRITE_EXTERNAL_STORAGE');
}
// if (manage !== false) {
// result.push('android.permission.MANAGE_EXTERNAL_STORAGE');
// }
return result;
}
case 'photo': {
if (SDK_VERSION >= ANDROID13) {
return ['android.permission.READ_MEDIA_IMAGES'];
}
return ['android.permission.READ_EXTERNAL_STORAGE'];
}
case 'video': {
if (SDK_VERSION >= ANDROID13) {
return ['android.permission.READ_MEDIA_VIDEO'];
}
return ['android.permission.READ_EXTERNAL_STORAGE'];
}
case 'audio': {
if (SDK_VERSION >= ANDROID13) {
return ['android.permission.READ_MEDIA_AUDIO'];
}
return ['android.permission.READ_EXTERNAL_STORAGE'];
}
case 'callPhone': {
return ['android.permission.CALL_PHONE'];
}
case 'readSms': {
return ['android.permission.READ_SMS'];
}
case 'receiveSms': {
return ['android.permission.RECEIVE_SMS'];
}
case 'bluetoothScan': {
if (SDK_VERSION >= ANDROIDS) {
return ['android.permission.BLUETOOTH_SCAN'];
}
break;
}
case 'bluetoothConnect': {
if (SDK_VERSION >= ANDROIDS) {
return ['android.permission.BLUETOOTH_CONNECT'];
}
break;
}
case 'bluetooth': {
if (SDK_VERSION >= ANDROIDS) {
return ['android.permission.BLUETOOTH_ADVERTISE'];
}
break;
}
case 'notification': {
if (SDK_VERSION >= ANDROID13) {
// @ts-ignore
return ['android.permission.POST_NOTIFICATIONS'];
}
break;
}
default:
return [permission];
}
return [];
}
const STORAGE_KEY = '@NSPermissions:didAskPermission:';
const setDidAskOnce = (permission) => Promise.resolve().then(() => setBoolean(STORAGE_KEY + permission, true));
const getDidAskOnce = (permission) => Promise.resolve(!!getBoolean(STORAGE_KEY + permission));
var PermissionsAndroid;
(function (PermissionsAndroid) {
/**
* A list of specified "dangerous" permissions that require prompting the user
*/
// export const PERMISSIONS = {
// READ_CALENDAR: 'android.permission.READ_CALENDAR',
// WRITE_CALENDAR: 'android.permission.WRITE_CALENDAR',
// CAMERA: 'android.permission.CAMERA',
// READ_CONTACTS: 'android.permission.READ_CONTACTS',
// WRITE_CONTACTS: 'android.permission.WRITE_CONTACTS',
// GET_ACCOUNTS: 'android.permission.GET_ACCOUNTS',
// ACCESS_FINE_LOCATION: 'android.permission.ACCESS_FINE_LOCATION',
// ACCESS_COARSE_LOCATION: 'android.permission.ACCESS_COARSE_LOCATION',
// RECORD_AUDIO: 'android.permission.RECORD_AUDIO',
// READ_PHONE_STATE: 'android.permission.READ_PHONE_STATE',
// CALL_PHONE: 'android.permission.CALL_PHONE',
// READ_CALL_LOG: 'android.permission.READ_CALL_LOG',
// WRITE_CALL_LOG: 'android.permission.WRITE_CALL_LOG',
// ADD_VOICEMAIL: 'com.android.voicemail.permission.ADD_VOICEMAIL',
// USE_SIP: 'android.permission.USE_SIP',
// PROCESS_OUTGOING_CALLS: 'android.permission.PROCESS_OUTGOING_CALLS',
// BODY_SENSORS: 'android.permission.BODY_SENSORS',
// SEND_SMS: 'android.permission.SEND_SMS',
// RECEIVE_SMS: 'android.permission.RECEIVE_SMS',
// READ_SMS: 'android.permission.READ_SMS',
// RECEIVE_WAP_PUSH: 'android.permission.RECEIVE_WAP_PUSH',
// RECEIVE_MMS: 'android.permission.RECEIVE_MMS',
// READ_EXTERNAL_STORAGE: 'android.permission.READ_EXTERNAL_STORAGE',
// WRITE_EXTERNAL_STORAGE: 'android.permission.WRITE_EXTERNAL_STORAGE'
// };
/**
* Returns a promise resolving to a boolean value as to whether the specified
* permissions has been granted
*
* See https://facebook.github.io/react-native/docs/permissionsandroid.html#check
*/
async function check(permission) {
const context = Utils.android.getApplicationContext();
let result = true;
const granted = android.content.pm.PackageManager.PERMISSION_GRANTED;
if (!Array.isArray(permission)) {
permission = [permission];
}
if (SDK_VERSION < MARSHMALLOW) {
permission.forEach((p) => (result = result && context.checkPermission(p, android.os.Process.myPid(), android.os.Process.myUid()) === granted));
}
else {
permission.forEach((p) => (result = result && context.checkSelfPermission(p) === granted));
}
return result;
}
PermissionsAndroid.check = check;
/**
* Prompts the user to enable a permission and returns a promise resolving to a
* string value indicating whether the user allowed or denied the request
*
* See https://facebook.github.io/react-native/docs/permissionsandroid.html#request
*/
async function request(permission) {
// if (rationale) {
// const shouldShowRationale = await shouldShowRequestPermissionRationale(permission);
// if (shouldShowRationale) {
// return new Promise((resolve, reject) => {
// NativeModules.DialogManagerAndroid.showAlert(rationale, () => reject(new Error('Error showing rationale')), () => resolve(requestPermission(permission)));
// });
// }
// }
return (await requestMultiplePermissions([permission]))[permission];
}
PermissionsAndroid.request = request;
/**
* Prompts the user to enable multiple permissions in the same dialog and
* returns an object with the permissions as keys and strings as values
* indicating whether the user allowed or denied the request
*
* See https://facebook.github.io/react-native/docs/permissionsandroid.html#requestmultiple
*/
function requestMultiple(permissions) {
return requestMultiplePermissions(permissions);
}
PermissionsAndroid.requestMultiple = requestMultiple;
})(PermissionsAndroid || (PermissionsAndroid = {}));
// PermissionsAndroid = new PermissionsAndroid();
let mRequestCode = 0;
async function requestMultiplePermissions(permissions) {
const grantedPermissions = {};
const permissionsToCheck = [];
let checkedPermissionsCount = 0;
if (Trace.isEnabled()) {
CLog(CLogTypes.info, 'requestMultiplePermissions', permissions);
}
const context = Utils.android.getApplicationContext();
for (let i = 0; i < permissions.length; i++) {
const perm = permissions[i];
if (SDK_VERSION < MARSHMALLOW) {
grantedPermissions[perm] =
context.checkPermission(perm, android.os.Process.myPid(), android.os.Process.myUid()) === android.content.pm.PackageManager.PERMISSION_GRANTED ? Status.Authorized : Status.Denied;
checkedPermissionsCount++;
}
else if (context.checkSelfPermission(perm) === android.content.pm.PackageManager.PERMISSION_GRANTED) {
grantedPermissions[perm] = Status.Authorized;
checkedPermissionsCount++;
}
else {
permissionsToCheck.push(perm);
}
}
if (permissions.length === checkedPermissionsCount) {
return grantedPermissions;
}
const activity = Application.android.foregroundActivity || Application.android.startActivity;
return new Promise((resolve, reject) => {
try {
const requestCode = mRequestCode++;
if (Trace.isEnabled()) {
CLog(CLogTypes.info, 'requestPermissions', permissionsToCheck);
}
activity.requestPermissions(permissionsToCheck, requestCode);
const onActivityResult = (args) => {
if (args.requestCode === requestCode) {
Application.android.off(Application.android.activityRequestPermissionsEvent, onActivityResult);
const results = args.grantResults;
if (Trace.isEnabled()) {
CLog(CLogTypes.info, 'requestPermissions results', results);
}
for (let j = 0; j < permissionsToCheck.length; j++) {
const permission = permissionsToCheck[j];
if (results.length > j && results[j] === android.content.pm.PackageManager.PERMISSION_GRANTED) {
grantedPermissions[permission] = Status.Authorized;
}
else {
if (activity.shouldShowRequestPermissionRationale(permission)) {
grantedPermissions[permission] = Status.Denied;
}
else {
grantedPermissions[permission] = Status.NeverAskAgain;
}
}
}
resolve(grantedPermissions);
}
};
Application.android.on(Application.android.activityRequestPermissionsEvent, onActivityResult);
}
catch (e) {
reject(e);
}
});
}
function shouldShowRequestPermissionRationale(permission) {
if (SDK_VERSION < MARSHMALLOW) {
return Promise.resolve(false);
}
const activity = Application.android.foregroundActivity || Application.android.startActivity;
try {
if (Array.isArray(permission)) {
return Promise.resolve(permission.reduce((accu, p) => accu && activity.shouldShowRequestPermissionRationale(p), true));
}
return Promise.resolve(activity.shouldShowRequestPermissionRationale(permission));
}
catch (e) {
return Promise.reject(e);
}
}
export async function canOpenSettings() {
return true;
}
const SETTINGS_REQUEST = 5140;
export function openSettings() {
const activity = Application.android.foregroundActivity || Application.android.startActivity;
return new Promise((resolve, reject) => {
const onActivityResultHandler = (data) => {
if (data.requestCode === 5140) {
Application.android.off(Application.android.activityResultEvent, onActivityResultHandler);
resolve();
}
};
Application.android.on(Application.android.activityResultEvent, onActivityResultHandler);
const intent = new android.content.Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(android.net.Uri.parse('package:' + activity.getPackageName()));
activity.startActivityForResult(intent, SETTINGS_REQUEST);
});
}
const NOTIF_SETTINGS_REQUEST = 5141;
export function openNotificationSettings() {
const activity = Application.android.foregroundActivity || Application.android.startActivity;
return new Promise((resolve, reject) => {
const onActivityResultHandler = (data) => {
if (data.requestCode === NOTIF_SETTINGS_REQUEST) {
Application.android.off(Application.android.activityResultEvent, onActivityResultHandler);
resolve();
}
};
Application.android.on(Application.android.activityResultEvent, onActivityResultHandler);
const intent = new android.content.Intent();
if (SDK_VERSION >= 25) {
intent.setAction(android.provider.Settings.ACTION_APP_NOTIFICATION_SETTINGS);
intent.putExtra(android.provider.Settings.EXTRA_APP_PACKAGE, activity.getPackageName());
}
else {
intent.setAction(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
const uri = android.net.Uri.fromParts('package', activity.getPackageName(), null);
intent.setData(uri);
}
intent.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK);
activity.startActivityForResult(intent, NOTIF_SETTINGS_REQUEST);
});
}
export function getTypes() {
return NativePermissionsTypes;
}
export async function check(permission, options) {
const perms = getNativePermissions(permission, options);
if (Trace.isEnabled()) {
CLog(CLogTypes.info, 'check', permission, options, SDK_VERSION, perms);
}
if (!perms || perms.length === 0) {
return Status.Authorized;
}
const isAuthorized = await PermissionsAndroid.check(perms);
if (isAuthorized) {
if (SDK_VERSION >= ANDROIDQ && permission === 'location') {
const type = typeof options === 'string' ? options : options && options.type;
if (type === 'always') {
const backAuthorized = await PermissionsAndroid.check('android.permission.ACCESS_BACKGROUND_LOCATION');
return backAuthorized ? Status.Authorized : Status.Denied;
}
}
return Status.Authorized;
}
return getDidAskOnce(permission).then((didAsk) => {
if (didAsk) {
return shouldShowRequestPermissionRationale(perms).then((shouldShow) => (shouldShow ? Status.Denied : Status.Restricted));
}
return Status.Undetermined;
});
}
export function request(permission, options) {
if (Trace.isEnabled()) {
CLog(CLogTypes.info, 'request', permission, options);
}
let types = [];
let permissions = [];
if (typeof permission === 'object') {
// const keys = as IOSPermissionTypes[];
permissions = Object.keys(permission);
permissions.forEach((s) => {
// if (s.startsWith('android.permission.')) {
// types.push(s);
// } else {
types.push(...getNativePermissions(s, permission[s]));
// }
});
}
else {
permissions = [permission];
// if (permission.startsWith('android.permission.')) {
// types.push(permission);
// } else {
types = getNativePermissions(permission, options);
// }
}
if (types.length === 0) {
// if (Trace.isEnabled()) {
// CLog(CLogTypes.warning, permission, 'is not a valid permission type on Android');
// }
return Promise.resolve(Status.Authorized);
}
if (types.length > 1) {
return requestMultiplePermissions(types);
}
return PermissionsAndroid.request(types[0]).then((result) => {
// PermissionsAndroid.request() to native module resolves to boolean
// rather than string if running on OS version prior to Android M
if (typeof result === 'boolean') {
return result ? Status.Authorized : Status.Denied;
}
if (permissions.length > 1) {
return Promise.all(permissions.map(setDidAskOnce)).then(() => result);
}
return setDidAskOnce(permissions[0]).then(() => result);
});
}
export function checkMultiple(permissions) {
if (Trace.isEnabled()) {
CLog(CLogTypes.info, 'checkMultiple', permissions);
}
return Promise.all(Object.keys(permissions).map((permission) => check(permission, permissions[permission]).then((r) => [permission, r]))).then((result) => result.reduce((acc, value, index) => {
acc[value[0]] = value[1];
return acc;
}, {}));
}
//# sourceMappingURL=index.android.js.map