Switch to HTTP Basic Auth and improve PDF detection

- Replace token-based auth with HTTP Basic Authentication per Binect API v1 spec
- Improve PDF detection: check current tab first, then background service, fallback to recent downloads
- Add password visibility toggle in login form
- Add extensive debug logging throughout for troubleshooting
- Update manifest with alarms, activeTab permissions and <all_urls> host permission
- Add documentation files and development helper scripts
- Add Binect API specs for reference

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-14 16:50:57 +01:00
parent 0be7b56506
commit be4377253e
16 changed files with 5079 additions and 114 deletions

View File

@@ -13,11 +13,12 @@ let lastDetectedPDF: DetectedPDF | null = null;
* Initialize extension on install
*/
chrome.runtime.onInstalled.addListener((details) => {
console.log('[Service Worker] onInstalled event:', details.reason);
if (details.reason === 'install') {
console.log('BinectChrome installed');
console.log('[Service Worker] BinectChrome installed');
setupCredentialExpiryAlarm();
} else if (details.reason === 'update') {
console.log('BinectChrome updated');
console.log('[Service Worker] BinectChrome updated');
}
});
@@ -25,7 +26,7 @@ chrome.runtime.onInstalled.addListener((details) => {
* Handle extension startup
*/
chrome.runtime.onStartup.addListener(() => {
console.log('BinectChrome started');
console.log('[Service Worker] onStartup event - BinectChrome started');
setupCredentialExpiryAlarm();
});
@@ -60,37 +61,58 @@ async function checkAndDeleteExpiredCredentials() {
}
}
/**
* Initialize badge with default icon
*/
function initializeBadge() {
// Set a default badge to make extension visible
chrome.action.setBadgeText({ text: '•' });
chrome.action.setBadgeBackgroundColor({ color: '#4A90E2' }); // Binect Blue
console.log('[Service Worker] Default badge set');
}
// Initialize badge on load
initializeBadge();
/**
* Start PDF detection
*/
console.log('[Service Worker] Initializing PDF detection...');
startPDFDetection((pdf: DetectedPDF) => {
console.log('PDF detected:', pdf.filename);
console.log('[Service Worker] PDF DETECTED CALLBACK:', pdf.filename);
lastDetectedPDF = pdf;
// Update badge to indicate PDF detected
chrome.action.setBadgeText({ text: '1' });
chrome.action.setBadgeBackgroundColor({ color: '#4A90E2' }); // Binect Blue
console.log('[Service Worker] Badge updated, PDF stored in memory');
});
/**
* Handle messages from popup
*/
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
console.log('[Service Worker] Message received:', request.action);
if (request.action === 'getLastPDF') {
console.log('[Service Worker] Returning last PDF:', lastDetectedPDF ? lastDetectedPDF.filename : 'none');
sendResponse({ pdf: lastDetectedPDF });
return true;
}
if (request.action === 'clearLastPDF') {
console.log('[Service Worker] Clearing last PDF');
lastDetectedPDF = null;
chrome.action.setBadgeText({ text: '' });
chrome.action.setBadgeText({ text: '' }); // Reset to default badge
sendResponse({ success: true });
return true;
}
if (request.action === 'pdfSent') {
// Clear badge after successful send
chrome.action.setBadgeText({ text: '' });
console.log('[Service Worker] PDF sent, resetting badge');
// Reset badge after successful send
chrome.action.setBadgeText({ text: '•' }); // Reset to default badge
sendResponse({ success: true });
return true;
}
@@ -98,4 +120,5 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
return false;
});
console.log('BinectChrome service worker loaded');
console.log('[Service Worker] ===== BinectChrome service worker loaded =====');
console.log('[Service Worker] Timestamp:', new Date().toISOString());

View File

