<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Hiep Bao Le</title>
    <description>The latest articles on DEV Community by Hiep Bao Le (@hieplpvip).</description>
    <link>https://dev.to/hieplpvip</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F390977%2Fdee63564-092a-4e28-b31d-78d13c4c3215.jpeg</url>
      <title>DEV Community: Hiep Bao Le</title>
      <link>https://dev.to/hieplpvip</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hieplpvip"/>
    <language>en</language>
    <item>
      <title>Hide secret message with zero-width characters</title>
      <dc:creator>Hiep Bao Le</dc:creator>
      <pubDate>Thu, 04 Jun 2020 18:26:06 +0000</pubDate>
      <link>https://dev.to/hieplpvip/hide-secret-message-with-zero-width-characters-3606</link>
      <guid>https://dev.to/hieplpvip/hide-secret-message-with-zero-width-characters-3606</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally posted on my &lt;a href="https://komsciguy.com/misc/hide-secret-message-with-zero-width-characters/" rel="noopener noreferrer"&gt;blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Zero-width characters are non-printing characters that are not displayed by most applications, which leads to the name "zero-width." They are Unicode characters, typically used to mark possible line break or join/separate characters in writing systems that use ligatures.&lt;/p&gt;

&lt;p&gt;As they are "invisible," anyone can use them to co‎​‌​‌​​​​​‌‌​‌‌​​​‌‌​​‌​‌​‌‌​​​​‌​‌‌‌​​‌‌​‌‌​​‌​‌​​‌​​​​​​‌‌‌​​‌‌​‌‌‌​‌​‌​‌‌‌​​​​​‌‌‌​​​​​‌‌​‌‌‌‌​‌‌‌​​‌​​‌‌‌​‌​​​​‌​​​​​​‌‌​‌‌​‌​‌‌‌‌​​‌​​‌​​​​​​‌‌​​​‌​​‌‌​‌‌​​​‌‌​‌‌‌‌​‌‌​​‌‌‌​​‌‌‌​‌​​​‌​​​​​​‌‌​‌​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌​‌‌‌​​‌‌​‌‌​​​‌‌​‌‌​‌​​‌​‌‌​​‌‌‌​‌‌‌​‌​‌​‌‌‌‌​​‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌‏nceal messages or information within plain text. Don’t believe me? I left a secret message in the first sentence. Read this post to know how it's possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Available zero-width characters
&lt;/h2&gt;

&lt;p&gt;So far I’ve found 9 zero-width characters in the Unicode characters table.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Character&lt;/th&gt;
&lt;th&gt;Unicode&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Zero-width space&lt;/td&gt;
&lt;td&gt;U+200B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zero-width non-joiner&lt;/td&gt;
&lt;td&gt;U+200C&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zero-width joiner&lt;/td&gt;
&lt;td&gt;U+200D&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Left-To-Right Mark&lt;/td&gt;
&lt;td&gt;U+200E&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Right-To-Left Mark&lt;/td&gt;
&lt;td&gt;U+200F&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Left-To-Right Embedding&lt;/td&gt;
&lt;td&gt;U+202A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Right-To-Left Embedding&lt;/td&gt;
&lt;td&gt;U+202B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Word joiner&lt;/td&gt;
&lt;td&gt;U+2060&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zero-width no-break space&lt;/td&gt;
&lt;td&gt;U+FEFF&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There may be more, but nine is more than enough. In theory, only two different zero-width characters are enough to insert any type of data. Though binary representation is usually large, we can make use of every zero-width characters to effectively reduce the length of encoded data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fingerprinting
&lt;/h2&gt;

&lt;p&gt;Zero-width characters can be used to fingerprint text. For example, someone within your team is leaking confidential information but you don’t know who. Just send each member a classified text with their name encoded in it. Wait for it to be leaked, then extract the name, and do whatever you like with them.&lt;/p&gt;

&lt;p&gt;Unlike other steganography techniques (such as utilizing noises in images, videos, sound as the container), zero-width characters are not removed if the text is formatted, copied, pasted. It’s really hard to detect them without special tools, as most text editors don’t render them. In addition, we’re not limited in the amount of data that can be encoded. However, editors do count zero-width characters, so encoding too much data within a short text makes it more suspicious.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tool
&lt;/h2&gt;

