Wire up document scanner for file upload interception, bump to 0.9.23
- Inject document-scanner.js into page world via injector.js (as module, sets globalThis.DocumentScanner) - Add FormData interception to fetch hook: scans File/Blob entries through DocumentScanner.processUpload(), also substitutes string fields - Add document-scanner.js to web_accessible_resources in both manifests - Bump version to 0.9.23 in package.json, manifest.json, manifest.firefox.json https://claude.ai/code/session_01NNBEPuXMFGWezJb1f958nL
This commit is contained in:
+88
-22
@@ -737,6 +737,52 @@
|
||||
return isConfigured();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Document Scanner — process file uploads in FormData
|
||||
// ============================================================
|
||||
async function scanFormData(formData) {
|
||||
const newForm = new FormData();
|
||||
const allReplacements = [];
|
||||
let anyModified = false;
|
||||
|
||||
for (const [key, value] of formData.entries()) {
|
||||
if (value instanceof File || value instanceof Blob) {
|
||||
const filename = value instanceof File ? value.name : (key || 'file');
|
||||
try {
|
||||
const result = await globalThis.DocumentScanner.processUpload(
|
||||
value, filename, substituteAll
|
||||
);
|
||||
if (result.replacements.length > 0) {
|
||||
anyModified = true;
|
||||
allReplacements.push(...result.replacements);
|
||||
const newFile = new File([result.file], result.filename, { type: result.file.type });
|
||||
newForm.append(key, newFile, result.filename);
|
||||
} else {
|
||||
newForm.append(key, value, filename);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[Silent Send] Document scan failed for', filename, e);
|
||||
newForm.append(key, value, filename);
|
||||
}
|
||||
} else if (typeof value === 'string' && value.length >= MIN_STRING_LENGTH) {
|
||||
// Also substitute string fields in the FormData
|
||||
const result = substituteAll(value);
|
||||
if (result.modified) {
|
||||
anyModified = true;
|
||||
allReplacements.push(...result.replacements);
|
||||
newForm.append(key, result.text);
|
||||
} else {
|
||||
newForm.append(key, value);
|
||||
}
|
||||
} else {
|
||||
newForm.append(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
if (!anyModified) return null;
|
||||
return { formData: newForm, replacements: allReplacements };
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Fetch Interception — scans ALL POST requests with a body.
|
||||
// Service-agnostic: doesn't depend on URL patterns.
|
||||
@@ -765,35 +811,55 @@
|
||||
const urlStr = typeof url === 'string' ? url : url?.url || '';
|
||||
const method = (options?.method || 'GET').toUpperCase();
|
||||
|
||||
// Only intercept POST/PUT/PATCH with a string body
|
||||
// Only intercept POST/PUT/PATCH
|
||||
if (
|
||||
(method === 'POST' || method === 'PUT' || method === 'PATCH') &&
|
||||
options?.body && typeof options.body === 'string' &&
|
||||
!shouldSkipUrl(urlStr)
|
||||
options?.body && !shouldSkipUrl(urlStr)
|
||||
) {
|
||||
try {
|
||||
// Try JSON
|
||||
const body = JSON.parse(options.body);
|
||||
const { modified, replacements } = processBody(body);
|
||||
|
||||
if (modified) {
|
||||
options = { ...options, body: JSON.stringify(body) };
|
||||
notifySubstitutions(replacements);
|
||||
console.log(
|
||||
`[Silent Send] Substituted ${replacements.length} value(s) in ${urlStr}`
|
||||
);
|
||||
// FormData body — scan file uploads via DocumentScanner
|
||||
if (options.body instanceof FormData && typeof globalThis.DocumentScanner !== 'undefined') {
|
||||
try {
|
||||
const scannedForm = await scanFormData(options.body);
|
||||
if (scannedForm) {
|
||||
options = { ...options, body: scannedForm.formData };
|
||||
if (scannedForm.replacements.length > 0) {
|
||||
notifySubstitutions(scannedForm.replacements);
|
||||
console.log(
|
||||
`[Silent Send] Substituted ${scannedForm.replacements.length} value(s) in file upload to ${urlStr}`
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[Silent Send] FormData scan failed:', e);
|
||||
}
|
||||
} catch (e) {
|
||||
// Not JSON — try raw string substitution (form data, etc.)
|
||||
if (options.body.length > MIN_STRING_LENGTH) {
|
||||
const result = substituteAll(options.body);
|
||||
if (result.modified) {
|
||||
options = { ...options, body: result.text };
|
||||
notifySubstitutions(result.replacements);
|
||||
}
|
||||
|
||||
// String body — JSON or raw text
|
||||
if (typeof options.body === 'string') {
|
||||
try {
|
||||
// Try JSON
|
||||
const body = JSON.parse(options.body);
|
||||
const { modified, replacements } = processBody(body);
|
||||
|
||||
if (modified) {
|
||||
options = { ...options, body: JSON.stringify(body) };
|
||||
notifySubstitutions(replacements);
|
||||
console.log(
|
||||
`[Silent Send] Substituted ${result.replacements.length} value(s) in form body`
|
||||
`[Silent Send] Substituted ${replacements.length} value(s) in ${urlStr}`
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
// Not JSON — try raw string substitution (form data, etc.)
|
||||
if (options.body.length > MIN_STRING_LENGTH) {
|
||||
const result = substituteAll(options.body);
|
||||
if (result.modified) {
|
||||
options = { ...options, body: result.text };
|
||||
notifySubstitutions(result.replacements);
|
||||
console.log(
|
||||
`[Silent Send] Substituted ${result.replacements.length} value(s) in form body`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,14 @@
|
||||
// Merge active profiles into a flat identity object for the content script
|
||||
const identity = mergeProfiles(identityData);
|
||||
|
||||
// Inject the document scanner into the page's world first (sets globalThis.DocumentScanner)
|
||||
const docScannerScript = document.createElement('script');
|
||||
docScannerScript.type = 'module';
|
||||
docScannerScript.src = api.runtime.getURL('src/lib/document-scanner.js');
|
||||
(document.head || document.documentElement).appendChild(docScannerScript);
|
||||
await new Promise(resolve => { docScannerScript.onload = resolve; docScannerScript.onerror = resolve; });
|
||||
docScannerScript.remove();
|
||||
|
||||
// Inject the main interception script into the page's world
|
||||
const script = document.createElement('script');
|
||||
script.setAttribute('data-ss-config', JSON.stringify({ mappings, identity, settings }));
|
||||
|
||||
Reference in New Issue
Block a user