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.
Files changed (87) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +160 -2
  3. package/dist/config/env.d.ts +77 -0
  4. package/dist/config/env.js +38 -0
  5. package/dist/config/env.js.map +1 -0
  6. package/dist/config/plans.d.ts +15 -0
  7. package/dist/config/plans.js +104 -0
  8. package/dist/config/plans.js.map +1 -0
  9. package/dist/config/templates.d.ts +10 -0
  10. package/dist/config/templates.js +746 -0
  11. package/dist/config/templates.js.map +1 -0
  12. package/dist/mcp/server.d.ts +10 -0
  13. package/dist/mcp/server.js +897 -0
  14. package/dist/mcp/server.js.map +1 -0
  15. package/dist/mcp/stdio.d.ts +2 -0
  16. package/dist/mcp/stdio.js +27 -0
  17. package/dist/mcp/stdio.js.map +1 -0
  18. package/dist/routes/dashboard.d.ts +2 -0
  19. package/dist/routes/dashboard.js +454 -0
  20. package/dist/routes/dashboard.js.map +1 -0
  21. package/dist/routes/mcp.d.ts +2 -0
  22. package/dist/routes/mcp.js +329 -0
  23. package/dist/routes/mcp.js.map +1 -0
  24. package/dist/routes/render.d.ts +2 -0
  25. package/dist/routes/render.js +444 -0
  26. package/dist/routes/render.js.map +1 -0
  27. package/dist/routes/scrape.d.ts +2 -0
  28. package/dist/routes/scrape.js +132 -0
  29. package/dist/routes/scrape.js.map +1 -0
  30. package/dist/routes/templates.d.ts +2 -0
  31. package/dist/routes/templates.js +2449 -0
  32. package/dist/routes/templates.js.map +1 -0
  33. package/dist/routes/usage.d.ts +31 -0
  34. package/dist/routes/usage.js +73 -0
  35. package/dist/routes/usage.js.map +1 -0
  36. package/dist/routes/webhooks.d.ts +2 -0
  37. package/dist/routes/webhooks.js +227 -0
  38. package/dist/routes/webhooks.js.map +1 -0
  39. package/dist/schemas/render.d.ts +422 -0
  40. package/dist/schemas/render.js +91 -0
  41. package/dist/schemas/render.js.map +1 -0
  42. package/dist/schemas/scrape.d.ts +65 -0
  43. package/dist/schemas/scrape.js +24 -0
  44. package/dist/schemas/scrape.js.map +1 -0
  45. package/dist/server.d.ts +2 -0
  46. package/dist/server.js +206 -0
  47. package/dist/server.js.map +1 -0
  48. package/dist/services/browser.d.ts +21 -0
  49. package/dist/services/browser.js +299 -0
  50. package/dist/services/browser.js.map +1 -0
  51. package/dist/services/carousel.d.ts +43 -0
  52. package/dist/services/carousel.js +300 -0
  53. package/dist/services/carousel.js.map +1 -0
  54. package/dist/services/chart.d.ts +29 -0
  55. package/dist/services/chart.js +129 -0
  56. package/dist/services/chart.js.map +1 -0
  57. package/dist/services/eInvoice.d.ts +62 -0
  58. package/dist/services/eInvoice.js +413 -0
  59. package/dist/services/eInvoice.js.map +1 -0
  60. package/dist/services/email.d.ts +24 -0
  61. package/dist/services/email.js +177 -0
  62. package/dist/services/email.js.map +1 -0
  63. package/dist/services/mockup.d.ts +14 -0
  64. package/dist/services/mockup.js +130 -0
  65. package/dist/services/mockup.js.map +1 -0
  66. package/dist/services/pdfStamper.d.ts +16 -0
  67. package/dist/services/pdfStamper.js +47 -0
  68. package/dist/services/pdfStamper.js.map +1 -0
  69. package/dist/services/qr.d.ts +20 -0
  70. package/dist/services/qr.js +44 -0
  71. package/dist/services/qr.js.map +1 -0
  72. package/dist/services/redis.d.ts +65 -0
  73. package/dist/services/redis.js +318 -0
  74. package/dist/services/redis.js.map +1 -0
  75. package/dist/services/scraper.d.ts +16 -0
  76. package/dist/services/scraper.js +138 -0
  77. package/dist/services/scraper.js.map +1 -0
  78. package/dist/services/storage.d.ts +38 -0
  79. package/dist/services/storage.js +123 -0
  80. package/dist/services/storage.js.map +1 -0
  81. package/dist/services/stripe.d.ts +32 -0
  82. package/dist/services/stripe.js +229 -0
  83. package/dist/services/stripe.js.map +1 -0
  84. package/dist/services/visualDiff.d.ts +33 -0
  85. package/dist/services/visualDiff.js +218 -0
  86. package/dist/services/visualDiff.js.map +1 -0
  87. 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":"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"}
@@ -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;