&lt;p&gt;To demonstrate the ability to hide secret messages with zero-width characters, I created a tool &lt;a href="http://lebaohiep.me/zwc-tool/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fexeib4rrwx2dxozjh50f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fexeib4rrwx2dxozjh50f.png" alt="ZWC Tool"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How does it work?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;TextEncoder&lt;/code&gt; to the secret message from &lt;code&gt;String&lt;/code&gt; to &lt;code&gt;Uint8Array&lt;/code&gt;, which is an array of 8-bit unsigned integers.&lt;/li&gt;
&lt;li&gt;Convert each integer to 8 bits, then convert each bit to zero-width characters:

&lt;ul&gt;
&lt;li&gt;Bit value 0 is encoded as &lt;code&gt;Zero-width space (U+200B)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Bit value 1 is encoded as &lt;code&gt;Zero-width non-joiner (U+200C)&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Hide the encoded string in the middle of the carrier message.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;In addition, two other zero-width characters are used to mark the beginning and ending of the encoded string:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Left-To-Right Mark (U+200E)&lt;/code&gt; marks the beginning&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Right-To-Left Mark (U+200F)&lt;/code&gt; marks the end&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes it easier to detect the position of the encoded string when decoding it.&lt;/p&gt;

&lt;p&gt;Please refer to &lt;a href="https://github.com/hieplpvip/zwc-tool" rel="noopener noreferrer"&gt;source code&lt;/a&gt; for more details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Detect zero-width characters
&lt;/h2&gt;

&lt;p&gt;Use any text editor that supports rendering of zero-width characters.&lt;/p&gt;

&lt;p&gt;For quick test, you can use Chrome Developer Tools console:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F9whd8ply9xr0773ot8re.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F9whd8ply9xr0773ot8re.png" alt="Chrome Developer Tools console"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://chrome.google.com/webstore/detail/replace-zero-width-charac/lgaiigbekmcejmhenhhleeaicbcjjddi/related" rel="noopener noreferrer"&gt;This Chrome extension&lt;/a&gt; will convert any zero-width characters to emojis.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://medium.com/@umpox/be-careful-what-you-copy-invisibly-inserting-usernames-into-text-with-zero-width-characters-18b4e6f17b66" rel="noopener noreferrer"&gt;Be careful what you copy: Invisibly inserting usernames into text with Zero-Width Characters&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>steganography</category>
      <category>fingerprinting</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Get a free domain with Freenom and Cloudflare</title>
      <dc:creator>Hiep Bao Le</dc:creator>
      <pubDate>Sun, 24 May 2020 07:55:36 +0000</pubDate>
      <link>https://dev.to/hieplpvip/get-a-free-domain-with-freenom-and-cloudflare-k1j</link>
      <guid>https://dev.to/hieplpvip/get-a-free-domain-with-freenom-and-cloudflare-k1j</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally posted on my &lt;a href="https://komsciguy.com/misc/get-a-free-domain-with-freenom-and-cloudflare/" rel="noopener noreferrer"&gt;blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Freenom is a domain registrar that provides unlimited free domains that last for one year. The only limit is that you can only get domains ending in &lt;code&gt;.tk&lt;/code&gt;, &lt;code&gt;.ml&lt;/code&gt;, &lt;code&gt;.ga&lt;/code&gt;, &lt;code&gt;.cf&lt;/code&gt;, or &lt;code&gt;.gq&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In this post, I’ll show you how to register a domain at Freenom step-by-step. Then I’ll show you how to replace Freenom’s default nameservers (which are pretty bad) with Cloudflare’s to have better control of your domains.&lt;/p&gt;

