polotno-node 3.0.2 → 3.0.4
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/dist/assets/{html2canvas-BeKaVBkZ.js → html2canvas-BSqd4IRA.js} +1 -1
- package/dist/assets/index-Del05vQF.js +541 -0
- package/dist/assets/{index.es-DwOGAynS.js → index.es-B79HmWnW.js} +1 -1
- package/dist/assets/{jspdf.es.min-BBVOPmzA.js → jspdf.es.min-BQBTUk1M.js} +4 -4
- package/dist/assets/{lib-CuK_SezV.js → lib-DZqExZxJ.js} +2 -2
- package/dist/index.html +1 -1
- package/lib/errors.d.ts +25 -0
- package/lib/errors.js +36 -0
- package/lib/index.d.ts +1 -0
- package/lib/instance.d.ts +1 -1
- package/lib/instance.js +52 -35
- package/package.json +9 -4
- package/dist/assets/index-DFqP22V0.js +0 -540
package/lib/instance.js
CHANGED
|
@@ -8,6 +8,7 @@ import { Readable } from "stream";
|
|
|
8
8
|
import { pipeline } from "stream/promises";
|
|
9
9
|
import { assertValidDesign } from "@polotno/schema";
|
|
10
10
|
import * as fontCache from "./font-cache.js";
|
|
11
|
+
import { polotnoError, rehydrateError } from "./errors.js";
|
|
11
12
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
13
|
// Compiled to lib/instance.js; the client bundle is published to ../dist.
|
|
13
14
|
const DEFAULT_CLIENT = `file:${path.join(__dirname, "..", "dist", "index.html")}`;
|
|
@@ -85,7 +86,7 @@ const guessExtension = (urlString, contentType) => {
|
|
|
85
86
|
};
|
|
86
87
|
const downloadUrlToFile = async (urlString, destinationPath) => {
|
|
87
88
|
if (typeof fetch !== "function") {
|
|
88
|
-
throw
|
|
89
|
+
throw polotnoError("FETCH_FAILED", "Global fetch API is not available in this Node.js runtime. Please use Node 18+.", { url: urlString });
|
|
89
90
|
}
|
|
90
91
|
const MAX_ATTEMPTS = 3;
|
|
91
92
|
const BASE_DELAY_MS = 300;
|
|
@@ -113,10 +114,12 @@ const downloadUrlToFile = async (urlString, destinationPath) => {
|
|
|
113
114
|
]
|
|
114
115
|
.filter(Boolean)
|
|
115
116
|
.join(", ");
|
|
116
|
-
throw
|
|
117
|
+
throw polotnoError("FETCH_FAILED", `HTTP error while downloading: ${details}`, { url: urlString, status: res.status });
|
|
117
118
|
}
|
|
118
119
|
if (!res.body) {
|
|
119
|
-
throw
|
|
120
|
+
throw polotnoError("FETCH_FAILED", "Empty response body", {
|
|
121
|
+
url: urlString,
|
|
122
|
+
});
|
|
120
123
|
}
|
|
121
124
|
await pipeline(Readable.fromWeb(res.body), fs.createWriteStream(destinationPath));
|
|
122
125
|
return res.headers.get("content-type") || "";
|
|
@@ -131,7 +134,7 @@ const downloadUrlToFile = async (urlString, destinationPath) => {
|
|
|
131
134
|
}
|
|
132
135
|
}
|
|
133
136
|
const message = lastError && lastError.message ? lastError.message : String(lastError);
|
|
134
|
-
throw
|
|
137
|
+
throw polotnoError("FETCH_FAILED", `Failed to download media after ${MAX_ATTEMPTS} attempts: ${urlString}\nLast error: ${message}`, { url: urlString, attempts: MAX_ATTEMPTS }, lastError);
|
|
135
138
|
};
|
|
136
139
|
// Returns a human-readable reason string if the video file needs transcoding
|
|
137
140
|
// for Chromium headless compatibility, or null if it is already render-safe.
|
|
@@ -400,37 +403,41 @@ export const createInstance = async ({ key, url, useParallelPages = true, browse
|
|
|
400
403
|
// across all exports (dataURL/image/pdf/video).
|
|
401
404
|
if (!window.__polotnoConsumeAssetError) {
|
|
402
405
|
window.__polotnoConsumeAssetError = (attrs) => {
|
|
403
|
-
const
|
|
404
|
-
if (!
|
|
406
|
+
const payload = window._polotnoError;
|
|
407
|
+
if (!payload)
|
|
405
408
|
return null;
|
|
406
409
|
// Clear immediately to avoid double-reporting.
|
|
407
410
|
window._polotnoError = null;
|
|
408
|
-
|
|
409
|
-
//
|
|
410
|
-
//
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
const skipImageError = isImageError && attrs && attrs.skipImageError;
|
|
417
|
-
if (skipFontError || skipImageError) {
|
|
411
|
+
// The client stashes a structured `{code, message, details}`
|
|
412
|
+
// payload (image timeouts arrive as IMAGE_FAILED/reason:'timeout',
|
|
413
|
+
// so skip flags cover them too).
|
|
414
|
+
const code = typeof payload === "object" ? payload.code : null;
|
|
415
|
+
if (code === "FONT_FAILED" && attrs && attrs.skipFontError) {
|
|
416
|
+
return null;
|
|
417
|
+
}
|
|
418
|
+
if (code === "IMAGE_FAILED" && attrs && attrs.skipImageError) {
|
|
418
419
|
return null;
|
|
419
420
|
}
|
|
420
|
-
|
|
421
|
+
// Return the payload as-is (object, or string from a legacy client);
|
|
422
|
+
// the Node side rehydrates it into an Error with code + details.
|
|
423
|
+
return payload;
|
|
421
424
|
};
|
|
422
425
|
}
|
|
423
426
|
if (!window.__polotnoThrowAssetErrorIfAny) {
|
|
424
427
|
window.__polotnoThrowAssetErrorIfAny = (attrs) => {
|
|
425
|
-
const
|
|
426
|
-
if (
|
|
427
|
-
|
|
428
|
+
const payload = window.__polotnoConsumeAssetError(attrs);
|
|
429
|
+
if (payload) {
|
|
430
|
+
// Re-stash so the Node side can pull the structured payload after
|
|
431
|
+
// this throw is flattened by page.evaluate (thrown Errors cross
|
|
432
|
+
// the boundary as message-only).
|
|
433
|
+
window._polotnoError = payload;
|
|
434
|
+
throw new Error(typeof payload === "string" ? payload : String(payload.message));
|
|
428
435
|
}
|
|
429
436
|
};
|
|
430
437
|
}
|
|
431
438
|
});
|
|
432
439
|
}
|
|
433
|
-
async function
|
|
440
|
+
async function consumeAssetLoadingError(page, attrs) {
|
|
434
441
|
try {
|
|
435
442
|
return await page.evaluate((attrs) => {
|
|
436
443
|
if (window.__polotnoConsumeAssetError) {
|
|
@@ -449,7 +456,7 @@ export const createInstance = async ({ key, url, useParallelPages = true, browse
|
|
|
449
456
|
? await createPage(browser, visitPage, requestInterceptor, useFontCache)
|
|
450
457
|
: firstPage;
|
|
451
458
|
if (busyPages.indexOf(page) >= 0) {
|
|
452
|
-
throw
|
|
459
|
+
throw polotnoError("EXPORT_FAILED", "Current rendering context is busy with another task. Please use `useParallelPages: true` option to run multiple tasks in parallel or make sure previous task is finished before starting a new one.", { reason: "busy" });
|
|
453
460
|
}
|
|
454
461
|
busyPages.push(page);
|
|
455
462
|
// Setup profiling if profilePath is provided
|
|
@@ -478,7 +485,13 @@ export const createInstance = async ({ key, url, useParallelPages = true, browse
|
|
|
478
485
|
await page.evaluate(() => {
|
|
479
486
|
if (window.config?.onLoadError) {
|
|
480
487
|
window.config.onLoadError((error) => {
|
|
481
|
-
|
|
488
|
+
// New clients pass an Error with `code`/`details` — stash a JSON
|
|
489
|
+
// form so both survive the trip to Node. Old clients pass a string.
|
|
490
|
+
const e = error;
|
|
491
|
+
window._polotnoError =
|
|
492
|
+
e && e.code
|
|
493
|
+
? { code: e.code, message: String(e.message), details: e.details }
|
|
494
|
+
: String(error);
|
|
482
495
|
});
|
|
483
496
|
}
|
|
484
497
|
else {
|
|
@@ -493,9 +506,9 @@ export const createInstance = async ({ key, url, useParallelPages = true, browse
|
|
|
493
506
|
fs.writeFileSync(profilePath, JSON.stringify(profile));
|
|
494
507
|
console.log(`CPU profile saved to: ${profilePath}`);
|
|
495
508
|
}
|
|
496
|
-
const
|
|
497
|
-
if (
|
|
498
|
-
throw
|
|
509
|
+
const assetError = await consumeAssetLoadingError(page, args[1]);
|
|
510
|
+
if (assetError) {
|
|
511
|
+
throw rehydrateError(assetError);
|
|
499
512
|
}
|
|
500
513
|
// remove busy page
|
|
501
514
|
busyPages.splice(busyPages.indexOf(page), 1);
|
|
@@ -519,14 +532,14 @@ export const createInstance = async ({ key, url, useParallelPages = true, browse
|
|
|
519
532
|
}
|
|
520
533
|
// If `page.evaluate()` failed (e.g. thrown during onProgress), still prefer
|
|
521
534
|
// a captured Polotno asset-loading error when available.
|
|
522
|
-
const
|
|
535
|
+
const assetError = await consumeAssetLoadingError(page, args[1]);
|
|
523
536
|
// remove busy page
|
|
524
537
|
busyPages.splice(busyPages.indexOf(page), 1);
|
|
525
538
|
if (useParallelPages) {
|
|
526
539
|
await page.close();
|
|
527
540
|
}
|
|
528
|
-
if (
|
|
529
|
-
throw
|
|
541
|
+
if (assetError) {
|
|
542
|
+
throw rehydrateError(assetError);
|
|
530
543
|
}
|
|
531
544
|
throw e;
|
|
532
545
|
}
|
|
@@ -608,9 +621,9 @@ export const createInstance = async ({ key, url, useParallelPages = true, browse
|
|
|
608
621
|
// even if user didn't provide onProgress
|
|
609
622
|
const progressCallback = (progress, frameTime) => {
|
|
610
623
|
// Check for asset-loading errors during render (without throwing)
|
|
611
|
-
const
|
|
612
|
-
if (
|
|
613
|
-
capturedError =
|
|
624
|
+
const assetError = window.__polotnoConsumeAssetError(attrs);
|
|
625
|
+
if (assetError) {
|
|
626
|
+
capturedError = assetError;
|
|
614
627
|
abortController.abort();
|
|
615
628
|
return;
|
|
616
629
|
}
|
|
@@ -630,15 +643,19 @@ export const createInstance = async ({ key, url, useParallelPages = true, browse
|
|
|
630
643
|
});
|
|
631
644
|
}
|
|
632
645
|
catch (e) {
|
|
633
|
-
// If we aborted due to asset error, throw that instead
|
|
646
|
+
// If we aborted due to asset error, throw that instead. Re-stash
|
|
647
|
+
// it and let the shared helper own the flatten/re-stash protocol
|
|
648
|
+
// (capturedError already passed the skip flags in consume).
|
|
634
649
|
if (capturedError) {
|
|
635
|
-
|
|
650
|
+
window._polotnoError = capturedError;
|
|
651
|
+
window.__polotnoThrowAssetErrorIfAny(attrs);
|
|
636
652
|
}
|
|
637
653
|
throw e;
|
|
638
654
|
}
|
|
639
655
|
// Final check for any errors that occurred
|
|
640
656
|
if (capturedError) {
|
|
641
|
-
|
|
657
|
+
window._polotnoError = capturedError;
|
|
658
|
+
window.__polotnoThrowAssetErrorIfAny(attrs);
|
|
642
659
|
}
|
|
643
660
|
// Stream blob in chunks via exposed function to avoid large payload issues.
|
|
644
661
|
// CRITICAL (!!!): Chunk size MUST be a multiple of 3 bytes because base64 encodes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polotno-node",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.4",
|
|
4
4
|
"description": "Polotno workflow from NodeJS",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"type": "module",
|
|
@@ -16,6 +16,11 @@
|
|
|
16
16
|
"types": "./lib/instance.d.ts",
|
|
17
17
|
"import": "./lib/instance.js",
|
|
18
18
|
"default": "./lib/instance.js"
|
|
19
|
+
},
|
|
20
|
+
"./errors": {
|
|
21
|
+
"types": "./lib/errors.d.ts",
|
|
22
|
+
"import": "./lib/errors.js",
|
|
23
|
+
"default": "./lib/errors.js"
|
|
19
24
|
}
|
|
20
25
|
},
|
|
21
26
|
"files": [
|
|
@@ -42,8 +47,8 @@
|
|
|
42
47
|
"vite": "^8.0.16",
|
|
43
48
|
"vitest": "^4.1.9",
|
|
44
49
|
"web-audio-api": "^1.5.1",
|
|
45
|
-
"polotno": "4.
|
|
46
|
-
"@polotno/video-export": "0.
|
|
50
|
+
"polotno": "4.3.0",
|
|
51
|
+
"@polotno/video-export": "0.2.0"
|
|
47
52
|
},
|
|
48
53
|
"overrides": {
|
|
49
54
|
"@polotno/video-export": {
|
|
@@ -56,7 +61,7 @@
|
|
|
56
61
|
"@sparticuz/chromium": "^149.0.0",
|
|
57
62
|
"puppeteer": "25.1.0",
|
|
58
63
|
"puppeteer-core": "25.1.0",
|
|
59
|
-
"@polotno/schema": "0.
|
|
64
|
+
"@polotno/schema": "0.2.0"
|
|
60
65
|
},
|
|
61
66
|
"bugs": {
|
|
62
67
|
"url": "https://github.com/polotno-project/polotno-node/issues"
|