@things-factory/integration-headless 7.0.49 → 7.0.54

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 (28) hide show
  1. package/dist-server/engine/task/headless-pdf-capture-board.d.ts +59 -1
  2. package/dist-server/engine/task/headless-pdf-capture-board.js +29 -250
  3. package/dist-server/engine/task/headless-pdf-capture-board.js.map +1 -1
  4. package/dist-server/engine/task/headless-pdf-capture-markdown.d.ts +52 -0
  5. package/dist-server/engine/task/headless-pdf-capture-markdown.js +38 -0
  6. package/dist-server/engine/task/headless-pdf-capture-markdown.js.map +1 -0
  7. package/dist-server/engine/task/headless-pdf-capture.d.ts +52 -1
  8. package/dist-server/engine/task/headless-pdf-capture.js +13 -196
  9. package/dist-server/engine/task/headless-pdf-capture.js.map +1 -1
  10. package/dist-server/engine/task/headless-pdf-open.js +27 -257
  11. package/dist-server/engine/task/headless-pdf-open.js.map +1 -1
  12. package/dist-server/engine/task/headless-pdf-save.js +32 -20
  13. package/dist-server/engine/task/headless-pdf-save.js.map +1 -1
  14. package/dist-server/engine/task/index.d.ts +1 -0
  15. package/dist-server/engine/task/index.js +1 -0
  16. package/dist-server/engine/task/index.js.map +1 -1
  17. package/dist-server/engine/task/pdf-capture-util.d.ts +96 -0
  18. package/dist-server/engine/task/pdf-capture-util.js +252 -0
  19. package/dist-server/engine/task/pdf-capture-util.js.map +1 -0
  20. package/dist-server/tsconfig.tsbuildinfo +1 -1
  21. package/package.json +2 -2
  22. package/server/engine/task/headless-pdf-capture-board.ts +41 -293
  23. package/server/engine/task/headless-pdf-capture-markdown.ts +41 -0
  24. package/server/engine/task/headless-pdf-capture.ts +12 -226
  25. package/server/engine/task/headless-pdf-open.ts +28 -292
  26. package/server/engine/task/headless-pdf-save.ts +34 -21
  27. package/server/engine/task/index.ts +1 -0
  28. package/server/engine/task/pdf-capture-util.ts +319 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/integration-headless",
3
- "version": "7.0.49",
3
+ "version": "7.0.54",
4
4
  "main": "dist-server/index.js",
5
5
  "things-factory": true,
6
6
  "author": "heartyoh <heartyoh@hatiolab.com>",
@@ -29,5 +29,5 @@
29
29
  "ejs": "^3.1.10",
30
30
  "pdf-lib": "^1.17.1"
31
31
  },
32
- "gitHead": "3fdc2d9c86f8ebf1470fa5d2155b7b3019df6c0d"
32
+ "gitHead": "14a3320fb61bd0a47c147d4c3ff8310de6172f61"
33
33
  }
@@ -1,9 +1,6 @@
1
- import * as ejs from 'ejs'
2
1
  import { TaskRegistry } from '@things-factory/integration-base'
3
- import { ConnectionManager } from '@things-factory/integration-base'
4
- import { access } from '@things-factory/utils'
2
+ import { PDFCaptureUtil, getCommonParameterSpec } from './pdf-capture-util'
5
3
  import { BoardFunc } from '@things-factory/board-service'
6
- import { PDFDocument } from 'pdf-lib'
7
4
 
