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; }, };