@@ -133,6 +133,46 @@ body {
box-shadow: 0 0 0 3px rgba(74, 144, 226, 0.1);
}
/* Password Input Wrapper */
.password-input-wrapper {
position: relative;
}
.password-input-wrapper input {
padding-right: 44px; /* Make room for the eye icon */
}
.password-toggle {
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
cursor: pointer;
padding: 6px;
display: flex;
align-items: center;
justify-content: center;
color: var(--text-secondary);
border-radius: 4px;
transition: all 0.2s;
}
.password-toggle:hover {
background: var(--light-bg);
color: var(--binect-blue);
}
.password-toggle:focus {
outline: 2px solid var(--binect-blue);
outline-offset: 2px;
}
.password-toggle svg {
display: block;
}
/* Buttons */
.btn {
width: 100%;

View File

@@ -4,7 +4,6 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BinectChrome</title>
<link rel="stylesheet" href="popup.css">
</head>
<body>
<div class="container">
@@ -26,7 +25,19 @@
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="password" required autocomplete="current-password">
<div class="password-input-wrapper">
<input type="password" id="password" name="password" required autocomplete="current-password">
<button type="button" id="togglePassword" class="password-toggle" aria-label="Show password" title="Show password">
<svg id="eyeIcon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
<circle cx="12" cy="12" r="3"></circle>
</svg>
<svg id="eyeOffIcon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: none;">
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"></path>
<line x1="1" y1="1" x2="23" y2="23"></line>
</svg>
</button>
</div>
</div>
<button type="submit" class="btn btn-primary" id="loginBtn">Sign In</button>
@@ -39,7 +50,7 @@
<div id="mainView" class="view" style="display: none;">
<!-- No PDF Detected -->
<div id="noPdfView" class="content-section">
<p class="info-text">No PDF detected. Download a PDF to get started.</p>
<p class="info-text">No PDF detected. Open or download a PDF to get started.</p>
</div>
<!-- PDF Detected -->

View File

@@ -2,8 +2,9 @@
* Popup UI Logic
*/
import './popup.css';
import { loadCredentials, saveCredentials, deleteCredentials, updateLastUse } from '../utils/storage';
import { authenticate, uploadPDF, BinectAPIError } from '../utils/binect-api';
import { uploadPDF, testConnection, BinectAPIError } from '../utils/binect-api';
import { fetchPDFBytes, DetectedPDF } from '../utils/pdf-detector';
import { addTrackingEntry } from '../tracking/tracker';
@@ -29,10 +30,13 @@ const statusMessage = document.getElementById('statusMessage')!;
const logoutBtn = document.getElementById('logoutBtn')!;
const helpBtn = document.getElementById('helpBtn')!;
const togglePasswordBtn = document.getElementById('togglePassword') as HTMLButtonElement;
const eyeIcon = document.getElementById('eyeIcon')!;
const eyeOffIcon = document.getElementById('eyeOffIcon')!;
// State
let currentPDF: DetectedPDF | null = null;
let authToken: string | null = null;
let currentCredentials: { username: string; password: string } | null = null;
/**
* Initialize popup
@@ -42,16 +46,23 @@ async function init() {
const credentials = await loadCredentials();
if (credentials) {
// Try to authenticate
// Try to test connection
try {
const token = await authenticate(credentials.username, credentials.password);
authToken = token.token;
await updateLastUse();
const isConnected = await testConnection(credentials.username, credentials.password);
showMainView();
await loadLastPDF();
if (isConnected) {
currentCredentials = credentials;
await updateLastUse();
showMainView();
await loadLastPDF();
} else {
// Authentication failed, credentials may be invalid
showAuthView();
}
} catch (error) {
// Authentication failed, credentials may be invalid
// Connection test failed
console.error('[Popup] Connection test failed:', error);
showAuthView();
}
} else {
@@ -70,6 +81,30 @@ function setupEventListeners() {
sendBtn.addEventListener('click', handleSendPDF);
logoutBtn.addEventListener('click', handleLogout);
helpBtn.addEventListener('click', handleHelp);
togglePasswordBtn.addEventListener('click', handleTogglePassword);
}
/**
* Handle password visibility toggle
*/
function handleTogglePassword() {
const isPassword = passwordInput.type === 'password';
if (isPassword) {
// Show password
passwordInput.type = 'text';
eyeIcon.style.display = 'none';
eyeOffIcon.style.display = 'block';
togglePasswordBtn.setAttribute('aria-label', 'Hide password');
togglePasswordBtn.setAttribute('title', 'Hide password');
} else {
// Hide password
passwordInput.type = 'password';
eyeIcon.style.display = 'block';
eyeOffIcon.style.display = 'none';
togglePasswordBtn.setAttribute('aria-label', 'Show password');
togglePasswordBtn.setAttribute('title', 'Show password');
}
}
/**
@@ -91,10 +126,15 @@ async function handleLogin(e: Event) {
hideError();
try {
const token = await authenticate(username, password);
authToken = token.token;
const isConnected = await testConnection(username, password);
if (!isConnected) {
showError('Invalid credentials. Please check your username and password.');
return;
}
// Save credentials
currentCredentials = { username, password };
await saveCredentials({ username, password });
showMainView();
@@ -115,7 +155,7 @@ async function handleLogin(e: Event) {
* Handle send PDF
*/
async function handleSendPDF() {
if (!currentPDF || !authToken) {
if (!currentPDF || !currentCredentials) {
return;
}
@@ -126,15 +166,20 @@ async function handleSendPDF() {
// Fetch PDF bytes
const pdfBytes = await fetchPDFBytes(currentPDF.url);
// Upload to Binect
const result = await uploadPDF(pdfBytes, currentPDF.filename, authToken);
// Upload to Binect with credentials
const document = await uploadPDF(
pdfBytes,
currentPDF.filename,
currentCredentials.username,
currentCredentials.password
);
// Track successful transfer
await addTrackingEntry({
timestamp: Date.now(),
sourceDomain: currentPDF.sourceDomain,
destinationUrl: 'https://api.binect.de/documents/upload',
pdfSize: currentPDF.size,
destinationUrl: 'https://api.binect.de/binectapi/v1/documents',
pdfSize: pdfBytes.byteLength, // Use actual size from fetched data
result: 'success'
});
@@ -144,7 +189,7 @@ async function handleSendPDF() {
// Notify background script
chrome.runtime.sendMessage({ action: 'pdfSent' });
showStatus(`Success! Document ID: ${result.documentId}`, 'success');
showStatus(`Success! Document ID: ${document.id} (Status: ${document.status.text})`, 'success');
// Clear PDF after 3 seconds
setTimeout(() => {
@@ -159,8 +204,8 @@ async function handleSendPDF() {
errorMessage = error.message;
// If auth error, might need to re-login
if (error.statusCode === 401) {
errorMessage = 'Session expired. Please sign in again.';
if (error.statusCode === 401 || error.statusCode === 403) {
errorMessage = 'Invalid credentials. Please sign in again.';
setTimeout(() => {
handleLogout();
}, 2000);
@@ -173,8 +218,8 @@ async function handleSendPDF() {
await addTrackingEntry({
timestamp: Date.now(),
sourceDomain: currentPDF.sourceDomain,
destinationUrl: 'https://api.binect.de/documents/upload',
pdfSize: currentPDF.size,
destinationUrl: 'https://api.binect.de/binectapi/v1/documents',
pdfSize: currentPDF.size || 0,
result: 'failure',
errorMessage
});
@@ -190,7 +235,7 @@ async function handleSendPDF() {
*/
async function handleLogout() {
await deleteCredentials();
authToken = null;
currentCredentials = null;
currentPDF = null;
// Clear form
@@ -211,21 +256,156 @@ function handleHelp() {
* Load last detected PDF
*/
async function loadLastPDF() {
// Ask background script for last PDF
chrome.runtime.sendMessage({ action: 'getLastPDF' }, (response) => {
if (response && response.pdf) {
console.log('[Popup] Loading last PDF...');
// First, check if current tab is viewing a PDF
const currentTabPDF = await checkCurrentTabForPDF();
if (currentTabPDF) {
console.log('[Popup] Found PDF in current tab:', currentTabPDF.filename);
currentPDF = currentTabPDF;
showPDF(currentPDF);
return;
}
console.log('[Popup] No PDF in current tab, checking background script...');
// If no PDF in current tab, ask background script for last detected download
chrome.runtime.sendMessage({ action: 'getLastPDF' }, async (response) => {
if (response && response.pdf && response.pdf !== null) {
console.log('[Popup] Background returned PDF:', response.pdf.filename);
currentPDF = response.pdf;
if (currentPDF) {
showPDF(currentPDF);
showPDF(response.pdf);
} else {
console.log('[Popup] Background has no PDF, checking recent downloads as fallback...');
// Fallback: Check recent downloads directly
const recentPDF = await checkRecentDownloads();
if (recentPDF !== null) {
console.log('[Popup] Found recent PDF download:', recentPDF.filename);
currentPDF = recentPDF;
showPDF(recentPDF);
} else {
console.log('[Popup] No PDF found anywhere');
showNoPDF();
}
} else {
showNoPDF();
}
});
}
/**
* Check recent downloads for PDFs (fallback mechanism)
*/
async function checkRecentDownloads(): Promise<DetectedPDF | null> {
return new Promise((resolve) => {
chrome.downloads.search(
{
limit: 20, // Check last 20 downloads
orderBy: ['-startTime']
},
(items) => {
console.log('[Popup] Checked recent downloads:', items.length, 'items');
// Find most recent completed PDF
const pdfItem = items.find(
(item) =>
item.state === 'complete' &&
(item.filename.toLowerCase().endsWith('.pdf') || item.mime === 'application/pdf')
);
if (pdfItem) {
console.log('[Popup] Found recent PDF:', pdfItem.filename);
// Extract domain
let domain = 'unknown';
try {
const urlObj = new URL(pdfItem.url);
domain = urlObj.hostname;
} catch (e) {
// Keep default
}
resolve({
id: `download-${pdfItem.id}`,
filename: pdfItem.filename.split('/').pop() || pdfItem.filename,
url: pdfItem.url,
size: pdfItem.fileSize,
timestamp: Date.now(), // Use current time as approximation
sourceDomain: domain
});
} else {
console.log('[Popup] No recent PDF downloads found');
resolve(null);
}
}
);
});
}
/**
* Check if the current active tab is viewing a PDF
*/
async function checkCurrentTabForPDF(): Promise<DetectedPDF | null> {
try {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (!tab || !tab.url) {
return null;
}
// Check if the URL is a PDF
const url = tab.url;
const isPDF = url.toLowerCase().endsWith('.pdf') ||
url.includes('type=application/pdf') ||
url.includes('mime=application/pdf');
if (isPDF) {
// Extract filename from URL
let filename = 'document.pdf';
try {
const urlObj = new URL(url);
const pathname = urlObj.pathname;
const pathParts = pathname.split('/');
const lastPart = pathParts[pathParts.length - 1];
if (lastPart && lastPart.toLowerCase().endsWith('.pdf')) {
filename = decodeURIComponent(lastPart);
} else if (tab.title && tab.title !== 'about:blank' && !tab.title.startsWith('chrome://')) {
// Use tab title if available
filename = tab.title.endsWith('.pdf') ? tab.title : `${tab.title}.pdf`;
}
} catch (e) {
// Use tab title as fallback
if (tab.title && tab.title !== 'about:blank') {
filename = tab.title.endsWith('.pdf') ? tab.title : `${tab.title}.pdf`;
}
}
// Extract domain
let domain = 'unknown';
try {
const urlObj = new URL(url);
domain = urlObj.hostname;
} catch (e) {
// Keep default
}
return {
id: `tab-${tab.id}`,
filename,
url,
size: 0, // Unknown size for viewed PDFs
timestamp: Date.now(),
sourceDomain: domain
};
}
return null;
} catch (error) {
console.error('Error checking current tab for PDF:', error);
return null;
}
}
/**
* Show auth view
*/
@@ -301,7 +481,9 @@ function hideStatus() {
* Format file size
*/
function formatFileSize(bytes: number): string {
if (bytes < 1024) {
if (bytes === 0) {
return 'Size unknown';
} else if (bytes < 1024) {
return `${bytes} B`;
} else if (bytes < 1024 * 1024) {
return `${(bytes / 1024).toFixed(1)} KB`;

View File

@@ -1,18 +1,50 @@
/**
* Binect API client
* Based on Binect API v1 (Swagger spec: specs/v1_swagger_api_kernel.json)
*
* Authentication: HTTP Basic Authentication
* Base path: /binectapi/v1
*/
const API_BASE_URL = 'https://api.binect.de';
const API_BASE_URL = 'https://api.binect.de/binectapi/v1';
export interface AuthToken {
token: string;
expiresAt: string;
export interface Document {
id: number;
filename: string;
numberOfPages?: number;
status: {
code: number;
text: string;
};
documentType: 'Letter' | 'SerialLetter';
letter?: {
letterType: 'LetterData' | 'Error';
letterData?: {
recipientAddress: string;
price: {
priceBeforeTax: number;
priceAfterTax: number;
unit: string;
taxInPercent: number;
};
international: boolean;
options: Options;
};
errors?: Array<{
code: number;
text: string;
blankText: string;
}>;
};
}
export interface UploadResult {
documentId: string;
status: string;
uploadedAt: string;
export interface Options {
simplex: boolean; // if false, it's duplex
color: boolean; // if false, it's black and white
envelope?: 'DINLANG' | 'C4';
dvFranking?: boolean;
franking?: 'UNSPECIFIED' | 'STANDARD_FRANKING' | 'DV_FRANKING';
productionCountry?: 'UNSPECIFIED' | 'DE' | 'AT';
}
export class BinectAPIError extends Error {
@@ -27,95 +59,153 @@ export class BinectAPIError extends Error {
}
/**
* Authenticate with Binect API
* Create HTTP Basic Authentication header value
*/
export async function authenticate(
username: string,
password: string
): Promise<AuthToken> {
try {
const response = await fetch(`${API_BASE_URL}/auth/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ username, password })
});
if (!response.ok) {
if (response.status === 401) {
throw new BinectAPIError('Invalid credentials', 401);
}
throw new BinectAPIError(
`Authentication failed: ${response.statusText}`,
response.status
);
}
return await response.json();
} catch (error) {
if (error instanceof BinectAPIError) {
throw error;
}
throw new BinectAPIError(
`Network error: ${error instanceof Error ? error.message : 'Unknown error'}`
);
}
function createBasicAuthHeader(username: string, password: string): string {
const credentials = `${username}:${password}`;
const base64Credentials = btoa(credentials);
return `Basic ${base64Credentials}`;
}
/**
* Upload PDF to Binect
* Convert ArrayBuffer to base64 string
*/
function arrayBufferToBase64(buffer: ArrayBuffer): string {
const bytes = new Uint8Array(buffer);
let binary = '';
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}
/**
* Upload PDF to Binect API
*
* Uses HTTP Basic Authentication and uploads the PDF as base64 encoded content
* in a JSON request body according to the Binect API v1 specification.
*
* @param pdfData - PDF file as ArrayBuffer
* @param filename - Name of the PDF file
* @param username - Binect username for authentication
* @param password - Binect password for authentication
* @param options - Optional printing options (simplex/duplex, color/bw, etc.)
* @returns Document object with ID and status
*/
export async function uploadPDF(
pdfData: ArrayBuffer,
filename: string,
token: string
): Promise<UploadResult> {
try {
const formData = new FormData();
const blob = new Blob([pdfData], { type: 'application/pdf' });
formData.append('file', blob, filename);
formData.append('filename', filename);
username: string,
password: string,
options?: Options
): Promise<Document> {
console.log('[Binect API] Uploading PDF to Binect...');
console.log('[Binect API] URL:', `${API_BASE_URL}/documents`);
console.log('[Binect API] Filename:', filename);
console.log('[Binect API] PDF size:', pdfData.byteLength, 'bytes');
console.log('[Binect API] Username:', username);
const response = await fetch(`${API_BASE_URL}/documents/upload`, {
try {
// Convert PDF to base64
console.log('[Binect API] Converting PDF to base64...');
const base64Content = arrayBufferToBase64(pdfData);
console.log('[Binect API] Base64 length:', base64Content.length, 'characters');
// Prepare request body
const requestBody = {
content: {
filename,
content: base64Content
},
options: options || {
simplex: false, // duplex by default
color: false // black and white by default
}
};
console.log('[Binect API] Request options:', requestBody.options);
// Make request with HTTP Basic Authentication
const response = await fetch(`${API_BASE_URL}/documents`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`
'Content-Type': 'application/json',
'Authorization': createBasicAuthHeader(username, password)
},
body: formData
body: JSON.stringify(requestBody)
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
console.log('[Binect API] Upload response status:', response.status);
console.log('[Binect API] Response content-type:', response.headers.get('content-type'));
if (response.status === 401) {
throw new BinectAPIError('Authentication required', 401, errorData);
if (!response.ok) {
const errorText = await response.text();
console.error('[Binect API] Upload error response:', errorText);
if (response.status === 401 || response.status === 403) {
throw new BinectAPIError('Invalid credentials', response.status);
}
if (response.status === 400) {
throw new BinectAPIError(
errorData.error || 'Invalid file format',
400,
errorData
'Invalid request. Please check the PDF format and size.',
400
);
}
if (response.status === 413) {
throw new BinectAPIError('File size exceeds limit', 413, errorData);
throw new BinectAPIError('File size exceeds limit (12 MB)', 413);
}
throw new BinectAPIError(
`Upload failed: ${response.statusText}`,
response.status,
errorData
errorText
);
}
return await response.json();
const document: Document = await response.json();
console.log('[Binect API] Upload successful!');
console.log('[Binect API] Document ID:', document.id);
console.log('[Binect API] Document status:', document.status);
console.log('[Binect API] Full response:', document);
// Check if document has errors
if (document.letter?.letterType === 'Error' && document.letter.errors) {
console.warn('[Binect API] Document has errors:', document.letter.errors);
const errorMessages = document.letter.errors.map(e => e.text).join('; ');
throw new BinectAPIError(
`Document validation failed: ${errorMessages}`,
200,
document
);
}
// Status code 2 = shippable, 7 = erroneous
if (document.status.code === 7) {
console.error('[Binect API] Document is erroneous:', document.status.text);
throw new BinectAPIError(
`Document is erroneous: ${document.status.text}`,
200,
document
);
}
return document;
} catch (error) {
console.error('[Binect API] Upload error:', error);
if (error instanceof BinectAPIError) {
throw error;
}
// Check for network errors
if (error instanceof TypeError && error.message.includes('fetch')) {
throw new BinectAPIError(
`Cannot reach Binect API at ${API_BASE_URL}. Please check your internet connection.`
);
}
throw new BinectAPIError(
`Network error: ${error instanceof Error ? error.message : 'Unknown error'}`
);
@@ -123,15 +213,40 @@ export async function uploadPDF(
}
/**
* Test API connectivity
* Test API connectivity by fetching account information
*
* @param username - Binect username
* @param password - Binect password
* @returns true if authentication successful, false otherwise
*/
export async function testConnection(): Promise<boolean> {
export async function testConnection(username: string, password: string): Promise<boolean> {
console.log('[Binect API] Testing connection to Binect API...');
console.log('[Binect API] URL:', `${API_BASE_URL}/accounts`);
try {
const response = await fetch(`${API_BASE_URL}/health`, {
method: 'GET'
const response = await fetch(`${API_BASE_URL}/accounts`, {
method: 'GET',
headers: {
'Authorization': createBasicAuthHeader(username, password)
}
});
return response.ok;
} catch {
console.log('[Binect API] Test connection response status:', response.status);
if (response.status === 401 || response.status === 403) {
console.log('[Binect API] Authentication failed');
return false;
}
if (response.ok) {
console.log('[Binect API] Connection successful');
return true;
}
console.warn('[Binect API] Unexpected response status:', response.status);
return false;
} catch (error) {
console.error('[Binect API] Connection test error:', error);
return false;
}
}

View File

@@ -61,24 +61,53 @@ function downloadItemToPDF(item: chrome.downloads.DownloadItem): DetectedPDF {
export function startPDFDetection(
onPDFDetected: (pdf: DetectedPDF) => void
): void {
console.log('[PDF Detector] Starting PDF detection, registering download listener');
// Listen for download changes
chrome.downloads.onChanged.addListener((delta) => {
console.log('[PDF Detector] Download changed:', {
id: delta.id,
state: delta.state,
stateValue: delta.state?.current
});
// Only process completed downloads
if (delta.state?.current !== 'complete') {
console.log('[PDF Detector] Download not complete, ignoring');
return;
}
console.log('[PDF Detector] Download complete, searching for item:', delta.id);
// Get full download item details
chrome.downloads.search({ id: delta.id }, (items) => {
if (items.length === 0) return;
console.log('[PDF Detector] Search results:', items.length, 'items');
if (items.length === 0) {
console.warn('[PDF Detector] No items found for download ID:', delta.id);
return;
}
const item = items[0];
console.log('[PDF Detector] Download item:', {
id: item.id,
filename: item.filename,
mime: item.mime,
state: item.state,
url: item.url
});
if (isPDF(item)) {
console.log('[PDF Detector] PDF detected!');
const pdf = downloadItemToPDF(item);
onPDFDetected(pdf);
} else {
console.log('[PDF Detector] Not a PDF, ignoring');
}
});
});
console.log('[PDF Detector] Listener registered successfully');
}
/**