&lt;h1&gt;
  
  
  Register domain at Freenom
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Go to &lt;a href="https://www.freenom.com" rel="noopener noreferrer"&gt;https://www.freenom.com&lt;/a&gt; and get yourself an account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Go to the homepage. Type the domain you would like to register in the &lt;strong&gt;Find a new FREE domain&lt;/strong&gt; field. Then click &lt;strong&gt;Check Availability&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fhieplpvip%2Fblog-images%2Fraw%2Fmaster%2Ffreenom%2F1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fhieplpvip%2Fblog-images%2Fraw%2Fmaster%2Ffreenom%2F1.png" alt="alt text for accessibility"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; If the domain name is available click &lt;strong&gt;Get it now!&lt;/strong&gt; and then click &lt;strong&gt;Checkout&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fhieplpvip%2Fblog-images%2Fraw%2Fmaster%2Ffreenom%2F2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fhieplpvip%2Fblog-images%2Fraw%2Fmaster%2Ffreenom%2F2.png" alt="alt text for accessibility"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4:&lt;/strong&gt; Set the period to 12 months. Then click &lt;strong&gt;Continue&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fhieplpvip%2Fblog-images%2Fraw%2Fmaster%2Ffreenom%2F3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fhieplpvip%2Fblog-images%2Fraw%2Fmaster%2Ffreenom%2F3.png" alt="alt text for accessibility"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5:&lt;/strong&gt; Check &lt;strong&gt;I have read and agree to the Terms &amp;amp; Conditions&lt;/strong&gt;. Then click &lt;strong&gt;Complete Order&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fhieplpvip%2Fblog-images%2Fraw%2Fmaster%2Ffreenom%2F4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fhieplpvip%2Fblog-images%2Fraw%2Fmaster%2Ffreenom%2F4.png" alt="alt text for accessibility"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 6:&lt;/strong&gt; You’ve successfully registered your free domains. But we’re not done yet. Let’s move to the Cloudflare part.&lt;/p&gt;

&lt;h1&gt;
  
  
  Add your domains to Cloudflare
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Go to &lt;a href="https://www.cloudflare.com" rel="noopener noreferrer"&gt;https://www.cloudflare.com&lt;/a&gt; and get yourself an account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Click &lt;strong&gt;Add a site&lt;/strong&gt; in Account Dashboard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fhieplpvip%2Fblog-images%2Fraw%2Fmaster%2Ffreenom%2F5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fhieplpvip%2Fblog-images%2Fraw%2Fmaster%2Ffreenom%2F5.png" alt="alt text for accessibility"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; Type in your domain and click &lt;strong&gt;Add site&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4:&lt;/strong&gt; Select plan &lt;strong&gt;Free&lt;/strong&gt; and click &lt;strong&gt;Confirm plan&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5:&lt;/strong&gt; Cloudflare will scan for existing DNS records. Wait until it finishes, and click &lt;strong&gt;Continue&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 6:&lt;/strong&gt; Cloudflare will give you two nameservers that you need to set in Freenom.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fhieplpvip%2Fblog-images%2Fraw%2Fmaster%2Ffreenom%2F6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fhieplpvip%2Fblog-images%2Fraw%2Fmaster%2Ffreenom%2F6.png" alt="alt text for accessibility"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 7:&lt;/strong&gt; Go back to Freenom. Click &lt;strong&gt;Services&lt;/strong&gt; &amp;gt; &lt;strong&gt;My Domains&lt;/strong&gt;. You should see all domains you’ve registered. Click &lt;strong&gt;Manage Domain&lt;/strong&gt; on the domain that you’re configuring.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fhieplpvip%2Fblog-images%2Fraw%2Fmaster%2Ffreenom%2F7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fhieplpvip%2Fblog-images%2Fraw%2Fmaster%2Ffreenom%2F7.png" alt="alt text for accessibility"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 8:&lt;/strong&gt; Click &lt;strong&gt;Management Tools&lt;/strong&gt; &amp;gt; &lt;strong&gt;Nameservers&lt;/strong&gt; &amp;gt; &lt;strong&gt;Use custom nameservers (enter below)&lt;/strong&gt;. Now enter the nameservers provided by Cloudflare, and click &lt;strong&gt;Change Nameservers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fhieplpvip%2Fblog-images%2Fraw%2Fmaster%2Ffreenom%2F8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fhieplpvip%2Fblog-images%2Fraw%2Fmaster%2Ffreenom%2F8.png" alt="alt text for accessibility"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 9:&lt;/strong&gt; Go back to Cloudflare, click &lt;strong&gt;Done, check nameservers&lt;/strong&gt;. It will take a while, so please be patient.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 10:&lt;/strong&gt; Enjoy your free domain!&lt;/p&gt;

&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;These domains are not suitable for professional usage, but if you can’t afford to buy a domain or just want to find one to test your project, Freenom + Cloudflare is a perfect choice. Personally, I use it for a lot of my projects.&lt;/p&gt;

&lt;p&gt;I hope you’ll find this post helpful!&lt;/p&gt;

