feat(browser): implement browser extension for sending files to DocuElevate

- Add complete browser extension with popup UI and background workers
- Support Chrome, Firefox, Edge, and other Chromium-based browsers
- Include context menu integration for quick file sending
- Add comprehensive documentation for users and administrators
- Update main README and API docs to include browser extension
- Implement secure configuration storage in browser extension storage
- Add SSRF-protected URL upload endpoint integration

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-02-12 03:04:36 +00:00
parent 66f0f3236c
commit 817dc31e12
14 changed files with 1282 additions and 4 deletions
+238
View File
@@ -0,0 +1,238 @@
# DocuElevate Browser Extension
Send files from your browser directly to DocuElevate for processing with a single click.
## Features
- **One-Click File Sending**: Send file URLs from your browser to DocuElevate
- **Context Menu Integration**: Right-click on links or pages to send them to DocuElevate
- **Secure Configuration**: Store your DocuElevate server URL and authentication in the extension
- **Cross-Browser Support**: Compatible with Chrome, Firefox, Edge, and other Chromium-based browsers
- **Minimal Permissions**: Only requests necessary permissions for functionality
- **Status Feedback**: Get immediate feedback on file submission success or errors
## Installation
### Chrome / Edge / Chromium-based Browsers
1. **Download the Extension**:
- Download or clone the DocuElevate repository
- Navigate to the `browser-extension` folder
2. **Load the Extension**:
- Open your browser and go to the extensions page:
- Chrome: `chrome://extensions/`
- Edge: `edge://extensions/`
- Enable "Developer mode" (toggle in the top right)
- Click "Load unpacked"
- Select the `browser-extension` folder
3. **Configure the Extension**:
- Click the DocuElevate icon in your browser toolbar
- Enter your DocuElevate server URL (e.g., `https://docuelevate.example.com`)
- If authentication is enabled, enter your session cookie (optional)
- Click "Save Configuration"
### Firefox
1. **Download the Extension**:
- Download or clone the DocuElevate repository
- Navigate to the `browser-extension` folder
2. **Load the Extension Temporarily**:
- Open Firefox and go to `about:debugging#/runtime/this-firefox`
- Click "Load Temporary Add-on"
- Select the `manifest.json` file in the `browser-extension` folder
3. **Configure the Extension**:
- Click the DocuElevate icon in your browser toolbar
- Enter your DocuElevate server URL
- If authentication is enabled, enter your session cookie (optional)
- Click "Save Configuration"
**Note**: For permanent installation in Firefox, you'll need to sign the extension through Mozilla's add-on portal.
## Usage
### Method 1: Extension Popup
1. Navigate to a page with a file URL (e.g., a PDF, DOCX, image)
2. Click the DocuElevate extension icon
3. Optionally, enter a custom filename
4. Click "Send to DocuElevate"
5. Wait for confirmation that the file was sent
### Method 2: Context Menu
1. Right-click on a link or the current page
2. Select "Send to DocuElevate" from the context menu
3. A notification will confirm the file was sent or show an error
## Configuration
### Server URL
The DocuElevate server URL should point to your DocuElevate instance:
- Format: `https://your-domain.com` or `http://localhost:8000`
- Do not include trailing slashes or API paths
- The extension will automatically append `/api/process-url`
### Session Cookie (Optional)
If your DocuElevate instance has authentication enabled, you need to provide a session cookie:
1. **Get Your Session Cookie**:
- Log in to DocuElevate in your browser
- Open browser DevTools (F12)
- Go to the "Application" or "Storage" tab
- Find "Cookies" in the left sidebar
- Look for a cookie named `session`
- Copy its value
2. **Enter in Extension**:
- Format: `session=your_session_value_here`
- The extension will include this in API requests
**Security Note**: Your session cookie is stored securely in the browser's extension storage. Never share your session cookie with others.
## Supported File Types
The extension can send any URL, but DocuElevate will only process supported file types:
- **Documents**: PDF, DOC, DOCX, XLS, XLSX, PPT, PPTX, TXT, CSV, RTF
- **Images**: JPG, PNG, GIF, BMP, TIFF, WebP, SVG
## Troubleshooting
### "Failed to connect to DocuElevate server"
**Cause**: The extension cannot reach your DocuElevate server.
**Solutions**:
- Verify your server URL is correct
- Check that your DocuElevate server is running
- Ensure there are no firewall or CORS issues
- Try accessing the API directly: `https://your-server.com/api/process-url`
### "Authentication required" or 401 Error
**Cause**: Your DocuElevate instance requires authentication.
**Solutions**:
- Get your session cookie (see Configuration section)
- Enter the session cookie in the extension settings
- Ensure your session hasn't expired (log in again if needed)
### "Unsupported file type"
**Cause**: The URL doesn't point to a supported file type.
**Solution**:
- Verify the URL ends with a supported file extension
- Check that the Content-Type header is set correctly by the server
### "File too large"
**Cause**: The file exceeds the maximum upload size configured in DocuElevate.
**Solutions**:
- Check your DocuElevate `MAX_UPLOAD_SIZE` configuration
- Try a smaller file
- Contact your DocuElevate administrator to increase the limit
## Privacy & Security
### Permissions Explained
The extension requests minimal permissions:
- **activeTab**: To get the URL of the current tab
- **storage**: To save your server URL and session cookie configuration
- **contextMenus**: To add the "Send to DocuElevate" option to right-click menus
- **notifications**: To show success/error notifications
### Data Handling
- **No Data Collection**: The extension does not collect, store, or transmit any data except what you explicitly send to your DocuElevate server
- **Local Configuration**: Your server URL and session cookie are stored locally in your browser
- **Direct Communication**: All API requests go directly from your browser to your DocuElevate server
- **No Third Parties**: No data is sent to third-party services
## Development
### Building from Source
The extension is already in a usable state in the `browser-extension` folder. No build process is required.
### File Structure
```
browser-extension/
├── manifest.json # Extension manifest (Chrome/Firefox compatible)
├── icons/ # Extension icons
│ ├── icon16.png
│ ├── icon32.png
│ ├── icon48.png
│ └── icon128.png
├── popup/ # Extension popup UI
│ ├── popup.html
│ ├── popup.css
│ └── popup.js
└── scripts/ # Background and content scripts
├── background.js # Service worker for background tasks
└── content.js # Content script for page interaction
```
### Testing
1. Load the extension in developer mode
2. Configure it with your local DocuElevate instance
3. Test with various file URLs
4. Check the browser console for any errors
5. Verify files are being processed in DocuElevate
## API Endpoint
The extension uses the DocuElevate URL upload API:
**Endpoint**: `POST /api/process-url`
**Request Body**:
```json
{
"url": "https://example.com/document.pdf",
"filename": "optional-custom-name.pdf"
}
```
**Response**:
```json
{
"task_id": "abc123",
"status": "queued",
"message": "File downloaded from URL and queued for processing",
"filename": "document.pdf",
"size": 1024
}
```
See the [DocuElevate API Documentation](../docs/API.md) for more details.
## License
This extension is part of the DocuElevate project and is licensed under the same terms as the main project.
## Support
For issues, questions, or feature requests:
- Open an issue on the [DocuElevate GitHub repository](https://github.com/christianlouis/DocuElevate/issues)
- Refer to the main [DocuElevate documentation](../docs/)
## Version History
### 1.0.0 (Current)
- Initial release
- Basic URL sending functionality
- Configuration management
- Context menu integration
- Notifications support
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

+38
View File
@@ -0,0 +1,38 @@
{
"manifest_version": 3,
"name": "DocuElevate - Send to Document Processor",
"version": "1.0.0",
"description": "Send files from your browser directly to DocuElevate for processing",
"permissions": [
"activeTab",
"storage",
"contextMenus",
"notifications"
],
"host_permissions": [],
"action": {
"default_popup": "popup/popup.html",
"default_icon": {
"16": "icons/icon16.png",
"32": "icons/icon32.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
},
"icons": {
"16": "icons/icon16.png",
"32": "icons/icon32.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
},
"background": {
"service_worker": "scripts/background.js"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["scripts/content.js"],
"run_at": "document_idle"
}
]
}
+200
View File
@@ -0,0 +1,200 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
font-size: 14px;
line-height: 1.5;
color: #333;
background-color: #f8f9fa;
width: 400px;
min-height: 300px;
}
.container {
padding: 16px;
}
.header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 20px;
padding-bottom: 12px;
border-bottom: 2px solid #e9ecef;
}
.logo {
width: 32px;
height: 32px;
}
h1 {
font-size: 20px;
font-weight: 600;
color: #2c3e50;
}
h2 {
font-size: 16px;
font-weight: 600;
color: #495057;
margin-bottom: 12px;
}
.section {
margin-bottom: 16px;
}
.section.hidden {
display: none;
}
.form-group {
margin-bottom: 16px;
}
label {
display: block;
margin-bottom: 6px;
font-weight: 500;
color: #495057;
}
input[type="url"],
input[type="text"] {
width: 100%;
padding: 8px 12px;
border: 1px solid #ced4da;
border-radius: 4px;
font-size: 14px;
transition: border-color 0.15s ease-in-out;
}
input[type="url"]:focus,
input[type="text"]:focus {
outline: none;
border-color: #4CAF50;
box-shadow: 0 0 0 3px rgba(76, 175, 80, 0.1);
}
small {
display: block;
margin-top: 4px;
color: #6c757d;
font-size: 12px;
}
.btn {
width: 100%;
padding: 10px 16px;
border: none;
border-radius: 4px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.15s ease-in-out;
}
.btn-primary {
background-color: #4CAF50;
color: white;
margin-bottom: 8px;
}
.btn-primary:hover {
background-color: #45a049;
}
.btn-primary:active {
background-color: #3d8b40;
}
.btn-primary:disabled {
background-color: #95c997;
cursor: not-allowed;
}
.btn-secondary {
background-color: #6c757d;
color: white;
}
.btn-secondary:hover {
background-color: #5a6268;
}
.info-box {
background-color: #e7f3ff;
border: 1px solid #b3d9ff;
border-radius: 4px;
padding: 12px;
margin-bottom: 16px;
}
.info-box p {
margin-bottom: 4px;
}
.info-box strong {
color: #0056b3;
}
.url-display {
word-break: break-all;
font-family: 'Monaco', 'Courier New', monospace;
font-size: 12px;
color: #495057;
}
#status-message {
padding: 12px;
border-radius: 4px;
margin-top: 12px;
font-size: 13px;
}
#status-message.success {
background-color: #d4edda;
border: 1px solid #c3e6cb;
color: #155724;
}
#status-message.error {
background-color: #f8d7da;
border: 1px solid #f5c6cb;
color: #721c24;
}
#status-message.info {
background-color: #d1ecf1;
border: 1px solid #bee5eb;
color: #0c5460;
}
.loading {
position: relative;
}
.loading::after {
content: "";
position: absolute;
width: 16px;
height: 16px;
top: 50%;
left: 50%;
margin-top: -8px;
margin-left: -8px;
border: 2px solid #f3f3f3;
border-top: 2px solid #4CAF50;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
+51
View File
@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DocuElevate</title>
<link rel="stylesheet" href="popup.css">
</head>
<body>
<div class="container">
<div class="header">
<img src="../icons/icon48.png" alt="DocuElevate" class="logo">
<h1>DocuElevate</h1>
</div>
<div id="config-section" class="section">
<h2>Configuration</h2>
<div class="form-group">
<label for="server-url">DocuElevate Server URL:</label>
<input type="url" id="server-url" placeholder="https://docuelevate.example.com" required>
</div>
<div class="form-group">
<label for="session-cookie">Session Cookie (optional):</label>
<input type="text" id="session-cookie" placeholder="session=your_session_value">
<small>Required if authentication is enabled</small>
</div>
<button id="save-config" class="btn btn-primary">Save Configuration</button>
</div>
<div id="send-section" class="section hidden">
<h2>Send File to DocuElevate</h2>
<div class="info-box">
<p><strong>Current URL:</strong></p>
<p id="current-url" class="url-display"></p>
</div>
<div class="form-group">
<label for="filename">Filename (optional):</label>
<input type="text" id="filename" placeholder="Leave blank to use URL filename">
</div>
<button id="send-file" class="btn btn-primary">Send to DocuElevate</button>
<button id="show-config" class="btn btn-secondary">Change Settings</button>
</div>
<div id="status-section" class="section hidden">
<div id="status-message"></div>
</div>
</div>
<script src="popup.js"></script>
</body>
</html>
+174
View File
@@ -0,0 +1,174 @@
// Popup script for DocuElevate browser extension
// DOM elements
const configSection = document.getElementById('config-section');
const sendSection = document.getElementById('send-section');
const statusSection = document.getElementById('status-section');
const statusMessage = document.getElementById('status-message');
const serverUrlInput = document.getElementById('server-url');
const sessionCookieInput = document.getElementById('session-cookie');
const filenameInput = document.getElementById('filename');
const currentUrlDisplay = document.getElementById('current-url');
const saveConfigBtn = document.getElementById('save-config');
const sendFileBtn = document.getElementById('send-file');
const showConfigBtn = document.getElementById('show-config');
// Load configuration and current tab URL on popup open
document.addEventListener('DOMContentLoaded', async () => {
// Load saved configuration
const config = await loadConfig();
if (config.serverUrl) {
serverUrlInput.value = config.serverUrl;
}
if (config.sessionCookie) {
sessionCookieInput.value = config.sessionCookie;
}
// Get current tab URL
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
const currentUrl = tabs[0]?.url || '';
currentUrlDisplay.textContent = currentUrl;
// Show appropriate section
if (config.serverUrl) {
showSendSection();
} else {
showConfigSection();
}
});
// Save configuration
saveConfigBtn.addEventListener('click', async () => {
const serverUrl = serverUrlInput.value.trim();
if (!serverUrl) {
showStatus('Please enter a server URL', 'error');
return;
}
// Validate URL format
try {
new URL(serverUrl);
} catch (e) {
showStatus('Invalid server URL format', 'error');
return;
}
const config = {
serverUrl: serverUrl,
sessionCookie: sessionCookieInput.value.trim()
};
await saveConfig(config);
showStatus('Configuration saved successfully!', 'success');
setTimeout(() => {
showSendSection();
}, 1000);
});
// Send file to DocuElevate
sendFileBtn.addEventListener('click', async () => {
const config = await loadConfig();
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
const currentUrl = tabs[0]?.url || '';
if (!currentUrl) {
showStatus('No URL found in current tab', 'error');
return;
}
// Disable button and show loading
sendFileBtn.disabled = true;
sendFileBtn.classList.add('loading');
showStatus('Sending file to DocuElevate...', 'info');
try {
const payload = {
url: currentUrl,
filename: filenameInput.value.trim() || null
};
const headers = {
'Content-Type': 'application/json'
};
// Add session cookie if provided
if (config.sessionCookie) {
headers['Cookie'] = config.sessionCookie;
}
const response = await fetch(`${config.serverUrl}/api/process-url`, {
method: 'POST',
headers: headers,
body: JSON.stringify(payload),
credentials: 'include'
});
const result = await response.json();
if (response.ok) {
showStatus(
`✓ File sent successfully! Task ID: ${result.task_id}\nFilename: ${result.filename}`,
'success'
);
} else {
showStatus(
`Error: ${result.detail || 'Failed to send file'}`,
'error'
);
}
} catch (error) {
showStatus(
`Error: ${error.message || 'Failed to connect to DocuElevate server'}`,
'error'
);
} finally {
sendFileBtn.disabled = false;
sendFileBtn.classList.remove('loading');
}
});
// Show configuration section
showConfigBtn.addEventListener('click', () => {
showConfigSection();
});
// Utility functions
function showConfigSection() {
configSection.classList.remove('hidden');
sendSection.classList.add('hidden');
statusSection.classList.add('hidden');
}
function showSendSection() {
configSection.classList.add('hidden');
sendSection.classList.remove('hidden');
statusSection.classList.add('hidden');
}
function showStatus(message, type) {
statusMessage.textContent = message;
statusMessage.className = type;
statusSection.classList.remove('hidden');
}
async function loadConfig() {
return new Promise((resolve) => {
chrome.storage.sync.get(['serverUrl', 'sessionCookie'], (result) => {
resolve(result);
});
});
}
async function saveConfig(config) {
return new Promise((resolve) => {
chrome.storage.sync.set(config, () => {
resolve();
});
});
}
+113
View File
@@ -0,0 +1,113 @@
// Background service worker for DocuElevate browser extension
// Listen for installation
chrome.runtime.onInstalled.addListener((details) => {
if (details.reason === 'install') {
console.log('DocuElevate extension installed');
// Open options page on first install
chrome.tabs.create({
url: chrome.runtime.getURL('popup/popup.html')
});
} else if (details.reason === 'update') {
console.log('DocuElevate extension updated');
}
});
// Listen for messages from content script or popup
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'SEND_URL') {
handleSendUrl(message.data)
.then(result => sendResponse({ success: true, data: result }))
.catch(error => sendResponse({ success: false, error: error.message }));
return true; // Keep channel open for async response
}
});
// Handle sending URL to DocuElevate
async function handleSendUrl(data) {
const { url, filename, serverUrl, sessionCookie } = data;
if (!url || !serverUrl) {
throw new Error('URL and server URL are required');
}
const headers = {
'Content-Type': 'application/json'
};
if (sessionCookie) {
headers['Cookie'] = sessionCookie;
}
const payload = {
url: url,
filename: filename || null
};
const response = await fetch(`${serverUrl}/api/process-url`, {
method: 'POST',
headers: headers,
body: JSON.stringify(payload),
credentials: 'include'
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({ detail: 'Unknown error' }));
throw new Error(errorData.detail || `HTTP ${response.status}`);
}
return await response.json();
}
// Add context menu item for sending URLs
chrome.runtime.onInstalled.addListener(() => {
chrome.contextMenus.create({
id: 'send-to-docuelevate',
title: 'Send to DocuElevate',
contexts: ['link', 'page']
});
});
// Handle context menu clicks
chrome.contextMenus.onClicked.addListener(async (info, tab) => {
if (info.menuItemId === 'send-to-docuelevate') {
// Get the URL to send (link URL or page URL)
const targetUrl = info.linkUrl || info.pageUrl;
// Load configuration
const config = await new Promise((resolve) => {
chrome.storage.sync.get(['serverUrl', 'sessionCookie'], resolve);
});
if (!config.serverUrl) {
// Open popup to configure
chrome.action.openPopup();
return;
}
// Send the URL
try {
const result = await handleSendUrl({
url: targetUrl,
serverUrl: config.serverUrl,
sessionCookie: config.sessionCookie
});
// Show success notification
chrome.notifications.create({
type: 'basic',
iconUrl: 'icons/icon48.png',
title: 'DocuElevate',
message: `File sent successfully! Task ID: ${result.task_id}`
});
} catch (error) {
// Show error notification
chrome.notifications.create({
type: 'basic',
iconUrl: 'icons/icon48.png',
title: 'DocuElevate Error',
message: `Failed to send file: ${error.message}`
});
}
}
});
+33
View File
@@ -0,0 +1,33 @@
// Content script for DocuElevate browser extension
// This script runs on all web pages to detect file URLs
// and enable communication between page content and the extension
// Listen for messages from the popup
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'GET_PAGE_INFO') {
// Return information about the current page
const pageInfo = {
url: window.location.href,
title: document.title
};
sendResponse(pageInfo);
}
});
// Detect if current page is a direct file link
function isDirectFileUrl(url) {
const fileExtensions = [
'.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx',
'.txt', '.csv', '.rtf', '.jpg', '.jpeg', '.png', '.gif',
'.bmp', '.tiff', '.webp', '.svg'
];
const urlLower = url.toLowerCase();
return fileExtensions.some(ext => urlLower.endsWith(ext));
}
// Add visual indicator for file pages (optional enhancement)
if (isDirectFileUrl(window.location.href)) {
console.log('DocuElevate: Direct file URL detected');
}