<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Jakub T. Jankiewicz - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Jakub T. Jankiewicz - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Thu, 21 May 2026 16:10:20 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/author/jcubic/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Create a Table of Contents for Your Article ]]>
                </title>
                <description>
                    <![CDATA[ When you create an article, such as a blog post for freeCodeCamp, Hashnode, Medium, or DEV.to, you can help guide the reader by creating a Table of Contents (ToC). In this article, I'll explain how to ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-create-a-table-of-contents-for-your-article/</link>
                <guid isPermaLink="false">69b27bc5f22e712aaa45f840</guid>
                
                    <category>
                        <![CDATA[ blog ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Accessibility ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ devtools ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Jakub T. Jankiewicz ]]>
                </dc:creator>
                <pubDate>Thu, 12 Mar 2026 08:39:33 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5fc16e412cae9c5b190b6cdd/ff72c490-a57b-46c4-b0d9-8c2654853b7c.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>When you create an article, such as a blog post for freeCodeCamp, Hashnode, Medium, or DEV.to, you can help guide the reader by creating a <a href="https://en.wikipedia.org/wiki/Table_of_contents">Table of Contents</a> (ToC). In this article, I'll explain how to create one with the help of JavaScript and browser DevTools. The article will explain how to use Google Chrome Dev Tools. But the same can be applied to any modern browser.</p>
<p>The process in this article needs to be done once per platform. Once you have the code, you can apply it every time to create a ToC. Note that if the platform changes something, you may need to adjust the script.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-browser-dev-tools">Browser Dev Tools</a></p>
</li>
<li><p><a href="#heading-javascript-console">JavaScript Console</a></p>
</li>
<li><p><a href="#heading-understanding-the-dom-structure">Understanding the DOM Structure</a></p>
</li>
<li><p><a href="#heading-creating-toc-in-markdown">Creating TOC in Markdown</a></p>
</li>
<li><p><a href="#heading-how-to-create-an-html-toc">How to create an HTML TOC?</a></p>
</li>
<li><p><a href="#heading-copy-the-html-code-for-the-editor">Copy the HTML code for the editor</a></p>
</li>
<li><p><a href="#heading-what-to-do-if-i-dont-have-headers">What to do if I don’t have headers?</a></p>
<ul>
<li><a href="#heading-create-table-of-contents-for-devto">Create Table of Contents for</a> <a href="http://DEV.to">DEV.to</a></li>
</ul>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-browser-dev-tools">Browser Dev Tools</h2>
<p>Dev Tools is an extension to the browser that can allow you to inspect and manipulate the DOM (<a href="https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model">Document Object Model</a>), which is a representation of the HTML the browser keeps in memory in the form of a tree. It also gives access to the JavaScript console, where you can write short code snippets to test something. It has a lot more features, but we'll only use those two.</p>
<p>To open Dev Tools (in Google Chrome), you can press F12 or right-click on the page with your mouse and click Inspect.</p>
<div>
<div>⚠</div>
<div>In Safari, the browser Dev Tools are disabled initially. To enable it, read: <a target="_self" rel="noopener" class="text-primary underline underline-offset-2 hover:text-primary/80 cursor-pointer eVNpHGjtxRBq_gLOfGDr LQNqh2U1kzYxREs65IJu" href="https://support.apple.com/guide/safari/use-the-developer-tools-in-the-develop-menu-sfri20948/mac" style="pointer-events:none">Use the developer tools in the Develop menu in Safari on Mac</a>.</div>
</div>

<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763748137160/7e4df24f-6d25-4d43-a67b-c671bd85789a.png" alt="A browser window split in half. One the right there is an illustration of the laptop with FreeCodeCamp article on the right there is browser DevTools with DOM Tree and CSS panel." style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>Above is the screenshot of DevTools with a preview of this article. On the right, you can see a selected <code>h1</code> HTML tag (the title) and CSS applied to that tag. The tree structure you see is the DOM.</p>
<div>
<div>💡</div>
<div>When creating a ToC for <strong>freeCodeCamp,</strong> you should open the preview in a new tab.</div>
</div>

<h2 id="heading-javascript-console">JavaScript Console</h2>
<p>We will need to have access to the JavaScript console. To open the console in Google Chrome, you can use F12, right-click on the page and select Inspect from the context menu, or use the shortcut CTRL+SHIFT+C (Windows, Linux) or CMD+OPTION+C (Mac).</p>
<p>In Chrome DevTools, you can pick the Console tab at the top of the DevTools. But this will hide the DOM tree. It’s better to open the bottom drawer. You need to click the 3 dots in the top right corner and pick “show console drawer”.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763749509540/7968ace9-624e-4037-b09a-fe298ba9b865.png" alt="Screenshot of a menu whic hallow docking the dev tools to the right, left, bottom, or in standalone window." style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>The Dev Tools will look like this:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763749614077/640a2467-ac85-4788-9836-3431a1c503bb.png" alt="Screenshot of Browser DevTools showing DOM Tree, CSS panel, and Console Drawer." style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<div>
<div>💡</div>
<div>You can ignore any errors or warnings in the console. You can click this icon 🚫 on the left side of the drawer, and it will clear the console.</div>
</div>

<p>The console is a so-called <a href="https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop">Read-Eval-Print-Loop</a>. A classic interface, where you type some commands, here JavaScript code, and when you press enter, the code is executed in the context of the page the DevTools is on.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763749997534/b61f8cc0-62eb-4586-9898-d41a7519cf3e.png" alt="Screenshot which shows browser alert popup and JavaScript code in DevTools console which open the alert." style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>Above, you can see a page alert executed from the console.</p>
<h2 id="heading-understanding-the-dom-structure">Understanding the DOM Structure</h2>
<p>The first step to create a ToC is to inspect the DOM and find the headers. They are usually <strong>H1…H6</strong> tags. H1 is often the title of the page. In an ideal world, it would always be.</p>
<p>In my case, the header looks like this:</p>
<pre><code class="language-xml">&lt;h2 id="heading-dev-tools"&gt;Dev Tools&lt;/h2&gt;
</code></pre>
<p>The article only has H2 tags, but later in the article, I will also explain how to create a nested ToC.</p>
<div>
<div>💡</div>
<div>Your headers need to have an “id” attribute. It can look different, for example, be on a different element, but it has to be in the DOM. Later in the article, I will explain a few different structures and how to handle them.</div>
</div>

<p>Now with DevTools, we can write code that will find every header:</p>
<pre><code class="language-javascript">document.querySelectorAll('h2[id], h3[id], main h4[id]');
</code></pre>
<p>In the case of my article on freeCodeCamp, it returned this output:</p>
<pre><code class="language-plaintext">NodeList(5)&nbsp;[h2#heading-dev-tools, h2#heading-javascript-console, h2#heading-understanding-the-dom-structure, h2#trending-guides.col-header, h2#mobile-app.col-header]
</code></pre>
<p>First, it’s a NodeList that we need to convert to an Array. Second is that besides our headers that we have so far, we also have two headers that are part of the website and not the main content. So we need to find out the single element that is the parent of the headers we need.</p>
<p>You can right-click on the white page that contains the article and pick <strong>Inspect Element</strong>. In our case, it found an element <code>&lt;main&gt;</code>. So we can rewrite our selector as:</p>
<pre><code class="language-javascript">document.querySelectorAll('main h2[id], main h3[id], main h4[id]');
</code></pre>
<p>And now it returns our headers and nothing more.</p>
<div>
<div>💡</div>
<div>The <code>[id]</code> attribute selector is not needed here, actually. At least not on freeCodeCamp.</div>
</div>

<h2 id="heading-how-to-create-the-toc-in-markdown">How to Create the ToC in Markdown</h2>
<p>A lot of blogging platforms support Markdown, so it'll be the first thing we'll create.</p>
<p>First, we'll convert the Node list to an array. We can use the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax">spread operator</a>:</p>
<pre><code class="language-javascript">[...document.querySelectorAll('main h2[id], main h3[id], main h4[id]')];
</code></pre>
<p>Then we can map over the array and create the Markdown links that point to the given header.</p>
<pre><code class="language-javascript">const headers = [...document.querySelectorAll('main h2[id], main h3[id], main h4[id]')];

headers.map(function(node) {
    // H2 header should have 0 indent
    const level = parseInt(node.nodeName.replace('H', '')) - 2;
    const hash = node.getAttribute('id');
    const indent = ' '.repeat(level * 2);
    return `\({indent}* [\){node.innerText}](#${hash})`;
});
</code></pre>
<p>The output looks like this:</p>
<pre><code class="language-plaintext">(4)&nbsp;['* [Dev Tools](#heading-dev-tools)', '* [JavaScript Console](#heading-javascript-console)', '* [Understanding the DOM Structure](#heading-understanding-the-dom-structure)', '* [What to do if I don’t have headers?](#heading-what-to-do-if-i-dont-have-headers)']
</code></pre>
<p>To get the text, we can join the array with a newline character and use <code>console.lo</code>g to display the output. If we don’t use <code>console.log</code>, it will show a string with <code>\n</code> characters.</p>
<pre><code class="language-javascript">const headers = [...document.querySelectorAll('main h2[id], main h3[id], main h4[id]')];

console.log(headers.map(function(node) {
    // H2 header should have 0 indent
    const level = parseInt(node.nodeName.replace('H', '')) - 2;
    const hash = node.getAttribute('id');
    const indent = ' '.repeat(level * 2);
    return `\({indent}* [\){node.innerText}](#${hash})`;
}).join('\n'));
</code></pre>
<p>The output for this article will look like this:</p>
<pre><code class="language-markdown">* [Dev Tools](#heading-dev-tools)
* [JavaScript Console](#heading-javascript-console)
* [Understanding the DOM Structure](#heading-understanding-the-dom-structure)
* [Creating TOC in Markdown](#heading-creating-toc-in-markdown)
  * [This is fake header](#heading-this-is-fake-header)
</code></pre>
<p>I created one fake subheader. Platforms, even when not supporting Markdown when writing articles, often support Markdown when copy-pasted. The ToC at the top of the article was created by copying and pasting markdown generated with the last JavaScript snippet.</p>
<h2 id="heading-how-to-create-an-html-toc">How to Create an HTML ToC</h2>
<p>If your platform doesn’t support Markdown (like Medium), you can create HTML, preview that HTML, and copy the output to the clipboard. Pasting that into the editor of the platform you're using should keep the formatting.</p>
<div>
<div>💡</div>
<div>On Medium, the content is inside a <code>&lt;section&gt;</code> element, so the selector must be updated.</div>
</div>

<p>To convert Markdown to HTML, you can use any online tool, but you'll see how to create it yourself in the snippet. It will be faster after you create the code.</p>
<pre><code class="language-javascript">const headers = [...document.querySelectorAll('main h2[id], main h3[id], main h4[id]')]

function indent(state) {
    return ' '.repeat((state.level - 1) * 2);
}

function closeUlTags(state, targetLevel) {
    while (state.level &gt; targetLevel) {
        state.level--;
        state.lines.push(`${indent(state)}&lt;/ul&gt;`);
    }
}

function openUlTags(state, targetLevel) {
    while (state.level &lt; targetLevel) {
        state.lines.push(`${indent(state)}&lt;ul&gt;`);
        state.level++;
    }
}

const result = headers.reduce((state, node) =&gt; {
    const level = parseInt(node.nodeName.replace('H', ''));

    closeUlTags(state, level);
    openUlTags(state, level);
    
    const hash = node.getAttribute('id');
    state.lines.push(`\({indent(state)}&lt;li&gt;&lt;a href="#\){hash}"&gt;${node.innerText}&lt;/a&gt;&lt;/li&gt;`);
    return state;
}, { lines: [], level: 1 });

closeUlTags(result, 1);

console.log(result.lines.join('\n'));
</code></pre>
<p>This is the output of the code in this article:</p>
<pre><code class="language-html">&lt;ul&gt;
  &lt;li&gt;&lt;a href="#heading-table-of-contents"&gt;Table of Contents&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="#heading-dev-tools"&gt;Dev Tools&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="#heading-javascript-console"&gt;JavaScript Console&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="#heading-understanding-the-dom-structure"&gt;Understanding the DOM Structure&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="#heading-creating-toc-in-markdown"&gt;Creating TOC in Markdown&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="#heading-how-to-create-html-toc"&gt;How to create HTML TOC&lt;/a&gt;&lt;/li&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href="#heading-level-3"&gt;Level 3&lt;/a&gt;&lt;/li&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href="#heading-level-4"&gt;Level 4&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/ul&gt;
  &lt;li&gt;&lt;a href="#heading-what-to-do-if-i-dont-have-headers"&gt;What to do if I don’t have headers?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</code></pre>
<p>I added a few headers at the end, so you can see that it will work for any level of nested headers. Note that we also have the ToC as the first element on the list.</p>
<div>
<div>💡</div>
<div>Note that the above HTML code includes a link to the Table of Contents. This happens if you run the script again after adding the TOC. You can remove it by hand. If you want to improve the code, you can add a filter.</div>
</div>

<h2 id="heading-copy-the-html-code-for-the-editor">Copy the HTML code for the editor</h2>
<p>Most so-called <a href="https://en.wikipedia.org/wiki/WYSIWYG">WYSIWYG</a> editors are using HTML, and you should be able to copy the output of HTML code with formatting and paste it into that editor. The easiest is to just save that into a file, open that file, and select the text:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763758802247/7e4fa0cd-377d-44ca-9cdb-b53ec14da4b8.png" alt="Screenshot of the browser window with file open. The page in the browser shows the table of content where all text is highlighted by selection." style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<h2 id="heading-what-to-do-if-i-dont-have-headers">What to Do If I Don’t Have Headers?</h2>
<p>You need to find anything that can be targeted with CSS. If they are <code>p</code> tags with a specific class (like header), you can use <code>p.header</code> instead of <code>h2</code>.</p>
<h3 id="heading-how-to-create-a-table-of-contents-for-devto">How to Create a Table of Contents for DEV.to</h3>
<p>If you have a different DOM structure, you can use different DOM methods to extract the element you need. For example, on DEV.to, the headers look like this:</p>
<pre><code class="language-xml">&lt;h2&gt;
  &lt;a name="overview" href="#overview"&gt;
  &lt;/a&gt;
  Overview
&lt;/h2&gt;
</code></pre>
<p>So the selector needs to be just <code>main h2</code>. But when you execute this code:</p>
<pre><code class="language-javascript">[...document.querySelectorAll('main h2, main h3, main h4')];
</code></pre>
<p>You will see that there are way more headers than the content of the document. Luckily, we can use a new selector in CSS <code>:has()</code>. The final selector for one header can look like this: <code>main h2:has(a[name])</code>.</p>
<p>Here is the full code:</p>
<pre><code class="language-javascript">const selector = 'main h2:has(a[name]), main h3:has(a[name]), main h4:has(a[name])';
const headers = [...document.querySelectorAll(selector)];

console.log(headers.map(function(node) {
    // H2 header should have 0 indent
    const level = parseInt(node.nodeName.replace('H', '')) - 2;
    // this is how you get the hash
    // you can also access href attribute and remove # from the output string
    const hash = node.querySelector('a').getAttribute('name');
    const indent = ' '.repeat(level);
    return `\({indent}* [\){node.innerText}](#${hash})`;
}).join('\n'));
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Creating a table of contents can help your readers digest your article. Since most people don’t read the whole article, they only scan for what they need. You can also find a lot of articles about its impact on SEO. So it’s always worth adding one if the article is longer.</p>
<p>And as you can see, creating a ToC is not that hard with a bit of web development knowledge.</p>
<p>If you like this article, you may want to follow me on Social Media: (<a href="https://x.com/jcubic">Twitter/X</a>, <a href="https://github.com/jcubic">GitHub</a>, and/or <a href="https://www.linkedin.com/in/jakubjankiewicz/">LinkedIn</a>). You can also check my <a href="https://jakub.jankiewicz.org/">personal website</a> and my <a href="https://jakub.jankiewicz.org/blog/">new blog</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Match Parentheses in JavaScript without Using Regex ]]>
                </title>
                <description>
                    <![CDATA[ While writing my Lisp interpreter (for the Scheme dialect, to be precise), I decided to include support for square brackets. I did this because some of the Scheme books use them interchangeably with parentheses. But I didn't want to make the parser t... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-match-parentheses-in-javascript-without-using-regex/</link>
                <guid isPermaLink="false">66ba29031567ba6dc973f17e</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ algorithms ]]>
                    </category>
                
                    <category>
                        <![CDATA[ parsing ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Jakub T. Jankiewicz ]]>
                </dc:creator>
                <pubDate>Mon, 12 Aug 2024 15:23:47 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1723400046627/d6cdb4d5-9c6e-4ce0-92a4-1438e5c998e3.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>While writing my <a target="_blank" href="https://lips.js.org/">Lisp interpreter</a> (for the Scheme dialect, to be precise), I decided to include support for square brackets. I did this because some of the Scheme books use them interchangeably with parentheses. But I didn't want to make the parser too complex, so I didn't check if the pair of brackets matched.</p>
<p>In my Lisp interpreter, the brackets could be mixed like this:</p>
<pre><code class="lang-lisp">(<span class="hljs-name">let</span> [(<span class="hljs-name">x</span> <span class="hljs-number">10</span>])
  [+ x x)]
</code></pre>
<p>The above code is an example of an S-Expression where we have tokens, a sequence of characters with meaning (like name <code>let</code> or number <code>10</code>), and brackets/parentheses. However, the code is not valid because it mixes pairs of parentheses and brackets.</p>
<pre><code class="lang-lisp">(<span class="hljs-name">let</span> [(<span class="hljs-name">x</span> <span class="hljs-number">10</span>)]
  [+ x x])
</code></pre>
<p>This is the proper Lisp syntax (if brackets are supported), where opening parentheses always match closing parenthesis and opening brackets always match closing brackets.</p>
<p>In this article, I'll show you how you can detect if the input is the second valid S-Expression and not the first invalid S-Expression.</p>
<p>We will also handle cases like this:</p>
<pre><code class="lang-lisp">(<span class="hljs-name">define</span> foo <span class="hljs-number">10</span>))))
</code></pre>
<p>In the code above, you see invalid syntax with more closing than opening parentheses.</p>
<p>So in simple terms, we will detect if pairs of opening and closing characters like <code>()</code>, <code>[]</code>, and <code>{}</code> match inside the string like <code>"[foo () {bar}]"</code>.</p>
<p>The solution to this problem can be useful when implementing a Lisp parser, but you can also use it for different purposes (like some kind of validation or part of a math expression evaluator). It's also a good exercise.</p>
<p><strong>NOTE</strong>: to understand this article and code, you don't need to know anything about Lisp. But if you want some more context, you can read this article: <a target="_blank" href="https://lips.js.org/docs/scheme-intro/what-is-lisp">What is Lisp and Scheme</a>?</p>
<h2 id="heading-pairing-with-the-stack-data-structure">Pairing with the Stack Data Structure</h2>
<p>You may think that the simplest way to solve this problem is using <a target="_blank" href="https://www.freecodecamp.org/news/regular-expressions-crash-course/">Regular Expressions (RegExp)</a>. It may be possible to use Regex for simple cases, but soon it will get too complex and have more problems than it solves.</p>
<p>Actually, the easiest way of pairing opening and closing characters is using the stack. Stack is the simplest data structure. We have two basic operations: push, for adding an item to the stack, and pop, for removing an item.</p>
<p>This works similarly to a stack of books. The last item that you put onto the stack will be the first one that you take out.</p>
<p>You can read more about this data structure from this article: <a target="_blank" href="https://www.freecodecamp.org/news/data-structures-101-stacks-696b3282980/">Data Structures 101: Stacks</a>.</p>
<p>With a stack, it's easier to process (parse) the characters, which have a beginning and end, like XML tags or simple parentheses.</p>
<p>For example, say we have this XML code:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">element</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">item</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">sub-item</span>&gt;</span>text<span class="hljs-tag">&lt;/<span class="hljs-name">sub-item</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">item</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">element</span>&gt;</span>
</code></pre>
<p>When we use the stack, the last tag we open (inner <code>&lt;sub-item&gt;</code> for example) will always be the first we need to close (if the XML is valid).</p>
<p>So if we use the stack, we can push the item <code>&lt;sub-item&gt;</code> when we open it, and when we need to close it we can pop it from the stack. We only need to make sure that the last item on the stack (which is always the opening tag) matches the closing one.</p>
<p>We will have exact same logic with parentheses.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1723399558190/e70c9542-dad5-4259-8109-0d198796e5b5.png" alt="Illustration of processing a sequence of xml tags with a stack" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Note that if we have XML self-closing tags, we can skip them since they are open and automatically closed.</p>
<p>Arrays in JavaScript have both methods (push and pop), so they can be used as a Stack. But it's more convenient to create an abstraction in the form of a class, so we can add additional methods like <code>top()</code> and <code>is_empty()</code> that are easier to read.</p>
<p>But even if we didn't add new methods, it's always better to create abstractions like this, because it makes the code simpler and easier to read.</p>
<p>Most developers know common data structures and will immediately recognize them and not need to think about them. The most important thing with programming is forgetting not related stuff that are required at any given moment. The human memory is limited and can only hold about 4±1 items at the same time. You can read more about <a target="_blank" href="https://en.wikipedia.org/wiki/Short-term_memory">short-term memory</a> on Wikipedia.</p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Stack</span> </span>{
    #data;
    <span class="hljs-keyword">constructor</span>() {
        <span class="hljs-comment">// creating new empty array as hiden property</span>
        <span class="hljs-built_in">this</span>.#data = [];
    }
    push(item) {
        <span class="hljs-comment">// push method just use native Array::push()</span>
        <span class="hljs-comment">// and add the item at the end of the array</span>
        <span class="hljs-built_in">this</span>.#data.push(item);
    }
    len() {
        <span class="hljs-comment">// size of the Stack</span>
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.#data.length;
    }
    top() {
        <span class="hljs-comment">// sice the items are added at the end</span>
        <span class="hljs-comment">// the top of the stack is the last item</span>
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.#data[<span class="hljs-built_in">this</span>.len() - <span class="hljs-number">1</span>];
    }
    pop() {
        <span class="hljs-comment">// pop is similar to push and use native Array::pop()</span>
        <span class="hljs-comment">// it removes and returns the last item in array</span>
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.#data.pop();
    }
    is_empty() {
        <span class="hljs-comment">// this comment shortcut !0 is true</span>
        <span class="hljs-comment">// since 0 can is coerced to false</span>
        <span class="hljs-keyword">return</span> !<span class="hljs-built_in">this</span>.len();
    }
}
</code></pre>
<p>The above code uses <a target="_blank" href="https://www.freecodecamp.org/news/write-less-do-more-with-javascript-es6-5fd4a8e50ee2/">ES6 (ES2015)</a> class and ES2022 <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_properties">private</a><br><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_properties">properties</a>.</p>
<h2 id="heading-the-parentheses-matching-algorithm">The Parentheses Matching Algorithm</h2>
<p>Now I will describe the algorithm (steps) needed to parse the parentheses.</p>
<p>We need a loop that will go through each token – and it's best if all other tokens are removed before processing.</p>
<p>When we have an opening parenthesis, we need to push that element into the stack. When we have a closing parenthesis, we need to check if the last element on the stack matches the one we are processing.</p>
<p>If the element is matching, we remove it from the stack. If not, it means that we have messed up parentheses, and we need to throw an exception.</p>
<p>When we have a closing bracket, but there is nothing on the stack, we also need to throw an exception, because there is no opening parenthesis that matches the closing one we have.</p>
<p>After checking all the characters (tokens), if something is left of the stack, it means that we didn't close all the parentheses. But this case is ok, because the user may be in the process of writing. So in this case, we return <code>false</code>, not an exception.</p>
<p>If the stack is empty, we return <code>true</code>. This means that we have a complete expression. If this was an S-Expression, we could use the parser to process it, and we would not need to worry about invalid results (of course if the parser was correct).</p>
<h2 id="heading-the-source-code">The Source Code</h2>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">balanced</span>(<span class="hljs-params">str</span>) </span>{
    <span class="hljs-comment">// pairs of matching characters</span>
    <span class="hljs-keyword">const</span> maching_pairs = {
        <span class="hljs-string">'['</span>: <span class="hljs-string">']'</span>,
        <span class="hljs-string">'('</span>: <span class="hljs-string">')'</span>,
        <span class="hljs-string">'{'</span>: <span class="hljs-string">'}'</span>
    };
    <span class="hljs-keyword">const</span> open_tokens = <span class="hljs-built_in">Object</span>.keys(maching_pairs);
    <span class="hljs-keyword">const</span> brackets = <span class="hljs-built_in">Object</span>.values(maching_pairs).concat(open_tokens);
    <span class="hljs-comment">// we filter out what is not our matching characters</span>
    <span class="hljs-keyword">const</span> tokens = tokenize(str).filter(<span class="hljs-function"><span class="hljs-params">token</span> =&gt;</span> {
        <span class="hljs-keyword">return</span> brackets.includes(token);
    });
    <span class="hljs-keyword">const</span> stack = <span class="hljs-keyword">new</span> Stack();
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> token <span class="hljs-keyword">of</span> tokens) {
        <span class="hljs-keyword">if</span> (open_tokens.includes(token)) {
            stack.push(token);
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (!stack.is_empty()) {
             <span class="hljs-comment">// there are matching characters on the stack</span>
            <span class="hljs-keyword">const</span> last = stack.top();
            <span class="hljs-comment">// the last opening character needs to match</span>
            <span class="hljs-comment">// the closing bracket we have</span>
            <span class="hljs-keyword">const</span> closing_token = maching_pairs[last];
            <span class="hljs-keyword">if</span> (token === closing_token) {
                stack.pop();
            } <span class="hljs-keyword">else</span> {
                <span class="hljs-comment">// the character don't match</span>
                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`Syntax error: missing closing <span class="hljs-subst">${closing_token}</span>`</span>);
            }
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-comment">// this case when we have closing token but no opening one,</span>
            <span class="hljs-comment">// becauase the stack is empty</span>
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`Syntax error: not matched closing <span class="hljs-subst">${token}</span>`</span>);
        }
    }
    <span class="hljs-keyword">return</span> stack.is_empty();
}
</code></pre>
<p>The above code was implemented as part of my <a target="_blank" href="https://www.freecodecamp.org/news/s-expressions-in-javascript/">S-Expression parser</a>, but the only thing I've used from that article is a function <code>tokenize()</code> that splits the string into tokens (where a token is a single object like number 123 or a string <code>"hello"</code>). If you want to process just the characters, you can use <code>str.split('')</code>, so the tokens would be an array of characters.</p>
<p>The code is much simpler than the S-Expression parser because we don't need to process all the tokens. But when we use the <code>tokenize()</code> function from the above article, we will be able to test input like this:</p>
<pre><code class="lang-lisp">(<span class="hljs-name">this</span> <span class="hljs-string">"))))"</span>)
</code></pre>
<p>The full source code (including the <code>tokenize()</code> function) can be found on <a target="_blank" href="https://gist.github.com/jcubic/4dd735dc31829ee69ce30ea4640c6fd8">GitHub</a>.</p>
<h2 id="heading-summary">Summary</h2>
<p>You shouldn't even start processing expressions that have opening and closing characters with regular expressions. On StackOverflow there is this famous answer to this question:</p>
<p><a target="_blank" href="https://stackoverflow.com/a/1732454/387194">RegEx match open tags except XHTML self-contained tags</a></p>
<p>Matching parentheses presents exactly the same problem, as parsing HTML. As you can see from the above code, ⁣it's a simpler problem to solve if we use the stack.</p>
<p>It's possible that we could write a Regular Expression that would check if the sequence of characters has matching parentheses. But it would soon get complicated if we had strings as tokens (sequence of characters), as with S-Expressions, and parentheses inside those stings should be ignored. It turns out that the solution is rather simple if we use the proper tools.</p>
<p>Personally, I love Regular Expressions, but you always need to decide if they're the right tool for the job.</p>
<p><strong>NOTE</strong>: This article was based on an article in the Polish blog <a target="_blank" href="https://jcubic.pl/">Głównie JavaScript</a> (ang. Mostly JavaScript), with a title: <a target="_blank" href="https://jcubic.pl/2020/04/parowanie-nawiasow-javascript.html">Jak parować nawiasy lub inne znaki w</a><br><a target="_blank" href="https://jcubic.pl/2020/04/parowanie-nawiasow-javascript.html">JavaScript?</a></p>
<p>If you like this article, you may want to follow me on Social Media: (<a target="_blank" href="https://x.com/jcubic">Twitter/X</a> and/or <a target="_blank" href="https://www.linkedin.com/in/jakubjankiewicz/">LinkedIn</a>). You can also check my <a target="_blank" href="https://jakub.jankiewicz.org/">personal website</a> and my <a target="_blank" href="https://jakub.jankiewicz.org/blog/">new blog</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Create a REST API Without a Server ]]>
                </title>
                <description>
                    <![CDATA[ If you're a Front-End developer and want to showcase your skills, it may be a problem if you use GitHub pages or Netlify to show your apps. Instead, you can create a REST API directly in the browser without the need of any server. With this, you can ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-create-a-rest-api-without-a-server/</link>
                <guid isPermaLink="false">66ba5ababab56b9458240035</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ REST API ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Jakub T. Jankiewicz ]]>
                </dc:creator>
                <pubDate>Mon, 20 May 2024 10:13:43 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/05/cover-5.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you're a Front-End developer and want to showcase your skills, it may be a problem if you use GitHub pages or Netlify to show your apps.</p>
