2026-01-13 00:30:39 +01:00
|
|
|
/**
|
|
|
|
|
* Binect API client
|
2026-01-14 16:50:57 +01:00
|
|
|
* Based on Binect API v1 (Swagger spec: specs/v1_swagger_api_kernel.json)
|
|
|
|
|
*
|
|
|
|
|
* Authentication: HTTP Basic Authentication
|
|
|
|
|
* Base path: /binectapi/v1
|
2026-01-13 00:30:39 +01:00
|
|
|
*/
|
|
|
|
|
|
2026-01-14 16:50:57 +01:00
|
|
|
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;
|
|
|
|
|
}>;
|
|
|
|
|
};
|
2026-01-13 00:30:39 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-14 16:50:57 +01:00
|
|
|
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';
|
2026-01-13 00:30:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export class BinectAPIError extends Error {
|
|
|
|
|
constructor(
|
|
|
|
|
message: string,
|
|
|
|
|
public statusCode?: number,
|
|
|
|
|
public response?: unknown
|
|
|
|
|
) {
|
|
|
|
|
super(message);
|
|
|
|
|
this.name = 'BinectAPIError';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-01-14 16:50:57 +01:00
|
|
|
* Create HTTP Basic Authentication header value
|
2026-01-13 00:30:39 +01:00
|
|
|
*/
|
2026-01-14 16:50:57 +01:00
|
|
|
function createBasicAuthHeader(username: string, password: string): string {
|
|
|
|
|
const credentials = `${username}:${password}`;
|
|
|
|
|
const base64Credentials = btoa(credentials);
|
|
|
|
|
return `Basic ${base64Credentials}`;
|
|
|
|
|
}
|
2026-01-13 00:30:39 +01:00
|
|
|
|
2026-01-14 16:50:57 +01:00
|
|
|
/**
|
|
|
|
|
* 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]);
|
2026-01-13 00:30:39 +01:00
|
|
|
}
|
2026-01-14 16:50:57 +01:00
|
|
|
return btoa(binary);
|
2026-01-13 00:30:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-01-14 16:50:57 +01:00
|
|
|
* 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
|
2026-01-13 00:30:39 +01:00
|
|
|
*/
|
|
|
|
|
export async function uploadPDF(
|
|
|
|
|
pdfData: ArrayBuffer,
|
|
|
|
|
filename: string,
|
2026-01-14 16:50:57 +01:00
|
|
|
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);
|
|
|
|
|
|
2026-01-13 00:30:39 +01:00
|
|
|
try {
|
2026-01-14 16:50:57 +01:00
|
|
|
// 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);
|
2026-01-13 00:30:39 +01:00
|
|
|
|
2026-01-14 16:50:57 +01:00
|
|
|
// Make request with HTTP Basic Authentication
|
|
|
|
|
const response = await fetch(`${API_BASE_URL}/documents`, {
|
2026-01-13 00:30:39 +01:00
|
|
|
method: 'POST',
|
|
|
|
|
headers: {
|
2026-01-14 16:50:57 +01:00
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
'Authorization': createBasicAuthHeader(username, password)
|
2026-01-13 00:30:39 +01:00
|
|
|
},
|
2026-01-14 16:50:57 +01:00
|
|
|
body: JSON.stringify(requestBody)
|
2026-01-13 00:30:39 +01:00
|
|
|
});
|
|
|
|
|
|
2026-01-14 16:50:57 +01:00
|
|
|
console.log('[Binect API] Upload response status:', response.status);
|
|
|
|
|
console.log('[Binect API] Response content-type:', response.headers.get('content-type'));
|
|
|
|
|
|
2026-01-13 00:30:39 +01:00
|
|
|
if (!response.ok) {
|
2026-01-14 16:50:57 +01:00
|
|
|
const errorText = await response.text();
|
|
|
|
|
console.error('[Binect API] Upload error response:', errorText);
|
2026-01-13 00:30:39 +01:00
|
|
|
|
2026-01-14 16:50:57 +01:00
|
|
|
if (response.status === 401 || response.status === 403) {
|
|
|
|
|
throw new BinectAPIError('Invalid credentials', response.status);
|
2026-01-13 00:30:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (response.status === 400) {
|
|
|
|
|
throw new BinectAPIError(
|
2026-01-14 16:50:57 +01:00
|
|
|
'Invalid request. Please check the PDF format and size.',
|
|
|
|
|
400
|
2026-01-13 00:30:39 +01:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (response.status === 413) {
|
2026-01-14 16:50:57 +01:00
|
|
|
throw new BinectAPIError('File size exceeds limit (12 MB)', 413);
|
2026-01-13 00:30:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
throw new BinectAPIError(
|
|
|
|
|
`Upload failed: ${response.statusText}`,
|
|
|
|
|
response.status,
|
2026-01-14 16:50:57 +01:00
|
|
|
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
|
2026-01-13 00:30:39 +01:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-14 16:50:57 +01:00
|
|
|
return document;
|
2026-01-13 00:30:39 +01:00
|
|
|
} catch (error) {
|
2026-01-14 16:50:57 +01:00
|
|
|
console.error('[Binect API] Upload error:', error);
|
|
|
|
|
|
2026-01-13 00:30:39 +01:00
|
|
|
if (error instanceof BinectAPIError) {
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
2026-01-14 16:50:57 +01:00
|
|
|
|
|
|
|
|
// 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.`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-13 00:30:39 +01:00
|
|
|
throw new BinectAPIError(
|
|
|
|
|
`Network error: ${error instanceof Error ? error.message : 'Unknown error'}`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-01-14 16:50:57 +01:00
|
|
|
* Test API connectivity by fetching account information
|
|
|
|
|
*
|
|
|
|
|
* @param username - Binect username
|
|
|
|
|
* @param password - Binect password
|
|
|
|
|
* @returns true if authentication successful, false otherwise
|
2026-01-13 00:30:39 +01:00
|
|
|
*/
|
2026-01-14 16:50:57 +01:00
|
|
|
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`);
|
|
|
|
|
|
2026-01-13 00:30:39 +01:00
|
|
|
try {
|
2026-01-14 16:50:57 +01:00
|
|
|
const response = await fetch(`${API_BASE_URL}/accounts`, {
|
|
|
|
|
method: 'GET',
|
|
|
|
|
headers: {
|
|
|
|
|
'Authorization': createBasicAuthHeader(username, password)
|
|
|
|
|
}
|
2026-01-13 00:30:39 +01:00
|
|
|
});
|
2026-01-14 16:50:57 +01:00
|
|
|
|
|
|
|
|
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);
|
2026-01-13 00:30:39 +01:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|