Release 0.1: Complete BinectChrome implementation

Implements all requirements from ProductRequirementsDocument.md:
- PDF detection via Chrome Downloads API
- Secure credential storage with AES-GCM encryption
- Binect API integration for PDF uploads
- Popup UI with Binect branding
- Local transfer tracking (500 entry cap)
- Help page with tracking view and CSV export
- 60-day credential retention with auto-expiry
- Accessibility compliance (WCAG 2.1 AA)

Technical implementation:
- Chrome Extension Manifest V3
- TypeScript with strict mode
- Webpack build system
- Jest test suite (22/22 passing)
- ESLint configured (0 errors)

Build output: 13 KB total (production minified)
Test coverage: crypto, pdf-detector, tracker, binect-api

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-13 00:30:39 +01:00
parent 8f85c51d4e
commit b09290cb83
43 changed files with 12078 additions and 2 deletions

137
src/utils/binect-api.ts Normal file
View File

@@ -0,0 +1,137 @@
/**
* Binect API client
*/
const API_BASE_URL = 'https://api.binect.de';
export interface AuthToken {
token: string;
expiresAt: string;
}
export interface UploadResult {
documentId: string;
status: string;
uploadedAt: string;
}
export class BinectAPIError extends Error {
constructor(
message: string,
public statusCode?: number,
public response?: unknown
) {
super(message);
this.name = 'BinectAPIError';
}
}
/**
* Authenticate with Binect API
*/
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'}`
);
}
}
/**
* Upload PDF to Binect
*/
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);
const response = await fetch(`${API_BASE_URL}/documents/upload`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`
},
body: formData
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
if (response.status === 401) {
throw new BinectAPIError('Authentication required', 401, errorData);
}
if (response.status === 400) {
throw new BinectAPIError(
errorData.error || 'Invalid file format',
400,
errorData
);
}
if (response.status === 413) {
throw new BinectAPIError('File size exceeds limit', 413, errorData);
}
throw new BinectAPIError(
`Upload failed: ${response.statusText}`,
response.status,
errorData
);
}
return await response.json();
} catch (error) {
if (error instanceof BinectAPIError) {
throw error;
}
throw new BinectAPIError(
`Network error: ${error instanceof Error ? error.message : 'Unknown error'}`
);
}
}
/**
* Test API connectivity
*/
export async function testConnection(): Promise<boolean> {
try {
const response = await fetch(`${API_BASE_URL}/health`, {
method: 'GET'
});
return response.ok;
} catch {
return false;
}
}