Search code examples
javascripthtmlpromisenavigator

Unable to call navigator.clipboard.writeText from within Promise


I am currently trying to check for read/write clipboard permissions before I actually execute navigator.clipboard.writeText(...) but it doesn't look like the writeText is being called. When I try to paste what I just copied, it pastes the previously copied item.

Below is my code.

const permissionsCheck = async () => {
    let read = await navigator.permissions.query({name: 'clipboard-read'})
    let write = await navigator.permissions.query({name: 'clipboard-write'})
    return write.state === 'granted' && read.state != 'denied'
}

const updateClipboard = (content) => { 
  navigator.clipboard.writeText(content).then(() => {
        console.log('Copied links!')
        return true
      /* clipboard successfully set */
    }, () => {
        console.log('Could not copy links!')
        return false
    });
}

const init = async () => {
    await permissionsCheck().then(allowed => {
        if (allowed){
           updateClipboard('Hello world')
        } else {
            alert('NOT ALLOWED')
        }
    })

} 

init()

I also tried to write it where init wasn't asynchronous, like so.

var init = () => {
    permissionsCheck().then(x=>{
        if (allowed){
           updateClipboard('Hello world')
        } else {
            alert('NOT ALLOWED')
        }
    })

}

I also tried this syntax.

var init = () => {
    const allowed = permissionsCheck()
    if (allowed){
        updateClipboard('Hello world')
    } else {
        alert('NOT ALLOWED')
    }
}

The problem with that second and third way I wrote it is that the permissions results were coming back as Promises (as checkPermissions is async), so the results were inconsistent. Sometimes the copy would work, other times it would give the alert first and then work on the second or third try.

Expected Behavior

init waits for the permissions Promise to resolve. Once it's resolved, run updateClipboard (if permissions come back positive). It seems like I can't find the happy medium between waiting for the permission results to come back AND executing writeText effectively, which is also asynchronous.

I've checked out the Mozilla Docs and scoured Stack but haven't found a use case quite like mine (or I haven't found a clear way to describe what I'm looking for). Any advice would be greatly appreciated.

Below is the snippet but on Stack Overflow it keeps returning false so I opened a CodePen using the same code where it works better.

const permissionsCheck = async () => {
let read = await navigator.permissions.query({name: 'clipboard-read'})
let write = await navigator.permissions.query({name: 'clipboard-write'})
return write.state === 'granted' && read.state != 'denied'
}

const updateClipboard = (content) => { 
  navigator.clipboard.writeText(content).then(() => {
    console.log('Copied links!')
    return true
  /* clipboard successfully set */
}, () => {
    console.log('Could not copy links!')
    return false
});
}

const init = async () => {
await permissionsCheck().then(allowed => {
    if (allowed){
       updateClipboard('Hello world')
    } else {
        alert('NOT ALLOWED')
    }
})

} 

init()


Solution

  • While there doesn't seem to be much of an issue in you implementation You could be more consistent in the way you write your asynchronous code and how you handle errors in order to prevent and better understand issues in the code:

    So a few issues:

    • permissionsCheck could fail, so I would at least handle this in your init function.
    • UpdateClipboard will never return anything, since your Boolean returns are in the callback functions invoked on promise fulfilment and therefore the overall function will return undefined. I would remove this logic or change to await based logic.
    • I would incorporate a try...catch at least at top level, to handle any promise rejections in your stack.
    • Try to avoid arrow function expressions for declared functions for readability.
    • For your issue where the document may not be in focus, add a document.hasFocus check.
    async function permissionsCheck() {
        const read = await navigator.permissions.query({
            name: 'clipboard-read',
        });
        const write = await navigator.permissions.query({
            name: 'clipboard-write',
        });
        return write.state === 'granted' && read.state !== 'denied';
    }
    
    async function updateClipboard(content) {
        await navigator.clipboard.writeText(content);
        console.log('Copied links!');
    }
    
    async function init() {
        try {
            const hasPermissions = await permissionsCheck();
    
            if (hasPermissions && document.hasFocus()) {
                updateClipboard('Hello world');
            } else {
                alert('NOT ALLOWED');
            }
        } catch (err) {
            console.error(err);
        }
    }
    
    init();
    
    

    Here, if any part of this call stack throws, init will catch and log it.