<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: AnhChienVu</title>
    <description>The latest articles on DEV Community by AnhChienVu (@anhchienvu).</description>
    <link>https://dev.to/anhchienvu</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2027891%2Ff5b4bb90-e9e8-42a8-affe-9fb7455f5b0b.jpeg</url>
      <title>DEV Community: AnhChienVu</title>
      <link>https://dev.to/anhchienvu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/anhchienvu"/>
    <language>en</language>
    <item>
      <title>Create a Pull Request for a new Feature of ImprovedTub</title>
      <dc:creator>AnhChienVu</dc:creator>
      <pubDate>Fri, 06 Dec 2024 01:36:23 +0000</pubDate>
      <link>https://dev.to/anhchienvu/create-a-pull-request-for-a-new-feature-of-improvedtub-4fje</link>
      <guid>https://dev.to/anhchienvu/create-a-pull-request-for-a-new-feature-of-improvedtub-4fje</guid>
      <description>&lt;p&gt;After 2 weeks contributing to the open-source project &lt;a href="https://github.com/code-charity/youtube" rel="noopener noreferrer"&gt;ImprovedTub&lt;/a&gt;, I am excited to share my &lt;a href="https://github.com/code-charity/youtube/pull/2712/" rel="noopener noreferrer"&gt;Pull Request&lt;/a&gt; for the &lt;a href="https://github.com/code-charity/youtube/issues/294" rel="noopener noreferrer"&gt;new feature&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To recap the work I've over the past 2 weeks, you can refer to the following blog posts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/anhchienvu/exploring-improvedtube-a-beginners-journey-into-open-source-chrome-extensions-1ido"&gt;Planning&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/anhchienvu/continuous-progress-in-adding-new-feature-for-improvedtub-3nh9"&gt;Progress&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this blog, I will walk you through the code, steps and key learnings I've gained from this experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing the Details Switch
&lt;/h2&gt;

&lt;p&gt;Upon reviewing the source code, I discovered that all the sub-menu features represented in the extension were listed under the &lt;code&gt;menu/skeleton-parts&lt;/code&gt; folder:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6qwlv3ocvnebl0cggbnk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6qwlv3ocvnebl0cggbnk.png" alt="Image description" width="193" height="497"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;UI's Extension:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frr5f2s5tq94elvcd9fkh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frr5f2s5tq94elvcd9fkh.png" alt="Image description" width="318" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since I was implementing the feature to display the details of the currently playing video, along with its associated channel information, I decided to introduce a new switch within the &lt;code&gt;channel.js&lt;/code&gt; file. This file is responsible for defining the structure of the UI of &lt;code&gt;Channel&lt;/code&gt; component, and in this case, it manages the behavior of the &lt;code&gt;Channel&lt;/code&gt; button:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;skeleton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;layers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;section&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;channel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;click&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Other buttons ...&lt;/span&gt;
            &lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="na"&gt;channel_details_button&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;switch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Details&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Default state is off&lt;/span&gt;
                &lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;change&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;YOUR_API_KEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;switchElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;closest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.satus-switch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isChecked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;switchElement&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;switchElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

                        &lt;span class="c1"&gt;// Store the switch state in chrome.storage.local&lt;/span&gt;
                        &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;switchState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isChecked&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

                        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;videoId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getCurrentVideoId&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;videoInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getVideoInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channelId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;videoInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;snippet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channelId&lt;/span&gt;
                            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channelInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getChannelInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;channelId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                            &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;currentWindow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                                &lt;span class="c1"&gt;//console.log("Sending message to content.js");&lt;/span&gt;
                                &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                                    &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isChecked&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;append-channel-info&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;remove-channel-info&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                    &lt;span class="na"&gt;channelName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;channelInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channelName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                    &lt;span class="na"&gt;uploadTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;videoInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;snippet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publishedAt&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                                    &lt;span class="na"&gt;videoCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;channelInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;videoCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                    &lt;span class="na"&gt;customUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;channelInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customUrl&lt;/span&gt;
                                &lt;span class="p"&gt;})&lt;/span&gt;
                            &lt;span class="p"&gt;});&lt;/span&gt;
                        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
                            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                        &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the partial snippet code above, you will notice that I used a placeholder for the &lt;code&gt;YOUTUBE_API_KEY&lt;/code&gt;. As mentioned in my &lt;a href=""&gt;Progress&lt;/a&gt; blog, I was unable to implement &lt;code&gt;.env&lt;/code&gt; variable within a Chrome Extension. Therefore, to ensure the feature works correctly, the maintainer will need to generate an API key &lt;a href="https://developers.google.com/youtube/v3/getting-started" rel="noopener noreferrer"&gt;here&lt;/a&gt;. During development, I used my own API key for testing purposes; however, I could not include it in the source code for security reasons.&lt;/p&gt;

&lt;p&gt;Next, I needed to track the state of the switch. To achieve this, I set its default value to &lt;code&gt;false&lt;/code&gt; via the &lt;code&gt;value&lt;/code&gt; attribute. I then implemented the necessary logic to track its state within the &lt;code&gt;on { change:..}&lt;/code&gt; event listener. Since this function require fetching video data from the API, it needed to be asynchronous. To ensure the switch's state remains persistent, I saved it to Chrome's local storage using the following method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To adhere the DRY (Dont' Repeat Yourself) principle, I modularized the code into smaller functions, such as &lt;code&gt;getCurrentVideoId()&lt;/code&gt;, &lt;code&gt;getVideoInfo()&lt;/code&gt;, and &lt;code&gt;getChannelInfo()&lt;/code&gt;, each responsible for fetching specific video data. Once all the required information is gathered, I passed it to the &lt;code&gt;content.js&lt;/code&gt; file (which I will explain further below). The &lt;code&gt;content.js&lt;/code&gt; file checks if the switch is on; if so, it displays the details, otherwise, it remains hidden.&lt;/p&gt;

&lt;p&gt;These are all support function to get the Video data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getCurrentVideoId&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;videoId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;//console.log('Retrieved video ID from storage:', result.videoId); // Debugging log&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Video ID not found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getVideoInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://www.googleapis.com/youtube/v3/videos?id=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;key=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;part=snippet,contentDetails,statistics,status`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getChannelInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;channelId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://www.googleapis.com/youtube/v3/channels?id=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;channelId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;key=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;part=snippet,contentDetails,statistics,status`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;customUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;snippet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channelName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;snippet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;videoCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statistics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;videoCount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;channelName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;videoCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;customUrl&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After adding &lt;code&gt;Details&lt;/code&gt; switch, the UI will look as this:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5a3ymmuv3e9o5lkng4x0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5a3ymmuv3e9o5lkng4x0.png" alt="Image description" width="315" height="371"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Coding &lt;code&gt;content.js&lt;/code&gt; file
&lt;/h2&gt;

&lt;p&gt;As mentioned in my &lt;a href=""&gt;Progress&lt;/a&gt; post, the &lt;code&gt;content.js&lt;/code&gt; file is responsible for managing the UI of the webpage. Specially, this file handles the logic to track the state of the switch, which is sent from the &lt;code&gt;channel.js&lt;/code&gt; as well as &lt;code&gt;backend.js&lt;/code&gt;. The goal is to ensure the extension can maintain and reflect the switch's state across various scenarios. For instance, if a user turns on the switch while on a different webpage (not Youtube), the extension should retain this state when the user navigates to the YouTube page and display the details block accordingly. Similarly, if the switch is turned off, it should remain off upon navigating to YouTube.&lt;/p&gt;

&lt;p&gt;To achieve this, all the primary logic is implemented after the DOM is fully loaded. Once the DOM is ready, I listen for changes in the video URL to ensure the extension correctly updates the displayed details. This enables the extension to send the updated Video ID whenever a user switches to a new video:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;previousVideoId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getVideoIdFromUrl&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Listen for changes in the video URL and send the updated video ID&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;observer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MutationObserver&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentVideoId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getVideoIdFromUrl&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentVideoId&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;previousVideoId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;previousVideoId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;currentVideoId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="nf"&gt;sendVideoIdToBackground&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="nf"&gt;checkSwitchStateAndFetchData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;childList&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;subtree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, this approach only addresses the scenario where the video ID in the URL changes. It does not handle the case where the video remains the same, but the user adjusts the switch from a different tab. To resolve this, I devised another solution: listen for visibility changes to handle page navigation. This means that when the users navigate to another page and toggle the switch, the extension will ensure that when they return to the YouTube page, the details switch will still function correctly - either displaying or hiding the details area based on the switch's state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Listen for visibility changes to handle page navigation&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;visibilitychange&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visibilityState&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;visible&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;checkSwitchStateAndFetchData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I will need to combine both of these solution to fix all the edge cases that may happen with the switch function, and to DRY the code, I modularized the function &lt;code&gt;checkSwitchStateAndFetchData()&lt;/code&gt; as a support function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Check if the switch is on and fetch new data. Otherwise, remove the details block&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;checkSwitchStateAndFetchData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;check-switch-state&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isSwitchOn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fetch-new-data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Remove existing video details if the switch is off&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;targetElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.channel-info&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;targetElement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;targetElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since &lt;code&gt;content.js&lt;/code&gt; needs to communicate with &lt;code&gt;backend.js&lt;/code&gt; through the messaging system, it listens for messages from the backend to determine when to display the details area. To ensure the details are updated when the user switches to another video, the extension will remove the existing details before adding the new ones.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;backend.js&lt;/code&gt; sends 2 actions to &lt;code&gt;content.js&lt;/code&gt; to notify it of the switch's state:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;append-channel-info&lt;/code&gt;: This action is sent when the switch is turned on, signaling that the details should be displayed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;remove-channel-info&lt;/code&gt;: This action is sent when the switch is turned off, indicating that the details should be hidden.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sendResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;//console.log('Received message from background:', message); // Debugging log&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;append-channel-info&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;//console.log('Switch on Details mode'); &lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;channelName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;uploadTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;videoCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;customUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channelInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createChannelInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;channelName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;uploadTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;videoCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;customUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;targetElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ytd-video-owner-renderer.style-scope.ytd-watch-metadata&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;targetElement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Removing existing channel info if present&lt;/span&gt;
                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;existingChannelInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;targetElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.channel-info&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;existingChannelInfo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nx"&gt;existingChannelInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="nx"&gt;targetElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;channelInfo&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Target element not found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;remove-channel-info&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;//console.log('Switch off Details mode');&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;targetElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.channel-info&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;targetElement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;targetElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Channel info element not found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Exploring &lt;code&gt;backend.js&lt;/code&gt; file
&lt;/h2&gt;

&lt;p&gt;I'd like to once again thank the maintainer for providing clear comments throughout the code, which made it easy to identify where the messages sent from &lt;code&gt;content.js&lt;/code&gt; are handled. In this section, I added 3 new action cases: &lt;code&gt;store-video-id&lt;/code&gt;, &lt;code&gt;check-switch-state&lt;/code&gt;, and &lt;code&gt;fetch-new-data&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;store-video-id&lt;/code&gt;: Upon receiving this action from &lt;code&gt;content.js&lt;/code&gt;, the backend is responsible for storing the video ID in local storage. Once this is done, the backend sends a response message back to &lt;code&gt;content.js&lt;/code&gt;, confirming that the request has been fulfilled. Notably, the &lt;code&gt;return true&lt;/code&gt; statement is crucial here, as it ensures that &lt;code&gt;sendResponse&lt;/code&gt; is called asynchronously. Without this, &lt;code&gt;sendResponse&lt;/code&gt; would not function as expected.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;store-video-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="c1"&gt;//console.log('Storing video ID:', message.videoId); // Debugging log&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;videoId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;sendResponse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;sendResponse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;No video ID provided&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Indicates that sendResponse will be called asynchronously&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;check-switch-state&lt;/code&gt;: As discussed earlier, tracking the switch state is important. In this case, the backend retrieves the current state of the switch from local storage (where it was set by content.js) and sends it back to content.js.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;check-switch-state&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;switchState&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;sendResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;isSwitchOn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;switchState&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt; 
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;fetch-new-data&lt;/code&gt;: This action is triggered when content.js detects that the user has switched to a new video. The backend then fetches the relevant data for the new video. The logic here mirrors the process used in content.js to fetch video data, ensuring consistency across both components.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fetch-new-data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;videoId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;videoId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AIzaSyA0r8em0ndGCnx6vZu1Tv6T0iyLW4nB1jI&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Replace with your YouTube Data API key&lt;/span&gt;
                &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;videoInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getVideoInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channelId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;videoInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;snippet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channelId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channelInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getChannelInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;channelId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                    &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;currentWindow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="c1"&gt;//console.log("Sending message to content.js");&lt;/span&gt;
                        &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;append-channel-info&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="na"&gt;channelName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;channelInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channelName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="na"&gt;uploadTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;videoInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;snippet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publishedAt&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                            &lt;span class="na"&gt;videoCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;channelInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;videoCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="na"&gt;customUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;channelInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customUrl&lt;/span&gt;
                        &lt;span class="p"&gt;});&lt;/span&gt;
                    &lt;span class="p"&gt;});&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Finally
