pdfdancer-client-typescript 1.0.13 → 1.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +26 -18
- package/.github/workflows/ci.yml +51 -2
- package/.github/workflows/daily-tests.yml +54 -0
- package/README.md +50 -6
- package/dist/__tests__/e2e/test-helpers.d.ts.map +1 -1
- package/dist/__tests__/e2e/test-helpers.js +17 -5
- package/dist/__tests__/e2e/test-helpers.js.map +1 -1
- package/dist/fingerprint.d.ts.map +1 -1
- package/dist/fingerprint.js +16 -5
- package/dist/fingerprint.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/models.d.ts +18 -15
- package/dist/models.d.ts.map +1 -1
- package/dist/models.js +21 -18
- package/dist/models.js.map +1 -1
- package/dist/pdfdancer_v1.d.ts +71 -3
- package/dist/pdfdancer_v1.d.ts.map +1 -1
- package/dist/pdfdancer_v1.js +301 -35
- package/dist/pdfdancer_v1.js.map +1 -1
- package/docs/openapi.yml +637 -73
- package/jest.config.js +1 -1
- package/package.json +2 -2
- package/src/__tests__/e2e/acroform.test.ts +58 -0
- package/src/__tests__/e2e/form_x_object.test.ts +29 -0
- package/src/__tests__/e2e/image-showcase.test.ts +6 -6
- package/src/__tests__/e2e/image.test.ts +39 -5
- package/src/__tests__/e2e/line-showcase.test.ts +6 -11
- package/src/__tests__/e2e/line.test.ts +63 -7
- package/src/__tests__/e2e/page-showcase.test.ts +12 -12
- package/src/__tests__/e2e/page.test.ts +3 -3
- package/src/__tests__/e2e/paragraph-showcase.test.ts +0 -8
- package/src/__tests__/e2e/paragraph.test.ts +64 -8
- package/src/__tests__/e2e/path.test.ts +33 -4
- package/src/__tests__/e2e/snapshot-showcase.test.ts +10 -10
- package/src/__tests__/e2e/snapshot.test.ts +18 -18
- package/src/__tests__/e2e/test-helpers.ts +16 -5
- package/src/__tests__/e2e/token_from_env.test.ts +0 -15
- package/src/__tests__/retry-mechanism.test.ts +420 -0
- package/src/fingerprint.ts +20 -7
- package/src/index.ts +3 -1
- package/src/models.ts +21 -17
- package/src/pdfdancer_v1.ts +467 -71
package/dist/pdfdancer_v1.js
CHANGED
|
@@ -23,6 +23,93 @@ const DEFAULT_TOLERANCE = 0.01;
|
|
|
23
23
|
const DEBUG = (process.env.PDFDANCER_CLIENT_DEBUG ?? '').toLowerCase() === 'true' ||
|
|
24
24
|
(process.env.PDFDANCER_CLIENT_DEBUG ?? '') === '1' ||
|
|
25
25
|
(process.env.PDFDANCER_CLIENT_DEBUG ?? '').toLowerCase() === 'yes';
|
|
26
|
+
/**
|
|
27
|
+
* Default retry configuration
|
|
28
|
+
*/
|
|
29
|
+
const DEFAULT_RETRY_CONFIG = {
|
|
30
|
+
maxRetries: 3,
|
|
31
|
+
initialDelay: 1000,
|
|
32
|
+
maxDelay: 10000,
|
|
33
|
+
retryableStatusCodes: [429, 500, 502, 503, 504],
|
|
34
|
+
retryOnNetworkError: true,
|
|
35
|
+
backoffMultiplier: 2,
|
|
36
|
+
useJitter: true
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Static helper function for retry logic with exponential backoff.
|
|
40
|
+
* Used by static methods that don't have access to instance retry config.
|
|
41
|
+
*/
|
|
42
|
+
async function fetchWithRetry(url,
|
|
43
|
+
// eslint-disable-next-line no-undef
|
|
44
|
+
options, retryConfig, context = 'request') {
|
|
45
|
+
let lastError = null;
|
|
46
|
+
let lastResponse = null;
|
|
47
|
+
for (let attempt = 0; attempt <= retryConfig.maxRetries; attempt++) {
|
|
48
|
+
try {
|
|
49
|
+
const response = await fetch(url, options);
|
|
50
|
+
// Check if we should retry based on status code
|
|
51
|
+
if (!response.ok && retryConfig.retryableStatusCodes.includes(response.status)) {
|
|
52
|
+
lastResponse = response;
|
|
53
|
+
// If this is not the last attempt, wait and retry
|
|
54
|
+
if (attempt < retryConfig.maxRetries) {
|
|
55
|
+
const delay = calculateRetryDelay(attempt, retryConfig);
|
|
56
|
+
if (DEBUG) {
|
|
57
|
+
console.log(`${Date.now() / 1000}|Retry attempt ${attempt + 1}/${retryConfig.maxRetries} for ${context} after ${delay}ms (status: ${response.status})`);
|
|
58
|
+
}
|
|
59
|
+
await sleep(delay);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Request succeeded or non-retryable error
|
|
64
|
+
return response;
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
lastError = error;
|
|
68
|
+
// Check if this is a network error and we should retry
|
|
69
|
+
if (retryConfig.retryOnNetworkError && attempt < retryConfig.maxRetries) {
|
|
70
|
+
const delay = calculateRetryDelay(attempt, retryConfig);
|
|
71
|
+
if (DEBUG) {
|
|
72
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
73
|
+
console.log(`${Date.now() / 1000}|Retry attempt ${attempt + 1}/${retryConfig.maxRetries} for ${context} after ${delay}ms (error: ${errorMessage})`);
|
|
74
|
+
}
|
|
75
|
+
await sleep(delay);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
// Non-retryable error or last attempt
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// If we exhausted all retries due to retryable status codes, return the last response
|
|
83
|
+
if (lastResponse) {
|
|
84
|
+
return lastResponse;
|
|
85
|
+
}
|
|
86
|
+
// If we exhausted all retries due to network errors, throw the last error
|
|
87
|
+
if (lastError) {
|
|
88
|
+
throw lastError;
|
|
89
|
+
}
|
|
90
|
+
// This should never happen, but just in case
|
|
91
|
+
throw new Error('Unexpected retry exhaustion');
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Calculates the delay for the next retry attempt using exponential backoff.
|
|
95
|
+
*/
|
|
96
|
+
function calculateRetryDelay(attemptNumber, retryConfig) {
|
|
97
|
+
// Calculate base delay: initialDelay * (backoffMultiplier ^ attemptNumber)
|
|
98
|
+
let delay = retryConfig.initialDelay * Math.pow(retryConfig.backoffMultiplier, attemptNumber);
|
|
99
|
+
// Cap at maxDelay
|
|
100
|
+
delay = Math.min(delay, retryConfig.maxDelay);
|
|
101
|
+
// Add jitter if enabled (randomize between 50% and 100% of calculated delay)
|
|
102
|
+
if (retryConfig.useJitter) {
|
|
103
|
+
delay = delay * (0.5 + Math.random() * 0.5);
|
|
104
|
+
}
|
|
105
|
+
return Math.floor(delay);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Sleep for the specified number of milliseconds.
|
|
109
|
+
*/
|
|
110
|
+
function sleep(ms) {
|
|
111
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
112
|
+
}
|
|
26
113
|
/**
|
|
27
114
|
* Generate a timestamp string in the format expected by the API.
|
|
28
115
|
* Format: YYYY-MM-DDTHH:MM:SS.ffffffZ (with microseconds)
|
|
@@ -214,6 +301,75 @@ class PageClient {
|
|
|
214
301
|
async getSnapshot(types) {
|
|
215
302
|
return this._client.getPageSnapshot(this._pageIndex, types);
|
|
216
303
|
}
|
|
304
|
+
// Singular convenience methods - return the first element or null
|
|
305
|
+
async selectPath() {
|
|
306
|
+
const paths = await this.selectPaths();
|
|
307
|
+
return paths.length > 0 ? paths[0] : null;
|
|
308
|
+
}
|
|
309
|
+
async selectPathAt(x, y, tolerance = 0) {
|
|
310
|
+
const paths = await this.selectPathsAt(x, y, tolerance);
|
|
311
|
+
return paths.length > 0 ? paths[0] : null;
|
|
312
|
+
}
|
|
313
|
+
async selectImage() {
|
|
314
|
+
const images = await this.selectImages();
|
|
315
|
+
return images.length > 0 ? images[0] : null;
|
|
316
|
+
}
|
|
317
|
+
async selectImageAt(x, y, tolerance = 0) {
|
|
318
|
+
const images = await this.selectImagesAt(x, y, tolerance);
|
|
319
|
+
return images.length > 0 ? images[0] : null;
|
|
320
|
+
}
|
|
321
|
+
async selectForm() {
|
|
322
|
+
const forms = await this.selectForms();
|
|
323
|
+
return forms.length > 0 ? forms[0] : null;
|
|
324
|
+
}
|
|
325
|
+
async selectFormAt(x, y, tolerance = 0) {
|
|
326
|
+
const forms = await this.selectFormsAt(x, y, tolerance);
|
|
327
|
+
return forms.length > 0 ? forms[0] : null;
|
|
328
|
+
}
|
|
329
|
+
async selectFormField() {
|
|
330
|
+
const fields = await this.selectFormFields();
|
|
331
|
+
return fields.length > 0 ? fields[0] : null;
|
|
332
|
+
}
|
|
333
|
+
async selectFormFieldAt(x, y, tolerance = 0) {
|
|
334
|
+
const fields = await this.selectFormFieldsAt(x, y, tolerance);
|
|
335
|
+
return fields.length > 0 ? fields[0] : null;
|
|
336
|
+
}
|
|
337
|
+
async selectFormFieldByName(fieldName) {
|
|
338
|
+
const fields = await this.selectFormFieldsByName(fieldName);
|
|
339
|
+
return fields.length > 0 ? fields[0] : null;
|
|
340
|
+
}
|
|
341
|
+
async selectParagraph() {
|
|
342
|
+
const paragraphs = await this.selectParagraphs();
|
|
343
|
+
return paragraphs.length > 0 ? paragraphs[0] : null;
|
|
344
|
+
}
|
|
345
|
+
async selectParagraphStartingWith(text) {
|
|
346
|
+
const paragraphs = await this.selectParagraphsStartingWith(text);
|
|
347
|
+
return paragraphs.length > 0 ? paragraphs[0] : null;
|
|
348
|
+
}
|
|
349
|
+
async selectParagraphMatching(pattern) {
|
|
350
|
+
const paragraphs = await this.selectParagraphsMatching(pattern);
|
|
351
|
+
return paragraphs.length > 0 ? paragraphs[0] : null;
|
|
352
|
+
}
|
|
353
|
+
async selectParagraphAt(x, y, tolerance = DEFAULT_TOLERANCE) {
|
|
354
|
+
const paragraphs = await this.selectParagraphsAt(x, y, tolerance);
|
|
355
|
+
return paragraphs.length > 0 ? paragraphs[0] : null;
|
|
356
|
+
}
|
|
357
|
+
async selectTextLine() {
|
|
358
|
+
const lines = await this.selectTextLines();
|
|
359
|
+
return lines.length > 0 ? lines[0] : null;
|
|
360
|
+
}
|
|
361
|
+
async selectTextLineStartingWith(text) {
|
|
362
|
+
const lines = await this.selectTextLinesStartingWith(text);
|
|
363
|
+
return lines.length > 0 ? lines[0] : null;
|
|
364
|
+
}
|
|
365
|
+
async selectTextLineMatching(pattern) {
|
|
366
|
+
const lines = await this.selectTextLinesMatching(pattern);
|
|
367
|
+
return lines.length > 0 ? lines[0] : null;
|
|
368
|
+
}
|
|
369
|
+
async selectTextLineAt(x, y, tolerance = DEFAULT_TOLERANCE) {
|
|
370
|
+
const lines = await this.selectTextLinesAt(x, y, tolerance);
|
|
371
|
+
return lines.length > 0 ? lines[0] : null;
|
|
372
|
+
}
|
|
217
373
|
}
|
|
218
374
|
// noinspection ExceptionCaughtLocallyJS,JSUnusedLocalSymbols
|
|
219
375
|
/**
|
|
@@ -229,7 +385,7 @@ class PDFDancer {
|
|
|
229
385
|
* This constructor initializes the client, uploads the PDF data to open
|
|
230
386
|
* a new session, and prepares the client for PDF manipulation operations.
|
|
231
387
|
*/
|
|
232
|
-
constructor(token, pdfData, baseUrl = null, readTimeout =
|
|
388
|
+
constructor(token, pdfData, baseUrl = null, readTimeout = 60000, retryConfig) {
|
|
233
389
|
// Snapshot caches for optimizing find operations
|
|
234
390
|
this._documentSnapshotCache = null;
|
|
235
391
|
this._pageSnapshotCache = new Map();
|
|
@@ -251,6 +407,11 @@ class PDFDancer {
|
|
|
251
407
|
this._token = token.trim();
|
|
252
408
|
this._baseUrl = resolvedBaseUrl.replace(/\/$/, ''); // Remove trailing slash
|
|
253
409
|
this._readTimeout = readTimeout;
|
|
410
|
+
// Merge retry config with defaults
|
|
411
|
+
this._retryConfig = {
|
|
412
|
+
...DEFAULT_RETRY_CONFIG,
|
|
413
|
+
...retryConfig
|
|
414
|
+
};
|
|
254
415
|
// Process PDF data with validation
|
|
255
416
|
this._pdfBytes = this._processPdfData(pdfData);
|
|
256
417
|
// Initialize caches
|
|
@@ -266,16 +427,16 @@ class PDFDancer {
|
|
|
266
427
|
this._sessionId = await this._createSession();
|
|
267
428
|
return this;
|
|
268
429
|
}
|
|
269
|
-
static async open(pdfData, token, baseUrl, timeout) {
|
|
270
|
-
const resolvedToken = token ?? process.env.PDFDANCER_TOKEN;
|
|
430
|
+
static async open(pdfData, token, baseUrl, timeout, retryConfig) {
|
|
271
431
|
const resolvedBaseUrl = baseUrl ??
|
|
272
432
|
process.env.PDFDANCER_BASE_URL ??
|
|
273
433
|
"https://api.pdfdancer.com";
|
|
274
|
-
const resolvedTimeout = timeout ??
|
|
434
|
+
const resolvedTimeout = timeout ?? 60000;
|
|
435
|
+
let resolvedToken = token?.trim() ?? process.env.PDFDANCER_TOKEN?.trim() ?? null;
|
|
275
436
|
if (!resolvedToken) {
|
|
276
|
-
|
|
437
|
+
resolvedToken = await PDFDancer._obtainAnonymousToken(resolvedBaseUrl, resolvedTimeout);
|
|
277
438
|
}
|
|
278
|
-
const client = new PDFDancer(resolvedToken, pdfData, resolvedBaseUrl, resolvedTimeout);
|
|
439
|
+
const client = new PDFDancer(resolvedToken, pdfData, resolvedBaseUrl, resolvedTimeout, retryConfig);
|
|
279
440
|
return await client.init();
|
|
280
441
|
}
|
|
281
442
|
/**
|
|
@@ -287,16 +448,17 @@ class PDFDancer {
|
|
|
287
448
|
* @param options.initialPageCount Number of initial pages (default: 1)
|
|
288
449
|
* @param token Authentication token (optional, can use PDFDANCER_TOKEN env var)
|
|
289
450
|
* @param baseUrl Base URL for the PDFDancer API (optional)
|
|
290
|
-
* @param timeout Request timeout in milliseconds (default:
|
|
451
|
+
* @param timeout Request timeout in milliseconds (default: 60000)
|
|
452
|
+
* @param retryConfig Retry configuration (optional, uses defaults if not specified)
|
|
291
453
|
*/
|
|
292
|
-
static async new(options, token, baseUrl, timeout) {
|
|
293
|
-
const resolvedToken = token ?? process.env.PDFDANCER_TOKEN;
|
|
454
|
+
static async new(options, token, baseUrl, timeout, retryConfig) {
|
|
294
455
|
const resolvedBaseUrl = baseUrl ??
|
|
295
456
|
process.env.PDFDANCER_BASE_URL ??
|
|
296
457
|
"https://api.pdfdancer.com";
|
|
297
|
-
const resolvedTimeout = timeout ??
|
|
458
|
+
const resolvedTimeout = timeout ?? 60000;
|
|
459
|
+
let resolvedToken = token?.trim() ?? process.env.PDFDANCER_TOKEN?.trim() ?? null;
|
|
298
460
|
if (!resolvedToken) {
|
|
299
|
-
|
|
461
|
+
resolvedToken = await PDFDancer._obtainAnonymousToken(resolvedBaseUrl, resolvedTimeout);
|
|
300
462
|
}
|
|
301
463
|
let createRequest;
|
|
302
464
|
try {
|
|
@@ -313,8 +475,8 @@ class PDFDancer {
|
|
|
313
475
|
const url = `${base}/${endpoint}`;
|
|
314
476
|
// Generate fingerprint for this request
|
|
315
477
|
const fingerprint = await (0, fingerprint_1.generateFingerprint)();
|
|
316
|
-
// Make request to create endpoint
|
|
317
|
-
const response = await
|
|
478
|
+
// Make request to create endpoint with retry logic
|
|
479
|
+
const response = await fetchWithRetry(url, {
|
|
318
480
|
method: 'POST',
|
|
319
481
|
headers: {
|
|
320
482
|
'Authorization': `Bearer ${resolvedToken}`,
|
|
@@ -324,7 +486,7 @@ class PDFDancer {
|
|
|
324
486
|
},
|
|
325
487
|
body: JSON.stringify(createRequest.toDict()),
|
|
326
488
|
signal: resolvedTimeout > 0 ? AbortSignal.timeout(resolvedTimeout) : undefined
|
|
327
|
-
});
|
|
489
|
+
}, DEFAULT_RETRY_CONFIG, 'POST /session/new');
|
|
328
490
|
logGeneratedAtHeader(response, 'POST', '/session/new');
|
|
329
491
|
if (!response.ok) {
|
|
330
492
|
const errorText = await response.text();
|
|
@@ -340,6 +502,11 @@ class PDFDancer {
|
|
|
340
502
|
client._readTimeout = resolvedTimeout;
|
|
341
503
|
client._pdfBytes = new Uint8Array();
|
|
342
504
|
client._sessionId = sessionId;
|
|
505
|
+
// Initialize retry config
|
|
506
|
+
client._retryConfig = {
|
|
507
|
+
...DEFAULT_RETRY_CONFIG,
|
|
508
|
+
...retryConfig
|
|
509
|
+
};
|
|
343
510
|
// Initialize caches
|
|
344
511
|
client._documentSnapshotCache = null;
|
|
345
512
|
client._pageSnapshotCache = new Map();
|
|
@@ -354,6 +521,39 @@ class PDFDancer {
|
|
|
354
521
|
throw new exceptions_1.HttpClientException(`Failed to create new PDF: ${errorMessage}`, undefined, error);
|
|
355
522
|
}
|
|
356
523
|
}
|
|
524
|
+
static async _obtainAnonymousToken(baseUrl, timeout = 60000) {
|
|
525
|
+
const normalizedBaseUrl = (baseUrl || "https://api.pdfdancer.com").replace(/\/+$/, '');
|
|
526
|
+
const url = `${normalizedBaseUrl}/keys/anon`;
|
|
527
|
+
try {
|
|
528
|
+
const fingerprint = await (0, fingerprint_1.generateFingerprint)();
|
|
529
|
+
const response = await fetchWithRetry(url, {
|
|
530
|
+
method: 'POST',
|
|
531
|
+
headers: {
|
|
532
|
+
'Content-Type': 'application/json',
|
|
533
|
+
'X-Fingerprint': fingerprint,
|
|
534
|
+
'X-Generated-At': generateTimestamp()
|
|
535
|
+
},
|
|
536
|
+
signal: timeout > 0 ? AbortSignal.timeout(timeout) : undefined
|
|
537
|
+
}, DEFAULT_RETRY_CONFIG, 'POST /keys/anon');
|
|
538
|
+
if (!response.ok) {
|
|
539
|
+
const errorText = await response.text().catch(() => '');
|
|
540
|
+
throw new exceptions_1.HttpClientException(`Failed to obtain anonymous token: ${errorText || `HTTP ${response.status}`}`, response);
|
|
541
|
+
}
|
|
542
|
+
const tokenPayload = await response.json().catch(() => null);
|
|
543
|
+
const tokenValue = typeof tokenPayload?.token === 'string' ? tokenPayload.token.trim() : '';
|
|
544
|
+
if (!tokenValue) {
|
|
545
|
+
throw new exceptions_1.HttpClientException("Invalid anonymous token response format", response);
|
|
546
|
+
}
|
|
547
|
+
return tokenValue;
|
|
548
|
+
}
|
|
549
|
+
catch (error) {
|
|
550
|
+
if (error instanceof exceptions_1.HttpClientException) {
|
|
551
|
+
throw error;
|
|
552
|
+
}
|
|
553
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
554
|
+
throw new exceptions_1.HttpClientException(`Failed to obtain anonymous token: ${errorMessage}`, undefined, error);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
357
557
|
/**
|
|
358
558
|
* Process PDF data from various input types with strict validation.
|
|
359
559
|
*/
|
|
@@ -379,6 +579,17 @@ class PDFDancer {
|
|
|
379
579
|
// Note: File reading will be handled asynchronously in the session creation
|
|
380
580
|
return new Uint8Array(); // Placeholder, will be replaced in _createSession
|
|
381
581
|
}
|
|
582
|
+
else if (typeof pdfData === 'string') {
|
|
583
|
+
// Handle string as filepath
|
|
584
|
+
if (!fs_1.default.existsSync(pdfData)) {
|
|
585
|
+
throw new exceptions_1.ValidationException(`PDF file not found: ${pdfData}`);
|
|
586
|
+
}
|
|
587
|
+
const fileData = new Uint8Array(fs_1.default.readFileSync(pdfData));
|
|
588
|
+
if (fileData.length === 0) {
|
|
589
|
+
throw new exceptions_1.ValidationException("PDF file is empty");
|
|
590
|
+
}
|
|
591
|
+
return fileData;
|
|
592
|
+
}
|
|
382
593
|
else {
|
|
383
594
|
throw new exceptions_1.ValidationException(`Unsupported PDF data type: ${typeof pdfData}`);
|
|
384
595
|
}
|
|
@@ -453,7 +664,7 @@ class PDFDancer {
|
|
|
453
664
|
formData.append('pdf', blob, 'document.pdf');
|
|
454
665
|
}
|
|
455
666
|
const fingerprint = await this._getFingerprint();
|
|
456
|
-
const response = await
|
|
667
|
+
const response = await this._fetchWithRetry(this._buildUrl('/session/create'), {
|
|
457
668
|
method: 'POST',
|
|
458
669
|
headers: {
|
|
459
670
|
'Authorization': `Bearer ${this._token}`,
|
|
@@ -462,7 +673,7 @@ class PDFDancer {
|
|
|
462
673
|
},
|
|
463
674
|
body: formData,
|
|
464
675
|
signal: this._readTimeout > 0 ? AbortSignal.timeout(this._readTimeout) : undefined
|
|
465
|
-
});
|
|
676
|
+
}, 'POST /session/create');
|
|
466
677
|
logGeneratedAtHeader(response, 'POST', '/session/create');
|
|
467
678
|
if (!response.ok) {
|
|
468
679
|
const errorMessage = await this._extractErrorMessage(response);
|
|
@@ -499,6 +710,15 @@ class PDFDancer {
|
|
|
499
710
|
}
|
|
500
711
|
return this._fingerprintCache;
|
|
501
712
|
}
|
|
713
|
+
/**
|
|
714
|
+
* Executes a fetch request with retry logic based on the configured retry policy.
|
|
715
|
+
* Implements exponential backoff with optional jitter.
|
|
716
|
+
*/
|
|
717
|
+
async _fetchWithRetry(url,
|
|
718
|
+
// eslint-disable-next-line no-undef
|
|
719
|
+
options, context = 'request') {
|
|
720
|
+
return fetchWithRetry(url, options, this._retryConfig, context);
|
|
721
|
+
}
|
|
502
722
|
/**
|
|
503
723
|
* Make HTTP request with session headers and error handling.
|
|
504
724
|
*/
|
|
@@ -518,12 +738,12 @@ class PDFDancer {
|
|
|
518
738
|
'X-Fingerprint': fingerprint
|
|
519
739
|
};
|
|
520
740
|
try {
|
|
521
|
-
const response = await
|
|
741
|
+
const response = await this._fetchWithRetry(url.toString(), {
|
|
522
742
|
method,
|
|
523
743
|
headers,
|
|
524
744
|
body: data ? JSON.stringify(data) : undefined,
|
|
525
745
|
signal: this._readTimeout > 0 ? AbortSignal.timeout(this._readTimeout) : undefined
|
|
526
|
-
});
|
|
746
|
+
}, `${method} ${path}`);
|
|
527
747
|
logGeneratedAtHeader(response, method, path);
|
|
528
748
|
// Handle FontNotFoundException
|
|
529
749
|
if (response.status === 404) {
|
|
@@ -1129,7 +1349,7 @@ class PDFDancer {
|
|
|
1129
1349
|
const blob = new Blob([fontData.buffer], { type: 'font/ttf' });
|
|
1130
1350
|
formData.append('ttfFile', blob, filename);
|
|
1131
1351
|
const fingerprint = await this._getFingerprint();
|
|
1132
|
-
const response = await
|
|
1352
|
+
const response = await this._fetchWithRetry(this._buildUrl('/font/register'), {
|
|
1133
1353
|
method: 'POST',
|
|
1134
1354
|
headers: {
|
|
1135
1355
|
'Authorization': `Bearer ${this._token}`,
|
|
@@ -1138,8 +1358,8 @@ class PDFDancer {
|
|
|
1138
1358
|
'X-Fingerprint': fingerprint
|
|
1139
1359
|
},
|
|
1140
1360
|
body: formData,
|
|
1141
|
-
signal: AbortSignal.timeout(
|
|
1142
|
-
});
|
|
1361
|
+
signal: AbortSignal.timeout(60000)
|
|
1362
|
+
}, 'POST /font/register');
|
|
1143
1363
|
logGeneratedAtHeader(response, 'POST', '/font/register');
|
|
1144
1364
|
if (!response.ok) {
|
|
1145
1365
|
const errorMessage = await this._extractErrorMessage(response);
|
|
@@ -1224,17 +1444,24 @@ class PDFDancer {
|
|
|
1224
1444
|
let status;
|
|
1225
1445
|
const statusData = objData.status;
|
|
1226
1446
|
if (statusData && typeof statusData === 'object') {
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1447
|
+
const fontInfoSource = statusData.fontInfoDto ?? statusData.fontRecommendation;
|
|
1448
|
+
let fontInfo;
|
|
1449
|
+
if (fontInfoSource && typeof fontInfoSource === 'object') {
|
|
1450
|
+
const documentFontName = typeof fontInfoSource.documentFontName === 'string'
|
|
1451
|
+
? fontInfoSource.documentFontName
|
|
1452
|
+
: (typeof fontInfoSource.fontName === 'string' ? fontInfoSource.fontName : '');
|
|
1453
|
+
const systemFontName = typeof fontInfoSource.systemFontName === 'string'
|
|
1454
|
+
? fontInfoSource.systemFontName
|
|
1455
|
+
: (typeof fontInfoSource.fontName === 'string' ? fontInfoSource.fontName : '');
|
|
1456
|
+
fontInfo = new models_1.DocumentFontInfo(documentFontName, systemFontName);
|
|
1236
1457
|
}
|
|
1237
|
-
|
|
1458
|
+
const modified = statusData.modified !== undefined ? Boolean(statusData.modified) : false;
|
|
1459
|
+
const encodable = statusData.encodable !== undefined ? Boolean(statusData.encodable) : true;
|
|
1460
|
+
const fontTypeValue = typeof statusData.fontType === 'string'
|
|
1461
|
+
&& Object.values(models_1.FontType).includes(statusData.fontType)
|
|
1462
|
+
? statusData.fontType
|
|
1463
|
+
: models_1.FontType.SYSTEM;
|
|
1464
|
+
status = new models_1.TextStatus(modified, encodable, fontTypeValue, fontInfo);
|
|
1238
1465
|
}
|
|
1239
1466
|
const textObject = new models_1.TextObjectRef(internalId, position, objectType, typeof objData.text === 'string' ? objData.text : undefined, typeof objData.fontName === 'string' ? objData.fontName : undefined, typeof objData.fontSize === 'number' ? objData.fontSize : undefined, lineSpacings, undefined, this._parseColor(objData.color), status);
|
|
1240
1467
|
if (Array.isArray(objData.children) && objData.children.length > 0) {
|
|
@@ -1335,10 +1562,13 @@ class PDFDancer {
|
|
|
1335
1562
|
if (Array.isArray(data.fonts)) {
|
|
1336
1563
|
for (const fontData of data.fonts) {
|
|
1337
1564
|
if (fontData && typeof fontData === 'object') {
|
|
1338
|
-
const
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1565
|
+
const documentFontName = typeof fontData.documentFontName === 'string'
|
|
1566
|
+
? fontData.documentFontName
|
|
1567
|
+
: (typeof fontData.fontName === 'string' ? fontData.fontName : '');
|
|
1568
|
+
const systemFontName = typeof fontData.systemFontName === 'string'
|
|
1569
|
+
? fontData.systemFontName
|
|
1570
|
+
: (typeof fontData.fontName === 'string' ? fontData.fontName : '');
|
|
1571
|
+
fonts.push(new models_1.DocumentFontInfo(documentFontName, systemFontName));
|
|
1342
1572
|
}
|
|
1343
1573
|
}
|
|
1344
1574
|
}
|
|
@@ -1429,6 +1659,42 @@ class PDFDancer {
|
|
|
1429
1659
|
async selectLines() {
|
|
1430
1660
|
return this.selectTextLines();
|
|
1431
1661
|
}
|
|
1662
|
+
// Singular convenience methods - return the first element or null
|
|
1663
|
+
async selectImage() {
|
|
1664
|
+
const images = await this.selectImages();
|
|
1665
|
+
return images.length > 0 ? images[0] : null;
|
|
1666
|
+
}
|
|
1667
|
+
async selectPath() {
|
|
1668
|
+
const paths = await this.selectPaths();
|
|
1669
|
+
return paths.length > 0 ? paths[0] : null;
|
|
1670
|
+
}
|
|
1671
|
+
async selectForm() {
|
|
1672
|
+
const forms = await this.selectForms();
|
|
1673
|
+
return forms.length > 0 ? forms[0] : null;
|
|
1674
|
+
}
|
|
1675
|
+
async selectFormField() {
|
|
1676
|
+
const fields = await this.selectFormFields();
|
|
1677
|
+
return fields.length > 0 ? fields[0] : null;
|
|
1678
|
+
}
|
|
1679
|
+
async selectFieldByName(fieldName) {
|
|
1680
|
+
const fields = await this.selectFieldsByName(fieldName);
|
|
1681
|
+
return fields.length > 0 ? fields[0] : null;
|
|
1682
|
+
}
|
|
1683
|
+
async selectParagraph() {
|
|
1684
|
+
const paragraphs = await this.selectParagraphs();
|
|
1685
|
+
return paragraphs.length > 0 ? paragraphs[0] : null;
|
|
1686
|
+
}
|
|
1687
|
+
async selectParagraphMatching(pattern) {
|
|
1688
|
+
const paragraphs = await this.selectParagraphsMatching(pattern);
|
|
1689
|
+
return paragraphs.length > 0 ? paragraphs[0] : null;
|
|
1690
|
+
}
|
|
1691
|
+
async selectTextLine() {
|
|
1692
|
+
const lines = await this.selectTextLines();
|
|
1693
|
+
return lines.length > 0 ? lines[0] : null;
|
|
1694
|
+
}
|
|
1695
|
+
async selectLine() {
|
|
1696
|
+
return this.selectTextLine();
|
|
1697
|
+
}
|
|
1432
1698
|
}
|
|
1433
1699
|
exports.PDFDancer = PDFDancer;
|
|
1434
1700
|
//# sourceMappingURL=pdfdancer_v1.js.map
|