<p>Instead, you can create a REST API directly in the browser without the need of any server. With this, you can showcase your skills in applications that interact with a backend hosted in places where you can't access the server side.</p>
<p>Note that if you search for "API without a server" you may find articles about <a target="_blank" href="https://en.wikipedia.org/wiki/Serverless_computing">serverless</a> (which is still a kind of <a target="_blank" href="https://www.freecodecamp.org/news/web-servers-explained-by-running-a-microbrewery-d40b9824f882/">server</a>). This article is completely different and showcases a relatively new browser API. Read on if you're interested.</p>
<h2 id="heading-table-of-contents">Table of contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#what-is-service-worker">What is a Service Worker?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-register-a-service-worker">How to register a Service Worker?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-a-basic-http-response">How to create a basic HTTP response</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-a-base-project">How to create a base project</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-set-up-vite">Set up Vite</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-use-the-wayne-library">Use the Wayne library</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-install-the-service-worker">Install the Service Worker</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-test-on-the-web-server">Test on the Web Server</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-add-react-authentication">How to add React authentication</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-create-a-jwt-token">Create a JWT token</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-add-authentication-api">Add authentication API</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-add-authentication-to-react">Add authentication to React</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-next-steps">Next steps</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-fully-working-demo">Fully working demo</a></p>
</li>
</ul>
<h2 id="heading-what-is-a-service-worker">What is a Service Worker?</h2>
<p>The browser API that allows you to create pure in the browser HTTP responses to HTTP requests is called a Service Worker. This API was mostly created to intercept HTTP requests originated from the browser and serve them from cache.</p>
<p>This allows you to create applications called <a target="_blank" href="https://www.freecodecamp.org/news/what-are-progressive-web-apps-pwa-guide/">PWA</a> that work when you don't have internet connection. So you can use them while on the train, where you may have unstable internet. When you're offline, the HTTP requests can be stored and sent to the real server when you get back online.</p>
<p>But this is not all what Service Workers can do. With them, you can create HTTP requests that never existed. It can intercept any HTTP requests, for example when you open an image in new tab or using AJAX (like with <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API">fetch API</a>).</p>
<h2 id="heading-how-to-register-a-service-worker">How to Register a Service Worker?</h2>
<p>Service worker needs to be written in a separate file (often called <code>sw.js</code>, but you can name it whatever you want).</p>
<p>The location of that file is important. It should be located in the root of your app, often in the root of the <a target="_blank" href="https://en.wikipedia.org/wiki/Domain_name">domain</a>.</p>
<p>To register a service worker, you need to execute this code:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">if</span> (<span class="hljs-string">'serviceWorker'</span> <span class="hljs-keyword">in</span> navigator) {
  <span class="hljs-keyword">var</span> scope = location.pathname.replace(<span class="hljs-regexp">/\/[^\/]+$/</span>, <span class="hljs-string">'/'</span>)
  navigator.serviceWorker.register(<span class="hljs-string">'sw.js'</span>, { scope })
    .then(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">reg</span>) </span>{
       reg.addEventListener(<span class="hljs-string">'updatefound'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
         <span class="hljs-keyword">var</span> installingWorker = reg.installing;
         <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'A new service worker is being installed:'</span>,
                     installingWorker);
       });
       <span class="hljs-comment">// registration worked</span>
       <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Registration succeeded. Scope is '</span> + reg.scope);
    }).catch(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">error</span>) </span>{
      <span class="hljs-comment">// registration failed</span>
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Registration failed with '</span> + error);
    });
}
</code></pre>
<p>This will install a service worker that can start to intercept HTTP requests.</p>
<p><strong>NOTE:</strong> The service worker works only with HTTPS and localhost.</p>
<h2 id="heading-how-to-create-a-basic-http-response">How to Create a Basic HTTP Response</h2>
<p>The API of the Service Worker is very simple – you have an event called <code>fetch</code><br>and you can respond to that event with any response:</p>
<pre><code class="lang-javascript">self.addEventListener(<span class="hljs-string">'fetch'</span>, <span class="hljs-function"><span class="hljs-params">event</span> =&gt;</span> {
    <span class="hljs-keyword">const</span> url = <span class="hljs-keyword">new</span> URL(event.request.url);
    <span class="hljs-keyword">if</span> (url.pathname === <span class="hljs-string">'/api/hello/'</span>) {
        <span class="hljs-keyword">const</span> headers = {
            <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'text/plain'</span>
        };
        <span class="hljs-keyword">const</span> msg = <span class="hljs-string">'Hello, Service Worker!'</span>
        event.respondWith(textResponse(msg, headers));
   }
});

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">textResponse</span>(<span class="hljs-params">string, headers</span>) </span>{
    <span class="hljs-keyword">const</span> blob = <span class="hljs-keyword">new</span> Blob([string], {
        <span class="hljs-attr">type</span>: <span class="hljs-string">'text/plain'</span>
    });
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Response(blob, { headers });
}
</code></pre>
<p>With this you code, you can open the URL <code>/api/hello/</code> and it will display the text <code>"Hello, Service Worker!"</code> as a text file.</p>
<p>Also, one important thing: if you want to use the Service Worker immediately after it's installed, you need to add this code:</p>
<pre><code class="lang-javascript">self.addEventListener(<span class="hljs-string">'activate'</span>, <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
  event.waitUntil(clients.claim());
});
</code></pre>
<p>Normally, the Service Worker intercepts requests only after you refresh the page. This code forces to accept requests immediately after installation.</p>
<p><strong>NOTE:</strong> With service worker, you can also intercept request that are sent to different domains. If you have your app on GitHub pages, you can intercept the requests to any domain. Because there are no checks of the domain, this code:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">await</span> fetch(<span class="hljs-string">'https://example.com/api/hello'</span>).then(<span class="hljs-function"><span class="hljs-params">res</span> =&gt;</span> res.text())
</code></pre>
<p>will also return <code>Hello, Service Worker!</code>.</p>
<h2 id="heading-how-to-create-a-base-project">How to Create a Base Project</h2>
<p>You will create something more useful by creating a React project with very simple user authentication.</p>
<p>Note that this is not secure in any way, because user information and passwords will be visible in the code. But it can show that you know how to interact with an API in React.</p>
<h3 id="heading-set-up-vite">Set Up Vite</h3>
<p>First, you need to set up a simple React application with <a target="_blank" href="https://vitejs.dev/">Vite</a>.</p>
<p>To use Vite, you need to have Node.js installed. If you don't have it, you can read how to install it from <a target="_blank" href="https://www.freecodecamp.org/news/how-to-install-node-in-your-machines-macos-linux-windows/">this article</a>.</p>
<p>Then, you need to run this command from the terminal:</p>
<pre><code class="lang-bash">npm create vite@latest
</code></pre>
<p>I've picked name <code>auth</code>, React, and JavaScript. This is the output I've got:</p>
<pre><code class="lang-python">✔ Project name: … auth
✔ Select a framework: › React
✔ Select a variant: › JavaScript

Scaffolding project <span class="hljs-keyword">in</span> /home/kuba/auth...

Done. Now run:

  cd auth
  npm install
  npm run dev
