Vollständige Next.js 15 Portfolio-Website mit: - Blog-System mit 100+ Artikeln - Supabase-Integration - Responsive Design mit Tailwind CSS - TypeScript-Konfiguration - Testing-Setup mit Vitest und Playwright Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
508 lines
13 KiB
Markdown
508 lines
13 KiB
Markdown
# Browser Extensions für Automation & Productivity
|
|
|
|
**Meta-Description:** Browser Extensions entwickeln für Automation. Manifest V3, Content Scripts, Background Workers und Cross-Browser Kompatibilität.
|
|
|
|
**Keywords:** Browser Extension, Chrome Extension, Manifest V3, Content Script, Automation, Web Extension API
|
|
|
|
---
|
|
|
|
## Einführung
|
|
|
|
Browser Extensions erweitern die Browser-Funktionalität direkt. Mit **Manifest V3** und der WebExtensions API können Sie Workflows automatisieren, Daten extrahieren und Produktivität steigern.
|
|
|
|
---
|
|
|
|
## Extension Architecture
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ BROWSER EXTENSION ARCHITECTURE │
|
|
├─────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ Components: │
|
|
│ ├── Manifest (manifest.json) │
|
|
│ │ └── Extension Metadata & Permissions │
|
|
│ │ │
|
|
│ ├── Service Worker (Background) │
|
|
│ │ └── Event Handling, API Calls, State │
|
|
│ │ │
|
|
│ ├── Content Scripts │
|
|
│ │ └── DOM Manipulation auf Web Pages │
|
|
│ │ │
|
|
│ ├── Popup (UI) │
|
|
│ │ └── Toolbar Button Interface │
|
|
│ │ │
|
|
│ └── Options Page │
|
|
│ └── Extension Settings │
|
|
│ │
|
|
│ Communication: │
|
|
│ Content Script ←→ Service Worker ←→ Popup │
|
|
│ (chrome.runtime.sendMessage / onMessage) │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Manifest V3 Setup
|
|
|
|
```json
|
|
// manifest.json
|
|
{
|
|
"manifest_version": 3,
|
|
"name": "Automation Helper",
|
|
"version": "1.0.0",
|
|
"description": "Automate repetitive browser tasks",
|
|
|
|
"permissions": [
|
|
"activeTab",
|
|
"storage",
|
|
"alarms",
|
|
"contextMenus"
|
|
],
|
|
|
|
"host_permissions": [
|
|
"https://*.example.com/*"
|
|
],
|
|
|
|
"background": {
|
|
"service_worker": "background.js",
|
|
"type": "module"
|
|
},
|
|
|
|
"content_scripts": [
|
|
{
|
|
"matches": ["https://*.example.com/*"],
|
|
"js": ["content.js"],
|
|
"css": ["content.css"],
|
|
"run_at": "document_idle"
|
|
}
|
|
],
|
|
|
|
"action": {
|
|
"default_popup": "popup.html",
|
|
"default_icon": {
|
|
"16": "icons/icon16.png",
|
|
"48": "icons/icon48.png",
|
|
"128": "icons/icon128.png"
|
|
}
|
|
},
|
|
|
|
"options_page": "options.html",
|
|
|
|
"icons": {
|
|
"16": "icons/icon16.png",
|
|
"48": "icons/icon48.png",
|
|
"128": "icons/icon128.png"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Service Worker (Background)
|
|
|
|
```typescript
|
|
// background.ts
|
|
chrome.runtime.onInstalled.addListener(() => {
|
|
console.log('Extension installed');
|
|
|
|
// Context Menu erstellen
|
|
chrome.contextMenus.create({
|
|
id: 'extract-data',
|
|
title: 'Daten extrahieren',
|
|
contexts: ['selection']
|
|
});
|
|
|
|
// Default Settings
|
|
chrome.storage.sync.set({
|
|
autoExtract: true,
|
|
extractInterval: 5
|
|
});
|
|
});
|
|
|
|
// Context Menu Handler
|
|
chrome.contextMenus.onClicked.addListener((info, tab) => {
|
|
if (info.menuItemId === 'extract-data' && info.selectionText) {
|
|
processSelectedText(info.selectionText);
|
|
}
|
|
});
|
|
|
|
// Message Handler
|
|
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
if (message.type === 'EXTRACT_PAGE') {
|
|
handleExtraction(message.data)
|
|
.then(result => sendResponse({ success: true, data: result }))
|
|
.catch(error => sendResponse({ success: false, error: error.message }));
|
|
|
|
return true; // Async Response
|
|
}
|
|
|
|
if (message.type === 'GET_SETTINGS') {
|
|
chrome.storage.sync.get(['autoExtract', 'extractInterval'], (settings) => {
|
|
sendResponse(settings);
|
|
});
|
|
return true;
|
|
}
|
|
});
|
|
|
|
// Alarm für wiederkehrende Tasks
|
|
chrome.alarms.create('sync-data', { periodInMinutes: 5 });
|
|
|
|
chrome.alarms.onAlarm.addListener((alarm) => {
|
|
if (alarm.name === 'sync-data') {
|
|
syncDataToServer();
|
|
}
|
|
});
|
|
|
|
async function handleExtraction(data: any) {
|
|
// API Call oder Datenverarbeitung
|
|
const response = await fetch('https://api.example.com/extract', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(data)
|
|
});
|
|
|
|
return response.json();
|
|
}
|
|
|
|
async function syncDataToServer() {
|
|
const { extractedData } = await chrome.storage.local.get('extractedData');
|
|
|
|
if (extractedData && extractedData.length > 0) {
|
|
await fetch('https://api.example.com/sync', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(extractedData)
|
|
});
|
|
|
|
// Clear nach Sync
|
|
await chrome.storage.local.set({ extractedData: [] });
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Content Script
|
|
|
|
```typescript
|
|
// content.ts
|
|
|
|
// DOM Ready
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', init);
|
|
} else {
|
|
init();
|
|
}
|
|
|
|
function init() {
|
|
console.log('Content script loaded on', window.location.href);
|
|
|
|
// UI Element einfügen
|
|
injectUI();
|
|
|
|
// Page Observer für dynamische Inhalte
|
|
observePageChanges();
|
|
|
|
// Auto-Extract wenn aktiviert
|
|
chrome.storage.sync.get('autoExtract', ({ autoExtract }) => {
|
|
if (autoExtract) {
|
|
extractPageData();
|
|
}
|
|
});
|
|
}
|
|
|
|
function injectUI() {
|
|
const container = document.createElement('div');
|
|
container.id = 'extension-helper';
|
|
container.innerHTML = `
|
|
<button id="ext-extract-btn" class="ext-btn">
|
|
Daten extrahieren
|
|
</button>
|
|
`;
|
|
|
|
document.body.appendChild(container);
|
|
|
|
document.getElementById('ext-extract-btn')?.addEventListener('click', () => {
|
|
extractPageData();
|
|
});
|
|
}
|
|
|
|
async function extractPageData() {
|
|
const data = {
|
|
url: window.location.href,
|
|
title: document.title,
|
|
timestamp: new Date().toISOString(),
|
|
content: extractContent()
|
|
};
|
|
|
|
// An Background Script senden
|
|
const response = await chrome.runtime.sendMessage({
|
|
type: 'EXTRACT_PAGE',
|
|
data
|
|
});
|
|
|
|
if (response.success) {
|
|
showNotification('Daten erfolgreich extrahiert!');
|
|
} else {
|
|
showNotification('Fehler: ' + response.error, 'error');
|
|
}
|
|
}
|
|
|
|
function extractContent() {
|
|
// Beispiel: Produkt-Daten extrahieren
|
|
const products: any[] = [];
|
|
|
|
document.querySelectorAll('.product-item').forEach((el) => {
|
|
products.push({
|
|
name: el.querySelector('.product-name')?.textContent?.trim(),
|
|
price: el.querySelector('.price')?.textContent?.trim(),
|
|
image: el.querySelector('img')?.getAttribute('src')
|
|
});
|
|
});
|
|
|
|
return products;
|
|
}
|
|
|
|
function observePageChanges() {
|
|
const observer = new MutationObserver((mutations) => {
|
|
mutations.forEach((mutation) => {
|
|
if (mutation.addedNodes.length > 0) {
|
|
// Neue Elemente verarbeiten
|
|
handleNewElements(mutation.addedNodes);
|
|
}
|
|
});
|
|
});
|
|
|
|
observer.observe(document.body, {
|
|
childList: true,
|
|
subtree: true
|
|
});
|
|
}
|
|
|
|
function handleNewElements(nodes: NodeList) {
|
|
nodes.forEach((node) => {
|
|
if (node instanceof HTMLElement) {
|
|
// Highlight neue Produkte
|
|
const products = node.querySelectorAll('.product-item');
|
|
products.forEach((product) => {
|
|
product.classList.add('ext-highlighted');
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
function showNotification(message: string, type: 'success' | 'error' = 'success') {
|
|
const notification = document.createElement('div');
|
|
notification.className = `ext-notification ext-${type}`;
|
|
notification.textContent = message;
|
|
|
|
document.body.appendChild(notification);
|
|
|
|
setTimeout(() => notification.remove(), 3000);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Popup UI
|
|
|
|
```html
|
|
<!-- popup.html -->
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<link rel="stylesheet" href="popup.css">
|
|
</head>
|
|
<body>
|
|
<div class="popup-container">
|
|
<h1>Automation Helper</h1>
|
|
|
|
<div class="status">
|
|
<span id="status-indicator" class="indicator"></span>
|
|
<span id="status-text">Bereit</span>
|
|
</div>
|
|
|
|
<div class="actions">
|
|
<button id="extract-btn" class="btn primary">
|
|
Aktuelle Seite extrahieren
|
|
</button>
|
|
|
|
<button id="batch-extract-btn" class="btn secondary">
|
|
Batch-Extraktion
|
|
</button>
|
|
</div>
|
|
|
|
<div class="stats">
|
|
<div class="stat">
|
|
<span class="stat-value" id="extracted-count">0</span>
|
|
<span class="stat-label">Extrahiert</span>
|
|
</div>
|
|
<div class="stat">
|
|
<span class="stat-value" id="synced-count">0</span>
|
|
<span class="stat-label">Synchronisiert</span>
|
|
</div>
|
|
</div>
|
|
|
|
<a href="options.html" target="_blank" class="settings-link">
|
|
Einstellungen
|
|
</a>
|
|
</div>
|
|
|
|
<script src="popup.js" type="module"></script>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
```typescript
|
|
// popup.ts
|
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
// Stats laden
|
|
await updateStats();
|
|
|
|
// Event Listeners
|
|
document.getElementById('extract-btn')?.addEventListener('click', extractCurrent);
|
|
document.getElementById('batch-extract-btn')?.addEventListener('click', batchExtract);
|
|
});
|
|
|
|
async function updateStats() {
|
|
const { extractedCount = 0, syncedCount = 0 } = await chrome.storage.local.get([
|
|
'extractedCount',
|
|
'syncedCount'
|
|
]);
|
|
|
|
document.getElementById('extracted-count')!.textContent = extractedCount.toString();
|
|
document.getElementById('synced-count')!.textContent = syncedCount.toString();
|
|
}
|
|
|
|
async function extractCurrent() {
|
|
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
|
|
|
if (!tab.id) return;
|
|
|
|
// Message an Content Script
|
|
const response = await chrome.tabs.sendMessage(tab.id, { type: 'EXTRACT' });
|
|
|
|
if (response.success) {
|
|
setStatus('success', 'Extrahiert!');
|
|
await updateStats();
|
|
} else {
|
|
setStatus('error', 'Fehler');
|
|
}
|
|
}
|
|
|
|
async function batchExtract() {
|
|
const tabs = await chrome.tabs.query({ url: 'https://*.example.com/*' });
|
|
|
|
setStatus('loading', `Extrahiere ${tabs.length} Tabs...`);
|
|
|
|
for (const tab of tabs) {
|
|
if (tab.id) {
|
|
await chrome.tabs.sendMessage(tab.id, { type: 'EXTRACT' });
|
|
}
|
|
}
|
|
|
|
setStatus('success', `${tabs.length} Seiten extrahiert`);
|
|
await updateStats();
|
|
}
|
|
|
|
function setStatus(type: 'success' | 'error' | 'loading', text: string) {
|
|
const indicator = document.getElementById('status-indicator')!;
|
|
const statusText = document.getElementById('status-text')!;
|
|
|
|
indicator.className = `indicator ${type}`;
|
|
statusText.textContent = text;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Storage API
|
|
|
|
```typescript
|
|
// Sync Storage (zwischen Geräten synchronisiert, 100KB Limit)
|
|
await chrome.storage.sync.set({
|
|
settings: {
|
|
autoExtract: true,
|
|
theme: 'dark'
|
|
}
|
|
});
|
|
|
|
const { settings } = await chrome.storage.sync.get('settings');
|
|
|
|
// Local Storage (nur lokal, 5MB Limit)
|
|
await chrome.storage.local.set({
|
|
extractedData: largeDataArray
|
|
});
|
|
|
|
// Storage Change Listener
|
|
chrome.storage.onChanged.addListener((changes, areaName) => {
|
|
for (const [key, { oldValue, newValue }] of Object.entries(changes)) {
|
|
console.log(`${areaName}.${key} changed from`, oldValue, 'to', newValue);
|
|
}
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Cross-Browser Kompatibilität
|
|
|
|
```typescript
|
|
// Browser Detection
|
|
const isChrome = typeof chrome !== 'undefined' && chrome.runtime;
|
|
const isFirefox = typeof browser !== 'undefined';
|
|
|
|
// Unified API
|
|
const browserAPI = isFirefox ? browser : chrome;
|
|
|
|
// Polyfill für Firefox Promise-based API
|
|
function promisify<T>(chromeMethod: (...args: any[]) => void): (...args: any[]) => Promise<T> {
|
|
return (...args) => new Promise((resolve, reject) => {
|
|
chromeMethod(...args, (result: T) => {
|
|
if (chrome.runtime.lastError) {
|
|
reject(chrome.runtime.lastError);
|
|
} else {
|
|
resolve(result);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// webextension-polyfill verwenden
|
|
import browser from 'webextension-polyfill';
|
|
|
|
// Unified API für alle Browser
|
|
const tabs = await browser.tabs.query({ active: true });
|
|
```
|
|
|
|
---
|
|
|
|
## Fazit
|
|
|
|
Browser Extensions bieten:
|
|
|
|
1. **Tiefe Integration**: Direkter DOM-Zugriff
|
|
2. **Automation**: Wiederkehrende Tasks automatisieren
|
|
3. **Produktivität**: Workflows optimieren
|
|
4. **Cross-Platform**: Chrome, Firefox, Edge
|
|
|
|
Mächtige Tools für Power-User und Entwickler.
|
|
|
|
---
|
|
|
|
## Bildprompts
|
|
|
|
1. "Browser with extension icon showing automation, productivity concept"
|
|
2. "Extension popup interacting with webpage, data extraction"
|
|
3. "Multiple browser windows synchronized, cross-browser extension"
|
|
|
|
---
|
|
|
|
## Quellen
|
|
|
|
- [Chrome Extension Documentation](https://developer.chrome.com/docs/extensions/)
|
|
- [Manifest V3 Migration](https://developer.chrome.com/docs/extensions/mv3/intro/)
|
|
- [Firefox WebExtensions](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions)
|
|
- [webextension-polyfill](https://github.com/nicknisi/webextension-polyfill)
|