diff --git a/AUDIT.md b/AUDIT.md
new file mode 100644
index 0000000..922d1a2
--- /dev/null
+++ b/AUDIT.md
@@ -0,0 +1,381 @@
+# Threads DraftCraft - Detaljan Audit Aplikacije
+
+**Datum:** 2026-03-21
+**Verzija:** 1.0.0
+**Tip aplikacije:** Chrome Extension (Manifest V3)
+**Ukupno linija koda:** ~3,830
+
+---
+
+## 1. PREGLED ARHITEKTURE
+
+### Struktura
+```
+background/background.js (433 LOC) - Service worker, lifecycle, messaging
+content/content.js (1,351 LOC) - DOM manipulacija na threads.com
+content/content.css (446 LOC) - Stilovi za content script
+popup/popup.html (159 LOC) - Popup UI
+popup/popup.js (611 LOC) - Popup kontroler
+popup/popup.css (783 LOC) - Popup stilovi
+manifest.json (47 LOC) - Extension konfiguracija
+```
+
+### Ocjena arhitekture: 7/10
+- Dobra separacija (background/content/popup)
+- Class-based pristup je konzistentan
+- Nema build sistema, lintinga, ni testova
+
+---
+
+## 2. SIGURNOSNI PROBLEMI
+
+### 2.1 KRITIΔNO: innerHTML koriscenje bez sanitizacije
+
+**Fajlovi:** `content/content.js:890`, `content/content.js:1258-1268`, `content/content.js:1309-1324`, `popup/popup.js:548-561`
+
+```javascript
+// content.js:890 - Sort indicator
+statusIndicator.innerHTML = `...${this.sortOrder}...`;
+
+// content.js:1258 - Time indicator fallback
+timeIndicator.innerHTML = `
π
${draft.scheduledTimeStr}
`;
+
+// content.js:1309 - Count badge
+countBadge.innerHTML = `π ${this.drafts.length}`;
+
+// popup.js:548 - Info message
+infoDiv.innerHTML = `π Navigate to Threads.com...
`;
+```
+
+**Rizik:** Dok su `this.sortOrder` i `this.drafts.length` kontrolisane vrijednosti, `draft.scheduledTimeStr` dolazi iz parsiranog DOM sadrzaja Threads.com stranice. Ako bi napadac kontrolisao sadrzaj koji se renderuje na Threads.com (npr. kroz draft tekst koji sadrzi `
diff --git a/popup/popup.js b/popup/popup.js
index 2a00af2..1632ae3 100644
--- a/popup/popup.js
+++ b/popup/popup.js
@@ -21,7 +21,7 @@ class ThreadsDraftCraftPopup {
* Initialize the popup
*/
async init() {
- console.log('[Threads DraftCraft] Popup initialized');
+ // Popup initialized
// Load current settings
await this.loadSettings();
@@ -55,7 +55,7 @@ class ThreadsDraftCraftPopup {
this.settings = { ...this.settings, ...result };
} catch (error) {
- console.error('[Threads DraftCraft] Failed to load settings:', error);
+ // Settings load failed
this.showError('Failed to load settings');
}
}
@@ -66,9 +66,9 @@ class ThreadsDraftCraftPopup {
async saveSettings() {
try {
await chrome.storage.sync.set(this.settings);
- console.log('[Threads DraftCraft] Settings saved:', this.settings);
+ // Settings saved
} catch (error) {
- console.error('[Threads DraftCraft] Failed to save settings:', error);
+ // Settings save failed
this.showError('Failed to save settings');
}
}
@@ -241,7 +241,7 @@ class ThreadsDraftCraftPopup {
this.showNoStatsMessage();
}
} catch (error) {
- console.error('[Threads DraftCraft] Failed to load draft stats:', error);
+ // Draft stats load failed
this.showNoStatsMessage();
}
}
@@ -261,7 +261,7 @@ class ThreadsDraftCraftPopup {
}
if (scheduledDrafts) {
- scheduledDrafts.textContent = stats.totalDrafts || '0';
+ scheduledDrafts.textContent = stats.scheduledDrafts || stats.totalDrafts || '0';
}
if (stats.nextScheduled && nextScheduledContainer && nextDraftText && nextDraftTime) {
@@ -275,140 +275,58 @@ class ThreadsDraftCraftPopup {
/**
- * Handle sort order change
+ * Generic helper: send a message to the active threads.com tab
*/
- async handleSortOrderChange(order) {
- this.settings.sortOrder = order;
- await this.saveSettings();
-
- // Send message to content script
+ async _sendToActiveTab(message) {
try {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (tab && tab.url.includes('threads.com')) {
- await chrome.tabs.sendMessage(tab.id, {
- action: 'changeSortOrder',
- sortOrder: order
- });
+ await chrome.tabs.sendMessage(tab.id, message);
}
} catch (error) {
- console.error('[Threads DraftCraft] Failed to change sort order:', error);
+ // Tab might not have content script
}
-
- this.showSuccess(`Sort order changed to ${order} first`);
}
/**
- * Handle auto sort toggle
+ * Generic toggle handler to reduce duplication
*/
- async handleAutoSortToggle(enabled) {
- this.settings.autoSort = enabled;
+ async _toggleSetting(settingKey, action, value, label) {
+ this.settings[settingKey] = value;
await this.saveSettings();
-
- // Send message to content script
- try {
- const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
- if (tab && tab.url.includes('threads.com')) {
- await chrome.tabs.sendMessage(tab.id, {
- action: 'toggleAutoSort',
- enabled: enabled
- });
- }
- } catch (error) {
- console.error('[Threads DraftCraft] Failed to toggle auto sort:', error);
+ await this._sendToActiveTab({ action, enabled: value });
+ if (typeof value === 'boolean') {
+ this.showSuccess(value ? `${label} enabled` : `${label} disabled`);
+ } else {
+ this.showSuccess(`${label} changed to ${value} first`);
}
-
- this.showSuccess(enabled ? 'Auto sort enabled' : 'Auto sort disabled');
}
- /**
- * Handle time indicators toggle
- */
- async handleTimeIndicatorsToggle(enabled) {
- this.settings.showTimeIndicators = enabled;
+ async handleSortOrderChange(order) {
+ this.settings.sortOrder = order;
await this.saveSettings();
+ await this._sendToActiveTab({ action: 'changeSortOrder', sortOrder: order });
+ this.showSuccess(`Sort order changed to ${order} first`);
+ }
- // Send message to content script
- try {
- const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
- if (tab && tab.url.includes('threads.com')) {
- await chrome.tabs.sendMessage(tab.id, {
- action: 'toggleTimeIndicators',
- enabled: enabled
- });
- }
- } catch (error) {
- console.error('[Threads DraftCraft] Failed to toggle time indicators:', error);
- }
+ async handleAutoSortToggle(enabled) {
+ await this._toggleSetting('autoSort', 'toggleAutoSort', enabled, 'Auto sort');
+ }
- this.showSuccess(enabled ? 'Time indicators enabled' : 'Time indicators disabled');
+ async handleTimeIndicatorsToggle(enabled) {
+ await this._toggleSetting('showTimeIndicators', 'toggleTimeIndicators', enabled, 'Time indicators');
}
- /**
- * Handle draft count toggle
- */
async handleDraftCountToggle(enabled) {
- this.settings.showDraftCount = enabled;
- await this.saveSettings();
-
- // Send message to content script
- try {
- const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
- if (tab && tab.url.includes('threads.com')) {
- await chrome.tabs.sendMessage(tab.id, {
- action: 'toggleDraftCount',
- enabled: enabled
- });
- }
- } catch (error) {
- console.error('[Threads DraftCraft] Failed to toggle draft count:', error);
- }
-
- this.showSuccess(enabled ? 'Draft count enabled' : 'Draft count disabled');
+ await this._toggleSetting('showDraftCount', 'toggleDraftCount', enabled, 'Draft count');
}
- /**
- * Handle sort indicator toggle
- */
async handleSortIndicatorToggle(enabled) {
- this.settings.showSortIndicator = enabled;
- await this.saveSettings();
-
- // Send message to content script
- try {
- const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
- if (tab && tab.url.includes('threads.com')) {
- await chrome.tabs.sendMessage(tab.id, {
- action: 'toggleSortIndicator',
- enabled: enabled
- });
- }
- } catch (error) {
- console.error('[Threads DraftCraft] Failed to toggle sort indicator:', error);
- }
-
- this.showSuccess(enabled ? 'Sort indicator enabled' : 'Sort indicator disabled');
+ await this._toggleSetting('showSortIndicator', 'toggleSortIndicator', enabled, 'Sort indicator');
}
- /**
- * Handle date divider toggle
- */
async handleDateDividerToggle(enabled) {
- this.settings.showDateDivider = enabled;
- await this.saveSettings();
-
- try {
- const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
- if (tab && tab.url.includes('threads.com')) {
- await chrome.tabs.sendMessage(tab.id, {
- action: 'toggleDateDivider',
- enabled: enabled
- });
- }
- } catch (error) {
- console.error('[Threads DraftCraft] Failed to toggle date divider:', error);
- }
-
- this.showSuccess(enabled ? 'Date divider enabled' : 'Date divider disabled');
+ await this._toggleSetting('showDateDivider', 'toggleDateDivider', enabled, 'Date divider');
}
/**
@@ -429,7 +347,7 @@ class ThreadsDraftCraftPopup {
await this.loadDraftStats();
this.showSuccess('Drafts refreshed');
} catch (error) {
- console.error('[Threads DraftCraft] Failed to refresh drafts:', error);
+ // Refresh failed
this.showError('Failed to refresh drafts');
} finally {
this.showLoading(false);
@@ -446,7 +364,7 @@ class ThreadsDraftCraftPopup {
});
window.close();
} catch (error) {
- console.error('[Threads DraftCraft] Failed to open Threads:', error);
+ // Open Threads failed
this.showError('Failed to open Threads.com');
}
}
@@ -462,7 +380,7 @@ class ThreadsDraftCraftPopup {
this.showThreadsNotActiveMessage();
}
} catch (error) {
- console.error('[Threads DraftCraft] Failed to check tab:', error);
+ // Tab check failed
}
}
@@ -482,11 +400,12 @@ class ThreadsDraftCraftPopup {
showError(message) {
const errorMessage = document.getElementById('errorMessage');
const errorText = document.getElementById('errorText');
-
+
if (errorMessage && errorText) {
errorText.textContent = message;
errorMessage.style.display = 'flex';
-
+ errorMessage.focus();
+
// Auto hide after 5 seconds
setTimeout(() => {
this.hideError();
@@ -510,11 +429,11 @@ class ThreadsDraftCraftPopup {
showSuccess(message) {
const successMessage = document.getElementById('successMessage');
const successText = document.getElementById('successText');
-
+
if (successMessage && successText) {
successText.textContent = message;
successMessage.style.display = 'flex';
-
+
// Auto hide after 3 seconds
setTimeout(() => {
this.hideSuccess();
@@ -545,20 +464,10 @@ class ThreadsDraftCraftPopup {
// Show info message
const infoDiv = document.createElement('div');
infoDiv.className = 'info-message';
- infoDiv.innerHTML = `
-
- π Navigate to Threads.com to see draft statistics
-
- `;
+ const innerInfo = document.createElement('div');
+ innerInfo.className = 'info-message-content';
+ innerInfo.textContent = 'Navigate to Threads.com to see draft statistics';
+ infoDiv.appendChild(innerInfo);
const statsSection = document.querySelector('.stats-section');
if (statsSection && !document.querySelector('.info-message')) {