&lt;/h2&gt;

&lt;p&gt;There are all steps that I worked through to implement this feature, and here is the final outpu&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2spcv9ft6ghenv69q0y3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2spcv9ft6ghenv69q0y3.png" alt="Image description" width="572" height="89"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the area marked in red, I display the release time and date of the video, as well as the total number of videos posted by the channel. Additionally, I used an SVG icon to represent the "All Videos" link. When the user clicks on this icon, they are redirected to the video's channel. Another icon, representing "More Information," directs the user to a page where they can input a video link, after which all the video details are populated.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>programming</category>
      <category>improvedtub</category>
      <category>youtube</category>
    </item>
    <item>
      <title>Continuous Progress in Adding new Feature for ImprovedTub</title>
      <dc:creator>AnhChienVu</dc:creator>
      <pubDate>Fri, 29 Nov 2024 18:39:11 +0000</pubDate>
      <link>https://dev.to/anhchienvu/continuous-progress-in-adding-new-feature-for-improvedtub-3nh9</link>
      <guid>https://dev.to/anhchienvu/continuous-progress-in-adding-new-feature-for-improvedtub-3nh9</guid>
      <description>&lt;p&gt;This week marks the beginning of my work on a new feature, as outlined in my previous blog post on &lt;a href="https://dev.to/anhchienvu/exploring-improvedtube-a-beginners-journey-into-open-source-chrome-extensions-1ido"&gt;Planning Step&lt;/a&gt;. The goal is to enhance user experience by introducing a &lt;strong&gt;toggle switch button&lt;/strong&gt;, allowing users to enable or disable the display of detailed information about a played video and its associated channel.&lt;/p&gt;

&lt;p&gt;To implement this feature, I began by diving into the foundational files used in developing a Chrome Extension, specially:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;manifest.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;content.js&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;background.js&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these files plays a critical role in defining the behavior and structure of the extension. Below, I'll delve deeper into how I explored and utilized these components.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exploring the  &lt;code&gt;manifest.json&lt;/code&gt; file
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;manifest.json&lt;/code&gt; file is the cornerstone of any Chrome Extension. It serves as the configuration file that defines essential settings for the extension. For ImprovedTub, this file includes configurations like "background" scripts, &lt;code&gt;content_scripts&lt;/code&gt;, &lt;code&gt;permission&lt;/code&gt; and more. Without a properly configured &lt;code&gt;manifest.json&lt;/code&gt; file, the extension simply cannot function. &lt;/p&gt;

&lt;p&gt;These fields describes the extension:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fabpg0ph8hm856vet86kz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fabpg0ph8hm856vet86kz.png" alt="Image description" width="526" height="274"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;permissions&lt;/code&gt; field specify the scope of the extension's functionality. For example, to interact with Youtube's DOM or store user preferences, we need to explicitly request access in the manifest:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuatwoo8qx2bs11dg6nzu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuatwoo8qx2bs11dg6nzu.png" alt="Image description" width="200" height="90"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;host_permissions&lt;/code&gt; field tells that this extension is only dealing with Youtube pages:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqt34s99ixlxj49al1jn4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqt34s99ixlxj49al1jn4.png" alt="Image description" width="486" height="26"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;background&lt;/code&gt; field specify scripts that run in the background, maintaining the persistent state of the extension. For ImprovedTub, this is crucial for managing event listeners and controlling features like toggle button. The &lt;code&gt;background.js&lt;/code&gt; is a JavaScript file that will run separately from the web browser thread, the &lt;code&gt;service_worker&lt;/code&gt; would not have access to content of web page, but it has capabilities to speak with the extension using the extension messaging system.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqknchgiqlun4wcffikhi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqknchgiqlun4wcffikhi.png" alt="Image description" width="360" height="91"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;content_scripts&lt;/code&gt; field define the scripts that will run within the context of the web pages. This is where we inject the logic to manipulate the user interface, such as adding the toggle button and displaying or hiding the details block.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8iduo8x5q2nu1vp3325p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8iduo8x5q2nu1vp3325p.png" alt="Image description" width="752" height="731"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;default_popup&lt;/code&gt; field tells me what &lt;code&gt;html&lt;/code&gt; file will be served as the Extension's UI: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkzuqmh8v9tmlfn49umq9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkzuqmh8v9tmlfn49umq9.png" alt="Image description" width="384" height="96"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Adding Switch buton
&lt;/h2&gt;

