@things-factory/board-service 10.0.0-beta.9 → 10.0.0-beta.91
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/dist-server/controllers/fonts.js +2 -2
- package/dist-server/controllers/fonts.js.map +1 -1
- package/dist-server/controllers/headless-model.js +4 -4
- package/dist-server/controllers/headless-model.js.map +1 -1
- package/dist-server/controllers/headless-pdf-to-image.js +4 -3
- package/dist-server/controllers/headless-pdf-to-image.js.map +1 -1
- package/dist-server/controllers/headless-playlist.js +2 -2
- package/dist-server/controllers/headless-playlist.js.map +1 -1
- package/dist-server/controllers/headless-pool-for-board.d.ts +8 -0
- package/dist-server/controllers/headless-pool-for-board.js +14 -4
- package/dist-server/controllers/headless-pool-for-board.js.map +1 -1
- package/dist-server/controllers/headless-pool-for-label.js +14 -4
- package/dist-server/controllers/headless-pool-for-label.js.map +1 -1
- package/dist-server/controllers/headless-render.d.ts +18 -0
- package/dist-server/controllers/headless-render.js +122 -0
- package/dist-server/controllers/headless-render.js.map +1 -0
- package/dist-server/controllers/index.d.ts +3 -12
- package/dist-server/controllers/label-command.d.ts +22 -11
- package/dist-server/controllers/label-command.js +136 -32
- package/dist-server/controllers/label-command.js.map +1 -1
- package/dist-server/controllers/label-command.test.d.ts +13 -0
- package/dist-server/controllers/label-command.test.js +211 -0
- package/dist-server/controllers/label-command.test.js.map +1 -0
- package/dist-server/controllers/pdf.d.ts +2 -10
- package/dist-server/controllers/pdf.js +3 -111
- package/dist-server/controllers/pdf.js.map +1 -1
- package/dist-server/controllers/screenshot.d.ts +2 -2
- package/dist-server/controllers/screenshot.js +3 -107
- package/dist-server/controllers/screenshot.js.map +1 -1
- package/dist-server/controllers/thumbnail.d.ts +1 -1
- package/dist-server/index.d.ts +6 -1
- package/dist-server/index.js +8 -13
- package/dist-server/index.js.map +1 -1
- package/dist-server/migrations/1762190000000-AddSourceImportSessionToBoard.d.ts +11 -0
- package/dist-server/migrations/1762190000000-AddSourceImportSessionToBoard.js +45 -0
- package/dist-server/migrations/1762190000000-AddSourceImportSessionToBoard.js.map +1 -0
- package/dist-server/routes.js +0 -1
- package/dist-server/routes.js.map +1 -1
- package/dist-server/service/board/board-mutation.d.ts +1 -0
- package/dist-server/service/board/board-mutation.js +129 -40
- package/dist-server/service/board/board-mutation.js.map +1 -1
- package/dist-server/service/board/board-query.d.ts +2 -1
- package/dist-server/service/board/board-query.js +34 -4
- package/dist-server/service/board/board-query.js.map +1 -1
- package/dist-server/service/board/board-reorder.test.d.ts +4 -0
- package/dist-server/service/board/board-reorder.test.js +347 -0
- package/dist-server/service/board/board-reorder.test.js.map +1 -0
- package/dist-server/service/board/board.d.ts +20 -0
- package/dist-server/service/board/board.js +16 -0
- package/dist-server/service/board/board.js.map +1 -1
- package/dist-server/service/board/thumbnail-scheduler.d.ts +41 -0
- package/dist-server/service/board/thumbnail-scheduler.js +156 -0
- package/dist-server/service/board/thumbnail-scheduler.js.map +1 -0
- package/dist-server/service/board/thumbnail-scheduler.test.d.ts +14 -0
- package/dist-server/service/board/thumbnail-scheduler.test.js +293 -0
- package/dist-server/service/board/thumbnail-scheduler.test.js.map +1 -0
- package/dist-server/service/board-template/board-template-mutation.js +5 -5
- package/dist-server/service/board-template/board-template-mutation.js.map +1 -1
- package/dist-server/service/group/group-mutation.js +2 -2
- package/dist-server/service/group/group-mutation.js.map +1 -1
- package/dist-server/service/index.js +2 -5
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/views/internal-board-service-view.html +2 -1
|
@@ -23,8 +23,8 @@ const fonts = async (domain) => {
|
|
|
23
23
|
.addSelect('FONT.uri', 'uri')
|
|
24
24
|
.where({ active: true })
|
|
25
25
|
.getRawMany();
|
|
26
|
-
var googleFonts = fonts.filter(({ provider }) => provider
|
|
27
|
-
var customFonts = fonts.filter(({ provider }) => provider
|
|
26
|
+
var googleFonts = fonts.filter(({ provider }) => provider === 'google');
|
|
27
|
+
var customFonts = fonts.filter(({ provider }) => provider === 'custom');
|
|
28
28
|
var customFontCSS = '';
|
|
29
29
|
for (const font of customFonts) {
|
|
30
30
|
var files = domain
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fonts.js","sourceRoot":"","sources":["../../server/controllers/fonts.ts"],"names":[],"mappings":";;;AAEA,yDAAgD;AAChD,qEAA4D;AAC5D,iDAA6D;AAEtD,MAAM,KAAK,GAAG,KAAK,EAAE,MAAe,EAAE,EAAE;IAC7C,MAAM,EAAE,GAA6B,MAAM,IAAA,qBAAa,EAAC,gBAAI,CAAC,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAA;IAEzF,IAAI,KAAK,GAAG,MAAM;QAChB,CAAC,CAAC,MAAM,EAAE;aACL,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC;aAC3B,QAAQ,CAAC,IAAI,CAAC;aACd,SAAS,CAAC,eAAe,EAAE,UAAU,CAAC;aACtC,SAAS,CAAC,SAAS,EAAE,IAAI,CAAC;aAC1B,SAAS,CAAC,UAAU,EAAE,KAAK,CAAC;aAC5B,KAAK,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;aAClD,UAAU,EAAE;QACjB,CAAC,CAAC,MAAM,EAAE;aACL,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC;aAC3B,QAAQ,CAAC,IAAI,CAAC;aACd,SAAS,CAAC,eAAe,EAAE,UAAU,CAAC;aACtC,SAAS,CAAC,SAAS,EAAE,IAAI,CAAC;aAC1B,SAAS,CAAC,UAAU,EAAE,KAAK,CAAC;aAC5B,KAAK,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;aACvB,UAAU,EAAE,CAAA;IAEnB,IAAI,WAAW,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,QAAQ,
|
|
1
|
+
{"version":3,"file":"fonts.js","sourceRoot":"","sources":["../../server/controllers/fonts.ts"],"names":[],"mappings":"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","sourcesContent":["import { SelectQueryBuilder } from 'typeorm'\n\nimport { Font } from '@things-factory/font-base'\nimport { Attachment } from '@things-factory/attachment-base'\nimport { getRepository, Domain } from '@things-factory/shell'\n\nexport const fonts = async (domain?: Domain) => {\n const qb: SelectQueryBuilder<Font> = await getRepository(Font).createQueryBuilder('FONT')\n\n var fonts = domain\n ? await qb\n .select('FONT.name', 'name')\n .distinct(true)\n .addSelect('FONT.provider', 'provider')\n .addSelect('FONT.id', 'id')\n .addSelect('FONT.uri', 'uri')\n .where({ domain: { id: domain.id }, active: true })\n .getRawMany()\n : await qb\n .select('FONT.name', 'name')\n .distinct(true)\n .addSelect('FONT.provider', 'provider')\n .addSelect('FONT.id', 'id')\n .addSelect('FONT.uri', 'uri')\n .where({ active: true })\n .getRawMany()\n\n var googleFonts = fonts.filter(({ provider }) => provider === 'google')\n var customFonts = fonts.filter(({ provider }) => provider === 'custom')\n\n var customFontCSS: string = ''\n\n for (const font of customFonts) {\n var files: Attachment[] = domain\n ? await getRepository(Attachment).findBy({\n domain: { id: domain.id },\n refBy: font.id\n })\n : await getRepository(Attachment).findBy({\n refBy: font.id\n })\n\n if (files && files.length > 0) {\n customFontCSS += files\n .map(file => {\n const { name: filename, fullpath } = file\n const bold = filename.toUpperCase().indexOf('BOLD') !== -1\n\n return `@font-face {\n font-family: '${font.name}';\n src: local('${font.name}'), url(${fullpath});\n font-weight: ${bold ? 'bold' : 'normal'};\n }\n `\n })\n .join('\\n')\n } else {\n customFontCSS += `@font-face {\n font-family: '${font.name}';\n src: local('${font.name}')${font.uri ? `, url(${font.uri})` : ''};\n }\n `\n }\n }\n\n return googleFonts.length > 0 || customFonts.length > 0\n ? [\n {\n ...(googleFonts.length > 0 && {\n google: {\n families: googleFonts.map(({ name }) => name)\n }\n }),\n ...(customFonts.length > 0 && {\n custom: {\n families: customFonts.map(({ name }) => name)\n }\n })\n },\n customFonts.length > 0 ? customFontCSS : null\n ]\n : []\n}\n"]}
|
|
@@ -8,11 +8,11 @@ const board_history_js_1 = require("../service/board/board-history.js");
|
|
|
8
8
|
const headlessModel = async (target, draft = false) => {
|
|
9
9
|
var { domain, id, model, name } = target || {};
|
|
10
10
|
if (model) {
|
|
11
|
-
if (typeof model
|
|
11
|
+
if (typeof model === 'string') {
|
|
12
12
|
model = JSON.parse(model);
|
|
13
13
|
}
|
|
14
14
|
else if (typeof model !== 'object') {
|
|
15
|
-
throw 'model should be a string or object';
|
|
15
|
+
throw new Error('model should be a string or object');
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
else {
|
|
@@ -27,10 +27,10 @@ const headlessModel = async (target, draft = false) => {
|
|
|
27
27
|
});
|
|
28
28
|
}
|
|
29
29
|
else {
|
|
30
|
-
throw 'parameter model or id mandatory';
|
|
30
|
+
throw new Error('parameter model or id mandatory');
|
|
31
31
|
}
|
|
32
32
|
if (!draft && board) {
|
|
33
|
-
const latestReleased = board.state
|
|
33
|
+
const latestReleased = board.state === 'released'
|
|
34
34
|
? board
|
|
35
35
|
: await (0, shell_1.getRepository)(board_history_js_1.BoardHistory)
|
|
36
36
|
.createQueryBuilder('history')
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"headless-model.js","sourceRoot":"","sources":["../../server/controllers/headless-model.ts"],"names":[],"mappings":";;;AAAA,qCAA4B;AAC5B,iDAAqE;AAErE,wDAAiD;AACjD,wEAAgE;AAEzD,MAAM,aAAa,GAAG,KAAK,EAAE,MAAM,EAAE,QAAiB,KAAK,EAAE,EAAE;IACpE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,MAAM,IAAI,EAAE,CAAA;IAE9C,IAAI,KAAK,EAAE,CAAC;QACV,IAAI,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"headless-model.js","sourceRoot":"","sources":["../../server/controllers/headless-model.ts"],"names":[],"mappings":";;;AAAA,qCAA4B;AAC5B,iDAAqE;AAErE,wDAAiD;AACjD,wEAAgE;AAEzD,MAAM,aAAa,GAAG,KAAK,EAAE,MAAM,EAAE,QAAiB,KAAK,EAAE,EAAE;IACpE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,MAAM,IAAI,EAAE,CAAA;IAE9C,IAAI,KAAK,EAAE,CAAC;QACV,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;QAC3B,CAAC;aAAM,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YACrC,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAA;QACvD,CAAC;IACH,CAAC;SAAM,CAAC;QACN,IAAI,EAAE,EAAE,CAAC;YACP,IAAI,KAAK,GAAG,MAAM,IAAA,qBAAa,EAAC,gBAAK,CAAC,CAAC,OAAO,CAAC;gBAC7C,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,IAAA,YAAE,EAAC,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE;aAChF,CAAC,CAAA;QACJ,CAAC;aAAM,IAAI,IAAI,EAAE,CAAC;YAChB,IAAI,KAAK,GAAG,MAAM,IAAA,qBAAa,EAAC,gBAAK,CAAC,CAAC,OAAO,CAAC;gBAC7C,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,IAAA,YAAE,EAAC,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE;aAClF,CAAC,CAAA;QACJ,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAA;QACpD,CAAC;QAED,IAAI,CAAC,KAAK,IAAI,KAAK,EAAE,CAAC;YACpB,MAAM,cAAc,GAClB,KAAK,CAAC,KAAK,KAAK,UAAU;gBACxB,CAAC,CAAC,KAAK;gBACP,CAAC,CAAC,MAAM,IAAA,qBAAa,EAAC,+BAAY,CAAC;qBAC9B,kBAAkB,CAAC,SAAS,CAAC;qBAC7B,KAAK,CAAC,kCAAkC,EAAE,EAAE,UAAU,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC;qBACnE,OAAO,CAAC,iBAAiB,EAAE,MAAM,CAAC;qBAClC,KAAK,CAAC,CAAC,CAAC;qBACR,MAAM,EAAE,CAAA;YAEjB,KAAK,GAAG,CAAC,cAAc,IAAI,KAAK,CAAC,CAAC,KAAK,CAAA;QACzC,CAAC;aAAM,CAAC;YACN,KAAK,GAAG,KAAK,CAAC,KAAK,CAAA;QACrB,CAAC;QAED,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IAC3B,CAAC;IAED,OAAO;QACL,IAAI,EAAE,IAAA,sBAAc,EAAC,MAAM,CAAC;QAC5B,KAAK;QACL,KAAK;KACN,CAAA;AACH,CAAC,CAAA;AA9CY,QAAA,aAAa,iBA8CzB","sourcesContent":["import { In } from 'typeorm'\nimport { getContextPath, getRepository } from '@things-factory/shell'\n\nimport { Board } from '../service/board/board.js'\nimport { BoardHistory } from '../service/board/board-history.js'\n\nexport const headlessModel = async (target, draft: boolean = false) => {\n var { domain, id, model, name } = target || {}\n\n if (model) {\n if (typeof model === 'string') {\n model = JSON.parse(model)\n } else if (typeof model !== 'object') {\n throw new Error('model should be a string or object')\n }\n } else {\n if (id) {\n var board = await getRepository(Board).findOne({\n where: { domain: { id: In([domain.id, domain.parentId].filter(Boolean)) }, id }\n })\n } else if (name) {\n var board = await getRepository(Board).findOne({\n where: { domain: { id: In([domain.id, domain.parentId].filter(Boolean)) }, name }\n })\n } else {\n throw new Error('parameter model or id mandatory')\n }\n\n if (!draft && board) {\n const latestReleased =\n board.state === 'released'\n ? board\n : await getRepository(BoardHistory)\n .createQueryBuilder('history')\n .where('history.originalId = :originalId', { originalId: board.id })\n .orderBy('history.version', 'DESC')\n .limit(1)\n .getOne()\n\n model = (latestReleased || board).model\n } else {\n model = board.model\n }\n\n model = JSON.parse(model)\n }\n\n return {\n base: getContextPath(domain),\n model,\n board\n }\n}\n"]}
|
|
@@ -29,8 +29,9 @@ const pdfToImage = async ({ pdfPath, fileName, extension = 'png', quality = 2, d
|
|
|
29
29
|
data: { pdfUrl, quality },
|
|
30
30
|
nonce: crypto_1.default.randomBytes(16).toString('hex')
|
|
31
31
|
});
|
|
32
|
-
//
|
|
33
|
-
|
|
32
|
+
// setContent 의 waitUntil 은 'load'/'domcontentloaded' 만 지원 → API/fetch 응답
|
|
33
|
+
// 정착은 waitForNetworkIdle 로 별도 대기.
|
|
34
|
+
await page.setContent(html, { waitUntil: 'load' });
|
|
34
35
|
await page.waitForNetworkIdle();
|
|
35
36
|
await page.$('#page');
|
|
36
37
|
const screenshot = await page.screenshot({
|
|
@@ -52,7 +53,7 @@ const pdfToImage = async ({ pdfPath, fileName, extension = 'png', quality = 2, d
|
|
|
52
53
|
}
|
|
53
54
|
catch (e) {
|
|
54
55
|
await pool.release(browser);
|
|
55
|
-
console.
|
|
56
|
+
console.error('Error creating thumbnail', e);
|
|
56
57
|
throw new Error('Error creating thumbnail');
|
|
57
58
|
}
|
|
58
59
|
};
|
|
@@ -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,
|
|
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"]}
|
|
@@ -23,7 +23,7 @@ const headlessPlaylist = async (target, draft = false) => {
|
|
|
23
23
|
if (!draft && playGroup) {
|
|
24
24
|
const { boards } = playGroup;
|
|
25
25
|
for (let board of boards) {
|
|
26
|
-
const latestReleased = board.state
|
|
26
|
+
const latestReleased = board.state === 'released'
|
|
27
27
|
? board
|
|
28
28
|
: await (0, shell_1.getRepository)(board_history_js_1.BoardHistory)
|
|
29
29
|
.createQueryBuilder('history')
|
|
@@ -60,7 +60,7 @@ const headlessPlaylist = async (target, draft = false) => {
|
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
62
|
else {
|
|
63
|
-
throw 'parameter id or name mandatory';
|
|
63
|
+
throw new Error('parameter id or name mandatory');
|
|
64
64
|
}
|
|
65
65
|
return {
|
|
66
66
|
base: (0, shell_1.getContextPath)(domain),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"headless-playlist.js","sourceRoot":"","sources":["../../server/controllers/headless-playlist.ts"],"names":[],"mappings":";;;AAAA,iDAAqE;AAErE,uEAA+D;AAC/D,wEAAgE;AAEzD,MAAM,gBAAgB,GAAG,KAAK,EAAE,MAAM,EAAE,QAAiB,KAAK,EAAE,EAAE;IACvE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,MAAM,IAAI,EAAE,CAAA;IAEvC,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC;QACf,MAAM,UAAU,GAAG,IAAA,qBAAa,EAAC,yBAAS,CAAC,CAAA;QAE3C,IAAI,EAAE,EAAE,CAAC;YACP,IAAI,SAAS,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC;gBACvC,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE;gBACxC,SAAS,EAAE,CAAC,QAAQ,CAAC;aACtB,CAAC,CAAA;QACJ,CAAC;aAAM,IAAI,IAAI,EAAE,CAAC;YAChB,IAAI,SAAS,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC;gBACvC,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE;gBAC1C,SAAS,EAAE,CAAC,QAAQ,CAAC;aACtB,CAAC,CAAA;QACJ,CAAC;QAED,IAAI,CAAC,KAAK,IAAI,SAAS,EAAE,CAAC;YACxB,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,CAAA;YAC5B,KAAK,IAAI,KAAK,IAAI,MAAM,EAAE,CAAC;gBACzB,MAAM,cAAc,GAClB,KAAK,CAAC,KAAK,
|
|
1
|
+
{"version":3,"file":"headless-playlist.js","sourceRoot":"","sources":["../../server/controllers/headless-playlist.ts"],"names":[],"mappings":"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","sourcesContent":["import { getContextPath, getRepository } from '@things-factory/shell'\n\nimport { PlayGroup } from '../service/play-group/play-group.js'\nimport { BoardHistory } from '../service/board/board-history.js'\n\nexport const headlessPlaylist = async (target, draft: boolean = false) => {\n var { domain, id, name } = target || {}\n\n if (id || name) {\n const repository = getRepository(PlayGroup)\n\n if (id) {\n var playGroup = await repository.findOne({\n where: { domain: { id: domain.id }, id },\n relations: ['boards']\n })\n } else if (name) {\n var playGroup = await repository.findOne({\n where: { domain: { id: domain.id }, name },\n relations: ['boards']\n })\n }\n\n if (!draft && playGroup) {\n const { boards } = playGroup\n for (let board of boards) {\n const latestReleased =\n board.state === 'released'\n ? board\n : await getRepository(BoardHistory)\n .createQueryBuilder('history')\n .where('history.originalId = :originalId', { originalId: board.id })\n .orderBy('history.version', 'DESC')\n .limit(1)\n .getOne()\n\n if (latestReleased) {\n board.model = latestReleased.model\n }\n }\n }\n\n playGroup.boards = playGroup.boards.sort((a, b) => {\n // 배열 A에 포함된 아이디의 순서를 가져옵니다.\n const indexOfOrder = (playGroup.order || []).indexOf(a.id)\n const indexOfBoards = (playGroup.order || []).indexOf(b.id)\n\n // 두 아이디의 순서를 비교하여 정렬합니다.\n if (indexOfOrder === -1 && indexOfBoards === -1) {\n // 두 아이디 모두 배열 A에 없는 경우, 그대로 유지합니다.\n return 0\n } else if (indexOfOrder === -1) {\n // 아이디 A만 배열 A에 없는 경우, 아이디 B를 먼저 정렬합니다.\n return 1\n } else if (indexOfBoards === -1) {\n // 아이디 B만 배열 A에 없는 경우, 아이디 A를 먼저 정렬합니다.\n return -1\n } else {\n // 두 아이디 모두 배열 A에 있는 경우, 배열 A의 아이디 순서대로 정렬합니다.\n return indexOfOrder - indexOfBoards\n }\n })\n } else {\n throw new Error('parameter id or name mandatory')\n }\n\n return {\n base: getContextPath(domain),\n playGroup\n }\n}\n"]}
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Board Service Headless Pool
|
|
3
3
|
* Using the unified headless pool system from @things-factory/shell
|
|
4
|
+
*
|
|
5
|
+
* 운영 환경 (GPU 없는 EC2 등) 에서는 idle 브라우저 상주 부담이 크다. min=0 으로 두면
|
|
6
|
+
* 사용 안 할 때 메모리 0, 첫 요청 시에만 spawn (~1-2 초 cold start). thumbnail 은
|
|
7
|
+
* fire-and-forget 백그라운드라 cold start 가 사용자 경험에 영향 없다.
|
|
8
|
+
*
|
|
9
|
+
* 환경 변수로 운영자가 조정:
|
|
10
|
+
* HEADLESS_POOL_BOARD_MIN (default 0 — 메모리 절약)
|
|
11
|
+
* HEADLESS_POOL_BOARD_MAX (default 10)
|
|
4
12
|
*/
|
|
5
13
|
/**
|
|
6
14
|
* Get the board headless pool
|
|
@@ -2,14 +2,24 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Board Service Headless Pool
|
|
4
4
|
* Using the unified headless pool system from @things-factory/shell
|
|
5
|
+
*
|
|
6
|
+
* 운영 환경 (GPU 없는 EC2 등) 에서는 idle 브라우저 상주 부담이 크다. min=0 으로 두면
|
|
7
|
+
* 사용 안 할 때 메모리 0, 첫 요청 시에만 spawn (~1-2 초 cold start). thumbnail 은
|
|
8
|
+
* fire-and-forget 백그라운드라 cold start 가 사용자 경험에 영향 없다.
|
|
9
|
+
*
|
|
10
|
+
* 환경 변수로 운영자가 조정:
|
|
11
|
+
* HEADLESS_POOL_BOARD_MIN (default 0 — 메모리 절약)
|
|
12
|
+
* HEADLESS_POOL_BOARD_MAX (default 10)
|
|
5
13
|
*/
|
|
6
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
15
|
exports.getHeadlessPool = getHeadlessPool;
|
|
8
16
|
const shell_1 = require("@things-factory/shell");
|
|
17
|
+
const POOL_MIN = parseInt(process.env.HEADLESS_POOL_BOARD_MIN ?? '0', 10);
|
|
18
|
+
const POOL_MAX = parseInt(process.env.HEADLESS_POOL_BOARD_MAX ?? '10', 10);
|
|
9
19
|
// Create the board pool instance
|
|
10
20
|
const boardPool = (0, shell_1.getOrCreateHeadlessPool)('board', {
|
|
11
|
-
min:
|
|
12
|
-
max:
|
|
21
|
+
min: POOL_MIN,
|
|
22
|
+
max: POOL_MAX,
|
|
13
23
|
args: [
|
|
14
24
|
...shell_1.HEADLESS_POOL_ARGUMENT_SETS.basic,
|
|
15
25
|
...shell_1.HEADLESS_POOL_ARGUMENT_SETS.keychain_safe
|
|
@@ -30,8 +40,8 @@ function getHeadlessPool() {
|
|
|
30
40
|
available: 0,
|
|
31
41
|
borrowed: 0,
|
|
32
42
|
pending: 0,
|
|
33
|
-
max:
|
|
34
|
-
min:
|
|
43
|
+
max: POOL_MAX,
|
|
44
|
+
min: POOL_MIN
|
|
35
45
|
};
|
|
36
46
|
}
|
|
37
47
|
//# sourceMappingURL=headless-pool-for-board.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"headless-pool-for-board.js","sourceRoot":"","sources":["../../server/controllers/headless-pool-for-board.ts"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"headless-pool-for-board.js","sourceRoot":"","sources":["../../server/controllers/headless-pool-for-board.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG;;AAwBH,0CAWC;AAjCD,iDAA4F;AAE5F,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,GAAG,EAAE,EAAE,CAAC,CAAA;AACzE,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,IAAI,EAAE,EAAE,CAAC,CAAA;AAE1E,iCAAiC;AACjC,MAAM,SAAS,GAAG,IAAA,+BAAuB,EAAC,OAAO,EAAE;IACjD,GAAG,EAAE,QAAQ;IACb,GAAG,EAAE,QAAQ;IACb,IAAI,EAAE;QACJ,GAAG,mCAA2B,CAAC,KAAK;QACpC,GAAG,mCAA2B,CAAC,aAAa;KAC7C;IACD,oBAAoB,EAAE,KAAK;IAC3B,YAAY,EAAE,IAAI;IAClB,aAAa,EAAE,IAAI;CACpB,CAAC,CAAA;AAEF;;;GAGG;AACH,SAAgB,eAAe;IAC7B,OAAO;QACL,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE;QAClC,OAAO,EAAE,CAAC,QAAa,EAAE,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC;QACvD,IAAI,EAAE,CAAC,EAAE,iDAAiD;QAC1D,SAAS,EAAE,CAAC;QACZ,QAAQ,EAAE,CAAC;QACX,OAAO,EAAE,CAAC;QACV,GAAG,EAAE,QAAQ;QACb,GAAG,EAAE,QAAQ;KACd,CAAA;AACH,CAAC","sourcesContent":["/**\n * Board Service Headless Pool\n * Using the unified headless pool system from @things-factory/shell\n *\n * 운영 환경 (GPU 없는 EC2 등) 에서는 idle 브라우저 상주 부담이 크다. min=0 으로 두면\n * 사용 안 할 때 메모리 0, 첫 요청 시에만 spawn (~1-2 초 cold start). thumbnail 은\n * fire-and-forget 백그라운드라 cold start 가 사용자 경험에 영향 없다.\n *\n * 환경 변수로 운영자가 조정:\n * HEADLESS_POOL_BOARD_MIN (default 0 — 메모리 절약)\n * HEADLESS_POOL_BOARD_MAX (default 10)\n */\n\nimport { getOrCreateHeadlessPool, HEADLESS_POOL_ARGUMENT_SETS } from '@things-factory/shell'\n\nconst POOL_MIN = parseInt(process.env.HEADLESS_POOL_BOARD_MIN ?? '0', 10)\nconst POOL_MAX = parseInt(process.env.HEADLESS_POOL_BOARD_MAX ?? '10', 10)\n\n// Create the board pool instance\nconst boardPool = getOrCreateHeadlessPool('board', {\n min: POOL_MIN,\n max: POOL_MAX,\n args: [\n ...HEADLESS_POOL_ARGUMENT_SETS.basic,\n ...HEADLESS_POOL_ARGUMENT_SETS.keychain_safe\n ],\n acquireTimeoutMillis: 15000,\n testOnBorrow: true,\n enableCleanup: true\n})\n\n/**\n * Get the board headless pool\n * @returns Pool instance with acquire/release methods\n */\nexport function getHeadlessPool() {\n return {\n acquire: () => boardPool.acquire(),\n release: (resource: any) => boardPool.release(resource),\n size: 0, // These will be dynamically calculated if needed\n available: 0,\n borrowed: 0,\n pending: 0,\n max: POOL_MAX,\n min: POOL_MIN\n }\n}"]}
|
|
@@ -60,10 +60,20 @@ async function setupLabelPage(browser) {
|
|
|
60
60
|
await page.goto(url, { timeout: 0, waitUntil: 'load' });
|
|
61
61
|
return { browser, page };
|
|
62
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* 라벨 출력은 (자동 출고 워크플로우 등) 즉시성 요구 — cold start 비용 (browser+page
|
|
65
|
+
* setup ~2-3 초) 이 사용자 경험에 직결. min=1 로 최소 1 개 warm 유지가 기본.
|
|
66
|
+
* 사용 빈도가 매우 낮은 사이트는 환경 변수로 0 까지 낮출 수 있다.
|
|
67
|
+
*
|
|
68
|
+
* HEADLESS_POOL_LABEL_MIN (default 1)
|
|
69
|
+
* HEADLESS_POOL_LABEL_MAX (default 10)
|
|
70
|
+
*/
|
|
71
|
+
const LABEL_POOL_MIN = parseInt(process.env.HEADLESS_POOL_LABEL_MIN ?? '1', 10);
|
|
72
|
+
const LABEL_POOL_MAX = parseInt(process.env.HEADLESS_POOL_LABEL_MAX ?? '10', 10);
|
|
63
73
|
// Create the label pool instance with custom setup
|
|
64
74
|
const labelPool = (0, shell_1.getOrCreateHeadlessPool)('label', {
|
|
65
|
-
min:
|
|
66
|
-
max:
|
|
75
|
+
min: LABEL_POOL_MIN,
|
|
76
|
+
max: LABEL_POOL_MAX,
|
|
67
77
|
args: [
|
|
68
78
|
...shell_1.HEADLESS_POOL_ARGUMENT_SETS.basic,
|
|
69
79
|
...shell_1.HEADLESS_POOL_ARGUMENT_SETS.keychain_safe
|
|
@@ -93,8 +103,8 @@ function getHeadlessPool() {
|
|
|
93
103
|
available: 0,
|
|
94
104
|
borrowed: 0,
|
|
95
105
|
pending: 0,
|
|
96
|
-
max:
|
|
97
|
-
min:
|
|
106
|
+
max: LABEL_POOL_MAX,
|
|
107
|
+
min: LABEL_POOL_MIN,
|
|
98
108
|
clear: () => labelPool.reset() // For font change compatibility
|
|
99
109
|
};
|
|
100
110
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"headless-pool-for-label.js","sourceRoot":"","sources":["../../server/controllers/headless-pool-for-label.ts"],"names":[],"mappings":";AAAA;;;GAGG;;
|
|
1
|
+
{"version":3,"file":"headless-pool-for-label.js","sourceRoot":"","sources":["../../server/controllers/headless-pool-for-label.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Label Service Headless Pool\n * Using the unified headless pool system from @things-factory/shell\n */\n\nimport { getOrCreateHeadlessPool, HEADLESS_POOL_ARGUMENT_SETS } from '@things-factory/shell'\nimport { pubsub } from '@things-factory/shell'\nimport { fonts } from './fonts.js'\n\n// Custom setup function for label page\nasync function setupLabelPage(browser: any) {\n const protocol = 'http'\n const host = 'localhost'\n const path = '/internal-label-command-view'\n const port = process.env.PORT\n const url = `${protocol}://${host}:${port}${path}`\n\n const page = await browser.newPage()\n const [fontsToUse, fontStyles] = await fonts()\n\n await page.setRequestInterception(true)\n\n page.on('console', async msg => {\n const args = await Promise.all(msg.args().map(arg => arg.jsonValue().catch(() => undefined)))\n\n if (args.some(a => a !== undefined)) {\n console.log(`[headless ${msg.type()}]`, ...args.filter(a => a !== undefined))\n } else {\n const text = msg.text()\n if (text) console.log(`[headless ${msg.type()}]`, text)\n }\n })\n\n page.on('pageerror', error => {\n console.log(`[headless pageerror] ${error.message}`)\n console.log(error.stack)\n })\n\n page.on('error', error => {\n console.log(`[headless fault] ${error}`)\n console.log(error.stack)\n })\n\n page.on('requestfailed', request => {\n console.log('Request failed:', request.url())\n })\n\n page.on('request', request => {\n if (request.url() === url) {\n request.continue({\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json'\n },\n postData: JSON.stringify({\n fonts: fontsToUse,\n fontStyles\n })\n })\n } else {\n request.continue()\n }\n })\n\n await page.goto(url, { timeout: 0, waitUntil: 'load' })\n\n return { browser, page }\n}\n\n/**\n * 라벨 출력은 (자동 출고 워크플로우 등) 즉시성 요구 — cold start 비용 (browser+page\n * setup ~2-3 초) 이 사용자 경험에 직결. min=1 로 최소 1 개 warm 유지가 기본.\n * 사용 빈도가 매우 낮은 사이트는 환경 변수로 0 까지 낮출 수 있다.\n *\n * HEADLESS_POOL_LABEL_MIN (default 1)\n * HEADLESS_POOL_LABEL_MAX (default 10)\n */\nconst LABEL_POOL_MIN = parseInt(process.env.HEADLESS_POOL_LABEL_MIN ?? '1', 10)\nconst LABEL_POOL_MAX = parseInt(process.env.HEADLESS_POOL_LABEL_MAX ?? '10', 10)\n\n// Create the label pool instance with custom setup\nconst labelPool = getOrCreateHeadlessPool('label', {\n min: LABEL_POOL_MIN,\n max: LABEL_POOL_MAX,\n args: [\n ...HEADLESS_POOL_ARGUMENT_SETS.basic,\n ...HEADLESS_POOL_ARGUMENT_SETS.keychain_safe\n ],\n acquireTimeoutMillis: 15000,\n testOnBorrow: true,\n enableCleanup: true,\n customSetup: setupLabelPage\n})\n\n// Font change event subscription\nsetTimeout(async () => {\n const eventSource = pubsub.subscribe('notify-font-changed')\n for await (const data of eventSource) {\n // Reset the pool when fonts change\n await labelPool.reset()\n }\n})\n\n/**\n * Get the label headless pool\n * @returns Pool instance with acquire/release methods\n */\nexport function getHeadlessPool() {\n return {\n acquire: () => labelPool.acquire(),\n release: (resource: any) => labelPool.release(resource),\n size: 0, // These will be dynamically calculated if needed\n available: 0,\n borrowed: 0,\n pending: 0,\n max: LABEL_POOL_MAX,\n min: LABEL_POOL_MIN,\n clear: () => labelPool.reset() // For font change compatibility\n }\n}"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface HeadlessRenderOptions {
|
|
2
|
+
id?: string;
|
|
3
|
+
model?: any;
|
|
4
|
+
data?: any;
|
|
5
|
+
width?: number;
|
|
6
|
+
height?: number;
|
|
7
|
+
options?: any;
|
|
8
|
+
context?: any;
|
|
9
|
+
draft?: boolean;
|
|
10
|
+
/** true 이면 thumbnail 크기(400x300)로 축소 */
|
|
11
|
+
isThumbnail?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* headless 브라우저 페이지를 준비하고 renderFn 을 실행한 결과를 반환한다.
|
|
15
|
+
*
|
|
16
|
+
* 공통 처리: pool acquire → headlessModel → fonts → viewport → request intercept → goto → data 주입 → renderFn → cleanup
|
|
17
|
+
*/
|
|
18
|
+
export declare function withHeadlessPage<T>({ id, model, data, width: w, height: h, options, context, draft, isThumbnail }: HeadlessRenderOptions, renderFn: (page: any) => Promise<T>): Promise<T | null>;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.withHeadlessPage = withHeadlessPage;
|
|
4
|
+
const fonts_js_1 = require("./fonts.js");
|
|
5
|
+
const headless_pool_for_board_js_1 = require("./headless-pool-for-board.js");
|
|
6
|
+
const headless_model_js_1 = require("./headless-model.js");
|
|
7
|
+
const protocol = 'http';
|
|
8
|
+
const host = 'localhost';
|
|
9
|
+
const path = '/internal-board-service-view';
|
|
10
|
+
/** headless page 에 console/error 이벤트 핸들러를 일괄 등록한다. */
|
|
11
|
+
function attachPageListeners(page) {
|
|
12
|
+
page.on('console', async (msg) => {
|
|
13
|
+
const args = await Promise.all(msg.args().map(arg => arg.jsonValue().catch(() => undefined)));
|
|
14
|
+
if (args.some(a => a !== undefined)) {
|
|
15
|
+
console.log(`[headless ${msg.type()}]`, ...args.filter(a => a !== undefined));
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
const text = msg.text();
|
|
19
|
+
if (text)
|
|
20
|
+
console.log(`[headless ${msg.type()}]`, text);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
page.on('pageerror', error => {
|
|
24
|
+
console.error(`[headless pageerror] ${error.message}`);
|
|
25
|
+
console.error(error.stack);
|
|
26
|
+
});
|
|
27
|
+
page.on('error', error => {
|
|
28
|
+
console.error(`[headless fault] ${error}`);
|
|
29
|
+
console.error(error.stack);
|
|
30
|
+
});
|
|
31
|
+
page.on('requestfailed', request => {
|
|
32
|
+
console.log('Request failed:', request.url());
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* headless 브라우저 페이지를 준비하고 renderFn 을 실행한 결과를 반환한다.
|
|
37
|
+
*
|
|
38
|
+
* 공통 처리: pool acquire → headlessModel → fonts → viewport → request intercept → goto → data 주입 → renderFn → cleanup
|
|
39
|
+
*/
|
|
40
|
+
async function withHeadlessPage({ id = '', model = null, data = null, width: w = 0, height: h = 0, options = {}, context = {}, draft = false, isThumbnail = false }, renderFn) {
|
|
41
|
+
const browser = (await (0, headless_pool_for_board_js_1.getHeadlessPool)().acquire());
|
|
42
|
+
if (!browser)
|
|
43
|
+
return null;
|
|
44
|
+
const { domain, user } = context.state;
|
|
45
|
+
var { model: resolvedModel, base } = await (0, headless_model_js_1.headlessModel)({ domain, id, model }, draft);
|
|
46
|
+
const [fontsToUse, fontStyles] = await (0, fonts_js_1.fonts)(domain);
|
|
47
|
+
resolvedModel.fonts = fontsToUse;
|
|
48
|
+
resolvedModel.fontStyles = fontStyles;
|
|
49
|
+
let { width, height } = resolvedModel;
|
|
50
|
+
if (isThumbnail) {
|
|
51
|
+
const ratio = Math.min(400 / width, 300 / height);
|
|
52
|
+
width = width * ratio;
|
|
53
|
+
height = height * ratio;
|
|
54
|
+
}
|
|
55
|
+
else if (w || h) {
|
|
56
|
+
const ratio = Math.min((w || width) / width, (h || height) / height);
|
|
57
|
+
width = width * ratio;
|
|
58
|
+
height = height * ratio;
|
|
59
|
+
}
|
|
60
|
+
width = Math.floor(w || Number(width));
|
|
61
|
+
height = Math.floor(h || Number(height));
|
|
62
|
+
const port = process.env.PORT ? `:${process.env.PORT}` : '';
|
|
63
|
+
const url = `${protocol}://${host}${port}${path}`;
|
|
64
|
+
const page = await browser.newPage();
|
|
65
|
+
let result = null;
|
|
66
|
+
try {
|
|
67
|
+
await page.setViewport({ width, height });
|
|
68
|
+
await page.setRequestInterception(true);
|
|
69
|
+
await page.setDefaultTimeout(10000);
|
|
70
|
+
attachPageListeners(page);
|
|
71
|
+
const token = await user?.sign();
|
|
72
|
+
page.on('request', request => {
|
|
73
|
+
if (request.url() === url) {
|
|
74
|
+
request.continue({
|
|
75
|
+
method: 'POST',
|
|
76
|
+
headers: {
|
|
77
|
+
'Content-Type': 'application/json',
|
|
78
|
+
'x-things-factory-domain': domain?.subdomain,
|
|
79
|
+
Authorization: 'Bearer ' + token
|
|
80
|
+
},
|
|
81
|
+
postData: JSON.stringify({ model: resolvedModel, base })
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
else if (request.url().startsWith(`${protocol}://${host}${port}`)) {
|
|
85
|
+
request.continue({
|
|
86
|
+
headers: {
|
|
87
|
+
...request.headers(),
|
|
88
|
+
'x-things-factory-domain': domain?.subdomain,
|
|
89
|
+
Authorization: 'Bearer ' + token
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
request.continue();
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
await page.goto(url);
|
|
98
|
+
await page.evaluate(async (data) => {
|
|
99
|
+
if (data) {
|
|
100
|
+
// @ts-ignore
|
|
101
|
+
s.data = data;
|
|
102
|
+
}
|
|
103
|
+
return new Promise(resolve => {
|
|
104
|
+
// @ts-ignore
|
|
105
|
+
requestAnimationFrame(() => resolve());
|
|
106
|
+
});
|
|
107
|
+
}, data);
|
|
108
|
+
// board 내부 async 로딩 (차트 데이터, 이미지, GraphQL query 등) 정착까지
|
|
109
|
+
// 대기. timeout 시 무시 (= 빠른 정적 board 영향 최소화).
|
|
110
|
+
await page.waitForNetworkIdle().catch(() => { });
|
|
111
|
+
result = await renderFn(page);
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
console.log(error);
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
await page.close();
|
|
118
|
+
(0, headless_pool_for_board_js_1.getHeadlessPool)().release(browser);
|
|
119
|
+
}
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=headless-render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"headless-render.js","sourceRoot":"","sources":["../../server/controllers/headless-render.ts"],"names":[],"mappings":"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","sourcesContent":["import { fonts } from './fonts.js'\nimport { getHeadlessPool } from './headless-pool-for-board.js'\nimport { headlessModel } from './headless-model.js'\n\nconst protocol = 'http'\nconst host = 'localhost'\nconst path = '/internal-board-service-view'\n\n/** headless page 에 console/error 이벤트 핸들러를 일괄 등록한다. */\nfunction attachPageListeners(page: any) {\n page.on('console', async msg => {\n const args = await Promise.all(msg.args().map(arg => arg.jsonValue().catch(() => undefined)))\n\n if (args.some(a => a !== undefined)) {\n console.log(`[headless ${msg.type()}]`, ...args.filter(a => a !== undefined))\n } else {\n const text = msg.text()\n if (text) console.log(`[headless ${msg.type()}]`, text)\n }\n })\n\n page.on('pageerror', error => {\n console.error(`[headless pageerror] ${error.message}`)\n console.error(error.stack)\n })\n\n page.on('error', error => {\n console.error(`[headless fault] ${error}`)\n console.error(error.stack)\n })\n\n page.on('requestfailed', request => {\n console.log('Request failed:', request.url())\n })\n}\n\nexport interface HeadlessRenderOptions {\n id?: string\n model?: any\n data?: any\n width?: number\n height?: number\n options?: any\n context?: any\n draft?: boolean\n /** true 이면 thumbnail 크기(400x300)로 축소 */\n isThumbnail?: boolean\n}\n\n/**\n * headless 브라우저 페이지를 준비하고 renderFn 을 실행한 결과를 반환한다.\n *\n * 공통 처리: pool acquire → headlessModel → fonts → viewport → request intercept → goto → data 주입 → renderFn → cleanup\n */\nexport async function withHeadlessPage<T>(\n {\n id = '',\n model = null,\n data = null,\n width: w = 0,\n height: h = 0,\n options = {} as any,\n context = {} as any,\n draft = false,\n isThumbnail = false\n }: HeadlessRenderOptions,\n renderFn: (page: any) => Promise<T>\n): Promise<T | null> {\n const browser = (await getHeadlessPool().acquire()) as any\n if (!browser) return null\n\n const { domain, user } = context.state\n\n var { model: resolvedModel, base } = await headlessModel({ domain, id, model }, draft)\n const [fontsToUse, fontStyles] = await fonts(domain)\n\n resolvedModel.fonts = fontsToUse\n resolvedModel.fontStyles = fontStyles\n\n let { width, height } = resolvedModel\n\n if (isThumbnail) {\n const ratio = Math.min(400 / width, 300 / height)\n width = width * ratio\n height = height * ratio\n } else if (w || h) {\n const ratio = Math.min((w || width) / width, (h || height) / height)\n width = width * ratio\n height = height * ratio\n }\n\n width = Math.floor(w || Number(width))\n height = Math.floor(h || Number(height))\n\n const port = process.env.PORT ? `:${process.env.PORT}` : ''\n const url = `${protocol}://${host}${port}${path}`\n const page = await browser.newPage()\n\n let result: T | null = null\n\n try {\n await page.setViewport({ width, height })\n await page.setRequestInterception(true)\n await page.setDefaultTimeout(10000)\n\n attachPageListeners(page)\n\n const token = await user?.sign()\n\n page.on('request', request => {\n if (request.url() === url) {\n request.continue({\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'x-things-factory-domain': domain?.subdomain,\n Authorization: 'Bearer ' + token\n },\n postData: JSON.stringify({ model: resolvedModel, base })\n })\n } else if (request.url().startsWith(`${protocol}://${host}${port}`)) {\n request.continue({\n headers: {\n ...request.headers(),\n 'x-things-factory-domain': domain?.subdomain,\n Authorization: 'Bearer ' + token\n }\n })\n } else {\n request.continue()\n }\n })\n\n await page.goto(url)\n\n await page.evaluate(async data => {\n if (data) {\n // @ts-ignore\n s.data = data\n }\n return new Promise(resolve => {\n // @ts-ignore\n requestAnimationFrame(() => resolve())\n })\n }, data)\n\n // board 내부 async 로딩 (차트 데이터, 이미지, GraphQL query 등) 정착까지\n // 대기. timeout 시 무시 (= 빠른 정적 board 영향 최소화).\n await page.waitForNetworkIdle().catch(() => {})\n\n result = await renderFn(page)\n } catch (error) {\n console.log(error)\n } finally {\n await page.close()\n getHeadlessPool().release(browser)\n }\n\n return result\n}\n"]}
|
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
export declare const BoardFunc: {
|
|
2
|
-
boardToZpl: (
|
|
3
|
-
|
|
4
|
-
model: any;
|
|
5
|
-
data: any;
|
|
6
|
-
orientation: any;
|
|
7
|
-
mirror?: boolean;
|
|
8
|
-
upsideDown?: boolean;
|
|
9
|
-
context: any;
|
|
10
|
-
draft?: boolean;
|
|
11
|
-
}) => Promise<string>;
|
|
12
|
-
boardToPdf: ({ id, model, data, width: w, height: h, options, context }?: {
|
|
2
|
+
boardToZpl: (opts: import("./label-command.js").LabelCommandOptions) => Promise<string | undefined>;
|
|
3
|
+
boardToPdf: ({ id, model, data, width, height, options, context }?: {
|
|
13
4
|
id?: string;
|
|
14
5
|
model?: any;
|
|
15
6
|
data?: any;
|
|
@@ -17,7 +8,7 @@ export declare const BoardFunc: {
|
|
|
17
8
|
height?: number;
|
|
18
9
|
options?: any;
|
|
19
10
|
context?: any;
|
|
20
|
-
}) => Promise
|
|
11
|
+
}) => Promise<unknown>;
|
|
21
12
|
headlessModel: (target: any, draft?: boolean) => Promise<{
|
|
22
13
|
base: string;
|
|
23
14
|
model: any;
|
|
@@ -1,5 +1,25 @@
|
|
|
1
|
+
export interface LabelCommandOptions {
|
|
2
|
+
id?: string;
|
|
3
|
+
model?: any;
|
|
4
|
+
data?: any;
|
|
5
|
+
orientation?: string;
|
|
6
|
+
mirror?: boolean;
|
|
7
|
+
upsideDown?: boolean;
|
|
8
|
+
context: any;
|
|
9
|
+
draft?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare function fnv1a(s: string): string;
|
|
12
|
+
export declare function cacheKey(opts: LabelCommandOptions): string;
|
|
13
|
+
/** 진단 / 운영용 — 현재 cache hit ratio 등 모니터링 가능. */
|
|
14
|
+
export declare function getLabelCacheStats(): {
|
|
15
|
+
cacheSize: number;
|
|
16
|
+
inFlight: number;
|
|
17
|
+
};
|
|
18
|
+
/** Test-only — 캐시 / in-flight state 초기화. */
|
|
19
|
+
export declare function _resetLabelState(): void;
|
|
20
|
+
export declare function _setBuildOverride(fn: ((opts: LabelCommandOptions) => Promise<string | undefined>) | null): void;
|
|
1
21
|
/**
|
|
2
|
-
* 라벨 출력
|
|
22
|
+
* 라벨 출력 — ZPL/GRF 문자열 반환.
|
|
3
23
|
*
|
|
4
24
|
* @param {String} id 모델 ID
|
|
5
25
|
* @param {Object} data 매핑할 데이터
|
|
@@ -7,13 +27,4 @@
|
|
|
7
27
|
* @param {boolean} mirror 좌우반전
|
|
8
28
|
* @param {boolean} upsideDown 상하반전
|
|
9
29
|
*/
|
|
10
|
-
export declare const labelcommand: (
|
|
11
|
-
id: any;
|
|
12
|
-
model: any;
|
|
13
|
-
data: any;
|
|
14
|
-
orientation: any;
|
|
15
|
-
mirror?: boolean;
|
|
16
|
-
upsideDown?: boolean;
|
|
17
|
-
context: any;
|
|
18
|
-
draft?: boolean;
|
|
19
|
-
}) => Promise<string>;
|
|
30
|
+
export declare const labelcommand: (opts: LabelCommandOptions) => Promise<string | undefined>;
|