Skip to content

Instantly share code, notes, and snippets.

@honungsburk
Last active July 3, 2023 11:40
Show Gist options
  • Save honungsburk/2ec136130d432698adb9a63f0e4e3117 to your computer and use it in GitHub Desktop.
Save honungsburk/2ec136130d432698adb9a63f0e4e3117 to your computer and use it in GitHub Desktop.
Hook to query browser permissions
import React from 'react';
// Some persmissions are not supported by all browsers, microphones are
// not supported by Safari for example.
export type PermissionNamePlus = PermissionName | 'microphone';
export class PermissionError extends Error {}
// Cache the permissions so we don't have to show a loading state
// every time we check a permission.
type Permissions = Record<PermissionNamePlus, PermissionState | null>;
const permissionsCache: Permissions = {
microphone: null,
geolocation: null,
notifications: null,
'persistent-storage': null,
push: null,
'screen-wake-lock': null,
'xr-spatial-tracking': null,
};
/**
* Note: Some permissions are not supported by all browsers, microphones for example.
* If the permission is not supported, the hook will return null.
*
* @param name - the name of the permission to check
* @returns
*/
export default function usePermission(
name: PermissionNamePlus,
): [PermissionState | null, boolean, PermissionError | undefined] {
const [permissions, setPermissions] = React.useState<PermissionState | null>(
permissionsCache[name],
);
const [isLoading, setIsLoading] = React.useState<boolean>(
permissionsCache[name] === null,
);
const [error, setError] = React.useState<PermissionError | undefined>();
React.useEffect(() => {
const set = (state: PermissionState | null) => {
setPermissions(state);
permissionsCache[name] = state;
};
const onChange = function (this: PermissionStatus) {
set(this.state);
};
let cleanup = () => {};
navigator.permissions
.query({ name: name as PermissionName })
.then((permission) => {
onChange.call(permission);
permission.onchange = onChange;
})
.catch((err) => {
set(null);
setError(new PermissionError(err.message, { cause: err }));
})
.finally(() => {
setIsLoading(false);
});
return () => {
// We must wrap in a lambda so that the correct cleanup function is called
// when the component unmounts
cleanup();
};
}, []);
return [permissions, isLoading, error];
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment