tailprint 0.0.0-stage → 1.0.0
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/LICENSE +21 -0
- package/README.md +160 -2
- package/dist/config/env.d.ts +77 -0
- package/dist/config/env.js +38 -0
- package/dist/config/env.js.map +1 -0
- package/dist/config/plans.d.ts +15 -0
- package/dist/config/plans.js +104 -0
- package/dist/config/plans.js.map +1 -0
- package/dist/config/templates.d.ts +10 -0
- package/dist/config/templates.js +746 -0
- package/dist/config/templates.js.map +1 -0
- package/dist/mcp/server.d.ts +10 -0
- package/dist/mcp/server.js +897 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/stdio.d.ts +2 -0
- package/dist/mcp/stdio.js +27 -0
- package/dist/mcp/stdio.js.map +1 -0
- package/dist/routes/dashboard.d.ts +2 -0
- package/dist/routes/dashboard.js +454 -0
- package/dist/routes/dashboard.js.map +1 -0
- package/dist/routes/mcp.d.ts +2 -0
- package/dist/routes/mcp.js +329 -0
- package/dist/routes/mcp.js.map +1 -0
- package/dist/routes/render.d.ts +2 -0
- package/dist/routes/render.js +444 -0
- package/dist/routes/render.js.map +1 -0
- package/dist/routes/scrape.d.ts +2 -0
- package/dist/routes/scrape.js +132 -0
- package/dist/routes/scrape.js.map +1 -0
- package/dist/routes/templates.d.ts +2 -0
- package/dist/routes/templates.js +2449 -0
- package/dist/routes/templates.js.map +1 -0
- package/dist/routes/usage.d.ts +31 -0
- package/dist/routes/usage.js +73 -0
- package/dist/routes/usage.js.map +1 -0
- package/dist/routes/webhooks.d.ts +2 -0
- package/dist/routes/webhooks.js +227 -0
- package/dist/routes/webhooks.js.map +1 -0
- package/dist/schemas/render.d.ts +422 -0
- package/dist/schemas/render.js +91 -0
- package/dist/schemas/render.js.map +1 -0
- package/dist/schemas/scrape.d.ts +65 -0
- package/dist/schemas/scrape.js +24 -0
- package/dist/schemas/scrape.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +206 -0
- package/dist/server.js.map +1 -0
- package/dist/services/browser.d.ts +21 -0
- package/dist/services/browser.js +299 -0
- package/dist/services/browser.js.map +1 -0
- package/dist/services/carousel.d.ts +43 -0
- package/dist/services/carousel.js +300 -0
- package/dist/services/carousel.js.map +1 -0
- package/dist/services/chart.d.ts +29 -0
- package/dist/services/chart.js +129 -0
- package/dist/services/chart.js.map +1 -0
- package/dist/services/eInvoice.d.ts +62 -0
- package/dist/services/eInvoice.js +413 -0
- package/dist/services/eInvoice.js.map +1 -0
- package/dist/services/email.d.ts +24 -0
- package/dist/services/email.js +177 -0
- package/dist/services/email.js.map +1 -0
- package/dist/services/mockup.d.ts +14 -0
- package/dist/services/mockup.js +130 -0
- package/dist/services/mockup.js.map +1 -0
- package/dist/services/pdfStamper.d.ts +16 -0
- package/dist/services/pdfStamper.js +47 -0
- package/dist/services/pdfStamper.js.map +1 -0
- package/dist/services/qr.d.ts +20 -0
- package/dist/services/qr.js +44 -0
- package/dist/services/qr.js.map +1 -0
- package/dist/services/redis.d.ts +65 -0
- package/dist/services/redis.js +318 -0
- package/dist/services/redis.js.map +1 -0
- package/dist/services/scraper.d.ts +16 -0
- package/dist/services/scraper.js +138 -0
- package/dist/services/scraper.js.map +1 -0
- package/dist/services/storage.d.ts +38 -0
- package/dist/services/storage.js +123 -0
- package/dist/services/storage.js.map +1 -0
- package/dist/services/stripe.d.ts +32 -0
- package/dist/services/stripe.js +229 -0
- package/dist/services/stripe.js.map +1 -0
- package/dist/services/visualDiff.d.ts +33 -0
- package/dist/services/visualDiff.js +218 -0
- package/dist/services/visualDiff.js.map +1 -0
- package/package.json +74 -4
package/dist/server.js
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import Fastify from 'fastify';
|
|
2
|
+
import swagger from '@fastify/swagger';
|
|
3
|
+
import scalarFastifyApiReference from '@scalar/fastify-api-reference';
|
|
4
|
+
import fastifyRawBody from 'fastify-raw-body';
|
|
5
|
+
import { serializerCompiler, validatorCompiler, jsonSchemaTransform, hasZodFastifySchemaValidationErrors, } from 'fastify-type-provider-zod';
|
|
6
|
+
import { env } from './config/env.js';
|
|
7
|
+
import { renderRoutes } from './routes/render.js';
|
|
8
|
+
import { webhookRoutes } from './routes/webhooks.js';
|
|
9
|
+
import { templateRoutes } from './routes/templates.js';
|
|
10
|
+
import { dashboardRoutes } from './routes/dashboard.js';
|
|
11
|
+
import { scrapeRoutes } from './routes/scrape.js';
|
|
12
|
+
import { mcpRoutes } from './routes/mcp.js';
|
|
13
|
+
import { usageRoutes } from './routes/usage.js';
|
|
14
|
+
import { browserService, RenderTimeoutError } from './services/browser.js';
|
|
15
|
+
import { redisService } from './services/redis.js';
|
|
16
|
+
export async function buildApp() {
|
|
17
|
+
const fastify = Fastify({
|
|
18
|
+
logger: env.NODE_ENV !== 'test' ? {
|
|
19
|
+
level: env.NODE_ENV === 'production' ? 'info' : 'debug',
|
|
20
|
+
} : false,
|
|
21
|
+
});
|
|
22
|
+
// 1. Zod type provider integration
|
|
23
|
+
fastify.setValidatorCompiler(validatorCompiler);
|
|
24
|
+
fastify.setSerializerCompiler(serializerCompiler);
|
|
25
|
+
// 2. Raw body plugin (for Stripe signature validation)
|
|
26
|
+
await fastify.register(fastifyRawBody, {
|
|
27
|
+
field: 'rawBody',
|
|
28
|
+
global: false,
|
|
29
|
+
encoding: 'utf8',
|
|
30
|
+
runFirst: true,
|
|
31
|
+
});
|
|
32
|
+
// 3. Swagger OpenAPI 3.1 specification
|
|
33
|
+
await fastify.register(swagger, {
|
|
34
|
+
openapi: {
|
|
35
|
+
openapi: '3.1.0',
|
|
36
|
+
info: {
|
|
37
|
+
title: 'Tailprint API',
|
|
38
|
+
version: '1.0.0',
|
|
39
|
+
description: 'Tailprint is the high-performance Tailwind-Native Render & PDF Engine + AI Web Scraper (https://tailprint.com).\n\n' +
|
|
40
|
+
'Convert HTML styled with Tailwind CSS into pixel-perfect PDF documents and Open Graph preview images. Scrape any public URL into token-efficient Markdown for LLMs with 90%+ context savings.\n\n' +
|
|
41
|
+
'### Authentication\n' +
|
|
42
|
+
'All requests to `/v1/...` require an API key passed in the Authorization header:\n' +
|
|
43
|
+
'```http\n' +
|
|
44
|
+
'Authorization: Bearer sec_live_...\n' +
|
|
45
|
+
'```\n' +
|
|
46
|
+
'To obtain an API key or manage your plan, visit [tailprint.com/#pricing](https://tailprint.com/#pricing) or access your [Customer Dashboard](https://tailprint.com/dashboard).\n\n' +
|
|
47
|
+
'---\n\n' +
|
|
48
|
+
'### 🤖 Model Context Protocol (MCP) Reference\n' +
|
|
49
|
+
'Tailprint provides a production-grade, 100% compliant Model Context Protocol server exposing **13 Native Tools** across local stdio and remote Server-Sent Events (SSE):\n\n' +
|
|
50
|
+
'| MCP Tool | Category | Returns | Purpose |\n' +
|
|
51
|
+
'|---|---|---|---|\n' +
|
|
52
|
+
'| `render_pdf` | Documents | PDF Link + Base64 | Renders Tailwind HTML or public URL into vector PDF with headers/footers & encryption. |\n' +
|
|
53
|
+
'| `render_image` | Open Graph | Inline PNG + CDN Link | Renders Open Graph social preview cards from Tailwind CSS. |\n' +
|
|
54
|
+
'| `render_chart` | Data Visuals | Inline PNG + CDN Link | High-DPI bar, line, radar, doughnut, and pie charts with Chart.js. |\n' +
|
|
55
|
+
'| `generate_qr` | Utilities | Inline PNG + CDN Link | Mathematically verified scan-ready QR codes and barcodes. |\n' +
|
|
56
|
+
'| `render_device_mockup` | Marketing | Inline PNG + CDN Link | Wraps any URL into a MacBook Pro + floating iPhone 16 studio mockup canvas. |\n' +
|
|
57
|
+
'| `stamp_pdf` | Security | PDF Link + Base64 | Stamps diagonal watermarks (CONFIDENTIAL, DRAFT, FORTROLIGT) across all PDF pages. |\n' +
|
|
58
|
+
'| `compare_snapshots` | QA / Testing | Inline PNG + CDN Link | 3-panel visual regression diff heatmap highlighting layout shifts with neon-pink overlay. |\n' +
|
|
59
|
+
'| `render_carousel` | Social Media | LinkedIn PDF + PNG Cover | Multi-slide 1080x1350 px portrait carousels for LinkedIn and Instagram. |\n' +
|
|
60
|
+
'| `generate_einvoice` | Compliance / EU | Hybrid PDF + Factur-X XML | Generates EU-compliant hybrid e-invoices (Factur-X / ZUGFeRD / EN 16931) with embedded CII XML. |\n' +
|
|
61
|
+
'| `scrape_markdown` | AI Ingestion | Clean Markdown | Scrapes any web URL, stripping cookie walls and ads with 90%+ token savings. |\n' +
|
|
62
|
+
'| `render_template` | Automation | PDF / PNG + CDN Link | Directly renders 8 pre-built Tailwind templates with data substitution. |\n' +
|
|
63
|
+
'| `list_templates` | Automation | JSON Catalog | Lists all available templates in the Tailprint catalog. |\n' +
|
|
64
|
+
'| `get_template_source` | Automation | HTML Source | Retrieves full template source code for LLM inspection and customization. |\n\n' +
|
|
65
|
+
'#### Connecting Claude Desktop\n' +
|
|
66
|
+
'Add this configuration to `~/Library/Application Support/Claude/claude_desktop_config.json`:\n' +
|
|
67
|
+
'```json\n' +
|
|
68
|
+
'{\n' +
|
|
69
|
+
' "mcpServers": {\n' +
|
|
70
|
+
' "tailprint": {\n' +
|
|
71
|
+
' "command": "npx",\n' +
|
|
72
|
+
' "args": ["-y", "tailprint"],\n' +
|
|
73
|
+
' "env": {\n' +
|
|
74
|
+
' "TAILPRINT_API_KEY": "sec_live_your_key_here"\n' +
|
|
75
|
+
' }\n' +
|
|
76
|
+
' }\n' +
|
|
77
|
+
' }\n' +
|
|
78
|
+
'}\n' +
|
|
79
|
+
'```\n\n' +
|
|
80
|
+
'#### Remote SSE Endpoint\n' +
|
|
81
|
+
'Connect remote AI runtimes directly to `GET https://tailprint.com/v1/mcp/sse?api_key=sec_live_...`.',
|
|
82
|
+
contact: {
|
|
83
|
+
name: 'Tailprint Support',
|
|
84
|
+
url: 'https://tailprint.com',
|
|
85
|
+
email: 'support@tailprint.com',
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
servers: [
|
|
89
|
+
{
|
|
90
|
+
url: 'https://tailprint.com',
|
|
91
|
+
description: 'Production Server (HTTPS)',
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
url: 'https://api.tailprint.com',
|
|
95
|
+
description: 'Production API Subdomain (HTTPS)',
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
url: `http://${env.HOST === '0.0.0.0' ? 'localhost' : env.HOST}:${env.PORT}`,
|
|
99
|
+
description: 'Local Development Server',
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
components: {
|
|
103
|
+
securitySchemes: {
|
|
104
|
+
bearerAuth: {
|
|
105
|
+
type: 'http',
|
|
106
|
+
scheme: 'bearer',
|
|
107
|
+
bearerFormat: 'API Key',
|
|
108
|
+
description: 'Provide your API key in the format: Bearer sec_live_...',
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
transform: jsonSchemaTransform,
|
|
114
|
+
});
|
|
115
|
+
// 4. Scalar interactive documentation on /docs with dark mode
|
|
116
|
+
await fastify.register(scalarFastifyApiReference, {
|
|
117
|
+
routePrefix: '/docs',
|
|
118
|
+
configuration: {
|
|
119
|
+
theme: 'purple',
|
|
120
|
+
darkMode: true,
|
|
121
|
+
metaData: {
|
|
122
|
+
title: 'Tailprint API Reference',
|
|
123
|
+
description: 'Official API documentation and interactive test console for Tailprint (https://tailprint.com)',
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
// Health check endpoint
|
|
128
|
+
fastify.get('/health', {
|
|
129
|
+
schema: {
|
|
130
|
+
tags: ['System'],
|
|
131
|
+
summary: 'Service Health Check',
|
|
132
|
+
description: 'Returns health status and connected subsystems status.',
|
|
133
|
+
},
|
|
134
|
+
}, async () => {
|
|
135
|
+
return {
|
|
136
|
+
status: 'ok',
|
|
137
|
+
timestamp: new Date().toISOString(),
|
|
138
|
+
redisFallback: redisService.isMemoryFallback,
|
|
139
|
+
uptime: process.uptime(),
|
|
140
|
+
};
|
|
141
|
+
});
|
|
142
|
+
// 5. Register application routes
|
|
143
|
+
await fastify.register(templateRoutes);
|
|
144
|
+
await fastify.register(dashboardRoutes);
|
|
145
|
+
await fastify.register(renderRoutes);
|
|
146
|
+
await fastify.register(scrapeRoutes);
|
|
147
|
+
await fastify.register(mcpRoutes);
|
|
148
|
+
await fastify.register(usageRoutes);
|
|
149
|
+
await fastify.register(webhookRoutes);
|
|
150
|
+
// 6. Centralized Error Handler
|
|
151
|
+
fastify.setErrorHandler((error, request, reply) => {
|
|
152
|
+
request.log.error({ err: error }, 'Request processing error');
|
|
153
|
+
// Handle Strict Playwright Timeout
|
|
154
|
+
if (error instanceof RenderTimeoutError || error?.name === 'RenderTimeoutError') {
|
|
155
|
+
return reply.code(504).send({
|
|
156
|
+
statusCode: 504,
|
|
157
|
+
error: 'Gateway Timeout',
|
|
158
|
+
message: error?.message || `Render processing exceeded strict timeout limit of ${env.RENDER_TIMEOUT_MS}ms`,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
// Handle Zod Validation Errors
|
|
162
|
+
if (hasZodFastifySchemaValidationErrors(error)) {
|
|
163
|
+
return reply.code(400).send({
|
|
164
|
+
statusCode: 400,
|
|
165
|
+
error: 'Bad Request',
|
|
166
|
+
message: 'Validation failed for request input',
|
|
167
|
+
details: error.validation,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
// Pass through explicit Fastify HTTP error status
|
|
171
|
+
const statusCode = error?.statusCode && error.statusCode >= 400 && error.statusCode < 600
|
|
172
|
+
? error.statusCode
|
|
173
|
+
: 500;
|
|
174
|
+
return reply.code(statusCode).send({
|
|
175
|
+
statusCode,
|
|
176
|
+
error: error?.name || 'Internal Server Error',
|
|
177
|
+
message: error?.message || 'An unexpected error occurred during rendering',
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
// Graceful browser shutdown
|
|
181
|
+
fastify.addHook('onClose', async () => {
|
|
182
|
+
await browserService.close();
|
|
183
|
+
});
|
|
184
|
+
return fastify;
|
|
185
|
+
}
|
|
186
|
+
// Start server if executed directly
|
|
187
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
188
|
+
const start = async () => {
|
|
189
|
+
try {
|
|
190
|
+
const app = await buildApp();
|
|
191
|
+
// Pre-warm Chromium pool in the background
|
|
192
|
+
browserService.init().catch((err) => {
|
|
193
|
+
app.log.warn({ err }, 'Failed to pre-warm Chromium browser during boot');
|
|
194
|
+
});
|
|
195
|
+
await app.listen({ port: env.PORT, host: env.HOST });
|
|
196
|
+
app.log.info(`🚀 Tailprint running on http://${env.HOST}:${env.PORT}`);
|
|
197
|
+
app.log.info(`📖 Scalar API Documentation available at http://${env.HOST}:${env.PORT}/docs`);
|
|
198
|
+
}
|
|
199
|
+
catch (err) {
|
|
200
|
+
console.error('Fatal startup error:', err);
|
|
201
|
+
process.exit(1);
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
start();
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,OAAiC,MAAM,SAAS,CAAC;AACxD,OAAO,OAAO,MAAM,kBAAkB,CAAC;AACvC,OAAO,yBAAyB,MAAM,+BAA+B,CAAC;AACtE,OAAO,cAAc,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,mBAAmB,EACnB,mCAAmC,GACpC,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3E,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD,MAAM,CAAC,KAAK,UAAU,QAAQ;IAC5B,MAAM,OAAO,GAAG,OAAO,CAAC;QACtB,MAAM,EAAE,GAAG,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC;YAChC,KAAK,EAAE,GAAG,CAAC,QAAQ,KAAK,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO;SACxD,CAAC,CAAC,CAAC,KAAK;KACV,CAAC,CAAC;IAEH,mCAAmC;IACnC,OAAO,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,CAAC;IAChD,OAAO,CAAC,qBAAqB,CAAC,kBAAkB,CAAC,CAAC;IAElD,uDAAuD;IACvD,MAAM,OAAO,CAAC,QAAQ,CAAC,cAAc,EAAE;QACrC,KAAK,EAAE,SAAS;QAChB,MAAM,EAAE,KAAK;QACb,QAAQ,EAAE,MAAM;QAChB,QAAQ,EAAE,IAAI;KACf,CAAC,CAAC;IAEH,uCAAuC;IACvC,MAAM,OAAO,CAAC,QAAQ,CAAC,OAAO,EAAE;QAC9B,OAAO,EAAE;YACP,OAAO,EAAE,OAAO;YAChB,IAAI,EAAE;gBACJ,KAAK,EAAE,eAAe;gBACtB,OAAO,EAAE,OAAO;gBAChB,WAAW,EACT,qHAAqH;oBACrH,mMAAmM;oBACnM,sBAAsB;oBACtB,oFAAoF;oBACpF,WAAW;oBACX,sCAAsC;oBACtC,OAAO;oBACP,oLAAoL;oBACpL,SAAS;oBACT,iDAAiD;oBACjD,8KAA8K;oBAC9K,+CAA+C;oBAC/C,qBAAqB;oBACrB,6IAA6I;oBAC7I,wHAAwH;oBACxH,kIAAkI;oBAClI,qHAAqH;oBACrH,gJAAgJ;oBAChJ,uIAAuI;oBACvI,8JAA8J;oBAC9J,6IAA6I;oBAC7I,2KAA2K;oBAC3K,wIAAwI;oBACxI,uIAAuI;oBACvI,8GAA8G;oBAC9G,sIAAsI;oBACtI,kCAAkC;oBAClC,gGAAgG;oBAChG,WAAW;oBACX,KAAK;oBACL,qBAAqB;oBACrB,sBAAsB;oBACtB,2BAA2B;oBAC3B,sCAAsC;oBACtC,kBAAkB;oBAClB,yDAAyD;oBACzD,WAAW;oBACX,SAAS;oBACT,OAAO;oBACP,KAAK;oBACL,SAAS;oBACT,4BAA4B;oBAC5B,qGAAqG;gBACvG,OAAO,EAAE;oBACP,IAAI,EAAE,mBAAmB;oBACzB,GAAG,EAAE,uBAAuB;oBAC5B,KAAK,EAAE,uBAAuB;iBAC/B;aACF;YACD,OAAO,EAAE;gBACP;oBACE,GAAG,EAAE,uBAAuB;oBAC5B,WAAW,EAAE,2BAA2B;iBACzC;gBACD;oBACE,GAAG,EAAE,2BAA2B;oBAChC,WAAW,EAAE,kCAAkC;iBAChD;gBACD;oBACE,GAAG,EAAE,UAAU,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,EAAE;oBAC5E,WAAW,EAAE,0BAA0B;iBACxC;aACF;YACD,UAAU,EAAE;gBACV,eAAe,EAAE;oBACf,UAAU,EAAE;wBACV,IAAI,EAAE,MAAM;wBACZ,MAAM,EAAE,QAAQ;wBAChB,YAAY,EAAE,SAAS;wBACvB,WAAW,EAAE,yDAAyD;qBACvE;iBACF;aACF;SACF;QACD,SAAS,EAAE,mBAAmB;KAC/B,CAAC,CAAC;IAEH,8DAA8D;IAC9D,MAAM,OAAO,CAAC,QAAQ,CAAC,yBAAyB,EAAE;QAChD,WAAW,EAAE,OAAO;QACpB,aAAa,EAAE;YACb,KAAK,EAAE,QAAQ;YACf,QAAQ,EAAE,IAAI;YACd,QAAQ,EAAE;gBACR,KAAK,EAAE,yBAAyB;gBAChC,WAAW,EAAE,+FAA+F;aAC7G;SACF;KACF,CAAC,CAAC;IAEH,wBAAwB;IACxB,OAAO,CAAC,GAAG,CACT,SAAS,EACT;QACE,MAAM,EAAE;YACN,IAAI,EAAE,CAAC,QAAQ,CAAC;YAChB,OAAO,EAAE,sBAAsB;YAC/B,WAAW,EAAE,wDAAwD;SACtE;KACF,EACD,KAAK,IAAI,EAAE;QACT,OAAO;YACL,MAAM,EAAE,IAAI;YACZ,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,aAAa,EAAE,YAAY,CAAC,gBAAgB;YAC5C,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE;SACzB,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,iCAAiC;IACjC,MAAM,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;IACvC,MAAM,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC;IACxC,MAAM,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;IACrC,MAAM,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;IACrC,MAAM,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;IAClC,MAAM,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IACpC,MAAM,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;IAEtC,+BAA+B;IAC/B,OAAO,CAAC,eAAe,CAAC,CAAC,KAAU,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;QACrD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,0BAA0B,CAAC,CAAC;QAE9D,mCAAmC;QACnC,IAAI,KAAK,YAAY,kBAAkB,IAAI,KAAK,EAAE,IAAI,KAAK,oBAAoB,EAAE,CAAC;YAChF,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBAC1B,UAAU,EAAE,GAAG;gBACf,KAAK,EAAE,iBAAiB;gBACxB,OAAO,EAAE,KAAK,EAAE,OAAO,IAAI,sDAAsD,GAAG,CAAC,iBAAiB,IAAI;aAC3G,CAAC,CAAC;QACL,CAAC;QAED,+BAA+B;QAC/B,IAAI,mCAAmC,CAAC,KAAK,CAAC,EAAE,CAAC;YAC/C,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBAC1B,UAAU,EAAE,GAAG;gBACf,KAAK,EAAE,aAAa;gBACpB,OAAO,EAAE,qCAAqC;gBAC9C,OAAO,EAAE,KAAK,CAAC,UAAU;aAC1B,CAAC,CAAC;QACL,CAAC;QAED,kDAAkD;QAClD,MAAM,UAAU,GAAG,KAAK,EAAE,UAAU,IAAI,KAAK,CAAC,UAAU,IAAI,GAAG,IAAI,KAAK,CAAC,UAAU,GAAG,GAAG;YACvF,CAAC,CAAC,KAAK,CAAC,UAAU;YAClB,CAAC,CAAC,GAAG,CAAC;QAER,OAAO,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC;YACjC,UAAU;YACV,KAAK,EAAE,KAAK,EAAE,IAAI,IAAI,uBAAuB;YAC7C,OAAO,EAAE,KAAK,EAAE,OAAO,IAAI,+CAA+C;SAC3E,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,4BAA4B;IAC5B,OAAO,CAAC,OAAO,CAAC,SAAS,EAAE,KAAK,IAAI,EAAE;QACpC,MAAM,cAAc,CAAC,KAAK,EAAE,CAAC;IAC/B,CAAC,CAAC,CAAC;IAEH,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,oCAAoC;AACpC,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;IACpC,MAAM,KAAK,GAAG,KAAK,IAAI,EAAE;QACvB,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,QAAQ,EAAE,CAAC;YAE7B,2CAA2C;YAC3C,cAAc,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;gBAClC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,EAAE,iDAAiD,CAAC,CAAC;YAC3E,CAAC,CAAC,CAAC;YAEH,MAAM,GAAG,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;YACrD,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,kCAAkC,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;YACvE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,mDAAmD,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,OAAO,CAAC,CAAC;QAC/F,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,sBAAsB,EAAE,GAAG,CAAC,CAAC;YAC3C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC;IAEF,KAAK,EAAE,CAAC;AACV,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { RenderPdfRequest, RenderImageRequest } from '../schemas/render.js';
|
|
2
|
+
export declare class RenderTimeoutError extends Error {
|
|
3
|
+
constructor(message?: string);
|
|
4
|
+
}
|
|
5
|
+
export declare class BrowserService {
|
|
6
|
+
private static instance;
|
|
7
|
+
private browser;
|
|
8
|
+
private limit;
|
|
9
|
+
private isInitializing;
|
|
10
|
+
private constructor();
|
|
11
|
+
static getInstance(): BrowserService;
|
|
12
|
+
init(): Promise<void>;
|
|
13
|
+
close(): Promise<void>;
|
|
14
|
+
private isPrivateOrBlockedUrl;
|
|
15
|
+
private setupSsrfProtection;
|
|
16
|
+
private prepareHtmlWithTailwind;
|
|
17
|
+
private executeWithTimeout;
|
|
18
|
+
renderPdf(params: RenderPdfRequest): Promise<Buffer>;
|
|
19
|
+
renderImage(params: RenderImageRequest): Promise<Buffer>;
|
|
20
|
+
}
|
|
21
|
+
export declare const browserService: BrowserService;
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { chromium } from 'playwright';
|
|
2
|
+
import pLimit from 'p-limit';
|
|
3
|
+
import { encryptPDF } from 'cryptpdf';
|
|
4
|
+
import { env } from '../config/env.js';
|
|
5
|
+
export class RenderTimeoutError extends Error {
|
|
6
|
+
constructor(message = `Rendering exceeded timeout of ${env.RENDER_TIMEOUT_MS}ms`) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = 'RenderTimeoutError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export class BrowserService {
|
|
12
|
+
static instance = null;
|
|
13
|
+
browser = null;
|
|
14
|
+
limit = pLimit(env.CONCURRENCY_LIMIT);
|
|
15
|
+
isInitializing = false;
|
|
16
|
+
constructor() { }
|
|
17
|
+
static getInstance() {
|
|
18
|
+
if (!BrowserService.instance) {
|
|
19
|
+
BrowserService.instance = new BrowserService();
|
|
20
|
+
}
|
|
21
|
+
return BrowserService.instance;
|
|
22
|
+
}
|
|
23
|
+
async init() {
|
|
24
|
+
if (this.browser)
|
|
25
|
+
return;
|
|
26
|
+
if (this.isInitializing) {
|
|
27
|
+
while (this.isInitializing) {
|
|
28
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
29
|
+
}
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
this.isInitializing = true;
|
|
33
|
+
try {
|
|
34
|
+
this.browser = await chromium.launch({
|
|
35
|
+
headless: true,
|
|
36
|
+
args: [
|
|
37
|
+
'--no-sandbox',
|
|
38
|
+
'--disable-setuid-sandbox',
|
|
39
|
+
'--disable-dev-shm-usage',
|
|
40
|
+
'--disable-gpu',
|
|
41
|
+
'--no-first-run',
|
|
42
|
+
'--no-zygote',
|
|
43
|
+
],
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
this.isInitializing = false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async close() {
|
|
51
|
+
if (this.browser) {
|
|
52
|
+
await this.browser.close();
|
|
53
|
+
this.browser = null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
isPrivateOrBlockedUrl(rawUrl) {
|
|
57
|
+
try {
|
|
58
|
+
const parsed = new URL(rawUrl);
|
|
59
|
+
// Only allow http, https, data, and blob schemes
|
|
60
|
+
if (!['http:', 'https:', 'data:', 'blob:'].includes(parsed.protocol)) {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
if (parsed.protocol === 'data:' || parsed.protocol === 'blob:') {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
67
|
+
// Check localhost and local hostnames
|
|
68
|
+
if (hostname === 'localhost' ||
|
|
69
|
+
hostname.endsWith('.localhost') ||
|
|
70
|
+
hostname.endsWith('.local') ||
|
|
71
|
+
hostname.endsWith('.internal')) {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
// Check IPv4 private and link-local ranges
|
|
75
|
+
// 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, 0.0.0.0/8
|
|
76
|
+
const ipv4Match = hostname.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
|
|
77
|
+
if (ipv4Match) {
|
|
78
|
+
const [, aStr, bStr] = ipv4Match;
|
|
79
|
+
const a = parseInt(aStr, 10);
|
|
80
|
+
const b = parseInt(bStr, 10);
|
|
81
|
+
if (a === 127)
|
|
82
|
+
return true; // Loopback
|
|
83
|
+
if (a === 10)
|
|
84
|
+
return true; // Class A private
|
|
85
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
86
|
+
return true; // Class B private
|
|
87
|
+
if (a === 192 && b === 168)
|
|
88
|
+
return true; // Class C private
|
|
89
|
+
if (a === 169 && b === 254)
|
|
90
|
+
return true; // Link-local / Cloud metadata (169.254.169.254)
|
|
91
|
+
if (a === 0)
|
|
92
|
+
return true; // 0.0.0.0/8
|
|
93
|
+
}
|
|
94
|
+
// Check IPv6 loopback / unique local
|
|
95
|
+
if (hostname === '::1' ||
|
|
96
|
+
hostname.startsWith('fe80:') ||
|
|
97
|
+
hostname.startsWith('fc00:') ||
|
|
98
|
+
hostname.startsWith('fd00:')) {
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
setupSsrfProtection(page) {
|
|
108
|
+
page.route('**/*', async (route) => {
|
|
109
|
+
const request = route.request();
|
|
110
|
+
const url = request.url();
|
|
111
|
+
if (this.isPrivateOrBlockedUrl(url)) {
|
|
112
|
+
await route.abort('blockedbyclient');
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
await route.continue();
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
prepareHtmlWithTailwind(rawHtml) {
|
|
119
|
+
const hasTailwind = rawHtml.includes('cdn.tailwindcss.com') ||
|
|
120
|
+
rawHtml.includes('@tailwindcss');
|
|
121
|
+
const tailwindScriptTag = `<script src="${env.TAILWIND_CDN_URL}"></script>`;
|
|
122
|
+
// If it's a complete HTML document
|
|
123
|
+
if (/<html[\s>]/i.test(rawHtml)) {
|
|
124
|
+
if (hasTailwind) {
|
|
125
|
+
return rawHtml;
|
|
126
|
+
}
|
|
127
|
+
if (/<head[\s>]/i.test(rawHtml)) {
|
|
128
|
+
return rawHtml.replace(/<head[\s>]/i, (match) => `${match}\n ${tailwindScriptTag}`);
|
|
129
|
+
}
|
|
130
|
+
return rawHtml.replace(/<html[\s>]/i, (match) => `${match}\n<head>${tailwindScriptTag}</head>`);
|
|
131
|
+
}
|
|
132
|
+
// Fragment: wrap with standard HTML5 boilerplate and Tailwind
|
|
133
|
+
return `<!DOCTYPE html>
|
|
134
|
+
<html lang="en">
|
|
135
|
+
<head>
|
|
136
|
+
<meta charset="utf-8" />
|
|
137
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
138
|
+
${tailwindScriptTag}
|
|
139
|
+
</head>
|
|
140
|
+
<body class="bg-white text-gray-900 antialiased">
|
|
141
|
+
${rawHtml}
|
|
142
|
+
</body>
|
|
143
|
+
</html>`;
|
|
144
|
+
}
|
|
145
|
+
async executeWithTimeout(task, viewport = { width: 1200, height: 630 }) {
|
|
146
|
+
await this.init();
|
|
147
|
+
if (!this.browser) {
|
|
148
|
+
throw new Error('Browser is not initialized');
|
|
149
|
+
}
|
|
150
|
+
return this.limit(async () => {
|
|
151
|
+
let context = null;
|
|
152
|
+
let page = null;
|
|
153
|
+
let timeoutHandle = null;
|
|
154
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
155
|
+
timeoutHandle = setTimeout(() => {
|
|
156
|
+
reject(new RenderTimeoutError(`Operation exceeded limit of ${env.RENDER_TIMEOUT_MS}ms`));
|
|
157
|
+
}, env.RENDER_TIMEOUT_MS);
|
|
158
|
+
});
|
|
159
|
+
const workPromise = (async () => {
|
|
160
|
+
try {
|
|
161
|
+
context = await this.browser.newContext({
|
|
162
|
+
viewport,
|
|
163
|
+
deviceScaleFactor: 2,
|
|
164
|
+
});
|
|
165
|
+
page = await context.newPage();
|
|
166
|
+
page.setDefaultTimeout(env.RENDER_TIMEOUT_MS);
|
|
167
|
+
page.setDefaultNavigationTimeout(env.RENDER_TIMEOUT_MS);
|
|
168
|
+
this.setupSsrfProtection(page);
|
|
169
|
+
return await task(context, page);
|
|
170
|
+
}
|
|
171
|
+
finally {
|
|
172
|
+
if (page) {
|
|
173
|
+
await page.close().catch(() => { });
|
|
174
|
+
}
|
|
175
|
+
if (context) {
|
|
176
|
+
await context.close().catch(() => { });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
})();
|
|
180
|
+
try {
|
|
181
|
+
return await Promise.race([workPromise, timeoutPromise]);
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
if (err.name === 'TimeoutError' || err.message?.includes('Timeout')) {
|
|
185
|
+
throw new RenderTimeoutError();
|
|
186
|
+
}
|
|
187
|
+
throw err;
|
|
188
|
+
}
|
|
189
|
+
finally {
|
|
190
|
+
if (timeoutHandle) {
|
|
191
|
+
clearTimeout(timeoutHandle);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
async renderPdf(params) {
|
|
197
|
+
return this.executeWithTimeout(async (_context, page) => {
|
|
198
|
+
if (params.url) {
|
|
199
|
+
await page.goto(params.url, {
|
|
200
|
+
waitUntil: 'load',
|
|
201
|
+
timeout: env.RENDER_TIMEOUT_MS,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
else if (params.html) {
|
|
205
|
+
const fullHtml = this.prepareHtmlWithTailwind(params.html);
|
|
206
|
+
await page.setContent(fullHtml, {
|
|
207
|
+
waitUntil: 'load',
|
|
208
|
+
timeout: env.RENDER_TIMEOUT_MS,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
throw new Error('Either "html" or "url" must be provided');
|
|
213
|
+
}
|
|
214
|
+
// Wait for Tailwind JIT runtime and fonts
|
|
215
|
+
await Promise.all([
|
|
216
|
+
page.waitForLoadState('networkidle', { timeout: 2500 }).catch(() => { }),
|
|
217
|
+
page.evaluate(() => document.fonts?.ready).catch(() => { }),
|
|
218
|
+
]);
|
|
219
|
+
// Dynamic selector wait if requested
|
|
220
|
+
if (params.wait_for) {
|
|
221
|
+
await page.waitForSelector(params.wait_for, { timeout: 5000 });
|
|
222
|
+
}
|
|
223
|
+
// Delay in ms before rendering (e.g. for animations/charts to settle)
|
|
224
|
+
if (params.delay_ms && params.delay_ms > 0) {
|
|
225
|
+
const cappedDelay = Math.min(params.delay_ms, 5000);
|
|
226
|
+
await new Promise((r) => setTimeout(r, cappedDelay));
|
|
227
|
+
}
|
|
228
|
+
const hasHeaderFooter = Boolean(params.display_header_footer || params.header_html || params.footer_html);
|
|
229
|
+
// Sensible default margins when headers or footers are present to avoid overlapping content
|
|
230
|
+
const defaultMargins = hasHeaderFooter
|
|
231
|
+
? { top: '15mm', bottom: '15mm', left: '10mm', right: '10mm' }
|
|
232
|
+
: undefined;
|
|
233
|
+
const margin = params.margin
|
|
234
|
+
? {
|
|
235
|
+
top: params.margin.top,
|
|
236
|
+
bottom: params.margin.bottom,
|
|
237
|
+
left: params.margin.left,
|
|
238
|
+
right: params.margin.right,
|
|
239
|
+
}
|
|
240
|
+
: defaultMargins;
|
|
241
|
+
let pdfBuffer = await page.pdf({
|
|
242
|
+
format: params.format,
|
|
243
|
+
landscape: params.landscape,
|
|
244
|
+
printBackground: params.print_background,
|
|
245
|
+
displayHeaderFooter: hasHeaderFooter,
|
|
246
|
+
headerTemplate: params.header_html || '<span></span>',
|
|
247
|
+
footerTemplate: params.footer_html || '<span></span>',
|
|
248
|
+
margin,
|
|
249
|
+
});
|
|
250
|
+
// Optional AES-256 PDF encryption with user and optional owner password
|
|
251
|
+
if (params.password) {
|
|
252
|
+
const encrypted = await encryptPDF(new Uint8Array(pdfBuffer), params.password, params.owner_password || params.password);
|
|
253
|
+
pdfBuffer = Buffer.from(encrypted);
|
|
254
|
+
}
|
|
255
|
+
return pdfBuffer;
|
|
256
|
+
}, { width: 1200, height: 800 });
|
|
257
|
+
}
|
|
258
|
+
async renderImage(params) {
|
|
259
|
+
return this.executeWithTimeout(async (_context, page) => {
|
|
260
|
+
if (params.url) {
|
|
261
|
+
await page.goto(params.url, {
|
|
262
|
+
waitUntil: 'load',
|
|
263
|
+
timeout: env.RENDER_TIMEOUT_MS,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
else if (params.html) {
|
|
267
|
+
const fullHtml = this.prepareHtmlWithTailwind(params.html);
|
|
268
|
+
await page.setContent(fullHtml, {
|
|
269
|
+
waitUntil: 'load',
|
|
270
|
+
timeout: env.RENDER_TIMEOUT_MS,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
throw new Error('Either "html" or "url" must be provided');
|
|
275
|
+
}
|
|
276
|
+
// Wait for Tailwind JIT runtime and fonts
|
|
277
|
+
await Promise.all([
|
|
278
|
+
page.waitForLoadState('networkidle', { timeout: 2500 }).catch(() => { }),
|
|
279
|
+
page.evaluate(() => document.fonts?.ready).catch(() => { }),
|
|
280
|
+
]);
|
|
281
|
+
// Dynamic selector wait if requested
|
|
282
|
+
if (params.wait_for) {
|
|
283
|
+
await page.waitForSelector(params.wait_for, { timeout: 5000 });
|
|
284
|
+
}
|
|
285
|
+
// Delay in ms before capturing (e.g. for animations/charts to settle)
|
|
286
|
+
if (params.delay_ms && params.delay_ms > 0) {
|
|
287
|
+
const cappedDelay = Math.min(params.delay_ms, 5000);
|
|
288
|
+
await new Promise((r) => setTimeout(r, cappedDelay));
|
|
289
|
+
}
|
|
290
|
+
const imageBuffer = await page.screenshot({
|
|
291
|
+
type: params.type,
|
|
292
|
+
fullPage: false,
|
|
293
|
+
});
|
|
294
|
+
return imageBuffer;
|
|
295
|
+
}, { width: params.width || 1200, height: params.height || 630 });
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
export const browserService = BrowserService.getInstance();
|
|
299
|
+
//# sourceMappingURL=browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../../src/services/browser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type StoredFileMetadata } from './storage.js';
|
|
2
|
+
export interface CarouselSlide {
|
|
3
|
+
headline: string;
|
|
4
|
+
content: string | string[];
|
|
5
|
+
tag?: string;
|
|
6
|
+
takeaway?: string;
|
|
7
|
+
slideNumber?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface CarouselOptions {
|
|
10
|
+
title?: string;
|
|
11
|
+
subtitle?: string;
|
|
12
|
+
author?: {
|
|
13
|
+
name: string;
|
|
14
|
+
handle?: string;
|
|
15
|
+
role?: string;
|
|
16
|
+
avatarUrl?: string;
|
|
17
|
+
};
|
|
18
|
+
theme?: 'dark' | 'light' | 'emerald' | 'gradient';
|
|
19
|
+
slides?: CarouselSlide[];
|
|
20
|
+
markdown?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface RenderCarouselResult {
|
|
23
|
+
pdfBuffer: Buffer;
|
|
24
|
+
previewImageBuffer: Buffer;
|
|
25
|
+
slideCount: number;
|
|
26
|
+
pdfStored?: StoredFileMetadata;
|
|
27
|
+
previewStored?: StoredFileMetadata;
|
|
28
|
+
}
|
|
29
|
+
export declare class CarouselService {
|
|
30
|
+
private static instance;
|
|
31
|
+
private constructor();
|
|
32
|
+
static getInstance(): CarouselService;
|
|
33
|
+
/**
|
|
34
|
+
* Parses simple markdown divided by "---" into carousel slides.
|
|
35
|
+
*/
|
|
36
|
+
parseMarkdownToSlides(markdown: string): CarouselSlide[];
|
|
37
|
+
/**
|
|
38
|
+
* Generates a multi-page 1080x1350 px carousel PDF (LinkedIn standard)
|
|
39
|
+
* and a high-resolution PNG preview of the cover slide.
|
|
40
|
+
*/
|
|
41
|
+
renderCarousel(options: CarouselOptions): Promise<RenderCarouselResult>;
|
|
42
|
+
}
|
|
43
|
+
export declare const carouselService: CarouselService;
|