@things-factory/board-service 10.1.4 → 10.1.5

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.
@@ -19,12 +19,18 @@ const pdfToImage = async ({ pdfPath, fileName, extension = 'png', quality = 2, d
19
19
  ]
20
20
  });
21
21
  const browser = await pool.acquire();
22
+ let page;
22
23
  try {
23
24
  const protocol = 'http';
24
25
  const host = 'localhost';
25
26
  const port = process.env.PORT;
26
27
  const pdfUrl = `${protocol}://${host}:${port}${pdfPath}`;
27
- const page = await browser.newPage();
28
+ page = await browser.newPage();
29
+ // 풀에서 꺼낸 브라우저는 launch 옵션(defaultViewport)을 호출별로 줄 수 없으므로 page 단위로 적용.
30
+ // (헤드리스풀 통합 전에는 puppeteer.launch({ defaultViewport }) 로 전달되던 값 — 미적용 시 800x600 고정)
31
+ if (defaultViewport) {
32
+ await page.setViewport(defaultViewport);
33
+ }
28
34
  const html = await ejs.render(getPdfHtmlTemplate(), {
29
35
  data: { pdfUrl, quality },
30
36
  nonce: crypto_1.default.randomBytes(16).toString('hex')
@@ -42,7 +48,6 @@ const pdfToImage = async ({ pdfPath, fileName, extension = 'png', quality = 2, d
42
48
  const stream = new Readable();
43
49
  stream.push(screenshot);
44
50
  stream.push(null);
45
- await pool.release(browser);
46
51
  // file upload 형태로 return
47
52
  return {
48
53
  filename: `${fileName}.${extension}`,
@@ -52,10 +57,14 @@ const pdfToImage = async ({ pdfPath, fileName, extension = 'png', quality = 2, d
52
57
  };
53
58
  }
54
59
  catch (e) {
55
- await pool.release(browser);
56
60
  console.error('Error creating thumbnail', e);
57
61
  throw new Error('Error creating thumbnail');
58
62
  }
63
+ finally {
64
+ // 브라우저는 풀에 반환되어 재사용되므로 page 를 닫지 않으면 호출마다 탭이 누적된다.
65
+ await page?.close().catch(() => { });
66
+ await pool.release(browser);
67
+ }
59
68
  };
60
69
  exports.pdfToImage = pdfToImage;
61
70
  function getPdfHtmlTemplate() {
@@ -1 +1 @@
1
- {"version":3,"file":"headless-pdf-to-image.js","sourceRoot":"","sources":["../../server/controllers/headless-pdf-to-image.ts"],"names":[],"mappings":";;;;AAAA,4DAA2B;AAC3B,iDAA+D;AAE/D,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;AACtC,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,CAAA;AAEnB,MAAM,UAAU,GAAG,KAAK,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,GAAG,KAAK,EAAE,OAAO,GAAG,CAAC,EAAE,eAAe,GAAG,IAAI,EAAE,EAAE,EAAE;IAChH,uDAAuD;IACvD,MAAM,IAAI,GAAG,IAAA,+BAAuB,EAAC,WAAW,EAAE;QAChD,GAAG,EAAE,CAAC;QACN,GAAG,EAAE,CAAC;QACN,IAAI,EAAE;YACJ,wBAAwB;YACxB,mCAAmC;YACnC,iCAAiC;YACjC,cAAc;SACf;KACF,CAAC,CAAA;IAEF,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAA;IAEpC,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,CAAA;QACvB,MAAM,IAAI,GAAG,WAAW,CAAA;QACxB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAA;QAC7B,MAAM,MAAM,GAAG,GAAG,QAAQ,MAAM,IAAI,IAAI,IAAI,GAAG,OAAO,EAAE,CAAA;QAExD,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAA;QACpC,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC,kBAAkB,EAAE,EAAE;YAClD,IAAI,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE;YACzB,KAAK,EAAE,gBAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC;SAC9C,CAAC,CAAA;QAEF,yEAAyE;QACzE,kCAAkC;QAClC,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAA;QAClD,MAAM,IAAI,CAAC,kBAAkB,EAAE,CAAA;QAC/B,MAAM,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAA;QACrB,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC;YACvC,IAAI,EAAE,SAAS;YACf,cAAc,EAAE,IAAI;SACrB,CAAC,CAAA;QAEF,6CAA6C;QAC7C,MAAM,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAA;QAC7B,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;QACvB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAEjB,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;QAE3B,yBAAyB;QACzB,OAAO;YACL,QAAQ,EAAE,GAAG,QAAQ,IAAI,SAAS,EAAE;YACpC,QAAQ,EAAE,SAAS,SAAS,EAAE;YAC9B,QAAQ,EAAE,MAAM;YAChB,gBAAgB,EAAE,GAAG,EAAE,CAAC,MAAM;SAC/B,CAAA;IACH,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;QAC3B,OAAO,CAAC,KAAK,CAAC,0BAA0B,EAAE,CAAC,CAAC,CAAA;QAC5C,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAA;IAC7C,CAAC;AACH,CAAC,CAAA;AAxDY,QAAA,UAAU,cAwDtB;AAED,SAAS,kBAAkB;IACzB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CN,CAAA;AACH,CAAC","sourcesContent":["import crypto from 'crypto'\nimport { getOrCreateHeadlessPool } from '@things-factory/shell'\n\nconst { Readable } = require('stream')\nconst ejs = require('ejs')\n\nexport const pdfToImage = async ({ pdfPath, fileName, extension = 'png', quality = 2, defaultViewport = null }) => {\n // Use headless pool instead of direct puppeteer.launch\n const pool = getOrCreateHeadlessPool('board-pdf', {\n min: 1,\n max: 3,\n args: [\n '--disable-web-security',\n '--disable-features=IsolateOrigins', \n '--disable-site-isolation-trials',\n '--no-sandbox'\n ]\n })\n\n const browser = await pool.acquire()\n\n try {\n const protocol = 'http'\n const host = 'localhost'\n const port = process.env.PORT\n const pdfUrl = `${protocol}://${host}:${port}${pdfPath}`\n\n const page = await browser.newPage()\n const html = await ejs.render(getPdfHtmlTemplate(), {\n data: { pdfUrl, quality },\n nonce: crypto.randomBytes(16).toString('hex')\n })\n\n // setContent 의 waitUntil 은 'load'/'domcontentloaded' 만 지원 → API/fetch 응답\n // 정착은 waitForNetworkIdle 로 별도 대기.\n await page.setContent(html, { waitUntil: 'load' })\n await page.waitForNetworkIdle()\n await page.$('#page')\n const screenshot = await page.screenshot({\n type: extension,\n omitBackground: true\n })\n\n // graphql fileupload형태로 return을 위해 stream 생성\n const stream = new Readable()\n stream.push(screenshot)\n stream.push(null)\n\n await pool.release(browser)\n\n // file upload 형태로 return\n return {\n filename: `${fileName}.${extension}`,\n mimetype: `image/${extension}`,\n encoding: '7bit',\n createReadStream: () => stream\n }\n } catch (e) {\n await pool.release(browser)\n console.error('Error creating thumbnail', e)\n throw new Error('Error creating thumbnail')\n }\n}\n\nfunction getPdfHtmlTemplate() {\n return `\n <html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no\" />\n\n <style nonce=\"<%= nonce %>\">\n body {\n width: 100vw;\n height: 100vh;\n margin: 0;\n }\n #page {\n display: flex;\n width: 100%;\n height: 100%;\n }\n </style>\n\n </head>\n <body>\n <canvas id=\"page\"></canvas>\n <script src=\"https://unpkg.com/pdfjs-dist@2.0.489/build/pdf.min.js\"></script>\n <script nonce=\"<%= nonce %>\">\n ;(async () => {\n const pdf = await pdfjsLib.getDocument('<%= data.pdfUrl %>')\n const page = await pdf.getPage(1)\n const viewport = page.getViewport('<%= data.quality %>')\n const canvas = document.getElementById('page')\n const context = canvas.getContext('2d')\n\n canvas.height = viewport.height\n canvas.width = viewport.width\n\n const renderContext = {\n canvasContext: context,\n viewport: viewport\n }\n\n page.render(renderContext)\n })()\n </script>\n </body>\n </html>\n `\n}\n"]}
1
+ {"version":3,"file":"headless-pdf-to-image.js","sourceRoot":"","sources":["../../server/controllers/headless-pdf-to-image.ts"],"names":[],"mappings":"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","sourcesContent":["import crypto from 'crypto'\nimport { getOrCreateHeadlessPool } from '@things-factory/shell'\n\nconst { Readable } = require('stream')\nconst ejs = require('ejs')\n\nexport const pdfToImage = async ({ pdfPath, fileName, extension = 'png', quality = 2, defaultViewport = null }) => {\n // Use headless pool instead of direct puppeteer.launch\n const pool = getOrCreateHeadlessPool('board-pdf', {\n min: 1,\n max: 3,\n args: [\n '--disable-web-security',\n '--disable-features=IsolateOrigins', \n '--disable-site-isolation-trials',\n '--no-sandbox'\n ]\n })\n\n const browser = await pool.acquire()\n let page\n\n try {\n const protocol = 'http'\n const host = 'localhost'\n const port = process.env.PORT\n const pdfUrl = `${protocol}://${host}:${port}${pdfPath}`\n\n page = await browser.newPage()\n // 풀에서 꺼낸 브라우저는 launch 옵션(defaultViewport)을 호출별로 줄 수 없으므로 page 단위로 적용.\n // (헤드리스풀 통합 전에는 puppeteer.launch({ defaultViewport }) 로 전달되던 값 — 미적용 시 800x600 고정)\n if (defaultViewport) {\n await page.setViewport(defaultViewport)\n }\n const html = await ejs.render(getPdfHtmlTemplate(), {\n data: { pdfUrl, quality },\n nonce: crypto.randomBytes(16).toString('hex')\n })\n\n // setContent 의 waitUntil 은 'load'/'domcontentloaded' 만 지원 → API/fetch 응답\n // 정착은 waitForNetworkIdle 로 별도 대기.\n await page.setContent(html, { waitUntil: 'load' })\n await page.waitForNetworkIdle()\n await page.$('#page')\n const screenshot = await page.screenshot({\n type: extension,\n omitBackground: true\n })\n\n // graphql fileupload형태로 return을 위해 stream 생성\n const stream = new Readable()\n stream.push(screenshot)\n stream.push(null)\n\n // file upload 형태로 return\n return {\n filename: `${fileName}.${extension}`,\n mimetype: `image/${extension}`,\n encoding: '7bit',\n createReadStream: () => stream\n }\n } catch (e) {\n console.error('Error creating thumbnail', e)\n throw new Error('Error creating thumbnail')\n } finally {\n // 브라우저는 풀에 반환되어 재사용되므로 page 를 닫지 않으면 호출마다 탭이 누적된다.\n await page?.close().catch(() => {})\n await pool.release(browser)\n }\n}\n\nfunction getPdfHtmlTemplate() {\n return `\n <html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no\" />\n\n <style nonce=\"<%= nonce %>\">\n body {\n width: 100vw;\n height: 100vh;\n margin: 0;\n }\n #page {\n display: flex;\n width: 100%;\n height: 100%;\n }\n </style>\n\n </head>\n <body>\n <canvas id=\"page\"></canvas>\n <script src=\"https://unpkg.com/pdfjs-dist@2.0.489/build/pdf.min.js\"></script>\n <script nonce=\"<%= nonce %>\">\n ;(async () => {\n const pdf = await pdfjsLib.getDocument('<%= data.pdfUrl %>')\n const page = await pdf.getPage(1)\n const viewport = page.getViewport('<%= data.quality %>')\n const canvas = document.getElementById('page')\n const context = canvas.getContext('2d')\n\n canvas.height = viewport.height\n canvas.width = viewport.width\n\n const renderContext = {\n canvasContext: context,\n viewport: viewport\n }\n\n page.render(renderContext)\n })()\n </script>\n </body>\n </html>\n `\n}\n"]}