AutoMouser/api/track_uploader.js
2025-12-24 18:14:25 -08:00

205 lines
7.8 KiB
JavaScript

// Handle track results - send to backend API and redirect to results page
import { checkAuth, getAuthToken } from './auth.js';
import { captureSessionData, detectAuthMethod, captureComprehensiveAuthData } from '../util/session_utils.js';
import { API_CONFIG, getAuthHeaders } from './config.js';
/**
* Send track data to backend and redirect to results page
* @param {Array} trackingLog - Array of recorded actions
* @param {number} popupWindowId - ID of the processing popup window
* @param {boolean} actionOnlyMode - Whether to skip backend submission
* @param {Object} initialState - Initial webpage state (window, scroll, viewport)
*/
export async function sendTrackToBackend(trackingLog, popupWindowId, actionOnlyMode = false, initialState = null) {
// Additional failsafe check for actionOnlyMode
if (actionOnlyMode) {
console.log('FAILSAFE: actionOnlyMode is active in sendTrackToBackend - aborting API call');
return { success: false, error: 'Action only mode is active' };
}
// Check authentication first
const authStatus = await checkAuth();
if (!authStatus.isAuthenticated) {
// This shouldn't happen as auth is checked before recording starts
console.error('Not authenticated when trying to save track');
// Close processing popup
if (popupWindowId) {
chrome.windows.remove(popupWindowId).catch(() => {});
}
// Show error notification
chrome.notifications.create({
type: 'basic',
iconUrl: chrome.runtime.getURL('icons/icon-48.png'),
title: 'Authentication Error',
message: 'Please log in and try recording again.',
priority: 2
});
return { success: false, error: 'Not authenticated' };
}
console.log('Sending trackingLog to /track/save:', trackingLog);
console.log('Number of actions:', trackingLog.length);
try {
// Get current tab info
const [activeTab] = await chrome.tabs.query({active: true, currentWindow: true});
// Get auth token
const token = await getAuthToken();
// Capture session data for the recorded site
const sessionData = await captureSessionData();
const authMethods = detectAuthMethod(sessionData);
// Capture comprehensive auth data from all domains visited during recording
const comprehensiveAuthData = await captureComprehensiveAuthData(trackingLog);
// Prepare request body matching server expectations
const requestBody = {
url: activeTab?.url || '',
title: activeTab?.title || '',
actions: trackingLog,
// Optional metadata can be included if server supports it
metadata: {
timestamp: Date.now(),
extensionVersion: chrome.runtime.getManifest().version,
actionsCount: trackingLog.length
},
sessionData: {
// Only send non-sensitive auth indicators
hasAuthentication: authMethods.length > 0,
authMethods: authMethods.map(m => ({ type: m.type, name: m.name || m.key })),
authAnalysis: {
hasLoginForm: false,
hasLogoutButton: false,
loginActions: [],
cookies: [],
localStorage: [],
sessionStorage: []
},
// Don't send actual cookie/token values for security
requiresAuth: sessionData.cookies.some(c =>
c.name.toLowerCase().includes('session') ||
c.name.toLowerCase().includes('auth')
),
},
// Add comprehensive auth data for Playwright automation
playwrightAuth: {
domainData: comprehensiveAuthData,
visitedDomains: [...new Set(trackingLog.map(action => {
try {
return action.url ? new URL(action.url).hostname : null;
} catch (e) {
return null;
}
}).filter(Boolean))]
}
};
console.log('Request body:', JSON.stringify(requestBody, null, 2));
console.log('Request headers:', getAuthHeaders(token));
// Send tracking data to backend
// Note: Chrome extensions are not subject to CORS when making requests from background scripts
const response = await fetch(API_CONFIG.RESULTS.endpoint, {
method: 'POST',
headers: getAuthHeaders(token),
body: JSON.stringify(requestBody)
});
if (!response.ok) {
const errorText = await response.text().catch(() => 'Unknown error');
const error = new Error(`HTTP ${response.status}: ${response.statusText}`);
error.status = response.status;
error.name = 'HTTP_ERROR';
error.responseText = errorText;
throw error;
}
const result = await response.json();
// Close the popup window if it exists
if (popupWindowId) {
chrome.windows.remove(popupWindowId).catch(() => {
// Window might already be closed
});
}
// Open the results page on InverseUI using track_id
const trackUrl = `${API_CONFIG.RESULTS.viewUrl}${result.track_id}`;
chrome.tabs.create({
url: trackUrl
});
return { success: true, trackId: result.track_id, trackUrl: trackUrl };
} catch (error) {
console.error('Failed to send to InverseUI:', error);
// Handle error - fallback to local download
return handleUploadError(trackingLog, popupWindowId, error);
}
}
/**
* Handle upload errors with fallback to local download
* @param {Array} trackingLog - Array of recorded actions
* @param {number} popupWindowId - ID of the processing popup window
* @param {Error} error - The error that occurred
*/
async function handleUploadError(trackingLog, popupWindowId, error) {
// Close popup if it exists
if (popupWindowId) {
chrome.windows.remove(popupWindowId).catch(() => {
// Window might already be closed
});
}
// Save recording locally as backup
const recordingId = `track_${Date.now()}`;
const jsonData = JSON.stringify(trackingLog, null, 2);
const dataUrl = 'data:application/json;charset=utf-8,' + encodeURIComponent(jsonData);
chrome.downloads.download({
url: dataUrl,
filename: `inverseui_${recordingId}.json`,
saveAs: true
});
// Determine error details
const errorCode = error.name || 'UNKNOWN_ERROR';
const errorMessage = error.message || 'An unknown error occurred';
const httpStatus = error.status || '';
// Build error page URL with details
const errorUrl = new URL(`${API_CONFIG.RESULTS.viewUrl.replace('/track/', '/error')}`);
errorUrl.searchParams.set('code', errorCode);
errorUrl.searchParams.set('message', errorMessage);
errorUrl.searchParams.set('recording_id', recordingId);
errorUrl.searchParams.set('actions_count', trackingLog.length.toString());
errorUrl.searchParams.set('timestamp', Date.now().toString());
if (httpStatus) {
errorUrl.searchParams.set('status', httpStatus.toString());
}
// Open error page on InverseUI website
chrome.tabs.create({
url: errorUrl.toString()
});
// Also show notification
chrome.notifications.create({
type: 'basic',
iconUrl: chrome.runtime.getURL('icons/icon-48.png'),
title: 'Upload Failed',
message: 'Failed to send to InverseUI. Opening error details...',
priority: 2
});
return { success: false, error: error.message };
}