</code></pre>
<p>Next is to modify <code>vite.config.js</code> file, so Vite will know how to build the service worker file/</p>
<p>This is the config file Vite created:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { defineConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">'vite'</span>
<span class="hljs-keyword">import</span> react <span class="hljs-keyword">from</span> <span class="hljs-string">'@vitejs/plugin-react'</span>

<span class="hljs-comment">// https://vitejs.dev/config/</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
  <span class="hljs-attr">plugins</span>: [react()],
})
</code></pre>
<p>You need to modify the config file to include this code:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { join } <span class="hljs-keyword">from</span> <span class="hljs-string">"node:path"</span>;
<span class="hljs-keyword">import</span> { buildSync } <span class="hljs-keyword">from</span> <span class="hljs-string">"esbuild"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
  <span class="hljs-attr">plugins</span>: [
    react(),
    {
      <span class="hljs-attr">apply</span>: <span class="hljs-string">"build"</span>,
      <span class="hljs-attr">enforce</span>: <span class="hljs-string">"post"</span>,
      transformIndexHtml() {
        buildSync({
          <span class="hljs-attr">minify</span>: <span class="hljs-literal">true</span>,
          <span class="hljs-attr">bundle</span>: <span class="hljs-literal">true</span>,
          <span class="hljs-attr">entryPoints</span>: [join(process.cwd(), <span class="hljs-string">"src"</span>, <span class="hljs-string">"sw.js"</span>)],
          <span class="hljs-attr">outfile</span>: join(process.cwd(), <span class="hljs-string">"dist"</span>, <span class="hljs-string">"sw.js"</span>),
        });
      },
    },
  ]
})
</code></pre>
<p>You need to include both imports and you can replace the existing config with the one above. You can also add the code in the curly braces into a plugin array.</p>
<h3 id="heading-use-the-wayne-library">Use the Wayne library</h3>
<p>Then, you need to create a Service Worker file named <code>sw.js</code>. You will use <a target="_blank" href="https://github.com/jcubic/wayne">Wayne library</a> instead of writing the routes yourself. This will simplify the code.</p>
<p>First, you need to install Wayne:</p>
<pre><code class="lang-bash">npm install @jcubic/wayne
</code></pre>
<p>Then, you can create a file named <code>sw.js</code> (<strong>Note:</strong> you've put the <code>"src"</code> directory in the <code>vite.config.js</code> file, so you should save the file in that directory).</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { Wayne } <span class="hljs-keyword">from</span> <span class="hljs-string">'@jcubic/wayne'</span>;

<span class="hljs-keyword">const</span> app = <span class="hljs-keyword">new</span> Wayne();

app.get(<span class="hljs-string">'/api/hello/'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
   res.text(<span class="hljs-string">'Hello, Service Worker!'</span>);
});
</code></pre>
<p>This code will work exactly the same as our previous example.</p>
<h3 id="heading-install-the-service-worker">Install the Service Worker</h3>
<p>Now, the last thing you need to do to set up your service worker is to register it. You could use the code that you saw earlier, but now you will use a library for this.</p>
<p>First, you need to install it:</p>
<pre><code class="lang-bash">npm install register-service-worker
</code></pre>
<p>And update <code>src/main.jsx</code> with this code:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> { register } <span class="hljs-keyword">from</span> <span class="hljs-string">"register-service-worker"</span>;

register(<span class="hljs-string">`./sw.js`</span>);
</code></pre>
<p>The last thing is to build the project by executing:</p>
<pre><code class="lang-bash">npm run build
</code></pre>
<p><strong>NOTE</strong>: the <code>dev</code> mode will not work with the service worker – you need to build the project.</p>
<p>The instructions setting up a Service Worker with Vite were based on <a target="_blank" href="https://dev.to/reeshee/how-to-bundle-your-custom-service-worker-in-vite-without-using-pwa-4nk">this articl</a>e.</p>
<h3 id="heading-test-on-the-web-server">Test on the Web Server</h3>
<p>To test your project you can use this command:</p>
<pre><code class="lang-bash">npx http-server -p 3000 ./dist/
</code></pre>
<p>This will create a simple HTTP server where you can test your application.</p>
<p><strong>NOTE</strong>: if you open the<code>index.html</code> file in a browser (like with drag and drop), the service worker will not work. This is because the <code>file://</code> protocol has a lot of restrictions. That's why you need a web server.</p>
<p>If you test the app in the browser by opening the URL: <code>http://127.0.0.1:3000</code>, it will run the code that registers the service worker, and you will immediately be able to access our fake HTTP endpoint: <code>http://127.0.0.1:3000/api/hello/</code>. It should display the text:</p>
<pre><code class="lang-python">Hello, Service Worker!
</code></pre>
<p><strong>NOTE</strong>: to simplify testing, you can add <code>"http-server -p 3000 ./dist/"</code> to the package.json file into <code>scripts</code>:</p>
<pre><code class="lang-json"><span class="hljs-string">"serve"</span>: <span class="hljs-string">"http-server -p 3000 ./dist/"</span>,
</code></pre>
<p>Remember that <code>package.json</code> is a JSON file, so you can't put a trailing comma if this will be the last script.</p>
<p>To make it work, you need to install the package:</p>
<pre><code class="lang-bash">npm install http-server
</code></pre>
<p>Now you can run the server with <code>npm run serve</code>.</p>
<p><strong>NOTE</strong>: if you access the URL: <code>http://127.0.0.1:3000/api/hello</code> (you can read what is 127.0.0.1 in <a target="_blank" href="https://www.freecodecamp.org/news/what-is-localhost/">this article</a>), you will get an error from <code>http-server</code>. This is because the route you created in the service worker used a trailing slash. To fix this, you can add a redirect:</p>
<pre><code class="lang-javascript">app.get(<span class="hljs-string">'/api/hello'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
   res.redirect(<span class="hljs-number">301</span>, req.url + <span class="hljs-string">'/'</span>);
});
</code></pre>
<h2 id="heading-how-to-add-react-authentication">How to Add React Authentication</h2>
<p>Now, after you have set up everything, you can add a real authentication endpoint and connect it with your React app.</p>
<h3 id="heading-create-a-jwt-token">Create a JWT token</h3>
<p>We will use a popular JWT token for authentication. You can read more about them in <a target="_blank" href="https://www.freecodecamp.org/news/how-to-sign-and-validate-json-web-tokens/">this article</a>.</p>
<p>First, you need to install a JWT library:</p>
<pre><code class="lang-bash">npm install jose
</code></pre>
<p>Then, you need to create a new file named <code>jwt.js</code> in the <code>src</code> directory:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { SignJWT, jwtVerify } <span class="hljs-keyword">from</span> <span class="hljs-string">'jose'</span>;

<span class="hljs-keyword">const</span> secret = <span class="hljs-keyword">new</span> TextEncoder().encode(
  <span class="hljs-string">'cc7e0d44fd473002f1c42167459001140ec6389b7353f8088f4d9a95f2f596f2'</span>
);

<span class="hljs-keyword">const</span> alg = <span class="hljs-string">'HS256'</span>;

<span class="hljs-keyword">const</span> jwt = {
    <span class="hljs-attr">sign</span>: <span class="hljs-function">(<span class="hljs-params">payload</span>) =&gt;</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> SignJWT(payload)
            .setProtectedHeader({ alg })
            .setIssuedAt()
            .setIssuer(<span class="hljs-string">'https://freecodecamp.org'</span>)
            .setAudience(<span class="hljs-string">'https://freecodecamp.org'</span>)
            .setExpirationTime(<span class="hljs-string">'2h'</span>)
            .sign(secret)
    },
    <span class="hljs-attr">verify</span>: <span class="hljs-keyword">async</span> (token) =&gt; {
        <span class="hljs-keyword">const</span> { payload } = <span class="hljs-keyword">await</span> jwtVerify(token, secret, {
            <span class="hljs-attr">issuer</span>: <span class="hljs-string">'https://freecodecamp.org'</span>,
            <span class="hljs-attr">audience</span>: <span class="hljs-string">'https://freecodecamp.org'</span>,
        });
        <span class="hljs-keyword">return</span> payload;
    }
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> jwt;
</code></pre>
<p>This code is an <a target="_blank" href="https://www.freecodecamp.org/news/javascript-modules-beginners-guide/">ES Module</a> that uses the <a target="_blank" href="https://github.com/panva/jose"><code>jose</code> JWT token library</a> to create a new token, <code>jwt.sign</code>. It verifies that the token is correct with <code>jwt.verify</code>, and it also returns the payload, so you can extract anything you save in the token.</p>
<p>You can read more about the <code>jose</code> library from the documentation – the links to the <a target="_blank" href="https://github.com/panva/jose">docs are in the README</a>.</p>
<p><strong>NOTE</strong>: Because of the limitation of Service Worker, we can't create a proper real life authentication, where the access token is stored in a cookie (Service Worker don't allow creating cookies) and use refresh tokens to update the access token.</p>
<h3 id="heading-add-authentication-api">Add authentication API</h3>
<p>Now, you can use the previous functions to create an API endpoint:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> jwt <span class="hljs-keyword">from</span> <span class="hljs-string">'./jwt'</span>;

app.post(<span class="hljs-string">'/api/login'</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
    <span class="hljs-keyword">const</span> { username, password } = <span class="hljs-keyword">await</span> req.json() ?? {};
    <span class="hljs-keyword">if</span> (username === <span class="hljs-string">'demo'</span> &amp;&amp; password === <span class="hljs-string">'demo'</span>) {
        <span class="hljs-keyword">const</span> token = <span class="hljs-keyword">await</span> jwt.sign({ username });
        res.json({ <span class="hljs-attr">result</span>: token });
    } <span class="hljs-keyword">else</span> {
        res.json({ <span class="hljs-attr">error</span>: <span class="hljs-string">'Invalid username or password'</span> });
    }
});
</code></pre>
<p>This code will verify that the username and password are correct (both equal to <code>"demo"</code>), ⁣and create a new JWT token. If the username or password are not correct, it will return an error.</p>
<h3 id="heading-add-authentication-to-react">Add authentication to React</h3>
<p>You created a React App with Vite, so you need to use JSX to add front-end authentication logic.</p>
<p>First, you create a helper function that will send an HTTP request to the <code>/api/login</code> endpoint with <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch">Fetch API</a>:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">login</span>(<span class="hljs-params">username, password</span>) </span>{
    <span class="hljs-keyword">return</span> fetch(<span class="hljs-string">'/api/login'</span>, {
        <span class="hljs-attr">method</span>: <span class="hljs-string">'post'</span>,
        <span class="hljs-attr">headers</span>: {
            <span class="hljs-string">'Accept'</span>: <span class="hljs-string">'application/json'</span>,
            <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>
        },
        <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify({
            username,
            password
        })
    }).then(<span class="hljs-function"><span class="hljs-params">res</span> =&gt;</span> res.json());
}
</code></pre>
<p>Next, you need to create a basic form:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">form</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"user"</span>&gt;</span>username<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"user"</span> /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"password"</span>&gt;</span>password<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"password"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"password"</span> /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">button</span>&gt;</span>login<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
</code></pre>
<p>And a add bit of styling:</p>
<pre><code class="lang-css"><span class="hljs-selector-tag">form</span> {
  <span class="hljs-attribute">display</span>: inline-flex;
  <span class="hljs-attribute">flex-direction</span>: column;
  <span class="hljs-attribute">gap</span>: <span class="hljs-number">10px</span>;
  <span class="hljs-attribute">align-items</span>: flex-end;
}

<span class="hljs-selector-tag">label</span><span class="hljs-selector-pseudo">::after</span> {
  <span class="hljs-attribute">content</span>: <span class="hljs-string">":"</span>;
}

<span class="hljs-selector-tag">label</span> {
  <span class="hljs-attribute">width</span>: <span class="hljs-number">100px</span>;
  <span class="hljs-attribute">display</span>: inline-block;
  <span class="hljs-attribute">text-align</span>: right;
  <span class="hljs-attribute">margin-right</span>: <span class="hljs-number">10px</span>;
}
</code></pre>
<p>Next, you need an authentication function that you will add to an <code>onSubmit</code> event. ⁣<br>You will use two state variables for token and error:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [token, setToken] = useState(<span class="hljs-literal">null</span>);
  <span class="hljs-keyword">const</span> [error, setError] = useState(<span class="hljs-literal">null</span>);

  <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">auth</span>(<span class="hljs-params">event</span>) </span>{
    event.preventDefault();

    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> login(username, password);
    <span class="hljs-keyword">if</span> (res.result) {
      setToken(res.result);
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (res.error) {
      setError(res.error);
    }
  }
</code></pre>
<p>To get the username and password from the form you can use refs. You can also display the form only when the token is not set:</p>
<pre><code class="lang-jsx"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [token, setToken] = useState(<span class="hljs-literal">null</span>);
  <span class="hljs-keyword">const</span> [error, setError] = useState(<span class="hljs-literal">null</span>);
  <span class="hljs-keyword">const</span> userRef = useRef();
  <span class="hljs-keyword">const</span> passwordRef = useRef();

  <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">auth</span>(<span class="hljs-params">event</span>) </span>{
    event.preventDefault();
    <span class="hljs-keyword">const</span> username = userRef.current.value;
    <span class="hljs-keyword">const</span> username = passwordRef.current.value;

    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> login(username, password);
    <span class="hljs-keyword">if</span> (res.result) {
      setToken(res.result);
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (res.error) {
      setError(res.error);
    }
  }

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"card"</span>&gt;</span>
        {!token &amp;&amp; (
          <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{auth}</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"user"</span>&gt;</span>username<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"user"</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">{userRef}/</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"password"</span>&gt;</span>password<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"password"</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">{passwordRef}</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"password"</span>/&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">button</span>&gt;</span>login<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
        )}
        {error &amp;&amp; <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"error"</span>&gt;</span>{ error }<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>}
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
</code></pre>
<p>Now you can test the App. If you type the username and password, they don't reset.</p>
<p>You can fix it by setting the ref value to an empty string at the end of the function:</p>
<pre><code class="lang-javascript">userRef.current.value = <span class="hljs-string">''</span>;
passwordRef.current.value = <span class="hljs-string">''</span>;
</code></pre>
<p>There is another error. If you put a wrong username or password, you will get an error. But then, if you type the correct password, the error is not removed. To fix this issue, you need to reset the error state when setting the token:</p>
<pre><code class="lang-javascript">  <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">auth</span>(<span class="hljs-params">event</span>) </span>{
    event.preventDefault();
    <span class="hljs-keyword">const</span> username = userRef.current.value;
    <span class="hljs-keyword">const</span> username = passwordRef.current.value;

    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> login(username, password);
    <span class="hljs-keyword">if</span> (res.result) {
      setToken(res.result);
      setError(<span class="hljs-literal">null</span>);
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (res.error) {
      setError(res.error);
    }
    userRef.current.value = <span class="hljs-string">''</span>;
    passwordRef.current.value = <span class="hljs-string">''</span>;
  }
</code></pre>
<p>Next thing that you can do is to extract the username from the token. This will also verify that the token is correct in your React app. You need to use the <code>useEffect</code> hook to run the code when the token changes:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> jwt <span class="hljs-keyword">from</span> <span class="hljs-string">'./jwt'</span>;

  <span class="hljs-comment">// ...</span>
  <span class="hljs-keyword">const</span> [username, setUsername] = useState(<span class="hljs-literal">null</span>);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    jwt.verify(token).then(<span class="hljs-function"><span class="hljs-params">payload</span> =&gt;</span> {
      <span class="hljs-keyword">const</span> { username } = payload;
      setUsername(username);
    }).catch(<span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> {
      setError(e.message);
    });
  }, [token]);

  <span class="hljs-comment">// ...</span>
</code></pre>
<p>If you run this code, you will get an error: <code>Compact JWS must be a string or Uint8Array</code>.</p>
<p>The reason is that the <code>useEffect</code> hook will be triggered when the token is <code>null</code>. Before you verify, you need to check if the token was set:</p>
<pre><code class="lang-javascript">  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (token !== <span class="hljs-literal">null</span>) {
      jwt.verify(token).then(<span class="hljs-function"><span class="hljs-params">payload</span> =&gt;</span> {
        <span class="hljs-keyword">const</span> { username } = payload;
        setUsername(username);
      }).catch(<span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> {
        setError(e.message);
      });
    }
  }, [token]);
</code></pre>
<p>Next, you can display the username after user login:</p>
<pre><code class="lang-jsx">{token &amp;&amp; (
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Welcome {username}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
)}
</code></pre>
<h2 id="heading-next-steps">Next Steps</h2>
<p>The last thing we can do is to save the token in <code>localStorage</code> and add a logout button. But this is left as an exercise to the reader.</p>
<p>You can read about <code>localStorage</code> from <a target="_blank" href="https://www.freecodecamp.org/news/use-local-storage-in-modern-applications/">this freeCodeCamp article</a>.</p>
<p>You can improve this and add more endpoints, like getting real data that you will save in a <code>sw.js</code> file. You can store the data in IndexedDB, so it will be persistent like in in a real app. Read more about IndexedDB from <a target="_blank" href="https://www.freecodecamp.org/news/how-indexeddb-works-for-beginners/">this article</a>.</p>
<p>IndexedDB doesn't have a very nice API, but there are libraries that add abstraction on top of it. My favorite is the SQL library <a target="_blank" href="https://alasql.org/">AlaSQL</a>, and <a target="_blank" href="https://github.com/jakearchibald/idb">idb</a> by <a target="_blank" href="https://jakearchibald.com/">Jake Archibald</a>.</p>
<h2 id="heading-fully-working-demo">Fully working demo</h2>
<p>The full source code is available on GitHub in the repository <a target="_blank" href="https://github.com/jcubic/react-wayne-auth">jcubic/react-wayne-auth</a>. You can test a working demo on <a target="_blank" href="https://jcubic.github.io/react-wayne-auth/">GitHub pages</a>.</p>
<p>If you like this article, you may want to follow me on Social Media: (<a target="_blank" href="https://x.com/jcubic">Twitter/X</a> and/or <a target="_blank" href="https://www.linkedin.com/in/jakubjankiewicz/">LinkedIn</a>) and you an also check my <a target="_blank" href="https://jakub.jankiewicz.org/">personal website</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Create an Interactive Terminal Portfolio Website ]]>
                </title>
                <description>
                    <![CDATA[ In this article, you will learn how to create an interactive terminal-based portfolio and a résumé in JavaScript. We'll use the jQuery Terminal library (and a few other tools) to create a website that looks like a real terminal. This article will sho... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-create-an-interactive-terminal-portfolio-website/</link>
                <guid isPermaLink="false">66ba5abdf4ac8da2b2c2e865</guid>
                
                    <category>
                        <![CDATA[ portfolio ]]>
                    </category>
                
                    <category>
                        <![CDATA[ projects ]]>
                    </category>
                
                    <category>
                        <![CDATA[ terminal ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Jakub T. Jankiewicz ]]>
                </dc:creator>
                <pubDate>Mon, 29 Apr 2024 14:49:54 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1730895455049/8fefc48c-761d-4ec5-8f60-b6eb2f97a42a.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this article, you will learn how to create an interactive terminal-based portfolio and a résumé in JavaScript. We'll use the <a target="_blank" href="https://terminal.jcubic.pl/">jQuery Terminal library</a> (and a few other tools) to create a website that looks like a real terminal.</p>
<p>This article will show more advanced usage of the jQuery Terminal library. If you want something more basic, you can check this article: <a target="_blank" href="https://itnext.io/how-to-create-interactive-terminal-like-website-888bb0972288">How to create interactive terminal like website with JavaScript</a> that is written for more entry level programmers. You can also read it (or skim it) first before you begin reading this one.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-the-terminal-and-its-history">What is the terminal and its history?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-jquery-terminal">What is jQuery Terminal?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-base-html-file">Base html file</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-initialize-the-terminal">How to Initialize the Terminal</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-welcome-message">Welcome message</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-line-gaps">Line Gaps</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-add-colors-to-ascii-art">How to Add Colors to ASCII Art</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-terminal-formatting">Terminal Formatting</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-use-the-lolcat-library">How to Use the Lolcat Library</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-rainbow-ascii-art-greetings">Rainbow ASCII Art Greetings</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-make-the-greeting-text-white">How to Make the Greeting Text White</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-make-your-first-command">How to Make Your First Command</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-default-commands">Default Commands</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-make-help-commands-executable">How to Make Help Commands Executable</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-syntax-highlighting">Syntax Highlighting</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-tab-completion">Tab Completion</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-add-shell-commands">How to Add Shell Commands</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-improve-completion">How to Improve Completion</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-typing-animation-command">Typing Animation Command</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-credits-command">Credits Command</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-prefilled-commands">Prefilled Commands</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-sharing-link-to-terminal-session">Sharing Link to Terminal Session</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-working-terminal-portfolio-demo">Working Terminal Portfolio Demo</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-adding-executables-to-home-directory">Adding Executables to Home Directory</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-next">What Next?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-share-what-youve-created">Share what you’ve created</a></p>
</li>
</ul>
<h2 id="heading-what-is-the-terminal-and-its-history">What is the terminal and its history?</h2>
<p>Terminals have a long history. It started as an upgrade from <a target="_blank" href="https://en.wikipedia.org/wiki/Punched_card">punch cards</a>. Computers back in the day used teletypes, which was just a keyboard and a printer. You'd type on the keyboard, and the keystrokes would be sent to the computer (usually mainframe) and the output got printed on a printer.</p>
<p>Later on, the teletypes were replaced with terminals. A terminal was like the dump computer we see today. It was a CRT Monitor with a keyboard. So instead of getting the output on the printer, it would be displayed on the monitor.</p>
<p>Today we still use this type of interface (the Command Line) to talk with computers.</p>
<p>The command line is a terminal emulator and is a big part of Unix systems, like GNU/Linux or MacOS. On Windows, you have PowerShell or cmd.exe file that allows you to type commands and get responses in the form of text. You can also install GNU/Linux system on Windows in the form of WSL. CLI interfaces are used mostly by power users, developers, and system administrators.</p>
<p>If you're new to the command line, you can read this article: <a target="_blank" href="https://www.freecodecamp.org/news/command-line-for-beginners/">Command Line for Beginners – How to Use the Terminal Like a Pro [Full Handbook]</a>.</p>
<h2 id="heading-what-is-jquery-terminal">What is jQuery Terminal?</h2>
<p>jQuery Terminal is a JavaScript library. It's a plugin for the <a target="_blank" href="https://en.wikipedia.org/wiki/JQuery">jQuery library</a>. jQuery Terminal is more like a framework that has jQuery as its dependency. We'll mostly use JavaScript and very little jQuery in this article.</p>
<p>Let's create our terminal-based portfolio using jQuery Terminal.</p>
<h3 id="heading-base-html-file">Base HTML file</h3>
<p>The first thing you need to do is to include jQuery and jQuery Terminal library in your project.</p>
<p>This is a basic HTML file:</p>
<pre><code class="lang-html"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://cdn.jsdelivr.net/npm/jquery.terminal/css/jquery.terminal.min.css"</span>/&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://cdn.jsdelivr.net/npm/jquery"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://cdn.jsdelivr.net/npm/jquery.terminal/js/jquery.terminal.min.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"my-terminal.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>Then inside the <strong>my-terminal.js</strong> file, we will write our code in JavaScript.</p>
<h3 id="heading-how-to-initialize-the-terminal">How to Initialize the Terminal</h3>
<p>To create a basic terminal, you need to put in this code:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> commands = {};

<span class="hljs-keyword">const</span> term = $(<span class="hljs-string">'body'</span>).terminal(commands);
</code></pre>
<p>The string <code>'body'</code> indicates the CSS selector where terminal should be created. Here we use <code>'body'</code> so the terminal will be the only thing on the page. But it doesn't have to be full screen. You can create a website where the terminal is only part of the page, like in a window that looks like part of the Operating System.</p>
<p>The first argument to the terminal method is called an interpreter. It's a way to add your commands. An object is the simplest way to create them. See <a target="_blank" href="https://github.com/jcubic/jquery.terminal/wiki/Getting-Started#creating-the-interpreter">creating the interpreter</a> to learn more.</p>
<p>If the terminal font is too small, you can make it a little bit bigger with CSS custom properties (also known as CSS variables):</p>
<pre><code class="lang-css"><span class="hljs-selector-pseudo">:root</span> {
    <span class="hljs-attribute">--size</span>: <span class="hljs-number">1.2</span>;
}
</code></pre>
<h3 id="heading-welcome-message">Welcome Message</h3>
<p>The first thing we need to do is to get rid of the default greeting message and replace it with nice-looking custom <a target="_blank" href="https://en.wikipedia.org/wiki/ASCII_art">ASCII Art</a>. We will use the <a target="_blank" href="https://en.wikipedia.org/wiki/FIGlet">Filget library</a> written in JavaScript to do this.</p>
<p>There are a few Figlet libraries on npm. We will use a package named <a target="_blank" href="https://www.npmjs.com/package/figlet">figlet</a>.</p>
<p>The first thing you should do is pick the right font. Go to <a target="_blank" href="https://patorjk.com/software/taag/">figlet playground</a> and write the text you want for your greeting. We will use "Terminal Portfolio" and click "Test All". It should display your text with all the fonts. Scroll through the list and pick the font you like.</p>
<p>I picked a font "slant" that looks like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/Przechwycenie-obrazu-ekranu_2024-04-26_22-18-26.png" alt="Terminal Portfolio ASCII Art" width="600" height="400" loading="lazy"></p>
<p>You can copy this text and put into a string, but you will have issues like with the backslash that needs to be escaped using quote characters.</p>
<pre><code class="lang-lisp">const greetings = `  ______                    _             __   ____             __  ____      ___     
 /_  __/__  _________ ___  (<span class="hljs-name">_</span>)___  ____ _/ /  / __ \\____  _____/ /_/ __/___  / (<span class="hljs-name">_</span>)___ 
  / / / _ \\/ ___/ __ \`__ \\/ / __ \\/ __ \`/ /  / /_/ / __ \\/ ___/ __/ /_/ __ \\/ / / __ \\
 / / /  __/ /  / / / / / / / / / / /_/ / /  / ____/ /_/ / /  / /_/ __/ /_/ / / / /_/ /
/_/  \\___/_/  /_/ /_/ /_/_/_/ /_/\\__,_/_/  /_/    \\____/_/   \\__/_/  \\____/_/_/\\____/`

const term = $('body').terminal(<span class="hljs-name">commands</span>, {
    greetings
})<span class="hljs-comment">;</span>
</code></pre>
<p><strong>NOTE</strong>: The second argument to the jQuery Terminal is an object with options – we used a single option <code>greetings</code>.</p>
<p>This doesn't look good and it's hard to modify. Also, if you create the greeting by hardcoding a string, it may get distorted on smaller screens. That's why we will use the figlet library in JavaScript.</p>
<p>First, we need to include the figlet library in HTML:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://cdn.jsdelivr.net/npm/figlet/lib/figlet.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>To initialize the library in JavaScript, we need to load the fonts:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> font = <span class="hljs-string">'Slant'</span>;

figlet.defaults({ <span class="hljs-attr">fontPath</span>: <span class="hljs-string">'https://unpkg.com/figlet/fonts/'</span> });
figlet.preloadFonts([font], ready);
</code></pre>
<p>This code will load the <code>'Slant'</code> font and call the function <code>ready</code> when the font is loaded.</p>
<p>So we need to write this function:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ready</span>(<span class="hljs-params"></span>) </span>{

}
</code></pre>
<p>Now we can do two things, we can put the initialization of jQuery Terminal inside that function:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">let</span> term;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ready</span>(<span class="hljs-params"></span>) </span>{
   term =  $(<span class="hljs-string">'body'</span>).terminal(commands, {
      greetings
   });
}
</code></pre>
<p>With this, we can use the <code>greeting</code> option. But we can also use the <code>echo</code> method to render the greeting, and when initializing the terminal we will put <code>null</code> or <code>false</code> as a <code>greetings</code> to disable the default one:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> term = $(<span class="hljs-string">'body'</span>).terminal(commands, {
    <span class="hljs-attr">greetings</span>: <span class="hljs-literal">false</span>
});

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ready</span>(<span class="hljs-params"></span>) </span>{
   term.echo(greetings);
}
</code></pre>
<p>This will work better because the library will initialize the terminal immediately and will not need to wait for loading the fonts.</p>
<p>Note that we still need to define the greetings using figlet. To do this we can write this function:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">render</span>(<span class="hljs-params">text</span>) </span>{
    <span class="hljs-keyword">const</span> cols = term.cols();
    <span class="hljs-keyword">return</span> figlet.textSync(text, {
        <span class="hljs-attr">font</span>: font,
        <span class="hljs-attr">width</span>: cols,
        <span class="hljs-attr">whitespaceBreak</span>: <span class="hljs-literal">true</span>
    });
}
</code></pre>
<p>This function uses the <code>figlet::textSync()</code> method to return a string and use <code>terminal::cols()</code>, to get the number of characters per line. With this, we can make our text responsive.</p>
<p>This function can be used inside <code>ready</code>.</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ready</span>(<span class="hljs-params"></span>) </span>{
   term.echo(render(<span class="hljs-string">'Terminal Portfolio'</span>));
}
</code></pre>
<p>This will create a string and pass it to the <code>echo</code> method. But this will be the same as with:</p>
<pre><code class="lang-javascript">term.echo(greeting);
</code></pre>
<p>And our hard-coded greetings. So if you resize the terminal, the greetings can still get distorted. To make the text responsive, you need to <code>echo</code> a function. This function will be called on each re-render of the terminal, which will happen when you resize the page.</p>
<p>We can use the arrow function for this:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ready</span>(<span class="hljs-params"></span>) </span>{
   term.echo(<span class="hljs-function">() =&gt;</span> render(<span class="hljs-string">'Terminal Portfolio'</span>));
}
</code></pre>
<p>If you want to add some text below the ASCII art, you can do this by concatenating the string after render:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ready</span>(<span class="hljs-params"></span>) </span>{
   term.echo(<span class="hljs-function">() =&gt;</span> {
     <span class="hljs-keyword">const</span> ascii = render(<span class="hljs-string">'Terminal Portfolio'</span>);
     <span class="hljs-keyword">return</span> <span class="hljs-string">`<span class="hljs-subst">${ascii}</span>\nWelcome to my Terminal Portfolio\n`</span>;
   });
}
</code></pre>
<p><strong>NOTE</strong>: If you run this code, you will notice that there is an empty line after the ASCII art. This is because the figlet library adds some spaces after the text.</p>
<p>To get rid of this, you can use <code>string::replace()</code> with a regular expression that will remove all spaces and newlines from the end.</p>
<p>We can't use <code>string::trim()</code>, because we don't want to remove the leading lines:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">render</span>(<span class="hljs-params">text</span>) </span>{
    <span class="hljs-keyword">const</span> cols = term.cols();
    <span class="hljs-keyword">return</span> trim(figlet.textSync(text, {
        <span class="hljs-attr">font</span>: font,
        <span class="hljs-attr">width</span>: cols,
        <span class="hljs-attr">whitespaceBreak</span>: <span class="hljs-literal">true</span>
    }));
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">trim</span>(<span class="hljs-params">str</span>) </span>{
    <span class="hljs-keyword">return</span> str.replace(<span class="hljs-regexp">/[\n\s]+$/</span>, <span class="hljs-string">''</span>);
}
</code></pre>
<p>You can also pause the terminal when it loads the fonts:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> term = $(<span class="hljs-string">'body'</span>).terminal(commands, {
    <span class="hljs-attr">greetings</span>: <span class="hljs-literal">false</span>
});

term.pause();

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ready</span>(<span class="hljs-params"></span>) </span>{
   term.echo(<span class="hljs-function">() =&gt;</span> render(<span class="hljs-string">'Terminal Portfolio'</span>)).resume();
}
</code></pre>
<p>You can can chain terminal methods, the same as with jQuery.</p>
<h3 id="heading-line-gaps">Line Gaps</h3>
<p>If the font you pick creates gaps between the lines, like in this image with font ANSI Shadow:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Przechwycenie-obrazu-ekranu_2024-05-08_14-06-41.png" alt="Image: ASCII Art with line gaps" width="600" height="400" loading="lazy"></p>
<p>You can remove the gaps by adding the <code>ansi</code> option set to <code>true</code>. The option was added specifically to fix an issue with displaying <a target="_blank" href="https://en.wikipedia.org/wiki/ANSI_art">ANSI Art</a>.</p>
<pre><code class="lang-javascript">term.echo(<span class="hljs-function">() =&gt;</span> render(<span class="hljs-string">'Terminal Portfolio'</span>), { <span class="hljs-attr">ansi</span>: <span class="hljs-literal">true</span> });
</code></pre>
<p>The above ASCII Art will look like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Przechwycenie-obrazu-ekranu_2024-05-08_14-57-16.png" alt="Image: ASCII Art with gaps removed" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-add-colors-to-ascii-art">How to Add Colors to ASCII Art</h2>
<p>You can spice up your ASCII Art by using a library called lolcat. lolcat is a Linux command that can style text in the terminal with rainbow colors. There is also a library called <a target="_blank" href="https://www.npmjs.com/package/isomorphic-lolcat">isomorphic-lolcat</a>, that you can use in JavaScript to make your ASCII Art in rainbow colors.</p>
<h3 id="heading-terminal-formatting">Terminal Formatting</h3>
<p>To use the lolcat library, you first need to know how to change the colors of the terminal.</p>
<p>You can do this using low-level formatting that looks like this:</p>
<pre><code class="lang-lisp">[[b<span class="hljs-comment">;red;]some text]</span>
</code></pre>
<p>The whole text is wrapped in brackets and the formatting of the text is in additional brackets, where each argument is separated by a semicolon. To learn more about the syntax, you can read the Wiki Article: <a target="_blank" href="https://github.com/jcubic/jquery.terminal/wiki/Formatting-and-Syntax-Highlighting">Formatting and Syntax Highlighting</a>.</p>
<p>Here, we'll only use a basic change of color. Instead of red, you can use CSS color names, hex color, or <code>rgb()</code>.</p>
<h3 id="heading-how-to-use-the-lolcat-library">How to Use the Lolcat Library</h3>
<p>To use the library, we first need to include it in HTML:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://cdn.jsdelivr.net/npm/isomorphic-lolcat"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>To format the string with colors, we can use this function:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">rainbow</span>(<span class="hljs-params">string</span>) </span>{
    <span class="hljs-keyword">return</span> lolcat.rainbow(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">char, color</span>) </span>{
        char = $.terminal.escape_brackets(char);
        <span class="hljs-keyword">return</span> <span class="hljs-string">`[[;<span class="hljs-subst">${hex(color)}</span>;]<span class="hljs-subst">${char}</span>]`</span>;
    }, string).join(<span class="hljs-string">'\n'</span>);
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">hex</span>(<span class="hljs-params">color</span>) </span>{
    <span class="hljs-keyword">return</span> <span class="hljs-string">'#'</span> + [color.red, color.green, color.blue].map(<span class="hljs-function"><span class="hljs-params">n</span> =&gt;</span> {
        <span class="hljs-keyword">return</span> n.toString(<span class="hljs-number">16</span>).padStart(<span class="hljs-number">2</span>, <span class="hljs-string">'0'</span>);
    }).join(<span class="hljs-string">''</span>);
}
</code></pre>
<p>The <code>lolcat.rainbow</code> will call a function in every character from the input string, and pass color as an object with RGB values and the character.</p>
<h3 id="heading-rainbow-ascii-art-greetings">Rainbow ASCII Art Greetings</h3>
<p>To use this code, you need to wrap the call to render with <code>rainbow</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ready</span>(<span class="hljs-params"></span>) </span>{
   term.echo(<span class="hljs-function">() =&gt;</span> {
     <span class="hljs-keyword">const</span> ascii = rainbow(render(<span class="hljs-string">'Terminal Portfolio'</span>));
     <span class="hljs-keyword">return</span> <span class="hljs-string">`<span class="hljs-subst">${ascii}</span>\nWelcome to my Terminal Portfolio\n`</span>;
   }).resume();
}
</code></pre>
<p>You can also use two calls to echo, since only the Figlet message needs to be executed inside the function:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ready</span>(<span class="hljs-params"></span>) </span>{
   term.echo(<span class="hljs-function">() =&gt;</span> rainbow(render(<span class="hljs-string">'Terminal Portfolio'</span>)))
       .echo(<span class="hljs-string">'Welcome to my Terminal Portfolio\n'</span>).resume();
}
</code></pre>
<p>You'll notice that when you resize the window, the rainbow changes randomly. This is the default behavior of lolcat. To change it, you need to set the <a target="_blank" href="https://en.wikipedia.org/wiki/Random_seed">random seed</a>.</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">rand</span>(<span class="hljs-params">max</span>) </span>{
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">Math</span>.floor(<span class="hljs-built_in">Math</span>.random() * (max + <span class="hljs-number">1</span>));
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ready</span>(<span class="hljs-params"></span>) </span>{
   <span class="hljs-keyword">const</span> seed = rand(<span class="hljs-number">256</span>);
   term.echo(<span class="hljs-function">() =&gt;</span> rainbow(render(<span class="hljs-string">'Terminal Portfolio'</span>), seed))
       .echo(<span class="hljs-string">'Welcome to my Terminal Portfolio\n'</span>).resume();
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">rainbow</span>(<span class="hljs-params">string, seed</span>) </span>{
    <span class="hljs-keyword">return</span> lolcat.rainbow(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">char, color</span>) </span>{
        char = $.terminal.escape_brackets(char);
        <span class="hljs-keyword">return</span> <span class="hljs-string">`[[;<span class="hljs-subst">${hex(color)}</span>;]<span class="hljs-subst">${char}</span>]`</span>;
    }, string, seed).join(<span class="hljs-string">'\n'</span>);
}
</code></pre>
<p>The <code>rand</code> function returns a pseudo-random number from 0 to max value. Here we created a random value from 0 to 256.</p>
<h3 id="heading-how-to-make-the-greeting-text-white">How to Make the Greeting Text White</h3>
<p>As we showed previously, you can make the text white with terminal formatting. You can use:</p>
<ul>
<li><p><code>[[;white;]Welcome to my Terminal Portfolio]</code></p>
</li>
<li><p><code>[[;#fff;]Welcome to my Terminal Portfolio]</code></p>
</li>
<li><p><code>[[;rgb(255,255,255);]Welcome to my Terminal Portfolio]</code></p>
</li>
</ul>
<p>Moreover, if you include additional file XML formatting, you can use XML-like syntax. That makes formatting much easier.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://cdn.jsdelivr.net/npm/jquery.terminal/js/xml_formatting.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>After including the above file in HTML, you can use CSS named colors as XML tags:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">white</span>&gt;</span>Welcome to my Terminal Portfolio<span class="hljs-tag">&lt;/<span class="hljs-name">white</span>&gt;</span>
</code></pre>
<p>The XML formatting supports more tags like links and images. See <a target="_blank" href="https://github.com/jcubic/jquery.terminal/wiki/Formatting-and-Syntax-Highlighting#extension-xml-formatter">Extension XML Formatter</a> for more info.</p>
<p><strong>NOTE</strong>: XML formatter is a function added to <code>$.terminal.defaults.formatters</code>, which transforms the input XML-like text into terminal formatting. You can add the same to your own formatters.</p>
<h2 id="heading-how-to-make-your-first-command">How to Make Your First Command</h2>
<p>After the greeting, we can write our first command. It will be helpful and will work with any commands we add later.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> commanns = {
    help() {

    }
};
</code></pre>
<p>This will be our help command where we'll add a list of commands available to our terminal portfolio. We will use <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat">Intl.ListFormat</a>, which creates a list of elements with and before the last element.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> formatter = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Intl</span>.ListFormat(<span class="hljs-string">'en'</span>, {
  <span class="hljs-attr">style</span>: <span class="hljs-string">'long'</span>,
  <span class="hljs-attr">type</span>: <span class="hljs-string">'conjunction'</span>,
});
</code></pre>
<p>To create a list, we need to use <code>formatter.format()</code> and pass an array of commands. To get that array we can use <code>Object.keys()</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> commands = {
    help() {
        term.echo(<span class="hljs-string">`List of available commands: <span class="hljs-subst">${help}</span>`</span>);
    }
};