&lt;p&gt;After having some knowledges about files' structure and purpose via the &lt;code&gt;manifest.json&lt;/code&gt;, I started adding a switch button named &lt;code&gt;Details&lt;/code&gt; inside the &lt;code&gt;menu/skeleton-parts/channel.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;channel_details_button&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nl"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;switch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Details&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Default state is off&lt;/span&gt;
                &lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nl"&gt;change&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;YOUTUBE_API_KEY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;switchElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;closest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.satus-switch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isChecked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;switchElement&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;switchElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isChecked&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;videoId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getCurrentVideoId&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;videoInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getVideoInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channelId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;videoInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;snippet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channelId&lt;/span&gt;
                            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channelInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getChannelInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;channelId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                            &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;currentWindow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                                &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Sending message to content.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                                &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                                    &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isChecked&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;append-channel-info&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;remove-channel-info&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                    &lt;span class="na"&gt;channelName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;channelInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channelName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                    &lt;span class="na"&gt;uploadTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;videoInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;snippet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publishedAt&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                                    &lt;span class="na"&gt;videoCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;channelInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;videoCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                    &lt;span class="na"&gt;customUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;channelInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customUrl&lt;/span&gt;
                                &lt;span class="p"&gt;})&lt;/span&gt;
                            &lt;span class="p"&gt;});&lt;/span&gt;
                        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
                            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                        &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And it will look as: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4xeey2badar6bem0i0gf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4xeey2badar6bem0i0gf.png" alt="Image description" width="316" height="581"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When switch is off:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ukonfuzk78byeb5c6e5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ukonfuzk78byeb5c6e5.png" alt="Image description" width="575" height="59"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When switch is on:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F463i6j9sg8bvbk2in2il.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F463i6j9sg8bvbk2in2il.png" alt="Image description" width="572" height="116"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this stage, I am unsure how to use environment variables to store the &lt;code&gt;YOUTUBE_API_KEY&lt;/code&gt;, as the Chrome Extension project is not built with Node.js, making it impossible to use the &lt;code&gt;dotenv&lt;/code&gt; package. As a workaround, I plan to leave a note for the maintainer to manually add an API key to the placeholder for enabling API requests. Within the &lt;code&gt;change&lt;/code&gt; function, I attempted to fetch video information using the Youtube API via the &lt;code&gt;videoID&lt;/code&gt;. Once the data is retrieved, I used the &lt;code&gt;chrome.tabs.query&lt;/code&gt; function to send it to &lt;code&gt;content.js&lt;/code&gt;, enabling the UI to display the video details&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding &lt;code&gt;content.js&lt;/code&gt; file
&lt;/h2&gt;

&lt;p&gt;To implement the toggle functionality, I added a new JavaScript file under &lt;code&gt;js&lt;/code&gt; field in the &lt;code&gt;manifest.json&lt;/code&gt; file. This &lt;code&gt;content.js&lt;/code&gt; file will serves as a bridge for interacting with the main thread of web pages, such as injecting UI elements or handling DOM manipulations. However, at this stage, I faced a decision regarding the file's placement within the existing project structure.&lt;/p&gt;

&lt;p&gt;While exploring the repository, I noticed a pattern: all JavaScript files referenced in the &lt;code&gt;js&lt;/code&gt; field for web page interactions were located under the &lt;code&gt;js&amp;amp;css/extension/&lt;/code&gt; directory. Although this structure wasn't explicitly documented, it appeared to be the convention followed by the maintainers.&lt;/p&gt;

&lt;p&gt;Based on this observation, I decided to create the &lt;code&gt;content.js&lt;/code&gt; file within the same directory to maintain consistency. &lt;/p&gt;

&lt;h2&gt;
  
  
  Adding event listener in &lt;code&gt;background.js&lt;/code&gt; file
&lt;/h2&gt;

&lt;p&gt;This file will need to handle and send response back to the &lt;code&gt;content.js&lt;/code&gt;. Inside this file, maintainer marked a note for &lt;code&gt;MESSAGE LISTENER&lt;/code&gt; part where I will code to handle the message type from &lt;code&gt;content.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/*--------------------------------------------------------------
# MESSAGE LISTENER
--------------------------------------------------------------*/&lt;/span&gt;
&lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sendResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;//console.log(message);&lt;/span&gt;
    &lt;span class="c1"&gt;//console.log(sender);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Message received in background:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;store-video-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Storing video ID:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Debugging log&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;videoId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Video ID stored:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Debugging log&lt;/span&gt;
                    &lt;span class="nf"&gt;sendResponse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
                &lt;span class="p"&gt;});&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;sendResponse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;No video ID provided&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Indicates that sendResponse will be called asynchronously&lt;/span&gt;

            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fetch-new-data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;videoId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;videoId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AIzaSyA0r8em0ndGCnx6vZu1Tv6T0iyLW4nB1jI&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Replace with your YouTube Data API key&lt;/span&gt;
                    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;videoInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getVideoInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channelId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;videoInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;snippet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channelId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channelInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getChannelInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;channelId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                        &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;currentWindow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Sending message to content.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                            &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                                &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;append-channel-info&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                &lt;span class="na"&gt;channelName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;channelInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channelName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                &lt;span class="na"&gt;uploadTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;videoInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;snippet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publishedAt&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                                &lt;span class="na"&gt;videoCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;channelInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;videoCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                &lt;span class="na"&gt;customUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;channelInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customUrl&lt;/span&gt;
                            &lt;span class="p"&gt;});&lt;/span&gt;
                        &lt;span class="p"&gt;});&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;});&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    

        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I added 2 cases to handle events as &lt;code&gt;store-video-id&lt;/code&gt; and &lt;code&gt;fetch-new-data&lt;/code&gt;. As explained in &lt;code&gt;manifest.json&lt;/code&gt; file that I used the &lt;code&gt;chrome.storage&lt;/code&gt; to store the &lt;code&gt;videoId&lt;/code&gt; to browser, and will fetch video's data based on this id.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;So far, I’ve implemented most of the feature, but there’s still work to be done. I need to address a bug where the details section does not always reflect the toggle switch's status, especially when the switch is toggled on or off from a non-YouTube page. To fix this, I’ll need to add a mechanism to consistently check the switch’s state and ensure the UI aligns with it. Additionally, I need to dive deeper into the project’s code structure to determine the best place for my changes. Currently, my implementation is explicit and self-contained, but the maintainers follow a different coding style. To align with their approach, I’ll need to refactor my code to integrate seamlessly with the existing structure while maintaining the new feature’s functionality.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>opensource</category>
      <category>improvedtub</category>
      <category>chromeextensions</category>
    </item>
    <item>
      <title>Exploring ImprovedTube: A Beginner's Journey into Open Source Chrome Extensions</title>
      <dc:creator>AnhChienVu</dc:creator>
      <pubDate>Thu, 21 Nov 2024 23:00:08 +0000</pubDate>
      <link>https://dev.to/anhchienvu/exploring-improvedtube-a-beginners-journey-into-open-source-chrome-extensions-1ido</link>
      <guid>https://dev.to/anhchienvu/exploring-improvedtube-a-beginners-journey-into-open-source-chrome-extensions-1ido</guid>
      <description>&lt;p&gt;This marks my first experience working on a Chrome Extension project. The extension, called &lt;a href="https://github.com/code-charity/youtube" rel="noopener noreferrer"&gt;ImprovedTube&lt;/a&gt;, enhance the Youtube user experience by offering a wide range of customization options. Users can tailor their preferences, manage content discovery, adjust themes, and refines the UI, giving them full control over their Youtube interaction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ImprovedTube&lt;/strong&gt; has been in development since 2019 and is an open-source project with contributions from volunteer developers worldwide. Its longevity and active maintenance make it an intriguing project to explore.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I chose ImprovedTube
&lt;/h2&gt;

&lt;p&gt;When selecting a project to contribute to for my &lt;strong&gt;Release0.4&lt;/strong&gt; at school, several factors about ImprovedTube stood out:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Community and activity&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The GitHub repository boasts over &lt;strong&gt;3.5k Stars&lt;/strong&gt; and &lt;strong&gt;542 folks&lt;/strong&gt;, indicating a strong community interest.&lt;/li&gt;
&lt;li&gt;The project has seen &lt;strong&gt;69 releases&lt;/strong&gt;, with the latest release occurring just one month ago.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These metrics demonstrate an active and engaged community and a dedicated maintainer team still driving the project forward.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Real-world impact&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ImprovedTube is not just a development experiment - it's a production-ready extension available for immediate use. You can install it via the &lt;a href="https://chromewebstore.google.com/detail/improve-youtube-%F0%9F%8E%A7-for-yo/bnomihfieiccainjcjblhegjgglakjdd" rel="noopener noreferrer"&gt;Chrome Web Store&lt;/a&gt; and experience it features. Contributing to a project already in the hands of users was a significant motivator, as it gave my work tangible value&lt;/p&gt;

&lt;h2&gt;
  
  
  Overcoming the Learning Curve
&lt;/h2&gt;

&lt;p&gt;Since this was my first experience developing a Chrome extension, I initially felt overwhelmed by the unfamiliar structure and file organization. Fortunately, I found a helpful Youtube tutorial that explained how to approach Chrome Extension projects. This video provided insights into common file structures and code organizations, helping me break the ice and start exploring the repository more effectively. &lt;/p&gt;

&lt;h2&gt;
  
  
  My contribution
&lt;/h2&gt;

&lt;p&gt;After browsing the project's open issues, I came across an &lt;a href="https://github.com/code-charity/youtube/issues/294" rel="noopener noreferrer"&gt;enhancement request&lt;/a&gt; that piqued my interest. This feature would add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Detailed metadata about Youtube videos, including the published date and links to all videos on the channel.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tools to make navigating and analyzing Youtube channel more efficient.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This task resonated with me because it allowed me to contribute something both functional and impactful while expanding my technical knowledge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Goals and Reflections
&lt;/h2&gt;

