Browser-Tab-Manager/setup-extension.sh

468 lines
No EOL
15 KiB
Bash
Executable file

#!/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)