<span class="hljs-keyword">const</span> command_list = <span class="hljs-built_in">Object</span>.keys(commands);
<span class="hljs-keyword">const</span> help = formatter.format(command_list);
</code></pre>
<p>When you type help you should see:</p>
<pre><code class="lang-lisp">List of available commands: help
</code></pre>
<p>You also need to add the <code>echo</code> command:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> commands = {
    help() {
        term.echo(<span class="hljs-string">`List of available commands: <span class="hljs-subst">${help}</span>`</span>);
    },
    echo(...args) {
        term.echo(args.join(<span class="hljs-string">' '</span>));
    }
};
</code></pre>
<p>Now the help command works:</p>
<pre><code class="lang-lisp">List of available commands: help and echo
</code></pre>
<p>But if you try to execute 'echo hello' you will get an error:</p>
<pre><code class="lang-lisp">[Arity] Wrong number of arguments. The function 'echo' expects <span class="hljs-number">0</span> got <span class="hljs-number">1</span>!
</code></pre>
<p>By default, jQuery Terminal checks the number of arguments and the number of parameters the function accepts. The problem is that the <code>rest</code> operator makes all arguments optional and the length function property is 0. To fix the issue we need to disable the <code>Arity</code> check with an option:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> term = $(<span class="hljs-string">'body'</span>).terminal(commands, {
    <span class="hljs-attr">greetings</span>: <span class="hljs-literal">false</span>,
    <span class="hljs-attr">checkArity</span>: <span class="hljs-literal">false</span>
});
</code></pre>
<p>Now the echo commands should work.</p>
<h2 id="heading-default-commands">Default Commands</h2>
<p>By default, the jQuery Terminal has two default commands:</p>
<ul>
<li><p><code>clear</code>: this command clears everything on the terminal.</p>
</li>
<li><p><code>exit</code>: this command exits from nested interpreters.</p>
</li>
</ul>
<p>You can disable them by passing the name to the option and setting it to false. Since we won't use nested interpreters, we can disable <code>exit</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> term = $(<span class="hljs-string">'body'</span>).terminal(commands, {
    <span class="hljs-attr">greetings</span>: <span class="hljs-literal">false</span>,
    <span class="hljs-attr">checkArity</span>: <span class="hljs-literal">false</span>,
    <span class="hljs-attr">exit</span>: <span class="hljs-literal">false</span>
});
</code></pre>
<p>But <code>clear</code> can be useful. So we can add it to the list of commands:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> command_list = [<span class="hljs-string">'clear'</span>].concat(<span class="hljs-built_in">Object</span>.keys(commands));
</code></pre>
<h2 id="heading-how-to-make-help-commands-executable">How to Make Help Commands Executable</h2>
<p>We can make the UX better to allow clicking on the command and execute it just like when the user types it.</p>
<p>We will need a few things to do this. First, we need to add formatting to each command and add an HTML class attribute. We can also make the command white so it's more visible.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> command_list = <span class="hljs-built_in">Object</span>.keys(commands);
<span class="hljs-keyword">const</span> formatted_list = command_list.map(<span class="hljs-function"><span class="hljs-params">cmd</span> =&gt;</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-string">`&lt;white class="command"&gt;<span class="hljs-subst">${cmd}</span>&lt;/white&gt;`</span>;
});
<span class="hljs-keyword">const</span> help = formatter.format(formatted_list);
</code></pre>
<p>Next is to add <a target="_blank" href="https://en.wikipedia.org/wiki/Affordance">affordance</a>. To indicate that the user can click the command, we need to change the cursor in CSS:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.command</span> {
    <span class="hljs-attribute">cursor</span>: pointer;
}
</code></pre>
<p>The last step is to execute the command when the user clicks the command. We need to add an event handler with jQuery (jQuery Terminal dependency) or we can use the native browser <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener">addEventListener</a>. Here we use jQuery:</p>
<pre><code class="lang-javascript">term.on(<span class="hljs-string">'click'</span>, <span class="hljs-string">'.command'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
   <span class="hljs-keyword">const</span> command = $(<span class="hljs-built_in">this</span>).text();
   term.exec(command);
});
</code></pre>
<p><code>terminal::exec()</code> is a way to execute a command programmatically, just like user would type it and press enter.</p>
<p>You can test it by typing <code>help</code> and clicking <code>help</code> again.</p>
<p>Clicking <code>echo</code> will print an empty line. We can fix it by checking if the array of arguments is not empty, before executing <code>terminal::echo()</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> commands = {
    echo(...args) {
        <span class="hljs-keyword">if</span> (args.length &gt; <span class="hljs-number">0</span>) {
            term.echo(args.join(<span class="hljs-string">' '</span>));
        }
    }
};
</code></pre>
<p>Now clicking on <code>echo</code> will only show the executed command.</p>
<p><strong>NOTE</strong>: If for any reason you don't want to show the prompt and the command that has been executed, you can silence the <code>exec</code> by passing <code>true</code> as the second argument.</p>
<pre><code class="lang-javascript">term.exec(<span class="hljs-string">'help'</span>, <span class="hljs-literal">true</span>);
</code></pre>
<h2 id="heading-syntax-highlighting">Syntax Highlighting</h2>
<p>As we discussed earlier, we can use custom syntax highlighting of our shell by pushing a function into <code>$.terminal.defaults.formatters</code>.We can also use the <code>$.terminal.new_formatter</code> helper function.</p>
<p>Let's make our commands white as we type them. The formatter can be an array (of regex and replacement), or a function. We have a fixed number of commands and we only want to make those that are on the list white. We can do this by adding a regular expression:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> any_command_re = <span class="hljs-keyword">new</span> <span class="hljs-built_in">RegExp</span>(<span class="hljs-string">`^\s*(<span class="hljs-subst">${command_list.join(<span class="hljs-string">'|'</span>)}</span>)`</span>);
</code></pre>
<p>This regular expression will check if, at the beginning of the string, there is an optional whitespace and one of the commands. Right now the regex will look like this: <code>/^\s*(help|echo)/</code>. This is how to create new formatter:</p>
<pre><code class="lang-javascript">$.terminal.new_formatter([any_command_re, <span class="hljs-string">'&lt;white&gt;$1&lt;/white&gt;'</span>]);
</code></pre>
<p>If you would like to make command arguments in different colors, you'll need a function, where you will use <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace">String::replace()</a>.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> re = <span class="hljs-keyword">new</span> <span class="hljs-built_in">RegExp</span>(<span class="hljs-string">`^\s*(<span class="hljs-subst">${command_list.join(<span class="hljs-string">'|'</span>)}</span>) (.*)`</span>);

$.terminal.new_formatter(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">string</span>) </span>{
    <span class="hljs-keyword">return</span> string.replace(re, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">_, command, args</span>) </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-string">`&lt;white&gt;<span class="hljs-subst">${command}</span>&lt;/white&gt; &lt;aqua&gt;<span class="hljs-subst">${args}</span>&lt;/aqua&gt;`</span>;
    });
});
</code></pre>
<p>This is just an example of using <code>String::replace</code>. If you have just one replacement, you can use an array. This will be the same:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> re = <span class="hljs-keyword">new</span> <span class="hljs-built_in">RegExp</span>(<span class="hljs-string">`^\s*(<span class="hljs-subst">${command_list.join(<span class="hljs-string">'|'</span>)}</span>)(\s?.*)`</span>);

$.terminal.new_formatter([re, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">_, command, args</span>) </span>{
    <span class="hljs-keyword">return</span> <span class="hljs-string">`&lt;white&gt;<span class="hljs-subst">${command}</span>&lt;/white&gt;&lt;aqua&gt;<span class="hljs-subst">${args}</span>&lt;/aqua&gt;`</span>;
}]);
</code></pre>
<p><strong>NOTE</strong>: If you add the class <code>&lt;white class="command"&gt;</code> to the formatter, you will be able to click on the typed command to execute it again.</p>
<h2 id="heading-tab-completion">Tab Completion</h2>
<p>Another feature we can add is to complete the command when you press the tab key. This is super easy – we only need to add the completion option set to true:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> term = $(<span class="hljs-string">'body'</span>).terminal(commands, {
    <span class="hljs-attr">greetings</span>: <span class="hljs-literal">false</span>,
    <span class="hljs-attr">checkArity</span>: <span class="hljs-literal">false</span>,
    <span class="hljs-attr">exit</span>: <span class="hljs-literal">false</span>,
    <span class="hljs-attr">completion</span>: <span class="hljs-literal">true</span>
});
</code></pre>
<p>Now when you type <code>h</code> and press tab, it will complete the command <code>help</code> for you.</p>
<h2 id="heading-how-to-add-shell-commands">How to Add Shell Commands</h2>
<p>Now we can add the most important commands that allow us to navigate through the portfolio. We will implement directories as the main entry point so the user will need to type the <code>ls</code> command to see a list of things, <code>cd</code> into that directory, and <code>ls</code> again to see the contents.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> directories = {
    <span class="hljs-attr">education</span>: [
        <span class="hljs-string">''</span>,
        <span class="hljs-string">'&lt;white&gt;education&lt;/white&gt;'</span>,

        <span class="hljs-string">'* &lt;a href="https://en.wikipedia.org/wiki/Kielce_University_of_Technology"&gt;Kielce University of Technology&lt;/a&gt; &lt;yellow&gt;"Computer Science"&lt;/yellow&gt; 2002-2007 / 2011-2014'</span>,
        <span class="hljs-string">'* &lt;a href="https://pl.wikipedia.org/wiki/Szko%C5%82a_policealna"&gt;Post-secondary&lt;/a&gt; Electronic School &lt;yellow&gt;"Computer Systems"&lt;/yellow&gt; 2000-2002'</span>,
        <span class="hljs-string">'* Electronic &lt;a href="https://en.wikipedia.org/wiki/Technikum_(Polish_education)"&gt;Technikum&lt;/a&gt; with major &lt;yellow&gt;"RTV"&lt;/yellow&gt; 1995-2000'</span>,
        <span class="hljs-string">''</span>
    ],
    <span class="hljs-attr">projects</span>: [
        <span class="hljs-string">''</span>,
        <span class="hljs-string">'&lt;white&gt;Open Source projects&lt;/white&gt;'</span>,
        [
            [<span class="hljs-string">'jQuery Terminal'</span>,
             <span class="hljs-string">'https://terminal.jcubic.pl'</span>,
             <span class="hljs-string">'library that adds terminal interface to websites'</span>
            ],
            [<span class="hljs-string">'LIPS Scheme'</span>,
             <span class="hljs-string">'https://lips.js.org'</span>,
             <span class="hljs-string">'Scheme implementation in JavaScript'</span>
            ],
            [<span class="hljs-string">'Sysend.js'</span>,
             <span class="hljs-string">'https://jcu.bi/sysend'</span>,
             <span class="hljs-string">'Communication between open tabs'</span>
            ],
            [<span class="hljs-string">'Wayne'</span>,
             <span class="hljs-string">'https://jcu.bi/wayne'</span>,
             <span class="hljs-string">'Pure in browser HTTP requests'</span>
            ],
        ].map(<span class="hljs-function">(<span class="hljs-params">[name, url, description = <span class="hljs-string">''</span>]</span>) =&gt;</span> {
            <span class="hljs-keyword">return</span> <span class="hljs-string">`* &lt;a href="<span class="hljs-subst">${url}</span>"&gt;<span class="hljs-subst">${name}</span>&lt;/a&gt; &amp;mdash; &lt;white&gt;<span class="hljs-subst">${description}</span>&lt;/white&gt;`</span>;
        }),
        <span class="hljs-string">''</span>
    ].flat(),
    <span class="hljs-attr">skills</span>: [
        <span class="hljs-string">''</span>,
        <span class="hljs-string">'&lt;white&gt;languages&lt;/white&gt;'</span>,

        [
            <span class="hljs-string">'JavaScript'</span>,
            <span class="hljs-string">'TypeScript'</span>,
            <span class="hljs-string">'Python'</span>,
            <span class="hljs-string">'SQL'</span>,
            <span class="hljs-string">'PHP'</span>,
            <span class="hljs-string">'Bash'</span>
        ].map(<span class="hljs-function"><span class="hljs-params">lang</span> =&gt;</span> <span class="hljs-string">`* &lt;yellow&gt;<span class="hljs-subst">${lang}</span>&lt;/yellow&gt;`</span>),
        <span class="hljs-string">''</span>,
        <span class="hljs-string">'&lt;white&gt;libraries&lt;/white&gt;'</span>,
        [
            <span class="hljs-string">'React.js'</span>,
            <span class="hljs-string">'Redux'</span>,
            <span class="hljs-string">'Jest'</span>,
        ].map(<span class="hljs-function"><span class="hljs-params">lib</span> =&gt;</span> <span class="hljs-string">`* &lt;green&gt;<span class="hljs-subst">${lib}</span>&lt;/green&gt;`</span>),
        <span class="hljs-string">''</span>,
        <span class="hljs-string">'&lt;white&gt;tools&lt;/white&gt;'</span>,
        [
            <span class="hljs-string">'Docker'</span>,
            <span class="hljs-string">'git'</span>,
            <span class="hljs-string">'GNU/Linux'</span>
        ].map(<span class="hljs-function"><span class="hljs-params">lib</span> =&gt;</span> <span class="hljs-string">`* &lt;blue&gt;<span class="hljs-subst">${lib}</span>&lt;/blue&gt;`</span>),
        <span class="hljs-string">''</span>
    ].flat()
};
</code></pre>
<p>This is our basic structure. You can edit it and add your own information. First, we will add a <code>cd</code> command that changes the directory.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> root = <span class="hljs-string">'~'</span>;
<span class="hljs-keyword">let</span> cwd = root;

