generated from coulomb/repo-seed
Server sync, erroneous doc handling, and @binect/js v0.1.0 integration
- Add server sync to discover documents uploaded elsewhere (fixes missing basket documents issue) - Handle erroneous uploads: preserve binectDocumentId for delete button - Add "Delete from server" button for erroneous and canceled documents - Remove archive button for active documents (in_basket, in_production) - Auto-restore archived documents that have active status - Refactor to use @binect/js v0.1.0 features: - DocumentStatus enum instead of magic numbers - isErroneous(), getErrors() helper functions - getStatusDescription() for status text - Add binect-js improvement requirements document Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -253,6 +253,27 @@ async function loadPDFQueue() {
|
||||
pdfQueue = response?.entries || [];
|
||||
console.log('[Popup] Got', pdfQueue.length, 'live entries from background');
|
||||
|
||||
// Sync with server to discover documents uploaded elsewhere or missing locally
|
||||
if (currentCredentials) {
|
||||
await syncWithServer();
|
||||
}
|
||||
|
||||
// Auto-restore any archived documents that are in active states (in_basket, in_production)
|
||||
// These should never be archived as it confuses user expectations
|
||||
const archivedResponse = await chrome.runtime.sendMessage({ action: 'getArchivedProxies' });
|
||||
const archivedEntries: DocumentProxy[] = archivedResponse?.entries || [];
|
||||
for (const entry of archivedEntries) {
|
||||
if (entry.binectStatus === 'in_basket' || entry.binectStatus === 'in_production') {
|
||||
console.log('[Popup] Auto-restoring active document from archive:', entry.filename);
|
||||
await chrome.runtime.sendMessage({ action: 'restoreProxy', id: entry.id });
|
||||
}
|
||||
}
|
||||
|
||||
// Reload live proxies after potential restorations and server sync
|
||||
response = await chrome.runtime.sendMessage({ action: 'getLiveProxies' });
|
||||
pdfQueue = response?.entries || [];
|
||||
console.log('[Popup] After auto-restore and sync, got', pdfQueue.length, 'live entries');
|
||||
|
||||
// Check current tab for PDF and add to persistent queue via background
|
||||
const currentTabPDF = await checkCurrentTabForPDF();
|
||||
if (currentTabPDF) {
|
||||
@@ -289,6 +310,70 @@ async function loadPDFQueue() {
|
||||
renderPDFList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync local proxies with server documents
|
||||
* Creates local proxies for documents that exist on server but not locally
|
||||
*/
|
||||
async function syncWithServer() {
|
||||
if (!currentCredentials) {
|
||||
console.log('[Popup] No credentials, skipping server sync');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('[Popup] Syncing with Binect server...');
|
||||
|
||||
// Get list of documents from server
|
||||
const result = await chrome.runtime.sendMessage({
|
||||
action: 'listServerDocuments',
|
||||
username: currentCredentials.username,
|
||||
password: currentCredentials.password
|
||||
});
|
||||
|
||||
console.log('[Popup] listServerDocuments result:', result);
|
||||
|
||||
if (!result.success || !result.documents) {
|
||||
console.warn('[Popup] Failed to list server documents:', result.error);
|
||||
return;
|
||||
}
|
||||
|
||||
const serverDocs = result.documents as Array<{
|
||||
id: number;
|
||||
filename: string;
|
||||
status: number;
|
||||
statusText: string;
|
||||
price?: number;
|
||||
recipientAddress?: string;
|
||||
errorDetails?: string;
|
||||
}>;
|
||||
|
||||
console.log('[Popup] Found', serverDocs.length, 'documents on server');
|
||||
for (const doc of serverDocs) {
|
||||
console.log('[Popup] Server doc:', doc.id, doc.filename, 'status:', doc.status, doc.statusText);
|
||||
}
|
||||
|
||||
// Sync each server document to local proxy
|
||||
for (const doc of serverDocs) {
|
||||
console.log('[Popup] Syncing doc', doc.id, 'to local proxy...');
|
||||
const syncResult = await chrome.runtime.sendMessage({
|
||||
action: 'syncFromServer',
|
||||
binectDocumentId: doc.id,
|
||||
filename: doc.filename,
|
||||
binectStatusCode: doc.status,
|
||||
binectStatusText: doc.statusText,
|
||||
price: doc.price,
|
||||
recipientAddress: doc.recipientAddress,
|
||||
errorDetails: doc.errorDetails
|
||||
});
|
||||
console.log('[Popup] Sync result for doc', doc.id, ':', syncResult);
|
||||
}
|
||||
|
||||
console.log('[Popup] Server sync complete');
|
||||
} catch (error) {
|
||||
console.error('[Popup] Server sync error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check recent downloads for PDFs (fallback mechanism)
|
||||
*/
|
||||
@@ -428,33 +513,44 @@ function renderPDFItem(pdf: DocumentProxy, section: 'pending' | 'basket' | 'prod
|
||||
|
||||
let actionsHtml = '';
|
||||
|
||||
// Check if document can be deleted from server (erroneous or canceled)
|
||||
const canDeleteFromServer = pdf.binectDocumentId && (pdf.binectStatusCode === 7 || pdf.binectStatusCode === 6);
|
||||
|
||||
switch (section) {
|
||||
case 'pending':
|
||||
actionsHtml = `
|
||||
<button class="btn-send-item" data-id="${escapeHtml(pdf.id)}" ${pdf.binectStatus === 'uploading' ? 'disabled' : ''}>
|
||||
${pdf.binectStatus === 'uploading' ? 'Uploading...' : (pdf.binectStatus === 'failed' ? 'Retry' : 'Upload')}
|
||||
</button>
|
||||
<button class="btn-archive" data-id="${escapeHtml(pdf.id)}">Archive</button>
|
||||
${canDeleteFromServer
|
||||
? `<button class="btn-delete-server" data-id="${escapeHtml(pdf.id)}">Delete from server</button>`
|
||||
: `<button class="btn-archive" data-id="${escapeHtml(pdf.id)}">Archive</button>`
|
||||
}
|
||||
`;
|
||||
break;
|
||||
case 'basket':
|
||||
// No archive button for in_basket - these are active documents
|
||||
actionsHtml = `
|
||||
<button class="btn-order-item" data-id="${escapeHtml(pdf.id)}" ${pdf.binectStatus === 'ordering' ? 'disabled' : ''}>
|
||||
${pdf.binectStatus === 'ordering' ? 'Sending...' : 'Send'}
|
||||
</button>
|
||||
<button class="btn-archive" data-id="${escapeHtml(pdf.id)}">Archive</button>
|
||||
`;
|
||||
break;
|
||||
case 'production':
|
||||
// Archive button only
|
||||
actionsHtml = `
|
||||
<button class="btn-archive" data-id="${escapeHtml(pdf.id)}">Archive</button>
|
||||
`;
|
||||
// No archive button for in_production - these are active documents
|
||||
actionsHtml = '';
|
||||
break;
|
||||
case 'completed':
|
||||
actionsHtml = `
|
||||
<button class="btn-archive" data-id="${escapeHtml(pdf.id)}">Archive</button>
|
||||
`;
|
||||
// For sent/canceled documents - offer delete from server if applicable
|
||||
if (canDeleteFromServer) {
|
||||
actionsHtml = `
|
||||
<button class="btn-delete-server" data-id="${escapeHtml(pdf.id)}">Delete from server</button>
|
||||
`;
|
||||
} else {
|
||||
actionsHtml = `
|
||||
<button class="btn-archive" data-id="${escapeHtml(pdf.id)}">Archive</button>
|
||||
`;
|
||||
}
|
||||
break;
|
||||
case 'archived':
|
||||
actionsHtml = `
|
||||
@@ -526,6 +622,14 @@ function setupPDFListEventListeners() {
|
||||
});
|
||||
});
|
||||
|
||||
// Delete from server buttons
|
||||
pdfList.querySelectorAll('.btn-delete-server').forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
const id = (e.target as HTMLElement).dataset.id;
|
||||
if (id) handleDeleteFromServer(id);
|
||||
});
|
||||
});
|
||||
|
||||
// Delete/Remove buttons
|
||||
pdfList.querySelectorAll('.btn-dismiss').forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
@@ -647,13 +751,14 @@ async function handleSendPDF(id: string) {
|
||||
currentCredentials.password
|
||||
);
|
||||
|
||||
// Track successful transfer
|
||||
// Track transfer
|
||||
await addTrackingEntry({
|
||||
timestamp: Date.now(),
|
||||
sourceDomain: pdf.sourceDomain,
|
||||
destinationUrl: 'https://api.binect.de/binectapi/v1/documents',
|
||||
pdfSize: pdfBytes.byteLength,
|
||||
result: 'success'
|
||||
result: document.status.code === 7 ? 'failure' : 'success',
|
||||
errorMessage: document.status.code === 7 ? document.status.text : undefined
|
||||
});
|
||||
|
||||
// Update last use timestamp
|
||||
@@ -672,27 +777,57 @@ async function handleSendPDF(id: string) {
|
||||
meta.recipientAddress = document.letter.letterData.recipientAddress;
|
||||
}
|
||||
|
||||
// Update status to in_basket (document is now SHIPPABLE)
|
||||
await chrome.runtime.sendMessage({
|
||||
action: 'updatePDFStatus',
|
||||
id,
|
||||
status: 'in_basket',
|
||||
meta
|
||||
});
|
||||
// Check if document is erroneous (status 7)
|
||||
if (document.status.code === 7) {
|
||||
// Extract error message
|
||||
let errorMessage = document.status.text || 'Document has errors';
|
||||
if (document.letter?.errors && document.letter.errors.length > 0) {
|
||||
errorMessage = document.letter.errors.map(e => e.text || e.blankText).join('; ');
|
||||
}
|
||||
meta.errorMessage = errorMessage;
|
||||
|
||||
// Update local state
|
||||
pdf.binectStatus = 'in_basket';
|
||||
pdf.binectDocumentId = document.id;
|
||||
pdf.binectStatusCode = document.status.code;
|
||||
pdf.binectStatusText = document.status.text;
|
||||
pdf.contentHash = contentHash;
|
||||
if (document.letter?.letterData) {
|
||||
pdf.price = document.letter.letterData.price?.priceAfterTax;
|
||||
pdf.recipientAddress = document.letter.letterData.recipientAddress;
|
||||
// Update status to failed (erroneous)
|
||||
await chrome.runtime.sendMessage({
|
||||
action: 'updatePDFStatus',
|
||||
id,
|
||||
status: 'failed',
|
||||
meta
|
||||
});
|
||||
|
||||
// Update local state
|
||||
pdf.binectStatus = 'failed';
|
||||
pdf.binectDocumentId = document.id;
|
||||
pdf.binectStatusCode = document.status.code;
|
||||
pdf.binectStatusText = document.status.text;
|
||||
pdf.contentHash = contentHash;
|
||||
pdf.errorMessage = errorMessage;
|
||||
renderPDFList();
|
||||
|
||||
showStatus(`Document has errors: ${errorMessage}`, 'error');
|
||||
} else {
|
||||
// Document is shippable (status 2) - in basket
|
||||
// Update status to in_basket
|
||||
await chrome.runtime.sendMessage({
|
||||
action: 'updatePDFStatus',
|
||||
id,
|
||||
status: 'in_basket',
|
||||
meta
|
||||
});
|
||||
|
||||
// Update local state
|
||||
pdf.binectStatus = 'in_basket';
|
||||
pdf.binectDocumentId = document.id;
|
||||
pdf.binectStatusCode = document.status.code;
|
||||
pdf.binectStatusText = document.status.text;
|
||||
pdf.contentHash = contentHash;
|
||||
if (document.letter?.letterData) {
|
||||
pdf.price = document.letter.letterData.price?.priceAfterTax;
|
||||
pdf.recipientAddress = document.letter.letterData.recipientAddress;
|
||||
}
|
||||
renderPDFList();
|
||||
|
||||
showStatus(`Uploaded! Ready to send (${(pdf.price || 0) / 100} €)`, 'success');
|
||||
}
|
||||
renderPDFList();
|
||||
|
||||
showStatus(`Uploaded! Ready to order (${(pdf.price || 0) / 100} €)`, 'success');
|
||||
|
||||
// Start auto-refresh sequence
|
||||
startAutoRefresh();
|
||||
@@ -961,6 +1096,42 @@ async function handleRestorePDF(id: string) {
|
||||
setTimeout(() => hideStatus(), 2000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle delete from server (for erroneous or canceled documents)
|
||||
*/
|
||||
async function handleDeleteFromServer(id: string) {
|
||||
const pdf = pdfQueue.find(p => p.id === id);
|
||||
if (!pdf || !currentCredentials || !pdf.binectDocumentId) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await chrome.runtime.sendMessage({
|
||||
action: 'deleteServerDocument',
|
||||
documentId: pdf.binectDocumentId,
|
||||
username: currentCredentials.username,
|
||||
password: currentCredentials.password
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.error || 'Failed to delete from server');
|
||||
}
|
||||
|
||||
// Remove from local queue after successful server deletion
|
||||
await chrome.runtime.sendMessage({ action: 'removePDF', id });
|
||||
pdfQueue = pdfQueue.filter(p => p.id !== id);
|
||||
renderPDFList();
|
||||
|
||||
showStatus('Document deleted from server', 'success');
|
||||
setTimeout(() => hideStatus(), 2000);
|
||||
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Delete failed';
|
||||
showStatus(errorMessage, 'error');
|
||||
setTimeout(() => hideStatus(), 3000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle delete PDF (permanently remove)
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user