</description>
      <category>domain</category>
      <category>webdev</category>
    </item>
    <item>
      <title>A better way to copy text to Clipboard in JavaScript</title>
      <dc:creator>Hiep Bao Le</dc:creator>
      <pubDate>Sun, 24 May 2020 06:46:50 +0000</pubDate>
      <link>https://dev.to/hieplpvip/a-better-way-to-copy-text-to-clipboard-in-javascript-2cng</link>
      <guid>https://dev.to/hieplpvip/a-better-way-to-copy-text-to-clipboard-in-javascript-2cng</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally posted on my &lt;a href="https://komsciguy.com/js/a-better-way-to-copy-text-to-clipboard-in-javascript/"&gt;blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  The problem
&lt;/h1&gt;

&lt;p&gt;Typically, this is how copying text is done (taken from &lt;a href="https://hackernoon.com/copying-text-to-clipboard-with-javascript-df4d4988697f"&gt;here&lt;/a&gt;):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a &lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt; element to be appended to the document. Set its value to the string that we want to copy to the clipboard.&lt;/li&gt;
&lt;li&gt;Append said &lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt; element to the current HTML document.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;HTMLInputElement.select()&lt;/code&gt; to select the contents of the &lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt; element.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;document.execCommand('copy')&lt;/code&gt; to copy the contents of the &lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt; to the clipboard.&lt;/li&gt;
&lt;li&gt;Remove the &lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt; element from the document.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The code looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;copyToClipboard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;textarea&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;select&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;execCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;copy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;removeChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;There are two problems with this approach:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;There may be some flashing due to the temporary  element.&lt;/li&gt;
&lt;li&gt;It will unselect whatever the user is selecting.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We can work around both, but the function will become much longer.&lt;/p&gt;

&lt;h1&gt;
  
  
  Solution
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;When the user initiates a copy action, the user agent fires a clipboard event name copy.&lt;/p&gt;

&lt;p&gt;W3C Specification&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;Use addEventListener to attach our custom event handler, which will override the current data with our text.&lt;/li&gt;
&lt;li&gt;Use document.execCommand('copy') to trigger the copy action.&lt;/li&gt;
&lt;li&gt;Use removeEventListener to remove our event handler.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;copyToClipboard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;listener&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clipboardData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/plain&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;copy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;execCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;copy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;copy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  Bonus
&lt;/h1&gt;

&lt;p&gt;You can even copy rich text!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;copyRichText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;listener&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clipboardData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clipboardData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/plain&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;copy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;execCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;copy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;copy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;copyRichText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;i&amp;gt;Markup&amp;lt;/i&amp;gt; &amp;lt;b&amp;gt;text&amp;lt;/b&amp;gt;. Paste me into a rich text editor.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  Browser compatibility
&lt;/h1&gt;

&lt;p&gt;According to MDN Web Docs, this should work on all major browsers except Internet Explorer.&lt;/p&gt;

</description>
      <category>todayilearned</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How I cloned Chatible for my school</title>
      <dc:creator>Hiep Bao Le</dc:creator>
      <pubDate>Sat, 23 May 2020 14:40:27 +0000</pubDate>
      <link>https://dev.to/hieplpvip/how-i-created-a-clone-of-chatible-for-my-school-2a13</link>
      <guid>https://dev.to/hieplpvip/how-i-created-a-clone-of-chatible-for-my-school-2a13</guid>
      <description>&lt;p&gt;If you don't know what Chatible is, it's a Messenger bot that allows you to chat with someone random. I love its idea, but I don't want to chat with a total stranger. It would be great if we have something in common. Studying at the same school sounds good, so I cloned Chatible for my school, which I call PTNK Chatible (PTNK stands for my school name).&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="http://m.me/ptnkchat"&gt;PTNK Chatible&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Currently only Vietnamese is supported. English will be added soon.&lt;/p&gt;