<span class="hljs-keyword">const</span> commands = {
    cd(dir = <span class="hljs-literal">null</span>) {
        <span class="hljs-keyword">if</span> (dir === <span class="hljs-literal">null</span> || (dir === <span class="hljs-string">'..'</span> &amp;&amp; cwd !== root)) {
            cwd = root;
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (dir.startsWith(<span class="hljs-string">'~/'</span>) &amp;&amp; dirs.includes(dir.substring(<span class="hljs-number">2</span>))) {
            cwd = dir;
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (dir.startsWith(<span class="hljs-string">'../'</span>) &amp;&amp; cwd !== root &amp;&amp;
                   dirs.includes(dir.substring(<span class="hljs-number">3</span>))) {
            cwd = root + <span class="hljs-string">'/'</span> + dir.substring(<span class="hljs-number">3</span>);
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (dirs.includes(dir)) {
            cwd = root + <span class="hljs-string">'/'</span> + dir;
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-built_in">this</span>.error(<span class="hljs-string">'Wrong directory'</span>);
        }
    }
};
</code></pre>
<p>This will handle all the cases of changing the directory. Next is to add a prompt.</p>
<p>To see what directory we are in, we need to add a custom <code>prompt</code>. We can create a function as a <code>prompt</code> option:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> user = <span class="hljs-string">'guest'</span>;
<span class="hljs-keyword">const</span> server = <span class="hljs-string">'freecodecamp.org'</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">prompt</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">return</span> <span class="hljs-string">`&lt;green&gt;<span class="hljs-subst">${user}</span>@<span class="hljs-subst">${server}</span>&lt;/green&gt;:&lt;blue&gt;<span class="hljs-subst">${cwd}</span>&lt;/blue&gt;$ `</span>;
}
</code></pre>
<p>And use it as an option:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> term = $(<span class="hljs-string">'body'</span>).terminal(commands, {
    <span class="hljs-attr">greetings</span>: <span class="hljs-literal">false</span>,
    <span class="hljs-attr">checkArity</span>: <span class="hljs-literal">false</span>,
    <span class="hljs-attr">completion</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">exit</span>: <span class="hljs-literal">false</span>,
    prompt
});
</code></pre>
<p>The green color don't look very good, so we can use a color from Ubuntu to make the terminal look more real. We can overwrite the default XML color tags like this:</p>
<pre><code class="lang-javascript">$.terminal.xml_formatter.tags.green = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-string">`[[;#44D544;]`</span>;
};
</code></pre>
<p>Next is the <code>ls</code> command.</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">print_home</span>(<span class="hljs-params"></span>) </span>{
     term.echo(dirs.map(<span class="hljs-function"><span class="hljs-params">dir</span> =&gt;</span> {
         <span class="hljs-keyword">return</span> <span class="hljs-string">`&lt;blue class="directory"&gt;<span class="hljs-subst">${dir}</span>&lt;/blue&gt;`</span>;
     }).join(<span class="hljs-string">'\n'</span>));
}

<span class="hljs-keyword">const</span> commands = {
    ls(dir = <span class="hljs-literal">null</span>) {
        <span class="hljs-keyword">if</span> (dir) {
            <span class="hljs-keyword">if</span> (dir.match(<span class="hljs-regexp">/^~\/?$/</span>)) {
                <span class="hljs-comment">// ls ~ or ls ~/</span>
                print_home();
            } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (dir.startsWith(<span class="hljs-string">'~/'</span>)) {
                <span class="hljs-keyword">const</span> path = dir.substring(<span class="hljs-number">2</span>);
                <span class="hljs-keyword">const</span> dirs = path.split(<span class="hljs-string">'/'</span>);
                <span class="hljs-keyword">if</span> (dirs.length &gt; <span class="hljs-number">1</span>) {
                    <span class="hljs-built_in">this</span>.error(<span class="hljs-string">'Invalid directory'</span>);
                } <span class="hljs-keyword">else</span> {
                    <span class="hljs-keyword">const</span> dir = dirs[<span class="hljs-number">0</span>];
                    <span class="hljs-built_in">this</span>.echo(directories[dir].join(<span class="hljs-string">'\n'</span>));
                }
            } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (cwd === root) {
                <span class="hljs-keyword">if</span> (dir <span class="hljs-keyword">in</span> directories) {
                    <span class="hljs-built_in">this</span>.echo(directories[dir].join(<span class="hljs-string">'\n'</span>));
                } <span class="hljs-keyword">else</span> {
                    <span class="hljs-built_in">this</span>.error(<span class="hljs-string">'Invalid directory'</span>);
                }
            } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (dir === <span class="hljs-string">'..'</span>) {
                print_home();
            } <span class="hljs-keyword">else</span> {
                <span class="hljs-built_in">this</span>.error(<span class="hljs-string">'Invalid directory'</span>);
            }
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (cwd === root) {
            print_home();
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-keyword">const</span> dir = cwd.substring(<span class="hljs-number">2</span>);
            <span class="hljs-built_in">this</span>.echo(directories[dir].join(<span class="hljs-string">'\n'</span>));
        }
    }
};
</code></pre>
<p>Similar to the green we had before, the blue color is not that great. So we can use the color from Ubuntu again. To do this, we need to use the same custom XML tags for the color blue:</p>
<pre><code class="lang-javascript">$.terminal.xml_formatter.tags.blue = <span class="hljs-function">(<span class="hljs-params">attrs</span>) =&gt;</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-string">`[[;#55F;;<span class="hljs-subst">${attrs.<span class="hljs-keyword">class</span>}</span>]`</span>;
};
</code></pre>
<p>We added the HTML class for a reason. Let's change directory when the user clicks the directory. Just like we did with commands, we can invoke the <code>cd</code> command the same way as as a user would type it.</p>
<pre><code class="lang-javascript">term.on(<span class="hljs-string">'click'</span>, <span class="hljs-string">'.directory'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> dir = $(<span class="hljs-built_in">this</span>).text();
    term.exec(<span class="hljs-string">`cd ~/<span class="hljs-subst">${dir}</span>`</span>);
});
</code></pre>
<p><strong>NOTE</strong>: if you have long command and want to get the text for that command, it's better to use: <code>$(this).data('text')</code>. When the single formatting is wrapped (when text is longer than the width of the terminal) the <code>.text()</code> will no longer have full text, but the full text is always in the <code>data-text</code> HTML attribute.</p>
<p>We also need to update our CSS to change the cursor:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.command</span>, <span class="hljs-selector-class">.directory</span> {
    <span class="hljs-attribute">cursor</span>: pointer;
}
</code></pre>
<h2 id="heading-how-to-improve-completion">How to Improve Completion</h2>
<p>Our completion is not perfect as it only completes the commands. If you'd like to have completion that also handles directories, you need to use a function:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> term = $(<span class="hljs-string">'body'</span>).terminal(commands, {
    <span class="hljs-attr">greetings</span>: <span class="hljs-literal">false</span>,
    <span class="hljs-attr">checkArity</span>: <span class="hljs-literal">false</span>,
    completion(string) {
        <span class="hljs-comment">// in every function we can use `this` to reference term object</span>
        <span class="hljs-keyword">const</span> cmd = <span class="hljs-built_in">this</span>.get_command();
        <span class="hljs-comment">// we process the command to extract the command name</span>
        <span class="hljs-comment">// and the rest of the command (the arguments as one string)</span>
        <span class="hljs-keyword">const</span> { name, rest } = $.terminal.parse_command(cmd);
        <span class="hljs-keyword">if</span> ([<span class="hljs-string">'cd'</span>, <span class="hljs-string">'ls'</span>].includes(name)) {
            <span class="hljs-keyword">if</span> (rest.startsWith(<span class="hljs-string">'~/'</span>)) {
                <span class="hljs-keyword">return</span> dirs.map(<span class="hljs-function"><span class="hljs-params">dir</span> =&gt;</span> <span class="hljs-string">`~/<span class="hljs-subst">${dir}</span>`</span>);
            }
            <span class="hljs-keyword">if</span> (rest.startsWith(<span class="hljs-string">'../'</span>) &amp;&amp; cwd != root) {
                <span class="hljs-keyword">return</span> dirs.map(<span class="hljs-function"><span class="hljs-params">dir</span> =&gt;</span> <span class="hljs-string">`../<span class="hljs-subst">${dir}</span>`</span>);
            }
            <span class="hljs-keyword">if</span> (cwd === root) {
                <span class="hljs-keyword">return</span> dirs;
            }
        }
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">Object</span>.keys(commands);
    },
    prompt
});
</code></pre>
<p><strong>NOTE</strong>: The string argument was left as documentation. It can be used if you only want to complete a single word.</p>
<h2 id="heading-typing-animation-command">Typing Animation Command</h2>
<p>Another command that we will add is an animated joke. We will print random jokes using an API that looks like the user typing.</p>
<p>We will use the <a target="_blank" href="https://jokeapi.dev/">Joke API</a> for this purpose.</p>
<p>The API returns JSON with two types of responses: <code>twopart</code> and a <code>single</code>. This is the code that prints the text on the terminal:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// we use programming jokes so it fit better</span>
<span class="hljs-comment">// developer portfolio</span>
<span class="hljs-keyword">const</span> url = <span class="hljs-string">'https://v2.jokeapi.dev/joke/Programming'</span>;
<span class="hljs-keyword">const</span> commands = {
    <span class="hljs-keyword">async</span> joke() {
        <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> fetch(url);
        <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> res.json();
        <span class="hljs-keyword">if</span> (data.type == <span class="hljs-string">'twopart'</span>) {
            <span class="hljs-comment">// as said before in every function, passed directly</span>
            <span class="hljs-comment">// to the terminal, you can use `this` object</span>
            <span class="hljs-comment">// to reference terminal instance</span>
            <span class="hljs-built_in">this</span>.echo(<span class="hljs-string">`Q: <span class="hljs-subst">${data.setup}</span>`</span>);
            <span class="hljs-built_in">this</span>.echo(<span class="hljs-string">`A: <span class="hljs-subst">${data.delivery}</span>`</span>);
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (data.type === <span class="hljs-string">'single'</span>) {
            <span class="hljs-built_in">this</span>.echo(data.joke);
        }
    },
}
</code></pre>
<p>To add typing animation, you need to add an option to the <code>echo</code> method:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">this</span>.echo(data.joke, { <span class="hljs-attr">delay</span>: <span class="hljs-number">50</span>, <span class="hljs-attr">typing</span>: <span class="hljs-literal">true</span> });
</code></pre>
<p>There is one caveat: if you have a sequence of typing animations, you need to await for the previous one to finish (the echo will return a promise when animating). When creating such animation you can wrap your code with <code>animation</code> method:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// we use programming jokes so it fits better</span>
<span class="hljs-comment">// developer portfolio</span>
<span class="hljs-keyword">const</span> url = <span class="hljs-string">'https://v2.jokeapi.dev/joke/Programming'</span>;
<span class="hljs-keyword">const</span> commands = {
    <span class="hljs-keyword">async</span> joke() {
        <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> fetch(url);
        <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> res.json();
        <span class="hljs-keyword">if</span> (data.type == <span class="hljs-string">'twopart'</span>) {
            <span class="hljs-comment">// this method allow to create sequence of animations</span>
            <span class="hljs-built_in">this</span>.animation(<span class="hljs-keyword">async</span> () =&gt; {
                <span class="hljs-comment">// as said before in every function, passed </span>
                <span class="hljs-comment">// directly to terminal, you can use `this` object</span>
                <span class="hljs-comment">// to reference terminal instance</span>
                <span class="hljs-comment">// and since we use arrow function we reference</span>
                <span class="hljs-comment">// this from joke function/command</span>
                <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.echo(<span class="hljs-string">`Q: <span class="hljs-subst">${data.setup}</span>`</span>, {
                    <span class="hljs-attr">delay</span>: <span class="hljs-number">50</span>,
                    <span class="hljs-attr">typing</span>: <span class="hljs-literal">true</span>
                });
                <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.echo(<span class="hljs-string">`A: <span class="hljs-subst">${data.delivery}</span>`</span>, {
                    <span class="hljs-attr">delay</span>: <span class="hljs-number">50</span>,
                    <span class="hljs-attr">typing</span>: <span class="hljs-literal">true</span>
                });
            });
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (data.type === <span class="hljs-string">'single'</span>) {
            <span class="hljs-built_in">this</span>.echo(data.joke, {
                <span class="hljs-attr">delay</span>: <span class="hljs-number">50</span>,
                <span class="hljs-attr">typing</span>: <span class="hljs-literal">true</span>
            });
        }
    }
};
</code></pre>
<p>You can read more about typing animation in this article: <a target="_blank" href="https://github.com/jcubic/jquery.terminal/wiki/Typing-Animation">Typing Animation</a><a target="_blank" href="https://github.com/jcubic/jquery.terminal/wiki/Typing-Animation#sequence-of-animations">.</a></p>
<h2 id="heading-credits-command">Credits Command</h2>
<p>The last command we will add is a credits command where we will list the JavaScript libraries we used:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> commands = {
    credits() {
        <span class="hljs-keyword">return</span> [
            <span class="hljs-string">''</span>,
            <span class="hljs-string">'&lt;white&gt;Used libraries:&lt;/white&gt;'</span>,
            <span class="hljs-string">'* &lt;a href="https://terminal.jcubic.pl"&gt;jQuery Terminal&lt;/a&gt;'</span>,
            <span class="hljs-string">'* &lt;a href="https://github.com/patorjk/figlet.js/"&gt;Figlet.js&lt;/a&gt;'</span>,
            <span class="hljs-string">'* &lt;a href="https://github.com/jcubic/isomorphic-lolcat"&gt;Isomorphic Lolcat&lt;/a&gt;'</span>,
            <span class="hljs-string">'* &lt;a href="https://jokeapi.dev/"&gt;Joke API&lt;/a&gt;'</span>,
            <span class="hljs-string">''</span>
        ].join(<span class="hljs-string">'\n'</span>);
    }
};
</code></pre>
<p>This is an example of another way to print something on the terminal – if you return something from a function it will be printed. You can also return a <a target="_blank" href="https://www.freecodecamp.org/news/javascript-promises-explained/">Promise</a>, so you can send an <a target="_blank" href="https://en.wikipedia.org/wiki/Ajax_\(programming\)">AJAX</a> request to the server and print the results.</p>
<h2 id="heading-prefilled-commands">Prefilled Commands</h2>
<p>You can make it easier for users to know what to do with the terminal, especially if they are not that familiar with Unix. You can do this by executing example commands:</p>
<pre><code class="lang-javascript">term.exec(command)
</code></pre>
<p>You can also use animation with <code>exec</code>:</p>
<pre><code class="lang-javascript">term.exec(command, { <span class="hljs-attr">typing</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">delay</span>: <span class="hljs-number">50</span> });
</code></pre>
<h2 id="heading-sharing-link-to-terminal-session">Sharing Link to Terminal Session</h2>
<p>Another cool thing that I will show you is recording commands in the URL. You can create whole terminal session and save it in a <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/URL/hash">URL hash</a>. To start recoding a session you need to execute the following:</p>
<pre><code class="lang-javascript">term.history_state(<span class="hljs-literal">true</span>);
</code></pre>
<p>When you execute the command <code>echo x</code>, it should create a URL hash that looks like this: <code>#[[0,1,"echo%20x"]]</code>.</p>
<p>To stop recording, you can use:</p>
<pre><code class="lang-javascript">term.history_state(<span class="hljs-literal">false</span>);
</code></pre>
<p>You can write this into a command <code>record start | stop</code>, so it will be easier to record sessions.</p>
<p>The last thing to do to restore the session is to use the option <code>execHash: true</code>.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> term = $(<span class="hljs-string">'body'</span>).terminal(commands, {
    <span class="hljs-comment">/* rest of the options */</span>
    <span class="hljs-attr">execHash</span>: <span class="hljs-literal">true</span>
});
</code></pre>
<p>When you do this and refresh the page, while having the URL hash with the session, it should replay the session and you should see same output as you did when you recorded it.</p>
<p>If you want the <code>exec</code> to be animated you can use this option:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> term = $(<span class="hljs-string">'body'</span>).terminal(commands, {
    <span class="hljs-comment">/* rest of the options */</span>
    <span class="hljs-attr">execHash</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">execAnimation</span>: <span class="hljs-literal">true</span>
});
</code></pre>
<p>To share the link, it’s better to use a URL shortener like <a target="_blank" href="https://tinyurl.com/"><strong>TinyURL</strong></a><strong>.</strong> Make sure you test the shortened URL to see if it works.</p>
<h2 id="heading-how-to-add-executables-to-the-home-directory">How to Add Executables to the Home Directory</h2>
<p>Another thing you can do to improve the portfolio is to help your visitor learn what commands they can use, by introducing executable when running ls. They will look like binaries on the Linux system.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// not every command needs to be binary</span>
<span class="hljs-comment">// we picked those three that works more like real programs</span>
<span class="hljs-keyword">const</span> files = [
    <span class="hljs-string">'joke'</span>,
    <span class="hljs-string">'credits'</span>,
    <span class="hljs-string">'record'</span>
];

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">print_home</span>(<span class="hljs-params"></span>) </span>{
     term.echo(dirs.map(<span class="hljs-function"><span class="hljs-params">dir</span> =&gt;</span> {
         <span class="hljs-keyword">return</span> <span class="hljs-string">`&lt;blue class="directory"&gt;<span class="hljs-subst">${dir}</span>&lt;/blue&gt;`</span>;
     }).join(<span class="hljs-string">'\n'</span>));
     term.echo(files.map(<span class="hljs-function"><span class="hljs-params">file</span> =&gt;</span> {
         <span class="hljs-keyword">return</span> <span class="hljs-string">`&lt;green class="command"&gt;<span class="hljs-subst">${file}</span>&lt;/green&gt;`</span>;
     }).join(<span class="hljs-string">'\n'</span>));
}
</code></pre>
<p>With this, you will be able to click the command and execute it. So your visitors will know that they can run <code>joke</code> command without the need to type <code>help</code> command. For this to work, we need one last change, adding class to the green XML tag:</p>
<pre><code class="lang-javascript">$.terminal.xml_formatter.tags.green = <span class="hljs-function">(<span class="hljs-params">attrs</span>) =&gt;</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-string">`[[;#44D544;;<span class="hljs-subst">${attrs.<span class="hljs-keyword">class</span>}</span>]`</span>;
};
</code></pre>
<h2 id="heading-working-terminal-portfolio-demo">Working Terminal Portfolio Demo</h2>
<p>Here is a fully working Demo of our <a target="_blank" href="https://codepen.io/jcubic/full/ZEZPWRY">Interactive Terminal Portfolio Website</a>.</p>
<h2 id="heading-what-next">What Next?</h2>
<p>You can add a lot of commands to this portfolio. The only limitation is your imagination.</p>
<p>You can check these examples for inspiration:</p>
<ul>
<li><p>CodePen Collection with <a target="_blank" href="https://codepen.io/collection/LPjoaW">jQuery Terminal Demos</a>.</p>
</li>
<li><p><a target="_blank" href="https://codepen.io/jcubic/pen/BwBYOZ">Retro (Vintage) Terminal CodePen Demo</a>.</p>
</li>
<li><p><a target="_blank" href="https://terminal.jcubic.pl/examples.php">jQuery Terminal Examples Page</a>.</p>
</li>
<li><p><a target="_blank" href="https://terminal.jcubic.pl/404">Terminal 404 Error Page</a>.</p>
</li>
<li><p><a target="_blank" href="https://fake.terminal.jcubic.pl/">Fake GNU/Linux Terminal</a>.</p>
</li>
</ul>
<p>If you have an idea that is not listed here, you can ask on <a target="_blank" href="https://stackoverflow.com/questions/tagged/jquery-terminal">StackOverflow with jquery-terminal tag</a>. If you have something more time consuming, you can also ask for <a target="_blank" href="https://support.jcubic.pl/">paid support</a>.</p>
<h2 id="heading-share-what-youve-created">Share what you’ve created</h2>
<p>If you create a cool terminal portfolio, you can <a target="_blank" href="http://twitter.com/jcubic">share it and tag me on Twitter</a>. I would love to take a look. Especially if you create something more than what’s included in the tutorial. You can also share on a <a target="_blank" href="https://jcu.bi/chat">terminal chat on my website</a> (it’s a similar terminal portfolio, but with chat).</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Parse S-expressions in JavaScript ]]>
                </title>
                <description>
                    <![CDATA[ S-expressions are the base of the Lisp family of programming languages. In this article, I will show you how to create a simple S-expression parser step by step. This can be a base for the Lisp parser.  Lisp is the easiest language for implementation... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/s-expressions-in-javascript/</link>
                <guid isPermaLink="false">66ba5ac1194f72bcfe482c41</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Regular Expressions ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Jakub T. Jankiewicz ]]>
                </dc:creator>
                <pubDate>Thu, 04 Apr 2024 22:22:06 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/04/lisp-parser_2.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>S-expressions are the base of the Lisp family of programming languages. In this article, I will show you how to create a simple S-expression parser step by step. This can be a base for the Lisp parser. </p>
