[{"data":1,"prerenderedAt":669},["ShallowReactive",2],{"post-programmatic-copy-to-clipboard-duckduckgo-android":3},{"id":4,"title":5,"body":6,"canonicalUrl":653,"cover":654,"date":655,"description":656,"draft":657,"extension":658,"hashnodeId":659,"meta":660,"navigation":89,"path":661,"seo":662,"slug":663,"stem":663,"tags":664,"__hash__":668},"posts\u002Fprogrammatic-copy-to-clipboard-duckduckgo-android.md","Handling programmatic \"Copy to Clipboard\" on DuckDuckGo Android browser",{"type":7,"value":8,"toc":644},"minimark",[9,14,32,46,49,53,56,60,151,176,183,189,193,196,240,247,256,325,340,344,347,604,620,624,634,637,640],[10,11,13],"h2",{"id":12},"the-background","The background",[15,16,17,18,25,26,31],"p",{},"This is going to be a short article. Recently I launched an in-browser daily puzzle game (",[19,20,24],"a",{"href":21,"rel":22},"https:\u002F\u002Fgoldroad.web.app",[23],"nofollow","GoldRoad","). Later on, I added the ability to share your stats for the day on social media using the ",[19,27,30],{"href":28,"rel":29},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FWeb_Share_API",[23],"Web Share API",". This is how it looks on my Android phone",[33,34,35],"table",{},[36,37,38],"thead",{},[39,40,41,44],"tr",{},[42,43],"th",{},[42,45],{},[15,47,48],{},"But sharing menu may not be available everywhere, considering this is a web app which can be opened on laptops as well. So I also added a fallback to copy the stats to the device's clipboard, feeling proud & clever at the same time having thought of fallbacks and all.",[50,51],"media-embed",{"url":52},"https:\u002F\u002Fmedia.giphy.com\u002Fmedia\u002FK3vVkohWnJVO8rZ5Su\u002Fgiphy.gif",[15,54,55],{},"And then one of my friends who plays the game religiously informs me that this share button doesn't do anything for him. Knowing him, I asked which browser are you using, and he says DuckDuckGo on Android. And believe me, all my smugness is gone now :-)",[10,57,59],{"id":58},"the-original-code","The original code",[61,62,67],"pre",{"className":63,"code":64,"language":65,"meta":66,"style":66},"language-javascript shiki shiki-themes github-light github-dark","const shareStats = async () => {\n  const text = 'The text to share';\n\n  if (window.navigator.share) {\n    try {\n      await window.navigator.share({\n        text,\n      });\n    } catch (error) {}\n  } else {\n    await window.navigator.clipboard.writeText(text);\n  }\n};\n","javascript","",[68,69,70,78,84,91,97,103,109,115,121,127,133,139,145],"code",{"__ignoreMap":66},[71,72,75],"span",{"class":73,"line":74},"line",1,[71,76,77],{},"const shareStats = async () => {\n",[71,79,81],{"class":73,"line":80},2,[71,82,83],{},"  const text = 'The text to share';\n",[71,85,87],{"class":73,"line":86},3,[71,88,90],{"emptyLinePlaceholder":89},true,"\n",[71,92,94],{"class":73,"line":93},4,[71,95,96],{},"  if (window.navigator.share) {\n",[71,98,100],{"class":73,"line":99},5,[71,101,102],{},"    try {\n",[71,104,106],{"class":73,"line":105},6,[71,107,108],{},"      await window.navigator.share({\n",[71,110,112],{"class":73,"line":111},7,[71,113,114],{},"        text,\n",[71,116,118],{"class":73,"line":117},8,[71,119,120],{},"      });\n",[71,122,124],{"class":73,"line":123},9,[71,125,126],{},"    } catch (error) {}\n",[71,128,130],{"class":73,"line":129},10,[71,131,132],{},"  } else {\n",[71,134,136],{"class":73,"line":135},11,[71,137,138],{},"    await window.navigator.clipboard.writeText(text);\n",[71,140,142],{"class":73,"line":141},12,[71,143,144],{},"  }\n",[71,146,148],{"class":73,"line":147},13,[71,149,150],{},"};\n",[15,152,153,154,159,160,163,164,169,170,175],{},"I had used the `writeText` method of the Clipboard API thinking it has wide availability (as can be seen ",[19,155,158],{"href":156,"rel":157},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FClipboard_API",[23],"here"," and in the below image) so it should work in almost any browser, and also because \"The ",[68,161,162],{},"\"clipboard-write\""," permission of the ",[19,165,168],{"href":166,"rel":167},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FPermissions_API",[23],"Permissions API"," is granted automatically to pages when they are in the active tab\". There was ",[19,171,174],{"href":172,"rel":173},"https:\u002F\u002Fweb.dev\u002Fasync-clipboard\u002F#:~:text=the%20Clipboard%20API%20is%20only%20supported%20for%20pages%20served%20over%20HTTPS",[23],"one more caveat"," I found, \"the Clipboard API is only supported for pages served over HTTPS\" (which is true anyway in my case).",[15,177,178],{},[179,180],"img",{"alt":181,"src":182},"clipboard api support on major browsers","\u002Fimages\u002Fposts\u002Fprogrammatic-copy-to-clipboard-duckduckgo-android\u002Fc3ab6836-ba2d-4eb6-8b4b-d407befc18c0-249c314cf7.png",[15,184,185],{},[179,186],{"alt":187,"src":188},"writeText support on major browsers","\u002Fimages\u002Fposts\u002Fprogrammatic-copy-to-clipboard-duckduckgo-android\u002F8ac8f490-a99b-495f-a947-e3e98cb94188-da5cbeffcb.png",[10,190,192],{"id":191},"the-issues-with-duckduckgo-ddg","The issues with DuckDuckGo (DDG)",[15,194,195],{},"After doing some debugging my investigations revealed the following",[197,198,199,206,219,229],"ol",{},[200,201,202,205],"li",{},[68,203,204],{},"Navigator.share"," is not available on DDG",[200,207,208,211,212,215,216],{},[68,209,210],{},"Navigator.clipboard.writeText"," throws ",[68,213,214],{},"NotAllowedError"," with a message saying ",[68,217,218],{},"Write permission denied",[200,220,221,222,225,226,205],{},"Since write permission was denied, so maybe somehow we could query and ask for the needed permission using the ",[19,223,168],{"href":166,"rel":224},[23],"? Alas! ",[68,227,228],{},"Navigator.permissions",[200,230,231,232,239],{},"Tried adding the clipboardWrite permission to the ",[19,233,236],{"href":234,"rel":235},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FMozilla\u002FAdd-ons\u002FWebExtensions\u002Fmanifest.json\u002Fpermissions",[23],[68,237,238],{},"manifest.json"," file (though that is applicable for a web extension only), of course, it didn't work",[10,241,243,244],{"id":242},"workaround-with-execcommand","Workaround with ",[68,245,246],{},"execCommand",[15,248,249,250,255],{},"Since all my trials with valid methods failed to yield results, I had to fall back to the ",[19,251,254],{"href":252,"rel":253},"https:\u002F\u002Fweb.dev\u002Fasync-clipboard\u002F#:~:text=be%20triggered%20using-,document.execCommand(%27copy%27),-and%20document.execCommand",[23],"document.execCommand",". As shown in the linked article, you could use the below code to copy text to the device clipboard.",[61,257,259],{"className":63,"code":258,"language":65,"meta":66,"style":66},"button.addEventListener('click', (e) => {\n  const input = document.createElement('input');\n  input.style.display = 'none';\n  document.body.appendChild(input);\n  input.value = text;\n  input.focus();\n  input.select();\n  const result = document.execCommand('copy');\n  if (result === 'unsuccessful') {\n    console.error('Failed to copy text.');\n  }\n  input.remove();\n});\n",[68,260,261,266,271,276,281,286,291,296,301,306,311,315,320],{"__ignoreMap":66},[71,262,263],{"class":73,"line":74},[71,264,265],{},"button.addEventListener('click', (e) => {\n",[71,267,268],{"class":73,"line":80},[71,269,270],{},"  const input = document.createElement('input');\n",[71,272,273],{"class":73,"line":86},[71,274,275],{},"  input.style.display = 'none';\n",[71,277,278],{"class":73,"line":93},[71,279,280],{},"  document.body.appendChild(input);\n",[71,282,283],{"class":73,"line":99},[71,284,285],{},"  input.value = text;\n",[71,287,288],{"class":73,"line":105},[71,289,290],{},"  input.focus();\n",[71,292,293],{"class":73,"line":111},[71,294,295],{},"  input.select();\n",[71,297,298],{"class":73,"line":117},[71,299,300],{},"  const result = document.execCommand('copy');\n",[71,302,303],{"class":73,"line":123},[71,304,305],{},"  if (result === 'unsuccessful') {\n",[71,307,308],{"class":73,"line":129},[71,309,310],{},"    console.error('Failed to copy text.');\n",[71,312,313],{"class":73,"line":135},[71,314,144],{},[71,316,317],{"class":73,"line":141},[71,318,319],{},"  input.remove();\n",[71,321,322],{"class":73,"line":147},[71,323,324],{},"});\n",[15,326,327,328,331,332,335,336,339],{},"But there is a twist here as well, if you set the ",[68,329,330],{},"input"," element's ",[68,333,334],{},"display"," to ",[68,337,338],{},"none"," as shown above, then execCommand returns true but doesn't copy anything to the clipboard in the case of DDG (haven't tried it with other browsers, as for everyone else Clipboard API is working fine, at least for the latest versions where I tested)",[10,341,343],{"id":342},"final-code","Final Code",[15,345,346],{},"So without further ado, here is my final code which is working fine on DDG (sometimes I do see the phone's keyboard popping up for a split second, but that is fine I think).",[61,348,350],{"className":63,"code":349,"language":65,"meta":66,"style":66},"const shareStats = async () => {\n  const text = `The text to share\\nWith multiple lines`;\n\n  if (window.navigator.share) {\n    try {\n      await window.navigator.share({\n        text,\n      });\n    } catch (error) {}\n\n    return;\n  }\n\n  await copyToClipboard(text);\n};\n\nconst copyToClipboard = async (text) => {\n  if (window.navigator.clipboard) {\n    try {\n      await window.navigator.clipboard.writeText(text);\n      return;\n    } catch (error) {}\n  }\n\n  const textarea = document.createElement('textarea');\n  textarea.style.position = 'fixed';\n  textarea.style.width = '1px';\n  textarea.style.height = '1px';\n  textarea.style.padding = 0;\n  textarea.style.border = 'none';\n  textarea.style.outline = 'none';\n  textarea.style.boxShadow = 'none';\n  textarea.style.background = 'transparent';\n\n  document.body.appendChild(textarea);\n  \n  textarea.textContent = text;\n  textarea.focus();\n  textarea.select();\n\n  const result = document.execCommand('copy');\n  textarea.remove();\n  if (!result) {\n    \u002F\u002F Show some error message to the user\n  } else {\n    \u002F\u002F Show a success message to the user mentioning the text is copied to their clipboard\n  }\n};\n",[68,351,352,356,361,365,369,373,377,381,385,389,393,398,402,406,412,417,422,428,434,439,445,451,456,461,466,472,478,484,490,496,502,508,514,520,525,531,537,543,549,555,560,565,571,577,583,588,594,599],{"__ignoreMap":66},[71,353,354],{"class":73,"line":74},[71,355,77],{},[71,357,358],{"class":73,"line":80},[71,359,360],{},"  const text = `The text to share\\nWith multiple lines`;\n",[71,362,363],{"class":73,"line":86},[71,364,90],{"emptyLinePlaceholder":89},[71,366,367],{"class":73,"line":93},[71,368,96],{},[71,370,371],{"class":73,"line":99},[71,372,102],{},[71,374,375],{"class":73,"line":105},[71,376,108],{},[71,378,379],{"class":73,"line":111},[71,380,114],{},[71,382,383],{"class":73,"line":117},[71,384,120],{},[71,386,387],{"class":73,"line":123},[71,388,126],{},[71,390,391],{"class":73,"line":129},[71,392,90],{"emptyLinePlaceholder":89},[71,394,395],{"class":73,"line":135},[71,396,397],{},"    return;\n",[71,399,400],{"class":73,"line":141},[71,401,144],{},[71,403,404],{"class":73,"line":147},[71,405,90],{"emptyLinePlaceholder":89},[71,407,409],{"class":73,"line":408},14,[71,410,411],{},"  await copyToClipboard(text);\n",[71,413,415],{"class":73,"line":414},15,[71,416,150],{},[71,418,420],{"class":73,"line":419},16,[71,421,90],{"emptyLinePlaceholder":89},[71,423,425],{"class":73,"line":424},17,[71,426,427],{},"const copyToClipboard = async (text) => {\n",[71,429,431],{"class":73,"line":430},18,[71,432,433],{},"  if (window.navigator.clipboard) {\n",[71,435,437],{"class":73,"line":436},19,[71,438,102],{},[71,440,442],{"class":73,"line":441},20,[71,443,444],{},"      await window.navigator.clipboard.writeText(text);\n",[71,446,448],{"class":73,"line":447},21,[71,449,450],{},"      return;\n",[71,452,454],{"class":73,"line":453},22,[71,455,126],{},[71,457,459],{"class":73,"line":458},23,[71,460,144],{},[71,462,464],{"class":73,"line":463},24,[71,465,90],{"emptyLinePlaceholder":89},[71,467,469],{"class":73,"line":468},25,[71,470,471],{},"  const textarea = document.createElement('textarea');\n",[71,473,475],{"class":73,"line":474},26,[71,476,477],{},"  textarea.style.position = 'fixed';\n",[71,479,481],{"class":73,"line":480},27,[71,482,483],{},"  textarea.style.width = '1px';\n",[71,485,487],{"class":73,"line":486},28,[71,488,489],{},"  textarea.style.height = '1px';\n",[71,491,493],{"class":73,"line":492},29,[71,494,495],{},"  textarea.style.padding = 0;\n",[71,497,499],{"class":73,"line":498},30,[71,500,501],{},"  textarea.style.border = 'none';\n",[71,503,505],{"class":73,"line":504},31,[71,506,507],{},"  textarea.style.outline = 'none';\n",[71,509,511],{"class":73,"line":510},32,[71,512,513],{},"  textarea.style.boxShadow = 'none';\n",[71,515,517],{"class":73,"line":516},33,[71,518,519],{},"  textarea.style.background = 'transparent';\n",[71,521,523],{"class":73,"line":522},34,[71,524,90],{"emptyLinePlaceholder":89},[71,526,528],{"class":73,"line":527},35,[71,529,530],{},"  document.body.appendChild(textarea);\n",[71,532,534],{"class":73,"line":533},36,[71,535,536],{},"  \n",[71,538,540],{"class":73,"line":539},37,[71,541,542],{},"  textarea.textContent = text;\n",[71,544,546],{"class":73,"line":545},38,[71,547,548],{},"  textarea.focus();\n",[71,550,552],{"class":73,"line":551},39,[71,553,554],{},"  textarea.select();\n",[71,556,558],{"class":73,"line":557},40,[71,559,90],{"emptyLinePlaceholder":89},[71,561,563],{"class":73,"line":562},41,[71,564,300],{},[71,566,568],{"class":73,"line":567},42,[71,569,570],{},"  textarea.remove();\n",[71,572,574],{"class":73,"line":573},43,[71,575,576],{},"  if (!result) {\n",[71,578,580],{"class":73,"line":579},44,[71,581,582],{},"    \u002F\u002F Show some error message to the user\n",[71,584,586],{"class":73,"line":585},45,[71,587,132],{},[71,589,591],{"class":73,"line":590},46,[71,592,593],{},"    \u002F\u002F Show a success message to the user mentioning the text is copied to their clipboard\n",[71,595,597],{"class":73,"line":596},47,[71,598,144],{},[71,600,602],{"class":73,"line":601},48,[71,603,150],{},[15,605,606,607,610,611,613,614,616,617,619],{},"The only thing to note above is the use of a ",[68,608,609],{},"textarea"," instead of an ",[68,612,330],{},", because if our text is multiline then the ",[68,615,330],{}," will eat away all our newlines. Also, since we're not hiding the ",[68,618,609],{},", we are adding some styles to make it inconsequential.",[10,621,623],{"id":622},"conclusion","Conclusion",[15,625,626,628,629,633],{},[68,627,246],{}," seems to be working at this point, but it has been made obsolete and may go away in future versions of all browsers. Just to be on the safer side, it is ",[630,631,632],"strong",{},"better to surround the execCommand call with a try-catch block",". Hopefully in the future DDG will allow us to copy text using the Clipboard API itself.",[15,635,636],{},"Do let me know in the comments section if you spot an error, or if the explanation is wrong anywhere.",[15,638,639],{},"Thanks for reading :-)",[641,642,643],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":66,"searchDepth":80,"depth":80,"links":645},[646,647,648,649,651,652],{"id":12,"depth":80,"text":13},{"id":58,"depth":80,"text":59},{"id":191,"depth":80,"text":192},{"id":242,"depth":80,"text":650},"Workaround with execCommand",{"id":342,"depth":80,"text":343},{"id":622,"depth":80,"text":623},null,"\u002Fimages\u002Fposts\u002Fprogrammatic-copy-to-clipboard-duckduckgo-android\u002F96955551ec39f5fa3904edb052a0e688-aef10b1585.jpeg","2023-01-04T14:42:44.420Z","A workaround to the NotAllowedError you get when trying to use the write \u002F writeText methods of the clipboard API",false,"md","clchrqwxw000h08jubj8rbaz4",{},"\u002Fprogrammatic-copy-to-clipboard-duckduckgo-android",{"title":5,"description":656},"programmatic-copy-to-clipboard-duckduckgo-android",[65,665,666,667],"web-development","duckduckgo","clipboard","DhAsgMmh6RbhHtDlVRnY4mCqVrR81go27xRBam7k9co",1780470201954]