Has anyone else ran into this issue? I'm implementing the Web Share API to add a share button to a number of listings on a page. The code seems to be working fine initially, as I can click any of the "Share" buttons and the dialog will appear correctly.

However, if I close the dialog and try to click it again, I get an error saying "The request is not allowed by the user agent or the platform in the current context, possibly because the user denied permission."

Stranger still, I'm experiencing this same behavior on all these tutorial sites on how to implement the Web Share API (example: https://alligator.io/js/web-share-api/ - Try clicking the "Share Me!" button halfway down the page more than once on iOS Safari.)

Here's my code for reference:

const shareButtons = document.querySelectorAll('.share');

for (const button of shareButtons) {
    button.addEventListener("click", async () => {
        if (navigator.share) {
            try {
                await navigator.share({ 
                    url: button.getAttribute('data-url')
                });
            } catch (err) {
                  alert(err.message);
            }
        } else {
            // fallback
        }
    });
}

Appreciate any insight I can get on this - Thanks much

3

There are 3 best solutions below

3
On

Which version of IOS are you using?

It looks like there is a known bug in IOS 14.0.1 where the initial promise does not resolve.

https://developer.apple.com/forums/thread/662629

suggested fix from the article (which worked for me) is:

navigator.share(...)
.then(() => {
    // this will never happen in IOS 14.0.1
})
.catch(error => {
    //this will catch the second share attempt
    window.location.reload(true); // now share works again
});

or in your case with try/catch

const shareButtons = document.querySelectorAll('.share');

for (const button of shareButtons) {
    button.addEventListener("click", async () => {
        if (navigator.share) {
            try {
                await navigator.share({ 
                    url: button.getAttribute('data-url')
                });
            } catch (err) {
                  // this will catch the second share attempt on ios14
                  window.location.reload(true); // now share works again
                  alert(err.message);
            }
        } else {
            // fallback
        }
    });
}

The downside is it actually means the user will have to click twice to trigger the second share if they are using IOS14 - but it won't happen to other users

1
On

Thanks for all the answers on this really useful. I've played around with it and found the best solution for my use is to not use async and await and to use a promise instead as the share returns multiple times.

root.navigator.share({ title, url })
  .then(() => {
    /*
     this will be hit even if the message is logged
     due to iOS being a bit rubbish ATM
    */
  })
  .catch((err) => {
    const { message = '' } = err || {};
    if (message.indexOf('user denied permission') !== -1) {
      console.log(message);
    }
  });
1
On

EDIT: I have found a more concise solution that seems to solve this problem. It basically creates an iframe and uses the iframe's navigator to share instead of the page, that way you can hard reload the iframe every time you share to prevent it from hanging.

// create an invisible "sharing iframe" once
var sharingIframe     = document.createElement("iframe");
var sharingIframeBlob = new Blob([`<!DOCTYPE html><html>`],{type:"text/html"});
sharingIframe.src     = URL.createObjectURL(sharingIframeBlob);

sharingIframe.style.display = "none"; // make it so that it is hidden

document.documentElement.appendChild(sharingIframe); // add it to the DOM

// do not revoke the blob url because you will be reloading it later.
// also note that the following function must only be run after the iframe
// loads (and the iframe.contentWindow.navigator, which is what we are using)

function share(file){
  sharingIframe.contentWindow.navigator.share({files:[file]}).then(()=>{
    console.log("files shared");
    sharingIframe.contentWindow.location.reload(true); // reload sharing iframe to fix iOS bug
  }).catch((err)=>{
    console.error("user cancelled share");
    sharingIframe.contentWindow.location.reload(true); // reload sharing iframe to fix iOS bug
  });
}

My old, messier solution from earlier:

I encountered the same annoying bug, even with IOS 15+ safari still acts weird with navigator.share. my solution was to essentially create a temporary iframe when sharing something, using the iframe's contentWindow.navigator instead of the top window, and then closing/removing the iframe to kill the reference every time so it doesn't hang.

function share(file){

  // first, create temporary iframe and construct a temporary HTML file
  // this serves as a sort of modal to click thru and sits on top
  // of the main window until it is dismissed

  var iframe      = document.createElement("iframe");
  var iframeText  = `
  <!DOCTYPE html>
  <html style="position:absolute;width:100%;height:100%;background-color:rgba(0,0,0,0.5);">
    <div style="position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);padding:10px;background-color:rgba(255,255,255,0.5);;">
      <button id="cancel">cancel</button>
      <button id="download">download</button>
    </div>
  </html>
  `;
  var iframeBlob  = new Blob([iframeText],{type:"text/html"});
  iframe.src      = URL.createObjectURL(iframeBlob);
  
  iframe.style.all      = "unset";
  iframe.style.position = "fixed";
  iframe.style.width    = "100%";
  iframe.style.height   = "100%";
  
  document.body.appendChild(iframe);
  
  iframe.onload=()=>{

    URL.revokeObjectURL(iframe.src); // delete the object URL
    
    // select the elements in the iframe and add event handlers
    // to either share the file or dismiss and close the dialogue
    // (the iframe will close automatically after being dismissed
    // or if the user cancels share by tapping away)

    iframe.contentDocument.querySelector("#download").onclick=()=>{
      iframe.contentWindow.navigator.share({files:[file]}).then(()=>{
        console.log("files shared");
        iframe.contentWindow.close();
        iframe.remove();
      }).catch((err)=>{
        console.error("user cancelled share");
        iframe.contentWindow.close();
        iframe.remove();
      });
    };
    iframe.contentDocument.querySelector("#cancel").onclick=()=>{
      iframe.contentWindow.close();
      iframe.remove();
    };
  };

}