Tutorial

A Tour of the JavaScript Permissions API

Published on January 5, 2020
author

Jack Misteli

A Tour of the JavaScript Permissions API

If you have ever created a web application which requires different features (like push notifications, webcam access, midi access), you probably noticed that their APIs look very different.

// for the geolocation API you need to call getCurrentPosition to check if geolocation is accessible
navigator.geolocation.getCurrentPosition(gotLocation, didNotGetLocation);

// for notifications we can directly check on the Notification object

if (Notification.permission == 'granted')
  // do notification stuff
if (Notification.permission == 'denied')
  // ask for notification access ....

This is not very handy.

The Permissions API allows us to have overview of the permissions available on our pages. What we mean by “permission” is whether we can access a specific feature with our code. Features that require permission to access them with code are called powerful features. Camera, midi, notifications, geolocation are all powerful features.

All powerful feature’s APIs are a bit different. Thus, it can be a pain to figure out what is the state of each feature’s permissions. With the Permissions API, we manage all the permission’s statuses with a single interface.

Permissions API Basics

The permission API is very experimental at this stage and should be used carefully. You should only use it if it is mission critical and you can keep up with future breaking changes. For instance, a few browsers used to support navigator.permissions.revoke but it is now deprecated.

At the time of the writing, query is the only property we can access from then permissions interface. query takes an object as an argument called a PermissionDescriptor. The permission descriptor has one field called name, which is the name of the permission you want to access.

// This query will give us information about the permissions attached to the camera
navigator.permissions.query({name: 'camera'})

The query returns a promise which resolves to a PermissionStatus. PermissionStatus has two fields: state and onchange.

navigator.permissions.query({name: 'camera'}).then( permissionStatus => {
  console.log(permissionStatus)
  // in my browser on this page it logs:
  //{
  //   status: "prompt",
  //   onchange: null,
  // }
})

state has 3 possible states: “granted”, “denied” and “prompt”. “granted” means that we have access to the feature. “denied” means that we won’t be able to access the feature. “prompt” means that the User-Agent (i.e. the browser) will ask the user for permission if we try to access that feature.

Some PermissionDescriptor have additional fields and you can read more about them here. For example, camera’s PermissionDescriptor has an additional field called deviceId if you want to target a specific camera. Your query might look like this: .query({name: 'camera', deviceId: "my-device-id"}).

onchange is an event listener which activates whenever the permissions of the queried feature changes.

navigator.permissions.query({name:'camera'}).then(res => {
  res.onchange = ((e)=>{
    // detecting if the event is a change
    if (e.type === 'change'){
      // checking what the new permissionStatus state is
      const newState = e.target.state
      if (newState === 'denied') {
        console.log('why did you decide to block us?')
      } else if (newState === 'granted') {
        console.log('We will be together forever!')
      } else {
        console.log('Thanks for reverting things back to normal')
      }
    }
  })
})

All Permissions API

There are a lot of different powerful permissions and browser support is very uneven. In the following script, you can see all the permissions described by W3C’s editor’s draft in the permissionsName variable. The getAllPermissions function returns an array with the different permissions available and their state. Please note that the result will change depending on your browser, the user’s preference and of course the website’s setup.

const permissionsNames = [
  "geolocation",
  "notifications",
  "push",
  "midi",
  "camera",
  "microphone",
  "speaker",
  "device-info",
  "background-fetch",
  "background-sync",
  "bluetooth",
  "persistent-storage",
  "ambient-light-sensor",
  "accelerometer",
  "gyroscope",
  "magnetometer",
  "clipboard",
  "display-capture",
  "nfc"
]

const getAllPermissions = async () => {
  const allPermissions = []
  // We use Promise.all to wait until all the permission queries are resolved
  await Promise.all(
    permissionsNames.map(async permissionName => {
        try {
          let permission
          switch (permissionName) {
            case 'push':
              // Not necessary but right now Chrome only supports push messages with  notifications
              permission = await navigator.permissions.query({name: permissionName, userVisibleOnly: true})
              break
            default:
              permission = await navigator.permissions.query({name: permissionName})
          }
          console.log(permission)
          allPermissions.push({permissionName, state: permission.state})
        }
        catch(e){
          allPermissions.push({permissionName, state: 'error', errorMessage: e.toString()})
        }
    })
  )
  return allPermissions
}

If I then run the following code in my developer console on Alligator.io:

(async function () {
  const allPermissions = await getAllPermissions()
  console.log(allPermissions)
})()

Here’s a screenshot of what I get at the console:

Permissions in Workers

So far we only used the navigator.permissions API because it is much easier to write concise examples. The Permissions API is also available inside of workers. WorkerNavigator.permissions allows us to check permissions inside our workers.

Hopefully you now have a better idea on how to use the Permissions API. It’s not very complicated, nor is it essential but it does make it much easier for us to manage permissions in our JavaScript-based apps. There will probably be some new features and changes to the API and we’ll keep you updated!

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the authors
Default avatar
Jack Misteli

author

While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
1 Comments


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

I just wanted to point out that unfortunately the screenshot is missing :-)

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Featured on Community

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more