The W3C has published a working draft document about the Permissions API. These APIs allow to request the state of a permission and to know when that permission changes. How much is this information valuable?
Since 2020, the Permissions APIs specification reached the level of Working Draft at the W3C. This is the first step to the road of the Recommendation.
The document Permissions defines a method called query
to question about the state of a permission on a given element such as the permission to use the microphone or to access the geolocation, etc… Additionally to that, the document defines an event change
that is fired when the queried permission changes.
At this time of writing, the APIs are partially supported by the browsers. So at a first approach, we could deduce that it’s not a good idea to use them now due to the few support in browsers and due to the level of the specification: These API are not available in Safari and in Firefox, the set of permissions managed is very small and unfortunately, the microphone and the camera permissions are not yet supported.
But, after having used these APIs, I think that they bring to Chrome a way to enhance the user experience as the application knows the state of the microphone and camera permissions and so is able to adapt its behavior to that result. And so, for that part, I prefer implementing things specially for Chrome only.
Even if in most of the cases, the user sees, understands and authorizes the browser to access to the devices, in some cases, he declines voluntarily or not.
That case is complicated to manage from the application stands point due to the browser behavior.
First, the browser rejects any new attempts to access to the devices: This means that even if the application calls again the getUserMedia
API, an error of type NotAllowedError is returned. This is to prevent abuses, such when some applications want to force the access to a device and prompt the user until he accepts.
Secondly, Chrome doesn’t re-prompt for the permissions on reload: Once declined, the site goes to a “blocked” list. To recover from that situation, the user is able to authorize the site to access to the device by clicking on the device icon located in the URL bar or to remove it from the “blocked” list by using the settings. On other browsers such as Firefox or Safari, this could happens if the user actives the Remember this decision option (in Firefox) or clicks on the option Never for this website when prompted. But in these browsers, reloading the page prompt the user again.
Thirdly, Safari doesn’t display something when authorization is declined: On Chrome and Firefox, there is a specific icon displayed when the user declined the authorization, but not in Safari. In fact, this is the absence of the icon that indicates the issue. Not really user friendly and easily understandable…
This leads to a usability issue. If the user has accidentally declined the authorization, how could the application reacts when the authorization is changed by the user afterwards?
This is when permissions come in handy for the application.
Since a while, Chrome offers the Permissions API (Chrome 43). From the application, at any time, we can query for the state
of the permission.
Starting Chrome 64, the application is able to query for the microphone and for the camera permissions using the query
method.
try {const microphonePermission = await navigator.permissions.query({name:'microphone'});if (microphonePermission.state === "denied") {// Do something in case of the permission has been declined...)}
Using this permission, the application already knows that for example, the call to getUserMedia
will fail if the state of the permission is equals to denied
.
And so, the application can trigger a specific behavior such as informing the user or blocking some functionalities.
But there is more. Once got, a handler can be added to know when the permission changes: The following code reacts when the user authorizes the microphone.
// By reusing the permission object got previouslymicrophonePermission.onchange = function() {if(this.state === "granted") {// Do something in case of the permission has been granted...}}// or using the generic addEventListener functionmicrophonePermission.addEventListener('change', function() {if(this.state === "granted") {// Do something in case of the permission has been granted...}})
Mainly, we can face 2 cases:
granted
which means that the next call to getUserMedia
will not prompt the user.prompt
which means that the next call to getUserMedia
prompts the user.In both cases, the handler catches that change and allows the application to react accordingly such by enabling some buttons.
Additionally, to that, having the state of the permission is a great help in case of the application wants to explain to the users that the permission has been declined and how to restore it. It matters for example when revisiting the same website. As Chrome doesn’t re-prompt the user when he reloads the page, these API are mandatory to handle that case properly.
To notice, if the user is engaged into a call in listen only mode for example (which means without the need to share his microphone) and if he changes the permission on the fly for his microphone, you will not be able to just unmute him using the signaling part. To avoid complexing your code, the best is perhaps to hangup and restart the call using your normal flow. This is because even if the permission is changed, this has no affect on the current call.
When in Firefox, it starts well because Firefox implements the Permission API. But when executing the same code as for Chrome, it fails. Finally, this is worse because the microphone permission and the camera permission are not managed by Firefox. This means that when calling the same API as Chrome this results to an exception that need to be catched. Which is for me not friendly because there is no API that gives the list of permissions which are supported today. So, either the application uses it and always fails until that permission is supported or your application has to implement a dedicated case for Chrome and an other for Firefox.
//In Firefoxtry {const permissionStatus = await navigator.permissions.query({name:'microphone'})} catch (err) {// throws the following error each time the query is called for the microphone and for the camera// TypeError: 'microphone' (value of 'name' member of PermissionDescriptor) is not a valid value for enumeration PermissionName}
So at this time of writing and for WebRTC applications, the Permissions API don’t help.
That said, is it really mandatory in Firefox to deal with these APIs?
The short answer is NO: For example, when the user denies the authorization, the application catches the result and is able to react by disabling some actions. In the same way, the application can inform the user when he tries to do something that he has to first authorize the access to the device and then to continue his action. Alternatively, the application can inform the user that he has to reload the website to re-prompt the authorization to have access to his devices again. So there are many different ways to overcome that problem without needed to use the Permissions API.
Note: When changing the authorization on the fly from the crossed icon located in the URL bar, a message could appear that advises to reload the page even if it works. I don’t know in which case it could fail.
Being prompted each time the website is visited allows Firefox to deal with the denial of authorizations.
At this time of writing, things are clear when using Safari: the Permissions APIs are not yet available.
And as for Firefox, the user is prompted for the authorization each time he visits the website, the same logical blocks can be applied to Safari.
Handling issue around the denial of permissions is not easy but help improving the usability of your application.
Using the Permission API on Chrome is almost obligatory. And you, how do you manage the permissions in your application ?