@things-factory/integration-headless 7.0.37 → 7.0.39

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/integration-headless",
3
- "version": "7.0.37",
3
+ "version": "7.0.39",
4
4
  "main": "dist-server/index.js",
5
5
  "things-factory": true,
6
6
  "author": "heartyoh <heartyoh@hatiolab.com>",
@@ -22,10 +22,10 @@
22
22
  "clean": "npm run clean:server"
23
23
  },
24
24
  "dependencies": {
25
- "@things-factory/integration-base": "^7.0.33",
25
+ "@things-factory/integration-base": "^7.0.38",
26
26
  "@things-factory/pdf": "^7.0.37",
27
27
  "ejs": "^3.1.10",
28
28
  "pdf-lib": "^1.17.1"
29
29
  },
30
- "gitHead": "28b852bbf7bd1160522195f9db1775db56f18b30"
30
+ "gitHead": "97f7588d477aa1d6083b51113d7666b42f530989"
31
31
  }
@@ -53,7 +53,7 @@ export class HeadlessConnector implements Connector {
53
53
  }
54
54
 
55
55
  get taskPrefixes() {
56
- return ['headless']
56
+ return ['headless-pdf']
57
57
  }
58
58
 
59
59
  get description() {
@@ -1,5 +1,4 @@
1
- import * as ejs from 'ejs'
2
- import { PDFDocument, rgb, degrees } from 'pdf-lib'
1
+ import { PDFDocument } from 'pdf-lib'
3
2
 
4
3
  import { TaskRegistry } from '@things-factory/integration-base'
5
4
  import { ConnectionManager } from '@things-factory/integration-base'
@@ -11,28 +10,22 @@ async function HeadlessPDFCapture(step, context) {
11
10
  format,
12
11
  width,
13
12
  height,
14
- margin,
13
+ marginTop,
14
+ marginBottom,
15
+ marginLeft,
16
+ marginRight,
15
17
  scale,
16
18
  printBackground,
17
19
  landscape,
18
- displayHeaderFooter,
19
- headerTemplate,
20
- footerTemplate,
21
20
  preferCSSPageSize
22
21
  } = stepOptions || {}
23
- var { domain, data, __headless_pdf } = context
22
+ var { domain, __headless_pdf } = context
24
23
 
25
24
  if (!__headless_pdf) {
26
- throw new Error('it requires headless-pdf-open to be performed first')
25
+ throw new Error('It requires headless-pdf-open to be performed first')
27
26
  }
28
27
 
29
- var {
30
- pdf,
31
- header: headerTemplateFromContext,
32
- footer: footerTemplateFromContext,
33
- watermark,
34
- pageCount
35
- } = __headless_pdf
28
+ var { pdf, pageCount } = __headless_pdf
36
29
 
37
30
  var headlessPool = ConnectionManager.getConnectionInstanceByName(domain, connectionName)
38
31
  let browser
@@ -55,13 +48,16 @@ async function HeadlessPDFCapture(step, context) {
55
48
  format,
56
49
  width,
57
50
  height,
58
- margin,
51
+ margin: {
52
+ top: marginTop,
53
+ right: marginRight,
54
+ bottom: marginBottom,
55
+ left: marginLeft
56
+ },
59
57
  scale,
60
58
  printBackground,
61
59
  landscape,
62
- displayHeaderFooter,
63
- headerTemplate: displayHeaderFooter ? headerTemplate : undefined,
64
- footerTemplate: displayHeaderFooter ? footerTemplate : undefined,
60
+ displayHeaderFooter: false, // 첫 번째 패스에서는 헤더/푸터를 렌더링하지 않음
65
61
  preferCSSPageSize
66
62
  }
67
63
 
@@ -72,51 +68,7 @@ async function HeadlessPDFCapture(step, context) {
72
68
  const pdfDoc = await PDFDocument.load(pageBuffer)
73
69
  const pages = await pdf.copyPages(pdfDoc, pdfDoc.getPageIndices())
74
70
 
75
- pages.forEach((newPage, index) => {
76
- const currentPageNumber = pageCount + index + 1
77
-
78
- // 헤더와 푸터 렌더링 (페이지 번호 및 총 페이지 수 포함)
79
- if (displayHeaderFooter && (headerTemplateFromContext || footerTemplateFromContext)) {
80
- const renderedHeader = headerTemplateFromContext
81
- ? ejs.render(headerTemplateFromContext, {
82
- ...data,
83
- pageNumber: currentPageNumber,
84
- totalPages: pageCount + pages.length
85
- })
86
- : ''
87
- const renderedFooter = footerTemplateFromContext
88
- ? ejs.render(footerTemplateFromContext, {
89
- ...data,
90
- pageNumber: currentPageNumber,
91
- totalPages: pageCount + pages.length
92
- })
93
- : ''
94
-
95
- // Header/Footer 그리기
96
- if (renderedHeader) {
97
- newPage.drawText(renderedHeader, { x: 50, y: newPage.getHeight() - 30, size: 12, color: rgb(0, 0, 0) })
98
- }
99
- if (renderedFooter) {
100
- newPage.drawText(renderedFooter, { x: 50, y: 30, size: 12, color: rgb(0, 0, 0) })
101
- }
102
- }
103
-
104
- // 워터마크 추가
105
- if (watermark) {
106
- const fontSize = 50
107
- const pageWidth = newPage.getWidth()
108
- const pageHeight = newPage.getHeight()
109
-
110
- newPage.drawText(watermark, {
111
- x: pageWidth / 2 - (fontSize * watermark.length) / 4,
112
- y: pageHeight / 2 - fontSize / 2,
113
- size: fontSize,
114
- color: rgb(0.75, 0.75, 0.75),
115
- rotate: degrees(-45),
116
- opacity: 0.5
117
- })
118
- }
119
-
71
+ pages.forEach(newPage => {
120
72
  pdf.addPage(newPage)
121
73
  })
122
74
 
@@ -140,7 +92,7 @@ async function HeadlessPDFCapture(step, context) {
140
92
 
141
93
  HeadlessPDFCapture.parameterSpec = [
142
94
  {
143
- type: 'string',
95
+ type: 'textarea',
144
96
  name: 'htmlContent',
145
97
  label: 'html-content'
146
98
  },
@@ -148,42 +100,63 @@ HeadlessPDFCapture.parameterSpec = [
148
100
  type: 'select',
149
101
  name: 'format',
150
102
  label: 'page-format',
151
- options: [
152
- { label: 'A4', value: 'A4' },
153
- { label: 'A3', value: 'A3' },
154
- { label: 'Letter', value: 'Letter' },
155
- { label: 'Legal', value: 'Legal' }
156
- ],
103
+ property: {
104
+ options: [
105
+ { display: '', value: '' },
106
+ { display: 'A4', value: 'A4' },
107
+ { display: 'A3', value: 'A3' },
108
+ { display: 'Letter', value: 'Letter' },
109
+ { display: 'Legal', value: 'Legal' }
110
+ ]
111
+ },
157
112
  description: 'Select the paper format for the PDF'
158
113
  },
159
114
  {
160
115
  type: 'string',
161
116
  name: 'width',
162
117
  label: 'page-width',
118
+ placeholder: '(e.g., "8.5in", "21cm", "600px")',
163
119
  description: 'Specify the width of the page (e.g., "8.5in", "21cm", "600px")'
164
120
  },
165
121
  {
166
122
  type: 'string',
167
123
  name: 'height',
168
124
  label: 'page-height',
125
+ placeholder: '(e.g., "11in", "29.7cm", "800px")',
169
126
  description: 'Specify the height of the page (e.g., "11in", "29.7cm", "800px")'
170
127
  },
171
128
  {
172
- type: 'json',
173
- name: 'margin',
174
- label: 'page-margins',
175
- fields: [
176
- { type: 'string', name: 'top', label: 'Top Margin', description: 'Top margin (e.g., "1in")' },
177
- { type: 'string', name: 'right', label: 'Right Margin', description: 'Right margin (e.g., "1in")' },
178
- { type: 'string', name: 'bottom', label: 'Bottom Margin', description: 'Bottom margin (e.g., "1in")' },
179
- { type: 'string', name: 'left', label: 'Left Margin', description: 'Left margin (e.g., "1in")' }
180
- ],
181
- description: 'Set the margins for the page'
129
+ type: 'string',
130
+ name: 'marginTop',
131
+ label: 'page-margin-top',
132
+ placeholder: '(e.g., "0.5in", "1cm", "100px")',
133
+ description: 'Set the top margin for the page'
134
+ },
135
+ {
136
+ type: 'string',
137
+ name: 'marginBottom',
138
+ label: 'page-margin-bottom',
139
+ placeholder: '(e.g., "0.5in", "1cm", "100px")',
140
+ description: 'Set the bottom margin for the page'
141
+ },
142
+ {
143
+ type: 'string',
144
+ name: 'marginLeft',
145
+ label: 'page-margin-left',
146
+ placeholder: '(e.g., "0.5in", "1cm", "100px")',
147
+ description: 'Set the left margin for the page'
148
+ },
149
+ {
150
+ type: 'string',
151
+ name: 'marginRight',
152
+ label: 'page-margin-right',
153
+ placeholder: '(e.g., "0.5in", "1cm", "100px")',
154
+ description: 'Set the right margin for the page'
182
155
  },
183
156
  {
184
157
  type: 'number',
185
158
  name: 'scale',
186
- label: 'scale',
159
+ label: 'page-scale',
187
160
  defaultValue: 1,
188
161
  description: 'Set the scale of the page content (default is 1)'
189
162
  },
@@ -201,13 +174,6 @@ HeadlessPDFCapture.parameterSpec = [
201
174
  defaultValue: false,
202
175
  description: 'Print the PDF in landscape orientation'
203
176
  },
204
- {
205
- type: 'boolean',
206
- name: 'displayHeaderFooter',
207
- label: 'display-header/footer',
208
- defaultValue: false,
209
- description: 'Display header and footer in the PDF'
210
- },
211
177
  {
212
178
  type: 'boolean',
213
179
  name: 'preferCSSPageSize',
@@ -1,6 +1,5 @@
1
1
  import * as ejs from 'ejs'
2
2
  import { PDFDocument } from 'pdf-lib'
3
-
4
3
  import { TaskRegistry } from '@things-factory/integration-base'
5
4
  import { ConnectionManager } from '@things-factory/integration-base'
6
5
 
@@ -15,11 +14,38 @@ async function HeadlessPDFOpen(step, context) {
15
14
  fileName // 추가된 파일 이름
16
15
  } = stepOptions || {}
17
16
 
18
- // var { domain, data, variables } = context
19
- // var headlessPool = ConnectionManager.getConnectionInstanceByName(domain, connectionName)
20
-
21
17
  const pdf = await PDFDocument.create()
22
18
 
19
+ const headlessPool = ConnectionManager.getConnectionInstanceByName(context.domain, connectionName)
20
+ let browser
21
+
22
+ try {
23
+ browser = await headlessPool.acquire()
24
+ const page = await browser.newPage()
25
+
26
+ // Cover Page를 PDF로 변환
27
+ if (coverPage) {
28
+ await page.setContent(coverPage)
29
+ const coverPageBuffer = await page.pdf()
30
+ coverPage = coverPageBuffer
31
+ }
32
+
33
+ // Last Page를 PDF로 변환
34
+ if (lastPage) {
35
+ await page.setContent(lastPage)
36
+ const lastPageBuffer = await page.pdf()
37
+ lastPage = lastPageBuffer
38
+ }
39
+
40
+ await page.close()
41
+ headlessPool.release(browser)
42
+ } catch (error) {
43
+ if (browser) {
44
+ await browser.close()
45
+ }
46
+ throw error
47
+ }
48
+
23
49
  const data = {
24
50
  pdf,
25
51
  coverPage,
@@ -27,8 +53,8 @@ async function HeadlessPDFOpen(step, context) {
27
53
  header,
28
54
  footer,
29
55
  watermark,
30
- fileName, // 파일 이름을 context.data에 저장
31
- pageCount: 0 // 페이지 카운트를 초기화하여 context에 저장
56
+ fileName,
57
+ pageCount: 0
32
58
  }
33
59
 
34
60
  context.__headless_pdf = data
@@ -40,36 +66,38 @@ async function HeadlessPDFOpen(step, context) {
40
66
 
41
67
  HeadlessPDFOpen.parameterSpec = [
42
68
  {
43
- type: 'string',
69
+ type: 'textarea',
44
70
  name: 'coverPage',
45
71
  label: 'pdf-cover-page'
46
72
  },
47
73
  {
48
- type: 'string',
74
+ type: 'textarea',
49
75
  name: 'lastPage',
50
76
  label: 'pdf-last-page'
51
77
  },
52
78
  {
53
79
  type: 'string',
54
80
  name: 'header',
55
- label: 'header' // header template
81
+ label: 'header',
82
+ placeholder: 'Page <%= pageNumber %> of <%= totalPages %>'
56
83
  },
57
84
  {
58
85
  type: 'string',
59
86
  name: 'footer',
60
- label: 'footer' // footer template
87
+ label: 'footer',
88
+ placeholder: 'Page <%= pageNumber %> of <%= totalPages %>'
61
89
  },
62
90
  {
63
91
  type: 'string',
64
92
  name: 'watermark',
65
- label: 'watermark' // watermark
93
+ label: 'watermark'
66
94
  },
67
95
  {
68
96
  type: 'string',
69
97
  name: 'fileName',
70
- label: 'filename' // 파일 이름 지정
98
+ label: 'filename'
71
99
  }
72
100
  ]
73
- HeadlessPDFOpen.help = 'integration/task/headless-open'
101
+ HeadlessPDFOpen.help = 'integration/task/headless-pdf-open'
74
102
 
75
- TaskRegistry.registerTaskHandler('headless-open', HeadlessPDFOpen)
103
+ TaskRegistry.registerTaskHandler('headless-pdf-open', HeadlessPDFOpen)
@@ -1,24 +1,88 @@
1
1
  import * as fs from 'fs'
2
2
  import * as path from 'path'
3
+ import * as ejs from 'ejs'
3
4
  import { PDFDocument, rgb, degrees } from 'pdf-lib'
4
5
 
5
6
  import { TaskRegistry } from '@things-factory/integration-base'
6
7
 
8
+ async function HeadlessPDFRenderHeadersAndFooters(pdf, headerTemplate, footerTemplate, watermark, totalPages) {
9
+ const pages = pdf.getPages()
10
+
11
+ pages.forEach((page, index) => {
12
+ const pageNumber = index + 1
13
+ const pageWidth = page.getWidth()
14
+
15
+ // 헤더와 푸터 렌더링 (페이지 번호 및 총 페이지 수 포함)
16
+ if (headerTemplate || footerTemplate) {
17
+ const renderedHeader = headerTemplate
18
+ ? ejs.render(headerTemplate, {
19
+ pageNumber,
20
+ totalPages
21
+ })
22
+ : ''
23
+ const renderedFooter = footerTemplate
24
+ ? ejs.render(footerTemplate, {
25
+ pageNumber,
26
+ totalPages
27
+ })
28
+ : ''
29
+
30
+ const fontSize = 12 // 폰트 크기 설정
31
+ const textWidthHeader = fontSize * renderedHeader.length * 0.5
32
+ const textWidthFooter = fontSize * renderedFooter.length * 0.5
33
+
34
+ // Header/Footer 그리기 (가운데 정렬)
35
+ if (renderedHeader) {
36
+ page.drawText(renderedHeader, {
37
+ x: (pageWidth - textWidthHeader) / 2,
38
+ y: page.getHeight() - 30,
39
+ size: fontSize,
40
+ color: rgb(0, 0, 0)
41
+ })
42
+ }
43
+ if (renderedFooter) {
44
+ page.drawText(renderedFooter, {
45
+ x: (pageWidth - textWidthFooter) / 2,
46
+ y: 30,
47
+ size: fontSize,
48
+ color: rgb(0, 0, 0)
49
+ })
50
+ }
51
+ }
52
+
53
+ // 워터마크 추가
54
+ if (watermark) {
55
+ const fontSize = 50
56
+
57
+ page.drawText(watermark, {
58
+ x: pageWidth / 2 - (fontSize * watermark.length) / 4,
59
+ y: page.getHeight() / 2 - fontSize / 2,
60
+ size: fontSize,
61
+ color: rgb(0.75, 0.75, 0.75),
62
+ rotate: degrees(-45),
63
+ opacity: 0.5
64
+ })
65
+ }
66
+ })
67
+ }
68
+
7
69
  async function HeadlessPDFSave(step, context) {
8
70
  var { params: stepOptions } = step
9
71
  var { outputPath } = stepOptions || {}
10
- var { pdf, coverPage, lastPage, header, footer, watermark } = context.data
72
+ var { domain, data, __headless_pdf } = context
73
+
74
+ var { pdf, coverPage, lastPage, watermark, fileName } = __headless_pdf
75
+
76
+ const totalPages = pdf.getPages().length
77
+ await HeadlessPDFRenderHeadersAndFooters(pdf, __headless_pdf.header, __headless_pdf.footer, watermark, totalPages)
11
78
 
12
- // 파일 이름이 설정되지 않았을 경우 기본 파일 이름 생성
13
- const finalFileName = context.data.fileName || `document-${Date.now()}.pdf`
14
- const savePath = path.join(outputPath || '/path/to/default/storage', finalFileName)
79
+ const finalFileName = fileName || `document-${Date.now()}.pdf`
80
+ const savePath = path.join(outputPath || './attachments', finalFileName)
15
81
 
16
- // PDF 문서에 coverPage 및 lastPage 추가
17
82
  if (coverPage) {
18
83
  const coverPageDoc = await PDFDocument.load(coverPage)
19
84
  const [cover] = await pdf.copyPages(coverPageDoc, [0])
20
85
 
21
- // 커버 페이지에 워터마크 추가
22
86
  if (watermark) {
23
87
  const fontSize = 50
24
88
  const pageWidth = cover.getWidth()
@@ -41,7 +105,6 @@ async function HeadlessPDFSave(step, context) {
41
105
  const lastPageDoc = await PDFDocument.load(lastPage)
42
106
  const [last] = await pdf.copyPages(lastPageDoc, [0])
43
107
 
44
- // 마지막 페이지에 워터마크 추가
45
108
  if (watermark) {
46
109
  const fontSize = 50
47
110
  const pageWidth = last.getWidth()
@@ -60,13 +123,12 @@ async function HeadlessPDFSave(step, context) {
60
123
  pdf.addPage(last)
61
124
  }
62
125
 
63
- // PDF 파일로 저장
64
126
  const pdfBytes = await pdf.save()
65
127
  fs.writeFileSync(savePath, pdfBytes)
66
128
 
67
129
  return {
68
130
  data: {
69
- link: savePath // 저장된 PDF 파일의 경로를 반환
131
+ link: savePath
70
132
  }
71
133
  }
72
134
  }
@@ -75,7 +137,7 @@ HeadlessPDFSave.parameterSpec = [
75
137
  {
76
138
  type: 'string',
77
139
  name: 'outputPath',
78
- label: 'output-path' // PDF를 저장할 경로
140
+ label: 'output-path'
79
141
  }
80
142
  ]
81
143
 
@@ -8,12 +8,17 @@
8
8
  "label.output-path": "output path",
9
9
  "label.page-format": "page format",
10
10
  "label.page-height": "page height",
11
- "label.page-margins": "page margins",
11
+ "label.page-margin-top": "page margin top",
12
+ "label.page-margin-bottom": "page margin bottom",
13
+ "label.page-margin-left": "page margin left",
14
+ "label.page-margin-right": "page margins right",
12
15
  "label.page-width": "page width",
13
16
  "label.pdf-cover-page": "cover page template",
14
17
  "label.pdf-last-page": "last page template",
18
+ "label.pool-size-max": "maximum pool size",
19
+ "label.pool-size-min": "minimum pool size",
15
20
  "label.prefer-css-page-size": "prefer css page size",
16
21
  "label.print-background": "print background",
17
- "label.scale": "scale",
22
+ "label.page-scale": "scale",
18
23
  "label.watermark": "watermark"
19
24
  }
@@ -8,12 +8,17 @@
8
8
  "label.output-path": "出力パス",
9
9
  "label.page-format": "ページ形式",
10
10
  "label.page-height": "ページ高さ",
11
- "label.page-margins": "ページ余白",
11
+ "label.page-margin-top": "ページ上余白",
12
+ "label.page-margin-bottom": "ページ下余白",
13
+ "label.page-margin-left": "ページ左余白",
14
+ "label.page-margin-right": "ページ右余白",
12
15
  "label.page-width": "ページ幅",
13
16
  "label.pdf-cover-page": "表紙テンプレート",
14
17
  "label.pdf-last-page": "最終ページテンプレート",
18
+ "label.pool-size-max": "最大プールサイズ",
19
+ "label.pool-size-min": "最小プールサイズ",
15
20
  "label.prefer-css-page-size": "cssページサイズを優先",
16
21
  "label.print-background": "背景を印刷",
17
- "label.scale": "スケール",
22
+ "label.page-scale": "スケール",
18
23
  "label.watermark": "透かし"
19
24
  }
@@ -8,12 +8,17 @@
8
8
  "label.output-path": "출력 패스",
9
9
  "label.page-format": "페이지 형식",
10
10
  "label.page-height": "페이지 높이",
11
- "label.page-margins": "페이지 여백",
11
+ "label.page-margin-top": "페이지 여백 (위쪽)",
12
+ "label.page-margin-bottom": "페이지 여백 (아래쪽)",
13
+ "label.page-margin-left": "페이지 여백 (왼쪽)",
14
+ "label.page-margin-right": "페이지 여백 (오른쪽)",
12
15
  "label.page-width": "페이지 폭",
13
16
  "label.pdf-cover-page": "표지 템플릿",
14
17
  "label.pdf-last-page": "뒷표지 템플릿",
18
+ "label.pool-size-max": "최소 풀(Pool) 사이즈",
19
+ "label.pool-size-min": "최대 풀(Pool) 사이즈",
15
20
  "label.prefer-css-page-size": "선호 CSS 페이지 크기",
16
21
  "label.print-background": "배경 포함여부",
17
- "label.scale": "페이지 비율",
22
+ "label.page-scale": "페이지 비율",
18
23
  "label.watermark": "워터마크"
19
24
  }
@@ -8,12 +8,17 @@
8
8
  "label.output-path": "laluan output",
9
9
  "label.page-format": "format halaman",
10
10
  "label.page-height": "tinggi halaman",
11
- "label.page-margins": "margin halaman",
11
+ "label.page-margin-top": "margin atas halaman",
12
+ "label.page-margin-bottom": "margin bawah halaman",
13
+ "label.page-margin-left": "margin kiri halaman",
14
+ "label.page-margin-right": "margin kanan halaman",
12
15
  "label.page-width": "lebar halaman",
13
16
  "label.pdf-cover-page": "templat muka surat depan",
14
17
  "label.pdf-last-page": "templat muka surat terakhir",
18
+ "label.pool-size-max": "saiz pool maksimum",
19
+ "label.pool-size-min": "saiz pool minimum",
15
20
  "label.prefer-css-page-size": "utamakan saiz halaman css",
16
21
  "label.print-background": "cetak latar belakang",
17
- "label.scale": "skala",
22
+ "label.page-scale": "skala",
18
23
  "label.watermark": "tanda air"
19
24
  }
@@ -8,12 +8,17 @@
8
8
  "label.output-path": "输出路径",
9
9
  "label.page-format": "页面格式",
10
10
  "label.page-height": "页面高度",
11
- "label.page-margins": "页面边距",
11
+ "label.page-margin-top": "页面上边距",
12
+ "label.page-margin-bottom": "页面下边距",
13
+ "label.page-margin-left": "页面左边距",
14
+ "label.page-margin-right": "页面右边距",
12
15
  "label.page-width": "页面宽度",
13
16
  "label.pdf-cover-page": "封面模板",
14
17
  "label.pdf-last-page": "尾页模板",
18
+ "label.pool-size-max": "最大池大小",
19
+ "label.pool-size-min": "最小池大小",
15
20
  "label.prefer-css-page-size": "偏好 css 页面大小",
16
21
  "label.print-background": "打印背景",
17
- "label.scale": "缩放",
22
+ "label.page-scale": "缩放",
18
23
  "label.watermark": "水印"
19
24
  }