Fix 4 P1 and 2 P2 code review findings for FRE-4576

P1 fixes:
- Fix import paths in background/index.ts (./ -> ../lib/)
- Fix Promise-in-string bug in api-client.ts authenticate()
- Add missing background/service_worker key to manifest
- Copy HTML to public/ so Vite places them in dist

P2 fixes:
- Add notifications permission to manifest
- Make showWarningNotification async with proper await

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2026-05-10 11:53:25 -04:00
parent 4a2f6cf0fd
commit 35e9f7e812
5 changed files with 481 additions and 18 deletions

View File

@@ -3,12 +3,16 @@
"name": "ShieldAI - Phishing & Spam Protection",
"version": "0.1.0",
"description": "Real-time phishing detection and spam protection powered by ShieldAI",
"background": {
"service_worker": "background.js"
},
"permissions": [
"activeTab",
"storage",
"tabs",
"scripting",
"declarativeNetRequest"
"declarativeNetRequest",
"notifications"
],
"host_permissions": [
"https://*/*",

View File

@@ -0,0 +1,189 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ShieldAI Options</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 14px;
color: #1f2937;
background: #f9fafb;
padding: 32px;
max-width: 640px;
margin: 0 auto;
}
h1 { font-size: 24px; margin-bottom: 4px; }
.subtitle { color: #6b7280; margin-bottom: 32px; }
.section {
background: white;
border-radius: 12px;
padding: 24px;
margin-bottom: 24px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.section-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 1px solid #e5e7eb;
}
.form-group { margin-bottom: 16px; }
.form-group:last-child { margin-bottom: 0; }
label {
display: block;
font-size: 13px;
font-weight: 500;
margin-bottom: 6px;
color: #374151;
}
input[type="text"], input[type="password"], input[type="url"] {
width: 100%;
padding: 10px 12px;
border: 1px solid #d1d5db;
border-radius: 8px;
font-size: 14px;
outline: none;
transition: border-color 0.2s;
}
input:focus { border-color: #3b82f6; }
.checkbox-group {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 0;
}
.checkbox-group input[type="checkbox"] {
width: 18px;
height: 18px;
accent-color: #3b82f6;
}
.checkbox-group label { margin-bottom: 0; cursor: pointer; }
.btn {
padding: 10px 20px;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: opacity 0.2s;
}
.btn:hover { opacity: 0.9; }
.btn-primary { background: #3b82f6; color: white; }
.btn-secondary { background: #f3f4f6; color: #374151; }
.btn-danger { background: #ef4444; color: white; }
.btn-group { display: flex; gap: 8px; margin-top: 16px; }
.domain-list {
list-style: none;
padding: 0;
}
.domain-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
background: #f9fafb;
border-radius: 6px;
margin-bottom: 4px;
}
.domain-remove {
background: none;
border: none;
color: #ef4444;
cursor: pointer;
font-size: 16px;
padding: 0 4px;
}
.add-domain-row {
display: flex;
gap: 8px;
margin-top: 8px;
}
.add-domain-row input { flex: 1; }
.toast {
position: fixed;
bottom: 24px;
right: 24px;
background: #10b981;
color: white;
padding: 12px 20px;
border-radius: 8px;
font-size: 14px;
font-weight: 500;
opacity: 0;
transition: opacity 0.3s;
}
.toast.show { opacity: 1; }
</style>
</head>
<body>
<h1>🛡️ ShieldAI Options</h1>
<p class="subtitle">Configure your phishing & spam protection</p>
<div class="section">
<div class="section-title">Connection</div>
<div class="form-group">
<label for="api-url">API Base URL</label>
<input type="url" id="api-url" value="https://api.shieldai.com" placeholder="https://api.shieldai.com">
</div>
<div class="form-group">
<label for="auth-token">Auth Token (optional)</label>
<input type="password" id="auth-token" placeholder="Bearer token for ShieldAI account">
</div>
</div>
<div class="section">
<div class="section-title">Protection Settings</div>
<div class="checkbox-group">
<input type="checkbox" id="enabled" checked>
<label for="enabled">Enable protection</label>
</div>
<div class="checkbox-group">
<input type="checkbox" id="active-blocking">
<label for="active-blocking">Active blocking (Plus tier)</label>
</div>
<div class="checkbox-group">
<input type="checkbox" id="darkwatch-enabled">
<label for="darkwatch-enabled">DarkWatch credential exposure checks (Plus tier)</label>
</div>
<div class="checkbox-group">
<input type="checkbox" id="spam-enabled" checked>
<label for="spam-enabled">Spam protection</label>
</div>
<div class="checkbox-group">
<input type="checkbox" id="notifications" checked>
<label for="notifications">Show notifications</label>
</div>
</div>
<div class="section">
<div class="section-title">Blocked Domains</div>
<ul class="domain-list" id="blocked-domains"></ul>
<div class="add-domain-row">
<input type="text" id="new-blocked-domain" placeholder="example.com">
<button class="btn btn-secondary" id="add-blocked">Add</button>
</div>
</div>
<div class="section">
<div class="section-title">Allowed Domains (Whitelist)</div>
<ul class="domain-list" id="allowed-domains"></ul>
<div class="add-domain-row">
<input type="text" id="new-allowed-domain" placeholder="example.com">
<button class="btn btn-secondary" id="add-allowed">Add</button>
</div>
</div>
<div class="btn-group">
<button class="btn btn-primary" id="save-btn">Save Settings</button>
<button class="btn btn-secondary" id="reset-btn">Reset to Defaults</button>
</div>
<div class="toast" id="toast">Settings saved!</div>
<script src="options.js"></script>
</body>
</html>

View File

@@ -0,0 +1,271 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ShieldAI Protection</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
width: 360px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 14px;
color: #1f2937;
background: #f9fafb;
}
.header {
background: linear-gradient(135deg, #1e40af, #3b82f6);
color: white;
padding: 16px;
display: flex;
align-items: center;
gap: 10px;
}
.header h1 { font-size: 16px; font-weight: 600; }
.shield-icon { font-size: 24px; }
.status-section {
padding: 16px;
background: white;
border-bottom: 1px solid #e5e7eb;
}
.status-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.status-row:last-child { margin-bottom: 0; }
.status-label { color: #6b7280; font-size: 13px; }
.status-value { font-weight: 600; }
.status-value.safe { color: #22c55e; }
.status-value.warning { color: #f59e0b; }
.status-value.danger { color: #ef4444; }
.toggle-container {
display: flex;
align-items: center;
gap: 8px;
}
.toggle {
position: relative;
width: 44px;
height: 24px;
background: #d1d5db;
border-radius: 12px;
cursor: pointer;
transition: background 0.2s;
}
.toggle.active { background: #3b82f6; }
.toggle::after {
content: '';
position: absolute;
top: 2px;
left: 2px;
width: 20px;
height: 20px;
background: white;
border-radius: 50%;
transition: transform 0.2s;
}
.toggle.active::after { transform: translateX(20px); }
.stats-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
padding: 16px;
background: white;
border-bottom: 1px solid #e5e7eb;
}
.stat-card {
background: #f3f4f6;
border-radius: 8px;
padding: 12px;
text-align: center;
}
.stat-number { font-size: 24px; font-weight: 700; color: #1e40af; }
.stat-label { font-size: 11px; color: #6b7280; margin-top: 4px; text-transform: uppercase; letter-spacing: 0.5px; }
.features-section {
padding: 16px;
background: white;
border-bottom: 1px solid #e5e7eb;
}
.section-title {
font-size: 12px;
font-weight: 600;
color: #6b7280;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 12px;
}
.feature-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid #f3f4f6;
}
.feature-item:last-child { border-bottom: none; }
.feature-name { font-size: 13px; }
.tier-badge {
font-size: 10px;
padding: 2px 6px;
border-radius: 4px;
font-weight: 600;
text-transform: uppercase;
}
.tier-badge.basic { background: #e0e7ff; color: #4338ca; }
.tier-badge.plus { background: #fef3c7; color: #92400e; }
.tier-badge.premium { background: #dcfce7; color: #166534; }
.tier-badge.locked { background: #f3f4f6; color: #9ca3af; }
.actions-section {
padding: 16px;
display: flex;
flex-direction: column;
gap: 8px;
}
.btn {
padding: 10px 16px;
border: none;
border-radius: 8px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
text-align: center;
transition: opacity 0.2s;
}
.btn:hover { opacity: 0.9; }
.btn-primary { background: #3b82f6; color: white; }
.btn-secondary { background: #f3f4f6; color: #374151; }
.btn-danger { background: #ef4444; color: white; }
.report-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
}
.last-threat {
padding: 12px 16px;
background: #fef2f2;
border-radius: 8px;
margin: 0 16px 16px;
font-size: 12px;
color: #991b1b;
}
.last-threat strong { display: block; margin-bottom: 4px; }
.blocked-overlay {
position: fixed;
inset: 0;
background: rgba(255, 255, 255, 0.98);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 1000;
}
.blocked-overlay h2 { font-size: 28px; color: #ef4444; margin-bottom: 8px; }
.blocked-overlay p { color: #6b7280; margin-bottom: 24px; }
.blocked-url {
background: #f3f4f6;
padding: 8px 16px;
border-radius: 6px;
font-family: monospace;
font-size: 12px;
margin-bottom: 24px;
max-width: 90%;
word-break: break-all;
}
.hidden { display: none !important; }
</style>
</head>
<body>
<div id="blocked-view" class="blocked-overlay hidden">
<span class="shield-icon" style="font-size: 48px;">🛡️</span>
<h2>Page Blocked</h2>
<p>ShieldAI detected a potential threat</p>
<div class="blocked-url" id="blocked-url"></div>
<div style="display: flex; gap: 12px;">
<button class="btn btn-primary" id="continue-btn">Continue Anyway</button>
<button class="btn btn-secondary" id="back-btn">Go Back</button>
</div>
</div>
<div id="popup-view">
<div class="header">
<span class="shield-icon">🛡️</span>
<div>
<h1>ShieldAI</h1>
<div style="font-size: 11px; opacity: 0.8;">Phishing & Spam Protection</div>
</div>
</div>
<div class="status-section">
<div class="status-row">
<span class="status-label">Protection</span>
<div class="toggle-container">
<span id="status-text" class="status-value safe">Active</span>
<div class="toggle active" id="protection-toggle"></div>
</div>
</div>
<div class="status-row">
<span class="status-label">Account</span>
<span id="account-status" class="status-value">Guest</span>
</div>
<div class="status-row">
<span class="status-label">Tier</span>
<span id="tier-badge" class="tier-badge basic">Basic</span>
</div>
</div>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-number" id="threats-count">0</div>
<div class="stat-label">Threats Blocked</div>
</div>
<div class="stat-card">
<div class="stat-number" id="urls-count">0</div>
<div class="stat-label">URLs Checked</div>
</div>
</div>
<div id="last-threat" class="last-threat hidden">
<strong>⚠️ Last Threat</strong>
<span id="threat-description"></span>
</div>
<div class="features-section">
<div class="section-title">Active Features</div>
<div class="feature-item">
<span class="feature-name">URL Analysis</span>
<span class="tier-badge basic">Active</span>
</div>
<div class="feature-item">
<span class="feature-name">Spam Detection</span>
<span class="tier-badge basic">Active</span>
</div>
<div class="feature-item">
<span class="feature-name">Active Blocking</span>
<span id="blocking-badge" class="tier-badge locked">Plus+</span>
</div>
<div class="feature-item">
<span class="feature-name">DarkWatch Integration</span>
<span id="darkwatch-badge" class="tier-badge locked">Plus+</span>
</div>
<div class="feature-item">
<span class="feature-name">Real-time Scanning</span>
<span id="realtime-badge" class="tier-badge locked">Premium</span>
</div>
</div>
<div class="actions-section">
<button class="btn btn-danger report-btn" id="report-btn">
<span></span> Report Phishing
</button>
<div style="display: flex; gap: 8px;">
<button class="btn btn-secondary" style="flex: 1;" id="options-btn">Options</button>
<button class="btn btn-secondary" style="flex: 1;" id="login-btn">Login</button>
</div>
</div>
</div>
<script src="popup.js"></script>
</body>
</html>

View File

@@ -1,8 +1,8 @@
import { UrlCheckResult, UrlVerdict, ThreatInfo, BackgroundMessage, MessageType, SubscriptionTier, PhishingReport } from '../types';
import { urlCache, CACHE_TTL } from './cache';
import { phishingDetector } from './phishing-detector';
import { settingsManager } from './settings';
import { shieldApiClient } from './api-client';
import { UrlCheckResult, UrlVerdict, ThreatInfo, BackgroundMessage, MessageType, SubscriptionTier, PhishingReport, ExtensionSettings } from '../types';
import { urlCache, CACHE_TTL } from '../lib/cache';
import { phishingDetector } from '../lib/phishing-detector';
import { settingsManager } from '../lib/settings';
import { shieldApiClient } from '../lib/api-client';
let threatsBlockedToday = 0;
let urlsCheckedToday = 0;
@@ -140,17 +140,15 @@ async function showBlockedPage(tabId: number, url: string): Promise<void> {
await chrome.tabs.update(tabId, { url: blockedUrl });
}
function showWarningNotification(result: UrlCheckResult): void {
const showNotif = settingsManager.get().then(s => s.showNotifications);
Promise.resolve(showNotif).then((enabled) => {
if (!enabled) return;
chrome.notifications.create({
type: 'basic',
iconUrl: 'icons/icon48.png',
title: 'ShieldAI Warning',
message: `${result.verdict.toUpperCase()}: ${result.domain}`,
priority: result.verdict === UrlVerdict.PHISHING ? 2 : 0,
});
async function showWarningNotification(result: UrlCheckResult): Promise<void> {
const settings = await settingsManager.get();
if (!settings.showNotifications) return;
await chrome.notifications.create({
type: 'basic',
iconUrl: 'icons/icon48.png',
title: 'ShieldAI Warning',
message: `${result.verdict.toUpperCase()}: ${result.domain}`,
priority: result.verdict === UrlVerdict.PHISHING ? 2 : 0,
});
}

View File

@@ -88,7 +88,8 @@ export class ShieldApiClient {
async authenticate(apiKey: string): Promise<{ userId: string; tier: SubscriptionTier } | null> {
try {
const response = await fetch(`${settingsManager.get().then(s => s.apiBaseUrl)}/extension/auth`, {
const settings = await settingsManager.get();
const response = await fetch(`${settings.apiBaseUrl}/extension/auth`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',