#!/bin/bash set -e # Exit if any command fails SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" # Get the directory where this script is located and store it in SCRIPT_DIR cd "$SCRIPT_DIR" # Change to that directory echo "🚀 Building Browser Tab Manager Extension..." # Build using the existing Dockerfile which already has Flutter set up properly echo "🔨 Building Flutter app using existing container..." # Build the image if it doesn't exist or force rebuild podman build -t browser-tab-manager-builder -f - . << 'EOF' # "podman build" creates a container image # "-t browser-tab-manager-builder" names the image # "-f -" means read the Dockerfile from stdin (the following text) # "." means use current directory as build context # "<< 'EOF'" starts a here document - everything until 'EOF' is the Dockerfile FROM debian:bullseye-slim # Start from a base image: Debian Linux (bullseye version, slim variant) # "FROM" is always the first instruction in a Dockerfile # "debian:bullseye-slim" is a minimal Debian 11 operating system image # Install dependencies RUN apt-get update && apt-get install -y \ # "RUN" executes commands inside the container during build # "apt-get update" refreshes the list of available software packages # "apt-get install -y" installs packages (-y means yes to all prompts) # "\" continues the command on the next line curl \ # Tool for downloading files from the internet git \ # Version control system, needed to download Flutter unzip \ # Tool to extract zip files xz-utils \ # Tool to extract .xz compressed files zip \ # Tool to create zip files libglu1-mesa \ # Graphics library needed by Flutter && rm -rf /var/lib/apt/lists/* # Clean up package lists to make image smaller # "/var/lib/apt/lists/*" contains downloaded package info we no longer need # Create a non-root user RUN useradd -m -u 1000 -s /bin/bash flutter && \ # "useradd" creates a new user account # "-m" creates a home directory for the user # "-u 1000" sets user ID to 1000 (matches most host system users) # "-s /bin/bash" sets bash as the default shell # "flutter" is the username # "&&" means run next command if this succeeds mkdir -p /opt/flutter && \ # Create directory to install Flutter # "/opt" is conventional location for optional software chown -R flutter:flutter /opt/flutter # Change ownership of /opt/flutter to the flutter user # "-R" means recursive (all files and subfolders) # "flutter:flutter" means user:group # Switch to non-root user for Flutter installation USER flutter # All subsequent commands run as the "flutter" user instead of "root" # This is a security best practice # Install Flutter as non-root user RUN git clone https://github.com/flutter/flutter.git /opt/flutter -b stable --depth 1 # "git clone" downloads the Flutter repository # URL: https://github.com/flutter/flutter.git # Destination: /opt/flutter # "-b stable" checks out the stable branch (most reliable version) # "--depth 1" only downloads latest commit (saves space and time) ENV PATH="/opt/flutter/bin:${PATH}" # "ENV" sets environment variables # Add Flutter's bin directory to PATH so Flutter commands can be run from anywhere # "${PATH}" includes the existing PATH value # Pre-download Flutter web tools RUN flutter precache --web # Download the necessary files for Flutter web development # "--web" limits download to just web platform tools # This speeds up future builds # Configure Flutter RUN flutter config --no-analytics && \ # Configure Flutter settings: # "--no-analytics" disables anonymous usage statistics # "&&" run next command flutter config --enable-web # Enable web platform support in Flutter WORKDIR /home/flutter/app # "WORKDIR" sets the working directory for subsequent commands # Any files added or commands run will be relative to this directory EOF # End of the Dockerfile content echo " Building web app..." # Clean up any permission issues first echo " Checking permissions..." if [ -f "pubspec.lock" ]; then # Check if pubspec.lock file exists if [ ! -w "pubspec.lock" ]; then # Check if file is NOT writable by current user echo " Fixing pubspec.lock permissions..." sudo chown $(id -u):$(id -g) pubspec.lock 2>/dev/null || rm -f pubspec.lock # Try to change ownership to current user # "$(id -u)" gets your user ID # "$(id -g)" gets your group ID # If that fails (|| means or), just delete the file fi fi # Run the build in the container with proper user mapping podman run --rm \ # "podman run" starts a container # "--rm" removes container when it exits (clean up automatically) -v "$SCRIPT_DIR:/home/flutter/app:Z" \ # "-v" mounts a volume (shares files between host and container) # "$SCRIPT_DIR" on host maps to "/home/flutter/app" in container # ":Z" adjusts SELinux labels for shared access (needed on some Linux systems) -w /home/flutter/app \ # "-w" sets working directory inside container --user $(id -u):$(id -g) \ # Run container as current host user (not root) # Prevents file permission issues --userns=keep-id \ # Keep user namespace mapping consistent # Makes user IDs match between host and container browser-tab-manager-builder \ # Name of the container image to run bash -c ' # Run a bash command inside the container # Everything between single quotes is the command # Set HOME to writable location export HOME=/tmp/flutter-home # Set HOME environment variable to temporary directory # "export" makes variable available to child processes # Flutter needs a writable home directory export PUB_CACHE=/tmp/pub-cache # Set where Flutter stores downloaded packages # Use temp location since we're running as non-root # Create web platform if needed if [ ! -d "web" ]; then # Check if web directory doesn't exist flutter create . --platforms web > /dev/null 2>&1 # Create Flutter web project structure # "." means in current directory # "> /dev/null 2>&1" hides all output messages fi # Get dependencies flutter pub get # Download all packages specified in pubspec.yaml # "pub get" is Flutter's package manager command # Build for web flutter build web --release # Compile Flutter app to optimized JavaScript/HTML/CSS for web browsers # "--release" creates production-optimized build (smaller, faster) ' if [ $? -ne 0 ]; then # Check if previous command failed # "$?" contains exit code of last command # "-ne 0" means "not equal to 0" (0 means success) echo "❌ Build failed!" exit 1 fi # Fix permissions (files might be owned by container user) if [ -d "build/web" ]; then # Check if build output directory exists sudo chown -R $(id -u):$(id -g) build/ .dart_tool/ pubspec.lock 2>/dev/null || true # Change ownership of build files to current user # Needed because container may have created them as different user # "|| true" means ignore if this fails fi # Create extension directory echo "📦 Packaging extension..." rm -rf extension # Delete old extension folder if it exists mkdir -p extension # Create new extension folder # Copy Flutter build files cp -r build/web/* extension/ # Copy all files from Flutter's web build output to extension folder # These are the compiled HTML/JS/CSS files # Create manifest.json echo "📋 Adding extension files..." cat > extension/manifest.json << 'MANIFEST' # Create manifest.json file for browser extension # This file tells the browser how to load and run the extension { "manifest_version": 3, # Version of manifest format (3 is current standard) "name": "Browser Tab Manager", # Extension name shown in browser "version": "1.0.0", # Extension version number "description": "Manage your browser tabs, bookmarks, and history in a beautiful grid view", # Short description of what extension does "permissions": [ # List of browser APIs this extension needs access to "tabs", # Access to browser tabs (view, create, close tabs) "bookmarks", # Access to bookmarks (read, create, delete) "history", # Access to browsing history "storage" # Access to browser's storage API for saving data ], "action": { # Defines what happens when user clicks extension icon "default_popup": "index.html", # Open this HTML file in a popup window "default_icon": { # Icon sizes for different display contexts "16": "icons/Icon-192.png", "48": "icons/Icon-192.png", "128": "icons/Icon-512.png" } }, "background": { # Background script runs continuously, even when popup is closed "service_worker": "background.js" # JavaScript file that runs in background }, "content_security_policy": { # Security rules for what code can run in extension "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'" # Allow scripts from extension itself and WebAssembly (needed for Flutter) # "self" means only scripts packaged with the extension # "wasm-unsafe-eval" allows WebAssembly execution }, "icons": { # Icons for extension in browser's extension list "16": "icons/Icon-192.png", "48": "icons/Icon-192.png", "128": "icons/Icon-512.png" } } MANIFEST # End of manifest.json content # Create background.js cat > extension/background.js << 'BACKGROUND' # Create background service worker script # This runs continuously and handles events // Background service worker for Browser Tab Manager chrome.runtime.onInstalled.addListener(() => { // Event listener: runs when extension is first installed or updated // "chrome.runtime" is the extension API // "onInstalled.addListener" registers a function to run on install console.log('Browser Tab Manager installed'); // Log message to browser's developer console }); chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { // Listen for messages from other parts of the extension // "request" contains the message data // "sender" identifies who sent the message // "sendResponse" is function to send response back if (request.type === 'keepAlive') { // If message type is 'keepAlive' (heartbeat to keep service worker active) sendResponse({ status: 'alive' }); // Respond confirming we're running } return true; // Return true to indicate we'll send response asynchronously }); chrome.tabs.onCreated.addListener(() => { // Event listener: runs whenever a new tab is created notifyPopup('tabCreated'); // Call function to notify popup of the change }); chrome.tabs.onRemoved.addListener(() => { // Event listener: runs when a tab is closed notifyPopup('tabRemoved'); }); chrome.tabs.onUpdated.addListener(() => { // Event listener: runs when tab is updated (URL change, title change, etc.) notifyPopup('tabUpdated'); }); function notifyPopup(event) { // Function to send messages to the popup window chrome.runtime.sendMessage({ type: 'tabsChanged', event }, () => { // Send message with type and event info // Callback function (empty arrow function) handles response if (chrome.runtime.lastError) { // Check if there was an error sending message return; // If popup isn't open, this will error - just ignore it } }); } BACKGROUND # End of background.js content # Create browser-bridge.js cat > extension/browser-bridge.js << 'BRIDGE' # Create bridge script that connects Flutter app to browser APIs # This makes browser extension APIs available to the Flutter web app window.BrowserAPI = { // Create global object that Flutter can call // "window" is global JavaScript object in browsers async getTabs() { // Async function to get all open tabs // "async" means function returns a Promise (for asynchronous operations) if (typeof chrome !== 'undefined' && chrome.tabs) { // Check if Chrome extension API exists // "typeof" checks variable type // "!== 'undefined'" means "is defined" return new Promise((resolve) => { // Create Promise for async operation // "resolve" is function to call when operation completes chrome.tabs.query({}, (tabs) => { // Query all tabs (empty object {} means no filters) // Callback receives array of tab objects resolve(tabs.map(tab => ({ // Transform browser tab objects to our format // "map" creates new array by transforming each item // "=>" is arrow function syntax id: tab.id.toString(), // Convert tab ID to string title: tab.title, // Tab's page title url: tab.url, // Tab's current URL favicon: tab.favIconUrl || '', // Tab's favicon (small icon), or empty string if none // "||" means "or" - use right side if left is falsy isPinned: tab.pinned, // Whether tab is pinned isActive: tab.active, // Whether this is the currently selected tab windowId: tab.windowId, // ID of browser window containing this tab lastAccessed: new Date().toISOString() // Current timestamp in ISO format // "new Date()" creates current date/time // "toISOString()" converts to standard format }))); }); }); } return []; // If Chrome API not available, return empty array }, async getBookmarks() { // Function to get all bookmarks if (typeof chrome !== 'undefined' && chrome.bookmarks) { return new Promise((resolve) => { chrome.bookmarks.getTree((bookmarkTreeNodes) => { // Get entire bookmark tree structure const bookmarks = []; // Array to collect all bookmarks function traverse(nodes, folder = 'Root') { // Recursive function to walk through bookmark tree // "folder = 'Root'" sets default parameter value for (const node of nodes) { // Loop through each bookmark node // "for...of" iterates # This script builds a BROWSER EXTENSION version of the app. # Uses a temporary container to compile Flutter to JavaScript. # Creates an extension/ folder with all files needed for Chrome/Firefox extension. # Adds manifest.json for browser extension configuration. # Adds background.js for extension service worker. # Adds browser-bridge.js to connect Flutter with browser APIs. # You can then load the extension/ folder in your browser's developer mode. # DIFFERENCE FROM setup.sh: # - setup.sh: Builds for WEB (runs in browser or server with nginx) # - setup-extension.sh: Builds for BROWSER EXTENSION (installable add-on)