&lt;p&gt;Through this contribution, I aim to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gain hands-on experience in developing Chrome extensions.&lt;/li&gt;
&lt;li&gt;Learn how to navigate and contribute to a large, existing codebase.&lt;/li&gt;
&lt;li&gt;Build confidence in open-source collaboration by contributing to a real-world product used by thousands of users.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Working on ImprovedTube has been an exiting challenge, and I look forward to seeing how my contributions evolve as I continue to explore the world of open-source Chrome extensions. &lt;/p&gt;

</description>
      <category>javascript</category>
      <category>opensource</category>
      <category>youtube</category>
      <category>codenewbie</category>
    </item>
    <item>
      <title>VShell: Production Release Workflow</title>
      <dc:creator>AnhChienVu</dc:creator>
      <pubDate>Tue, 19 Nov 2024 20:05:37 +0000</pubDate>
      <link>https://dev.to/anhchienvu/vshell-production-release-workflow-48bm</link>
      <guid>https://dev.to/anhchienvu/vshell-production-release-workflow-48bm</guid>
      <description>&lt;p&gt;This week, I am preparing to release my command-line tool, &lt;a href="https://github.com/AnhChienVu/VShell" rel="noopener noreferrer"&gt;VShell&lt;/a&gt;, to production. This release will enable users to install and use &lt;code&gt;VShell&lt;/code&gt; directly via &lt;code&gt;npm&lt;/code&gt; without the need to clone and build the project locally. Written in JavaScript with Node.js, the tool will be hosted on &lt;code&gt;npm&lt;/code&gt; for ease of distribution. Below is a detailed breakdown of the steps I followed to achieve this release:&lt;/p&gt;

&lt;h2&gt;
  
  
  Automating the Build Process
&lt;/h2&gt;

&lt;p&gt;To ensure a reproducible and automated build, I created a &lt;code&gt;build.js&lt;/code&gt; script in the root of the project. This script copies all necessary files into a &lt;code&gt;dist&lt;/code&gt; folder, simplifying the build process. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F69pa1obvko3wjfomgxnv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F69pa1obvko3wjfomgxnv.png" alt="Image description" width="527" height="599"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Besides, I also set up a script to run it in &lt;code&gt;package.json&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;"build": "node build.js"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;To run it, just need to type: &lt;code&gt;npm run build&lt;/code&gt; on the console&lt;/p&gt;

&lt;p&gt;Currently, I just wanted to demonstrate a simply build as copying files, but in the future, if I implemented with the UI, then I would need to use template bundle script as &lt;code&gt;barbel&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing Semantic Versioning
&lt;/h2&gt;

&lt;p&gt;Previously, I often forgot to update the project version after making changes. For this release, I adopted Semantic Versioning to clearly communicate changes in the project API.&lt;/p&gt;

&lt;p&gt;Given the significant updates since &lt;code&gt;v0.0.1&lt;/code&gt;, I set the version to &lt;code&gt;v1.0.0&lt;/code&gt; by updating the &lt;code&gt;version&lt;/code&gt; field in &lt;code&gt;package.json&lt;/code&gt;. This version reflects a stable API with breaking changes and new features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tagging the Release in Git
&lt;/h2&gt;

&lt;p&gt;To synchronize the version update with GitHub releases, I created a Git tag for &lt;code&gt;v1.0.0&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git tag -a v1.0.0 -m "Release version 1.0.0"
git push origin --tags
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures that the release is properly tracked in the Git repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  Declaring Package Files
&lt;/h2&gt;

&lt;p&gt;To publish VShell as an npm package, I explicitly defined the files to include in the package using the &lt;code&gt;files&lt;/code&gt; field in &lt;code&gt;package.json&lt;/code&gt;. This ensures that only the necessary files and directories are included in the published package.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frbkuzw9a2kq0wc91xhr6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frbkuzw9a2kq0wc91xhr6.png" alt="Image description" width="231" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Publishing to npm
&lt;/h2&gt;

&lt;p&gt;To publish VShell on &lt;a href="https://www.npmjs.com/" rel="noopener noreferrer"&gt;npm&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sign Up and Log In: I created an npm account and logged in using:
&lt;code&gt;npm login&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Publishing: Once authenticated, I published the package:
&lt;code&gt;npm publish&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This made VShell available to users via npm.&lt;/p&gt;

&lt;h2&gt;
  
  
  Out of scope updates
&lt;/h2&gt;

&lt;p&gt;GitHub Dependabot flagged a security issue with the transitive dependency &lt;code&gt;cross-spawn&lt;/code&gt;. Since I didn’t install it directly, I used:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm ls cross-spawn&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;to identify which package depended on it. I discovered that &lt;code&gt;eslint&lt;/code&gt; was the source and resolved the issue by updating &lt;code&gt;eslint&lt;/code&gt; to the latest version.&lt;/p&gt;

&lt;p&gt;Additionally, I updated the &lt;code&gt;README.md&lt;/code&gt; to provide clear installation instructions for npm users. I also improved the &lt;code&gt;CONTRIBUTING.md&lt;/code&gt; file to guide contributors on setting up and running VShell locally.&lt;/p&gt;

&lt;p&gt;To enhance accuracy, I incorporated feedback from Prof. David Humphrey, expanding and clarifying the GROQ system documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automating Releases with GitHub Actions
&lt;/h2&gt;

&lt;p&gt;To streamline the release process, I added a job to my GitHub Actions workflow to automate npm publishing whenever a new tag is pushed.&lt;/p&gt;

