File size: 8,139 Bytes
0f27de7 3337c5d 4c0afe5 3337c5d 0f27de7 25b4391 7902802 3337c5d 0f27de7 4c0afe5 0f27de7 4c0afe5 3337c5d 4c0afe5 0f27de7 3337c5d 0f27de7 4c0afe5 0f27de7 3337c5d 0f27de7 4c0afe5 0f27de7 6c71866 4c0afe5 3337c5d 4c0afe5 6c71866 0f27de7 3337c5d 0f27de7 3337c5d 0f27de7 4c0afe5 3337c5d 4c0afe5 0f27de7 4c0afe5 0f27de7 3337c5d 4c0afe5 3337c5d 4c0afe5 3337c5d 4c0afe5 6c71866 3337c5d 4c0afe5 3337c5d 4c0afe5 0f27de7 3337c5d 4c0afe5 3337c5d 4c0afe5 0f27de7 4c0afe5 0f27de7 3337c5d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 | /**
* Authrix Extension β Background Service Worker v3
*
* Architecture (MV3-compliant):
* 1. tabCapture.getMediaStreamId() β get stream ID for the target tab
* 2. Create offscreen document β only place getUserMedia(tab) works in MV3
* 3. offscreen.js records the stream and sends BLOB_READY back here
* 4. We POST the blob to the local FastAPI backend
* 5. Result is sent to the content script overlay on the original tab
*/
const API_BASE = 'https://aarav13-authrix.hf.space';
const CAPTURE_SEC = 8; // Reduced from 20s β 8s gives enough frames for accurate detection
const OFFSCREEN_URL = chrome.runtime.getURL('offscreen.html');
// ββ Context menu ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
chrome.runtime.onInstalled.addListener(() => {
chrome.contextMenus.create({
id: 'authrix-capture',
title: 'π Analyze with Authrix',
contexts: ['page', 'video', 'frame'],
});
chrome.contextMenus.create({
id: 'authrix-url',
title: 'π Analyze video URL with Authrix',
contexts: ['link'],
});
});
chrome.contextMenus.onClicked.addListener((info, tab) => {
if (info.menuItemId === 'authrix-capture' && tab?.id) {
startCapture(tab.id);
}
if (info.menuItemId === 'authrix-url' && tab?.id && info.linkUrl) {
analyzeUrl(info.linkUrl, tab.id);
}
});
// ββ Message handler βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg.type === 'START_CAPTURE') {
const tabId = msg.tabId || sender.tab?.id;
startCapture(tabId)
.then(() => sendResponse({ ok: true }))
.catch(e => sendResponse({ ok: false, error: e.message }));
return true;
}
if (msg.type === 'ANALYZE_URL') {
const tabId = msg.tabId || sender.tab?.id;
analyzeUrl(msg.url, tabId)
.then(() => sendResponse({ ok: true }))
.catch(e => sendResponse({ ok: false, error: e.message }));
return true;
}
if (msg.type === 'CHECK_HEALTH') {
fetch(`${API_BASE}/health`)
.then(r => r.json())
.then(d => sendResponse({ ok: true, data: d }))
.catch(() => sendResponse({ ok: false }));
return true;
}
// ββ Messages from offscreen document βββββββββββββββββββββββββββββββββββββ
if (msg.type === 'BLOB_READY') {
handleBlobReady(msg.chunks, msg.mimeType, msg.tabId, msg.totalSize);
return false;
}
if (msg.type === 'RECORD_PROGRESS') {
if (msg.tabId) {
chrome.tabs.sendMessage(msg.tabId, {
type: 'CAPTURE_PROGRESS',
elapsed: msg.elapsed,
total: msg.total,
}).catch(() => {});
}
return false;
}
if (msg.type === 'RECORD_ERROR') {
if (msg.tabId) {
chrome.tabs.sendMessage(msg.tabId, {
type: 'ANALYSIS_ERROR',
error: msg.error,
}).catch(() => {});
}
closeOffscreen();
return false;
}
});
// ββ Start tab capture βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
async function startCapture(tabId) {
if (!tabId) throw new Error('No tab ID');
// Show loading overlay on the tab
await chrome.tabs.sendMessage(tabId, { type: 'SHOW_CAPTURE_OVERLAY' }).catch(() => {
// Content script may not be injected yet β inject it
return chrome.scripting.executeScript({
target: { tabId },
files: ['content.js'],
}).then(() =>
chrome.tabs.sendMessage(tabId, { type: 'SHOW_CAPTURE_OVERLAY' })
);
});
// Get stream ID (must be called from background, not offscreen)
const streamId = await new Promise((resolve, reject) => {
chrome.tabCapture.getMediaStreamId({ targetTabId: tabId }, id => {
if (chrome.runtime.lastError) {
reject(new Error(chrome.runtime.lastError.message));
} else {
resolve(id);
}
});
});
// Ensure offscreen document exists
await ensureOffscreen();
// Tell offscreen to start recording
await chrome.runtime.sendMessage({
type: 'RECORD_OFFSCREEN',
streamId,
durationMs: CAPTURE_SEC * 1000,
tabId,
});
}
// ββ Analyze a direct video URL ββββββββββββββββββββββββββββββββββββββββββββββββ
async function analyzeUrl(url, tabId) {
if (!url) throw new Error('No URL provided');
if (tabId) {
await chrome.tabs.sendMessage(tabId, { type: 'SHOW_URL_ANALYSIS_OVERLAY', url }).catch(() => {});
}
try {
const res = await fetch(`${API_BASE}/analyze-url`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url }),
});
if (!res.ok) {
const err = await res.json().catch(() => ({}));
throw new Error(err.detail || `Backend error ${res.status}`);
}
const result = await res.json();
result.file = url.length > 60 ? url.slice(0, 57) + 'β¦' : url;
chrome.storage.local.set({ lastResult: result });
if (tabId) {
chrome.tabs.sendMessage(tabId, { type: 'ANALYSIS_RESULT', result }).catch(() => {});
}
} catch (err) {
if (tabId) {
chrome.tabs.sendMessage(tabId, { type: 'ANALYSIS_ERROR', error: err.message }).catch(() => {});
}
throw err;
}
}
// ββ Handle blob from offscreen ββββββββββββββββββββββββββββββββββββββββββββββββ
async function handleBlobReady(chunks, mimeType, tabId, totalSize) {
closeOffscreen();
try {
if (tabId) {
chrome.tabs.sendMessage(tabId, { type: 'CAPTURE_PROGRESS', elapsed: 12, total: 12 }).catch(() => {});
}
const result = await submitBlob(chunks, mimeType, totalSize);
result.file = 'Tab capture';
chrome.storage.local.set({ lastResult: result });
if (tabId) {
chrome.tabs.sendMessage(tabId, { type: 'ANALYSIS_RESULT', result }).catch(() => {});
}
} catch (err) {
if (tabId) {
chrome.tabs.sendMessage(tabId, { type: 'ANALYSIS_ERROR', error: err.message }).catch(() => {});
}
}
}
// ββ Submit blob to backend ββββββββββββββββββββββββββββββββββββββββββββββββββββ
async function submitBlob(chunks, mimeType, totalSize) {
// Reassemble chunks from array format back to Uint8Array
const uint8Array = new Uint8Array(totalSize);
let offset = 0;
for (const chunk of chunks) {
uint8Array.set(chunk, offset);
offset += chunk.length;
}
const blob = new Blob([uint8Array], { type: mimeType || 'video/webm' });
const filename = `capture_${Date.now()}.webm`;
const fd = new FormData();
fd.append('file', blob, filename);
const res = await fetch(`${API_BASE}/analyze`, { method: 'POST', body: fd });
if (!res.ok) {
const err = await res.json().catch(() => ({}));
throw new Error(err.detail || `Backend error ${res.status}`);
}
return res.json();
}
// ββ Offscreen document management ββββββββββββββββββββββββββββββββββββββββββββ
async function ensureOffscreen() {
const existing = await chrome.offscreen.hasDocument().catch(() => false);
if (!existing) {
await chrome.offscreen.createDocument({
url: OFFSCREEN_URL,
reasons: ['USER_MEDIA'],
justification: 'Record tab video stream for deepfake analysis',
});
}
}
async function closeOffscreen() {
const exists = await chrome.offscreen.hasDocument().catch(() => false);
if (exists) {
await chrome.offscreen.closeDocument().catch(() => {});
}
}
|