DEV Community

AnhChienVu
AnhChienVu

Posted on

Continuous Progress in Adding new Feature for ImprovedTub

This week marks the beginning of my work on a new feature, as outlined in my previous blog post on Planning Step. The goal is to enhance user experience by introducing a toggle switch button, allowing users to enable or disable the display of detailed information about a played video and its associated channel.

To implement this feature, I began by diving into the foundational files used in developing a Chrome Extension, specially:

  • manifest.json
  • content.js
  • background.js

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.

Exploring the manifest.json file

The manifest.json 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, content_scripts, permission and more. Without a properly configured manifest.json file, the extension simply cannot function.

These fields describes the extension:
Image description

permissions 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:

Image description

The host_permissions field tells that this extension is only dealing with Youtube pages:

Image description

The background 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 background.js is a JavaScript file that will run separately from the web browser thread, the service_worker would not have access to content of web page, but it has capabilities to speak with the extension using the extension messaging system.

Image description

The content_scripts 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.

Image description

The default_popup field tells me what html file will be served as the Extension's UI:

Image description

Adding Switch buton

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

channel_details_button: {
                component: 'switch',
                text: 'Details',
                value: false, // Default state is off
                on: {
                    change: async function (event) {
                        const apiKey = YOUTUBE_API_KEY;
                        const switchElement = event.target.closest('.satus-switch');
                        const isChecked = switchElement && switchElement.dataset.value === 'true';
                        console.log(isChecked)
                        try {
                            const videoId = await getCurrentVideoId();
                            const videoInfo = await getVideoInfo(apiKey, videoId);

                            const channelId = videoInfo.snippet.channelId
                            const channelInfo = await getChannelInfo(apiKey, channelId);

                            chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
                                console.log("Sending message to content.js");
                                chrome.tabs.sendMessage(tabs[0].id, {
                                    action: isChecked ? 'append-channel-info' : 'remove-channel-info',
                                    channelName: channelInfo.channelName,
                                    uploadTime: new Date(videoInfo.snippet.publishedAt).toLocaleString(),
                                    videoCount: channelInfo.videoCount,
                                    customUrl: channelInfo.customUrl
                                })
                            });
                        } catch (error){
                            console.error(error);
                        }
                    }
                }
            }
Enter fullscreen mode Exit fullscreen mode

And it will look as:

Image description

When switch is off:

Image description

When switch is on:

Image description

At this stage, I am unsure how to use environment variables to store the YOUTUBE_API_KEY, as the Chrome Extension project is not built with Node.js, making it impossible to use the dotenv 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 change function, I attempted to fetch video information using the Youtube API via the videoID. Once the data is retrieved, I used the chrome.tabs.query function to send it to content.js, enabling the UI to display the video details

Adding content.js file

To implement the toggle functionality, I added a new JavaScript file under js field in the manifest.json file. This content.js 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.

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

Based on this observation, I decided to create the content.js file within the same directory to maintain consistency.

Adding event listener in background.js file

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

/*--------------------------------------------------------------
# MESSAGE LISTENER
--------------------------------------------------------------*/
chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) {
    //console.log(message);
    //console.log(sender);
    console.log('Message received in background:', message);

    switch (message.action || message.name || message) {
        ...
        case 'store-video-id':
            console.log('Storing video ID:', message.videoId); // Debugging log
            if (message.videoId) {
                chrome.storage.local.set({ videoId: message.videoId }, function() {
                    console.log('Video ID stored:', message.videoId); // Debugging log
                    sendResponse({ status: 'success' });
                });
            } else {
                sendResponse({ status: 'error', message: 'No video ID provided' });
            }
            return true; // Indicates that sendResponse will be called asynchronously

            case 'fetch-new-data':
                chrome.storage.local.get('videoId', async function(result) {
                    const videoId = result.videoId;
                    const apiKey = "AIzaSyA0r8em0ndGCnx6vZu1Tv6T0iyLW4nB1jI"; // Replace with your YouTube Data API key
                    try {
                        const videoInfo = await getVideoInfo(apiKey, videoId);
                        const channelId = videoInfo.snippet.channelId;
                        const channelInfo = await getChannelInfo(apiKey, channelId);

                        chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
                            console.log("Sending message to content.js");
                            chrome.tabs.sendMessage(tabs[0].id, {
                                action: 'append-channel-info',
                                channelName: channelInfo.channelName,
                                uploadTime: new Date(videoInfo.snippet.publishedAt).toLocaleString(),
                                videoCount: channelInfo.videoCount,
                                customUrl: channelInfo.customUrl
                            });
                        });
                    } catch (error) {
                        console.error(error);
                    }
                });
                return true;    

        }
});
Enter fullscreen mode Exit fullscreen mode

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

Conclusion

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.

Top comments (0)