&lt;p&gt;Workflow Addition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;release:
    needs: build

    runs-on: ubuntu-latest

    if: startsWith(github.ref, 'refs/tags/')

    permissions:
      contents: write

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Setup Node.js
      uses: actions/setup-node@v4
      with:
        node-version: 22.x
        registry-url: 'https://registry.npmjs.org'

    - name: Install dependencies
      run: npm ci

    - name: Build project
      run: npm run build

    - name: Update version
      run: npm version ${{ github.ref_name}} --no-git-tag-version

    - name: Publish to npm
      run: npm publish
      env: 
        NODE_AUTH_TOKEN: ${{secrets.NPM_AUTH_TOKEN}}

    - name: Create GitHub release
      uses: softprops/action-gh-release@v1
      with:
        name: Release ${{ github.ref_name}}
        draft: false
        prerelease: false
        token: ${{ secrets.GITHUB_TOKEN }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key Features:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conditional Trigger&lt;/strong&gt;: The release job is triggered only when a tag &lt;code&gt;(v*.*.*)&lt;/code&gt; is pushed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;npm Authentication&lt;/strong&gt;: Using a secure &lt;code&gt;NPM_AUTH_TOKEN&lt;/code&gt; stored in GitHub Secrets. You can create this token by going to your npm account and choose &lt;code&gt;Access Token&lt;/code&gt; section.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub Release&lt;/strong&gt;: Automatically generates a release using &lt;code&gt;softprops/action-gh-release&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By following these steps, I successfully automated the release of VShell to npm. Users can now install and use the tool effortlessly via:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm install -g vshell&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This streamlined process, coupled with proper versioning, dependency management, and documentation updates, ensures a professional and user-friendly release of VShell.&lt;/p&gt;

</description>
      <category>githubactions</category>
      <category>javascript</category>
      <category>opensource</category>
      <category>openai</category>
    </item>
    <item>
      <title>Working with GitHub OAuth Authentication</title>
      <dc:creator>AnhChienVu</dc:creator>
      <pubDate>Fri, 15 Nov 2024 22:29:41 +0000</pubDate>
      <link>https://dev.to/anhchienvu/working-with-github-oauth-authentication-48dk</link>
      <guid>https://dev.to/anhchienvu/working-with-github-oauth-authentication-48dk</guid>
      <description>&lt;p&gt;Hello everyone,&lt;/p&gt;

&lt;p&gt;In this blog post, I’ll discuss my second pull request (PR) as part of the &lt;code&gt;Release 0.3&lt;/code&gt; series for my assignment. This PR continues my work on the &lt;a href="https://github.com/jaiyankargupta/GitExplorer" rel="noopener noreferrer"&gt;GitExplorer&lt;/a&gt; project, which I initially contributed to during the &lt;code&gt;Hacktoberfest&lt;/code&gt; event. For this week &lt;a href="https://github.com/jaiyankargupta/GitExplorer/issues/6" rel="noopener noreferrer"&gt;issue&lt;/a&gt;, the project maintainer requested the implementation of GitHub authentication functionality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview of GitExplorer
&lt;/h2&gt;

&lt;p&gt;GitExplorer is a project aimed at simplifying interactions with GitHub repositories. If you’re interested in learning more about the project or my previous contributions to it, feel free to check out my previous &lt;a href="https://dev.to/anhchienvu/hacktoberfest-week-2-diving-deeper-into-code-contributions-12g1"&gt;blog&lt;/a&gt; post for this repository.&lt;/p&gt;

&lt;p&gt;The specific issue I tackled this time was the lack of GitHub authentication in the application. This limitation prevented the app from providing personalized features like starring repositories. My task was to implement a GitHub authentication feature that enables users to log in and log out using their GitHub accounts. Additionally, users would be able to star their favorite repositories and fetch a list of all their starred repositories with a simple button click.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges: Working Without a UI Framework
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;GitExplorer&lt;/code&gt; does not use any modern UI frameworks like &lt;code&gt;React&lt;/code&gt;. Instead, it relies solely on vanilla JavaScript, CSS, and HTML. While this simplicity has its advantages, it required me to dedicate more time to the issue as the original codebase was front-end focused. Adding GitHub authentication and related backend functionality meant venturing beyond the existing structure and implementing a server-side solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrating the Project and Setting Up the Backend
&lt;/h2&gt;

&lt;p&gt;To support the new features, I first restructured the project. I moved all existing front-end code into a public folder and created a backend folder to house the server-side implementation. For the backend, I chose the &lt;code&gt;Express&lt;/code&gt; framework as it integrates well with &lt;code&gt;Passport.js&lt;/code&gt;, a library I used to handle GitHub authentication.&lt;/p&gt;

&lt;p&gt;The steps included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Setting up Express to serve the application.&lt;/li&gt;
&lt;li&gt;Configuring &lt;code&gt;Passport.js&lt;/code&gt; to handle authentication using &lt;code&gt;CLIENT_ID&lt;/code&gt; and &lt;code&gt;CLIENT_SECRET&lt;/code&gt;, which I stored in an &lt;code&gt;.env&lt;/code&gt; file for security.&lt;/li&gt;
&lt;li&gt;Adding routes to manage user actions like starring and unstarring repositories.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  GitHub API Integration
&lt;/h2&gt;

&lt;p&gt;After setting up the backend, I integrated GitHub’s API to support the required functionality. This included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enabling users to log in via their GitHub accounts.&lt;/li&gt;
&lt;li&gt;Adding the ability to star and unstar repositories directly from the application.&lt;/li&gt;
&lt;li&gt;Fetching a list of the user’s starred repositories and displaying them on a dedicated page, accessible through a button on the UI.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Writing Modular, DRY Code
&lt;/h2&gt;

&lt;p&gt;Beyond implementing functionality, I focused on improving code quality by applying the DRY (Don’t Repeat Yourself) principle. I modularized the code by separating functions into reusable components, making it easier to maintain and extend in the future.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned and Looking Ahead
&lt;/h2&gt;

&lt;p&gt;This was my first experience working with GitHub authentication, and it provided valuable insights into integrating third-party APIs and managing backend services. While the project initially appeared simple, working on both the front-end and back-end gave me a deeper understanding of full-stack development.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;GitExplorer&lt;/code&gt; still has many interesting issues open for collaboration. If time permits, I plan to contribute further. The project has proven to be beginner-friendly while offering ample opportunities to learn new technologies and concepts.&lt;/p&gt;

&lt;p&gt;Thank you for reading, and I hope this post inspires you to explore similar opportunities for learning and growth.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>opensource</category>
      <category>codepen</category>
      <category>html</category>
    </item>
    <item>
      <title>Keep continuing with Open Source Development</title>
      <dc:creator>AnhChienVu</dc:creator>
      <pubDate>Tue, 12 Nov 2024 19:47:45 +0000</pubDate>
      <link>https://dev.to/anhchienvu/keep-continuing-with-open-source-development-3np2</link>
      <guid>https://dev.to/anhchienvu/keep-continuing-with-open-source-development-3np2</guid>
      <description>&lt;p&gt;Hello everyone, in this Release 0.3, I am focusing on two primary tasks. The first Pull Request I have chosen to address involves implementing a responsive navigation bar for the &lt;a href="https://github.com/anjalit03/Dazzle-and-Delight" rel="noopener noreferrer"&gt;Dazzle-and-Delight&lt;/a&gt; project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;The Dazzle-and-Delight repository hosts a web-based event management platform. This platform allows users to browse information about both past and upcoming events. The website is built using JavaScript, HTML, and CSS, and contains approximately seven pages that require updates to ensure the navigation bar is fully responsive across various screen sizes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Work Details
&lt;/h2&gt;

&lt;p&gt;While the project itself is relatively straightforward, the scope of work required for this task was more substantial. Specifically, I needed to &lt;a href="https://github.com/anjalit03/Dazzle-and-Delight/issues/87" rel="noopener noreferrer"&gt;implement a responsive navigation bar that adjusts to smaller screen sizes&lt;/a&gt; (e.g., mobile phones and tablets).&lt;/p&gt;

&lt;p&gt;To address this, I introduced a hamburger menu that, when clicked, reveals a vertical list of navigation sections. This menu is only displayed on screen sizes with a width of 768px or smaller. The implementation involved updating 14 files in total.&lt;/p&gt;

&lt;p&gt;For better code organization and ease of debugging, I created a new file, navbar_menu.js, which handles the event listeners for the menu button:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;menu-bar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;section-list&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;section-list&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;display&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;block&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;close-menu-btn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;section-list&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My PR: &lt;a href="https://github.com/anjalit03/Dazzle-and-Delight/pull/100" rel="noopener noreferrer"&gt;Make the navbar responsive&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation Steps for Each HTML File:
&lt;/h2&gt;

&lt;p&gt;Add a section element with the &lt;code&gt;id="section-list"&lt;/code&gt; under the &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt; tag. This section is responsible for displaying the vertical navigation when the hamburger menu is clicked.&lt;/p&gt;

&lt;p&gt;Include the script &lt;code&gt;&amp;lt;script src="js/navbar_menu.js"&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt; at the bottom of each HTML file. This script manages the show/hide functionality of the vertical navigation menu upon user interaction. &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion:
&lt;/h2&gt;

&lt;p&gt;While the project itself is not overly complex, the number of files affected by this change presented a considerable challenge. Compared to previous Pull Requests in this course, this task required more extensive file modifications. Although it may not be the most technically difficult task I've undertaken, the volume of updates made demonstrates progress in my open source contributions.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>opensource</category>
      <category>html</category>
      <category>css</category>
    </item>
    <item>
      <title>Implementing a CI Pipeline for VShell with GitHub Actions</title>
      <dc:creator>AnhChienVu</dc:creator>
      <pubDate>Tue, 12 Nov 2024 04:13:10 +0000</pubDate>
      <link>https://dev.to/anhchienvu/implementing-a-ci-pipeline-for-vshell-with-github-actions-ei1</link>
      <guid>https://dev.to/anhchienvu/implementing-a-ci-pipeline-for-vshell-with-github-actions-ei1</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;This week, I delved into setting up a Continuous Integration (CI) pipeline using GitHub Actions while also expanding my suite of unit tests for my project, &lt;a href="https://github.com/AnhChienVu/VShell" rel="noopener noreferrer"&gt;VShell&lt;/a&gt;. The process has enhanced my understanding of CI principles, automated testing, and the tools that help developers ensure reliable code delivery.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Continuous Integration?
&lt;/h2&gt;

&lt;p&gt;Continuous Integration is a development practice where code changes are automatically tested and integrated into a shared repository frequently. By leveraging a CI pipeline, developers can detect and address issues early in the development cycle, improving code quality and reducing integration risks. A CI/CD pipeline allows us to build, test, and deploy applications automatically, accelerating development and increasing reliability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up the CI Pipeline with GitHub Actions
&lt;/h2&gt;

&lt;p&gt;There are numerous CI tools available—Jenkins, CircleCI, TravisCI, and GitLab CI, among others—but for this week, I focused on GitHub Actions to implement CI/CD for my application. GitHub Actions offers an integrated, straightforward setup directly in GitHub, making it a great choice for my project.&lt;/p&gt;

&lt;p&gt;Configuring GitHub Actions for My Project To set up a CI pipeline, I navigated to the Actions tab of my GitHub repository and selected the Node.js template, as my application is JavaScript-based and runs on Node.js. This generated a &lt;code&gt;.yml&lt;/code&gt; configuration file that defines the CI pipeline’s behavior.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1n169ux5z08hjiruyag7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1n169ux5z08hjiruyag7.png" alt="Image description" width="338" height="484"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Understanding the YAML Configuration For those new to CI, the &lt;code&gt;.yml&lt;/code&gt; file might seem intimidating. Here’s a breakdown of how it works:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Triggering the Pipeline&lt;/strong&gt;: The pipeline is set to run whenever a push or pull request is made to the main branch, using the &lt;code&gt;on&lt;/code&gt; keyword to define these triggers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Defining Jobs&lt;/strong&gt;: The configuration includes a series of jobs that execute when the CI pipeline is triggered. These jobs specify an Ubuntu OS build environment, test across different Node.js versions, and contain steps to set up and execute the runtime environment. Finally, the tests are automatically run to validate the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  My experience
&lt;/h2&gt;

&lt;p&gt;Environment Variable Configuration During my initial pipeline setup, I encountered an error related to the &lt;code&gt;GROQ_API_KEY&lt;/code&gt;, which is essential for certain tests and defined in my local &lt;code&gt;.env&lt;/code&gt; file. While tests ran smoothly locally, GitHub Actions couldn't access the variable, leading to failed runs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;: Setting Up GitHub Secrets To address this, I configured a secret variable for the API key in my GitHub repository settings. Here’s a quick summary of the fix:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I navigated to the repository’s settings and added &lt;code&gt;GROQ_API_KEY&lt;/code&gt; as a secret under Settings &amp;gt; Secrets.&lt;/li&gt;
&lt;li&gt;In the &lt;code&gt;.yml&lt;/code&gt; file, I added the env keyword to instruct the pipeline to retrieve this key, ensuring the tests could access it without hardcoding sensitive data in the configuration file.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ci</category>
      <category>javascript</category>
      <category>codenewbie</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Testing VShell: A starting point into Test-Driven Development</title>
      <dc:creator>AnhChienVu</dc:creator>
      <pubDate>Thu, 07 Nov 2024 00:04:29 +0000</pubDate>
      <link>https://dev.to/anhchienvu/testing-vshell-a-starting-point-into-test-driven-development-ila</link>
      <guid>https://dev.to/anhchienvu/testing-vshell-a-starting-point-into-test-driven-development-ila</guid>
      <description>&lt;h2&gt;
  
  
  Testing Frameworks
&lt;/h2&gt;

&lt;p&gt;For testing this project, I chose the following tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Jest&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Why?&lt;/strong&gt;: Jest is a widely used testing framework for JavaScript projects, and since my project was written in JavaScript, it was a natural choice. Jest provides a simple API for unit and integration testing, built-in mocking, and snapshot testing. It is ideal for both small and large projects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Link&lt;/strong&gt;: &lt;a href="https://jestjs.io/docs/getting-started" rel="noopener noreferrer"&gt;Jest Documentation&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Nock&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Why?&lt;/strong&gt;: Nock is a powerful library that allows you to mock HTTP requests. It works well for testing scenarios where external API calls (like LLM integrations) are involved. By using Nock, I can simulate LLM responses without actually hitting the live API, which makes the tests faster and more reliable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Link&lt;/strong&gt;: &lt;a href="https://www.npmjs.com/package/nock" rel="noopener noreferrer"&gt;Nock Documentation&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Testing Configuration
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Installing Dependencies&lt;/strong&gt;&lt;br&gt;
The first step was to install Jest and Nock:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-dev&lt;/span&gt; jest nock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Configuring Jest with ESLint&lt;/strong&gt;&lt;br&gt;
Since Jest uses global variables like &lt;code&gt;describe&lt;/code&gt;, &lt;code&gt;test&lt;/code&gt;, &lt;code&gt;expect&lt;/code&gt;, etc., I had to configure &lt;code&gt;ESLint&lt;/code&gt; to avoid warnings related to uninitialized global variables. To do this, I added the following configuration in my &lt;code&gt;eslint.config.mjs&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;globals&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;globals&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;pluginJs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@eslint/js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;pluginJest&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eslint-plugin-jest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;**/*.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;languageOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;sourceType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;commonjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;ignores&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;build/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;coverage/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node_modules/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.env&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*.config.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*.config.mjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;examples/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;languageOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;globals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;globals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;globals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;jest&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;pluginJs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recommended&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pluginJest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;pluginJest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recommended&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells ESLint that these global variables are provided by Jest, and it shouldn't flag them as errors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setting Up Nock for Mocking HTTP Requests&lt;/strong&gt;&lt;br&gt;
In the test files, I imported Nock and used it to mock HTTP requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;nock&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, I defined mock HTTP responses for the LLM API like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mockResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chatcmpl-0b910ec8-e9d9-4095-99ed-1311b8efcf39&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;object&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chat.completion&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;created&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1730865897&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;llama3-8b-8192&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;assistant&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;This is a test response from the LLM.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;logprobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;finish_reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stop&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;queue_time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.003401526000000002&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;prompt_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;prompt_time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.079328471&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;completion_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;completion_time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.215833333&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;total_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;total_time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.295161804&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;system_fingerprint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fp_a97cfe35ae&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;x_groq&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;req_01jbztb85cf4nv71cyjd6pms9w&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="nf"&gt;nock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.groq.com/openai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/v1/chat/completions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mockResponse&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way, every time my code makes an HTTP request, Nock intercepts it and returns the mocked response.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges that I faced
&lt;/h2&gt;

&lt;p&gt;One of the major challenges I faced during this process was testing how the LLM handles streaming responses. This type of response is returned differently than a standard JSON object, which made mocking it a bit tricky. Here's what I learned:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Streaming Responses Are Different&lt;/strong&gt;: The LLM streaming API sends responses in "chunks", which is fundamentally different from regular responses that come in a single payload. This required me to modify my approach to testing, specifically how I simulated the stream-like behavior.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Nock Can't Mock Streams Directly&lt;/strong&gt;: Initially, I tried using Nock to mock the stream, but I quickly realized that it was designed to mock HTTP responses, not streams. I had to simulate the stream using an array of mocked chunks of data.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should return an object with response and tokenInfo properties&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Mock the stream&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; World&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
        &lt;span class="na"&gt;x_groq&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;queue_time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;prompt_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;prompt_time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.046&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;completion_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;completion_time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.203&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;total_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;total_time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="c1"&gt;// Call the readStream function&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;readStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Assert the result&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello World&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tokenInfo&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;completionToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;promptToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;totalToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing uncover some edge cases
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Empty Chunks&lt;/strong&gt;: One potential edge case I uncovered was how the system should behave when the streaming chunks are empty or incomplete. Handling empty chunks gracefully is crucial, as real-world API responses can sometimes contain empty or unexpected data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Missing Token Information&lt;/strong&gt;: I had to ensure that my code correctly handled cases where the token information (i.e., usage data) wasn't present in every chunk. This could happen if the stream is not fully sent or if it's split across multiple chunks.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These edge cases helped refine my implementation and ensure that my code could handle a variety of real-world situations.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learnt
&lt;/h2&gt;

&lt;p&gt;This was a highly educational experience. Here's what I took away from it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Testing is Essential&lt;/strong&gt;: Although I had written tests before, this project was my first time writing tests for an API integration, particularly for something as complex as a language model stream. I now see just how important it is to test all parts of the system, including edge cases and error handling.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mocking is Powerful&lt;/strong&gt;: I learned the importance of mocking external dependencies. Using Nock to mock the HTTP responses saved me from having to rely on a live connection to the LLM, which could have been slow or unreliable. Mocking also allowed me to simulate different responses easily.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Confidence in Code&lt;/strong&gt;: Writing tests made me feel more confident that my code was functioning as expected. The tests helped me catch potential issues early on, and they provided a safety net for future changes or refactoring.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Beyond Spellcheck: How Static Analysis Tools Enhance Collaboration in Coding</title>
      <dc:creator>AnhChienVu</dc:creator>
      <pubDate>Wed, 30 Oct 2024 21:02:36 +0000</pubDate>
      <link>https://dev.to/anhchienvu/beyond-spellcheck-how-static-analysis-tools-enhance-collaboration-in-coding-2fa9</link>
      <guid>https://dev.to/anhchienvu/beyond-spellcheck-how-static-analysis-tools-enhance-collaboration-in-coding-2fa9</guid>
      <description>&lt;p&gt;This week, I expanded my knowledge on the importance of code formatting in software development. Just as we rely on word processing software with spell check features to identify and correct spelling errors, developers should leverage code editors that offer formatting and linting capabilities to maintain code quality.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why Use Static Analysis Tools
&lt;/h1&gt;

&lt;p&gt;To &lt;strong&gt;enhance the quality&lt;/strong&gt; of our code and &lt;strong&gt;reduce development time&lt;/strong&gt;, it is essential for programmers to utilize static analysis tools. When collaborating on projects with multiple contributors, adherence to coding standards becomes crucial, as it allows others to read and understand the code more easily. This necessity has led to the emergence of various tools tailored to different programming languages. For my project this week, I chose to implement &lt;code&gt;Prettier&lt;/code&gt; for my JavaScript codebase. In addition to formatting tools, a linter is also essential. A linter assists developers in identifying overlooked errors that may not affect the program's execution but can lead to significant issues in the code if left unchecked. For this purpose, I selected &lt;code&gt;ESLint&lt;/code&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Prettier
&lt;/h1&gt;

&lt;p&gt;To use Prettier, I installed it locally in my project with the following command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm install --save-dev --save-exact prettier&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Next, I created two configuration files: &lt;code&gt;.prettierrc&lt;/code&gt; and &lt;code&gt;.prettierignore&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;.prettierrc&lt;/code&gt;: This configuration file contains the rules for formatting.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F04ya5kbjei3ndre5mbk2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F04ya5kbjei3ndre5mbk2.png" alt="Image description" width="160" height="126"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I left it as an empty object because I want to use the default Prettier's setup without overriding any rules.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;.prettierignore&lt;/code&gt;: This file lists the files or directories that Prettier should not format.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcj5eu4p0g32u6zg990az.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcj5eu4p0g32u6zg990az.png" alt="Image description" width="272" height="292"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I included all the files and folders that I don't want Prettier will approach. Most of them are configuration files and self-generated files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Notes&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Both files must be placed in the root directory of the project. &lt;/li&gt;
&lt;li&gt;After installation, I can test Prettier's functionality by running:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;npx prettier . --write&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To make it easier to run that command, I made a script that will run in command-line. Inside the &lt;code&gt;package.json&lt;/code&gt; file, I added a new script named &lt;code&gt;format&lt;/code&gt;. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F487h1rsou301ppok0x30.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F487h1rsou301ppok0x30.png" alt="Image description" width="333" height="119"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To use this, you just type this command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm run format&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In addition to the local setup and command-line usage, Prettier can also be integrated into the code editor (such as VSCode) via an extension. Once installed, it automatically formats code upon saving a file.&lt;/p&gt;

&lt;h1&gt;
  
  
  Linter
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;ESLint&lt;/code&gt; is a tool designed to identify and report on patterns in ECMAScript/JavaScript code, aiming to enhance code consistency and minimize bugs. You can install and configure &lt;code&gt;ESLint&lt;/code&gt; using:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm init @eslint/config@latest&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Upon executing this command, a series of questions will guide you through the setup in the terminal, and a new configuration file named &lt;code&gt;.eslint.config.mjs&lt;/code&gt; will be created. Since I am using &lt;code&gt;ESLint&lt;/code&gt; version 9.x, all configurations will be specified within this file. I also listed unnecessary files and folders for &lt;code&gt;ESLint&lt;/code&gt; to ignore using the ignore property.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpz3807qs3d0axgo19p0t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpz3807qs3d0axgo19p0t.png" alt="Image description" width="582" height="575"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To streamline the linting process, I added a script in &lt;code&gt;package.json&lt;/code&gt; for easier execution.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqazmdbquqq2wu7byla5j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqazmdbquqq2wu7byla5j.png" alt="Image description" width="341" height="118"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To run the script, you just type this command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm run lint&lt;/code&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Editor Integration
&lt;/h1&gt;

&lt;p&gt;While the aforementioned setup suffices for individual projects, ensuring a uniform development environment for all contributors is vital in collaborative settings. Therefore, I created a &lt;code&gt;.vscode&lt;/code&gt; folder in the root directory containing two files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;extensions.json&lt;/code&gt;: This file lists the necessary extensions that developers should install when opening the project, providing prompts upon project launch.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;settings.json&lt;/code&gt;: This configuration ensures that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The editor formats code on save using Prettier.&lt;/li&gt;
&lt;li&gt;ESLint automatically fixes linting issues on save.&lt;/li&gt;
&lt;li&gt;ESLint validates JavaScript files.&lt;/li&gt;
&lt;li&gt;Prettier requires a configuration file to format the code.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;By establishing the &lt;code&gt;.vscode&lt;/code&gt; folder with &lt;code&gt;settings.json&lt;/code&gt; and &lt;code&gt;extensions.json&lt;/code&gt;, I ensure that all contributors share a consistent development environment, with &lt;code&gt;Prettier&lt;/code&gt; and &lt;code&gt;ESLint&lt;/code&gt; seamlessly integrated into Visual Studio Code. This setup facilitates automatic code formatting and linting according to the project’s configuration, simplifying the maintenance of code quality and consistency.&lt;/p&gt;

&lt;h1&gt;
  
  
  Addressing Formatting Issues
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;After running &lt;code&gt;Prettier&lt;/code&gt;, I identified a few files that required manual adjustments, primarily adding commas to the last elements of objects. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Following the ESLint run, I discovered 13 issues, most of which were due to the use of the global object &lt;code&gt;process&lt;/code&gt;, which does not require initialization. To resolve this, I added the following comment at the top of the affected files:&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;/* eslint-disable no-undef */&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Additionally, there were some imported values that were not used; for these cases, I simply removed them.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Through my exploration of static analysis tools, I have gained a deeper understanding of their significance, particularly in collaborative projects. The primary goal of these tools is to facilitate teamwork and maintain high code quality, and I now appreciate how to effectively set up a project to integrate these essential tools from the outset.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>git</category>
      <category>prettier</category>
      <category>eslint</category>
    </item>
    <item>
      <title>Open Source, Open Doors: Wrapping Up Hacktoberfest 2024</title>
      <dc:creator>AnhChienVu</dc:creator>
      <pubDate>Mon, 28 Oct 2024 19:39:39 +0000</pubDate>
      <link>https://dev.to/anhchienvu/open-source-open-doors-wrapping-up-hacktoberfest-2024-2foi</link>
      <guid>https://dev.to/anhchienvu/open-source-open-doors-wrapping-up-hacktoberfest-2024-2foi</guid>
      <description>&lt;p&gt;After four consecutive weeks of Hacktoberfest, I’ve finally completed the challenge with four approved PRs. I have to admit, I felt quite nervous at the start of this event, as open-source felt like something outside my comfort zone. However, beginning with smaller issues helped me build confidence, and soon enough, I was motivated to seek out more complex issues to solve. Part of this drive, I think, came from being a student—contributing to a developer community gave me a sense of real-world experience.&lt;/p&gt;

&lt;p&gt;If you’d like to read more about my Hacktoberfest journey, feel free to check out my personal page, where I’ve recorded my reflections after each PR. But without further ado, let’s dive into my final week’s PR.&lt;/p&gt;

&lt;h1&gt;
  
  
  Overview of Mikochi: A Minimalist Remote File Browser
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://github.com/zer0tonin/Mikochi" rel="noopener noreferrer"&gt;Mikochi&lt;/a&gt; is a lightweight, self-hosted remote file browser, ideal for users managing files on private servers or NAS (Network Attached Storage). This tool allows users to easily navigate through remote directories, perform file management tasks like uploading, downloading, renaming, and deleting files, and even stream media files directly to players like VLC or MPV.&lt;/p&gt;

&lt;p&gt;Built with a modern web interface powered by JavaScript/Preact and an API backend in Go/Gin, Mikochi provides a seamless and responsive experience for remote file browsing.&lt;/p&gt;

&lt;h1&gt;
  
  
  What I worked on
&lt;/h1&gt;

&lt;p&gt;In this project, I tackled an &lt;a href="https://github.com/zer0tonin/Mikochi/issues/18" rel="noopener noreferrer"&gt;issue&lt;/a&gt; related to enhance user control and security in Mikochi, I implemented a log-out feature accessible via a "Log Out" button in the application’s navbar. This feature was designed to securely log users out by clearing authentication tokens and redirecting them to the login page. On the frontend, the log-out button triggers an API call to a new &lt;code&gt;/logout&lt;/code&gt; endpoint, clears the JWT from local storage, and refreshes or redirects the page to ensure users are fully logged out. On the backend, I built logic to handle the &lt;code&gt;/logout&lt;/code&gt; endpoint by adding the token ID to a list of invalidated tokens. This list is checked in the JWT authentication middleware, ensuring that any request with an invalidated token is rejected with a 403 response. This addition improved Mikochi's security, helping users control active sessions and protecting their accounts on shared or public devices.&lt;/p&gt;

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

&lt;p&gt;In this &lt;a href="https://github.com/zer0tonin/Mikochi/pull/23" rel="noopener noreferrer"&gt;PR&lt;/a&gt;, I implemented a secure log-out functionality for Mikochi, addressing both frontend and backend requirements to enhance user session control. On the frontend, I created a Logout component to handle the API call to &lt;code&gt;/api/logout&lt;/code&gt; and clear the JWT from local storage. When the user clicks "Log Out," the JWT is removed, a POST request is sent to the backend to validate the log-out, and the page redirects the user to the main login screen using &lt;code&gt;window.location.href&lt;/code&gt;. On the backend, I modified the JWT generation in &lt;code&gt;generateAuthToken()&lt;/code&gt; to include an ID property, allowing tokens to be invalidated effectively. I then added a handler in &lt;code&gt;backend/auth/handlers.go&lt;/code&gt; to manage logout requests by appending the JWT ID to an invalidated token list. The JWT middleware checks each request, denying access if the token has been invalidated and returning a 403 response. To verify the functionality, I conducted manual testing by re-inserting a previously invalidated token into local storage after logout, ensuring that any attempt to reuse it failed, effectively securing the logout process.&lt;/p&gt;

&lt;h1&gt;
  
  
  My Thoughts
&lt;/h1&gt;

&lt;p&gt;For me, this was a fascinating project, blending backend and frontend development into a single project. The issue I addressed on the frontend was relatively straightforward—creating a logout button, with a bit of CSS to improve its appearance. I’ve been learning React for a while, so while I needed a brief refresher, it helped me understand the code and components already in the source.&lt;/p&gt;

&lt;p&gt;On the backend, the project owner had containerized the project using Docker, which was familiar ground for me. The most time-consuming part, however, was working with Go, the backend language for this project. I had no prior experience with Go, but tackling this issue gave me the opportunity to learn it to a functional level, which I found quite rewarding.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>beginners</category>
      <category>hacktoberfest</category>
      <category>programming</category>
    </item>
    <item>
      <title>Open Source Achievement: Completing My Third PR in Hacktoberfest</title>
      <dc:creator>AnhChienVu</dc:creator>
      <pubDate>Fri, 18 Oct 2024 01:19:28 +0000</pubDate>
      <link>https://dev.to/anhchienvu/open-source-achievement-completing-my-third-pr-in-hacktoberfest-42ja</link>
      <guid>https://dev.to/anhchienvu/open-source-achievement-completing-my-third-pr-in-hacktoberfest-42ja</guid>
      <description>&lt;p&gt;Participating in Hacktoberfest has been exciting and rewarding journey for me, as I've had the opportunity to contribute to meaningful open-source projects while honing my skills. This year, for my third Pull Request, I chose to contribute to a project that's more complex than my first 2 PRs, it is the &lt;a href="https://github.com/zero-to-mastery/ZTM-Quest" rel="noopener noreferrer"&gt;ZTM-Quest&lt;/a&gt; repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  ZTM-Quest Overview
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;ZTM-Quest&lt;/strong&gt; is a project that is part of the Zero to Mastery Academy, a platform that helps developers improve their skills and build real-world projects. The project itself is an engaging interactive web app that allows users to complete quests, level up their coding abilities. In this blog, I'll walk you through my experience working on the &lt;strong&gt;ZTM-Quest&lt;/strong&gt; repo, the challenges I faced, what I learned, and how I managed to complete my third PR for &lt;code&gt;Hacktoberfest&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  My Pull Request
&lt;/h2&gt;

&lt;p&gt;This week, I tackled an &lt;a href="https://github.com/zero-to-mastery/ZTM-Quest/issues/3" rel="noopener noreferrer"&gt;issue&lt;/a&gt; focused on enhancing the &lt;code&gt;ZTM-Quest&lt;/code&gt; project by adding additional NPCs (Non-Playable Characters) to &lt;code&gt;map_start&lt;/code&gt;. Currently, the project has only a limited number of NPCs per map, and the maintainers wanted to populate the map_start area further. Fortunately, the project already included some code for generating NPCs randomly, which provided me with a solid starting point. Additionally, the maintainer suggested that I could create custom NPCs using my own spritesheets, but given the complexity and size of the codebase, I decided to proceed with the existing assets to meet the project requirements more efficiently.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ZTM-Quest&lt;/code&gt; project consists of three maps: &lt;code&gt;map_arcade&lt;/code&gt;, &lt;code&gt;map_city&lt;/code&gt;, and &lt;code&gt;map_start&lt;/code&gt;. For this issue, I focused solely on &lt;code&gt;map_start&lt;/code&gt; to align with the maintainer’s request. As someone who has never worked with a 2D game development library before, I initially found the task daunting. However, thanks to the maintainer’s guidance and detailed instructions, I was able to navigate through the codebase and identify the key files and structures related to the task.&lt;/p&gt;

&lt;p&gt;I started by exploring the &lt;code&gt;src/factories/npc.factory.js&lt;/code&gt; file, where the NPC creation logic is defined. From there, I began adding new NPCs by creating a file named &lt;code&gt;randNpcsOnRestroomSinkCouch.gameObject.js&lt;/code&gt; in the &lt;code&gt;src/gameObjects/map_start&lt;/code&gt; directory. My initial implementation placed NPCs in only one location (chairs), but upon further review, I realized that the maintainer intended for them to be distributed more dynamically across the map. To address this, I utilized the search functionality in my code editor to identify other relevant locations, such as sinks, couches, and washrooms.&lt;/p&gt;

&lt;p&gt;Based on the positions of these identified objects on the map, I added two NPCs standing near the sink, two sitting on the couch, and three positioned in the washroom area, thus creating a livelier and more interactive environment in map_start.&lt;br&gt;
&lt;strong&gt;&lt;a href="https://github.com/zero-to-mastery/ZTM-Quest/pull/89" rel="noopener noreferrer"&gt;My PR&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Working on this issue gave me invaluable insight into the open-source community and game development. The ZTM-Quest maintainers have been incredibly supportive and professional, providing timely feedback and resources to help me succeed. When I first joined the project, I was invited to their Discord group, where I could reach out whenever I had questions. This made the experience even more enjoyable and collaborative.&lt;/p&gt;

&lt;p&gt;Overall, this week pushed me out of my comfort zone as I took on a challenge in an area I had no prior experience in—2D game development. Despite the initial learning curve, I gained confidence in navigating unfamiliar codebases and problem-solving within a complex project. As I look forward to completing my final PR for Hacktoberfest, I aim to continue exploring new areas of development to broaden my skill set.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>beginners</category>
      <category>hacktoberfest</category>
    </item>
    <item>
      <title>Hacktoberfest Week 2: Diving Deeper into Code Contributions</title>
      <dc:creator>AnhChienVu</dc:creator>
      <pubDate>Fri, 11 Oct 2024 23:38:17 +0000</pubDate>
      <link>https://dev.to/anhchienvu/hacktoberfest-week-2-diving-deeper-into-code-contributions-12g1</link>
      <guid>https://dev.to/anhchienvu/hacktoberfest-week-2-diving-deeper-into-code-contributions-12g1</guid>
      <description>&lt;p&gt;Greetings, everyone! How’s Hacktoberfest going for you this year? Personally, I’m really enjoying it so far. We’ve now entered the second week of October, which means it’s time for my second pull request (PR) out of the four needed to complete the challenge. This week, I decided to push myself a bit further by contributing to a project’s codebase rather than focusing solely on documentation, as I did in &lt;a href="https://dev.to/anhchienvu/my-first-small-start-in-hacktoberfest-2024-1pa3"&gt;Week 1&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recap of Week 1
&lt;/h2&gt;

&lt;p&gt;In my first PR of Hacktoberfest, I worked on improving project documentation. As a beginner, I wanted to ease into the process with smaller, manageable tasks. However, for this second week, I decided to take on something more technical: contributing to the &lt;a href="https://github.com/jaiyankargupta/GitExplorer" rel="noopener noreferrer"&gt;GitExplorer&lt;/a&gt; project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Overview: GitExplorer
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://jaiyankargupta.github.io/GitExplorer/" rel="noopener noreferrer"&gt;GitExplorer&lt;/a&gt; is a web application that simplifies the discovery and exploration of top GitHub repositories. It allows users to filter repositories based on programming languages, topics, and other sorting criteria. While the UI is still under development, the project offers a valuable learning opportunity for contributors, especially beginners. Many of the issues in the repository are tagged as "good first issue," making it a great place to start.&lt;/p&gt;

&lt;p&gt;For my contribution, I chose to implement a navigation feature that enhances user experience when browsing repositories.&lt;/p&gt;

&lt;h2&gt;
  
  
  Identifying the Problem
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/jaiyankargupta/GitExplorer/issues/2" rel="noopener noreferrer"&gt;Issue&lt;/a&gt;&lt;br&gt;
The project initially had a button for navigating to the next page of repositories, but it lacked a full pagination system. Users couldn't easily navigate back and forth between pages or jump to a specific page, which posed usability issues. I noticed this limitation and requested the project maintainer to assign the issue to me.&lt;/p&gt;

&lt;p&gt;During my time working on the project, one aspect that stood out (and not in a good way) was the way the codebase was structured. The project had all the logic written into a single &lt;code&gt;script.js&lt;/code&gt; file, which could become difficult to maintain as the project grows. While I didn't want to make major changes in this PR—since it focused only on the pagination buttons—I plan to suggest refactoring the code structure in a future issue.&lt;/p&gt;

&lt;p&gt;Actually, before that there is another developer who created a button that navigate to the next page, but it's not good for User experience when they want to navigate back and forth, as well as specific page. Due to this reason, I commented to ask maintainer assign this issue for me. &lt;br&gt;
As the time when I was working on this project, there is one thing that I didn't like about it. It is the way the maintainer structure his code base as he was writing every logic into only 1 &lt;code&gt;script.js&lt;/code&gt; file. However, I don't want to have a lot of fix in this issue because it only ask for pagination buttons. I will create a new issue requesting to re-structure the code base later. &lt;/p&gt;

&lt;h2&gt;
  
  
  New Feature: Enhanced Pagination
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/jaiyankargupta/GitExplorer/pull/19" rel="noopener noreferrer"&gt;My PR&lt;/a&gt;&lt;br&gt;
The existing functionality had only one button to navigate between pages, with 10 repositories displayed per page. My goal was to implement a more user-friendly pagination system with "Previous," "Next," and numbered page buttons. This would allow users to jump directly to the page they're interested in, making the navigation experience smoother.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;GitHub API Constraints&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;While implementing the pagination, I encountered a limitation with GitHub's API, which restricts results to 1,000 items. If a search yields more than 1,000 repositories, attempting to navigate beyond this limit results in an error, returning undefined data. Given the design displays 10 repositories per page, I capped the number of pages at 100 to avoid hitting this issue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code Changes&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;I introduced a new &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; section in &lt;code&gt;script.js&lt;/code&gt; for pagination. This allowed me to add some CSS styles to improve the button aesthetics and user experience.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The core logic for pagination was encapsulated in two new functions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;createButton()&lt;/li&gt;
&lt;li&gt;renderPagination()
These functions dynamically generated the pagination buttons based on the available pages and applied styles to disable the current page button, preventing users from accidentally re-clicking it. I also added hover effects to the buttons for a more polished look.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;CSS Improvements&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;For the pagination design, I made sure that the current page button is disabled and styled differently to indicate its state. Other page buttons received hover effects to enhance interactivity and improve user experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;That wraps up my contribution for Week 2 of Hacktoberfest. I’m now on the lookout for another repository to contribute to next week. Ideally, I’d like to find a project that’s a bit more challenging, so I can continue to push myself and improve my skills progressively.&lt;/p&gt;

&lt;p&gt;Thanks for reading, and I look forward to sharing more about my Hacktoberfest journey!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>beginners</category>
      <category>opensource</category>
      <category>hacktoberfest</category>
    </item>
  </channel>
</rss>