8
5
  const PAGE_FORMATS = {
9
6
  A4: { width: 595.28, height: 841.89 },
@@ -12,250 +9,86 @@ const PAGE_FORMATS = {
12
9
  Legal: { width: 612, height: 1008 }
13
10
  }
14
11
 
15
- async function HeadlessPDFCaptureBoard(step, context) {
16
- var { connection: connectionName, params: stepOptions } = step
17
- var {
18
- accessor,
19
- board,
20
- draft,
21
- format = 'A4', // Set default value to A4
22
- width,
23
- height,
24
- marginTop = 0,
25
- marginBottom = 0,
26
- marginLeft = 0,
27
- marginRight = 0,
28
- scale,
29
- printBackground,
30
- landscape,
31
- preferCSSPageSize
32
- } = stepOptions || {}
33
- var { domain, data, user, logger, __headless_pdf } = context
12
+ type PageFormat = keyof typeof PAGE_FORMATS
34
13
 
35
- if (!__headless_pdf) {
36
- throw new Error('It requires headless-pdf-open to be performed first')
37
- }
38
-
39
- if (!board || !board.id) {
40
- throw new Error('The board property must be set')
41
- }
42
-
43
- var { pdfDoc, header, footer, watermark } = __headless_pdf
44
-
45
- var headlessPool = ConnectionManager.getConnectionInstanceByName(domain, connectionName)
46
- let browser
14
+ export async function HeadlessPDFCaptureBoard(step, context) {
15
+ const pdfUtil = new PDFCaptureUtil(context)
16
+ await pdfUtil.initBrowser(step.connection)
47
17
 
48
18
  try {
49
- browser = await headlessPool.acquire()
50
- const page = await browser.newPage()
19
+ const { board, draft, format = 'A4', landscape, preferCSSPageSize } = step.params
51
20
 
52
- const boardInput = access(accessor, data)
21
+ if (!board || !board.id) {
22
+ throw new Error('The board property must be set')
23
+ }
53
24
 
54
- var { model, base } = await BoardFunc.headlessModel({ domain, id: board.id, model }, draft)
55
- const [fontsToUse, fontStyles] = await BoardFunc.fonts(domain)
25
+ const { model, base } = await BoardFunc.headlessModel({ domain: context.domain, id: board.id }, draft)
26
+ const [fontsToUse, fontStyles] = await BoardFunc.fonts(context.domain)
56
27
 
57
28
  model.fonts = fontsToUse
58
29
  model.fontStyles = fontStyles
59
30
 
60
- var { width: boardWidthPx, height: boardHeightPx } = model
31
+ let width: number | undefined = PAGE_FORMATS[format as PageFormat]?.width
32
+ let height: number | undefined = PAGE_FORMATS[format as PageFormat]?.height
61
33
 
62
34
  if (preferCSSPageSize) {
63
35
  width = undefined
64
36
  height = undefined
65
- } else if (PAGE_FORMATS[format]) {
66
- const pageDimensions = PAGE_FORMATS[format]
67
- width = pageDimensions.width
68
- height = pageDimensions.height
69
37
  }
70
38
 
71
- if (landscape) {
39
+ if (landscape && width && height) {
72
40
  ;[width, height] = [height, width]
73
41
  }
74
42
 
75
- const contentWidth = width - marginLeft - marginRight
76
- const contentHeight = height - marginTop - marginBottom
77
-
78
- const boardWidthPt = (boardWidthPx * 72) / 96
79
- const boardHeightPt = (boardHeightPx * 72) / 96
80
-
81
- const widthRatio = contentWidth / boardWidthPt
82
- const heightRatio = contentHeight / boardHeightPt
83
- const ratio = Math.min(widthRatio, heightRatio)
84
-
85
- const scaledBoardWidthPt = boardWidthPt * ratio
86
- const scaledBoardHeightPt = boardHeightPt * ratio
87
-
88
- const offsetX = (contentWidth - scaledBoardWidthPt) / 2 + marginLeft
89
- const offsetY = (contentHeight - scaledBoardHeightPt) / 2 + marginBottom
90
-
91
- await page.setViewport({ width: Math.round(boardWidthPx * ratio), height: Math.round(boardHeightPx * ratio) })
92
- await page.setRequestInterception(true)
93
- await page.setDefaultTimeout(10000)
94
-
95
- page.on('console', async msg => {
96
- console.log(`[browser ${msg.type()}] ${msg.text()}`)
97
- })
98
-
99
- page.on('requestfailed', request => {
100
- console.log('Request failed:', request.url())
101
- })
102
-
103
43
  const protocol = 'http'
104
44
  const host = 'localhost'
105
45
  const port = process.env.PORT ? `:${process.env.PORT}` : ''
106
46
  const path = '/internal-board-service-view'
107
47
  const url = `${protocol}://${host}${port}${path}`
108
48
 
109
- const token = await user?.sign()
49
+ const page = await pdfUtil.browser!.newPage()
110
50
 
111
- page.on('request', request => {
112
- if (request.url() === url) {
113
- request.continue({
114
- method: 'POST',
115
- headers: {
116
- 'Content-Type': 'application/json',
117
- 'x-things-factory-domain': domain?.subdomain,
118
- Authorization: 'Bearer ' + token
119
- },
120
- postData: JSON.stringify({
121
- model,
122
- base
123
- })
124
- })
125
- } else if (request.url().startsWith(`${protocol}://${host}${port}`)) {
126
- request.continue({
127
- headers: {
128
- ...request.headers(),
129
- 'x-things-factory-domain': domain?.subdomain,
130
- Authorization: 'Bearer ' + token
131
- }
132
- })
133
- } else {
134
- request.continue()
135
- }
51
+ await page.setViewport({
52
+ width: Math.round(width || 800), // Fallback for width if not defined
53
+ height: Math.round(height || 600) // Fallback for height if not defined
136
54
  })
137
55
 
138
56
  await page.goto(url)
139
57
 
140
- await page.evaluate(data => {
141
- //@ts-ignore
142
- s.data = data
143
- return new Promise(resolve => {
144
- requestAnimationFrame(() => resolve(0))
145
- })
146
- }, boardInput)
147
-
148
- const renderTemplateSafely = (template, data) => {
149
- try {
150
- return ejs.render(template, data)
151
- } catch (error) {
152
- logger.warn(`Template rendering error: ${error.message}`)
153
- return template
154
- }
155
- }
156
-
157
- header = renderTemplateSafely(header, __headless_pdf)
158
- footer = renderTemplateSafely(footer, __headless_pdf)
159
-
160
- // Apply header, footer, and watermark using Puppeteer
161
- // Apply header, footer, and watermark using Puppeteer
162
- if (header || footer || watermark) {
163
- await page.evaluate(
164
- ({ header, footer, watermark, isLandscape }) => {
165
- const setPositioning = (element, position) => {
166
- element.style.position = 'fixed'
167
- element.style.left = '0'
168
- element.style.width = '100%'
169
- element.style.textAlign = 'center'
170
- element.style.fontSize = '12px'
171
- element.style.margin = '0'
172
- element.style.padding = '0'
173
- if (position === 'header') {
174
- element.style.top = '0'
175
- } else if (position === 'footer') {
176
- element.style.bottom = '0'
177
- }
178
- }
179
-
180
- if (header) {
181
- const headerElement = document.createElement('div')
182
- headerElement.innerHTML = header
183
- setPositioning(headerElement, 'header')
184
- document.body.appendChild(headerElement)
185
- }
186
-
187
- if (footer) {
188
- const footerElement = document.createElement('div')
189
- footerElement.innerHTML = footer
190
- setPositioning(footerElement, 'footer')
191
- document.body.appendChild(footerElement)
192
- }
193
-
194
- if (watermark) {
195
- const watermarkElement = document.createElement('div')
196
- watermarkElement.innerHTML = watermark
197
- watermarkElement.style.position = 'fixed'
198
- watermarkElement.style.top = isLandscape ? '40%' : '50%'
199
- watermarkElement.style.left = '50%'
200
- watermarkElement.style.transform = 'translate(-50%, -50%) rotate(-45deg)'
201
- watermarkElement.style.opacity = '0.2'
202
- watermarkElement.style.fontSize = '50px'
203
- watermarkElement.style.color = 'red'
204
- watermarkElement.style.pointerEvents = 'none'
205
- watermarkElement.style.zIndex = '9999'
206
- document.body.appendChild(watermarkElement)
207
- }
208
- },
209
- { header, footer, watermark, isLandscape: landscape }
210
- )
211
- }
212
-
213
- const pageOptions = {
214
- format,
215
- width: `${scaledBoardWidthPt}px`,
216
- height: `${scaledBoardHeightPt}px`,
217
- margin: {
218
- top: `${offsetY}px`,
219
- right: `${offsetX}px`,
220
- bottom: `${offsetY}px`,
221
- left: `${offsetX}px`
58
+ await page.evaluate(
59
+ data => {
60
+ //@ts-ignore
61
+ s.data = data
62
+ return new Promise(resolve => {
63
+ requestAnimationFrame(() => resolve(0))
64
+ })
222
65
  },
223
- scale,
224
- printBackground,
225
- landscape,
226
- displayHeaderFooter: false, // Handled via Puppeteer directly
227
- preferCSSPageSize
228
- }
229
-
230
- const contentBuffer = await page.pdf(pageOptions)
231
- const contentPdfDoc = await PDFDocument.load(contentBuffer)
232
- const copiedPages = await pdfDoc.copyPages(contentPdfDoc, contentPdfDoc.getPageIndices())
233
-
234
- copiedPages.forEach(page => pdfDoc.addPage(page))
235
-
236
- __headless_pdf.pageCount += copiedPages.length
66
+ { model, base }
67
+ )
68
+
69
+ // 페이지 옵션 설정
70
+ const pageOptions = pdfUtil.buildPageOptions({
71
+ ...step.params,
72
+ width: width ? `${width}px` : undefined,
73
+ height: height ? `${height}px` : undefined
74
+ })
237
75
 
238
- await page.close()
239
- headlessPool.release(browser)
76
+ // PDF 생성 및 추가
77
+ const htmlContent = '' // 보드에서 직접 렌더링되므로 HTML 컨텐츠가 비어있음
78
+ await pdfUtil.processPageAndGeneratePDF(pageOptions, htmlContent) // pageOptions 사용
240
79
 
241
80
  return {
242
- data: __headless_pdf
81
+ data: context.__headless_pdf
243
82
  }
244
83
  } catch (error) {
245
- if (browser) {
246
- await browser.close()
247
- }
248
-
249
84
  throw error
85
+ } finally {
86
+ await pdfUtil.closeBrowser()
250
87
  }
251
88
  }
252
89
 
253
90
  HeadlessPDFCaptureBoard.parameterSpec = [
254
- {
255
- type: 'scenario-step-input',
256
- name: 'accessor',
257
- label: 'accessor'
258
- },
91
+ ...getCommonParameterSpec(),
259
92
  {
260
93
  type: 'resource-object',
261
94
  name: 'board',
@@ -270,91 +103,6 @@ HeadlessPDFCaptureBoard.parameterSpec = [
270
103
  label: 'board-draft',
271
104
  defaultValue: false,
272
105
  description: 'Set whether to get the current working version or the last released version'
273
- },
274
- {
275
- type: 'select',
276
- name: 'format',
277
- label: 'page-format',
278
- property: {
279
- options: [
280
- { display: '', value: '' },
281
- { display: 'A4', value: 'A4' },
282
- { display: 'A3', value: 'A3' },
283
- { display: 'Letter', value: 'Letter' },
284
- { display: 'Legal', value: 'Legal' }
285
- ]
286
- },
287
- description: 'Select the paper format for the PDF'
288
- },
289
- {
290
- type: 'string',
291
- name: 'width',
292
- label: 'page-width',
293
- placeholder: '(e.g., "8.5in", "21cm", "600px")',
294
- description: 'Specify the width of the page (e.g., "8.5in", "21cm", "600px")'
295
- },
296
- {
297
- type: 'string',
298
- name: 'height',
299
- label: 'page-height',
300
- placeholder: '(e.g., "11in", "29.7cm", "800px")',
301
- description: 'Specify the height of the page (e.g., "11in", "29.7cm", "800px")'
302
- },
303
- {
304
- type: 'string',
305
- name: 'marginTop',
306
- label: 'page-margin-top',
307
- placeholder: '(e.g., "0.5in", "1cm", "100px")',
308
- description: 'Set the top margin for the page'
309
- },
310
- {
311
- type: 'string',
312
- name: 'marginBottom',
313
- label: 'page-margin-bottom',
314
- placeholder: '(e.g., "0.5in", "1cm", "100px")',
315
- description: 'Set the bottom margin for the page'
316
- },
317
- {
318
- type: 'string',
319
- name: 'marginLeft',
320
- label: 'page-margin-left',
321
- placeholder: '(e.g., "0.5in", "1cm", "100px")',
322
- description: 'Set the left margin for the page'
323
- },
324
- {
325
- type: 'string',
326
- name: 'marginRight',
327
- label: 'page-margin-right',
328
- placeholder: '(e.g., "0.5in", "1cm", "100px")',
329
- description: 'Set the right margin for the page'
330
- },
331
- {
332
- type: 'number',
333
- name: 'scale',
334
- label: 'page-scale',
335
- defaultValue: 1,
336
- description: 'Set the scale of the page content (default is 1)'
337
- },
338
- {
339
- type: 'boolean',
340
- name: 'printBackground',
341
- label: 'print-background',
342
- defaultValue: true,
343
- description: 'Include background graphics when printing the page'
344
- },
345
- {
346
- type: 'boolean',
347
- name: 'landscape',
348
- label: 'landscape',
349
- defaultValue: false,
350
- description: 'Print the PDF in landscape orientation'
351
- },
352
- {
353
- type: 'boolean',
354
- name: 'preferCSSPageSize',
355
- label: 'prefer-css-page-size',
356
- defaultValue: false,
357
- description: 'Whether to prefer the CSS-defined page size over the given width and height'
358
106
  }
359
107
  ]
360
108
 
@@ -0,0 +1,41 @@
1
+ import { TaskRegistry } from '@things-factory/integration-base'
2
+ import { access } from '@things-factory/utils'
3
+ import { marked } from 'marked'
4
+
5
+ import { PDFCaptureUtil, getCommonParameterSpec } from './pdf-capture-util'
6
+
7
+ export async function HeadlessPDFCaptureMarkdown(step, context) {
8
+ const pdfUtil = new PDFCaptureUtil(context)
9
+ await pdfUtil.initBrowser(step.connection)
10
+
11
+ try {
12
+ const { accessor, markdownContent } = step.params
13
+ const templateInput = access(accessor, context.data)
14
+
15
+ const renderedMarkdown = pdfUtil.renderTemplate(markdownContent, templateInput)
16
+ const htmlContent = marked(renderedMarkdown)
17
+
18
+ await pdfUtil.processPageAndGeneratePDF(step.params, htmlContent)
19
+
20
+ return {
21
+ data: context.__headless_pdf
22
+ }
23
+ } catch (error) {
24
+ throw error
25
+ } finally {
26
+ await pdfUtil.closeBrowser()
27
+ }
28
+ }
29
+
30
+ HeadlessPDFCaptureMarkdown.parameterSpec = [
31
+ ...getCommonParameterSpec(),
32
+ {
33
+ type: 'textarea',
34
+ name: 'markdownContent',
35
+ label: 'markdown-content'
36
+ }
37
+ ]
38
+
39
+ HeadlessPDFCaptureMarkdown.help = 'integration/task/headless-pdf-capture-markdown'
40
+
41
+ TaskRegistry.registerTaskHandler('headless-pdf-capture-markdown', HeadlessPDFCaptureMarkdown)