468 lines
15 KiB
Bash
468 lines
15 KiB
Bash
|
|
#!/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)
|