<p>Lisp is the easiest language for implementation, and creating a parser is the first step. We can use a parser generator for this, but it's easier to write the parser yourself. We'll use JavaScript.</p>
<h2 id="heading-what-are-s-expressions">What are S-expressions?</h2>
<p>If you're not familiar with the Lisp language, S-expressions look like this:</p>
<pre><code class="lang-scheme">(+ (second (list "xxx" 10)) 20)
</code></pre>
<p>This is a data format, where everything is created from atoms or lists surrounded with parenthesis (where atoms of other lists are separated by spaces).</p>
<p>S-expressions can have different data types, just like JSON:</p>
<ul>
<li>numbers</li>
<li>strings</li>
<li>symbols – which are like strings but without quotes – can be interpreted<br>as variable names from different languages.</li>
</ul>
<p>Additionally, you can use a special dot operator that creates a pair.</p>
<pre><code class="lang-scheme">(1 . b)
</code></pre>
<p>You can represent a list as doted pairs (which indicates that they are in fact a linked list data structure). </p>
<p>This list:</p>
<pre><code class="lang-scheme">(1 2 3 4)
</code></pre>
<p>Can be written as:</p>
<pre><code class="lang-scheme">(1 . (2 . (3 . (4 . nil))))
</code></pre>
<p><code>nil</code> is the special symbol that indicates the end of the list of an empty list. With this format, you can create any binary tree. But we'll not use this doted notation in our parser so we don't complicate things.</p>
<h2 id="heading-what-are-s-expressions-used-for">What are S-expressions Used For?</h2>
<p>Lisp code is created from S-expressions, but you can also use it as a data exchange format.</p>
<p>They are also part of <a target="_blank" href="https://developer.mozilla.org/en-US/docs/WebAssembly/Understanding_the_text_format">text representation of WebAssembly</a>. Probably because of the simplicity of the parser, and that you don't need to come up with your format. You can use them for the communication between server and browser, instead of JSON.</p>
<h3 id="heading-how-to-implement-s-expression-parser-in-javascript">How to Implement S-expression Parser in JavaScript</h3>
<h4 id="heading-tokenizer">Tokenizer</h4>
<p>The tokenizer is part of the parser that splits the text into tokens that then can be parsed.</p>
<p>Usually, a parser is accompanied by Lexer or a tokenizer that generates the tokens.<br>This is how some parser generators work (like lex and Yacc or flex and bison. The second one is the free and open source software, part of the GNU project). </p>
<p>The simplest way of tokenizing is to use regular expressions. If you're not familiar with regular expressions (or Regex for short) you can read this article:<br><a target="_blank" href="https://www.freecodecamp.org/news/practical-regex-guide-with-real-life-examples/">A Practical Guide to Regular Expressions – Learn Regex with Real Life Examples</a>.</p>
<p>This is the simplest way of tokenization:</p>
<pre><code class="lang-javascript"><span class="hljs-string">'(foo bar (baz))'</span>.split(<span class="hljs-regexp">/(\(|\)|\n|\s+|\S+)/</span>);
</code></pre>
<p>This is a union (with a pipe operator) or different cases we need to handle. Parentheses are special characters in Regex, so they need to be escaped by a slash.</p>
<p>It almost works. The first problem is that there are empty strings between the regex matching. Like this expression:</p>
<pre><code class="lang-javascript"><span class="hljs-string">'(('</span>.split(<span class="hljs-regexp">/(\(|\)|\n|\s+|\S+)/</span>);
<span class="hljs-comment">// ==&gt; [ '', '(', '', '(', '' ]</span>
</code></pre>
<p>We have 5 tokens instead of 2. We can solve this problem with an <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter">Array::filter</a>.</p>
<pre><code class="lang-javascript"><span class="hljs-string">'(('</span>.split(<span class="hljs-regexp">/(\(|\)|\n|\s+|\S+)/</span>).filter(<span class="hljs-function"><span class="hljs-params">token</span> =&gt;</span> token.length);
<span class="hljs-comment">// ==&gt; ["(", "("]</span>
</code></pre>
<p>If the token is empty, the length will return <code>0</code> and will be converted to <code>false</code>, which means that it will filter out all empty strings.</p>
<p>We'll also not need spaces, so we can also filter them out:</p>
<pre><code class="lang-javascript"><span class="hljs-string">'(   ('</span>.split(<span class="hljs-regexp">/(\(|\)|\n|\s+|\S+)/</span>).filter(<span class="hljs-function"><span class="hljs-params">token</span> =&gt;</span> token.trim().length);
<span class="hljs-comment">// ==&gt; ["(", "("]</span>
</code></pre>
<p>The second bigger problem is with <code>baz))</code> as the last token, here is an example:</p>
<pre><code class="lang-javascript"><span class="hljs-string">'(foo bar (baz))'</span>.split(<span class="hljs-regexp">/(\(|\)|\n|\s+|\S+)/</span>).filter(<span class="hljs-function"><span class="hljs-params">token</span> =&gt;</span> token.trim().length);
<span class="hljs-comment">// ==&gt; ["(", "foo", "bar", "(", "baz))"]</span>
</code></pre>
<p>The problem is the expression <code>\S+</code>, which is greedy and matches everything that is not a space. To fix the problem, we can use this expression: <code>[^\s()]+</code>.</p>
<p>It will match everything that is not a space and not a parentheses (same as <code>\S+</code> but<br>with parentheses).</p>
<pre><code class="lang-javascript">(foo bar (baz))<span class="hljs-string">'.split(/(\(|\)|\n|\s+|[^\s()]+)/).filter(token =&gt; token.trim().length);
// ==&gt; ["(", "foo", "bar", "(", "baz", ")", ")"]</span>
</code></pre>
<p>As you can see, the output is correct. Let's write this tokenizer as a function:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> tokens_re = <span class="hljs-regexp">/(\(|\)|\n|\s+|[^\s()]+)/</span>;
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">tokenize</span>(<span class="hljs-params">string</span>) </span>{
    string = string.trim();
    <span class="hljs-keyword">if</span> (!string.length) {
        <span class="hljs-keyword">return</span> [];
    }
    <span class="hljs-keyword">return</span> string.split(tokens_re).filter(<span class="hljs-function"><span class="hljs-params">token</span> =&gt;</span> token.trim());
}
</code></pre>
<p>We don't need to use <code>length</code> after <code>token.trim()</code> because an empty string is also converted to <code>false</code> value and the filter will remove those values.</p>
<p>But what about string expressions (those in quotes)? Let's see what will happen:</p>
<pre><code class="lang-javascript">tokenize(<span class="hljs-string">`(define (square x)
            "Function calculate square of a number"
            (* x x))`</span>);
<span class="hljs-comment">// ==&gt; ["(", "define", "(", "square", "x", ")", "\"Function", "calculate", "square",</span>
<span class="hljs-comment">// ==&gt;  "of", "a", "number\"", "(", "*", "x", "x", ")", ")"]</span>
</code></pre>
<p><strong>NOTE:</strong> This is a function in the Scheme dialect of Lisp. We used template literals so we could add newline characters inside the Lisp code.</p>
<p>As you can see from the output, the single stings are all split by spaces. Let's fix that:</p>
<h5 id="heading-regular-expressions-for-strings">Regular Expressions for Strings</h5>
<p>We need to add string literals as an exception to our tokenizer. The best is the first item in the union in our regex. </p>
<p>The expression that handles string literals looks like this:</p>
<pre><code class="lang-javascript">/<span class="hljs-string">"[^"</span>\\]*(?:\\[\S\s][^<span class="hljs-string">"\\]*)*"</span>/
</code></pre>
<p>It handles escaped quotes inside a string.</p>
<p>This is how the full regular expression should look like:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> tokens_re = <span class="hljs-regexp">/("[^"\\]*(?:\\[\S\s][^"\\]*)*"|\(|\)|\n|\s+|[^\s()]+)/</span>;
</code></pre>
<p><strong>NOTE:</strong> We can also add Lisp comments, but because this is not a Lisp parser but S-expression,⁣ we'll not do that here. JSON doesn't support comments as well. If you want to create a Lisp parser, you can add them as an exercise.</p>
<p>Our tokenizer now works correctly:</p>
<pre><code class="lang-javascript">tokenize(<span class="hljs-string">`(define (square x)
            "Function calculate square of a number"
            (* x x))`</span>);
<span class="hljs-comment">// ==&gt; ["(", "define", "(", "square", "x", ")",</span>
<span class="hljs-comment">// ==&gt;  "\"Function calculate square of a number\"",</span>
<span class="hljs-comment">// ==&gt;  "(", "*", "x", "x", ")", ")"]</span>
</code></pre>
<h4 id="heading-parser">Parser</h4>
<p>We'll create our parser using a <a target="_blank" href="https://www.freecodecamp.org/news/learn-data-structures-and-algorithms/">stack data structure</a> (LIFO - Last In First Out).</p>
<p>To fully understand how the parser works, it is good to know about data structures, like linked lists, binary trees, and stacks.</p>
<p>Here is the first version of our parser:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">parse</span>(<span class="hljs-params">string</span>) </span>{
    <span class="hljs-keyword">const</span> tokens = tokenize(string);
    <span class="hljs-keyword">const</span> result = []; <span class="hljs-comment">// as normal array</span>
    <span class="hljs-keyword">const</span> stack = []; <span class="hljs-comment">// as stack</span>
    tokens.forEach(<span class="hljs-function"><span class="hljs-params">token</span> =&gt;</span> {
        <span class="hljs-keyword">if</span> (token == <span class="hljs-string">'('</span>) {
            stack.push([]); <span class="hljs-comment">// add new list to stack</span>
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (token == <span class="hljs-string">')'</span>) {
            <span class="hljs-keyword">if</span> (stack.length) {
                <span class="hljs-comment">// top of the stack is already constructed list</span>
                <span class="hljs-keyword">const</span> top = stack.pop();
                <span class="hljs-keyword">if</span> (stack.length) {
                    <span class="hljs-comment">// add constructed list to previous list</span>
                    <span class="hljs-keyword">var</span> last = stack[stack.length - <span class="hljs-number">1</span>];
                    last.push(top);
                } <span class="hljs-keyword">else</span> {
                    result.push(top); <span class="hljs-comment">// fully constructed list</span>
                }
            } <span class="hljs-keyword">else</span> {
                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Syntax Error - unmached closing paren'</span>);
            }
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-comment">// found atom add to the top of the stack</span>
            <span class="hljs-comment">// top is used as an array we only add at the end</span>
            <span class="hljs-keyword">const</span> top = stack[stack.length - <span class="hljs-number">1</span>];
            top.push(token);
        }
    });
    <span class="hljs-keyword">if</span> (stack.length) {
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Syntax Error - expecting closing paren'</span>);
    }
    <span class="hljs-keyword">return</span> result;
}
</code></pre>
<p>The function returns an array of our structures in the form of arrays. If we need to parse more than one S-expressions, we will have more items in an array:</p>
<pre><code class="lang-javascript">parse(<span class="hljs-string">`(1 2 3) (1 2 3)`</span>)
<span class="hljs-comment">// ==&gt; [["1", "2", "3"], ["1", "2", "3"]]</span>
</code></pre>
<p>Although we don't need to handle dots, S-expressions can be in this form:</p>
<pre><code class="lang-scheme">((foo . 10) (bar . 20))
</code></pre>
<p>We don't need to create a special structure for our lists to have a working parser. But it's a good idea to have this structure from the beginning (so you can use this as a base for a Lisp interpreter). We will use a <code>Pair</code> class, so we'll be able to create any binary tree.</p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Pair</span> </span>{
    <span class="hljs-keyword">constructor</span>(head, tail) {
        <span class="hljs-built_in">this</span>.head = head;
        <span class="hljs-built_in">this</span>.tail = tail;
    }
}
</code></pre>
<p>We will also need something that will represent the end of the list (or an empty list). In Lisp language, it's usually <code>nil</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Nil</span> </span>{}
<span class="hljs-keyword">const</span> nil = <span class="hljs-keyword">new</span> Nil();
</code></pre>
<p>We can create a static method that will convert an array into our structure:</p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Pair</span> </span>{
    <span class="hljs-keyword">constructor</span>(head, tail) {
        <span class="hljs-built_in">this</span>.head = head;
        <span class="hljs-built_in">this</span>.tail = tail;
    }
    <span class="hljs-keyword">static</span> fromArray(array) {
        <span class="hljs-keyword">if</span> (!array.length) {
            <span class="hljs-keyword">return</span> nil;
        }
        <span class="hljs-keyword">let</span> [head, ...rest] = array
        <span class="hljs-keyword">if</span> (head <span class="hljs-keyword">instanceof</span> <span class="hljs-built_in">Array</span>) {
            head = Pair.fromArray(head);
        }
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Pair(head, Pair.fromArray(rest));
    }
}
</code></pre>
<p>To add this to our parser, all we have to do is to add it at the end:</p>
<pre><code class="lang-javascript">result.map(Pair.fromArray);
</code></pre>
<p><strong>NOTE:</strong> <code>I</code>f you would like to add a dot operator later, you will need to create pairs by hand, inside the parser.</p>
<p>We didn't convert the whole array because this will be the container for our S-expressions. Each element in an array should be a list, that's why we used <a target="_blank" href="https://www.freecodecamp.org/news/javascript-map-reduce-and-filter-explained-with-examples/">Array::map</a>.</p>
<p>Let's see how it works:</p>
<pre><code class="lang-javascript">parse(<span class="hljs-string">'(1 (1 2 3))'</span>)
</code></pre>
<p>The output will be a structure like this (this is the output of <code>JSON.stringify</code> with inserted value of  <code>nil</code>).</p>
<pre><code class="lang-javascript">{
    <span class="hljs-string">"head"</span>: <span class="hljs-string">"1"</span>,
    <span class="hljs-string">"tail"</span>: {
        <span class="hljs-string">"head"</span>: {
            <span class="hljs-string">"head"</span>: <span class="hljs-string">"1"</span>,
            <span class="hljs-string">"tail"</span>: {
                <span class="hljs-string">"head"</span>: <span class="hljs-string">"2"</span>,
                <span class="hljs-string">"tail"</span>: {
                    <span class="hljs-string">"head"</span>: <span class="hljs-string">"3"</span>,
                    <span class="hljs-string">"tail"</span>: nil
                }
            }
        },
        <span class="hljs-string">"tail"</span>: nil
    }
}
</code></pre>
<p>The last thing that we can add is to <code>stringify</code> the List, by adding a <code>toString</code> method to our <code>Pair</code> class:</p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Pair</span> </span>{
    <span class="hljs-keyword">constructor</span>(head, tail) {
        <span class="hljs-built_in">this</span>.head = head;
        <span class="hljs-built_in">this</span>.tail = tail;
    }
    toString() {
        <span class="hljs-keyword">const</span> arr = [<span class="hljs-string">'('</span>];
        <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.head) {
            <span class="hljs-keyword">const</span> value = <span class="hljs-built_in">this</span>.head.toString();
            arr.push(value);
            <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.tail <span class="hljs-keyword">instanceof</span> Pair) {
                <span class="hljs-comment">// replace hack for the nested list</span>
                <span class="hljs-comment">// because the structure is a tree</span>
                <span class="hljs-comment">// and here tail is next element</span>
                <span class="hljs-keyword">const</span> tail = <span class="hljs-built_in">this</span>.tail.toString().replace(<span class="hljs-regexp">/^\(|\)$/g</span>, <span class="hljs-string">''</span>);
                arr.push(<span class="hljs-string">' '</span>);
                arr.push(tail);
            }
        }
        arr.push(<span class="hljs-string">')'</span>);
        <span class="hljs-keyword">return</span> arr.join(<span class="hljs-string">''</span>);
    }
    <span class="hljs-keyword">static</span> fromArray(array) {
        <span class="hljs-comment">// ... same as before</span>
    }
}
</code></pre>
<p>Let's see how it works:</p>
<pre><code class="lang-javascript">parse(<span class="hljs-string">"(1 (1 2 (3)))"</span>)[<span class="hljs-number">0</span>].toString()
<span class="hljs-comment">// ==&gt; "(1 (1 2 (3)))"</span>
</code></pre>
<p>The last problem is that the output structure doesn't have numbers. Everything is a string.</p>
<h4 id="heading-parsing-of-atoms">Parsing of Atoms</h4>
<p>We'll use the regular expressions below:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> int_re = <span class="hljs-regexp">/^[-+]?[0-9]+([eE][-+]?[0-9]+)?$/</span>;
<span class="hljs-keyword">const</span> float_re = <span class="hljs-regexp">/^([-+]?((\.[0-9]+|[0-9]+\.[0-9]+)([eE][-+]?[0-9]+)?)|[0-9]+\.)$/</span>;
<span class="hljs-keyword">if</span> (atom.match(int_re) || atom.match(float_re)) {
    <span class="hljs-comment">// in javascript every number is float but if it's slow you can use parseInt for int_re</span>
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">parseFloat</span>(atom);
}
</code></pre>
<p>Next, we can parse strings. Our strings are almost the same as those in JSON, the only difference is that they can have newlines (this is usually how strings are handled in Lisp dialects). So we can use <code>JSON.parse</code> and only replace <code>\n</code> with <code>\\n</code> (escape the new line).</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">if</span> (atom.match(<span class="hljs-regexp">/^".*"$/</span>)) {
   <span class="hljs-keyword">return</span> <span class="hljs-built_in">JSON</span>.parse(atom.replace(<span class="hljs-regexp">/\n/g</span>, <span class="hljs-string">'\\n'</span>));
}
</code></pre>
<p>So with this, we can have all escape characters for free (that is: <code>\t</code> or Unicode characters <code>\u</code>).</p>
<p>The next element of S-expressions are symbols. They are any character sequences that are not numbers or strings. We can create an <code>LSymbol</code> class, to distinguish from <code>Symbol</code> from JavaScript.</p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LSymbol</span> </span>{
    <span class="hljs-keyword">constructor</span>(name) {
        <span class="hljs-built_in">this</span>.name = name;
    }
    toString() {
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.name;
    }
}
</code></pre>
<p>The function for parsing atoms can look like this:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">parseAtom</span>(<span class="hljs-params">atom</span>) </span>{
    <span class="hljs-keyword">if</span> (atom.match(int_re) || atom.match(float_re)) { <span class="hljs-comment">// numbers</span>
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">parseFloat</span>(atom);
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (atom.match(<span class="hljs-regexp">/^".*"$/</span>)) {
       <span class="hljs-keyword">return</span> <span class="hljs-built_in">JSON</span>.parse(atom.replace(<span class="hljs-regexp">/\n/g</span>, <span class="hljs-string">'\\n'</span>)); <span class="hljs-comment">// strings</span>
    } <span class="hljs-keyword">else</span> {
       <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> LSymbol(atom); <span class="hljs-comment">// symbols</span>
    }
}
</code></pre>
<p>Our parser function with add the <code>parseAtom</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">parse</span>(<span class="hljs-params">string</span>) </span>{
    <span class="hljs-keyword">const</span> tokens = tokenize(string);
    <span class="hljs-keyword">const</span> result = [];
    <span class="hljs-keyword">const</span> stack = [];
    tokens.forEach(<span class="hljs-function"><span class="hljs-params">token</span> =&gt;</span> {
        <span class="hljs-keyword">if</span> (token == <span class="hljs-string">'('</span>) {
           stack.push([]);
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (token == <span class="hljs-string">')'</span>) {
           <span class="hljs-keyword">if</span> (stack.length) {
               <span class="hljs-keyword">const</span> top = stack.pop();
               <span class="hljs-keyword">if</span> (stack.length) {
                  <span class="hljs-keyword">const</span> last = stack[stack.length - <span class="hljs-number">1</span>];
                  last.push(top);
               } <span class="hljs-keyword">else</span> {
                  result.push(top);
               }
           } <span class="hljs-keyword">else</span> {
               <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Syntax Error - unmached closing paren'</span>);
           }
        } <span class="hljs-keyword">else</span> {
           <span class="hljs-keyword">const</span> top = stack[stack.length - <span class="hljs-number">1</span>];
           top.push(parseAtom(token)); <span class="hljs-comment">// this line was added</span>
        }
    });
    <span class="hljs-keyword">if</span> (stack.length) {
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Syntax Error - expecting closing paren'</span>);
    }
    <span class="hljs-keyword">return</span> result.map(Pair.fromArray);
}
</code></pre>
<p>We can also improve the <code>toString</code> method on <code>Pair</code> to use <code>JSON.stringify</code> for strings to distinguish from symbols:</p>
<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Pair</span> </span>{
    <span class="hljs-keyword">constructor</span>(head, tail) {
        <span class="hljs-built_in">this</span>.head = head;
        <span class="hljs-built_in">this</span>.tail = tail;
    }
    toString() {
        <span class="hljs-keyword">const</span> arr = [<span class="hljs-string">'('</span>];
        <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.head) {
            <span class="hljs-keyword">let</span> value;
            <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> <span class="hljs-built_in">this</span>.head === <span class="hljs-string">'string'</span>) {
                value = <span class="hljs-built_in">JSON</span>.stringify(<span class="hljs-built_in">this</span>.head).replace(<span class="hljs-regexp">/\\n/g</span>, <span class="hljs-string">'\n'</span>);
            } <span class="hljs-keyword">else</span> {
                <span class="hljs-comment">// any object including Pair and LSymbol</span>
                value = <span class="hljs-built_in">this</span>.head.toString(); 
            }
            arr.push(value);
            <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.tail <span class="hljs-keyword">instanceof</span> Pair) {
                <span class="hljs-comment">// replace hack for the nested list because</span>
                <span class="hljs-comment">// the structure is a tree and here tail</span>
                <span class="hljs-comment">// is next element</span>
                <span class="hljs-keyword">const</span> tail = <span class="hljs-built_in">this</span>.tail.toString().replace(<span class="hljs-regexp">/^\(|\)$/g</span>, <span class="hljs-string">''</span>);
                arr.push(<span class="hljs-string">' '</span>);
                arr.push(tail);
            }
        }
        arr.push(<span class="hljs-string">')'</span>);
        <span class="hljs-keyword">return</span> arr.join(<span class="hljs-string">''</span>);
    }
    <span class="hljs-keyword">static</span> fromArray(array) {
        <span class="hljs-comment">// ... same as before</span>
    }   
}
</code></pre>
<p>And this is a whole parser. What's left are <code>true</code> and <code>false</code> values (and maybe <code>null</code>), but they are left as an exercise for the reader. The full code can be found on <a target="_blank" href="https://gist.github.com/jcubic/ca6548847137584138823f3ba90a002a">GitHub</a>.</p>
<h2 id="heading-different-approaches-to-lisp-parser-in-javascript">Different Approaches to Lisp parser in JavaScript</h2>
<p>The above code is good for simple Lisp implementation. I used a similar code as the initial implementation of <a target="_blank" href="https://lips.js.org/">LIPS Scheme</a>, which can still be found on <a target="_blank" href="https://codepen.io/jcubic/pen/gvvzdp">CodePen</a>.</p>
<p>Right now, LIPS uses a more advanced Lexer (using <a target="_blank" href="https://w.wiki/5v8">state machine</a>) instead of a tokenizer. The Lexer was rewritten because the approach with stack was too difficult to modify.</p>
<p><strong>NOTE</strong>: This article first appeared on the Polish blog <a target="_blank" href="https://jcubic.pl/"><strong>Głównie JavaScript</strong></a> (ang. Mostly JavaScript), the article was titled: <a target="_blank" href="https://jcubic.pl/2019/06/parser-jezyka-lisp-javascript.html">Parser S-Wyrażeń (języka LISP) w JavaScript</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ CSRF Protection Problem and How to Fix it ]]>
                </title>
                <description>
                    <![CDATA[ One day I was working on a feature at work. I had many branches created in JIRA tickets, so I wanted to open a bunch of PRs (Pull Requests) all at once in different tabs.  This is how I usually work – I have a lot of tabs open ]]>
                </description>
                <link>https://www.freecodecamp.org/news/csrf-protection-problem-and-how-to-fix-it/</link>
                <guid isPermaLink="false">66ba5ab88f51e6d52724bdf3</guid>
                
                    <category>
                        <![CDATA[ Application Security ]]>
                    </category>
                
                    <category>
                        <![CDATA[ cybersecurity ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Jakub T. Jankiewicz ]]>
                </dc:creator>
                <pubDate>Mon, 28 Mar 2022 18:39:45 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/03/laptop-security-virus-protection-internet-malware-1588329-pxhere.com.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>One day I was working on a feature at work. I had many branches created in JIRA tickets, so I wanted to open a bunch of PRs (Pull Requests) all at once in different tabs. </p>
