<?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: Olivier Cartier</title>
    <description>The latest articles on DEV Community by Olivier Cartier (@cestoliv).</description>
    <link>https://dev.to/cestoliv</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%2F887896%2F95c3420e-575c-4c57-8a16-969bf28cecb3.jpeg</url>
      <title>DEV Community: Olivier Cartier</title>
      <link>https://dev.to/cestoliv</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cestoliv"/>
    <language>en</language>
    <item>
      <title>"👩‍💻🎉".length = 7 ??? How to count emojis with Javascript</title>
      <dc:creator>Olivier Cartier</dc:creator>
      <pubDate>Mon, 14 Nov 2022 09:27:58 +0000</pubDate>
      <link>https://dev.to/cestoliv/length-7-how-to-count-emojis-with-javascript-149p</link>
      <guid>https://dev.to/cestoliv/length-7-how-to-count-emojis-with-javascript-149p</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was originally published on my blog: &lt;a href="https://cestoliv.com"&gt;cestoliv.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  In this article
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The problem&lt;/li&gt;
&lt;li&gt;A first solution with the spread operator&lt;/li&gt;
&lt;li&gt;A second algorithm with Zero Width Joiner&lt;/li&gt;
&lt;li&gt;
The best solution (to use in production)

&lt;ol&gt;
&lt;li&gt;Use it on Firefox/IE&lt;/li&gt;
&lt;li&gt;Use it with TypeScript&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. The problem
&lt;/h2&gt;

&lt;p&gt;This week, a friend of mine encountered a Javascript problem when he wanted to check that his user was entering only one character in a text input.&lt;br&gt;
Indeed, the first solution we think of is to look at the length of the string, but problems occur when this string contains emojis:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; 1&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;🛏&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; 2 ??&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dAW8XQpi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kzefc4jwn21bk19gk5wq.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dAW8XQpi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kzefc4jwn21bk19gk5wq.jpg" alt="Houston, we have a problem meme" width="640" height="355"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In fact, it is quite logical, knowing that the &lt;code&gt;.length&lt;/code&gt; function in Javascript returns the length of the string in UTF-16 code units, &lt;strong&gt;not the number of visible characters&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. A first solution with the spread operator
&lt;/h2&gt;

&lt;p&gt;The first solution I thought of was to split the string on each character and then get the number of elements:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;🛏&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; ["�","�"]&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;🛏&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; 2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Ouch...&lt;/strong&gt; Unfortunately, &lt;code&gt;.split('')&lt;/code&gt; also splits in UTF-16 code units.&lt;/p&gt;

&lt;p&gt;But there is another way to split a string on each character in Javascript, using the spread operator:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;🛏&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; ["🛏"]&lt;/span&gt;
&lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;🛏&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; 1, Hooray !!&lt;/span&gt;
&lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;🛏🎉&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; ["🛏", "🎉"]&lt;/span&gt;
&lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;🛏🎉&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; 2, Hooray !!&lt;/span&gt;

&lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;👩‍💻&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; ["👩"‍, "\u{200D}", "💻"]&lt;/span&gt;
&lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;👩‍💻&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; 3, Oops...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Damn...&lt;/strong&gt; Still not, unfortunately for us, some emojis are composed of several emojis, separated by a &lt;strong&gt;"‍" (U+200D, a Zero Width Joiner)&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;👩‍💻&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; ["👩"‍, "\u{200D}", "💻"]&lt;/span&gt;

&lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;👩‍💻👩‍❤️‍💋‍👩&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; ["👩", "\u{200D}", "💻", "👩", "\u{200D}", "❤", "\u{fe0f}", "\u{200D}", "💋", "\u{200D}", "👩"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  3. A second algorithm with Zero Width Joiner
&lt;/h2&gt;

&lt;p&gt;As you can see in this example, to count the number of visible characters, you can count the number of times two characters that are &lt;strong&gt;NOT&lt;/strong&gt; Zero Width Joiner are side by side.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a👩‍💻🎉&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="c1"&gt;// =&amp;gt; ["a", "👩", "\u{200D}", "💻", "🎉"]&lt;/span&gt;
&lt;span class="c1"&gt;//    | 1 |           2           |  3  |&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So we can make it a simple function:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-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;visibleLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;arr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s1"&gt;u{200D}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
            &lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s1"&gt;u{200D}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
            &lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s1"&gt;u{fe0f}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
            &lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s1"&gt;u{20e3}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;visibleLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello World&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; 11&lt;/span&gt;
&lt;span class="nx"&gt;visibleLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello World 👋&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; 13&lt;/span&gt;
&lt;span class="nx"&gt;visibleLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I'm going to 🛏 !&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 16&lt;/span&gt;
&lt;span class="nx"&gt;visibleLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;👨‍👩‍👧‍👦&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; 1&lt;/span&gt;
&lt;span class="nx"&gt;visibleLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;👩‍💻👩‍❤️‍💋‍👩&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; 2&lt;/span&gt;

&lt;span class="nx"&gt;visibleLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🇫🇷&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; 2 AAAAAAAAAAAAAA!!!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Our function works in many cases, &lt;strong&gt;but not in the case of flags&lt;/strong&gt;, because flags are two emojis-letters put side by side, but they are not separated by a &lt;strong&gt;Zero Width Joiner&lt;/strong&gt;, they are simply transformed into flags by the supported platforms.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;🇫🇷&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; ["🇫", "🇷"]&lt;/span&gt;
&lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;🇺🇸&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; ["🇺", "🇸"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  4. The best solution (to use in production)
&lt;/h2&gt;

&lt;p&gt;One of the best solutions we have to handle all these cases is to use a &lt;a href="https://en.wikipedia.org/wiki/Grapheme"&gt;Grapheme&lt;/a&gt; algorithm capable of separating strings into visible phrases, words or characters.&lt;/p&gt;

&lt;p&gt;To our delight, Javascript integrates this algorithm natively: &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter"&gt;Intl.Segmenter&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's pretty easy to use, &lt;strong&gt;AND IT WORKS WITH ALL CHARACTERS!&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-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;visibleLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Intl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Segmenter&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)].&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;visibleLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I'm going to 🛏 !&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; 16&lt;/span&gt;
&lt;span class="nx"&gt;visibleLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;👩‍💻&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; 1&lt;/span&gt;
&lt;span class="nx"&gt;visibleLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;👩‍💻👩‍❤️‍💋‍👩&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; 2&lt;/span&gt;
&lt;span class="nx"&gt;visibleLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;France 🇫🇷!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; 9&lt;/span&gt;
&lt;span class="nx"&gt;visibleLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;England 🏴󠁧󠁢󠁥󠁮󠁧󠁿!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; 10&lt;/span&gt;
&lt;span class="nx"&gt;visibleLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;と日本語の文章&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// =&amp;gt; 7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;There is just one small problem, &lt;code&gt;Intl.Segmenter()&lt;/code&gt; is not compatible at all with Firefox (both Desktop and Mobile) and Internet Explorer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0wwXzKbK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aan3v2klocuf3kt2xo5n.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0wwXzKbK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aan3v2klocuf3kt2xo5n.jpg" alt="Mozilla Developer Network screenshot of Intl Segmenter Browser compatibility" width="765" height="261"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  4.1 Use it on Firefox/IE
&lt;/h3&gt;

&lt;p&gt;To make this solution compatible with Firefox, we need to use this polyfill: &lt;a href="https://github.com/surferseo/intl-segmenter-polyfill"&gt;https://github.com/surferseo/intl-segmenter-polyfill&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Because the file is very large (1.77 MB), we need to make sure that it is only loaded for clients that do not yet support &lt;code&gt;Intl.Segmenter()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Because it's a bit out of the scope of this article, I'm just posting my solution:&lt;/p&gt;


&lt;div class="ltag__replit"&gt;
  &lt;iframe height="550px" src="https://repl.it/@cestoliv/JS-Visible-Length?lite=true"&gt;&lt;/iframe&gt;
&lt;/div&gt;



&lt;h3&gt;
  
  
  4.2. Use it with TypeScript
&lt;/h3&gt;

&lt;p&gt;Because this implementation is relatively new, if you want to use &lt;code&gt;Intl.Segmenter()&lt;/code&gt; with TypeScript, make sure you have at least &lt;strong&gt;ES2022&lt;/strong&gt; as target:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;File:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;tsconfig.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ES2022"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Thanks for reading!&lt;/strong&gt;&lt;/p&gt;


&lt;div class="ltag__user ltag__user__id__887896"&gt;
    &lt;a href="/cestoliv" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mI6c_vXA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--QuxESX1M--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/887896/95c3420e-575c-4c57-8a16-969bf28cecb3.jpeg" alt="cestoliv image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/cestoliv"&gt;Olivier Cartier&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/cestoliv"&gt;19 years old, passionate about computer science and student at the 42 school. I started at 12 years old with Minecraft plugins. Since then, I've mastered Copy/Paste.&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



</description>
      <category>tutorial</category>
      <category>javascript</category>
      <category>emojis</category>
    </item>
    <item>
      <title>Using IPFS for data replication</title>
      <dc:creator>Olivier Cartier</dc:creator>
      <pubDate>Fri, 15 Jul 2022 19:50:27 +0000</pubDate>
      <link>https://dev.to/cestoliv/using-ipfs-for-data-replication-l0k</link>
      <guid>https://dev.to/cestoliv/using-ipfs-for-data-replication-l0k</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was originally published on my blog: &lt;a href="https://cestoliv.com"&gt;cestoliv.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  In this article
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Whats is IPFS?&lt;/li&gt;
&lt;li&gt;Create an IPFS private network with IPFS cluster&lt;/li&gt;
&lt;li&gt;Create the main node&lt;/li&gt;
&lt;li&gt;Adding replication node(s)&lt;/li&gt;
&lt;li&gt;Going further&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. What is IPFS?
&lt;/h2&gt;

&lt;p&gt;IPFS stands for InterPlanetary File System. As its documentation says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;IPFS is a distributed system for storing and accessing files, websites, applications and data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ipfs.io"&gt;ipfs.io&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Decentralized&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In fact, it is a system that allows to store files in peer-to-peer, like torrents. Instead of requesting a file from a central server, you request it from all the peers who also own it.&lt;br&gt;
There is no central server, so the content is always accessible (as long as at least one peer owns it).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Content addressing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In most of the content we know, the content is addressed by a path: the files on your computer &lt;code&gt;/home/cestoliv/Documents/file.txt&lt;/code&gt; or on the web &lt;code&gt;cestoliv.com/file.txt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With &lt;strong&gt;IPFS&lt;/strong&gt;, content is not addressed by its path, but by its content. So the address of a file on &lt;strong&gt;IPFS&lt;/strong&gt; is the hash of its content, for example: &lt;code&gt;/ipfs/QmXoypizjW3WknFiJnKowHCnL72vedxjQkDDP1lXWo6uco&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Create an IPFS private network with IPFS cluster
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;IPFS private networks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If &lt;strong&gt;IPFS&lt;/strong&gt; allows, like torrents, everyone to access content, it is also possible to create a private network, where only the peers you allow can access certain content. This is what we will do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IPFS Cluster&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Furthermore, we want to replicate data, to do this we want several peers to download it. When we access a file on &lt;strong&gt;IPFS&lt;/strong&gt;, it remains available for several days on our machine and is then deleted to free up space. In the context of our replication, this is not what we want, we want all our peers to download all our files. &lt;strong&gt;IPFS&lt;/strong&gt; answers this problem thanks to the PIN concept. If you pin a file, it will be downloaded, and never deleted.&lt;/p&gt;

&lt;p&gt;Then we will use the &lt;strong&gt;IPFS-Cluster&lt;/strong&gt; tool, which allows to control all peers to tell them to pin a file. So, we will just have to pin a file in our cluster and all the peers will pin it automatically.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Create the main node
&lt;/h2&gt;

&lt;p&gt;Go to your main node (the one that will first receive the data to duplicate) and create a working folder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ipfs-cluster &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;ipfs-cluster
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then create a &lt;code&gt;docker-compose.yml&lt;/code&gt; file with the following content:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;Before launching the containers, we need to &lt;strong&gt;generate the swarm key&lt;/strong&gt; to create a private IPFS network and a &lt;strong&gt;CLUSTER_SECRET&lt;/strong&gt; to authenticate the other nodes in our private network.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generate the swarm key&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ./data/ipfs/data
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"/key/swarm/psk/1.0.0/&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;/base16/&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-dc&lt;/span&gt; &lt;span class="s1"&gt;'a-f0-9'&lt;/span&gt; &amp;lt; /dev/urandom | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-c64&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ./data/ipfs/data/swarm.key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Create the CLUSTER_SECRET&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"CLUSTER_SECRET=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;od&lt;/span&gt; &lt;span class="nt"&gt;-vN&lt;/span&gt; 32 &lt;span class="nt"&gt;-An&lt;/span&gt; &lt;span class="nt"&gt;-tx1&lt;/span&gt; /dev/urandom | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;' \n'&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Finally! You can start the containers!&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Check that everything went well in the logs: &lt;code&gt;docker compose logs&lt;/code&gt;&lt;br&gt;
&lt;em&gt;(You may see false error because the cluster is faster to start than the IPFS node, so the cluster will not be able to connect to the node at first)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Because this is a private network, we will remove all connections with public nodes:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nb"&gt;exec &lt;/span&gt;ipfs ipfs bootstrap &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;--all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  (Optional) Install the ipfs-cluster-ctl on the host
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ipfs-cluster-ctl&lt;/code&gt; is the tool that allows us to interact with our cluster (in particular to say which files should be pinned). This is done through port &lt;code&gt;9094&lt;/code&gt; that we have opened on the container.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wget https://dist.ipfs.io/ipfs-cluster-ctl/v1.0.2/ipfs-cluster-ctl_v1.0.2_linux-amd64.tar.gz
&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xvf&lt;/span&gt; ipfs-cluster-ctl_v1.0.2_linux-amd64.tar.gz
&lt;span class="nb"&gt;sudo cp &lt;/span&gt;ipfs-cluster-ctl/ipfs-cluster-ctl /usr/local/bin
&lt;span class="c"&gt;# Check that ipfs-cluster-ctl has been successfully installed&lt;/span&gt;
ipfs-cluster-ctl &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;It is not mandatory to install the tool on the host, because it is already installed in the container. If you don't want to install it, just replace the &lt;code&gt;ipfs-cluster-ctl&lt;/code&gt; command with &lt;code&gt;docker compose exec cluster ipfs-cluster-ctl&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Test that the main node is working
&lt;/h3&gt;

&lt;p&gt;To test that our network is private, we will add a file to the cluster and check that we cannot open this file from a public node.&lt;/p&gt;

&lt;p&gt;To do this, we need to add a file that will be unique, if we just add a file containing "Hello world!" and someone already did it on the public node, the hash will be the same, so our file will be indirectly accessible via the public nodes.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Hello &lt;/span&gt;&lt;span class="nv"&gt;$USER&lt;/span&gt;&lt;span class="s2"&gt; (&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;od&lt;/span&gt; &lt;span class="nt"&gt;-vN&lt;/span&gt; 6 &lt;span class="nt"&gt;-An&lt;/span&gt; &lt;span class="nt"&gt;-tx1&lt;/span&gt; /dev/urandom | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;' \n'&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="s2"&gt;)!"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; hello.txt
ipfs-cluster-ctl add hello.txt
&lt;span class="c"&gt;# output: added &amp;lt;your file hash&amp;gt; hello.txt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Let's test that our IPFS network is private. To do this, try to see the contents of the file from the container (it should work):&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nb"&gt;exec &lt;/span&gt;ipfs ipfs &lt;span class="nb"&gt;cat&lt;/span&gt; &amp;lt;your file &lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;# output: Hello &amp;lt;you&amp;gt; (&amp;lt;a random number&amp;gt;)!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;But if you try to view the contents of the file from a node that is not part of your private network, it should not work:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Try opening `https://ipfs.io/ipfs/&amp;lt;your file hash&amp;gt;` in your browser.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  4. Adding replication node(s)
&lt;/h2&gt;

&lt;p&gt;This part must be repeated for each replication node you want to add.&lt;/p&gt;

&lt;p&gt;Go to your replication node and create a working folder.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ipfs-cluster &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;ipfs-cluster
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Create a &lt;code&gt;docker-compose.yml&lt;/code&gt; file with the following content:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;Before launching the containers, we need to create the &lt;code&gt;.env&lt;/code&gt; file and put in it the same &lt;strong&gt;CLUSTER_SECRET&lt;/strong&gt; as the &lt;code&gt;.env&lt;/code&gt; of our main node.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;CLUSTER_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;your cluster secret&amp;gt;
&lt;span class="nv"&gt;MAIN_NODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/ip4/&amp;lt;main-node-ip&amp;gt;/tcp/9096/ipfs/&amp;lt;main-node-id&amp;gt;
&lt;span class="c"&gt;# e.g. MAIN_NODE=/ip4/192.168.1.1/tcp/9096/ipfs/13D3KooWFN75ytQMC94a6gVLDQ999zxADpFpi7qAir9ajGrNHn8d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The ID of the main node can be found by running the following command &lt;strong&gt;on the main node&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; ./data/cluster/identity.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We also need to copy the &lt;code&gt;swarm.key&lt;/code&gt; we generated on the main node to our replication node (&lt;code&gt;./data/ipfs/data/swarm.key&lt;/code&gt; on the main node)&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# On the Main node&lt;/span&gt;
&lt;span class="c"&gt;# Copy the result off:&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; ./data/ipfs/data/swarm.key
&lt;span class="c"&gt;# On the Replication node&lt;/span&gt;
&lt;span class="c"&gt;# Paste the swarm key&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ./data/ipfs/data
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"/key/swarm/psk/1.0.0/
/base16/
&amp;lt;your swarm key&amp;gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ./data/ipfs/data/swarm.key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You can now start the containers!&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Check that everything went well in the logs: &lt;code&gt;docker compose logs&lt;/code&gt; &lt;em&gt;(You may see false error because the cluster is faster to start than the IPFS node, so the cluster will not be able to connect to the node at first)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Because this is a private network, we will remove all connections with public nodes:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nb"&gt;exec &lt;/span&gt;ipfs ipfs bootstrap &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;--all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Connect to main node
&lt;/h3&gt;

&lt;p&gt;We now need to add our nodes as peers so that they can communicate. &lt;em&gt;The main node must know the replication node(s) and the replication node(s) must know the main node.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On the main node:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nb"&gt;exec &lt;/span&gt;ipfs ipfs bootstrap add /ip4/&amp;lt;replication_node_ip&amp;gt;/tcp/4001/p2p/&amp;lt;replication_node_id&amp;gt;
&lt;span class="c"&gt;# e.g. /ip4/192.168.1.2/tcp/4001/p2p/12D3KopWK6rfR6SKpmxDwKCjtnJWoK1VRYc7BDMZfJxnopljv68u&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The ID of the replication node can be found by running the following command &lt;strong&gt;on the replication node&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nb"&gt;exec &lt;/span&gt;ipfs ipfs config show | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"PeerID"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;On the replication node:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nb"&gt;exec &lt;/span&gt;ipfs ipfs bootstrap add /ip4/&amp;lt;main_node_ip&amp;gt;/tcp/4001/p2p/&amp;lt;main_node_id&amp;gt;
&lt;span class="c"&gt;# e.g. /ip4/192.168.1.1/tcp/4001/p2p/92D3KopWK6rfR6SKpmxDwKCjtnJWoK1VRYc7BDMZfJxnopljv69l&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The ID of the main node can be found by running the following command &lt;strong&gt;on the main node&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nb"&gt;exec &lt;/span&gt;ipfs ipfs config show | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"PeerID"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Testing our installation
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Hello &lt;/span&gt;&lt;span class="nv"&gt;$USER&lt;/span&gt;&lt;span class="s2"&gt; (&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;od&lt;/span&gt; &lt;span class="nt"&gt;-vN&lt;/span&gt; 6 &lt;span class="nt"&gt;-An&lt;/span&gt; &lt;span class="nt"&gt;-tx1&lt;/span&gt; /dev/urandom | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;' \n'&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="s2"&gt;)!"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; hello.txt
ipfs-cluster-ctl add hello.txt
&lt;span class="c"&gt;# output: added &amp;lt;your file hash&amp;gt; hello.txt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You can now see the replication in action with the command &lt;code&gt;ipfs-cluster-ctl status &amp;lt;your file hash&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--H75y7J9i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1yet8ivo4jtea9hrc7vn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--H75y7J9i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1yet8ivo4jtea9hrc7vn.png" alt="the return off the command with a cluster of three nodes" width="880" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The mention &lt;code&gt;PINNED&lt;/code&gt; means that the peer has pinned the file, so it has downloaded it and will keep it.&lt;/p&gt;
&lt;h2&gt;
  
  
  5. Going further
&lt;/h2&gt;

&lt;p&gt;Now I'll show you a more specific use case for our cluster: &lt;strong&gt;replicate a folder across all our nodes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;By adding the &lt;code&gt;-r&lt;/code&gt; option, you can synchronize a folder and all its contents.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ipfs-cluster-ctl add &lt;span class="nt"&gt;-r&lt;/span&gt; folder/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;As you can see, IPFS stores both the hash of the folder itself, and the hash of each file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--woyUfUEE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/edgk4p0rwplcxyhu3yv1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--woyUfUEE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/edgk4p0rwplcxyhu3yv1.png" alt="the return off the command that pin recursively on a cluster" width="723" height="242"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But what happens if the content of the folder changes, how do you replicate these changes?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To replicate changes to a folder that has changed, you must add it again. At this point, the hash of the folder will change and the hash of the modified files will have changed. If a file has not changed, its hash will not change.&lt;/p&gt;

&lt;p&gt;Here is the procedure to follow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We de-pin the current version&lt;/li&gt;
&lt;li&gt;Re-pin the new version&lt;/li&gt;
&lt;li&gt;Delete the files that no longer exist or their old version&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is the bashed version:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="c"&gt;# unpin everything&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;ipfs-cluster-ctl pin &lt;span class="nb"&gt;ls&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;ipfs-cluster-ctl pin &lt;span class="nb"&gt;ls&lt;/span&gt; | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="s1"&gt;' '&lt;/span&gt; &lt;span class="nt"&gt;-f1&lt;/span&gt; | xargs &lt;span class="nt"&gt;-n1&lt;/span&gt; ipfs-cluster-ctl pin &lt;span class="nb"&gt;rm
&lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# pin the new version of the folder&lt;/span&gt;
ipfs-cluster-ctl add &lt;span class="nt"&gt;-r&lt;/span&gt; folder/

&lt;span class="c"&gt;# run garbage collector, to remove the files that are no more pinned&lt;/span&gt;
&lt;span class="c"&gt;# (remove files, or their old version)&lt;/span&gt;
ipfs-cluster-ctl ipfs gc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Thanks for reading!&lt;/strong&gt;&lt;/p&gt;


&lt;div class="ltag__user ltag__user__id__887896"&gt;
    &lt;a href="/cestoliv" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mI6c_vXA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--QuxESX1M--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/887896/95c3420e-575c-4c57-8a16-969bf28cecb3.jpeg" alt="cestoliv image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/cestoliv"&gt;Olivier Cartier&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/cestoliv"&gt;19 years old, passionate about computer science and student at the 42 school. I started at 12 years old with Minecraft plugins. Since then, I've mastered Copy/Paste.&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;Illustration from &lt;a href="https://ipfs.io/"&gt;ipfs.io&lt;/a&gt;, licensed under &lt;a href="https://creativecommons.org/licenses/by/3.0/"&gt;CC-BY 3.0&lt;/a&gt;&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>productivity</category>
      <category>docker</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