&lt;h2&gt;
  
  
  Source code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vWogaON8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/github-logo-28d89282e0daa1e2496205e2f218a44c755b0dd6536bbadf5ed5a44a7ca54716.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/ptnkchat"&gt;
        ptnkchat
      &lt;/a&gt; / &lt;a href="https://github.com/ptnkchat/ptnkchat"&gt;
        ptnkchat
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      PTNK Chatible source code
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h2&gt;
PTNK Chatible&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://heroku.com/deploy" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/83b0e95b38892b49184e07ad572c94c8038323fb/68747470733a2f2f7777772e6865726f6b7563646e2e636f6d2f6465706c6f792f627574746f6e2e737667" alt="Deploy"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
Chatible clone written in TypeScript, based on Node, Express and Mongo&lt;/h4&gt;
&lt;p&gt;Demo: &lt;a href="https://m.me/ptnkchat" rel="nofollow"&gt;https://m.me/ptnkchat&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
Basic instruction&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Deploy to Heroku using the deploy button.&lt;/li&gt;
&lt;li&gt;Create a cluster on MongoDB Atlas. Whitelist IP addresses.&lt;/li&gt;
&lt;li&gt;Create an app on Facebook. Install Webhook. Get app secret and tokens.&lt;/li&gt;
&lt;li&gt;Set Heroku's &lt;code&gt;Config Vars&lt;/code&gt;. Check &lt;a href="https://raw.githubusercontent.com/ptnkchat/ptnkchat/master/src/config/index.ts"&gt;here&lt;/a&gt; to know which variables you need to set.&lt;/li&gt;
&lt;li&gt;Enjoy!&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
Features&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Admin dashboard (&lt;a href="https://github.com/ptnkchat/ptnkchat.github.io"&gt;code&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Pair by gender (e.g. male with female)&lt;/li&gt;
&lt;li&gt;Send cute dog/cat pictures&lt;/li&gt;
&lt;li&gt;Customizable message templates&lt;/li&gt;
&lt;li&gt;Cache database in memory to increase performance&lt;/li&gt;
&lt;li&gt;Developed with performance in mind&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
Planned features&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Allow editing profile via Messenger Webview&lt;/li&gt;
&lt;li&gt;Limiting rate of requests sent out to avoid being converted to &lt;a href="https://developers.facebook.com/docs/messenger-platform/send-messages/high-mps" rel="nofollow"&gt;high-MPS&lt;/a&gt; page&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
License&lt;/h2&gt;
&lt;p&gt;This project is licensed under the MIT License - see the &lt;a href="https://raw.githubusercontent.com/ptnkchat/ptnkchat/master/LICENSE.md"&gt;LICENSE.md&lt;/a&gt; file for details&lt;/p&gt;
&lt;h2&gt;
Credit&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Nguyen Xuan Son (a.k.a Nui or &lt;a href="https://github.com/ngxson"&gt;@ngxson&lt;/a&gt;) for &lt;a href="https://github.com/ngxson/chatbot-cnh"&gt;Chatbot CHN&lt;/a&gt; on which this project was originally based&lt;/li&gt;
&lt;li&gt;Le…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/ptnkchat/ptnkchat"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How I built it
&lt;/h2&gt;

&lt;p&gt;PTNK Chatible is written in TypeScript, run on Node.js.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/express"&gt;express&lt;/a&gt; is used to handle webhook events from Facebook.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/phin"&gt;phin&lt;/a&gt; is used to send messages to Facebook. I have tried many HTTP clients and found that &lt;a href="https://www.npmjs.com/package/phin"&gt;phin&lt;/a&gt; suits me best. It's lightweight, fast, and supports async/await.&lt;/p&gt;

&lt;p&gt;The database is the most interesting thing. Data is stored in MongoDB Atlas. &lt;a href="https://www.npmjs.com/package/mongoose"&gt;mongoose&lt;/a&gt; is used to interact with MongoDB Atlas. However, MongoDB Atlas is slow. To work around this, I store the entire database in &lt;a href="https://www.npmjs.com/package/megahash"&gt;megahash&lt;/a&gt;, a super-fast C++ hash table with wrappers for Node.js.&lt;/p&gt;

&lt;p&gt;I have plan to switch to a proper cache module. I'm considering &lt;a href="https://www.npmjs.com/package/node-cache"&gt;node-cache&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For deployment, I use Heroku Hobby Dyno from GitHub Student Pack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;I want to express my gratitude to GitHub and Heroku. This project will be much harder to complete without them.&lt;/p&gt;

</description>
      <category>octograd2020</category>
      <category>githubsdp</category>
      <category>node</category>
      <category>chatible</category>
    </item>
  </channel>
</rss>