<p>This is how I usually work – I have a lot of tabs open and this speeds things up, because I don't need to wait for the next page to load. </p>
<p>But after I'd created the first PR in BitBucket and tried to go on to the next page, I was welcomed with an error message about an invalid CSRF token. This is a common problem with web applications that have CSRF protection.</p>
<p>So in this article you'll learn what CSRF is and how to fix this error.</p>
<p></p><h2 id="heading-table-of-contents">Table of contents:</h2><p></p>
<ul>
    <li><a href="#what-is-csrf">What is CSRF?</a></li>
      <li><a href="#standard-csrf-protection">Standard CSRF protection</a></li>
    <li><a href="#the-problem-with-tokens">The Problem with Tokens</a></li>
    <li><a href="#cross-tab-communication-solution">Cross-tab Communication Solution</a>
        <ul>
              <li><a href="#sysend-library">Sysend library</a></li>

              <li><a href="#broadcast-channel">Broadcast Channel</a></li>
        </ul>
    </li>
      <li><a href="#conclusion">Conclusion</a></li>
</ul>

<h2 id="heading-what-is-csrf">What is CSRF?</h2>
<p>CSRF is an acronym for <strong>Cross-Site Request Forgery</strong>. It is a vector of attack that attackers commonly use to get into your system. </p>
<p>The way you usually protect against CSRF is to send a unique token generated by each HTTP request. If the token that is on the server doesn't match with the one from the request, you show an error to the user.</p>
<h2 id="heading-standard-csrf-protection">Standard CSRF protection</h2>
<p>This is one way you can protect against CSRF with a token:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> inital_token = <span class="hljs-string">'...'</span>;

<span class="hljs-keyword">const</span> secure_fetch = (<span class="hljs-function"><span class="hljs-params">token</span> =&gt;</span> {
    <span class="hljs-keyword">const</span> CSRF_HEADER = <span class="hljs-string">'X-CSRF-TOKEN'</span>;
    <span class="hljs-keyword">return</span> <span class="hljs-function">(<span class="hljs-params">url</span>) =&gt;</span> {
        <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(url, {
            <span class="hljs-attr">method</span>: <span class="hljs-string">'POST'</span>,
            <span class="hljs-attr">headers</span>: {
              [CSRF_HEADER]: token
            }
        });
        response.then(<span class="hljs-function"><span class="hljs-params">res</span> =&gt;</span> {
           token = res.headers[CSRF_HEADER]
        });
        <span class="hljs-keyword">return</span> response;
    };
})(inital_token);
</code></pre>
<p>This code uses the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API">fetch API</a> to send and receive a secure token in <a target="_blank" href="https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol">HTTP</a> headers. On the backed, you should generate the first initial token when the page loads. On the server, on each <a target="_blank" href="https://en.wikipedia.org/wiki/Ajax_(programming)">AJAX</a> request, you should check to see if the token is valid.</p>
<h2 id="heading-the-problem-with-tokens">The Problem with Tokens</h2>
<p>This works fine unless you have more than one tab open. Each tab can send requests to the server, which will break this solution. And power users may not be able to use your application the way they want.</p>
<p>But there is a simple solution to this problem which is cross-tab communication.</p>
<h2 id="heading-cross-tab-communication-solution">Cross-tab Communication Solution</h2>
<h3 id="heading-sysend-library">Sysend library</h3>
<p>You can use the <a target="_blank" href="https://github.com/jcubic/sysend.js">Sysend library</a>, an open source solution that I've created specifically for this purpose. It simplifies cross-tabs communication. </p>
<p>If you want, you can use a native browser API like <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/BroadcastChannel">Broadcast Channel</a> to do the same. More on how to do that later in this article. </p>
<p>But the <strong>Sysend</strong> library will work for browsers that don't support Broadcast Channel. It also works in IE (it has some bugs, which is not a surprise). You may also need to support some old mobile browsers. It also has a much simpler API.</p>
<p>This is the simplest example:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">let</span> token;
sysend.on(<span class="hljs-string">'token'</span>, <span class="hljs-function"><span class="hljs-params">new_token</span> =&gt;</span> {
    token = new_token;
});

<span class="hljs-comment">// ...</span>

sysend.broadcast(<span class="hljs-string">'token'</span>, token);
</code></pre>
<p>And this is how you would use this library to fix CSRF protection:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> inital_token = <span class="hljs-string">'...'</span>;

<span class="hljs-keyword">const</span> secure_fetch = (<span class="hljs-function"><span class="hljs-params">token</span> =&gt;</span> {
    <span class="hljs-keyword">const</span> CSRF_HEADER = <span class="hljs-string">'X-CSRF-TOKEN'</span>;
    <span class="hljs-keyword">const</span> EVENT_NAME = <span class="hljs-string">'csrf'</span>;
    sysend.on(EVENT_NAME, <span class="hljs-function"><span class="hljs-params">new_token</span> =&gt;</span> {
        <span class="hljs-comment">// get new toke from different tab</span>
        token = new_token;
    });
    <span class="hljs-keyword">return</span> <span class="hljs-function">(<span class="hljs-params">url</span>) =&gt;</span> {
        <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(url, {
            <span class="hljs-attr">method</span>: <span class="hljs-string">'POST'</span>,
            <span class="hljs-attr">headers</span>: {
              [CSRF_HEADER]: token
            }
        });
        response.then(<span class="hljs-function"><span class="hljs-params">res</span> =&gt;</span> {
           token = res.headers[CSRF_HEADER];
           <span class="hljs-comment">// send new toke to other tabs</span>
           sysend.broadcast(EVENT_NAME, token); 
        });
        <span class="hljs-keyword">return</span> response;
    };
})(inital_token);
</code></pre>
<p>All you have to do is to send and receive a single message from other tabs when sending the request. And your CSRF protected app will work on many tabs.</p>
<p>And that's it. This will let advanced users use your app that has CSRF protection when they want to open many tabs.</p>
<h3 id="heading-broadcast-channel">Broadcast Channel</h3>
<p>Here is the simplest possible example of using Broadcast Channel:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> channel = <span class="hljs-keyword">new</span> BroadcastChannel(<span class="hljs-string">'my-connection'</span>);
channel.addEventListener(<span class="hljs-string">'message'</span>, <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(e.data); <span class="hljs-comment">// 'some message'</span>
});
channel.postMessage(<span class="hljs-string">'some message'</span>);
</code></pre>
<p>So with this simple API you can do the same thing that we did before:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> inital_token = <span class="hljs-string">'...'</span>;

<span class="hljs-keyword">const</span> secure_fetch = (<span class="hljs-function"><span class="hljs-params">token</span> =&gt;</span> {
    <span class="hljs-keyword">const</span> CSRF_HEADER = <span class="hljs-string">'X-CSRF-TOKEN'</span>;
    <span class="hljs-keyword">const</span> channel = <span class="hljs-keyword">new</span> BroadcastChannel(<span class="hljs-string">'csrf-protection'</span>);
    channel.addEventListener(<span class="hljs-string">'message'</span>, <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
        <span class="hljs-comment">// get new toke from different tab</span>
        token = e.data;
    });
    <span class="hljs-keyword">return</span> <span class="hljs-function">(<span class="hljs-params">url</span>) =&gt;</span> {
        <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(url, {
            <span class="hljs-attr">method</span>: <span class="hljs-string">'POST'</span>,
            <span class="hljs-attr">headers</span>: {
              [CSRF_HEADER]: token
            }
        });
        response.then(<span class="hljs-function"><span class="hljs-params">res</span> =&gt;</span> {
           token = res.headers[CSRF_HEADER];
           <span class="hljs-comment">// send new token to other tabs</span>
           channel.postMessage(token);
        });
        <span class="hljs-keyword">return</span> response;
    };
})(inital_token);
</code></pre>
<p>As you can see from the above example, Broadcast Channel doesn't have any namespace for events. So if you want to send more than one type of event you need to create types of events.</p>
<p>Here is an example of using Broadcast Channel to do more than the CSRF protection fix we've discussed so far. </p>
<p>You can synchronize login and logout for your application. If you login into one tab, your other tabs will also sign you in. In the same way, you can synchronize the shopping cart in some e-commerce websites.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> channel = <span class="hljs-keyword">new</span> BroadcastChannel(<span class="hljs-string">'my-connection'</span>);
<span class="hljs-keyword">const</span> CSRF = <span class="hljs-string">'app/csrf'</span>;
<span class="hljs-keyword">const</span> LOGIN = <span class="hljs-string">'app/login'</span>;
<span class="hljs-keyword">const</span> LOGOUT = <span class="hljs-string">'app/logout'</span>;
<span class="hljs-keyword">let</span> token;
channel.addEventListener(<span class="hljs-string">'message'</span>, <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
    <span class="hljs-keyword">switch</span> (e.data.type) {
        <span class="hljs-keyword">case</span> CSRF:
            token = e.data.payload;
            <span class="hljs-keyword">break</span>;
        <span class="hljs-keyword">case</span> LOGIN:
            <span class="hljs-keyword">const</span> { user } = e.data.payload;
            autologin(user);
            <span class="hljs-keyword">break</span>;
        <span class="hljs-keyword">case</span> LOGOUT:
            logout();
            <span class="hljs-keyword">break</span>;
    }
});

channel.postMessage({<span class="hljs-attr">type</span>: <span class="hljs-string">'login'</span>, <span class="hljs-attr">payload</span>: { user } });
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>It's great if you protect your app against attackers. But keep in mind how people will be using your application, too so you don't make it unnecessarily hard to use. This applies not only to this particular problem.</p>
<p>The <strong>Sysend</strong> library is a simple way to communicate between open tabs in the same browser. And it can fix major issues with CSRF protection. The library has more features, and you can check its <a target="_blank" href="https://github.com/jcubic/sysend.js">GitHub repo</a> for more details.</p>
<p><strong>Broadcast Channel</strong> is also not that complicated. If you don't need to support old browsers or some older mobile devices, you can use this API. But if you need to support older browsers, or want to make your code simpler, you use can the sysend library.</p>
<p>If you want to see browser support for Broadcast Channel, you can see <a target="_blank" href="https://caniuse.com/broadcastchannel">Can I Use</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
