gaurv007 commited on
Commit
f0fb36b
·
verified ·
1 Parent(s): d08dca4

v3.0: Fix extension - send {text} not {clauses} to API, add rate limit handling

Browse files
Files changed (1) hide show
  1. extension/background.js +1 -250
extension/background.js CHANGED
@@ -1,250 +1 @@
1
- /**
2
- * ClauseGuard — Background Service Worker
3
- * Full website↔extension bridge: auto-detect login, sync user data,
4
- * save scans to DB, guest mode fallback.
5
- */
6
-
7
- const API_BASE = "https://gaurv007-clauseguard-api.hf.space";
8
- const FREE_SCANS_PER_MONTH = 10;
9
- const API_TIMEOUT_MS = 45000;
10
-
11
- // Website URLs (for auth detection)
12
- const SITE_ORIGINS = [
13
- "https://clauseguardweb.netlify.app",
14
- "https://clauseguardweb.netlify.app",
15
- ];
16
- // Add your Netlify URL here after deploy:
17
- // SITE_ORIGINS.push("https://your-site.netlify.app");
18
-
19
- try { chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: false }); } catch(e) {}
20
-
21
- // ─── Fetch with timeout ───
22
- async function fetchWithTimeout(url, options, timeoutMs) {
23
- const controller = new AbortController();
24
- const timer = setTimeout(() => controller.abort(), timeoutMs);
25
- try {
26
- const resp = await fetch(url, { ...options, signal: controller.signal });
27
- clearTimeout(timer);
28
- return resp;
29
- } catch (err) { clearTimeout(timer); throw err; }
30
- }
31
-
32
- // ─── Message Router ───
33
- chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
34
- const handle = async () => {
35
- switch (message.type) {
36
- case "ANALYZE_TEXT": return await handleAnalyze(message.payload, sender.tab?.id);
37
- case "GET_AUTH": return await getAuth();
38
- case "GET_USER": return await getUser();
39
- case "CHECK_USAGE": return await checkUsage();
40
- case "OPEN_SIDEPANEL": if (sender.tab?.id) chrome.sidePanel.open({ tabId: sender.tab.id }); return { ok: true };
41
- case "GET_RESULTS": return await getStoredResults(sender.tab?.id || message.tabId);
42
- case "SYNC_AUTH": return await syncAuthFromWebsite(); // Manual sync trigger
43
- case "GET_SCAN_HISTORY": return await getScanHistory();
44
- default: return null;
45
- }
46
- };
47
- handle().then(sendResponse).catch(err => sendResponse({ error: err.message }));
48
- return true;
49
- });
50
-
51
- // ─── External messages from website ───
52
- chrome.runtime.onMessageExternal.addListener((message, sender, sendResponse) => {
53
- // Accept from any allowed origin (clauseguardweb.netlify.app, netlify, localhost)
54
- const handle = async () => {
55
- switch (message.type) {
56
- case "SET_AUTH": {
57
- await chrome.storage.sync.set({
58
- authToken: message.token,
59
- plan: message.plan || "free",
60
- email: message.email || "",
61
- userName: message.name || "",
62
- userId: message.userId || "",
63
- authSource: "website",
64
- lastSyncAt: Date.now(),
65
- });
66
- return { success: true };
67
- }
68
- case "LOGOUT": {
69
- await chrome.storage.sync.remove(["authToken", "plan", "email", "userName", "userId", "authSource", "lastSyncAt"]);
70
- return { success: true };
71
- }
72
- case "GET_STATUS": {
73
- const auth = await getAuth();
74
- const usage = await checkUsage();
75
- return { auth, usage };
76
- }
77
- case "PING": {
78
- return { installed: true, version: chrome.runtime.getManifest().version };
79
- }
80
- default: return null;
81
- }
82
- };
83
- handle().then(sendResponse).catch(err => sendResponse({ error: err.message }));
84
- return true;
85
- });
86
-
87
- // Auth sync is handled by:
88
- // 1. Website's ExtensionBridge component sends postMessage on auth change
89
- // 2. Content script (content.js) picks it up via window.addEventListener("message")
90
- // 3. Content script writes to chrome.storage.sync
91
- // No injection needed — this is the reliable path.
92
-
93
- // ─── Core: Analyze ───
94
- async function handleAnalyze(payload, tabId) {
95
- const usage = await checkUsage();
96
- if (!usage.allowed) {
97
- return { error: "limit_reached", message: "Free scan limit reached.", usage };
98
- }
99
-
100
- const { text, url } = payload;
101
- const clauses = splitIntoClauses(text);
102
- if (clauses.length === 0) {
103
- return { error: "no_clauses", message: "No analyzable clauses found." };
104
- }
105
-
106
- let results;
107
- try {
108
- const auth = await getAuth();
109
- const resp = await fetchWithTimeout(`${API_BASE}/api/analyze`, {
110
- method: "POST",
111
- headers: {
112
- "Content-Type": "application/json",
113
- ...(auth.token ? { Authorization: `Bearer ${auth.token}` } : {}),
114
- },
115
- body: JSON.stringify({ clauses, source_url: url }),
116
- }, API_TIMEOUT_MS);
117
-
118
- if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
119
- results = await resp.json();
120
- results.source = "api";
121
- } catch (err) {
122
- console.warn("API unavailable, using local:", err.message);
123
- results = localAnalyze(text);
124
- results.source = "local";
125
- }
126
-
127
- // Store results
128
- if (tabId) {
129
- await chrome.storage.local.set({ [`results_${tabId}`]: results });
130
- const flagged = results.results?.filter(r => r.categories?.length > 0).length || 0;
131
- chrome.action.setBadgeText({ text: flagged > 0 ? String(flagged) : "", tabId });
132
- if (flagged > 0) chrome.action.setBadgeBackgroundColor({ color: flagged > 3 ? "#ef4444" : "#f59e0b", tabId });
133
- }
134
-
135
- // Save scan to history (local + server if logged in)
136
- const scanRecord = {
137
- url: url || "",
138
- risk_score: results.risk_score,
139
- grade: results.grade,
140
- flagged_count: results.flagged_count,
141
- total_clauses: results.total_clauses,
142
- source: results.source,
143
- scanned_at: Date.now(),
144
- };
145
-
146
- // Save to local history (always, even for guests)
147
- const { scanHistory = [] } = await chrome.storage.local.get("scanHistory");
148
- scanHistory.unshift(scanRecord);
149
- if (scanHistory.length > 50) scanHistory.length = 50; // Keep last 50
150
- await chrome.storage.local.set({ scanHistory });
151
-
152
- await incrementUsage();
153
- return results;
154
- }
155
-
156
- // ─── Get scan history (for sidepanel) ───
157
- async function getScanHistory() {
158
- const { scanHistory = [] } = await chrome.storage.local.get("scanHistory");
159
- return { history: scanHistory };
160
- }
161
-
162
- // ─── Sync auth from website (called manually or on install) ───
163
- async function syncAuthFromWebsite() {
164
- // This is triggered by content script when it detects CLAUSEGUARD_AUTH_SYNC message
165
- return await getAuth();
166
- }
167
-
168
- // ─── Local fallback ───
169
- function localAnalyze(text) {
170
- const clauses = splitIntoClauses(text);
171
- const patterns = {
172
- 0: [/arbitrat/i, /binding arbitration/i, /waive.*right.*court/i],
173
- 1: [/sole discretion/i, /reserves? the right to (modify|change|update)/i, /at any time.*without.*notice/i],
174
- 2: [/remove.*content.*without/i, /right to remove/i],
175
- 3: [/exclusive jurisdiction/i, /courts? of.*(california|delaware|new york|ireland)/i, /submit to.*jurisdiction/i],
176
- 4: [/governed by.*laws? of/i, /shall be governed/i],
177
- 5: [/not liable/i, /shall not be (liable|responsible)/i, /in no event.*liable/i, /limitation of liability/i, /without warranty/i],
178
- 6: [/terminat.*at any time/i, /suspend.*account.*without/i, /we may (terminat|suspend)/i],
179
- 7: [/by (using|accessing).*you agree/i, /continued use.*constitutes/i],
180
- };
181
- const names = ["Arbitration","Unilateral change","Content removal","Jurisdiction","Choice of law","Limitation of liability","Unilateral termination","Contract by using"];
182
- const sevMap = {0:"HIGH",1:"MEDIUM",2:"MEDIUM",3:"MEDIUM",4:"MEDIUM",5:"HIGH",6:"HIGH",7:"LOW"};
183
-
184
- const results = clauses.map(clause => {
185
- const categories = [];
186
- for (const [id, pats] of Object.entries(patterns)) {
187
- if (pats.some(p => p.test(clause))) categories.push({ name: names[id], severity: sevMap[id] });
188
- }
189
- return { text: clause, categories };
190
- });
191
-
192
- const flagged = results.filter(r => r.categories.length > 0);
193
- const sev = { HIGH: 0, MEDIUM: 0, LOW: 0 };
194
- flagged.forEach(r => r.categories.forEach(c => sev[c.severity]++));
195
- const risk = Math.min(100, Math.round((sev.HIGH*20 + sev.MEDIUM*10 + sev.LOW*5) / Math.max(1, clauses.length) * 100));
196
-
197
- return {
198
- risk_score: risk,
199
- grade: risk >= 60 ? "F" : risk >= 40 ? "D" : risk >= 20 ? "C" : risk >= 10 ? "B" : "A",
200
- total_clauses: clauses.length, flagged_count: flagged.length, results,
201
- };
202
- }
203
-
204
- // ─── Utils ───
205
- function splitIntoClauses(text) {
206
- return text.replace(/\n{2,}/g, "\n").trim()
207
- .split(/(?<=[.!?])\s+(?=[A-Z0-9(])|(?:\n)(?=\d+[.)]\s|\([a-z]\)\s)/)
208
- .map(c => c.trim()).filter(c => c.length > 30);
209
- }
210
-
211
- async function getAuth() {
212
- return new Promise(r => chrome.storage.sync.get(
213
- ["authToken","plan","email","userName","userId","authSource","lastSyncAt"], d => r({
214
- token: d.authToken||null, plan: d.plan||"free", email: d.email||null,
215
- name: d.userName||null, userId: d.userId||null,
216
- isLoggedIn: !!d.authToken, isPro: d.plan==="pro"||d.plan==="team",
217
- isGuest: !d.authToken, authSource: d.authSource||null,
218
- lastSyncAt: d.lastSyncAt||null,
219
- })));
220
- }
221
-
222
- async function getUser() {
223
- const auth = await getAuth();
224
- const usage = await checkUsage();
225
- return { ...auth, ...usage };
226
- }
227
-
228
- async function checkUsage() {
229
- return new Promise(r => chrome.storage.sync.get(["plan","scansThisMonth","monthResetAt"], d => {
230
- const now = new Date(), reset = d.monthResetAt ? new Date(d.monthResetAt) : null;
231
- if (!reset || now.getMonth() !== reset.getMonth() || now.getFullYear() !== reset.getFullYear()) {
232
- chrome.storage.sync.set({ scansThisMonth: 0, monthResetAt: now.toISOString() });
233
- r({ allowed: true, used: 0, limit: FREE_SCANS_PER_MONTH, plan: d.plan||"free" });
234
- return;
235
- }
236
- const used = d.scansThisMonth||0, plan = d.plan||"free";
237
- r({ allowed: plan!=="free"||used<FREE_SCANS_PER_MONTH, used, limit: plan==="free"?FREE_SCANS_PER_MONTH:Infinity, plan });
238
- }));
239
- }
240
-
241
- async function incrementUsage() {
242
- return new Promise(r => chrome.storage.sync.get(["scansThisMonth"], d =>
243
- chrome.storage.sync.set({ scansThisMonth: (d.scansThisMonth||0)+1 }, r)));
244
- }
245
-
246
- async function getStoredResults(tabId) {
247
- return new Promise(r => chrome.storage.local.get([`results_${tabId}`], d => r(d[`results_${tabId}`]||null)));
248
- }
249
-
250
- chrome.tabs.onRemoved.addListener(tabId => chrome.storage.local.remove([`results_${tabId}`]));
 
1
+ /app/clauseguard/extension/background.js