import {
PermissionInfo,
PermissionMap,
PermissionStatus,
PermissionType,
} from './Permissions.types';
/*
* TODO: Bacon: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia#Permissions
* Add messages to manifest like we do with iOS info.plist
*/
// https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia#Using_the_new_API_in_older_browsers
// Older browsers might not implement mediaDevices at all, so we set an empty object first
function _getUserMedia(constraints: MediaStreamConstraints): Promise<MediaStream> {
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
return navigator.mediaDevices.getUserMedia(constraints);
}
// Some browsers partially implement mediaDevices. We can't just assign an object
// with getUserMedia as it would overwrite existing properties.
// Here, we will just add the getUserMedia property if it's missing.
// First get ahold of the legacy getUserMedia, if present
const getUserMedia =
navigator.getUserMedia ||
(navigator as any).webkitGetUserMedia ||
(navigator as any).mozGetUserMedia ||
function() {
const error: any = new Error('Permission unimplemented');
error.code = 0;
error.name = 'NotAllowedError';
throw error;
};
return new Promise((resolve, reject) => {
getUserMedia.call(navigator, constraints, resolve, reject);
});
}
async function askForMediaPermissionAsync(
options: MediaStreamConstraints
): Promise<PermissionInfo> {
try {
await _getUserMedia(options);
return { status: PermissionStatus.GRANTED, expires: 'never' };
} catch ({ message }) {
// name: NotAllowedError
// code: 0
if (message === 'Permission dismissed') {
// message: Permission dismissed
return { status: PermissionStatus.UNDETERMINED, expires: 'never' };
} else {
// TODO: Bacon: [OSX] The system could deny access to chrome.
// TODO: Bacon: add: { status: 'unimplemented' }
// message: Permission denied
return { status: PermissionStatus.DENIED, expires: 'never' };
}
}
}
async function askForMicrophonePermissionAsync(): Promise<PermissionInfo> {
return await askForMediaPermissionAsync({ audio: true });
}
async function askForCameraPermissionAsync(): Promise<PermissionInfo> {
return await askForMediaPermissionAsync({ video: true });
}
async function askForLocationPermissionAsync(): Promise<PermissionInfo> {
return new Promise(resolve => {
navigator.geolocation.getCurrentPosition(
() => resolve({ status: PermissionStatus.GRANTED, expires: 'never' }),
({ code }: PositionError) => {
// https://developer.mozilla.org/en-US/docs/Web/API/PositionError/code
if (code === 1) {
resolve({ status: PermissionStatus.DENIED, expires: 'never' });
} else {
resolve({ status: PermissionStatus.UNDETERMINED, expires: 'never' });
}
}
);
});
}
async function getPermissionAsync(
permission: PermissionType,
shouldAsk: boolean
): Promise<PermissionInfo> {
switch (permission) {
case 'userFacingNotifications':
case 'notifications':
{
const { Notification = {} } = window as any;
if (Notification.requestPermission) {
let status = Notification.permission;
if (shouldAsk) {
status = await Notification.requestPermission();
}
if (!status || status === 'default') {
return { status: PermissionStatus.UNDETERMINED, expires: 'never' };
}
return { status, expires: 'never' };
}
}
break;
case 'location':
{
const { navigator = {} } = window as any;
if (navigator.permissions) {
const { state } = await navigator.permissions.query({ name: 'geolocation' });
if (state !== PermissionStatus.GRANTED && state !== PermissionStatus.DENIED) {
if (shouldAsk) {
return await askForLocationPermissionAsync();
}
return { status: PermissionStatus.UNDETERMINED, expires: 'never' };
}
return { status: state, expires: 'never' };
} else if (shouldAsk) {
// TODO: Bacon: should this function as ask async when not in chrome?
return await askForLocationPermissionAsync();
}
}
break;
case 'audioRecording':
if (shouldAsk) {
return await askForMicrophonePermissionAsync();
} else {
//TODO: Bacon: Is it possible to get this permission?
}
break;
case 'camera':
if (shouldAsk) {
return await askForCameraPermissionAsync();
} else {
//TODO: Bacon: Is it possible to get this permission?
}
break;
default:
break;
}
return { status: PermissionStatus.UNDETERMINED, expires: 'never' };
}
export default {
get name(): string {
return 'ExpoPermissions';
},
async getAsync(permissionTypes: PermissionType[]): Promise<PermissionMap> {
const results = {};
for (const permissionType of new Set(permissionTypes)) {
results[permissionType] = await getPermissionAsync(permissionType, /* shouldAsk */ false);
}
return results;
},
async askAsync(permissionTypes: PermissionType[]): Promise<PermissionMap> {
const results = {};
for (const permissionType of new Set(permissionTypes)) {
results[permissionType] = await getPermissionAsync(permissionType, /* shouldAsk */ true);
}
return results;
},
};