Initial commit: LinkSyncServer and LinkSyncExtension projects with complete documentation, models, API endpoints, tests, and extension implementation
This commit is contained in:
449
LinkSyncExtension/design.md
Normal file
449
LinkSyncExtension/design.md
Normal file
@@ -0,0 +1,449 @@
|
||||
# LinkSyncExtension - Design Documentation
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
LinkSyncExtension is a Firefox browser extension that synchronizes bookmarks with LinkSyncServer. It runs as a background service worker with popup and settings interfaces.
|
||||
|
||||
### Component Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ LinkSyncExtension │
|
||||
│ │
|
||||
│ ┌─────────────────┐ ┌─────────────────────────┐ │
|
||||
│ │ Background │ │ Popup UI │ │
|
||||
│ │ Service │ │ (Add/Edit Bookmarks) │ │
|
||||
│ │ Worker │ │ │ │
|
||||
│ └─────────────────┘ └─────────────────────────┘ │
|
||||
│ ┌─────────────────┐ ┌─────────────────────────┐ │
|
||||
│ │ Storage │ │ Settings UI │ │
|
||||
│ │ Manager │ │ (Configuration) │ │
|
||||
│ └─────────────────┘ └─────────────────────────┘ │
|
||||
│ ┌─────────────────┐ ┌─────────────────────────┐ │
|
||||
│ │ Sync Engine │ │ Query Engine │ │
|
||||
│ │ (3 modes) │ │ (Parser + Executor) │ │
|
||||
│ └─────────────────┘ └─────────────────────────┘ │
|
||||
│ ┌─────────────────┐ ┌─────────────────────────┐ │
|
||||
│ │ Bookmark │ │ API Client │ │
|
||||
│ │ Manipulator │ │ (REST calls) │ │
|
||||
│ └─────────────────┘ └─────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌───────────────────────────────────────────┐ │
|
||||
│ │ Browser Bookmarks API │ │
|
||||
│ └───────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
LinkSyncExtension/
|
||||
├── manifest.json # Extension manifest v2
|
||||
├── popup.html # Bookmark add/edit UI
|
||||
├── popup.css # Popup styling
|
||||
├── popup.js # Popup logic
|
||||
├── background.html # Settings page
|
||||
├── background.js # Service worker
|
||||
├── content/
|
||||
│ └── content.js # Content script (optional)
|
||||
├── utils/
|
||||
│ ├── bookmark.js # Bookmark manipulation
|
||||
│ ├── collection.js # Collection management
|
||||
│ ├── query-engine.js # Query parsing/execution
|
||||
│ └── sync.js # Sync logic
|
||||
├── icons/
|
||||
│ ├── icon-48.png # 48x48 icon
|
||||
│ └── icon-96.png # 96x96 icon
|
||||
└── styles/
|
||||
├── base.css # Common styles
|
||||
└── theme.css # Theme variables
|
||||
```
|
||||
|
||||
## Manifest Design
|
||||
|
||||
### manifest.json
|
||||
|
||||
```json
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"name": "LinkSync",
|
||||
"version": "1.0.0",
|
||||
"description": "Sync bookmarks with LinkSyncServer",
|
||||
"permissions": [
|
||||
"bookmarks",
|
||||
"storage",
|
||||
"activeTab",
|
||||
"http://*/*",
|
||||
"https://*/*"
|
||||
],
|
||||
"browser_action": {
|
||||
"default_icon": {
|
||||
"48": "icons/icon-48.png",
|
||||
"96": "icons/icon-96.png"
|
||||
},
|
||||
"default_title": "LinkSync"
|
||||
},
|
||||
"background": {
|
||||
"page": "background.html"
|
||||
},
|
||||
"browser_specific_settings": {
|
||||
"gecko": {
|
||||
"id": "{linksync-id}",
|
||||
"strict_min_version": "109.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Permissions
|
||||
|
||||
- `bookmarks` - Read/write Firefox bookmarks
|
||||
- `storage` - Store settings, API keys, state
|
||||
- `activeTab` - Get current page data
|
||||
- HTTP/HTTPS - API communication
|
||||
|
||||
## Background Worker Design
|
||||
|
||||
### Responsibilities
|
||||
|
||||
1. **Sync Loop**
|
||||
- Check for pending syncs
|
||||
- Compare browser vs server bookmarks
|
||||
- Apply sync mode rules
|
||||
- Handle conflicts
|
||||
|
||||
2. **Event Handlers**
|
||||
- `onMessage` - UI requests
|
||||
- `onInstall` - Initialization
|
||||
- `onUpdate` - Handle version changes
|
||||
|
||||
3. **State Management**
|
||||
- Store collection mapping
|
||||
- Track sync timestamps
|
||||
- Monitor pending changes
|
||||
|
||||
### Code Structure
|
||||
|
||||
```javascript
|
||||
// background.js
|
||||
const Background = {
|
||||
// Constants
|
||||
SYNC_CHECK_INTERVAL: 60000, // 1 minute
|
||||
|
||||
// Storage keys
|
||||
STORAGE: {
|
||||
API_KEY: 'linksync_api_key',
|
||||
COLLECTION: 'linksync_collection',
|
||||
MODE: 'linksync_sync_mode',
|
||||
DELETIONS: 'linksync_deletions',
|
||||
AUTO_SYNC: 'linksync_auto_sync'
|
||||
},
|
||||
|
||||
// Methods
|
||||
init(), // Initialize on install/update
|
||||
checkSync(), // Run sync loop
|
||||
handleSyncAction(), // Process sync actions
|
||||
handleEvent(), // Event handlers
|
||||
sendMessage(), // UI communication
|
||||
authenticate() // Handle auth
|
||||
};
|
||||
```
|
||||
|
||||
### Sync Logic
|
||||
|
||||
```javascript
|
||||
async function handleSync() {
|
||||
const config = await loadConfig();
|
||||
|
||||
// Get browser bookmarks
|
||||
const browserBookmarks = await getBrowserBookmarks();
|
||||
|
||||
// Get server bookmarks via API
|
||||
const serverBookmarks = await fetchServerBookmarks();
|
||||
|
||||
// Apply sync mode
|
||||
const actions = applySyncMode(config.mode, browserBookmarks, serverBookmarks);
|
||||
|
||||
// Process deletions if enabled
|
||||
if (config.deletions) {
|
||||
actions = applyDeletions(actions);
|
||||
}
|
||||
|
||||
// Apply actions
|
||||
await applyActions(actions);
|
||||
|
||||
// Update sync timestamp
|
||||
await saveSyncTimestamp();
|
||||
}
|
||||
```
|
||||
|
||||
### Sync Modes
|
||||
|
||||
| Mode | Browser→Server | Server→Browser |
|
||||
|------|---------------|---------------|
|
||||
| **Bi-directional** | Push | Push |
|
||||
| **Browser Authoritative** | Push | Overwrite |
|
||||
| **Server Authoritative** | Download | Overwrite |
|
||||
|
||||
## Popup Design
|
||||
|
||||
### Components
|
||||
|
||||
1. **Add/Edit Form**
|
||||
- URL (auto-filled)
|
||||
- Title (auto-filled)
|
||||
- Description (auto-filled)
|
||||
- Notes
|
||||
- Tags input
|
||||
- Folder path
|
||||
- Actions (Add, Edit, Delete)
|
||||
|
||||
2. **Bookmark List**
|
||||
- Paginated list of synced bookmarks
|
||||
- Search filter
|
||||
- Select for batch operations
|
||||
|
||||
3. **Collections Panel**
|
||||
- View all collections
|
||||
- Execute query
|
||||
- Create dynamic collection
|
||||
|
||||
4. **Settings Modal**
|
||||
- Server URL
|
||||
- API Key
|
||||
- Collection name
|
||||
- Sync mode
|
||||
- Auto-sync toggle
|
||||
|
||||
### HTML Structure
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="popup.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header -->
|
||||
<header>
|
||||
<h1>LinkSync</h1>
|
||||
</header>
|
||||
|
||||
<!-- Add/Edit Form -->
|
||||
<section id="bookmark-form">
|
||||
<form id="bookmark-form">
|
||||
<input id="url" type="url">
|
||||
<input id="title" type="text">
|
||||
<textarea id="description"></textarea>
|
||||
<textarea id="notes"></textarea>
|
||||
<input id="tags" placeholder="comma-separated">
|
||||
<input id="folder" placeholder="path">
|
||||
<button id="submit">Add Bookmark</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<!-- Bookmark List -->
|
||||
<section id="bookmark-list">
|
||||
<!-- Bookmarks -->
|
||||
</section>
|
||||
|
||||
<!-- Footer with actions -->
|
||||
<footer>
|
||||
<button id="sync-btn">Sync Now</button>
|
||||
<button id="settings-btn">Settings</button>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Storage Design
|
||||
|
||||
### localStorage Keys
|
||||
|
||||
| Key | Type | Description |
|
||||
|-----|------|-------------|
|
||||
| `linksync_api_key` | string | JWT API token |
|
||||
| `linksync_collection` | string | Collection name |
|
||||
| `linksync_sync_mode` | string | Sync mode |
|
||||
| `linksync_deletions` | boolean | Enable deletions |
|
||||
| `linksync_auto_sync` | boolean | Auto-sync toggle |
|
||||
| `linksync_last_sync` | timestamp | Last sync time |
|
||||
| `linksync_pending` | number | Pending changes count |
|
||||
|
||||
### Encrypted Storage
|
||||
|
||||
API keys should be encrypted before storage:
|
||||
|
||||
```javascript
|
||||
async function saveEncryptedKey(key) {
|
||||
const iv = crypto.getRandomValues(new Uint8Array(16));
|
||||
const encrypted = await window.crypto.subtle.encrypt(
|
||||
{ name: "AES-GCM", length: 256 },
|
||||
await window.crypto.subtle.importKey(
|
||||
"raw",
|
||||
encryptionKey,
|
||||
{ name: "AES-GCM" },
|
||||
false
|
||||
),
|
||||
key
|
||||
);
|
||||
// Store iv + encrypted data
|
||||
}
|
||||
```
|
||||
|
||||
## Query Engine Design
|
||||
|
||||
### Query Syntax
|
||||
|
||||
```
|
||||
('term1', 'term2') OR tagA AND tagB XOR url:example.com
|
||||
```
|
||||
|
||||
### Parser
|
||||
|
||||
```javascript
|
||||
class QueryParser {
|
||||
parse(expression) {
|
||||
// Tokenize
|
||||
const tokens = this.tokenize(expression);
|
||||
|
||||
// Build AST
|
||||
const ast = this.buildAST(tokens);
|
||||
|
||||
// Validate
|
||||
this.validate(ast);
|
||||
|
||||
return this.serialize(ast);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Executor
|
||||
|
||||
```javascript
|
||||
class QueryExecutor {
|
||||
async execute(ast) {
|
||||
// Build SQL
|
||||
const sql = this.buildSQL(ast);
|
||||
|
||||
// Execute
|
||||
const result = await fetch(`/api/links/?sql=${sql}`);
|
||||
|
||||
return await result.json();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Client Design
|
||||
|
||||
### REST API Integration
|
||||
|
||||
```javascript
|
||||
const API = {
|
||||
baseUrl: '',
|
||||
headers: {
|
||||
'Authorization': 'Token {key}',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
|
||||
async login() {
|
||||
const response = await fetch(`${this.baseUrl}/api/auth/login/`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ username, password })
|
||||
});
|
||||
const data = await response.json();
|
||||
this.storeApiKey(data.token);
|
||||
return data;
|
||||
},
|
||||
|
||||
async listLinks() {
|
||||
return await this.request('/api/links/');
|
||||
},
|
||||
|
||||
async createLink(link) {
|
||||
return await this.post('/api/links/', link);
|
||||
},
|
||||
|
||||
async executeQuery(expression) {
|
||||
return await this.post('/api/queries/execute/', { expression });
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## UI/UX Design
|
||||
|
||||
### Color Scheme
|
||||
|
||||
```css
|
||||
:root {
|
||||
--primary: #3b82f6;
|
||||
--secondary: #6b7280;
|
||||
--success: #10b981;
|
||||
--warning: #f59e0b;
|
||||
--error: #ef4444;
|
||||
--background: #ffffff;
|
||||
--surface: #f9fafb;
|
||||
--border: #e5e7eb;
|
||||
--text: #111827;
|
||||
--text-secondary: #6b7280;
|
||||
}
|
||||
```
|
||||
|
||||
### Typography
|
||||
|
||||
- Font family: System UI
|
||||
- Base size: 14px
|
||||
- Heading: 18px
|
||||
- Form labels: 12px
|
||||
|
||||
### Responsive Design
|
||||
|
||||
- Mobile-first approach
|
||||
- Breakpoint: 480px for landscape
|
||||
- Touch-friendly tap targets (44px minimum)
|
||||
|
||||
## Security Design
|
||||
|
||||
### API Key Handling
|
||||
|
||||
1. **Storage**
|
||||
- Encrypted in localStorage
|
||||
- Never logged or exposed
|
||||
|
||||
2. **Transmission**
|
||||
- HTTPS preferred
|
||||
- Token in Authorization header
|
||||
- No tokens in URL params
|
||||
|
||||
3. **Validation**
|
||||
- Verify response signatures
|
||||
- Check rate limits
|
||||
- Handle 401/403 gracefully
|
||||
|
||||
### Data Privacy
|
||||
|
||||
- No bookmarks stored locally after sync
|
||||
- API keys user-managed
|
||||
- No telemetry or analytics
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests
|
||||
|
||||
- Sync logic modes
|
||||
- Conflict detection
|
||||
- Query parsing
|
||||
- Storage operations
|
||||
|
||||
### Integration Tests
|
||||
|
||||
- API endpoint calls
|
||||
- Background worker events
|
||||
- Popup communication
|
||||
|
||||
### Manual Testing
|
||||
|
||||
- Add/edit/delete bookmarks
|
||||
- Collection creation
|
||||
- Query execution
|
||||
- Conflict scenarios
|
||||
Reference in New Issue
Block a user