/** * 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/binectapi/v1'; 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 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 { constructor( message: string, public statusCode?: number, public response?: unknown ) { super(message); this.name = 'BinectAPIError'; } } /** * Create HTTP Basic Authentication header value */ function createBasicAuthHeader(username: string, password: string): string { const credentials = `${username}:${password}`; const base64Credentials = btoa(credentials); return `Basic ${base64Credentials}`; } /** * 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, username: string, password: string, options?: Options ): Promise { 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); 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: { 'Content-Type': 'application/json', 'Authorization': createBasicAuthHeader(username, password) }, body: JSON.stringify(requestBody) }); console.log('[Binect API] Upload response status:', response.status); console.log('[Binect API] Response content-type:', response.headers.get('content-type')); 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( 'Invalid request. Please check the PDF format and size.', 400 ); } if (response.status === 413) { throw new BinectAPIError('File size exceeds limit (12 MB)', 413); } throw new BinectAPIError( `Upload failed: ${response.statusText}`, response.status, errorText ); } 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'}` ); } } /** * 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(username: string, password: string): Promise { 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}/accounts`, { method: 'GET', headers: { 'Authorization': createBasicAuthHeader(username, password) } }); 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; } }