@things-factory/board-service 10.0.0-beta.8 → 10.0.0-beta.85

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 (50) hide show
  1. package/dist-server/controllers/fonts.js +2 -2
  2. package/dist-server/controllers/fonts.js.map +1 -1
  3. package/dist-server/controllers/headless-model.js +4 -4
  4. package/dist-server/controllers/headless-model.js.map +1 -1
  5. package/dist-server/controllers/headless-pdf-to-image.js +3 -2
  6. package/dist-server/controllers/headless-pdf-to-image.js.map +1 -1
  7. package/dist-server/controllers/headless-playlist.js +2 -2
  8. package/dist-server/controllers/headless-playlist.js.map +1 -1
  9. package/dist-server/controllers/headless-render.d.ts +18 -0
  10. package/dist-server/controllers/headless-render.js +122 -0
  11. package/dist-server/controllers/headless-render.js.map +1 -0
  12. package/dist-server/controllers/index.d.ts +2 -2
  13. package/dist-server/controllers/label-command.js +25 -20
  14. package/dist-server/controllers/label-command.js.map +1 -1
  15. package/dist-server/controllers/pdf.d.ts +2 -10
  16. package/dist-server/controllers/pdf.js +3 -111
  17. package/dist-server/controllers/pdf.js.map +1 -1
  18. package/dist-server/controllers/screenshot.d.ts +2 -2
  19. package/dist-server/controllers/screenshot.js +3 -107
  20. package/dist-server/controllers/screenshot.js.map +1 -1
  21. package/dist-server/controllers/thumbnail.d.ts +1 -1
  22. package/dist-server/index.d.ts +6 -1
  23. package/dist-server/index.js +8 -13
  24. package/dist-server/index.js.map +1 -1
  25. package/dist-server/migrations/1762190000000-AddSourceImportSessionToBoard.d.ts +11 -0
  26. package/dist-server/migrations/1762190000000-AddSourceImportSessionToBoard.js +45 -0
  27. package/dist-server/migrations/1762190000000-AddSourceImportSessionToBoard.js.map +1 -0
  28. package/dist-server/routes.js +0 -1
  29. package/dist-server/routes.js.map +1 -1
  30. package/dist-server/service/board/board-mutation.d.ts +1 -0
  31. package/dist-server/service/board/board-mutation.js +125 -13
  32. package/dist-server/service/board/board-mutation.js.map +1 -1
  33. package/dist-server/service/board/board-query.d.ts +2 -1
  34. package/dist-server/service/board/board-query.js +34 -4
  35. package/dist-server/service/board/board-query.js.map +1 -1
  36. package/dist-server/service/board/board-reorder.test.d.ts +4 -0
  37. package/dist-server/service/board/board-reorder.test.js +347 -0
  38. package/dist-server/service/board/board-reorder.test.js.map +1 -0
  39. package/dist-server/service/board/board.d.ts +7 -0
  40. package/dist-server/service/board/board.js +11 -0
  41. package/dist-server/service/board/board.js.map +1 -1
  42. package/dist-server/service/board-template/board-template-mutation.js +5 -5
  43. package/dist-server/service/board-template/board-template-mutation.js.map +1 -1
  44. package/dist-server/service/group/group-mutation.js +2 -2
  45. package/dist-server/service/group/group-mutation.js.map +1 -1
  46. package/dist-server/service/index.js +2 -5
  47. package/dist-server/service/index.js.map +1 -1
  48. package/dist-server/tsconfig.tsbuildinfo +1 -1
  49. package/package.json +7 -7
  50. 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 == 'google');
27
- var customFonts = fonts.filter(({ provider }) => provider == 'custom');
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,IAAI,QAAQ,CAAC,CAAA;IACtE,IAAI,WAAW,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,QAAQ,IAAI,QAAQ,CAAC,CAAA;IAEtE,IAAI,aAAa,GAAW,EAAE,CAAA;IAE9B,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;QAC/B,IAAI,KAAK,GAAiB,MAAM;YAC9B,CAAC,CAAC,MAAM,IAAA,qBAAa,EAAC,4BAAU,CAAC,CAAC,MAAM,CAAC;gBACrC,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE;gBACzB,KAAK,EAAE,IAAI,CAAC,EAAE;aACf,CAAC;YACJ,CAAC,CAAC,MAAM,IAAA,qBAAa,EAAC,4BAAU,CAAC,CAAC,MAAM,CAAC;gBACrC,KAAK,EAAE,IAAI,CAAC,EAAE;aACf,CAAC,CAAA;QAEN,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC9B,aAAa,IAAI,KAAK;iBACnB,GAAG,CAAC,IAAI,CAAC,EAAE;gBACV,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAA;gBACzC,MAAM,IAAI,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;gBAE1D,OAAO;4BACW,IAAI,CAAC,IAAI;0BACX,IAAI,CAAC,IAAI,WAAW,QAAQ;2BAC3B,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ;;WAExC,CAAA;YACH,CAAC,CAAC;iBACD,IAAI,CAAC,IAAI,CAAC,CAAA;QACf,CAAC;aAAM,CAAC;YACN,aAAa,IAAI;0BACG,IAAI,CAAC,IAAI;wBACX,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE;;SAEjE,CAAA;QACL,CAAC;IACH,CAAC;IAED,OAAO,WAAW,CAAC,MAAM,GAAG,CAAC,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC;QACrD,CAAC,CAAC;YACE;gBACE,GAAG,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,IAAI;oBAC5B,MAAM,EAAE;wBACN,QAAQ,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC;qBAC9C;iBACF,CAAC;gBACF,GAAG,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,IAAI;oBAC5B,MAAM,EAAE;wBACN,QAAQ,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC;qBAC9C;iBACF,CAAC;aACH;YACD,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI;SAC9C;QACH,CAAC,CAAC,EAAE,CAAA;AACR,CAAC,CAAA;AA5EY,QAAA,KAAK,SA4EjB","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"]}
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 == 'string') {
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 == 'released'
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,IAAI,QAAQ,EAAE,CAAC;YAC7B,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;QAC3B,CAAC;aAAM,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YACrC,MAAM,oCAAoC,CAAA;QAC5C,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,iCAAiC,CAAA;QACzC,CAAC;QAED,IAAI,CAAC,KAAK,IAAI,KAAK,EAAE,CAAC;YACpB,MAAM,cAAc,GAClB,KAAK,CAAC,KAAK,IAAI,UAAU;gBACvB,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 '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 '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"]}
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
- await page.setContent(html, { waitUntil: 'networkidle0' });
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({
@@ -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,uBAAuB;QACvB,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,cAAc,EAAE,CAAC,CAAA;QAC1D,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,GAAG,CAAC,0BAA0B,EAAE,CAAC,CAAC,CAAA;QAC1C,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAA;IAC7C,CAAC;AACH,CAAC,CAAA;AAvDY,QAAA,UAAU,cAuDtB;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 // 페이지 로딩시 까지 기다리고 스크린샷\n await page.setContent(html, { waitUntil: 'networkidle0' })\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.log('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":";;;;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,GAAG,CAAC,0BAA0B,EAAE,CAAC,CAAC,CAAA;QAC1C,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.log('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 == 'released'
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":"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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 'parameter id or name mandatory'\n }\n\n return {\n base: getContextPath(domain),\n playGroup\n }\n}\n"]}
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"]}
@@ -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.log(`[headless pageerror] ${error.message}`);
25
+ console.log(error.stack);
26
+ });
27
+ page.on('error', error => {
28
+ console.log(`[headless fault] ${error}`);
29
+ console.log(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":";;AAsDA,4CAyGC;AA/JD,yCAAkC;AAClC,6EAA8D;AAC9D,2DAAmD;AAEnD,MAAM,QAAQ,GAAG,MAAM,CAAA;AACvB,MAAM,IAAI,GAAG,WAAW,CAAA;AACxB,MAAM,IAAI,GAAG,8BAA8B,CAAA;AAE3C,sDAAsD;AACtD,SAAS,mBAAmB,CAAC,IAAS;IACpC,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,KAAK,EAAC,GAAG,EAAC,EAAE;QAC7B,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,CAAA;QAE7F,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,EAAE,CAAC;YACpC,OAAO,CAAC,GAAG,CAAC,aAAa,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAA;QAC/E,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAA;YACvB,IAAI,IAAI;gBAAE,OAAO,CAAC,GAAG,CAAC,aAAa,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,CAAA;QACzD,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,EAAE,CAAC,WAAW,EAAE,KAAK,CAAC,EAAE;QAC3B,OAAO,CAAC,GAAG,CAAC,wBAAwB,KAAK,CAAC,OAAO,EAAE,CAAC,CAAA;QACpD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IAC1B,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE;QACvB,OAAO,CAAC,GAAG,CAAC,oBAAoB,KAAK,EAAE,CAAC,CAAA;QACxC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IAC1B,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,EAAE,CAAC,eAAe,EAAE,OAAO,CAAC,EAAE;QACjC,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;IAC/C,CAAC,CAAC,CAAA;AACJ,CAAC;AAeD;;;;GAIG;AACI,KAAK,UAAU,gBAAgB,CACpC,EACE,EAAE,GAAG,EAAE,EACP,KAAK,GAAG,IAAI,EACZ,IAAI,GAAG,IAAI,EACX,KAAK,EAAE,CAAC,GAAG,CAAC,EACZ,MAAM,EAAE,CAAC,GAAG,CAAC,EACb,OAAO,GAAG,EAAS,EACnB,OAAO,GAAG,EAAS,EACnB,KAAK,GAAG,KAAK,EACb,WAAW,GAAG,KAAK,EACG,EACxB,QAAmC;IAEnC,MAAM,OAAO,GAAG,CAAC,MAAM,IAAA,4CAAe,GAAE,CAAC,OAAO,EAAE,CAAQ,CAAA;IAC1D,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAA;IAEzB,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;IAEtC,IAAI,EAAE,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,GAAG,MAAM,IAAA,iCAAa,EAAC,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,KAAK,CAAC,CAAA;IACtF,MAAM,CAAC,UAAU,EAAE,UAAU,CAAC,GAAG,MAAM,IAAA,gBAAK,EAAC,MAAM,CAAC,CAAA;IAEpD,aAAa,CAAC,KAAK,GAAG,UAAU,CAAA;IAChC,aAAa,CAAC,UAAU,GAAG,UAAU,CAAA;IAErC,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,aAAa,CAAA;IAErC,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,KAAK,EAAE,GAAG,GAAG,MAAM,CAAC,CAAA;QACjD,KAAK,GAAG,KAAK,GAAG,KAAK,CAAA;QACrB,MAAM,GAAG,MAAM,GAAG,KAAK,CAAA;IACzB,CAAC;SAAM,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAClB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,GAAG,KAAK,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,GAAG,MAAM,CAAC,CAAA;QACpE,KAAK,GAAG,KAAK,GAAG,KAAK,CAAA;QACrB,MAAM,GAAG,MAAM,GAAG,KAAK,CAAA;IACzB,CAAC;IAED,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;IACtC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC,CAAA;IAExC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;IAC3D,MAAM,GAAG,GAAG,GAAG,QAAQ,MAAM,IAAI,GAAG,IAAI,GAAG,IAAI,EAAE,CAAA;IACjD,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAA;IAEpC,IAAI,MAAM,GAAa,IAAI,CAAA;IAE3B,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,WAAW,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;QACzC,MAAM,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,CAAA;QACvC,MAAM,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAA;QAEnC,mBAAmB,CAAC,IAAI,CAAC,CAAA;QAEzB,MAAM,KAAK,GAAG,MAAM,IAAI,EAAE,IAAI,EAAE,CAAA;QAEhC,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE;YAC3B,IAAI,OAAO,CAAC,GAAG,EAAE,KAAK,GAAG,EAAE,CAAC;gBAC1B,OAAO,CAAC,QAAQ,CAAC;oBACf,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE;wBACP,cAAc,EAAE,kBAAkB;wBAClC,yBAAyB,EAAE,MAAM,EAAE,SAAS;wBAC5C,aAAa,EAAE,SAAS,GAAG,KAAK;qBACjC;oBACD,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;iBACzD,CAAC,CAAA;YACJ,CAAC;iBAAM,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,GAAG,QAAQ,MAAM,IAAI,GAAG,IAAI,EAAE,CAAC,EAAE,CAAC;gBACpE,OAAO,CAAC,QAAQ,CAAC;oBACf,OAAO,EAAE;wBACP,GAAG,OAAO,CAAC,OAAO,EAAE;wBACpB,yBAAyB,EAAE,MAAM,EAAE,SAAS;wBAC5C,aAAa,EAAE,SAAS,GAAG,KAAK;qBACjC;iBACF,CAAC,CAAA;YACJ,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,QAAQ,EAAE,CAAA;YACpB,CAAC;QACH,CAAC,CAAC,CAAA;QAEF,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAEpB,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAC,IAAI,EAAC,EAAE;YAC/B,IAAI,IAAI,EAAE,CAAC;gBACT,aAAa;gBACb,CAAC,CAAC,IAAI,GAAG,IAAI,CAAA;YACf,CAAC;YACD,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE;gBAC3B,aAAa;gBACb,qBAAqB,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAA;YACxC,CAAC,CAAC,CAAA;QACJ,CAAC,EAAE,IAAI,CAAC,CAAA;QAER,wDAAwD;QACxD,2CAA2C;QAC3C,MAAM,IAAI,CAAC,kBAAkB,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;QAE/C,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAA;IAC/B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;IACpB,CAAC;YAAS,CAAC;QACT,MAAM,IAAI,CAAC,KAAK,EAAE,CAAA;QAClB,IAAA,4CAAe,GAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;IACpC,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC","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.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\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"]}
@@ -9,7 +9,7 @@ export declare const BoardFunc: {
9
9
  context: any;
10
10
  draft?: boolean;
11
11
  }) => Promise<string>;
12
- boardToPdf: ({ id, model, data, width: w, height: h, options, context }?: {
12
+ boardToPdf: ({ id, model, data, width, height, options, context }?: {
13
13
  id?: string;
14
14
  model?: any;
15
15
  data?: any;
@@ -17,7 +17,7 @@ export declare const BoardFunc: {
17
17
  height?: number;
18
18
  options?: any;
19
19
  context?: any;
20
- }) => Promise</*elided*/ any>;
20
+ }) => Promise<unknown>;
21
21
  headlessModel: (target: any, draft?: boolean) => Promise<{
22
22
  base: string;
23
23
  model: any;
@@ -21,32 +21,37 @@ const labelcommand = async ({ id, model, data, orientation, mirror = false, upsi
21
21
  if (!browser) {
22
22
  return;
23
23
  }
24
- const { page } = browser;
25
- var { model } = await (0, headless_model_js_1.headlessModel)({ domain, model, id }, draft);
26
- const grf = await page.evaluate(async (model, data, orientation, mirror, upsideDown) => {
27
- //@ts-ignore
28
- let s = createScene(model);
29
- if (data) {
30
- s.data = data;
31
- }
32
- return new Promise(resolve => {
33
- // @ts-ignore
34
- requestAnimationFrame(() => {
24
+ try {
25
+ const { page } = browser;
26
+ var { model } = await (0, headless_model_js_1.headlessModel)({ domain, model, id }, draft);
27
+ const grf = await page.evaluate(async (model, data, orientation, mirror, upsideDown) => {
28
+ //@ts-ignore
29
+ let s = createScene(model);
30
+ if (data) {
31
+ s.data = data;
32
+ }
33
+ return new Promise(resolve => {
35
34
  // @ts-ignore
36
- let grf = imageDataToGrf(s, model, orientation, mirror, upsideDown);
37
- resolve(grf);
38
- // @ts-ignore
39
- sceneContainer.removeChild(s.target);
40
- s.dispose();
35
+ requestAnimationFrame(() => {
36
+ // @ts-ignore
37
+ let grf = imageDataToGrf(s, model, orientation, mirror, upsideDown);
38
+ resolve(grf);
39
+ // @ts-ignore
40
+ sceneContainer.removeChild(s.target);
41
+ s.dispose();
42
+ });
41
43
  });
42
- });
43
- }, model, data, orientation, mirror, upsideDown);
44
- (0, headless_pool_for_label_js_1.getHeadlessPool)().release(browser);
45
- return `
44
+ }, model, data, orientation, mirror, upsideDown);
45
+ return `
46
46
  ^XA
47
47
  ^GFA,${grf}
48
48
  ^FS
49
49
  ^XZ`;
50
+ }
51
+ finally {
52
+ // 에러 발생 시에도 반드시 풀에 브라우저를 반환
53
+ (0, headless_pool_for_label_js_1.getHeadlessPool)().release(browser);
54
+ }
50
55
  };
51
56
  exports.labelcommand = labelcommand;
52
57
  //# sourceMappingURL=label-command.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"label-command.js","sourceRoot":"","sources":["../../server/controllers/label-command.ts"],"names":[],"mappings":";AAAA;;GAEG;;;AAEH,6EAA8D;AAC9D,2DAAmD;AAEnD;;;;;;;;GAQG;AACI,MAAM,YAAY,GAAG,KAAK,EAAE,EACjC,EAAE,EACF,KAAK,EACL,IAAI,EACJ,WAAW,EACX,MAAM,GAAG,KAAK,EACd,UAAU,GAAG,KAAK,EAClB,OAAO,EACP,KAAK,GAAG,KAAK,EACd,EAAE,EAAE;IACH,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;IAEhC,MAAM,OAAO,GAAG,CAAC,MAAM,IAAA,4CAAe,GAAE,CAAC,OAAO,EAAE,CAAQ,CAAA;IAC1D,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAM;IACR,CAAC;IAED,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAA;IAExB,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,IAAA,iCAAa,EAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,CAAA;IAEjE,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,QAAQ,CAC7B,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE;QACrD,YAAY;QACZ,IAAI,CAAC,GAAG,WAAW,CAAC,KAAK,CAAC,CAAA;QAC1B,IAAI,IAAI,EAAE,CAAC;YACT,CAAC,CAAC,IAAI,GAAG,IAAI,CAAA;QACf,CAAC;QACD,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE;YAC3B,aAAa;YACb,qBAAqB,CAAC,GAAG,EAAE;gBACzB,aAAa;gBACb,IAAI,GAAG,GAAG,cAAc,CAAC,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,CAAC,CAAA;gBACnE,OAAO,CAAC,GAAG,CAAC,CAAA;gBACZ,aAAa;gBACb,cAAc,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAA;gBACpC,CAAC,CAAC,OAAO,EAAE,CAAA;YACb,CAAC,CAAC,CAAA;QACJ,CAAC,CAAC,CAAA;IACJ,CAAC,EACD,KAAK,EACL,IAAI,EACJ,WAAW,EACX,MAAM,EACN,UAAU,CACX,CAAA;IAED,IAAA,4CAAe,GAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;IAElC,OAAO;;OAEF,GAAG;;IAEN,CAAA;AACJ,CAAC,CAAA;AAtDY,QAAA,YAAY,gBAsDxB","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\n\nimport { getHeadlessPool } from './headless-pool-for-label.js'\nimport { headlessModel } from './headless-model.js'\n\n/**\n * 라벨 출력\n *\n * @param {String} id 모델 ID\n * @param {Object} data 매핑할 데이터\n * @param {String} orientation (시계방향) N: 0, R: 90, I: 180, B: 270\n * @param {boolean} mirror 좌우반전\n * @param {boolean} upsideDown 상하반전\n */\nexport const labelcommand = async ({\n id,\n model,\n data,\n orientation,\n mirror = false,\n upsideDown = false,\n context,\n draft = false\n}) => {\n const { domain } = context.state\n\n const browser = (await getHeadlessPool().acquire()) as any\n if (!browser) {\n return\n }\n\n const { page } = browser\n\n var { model } = await headlessModel({ domain, model, id }, draft)\n\n const grf = await page.evaluate(\n async (model, data, orientation, mirror, upsideDown) => {\n //@ts-ignore\n let s = createScene(model)\n if (data) {\n s.data = data\n }\n return new Promise(resolve => {\n // @ts-ignore\n requestAnimationFrame(() => {\n // @ts-ignore\n let grf = imageDataToGrf(s, model, orientation, mirror, upsideDown)\n resolve(grf)\n // @ts-ignore\n sceneContainer.removeChild(s.target)\n s.dispose()\n })\n })\n },\n model,\n data,\n orientation,\n mirror,\n upsideDown\n )\n\n getHeadlessPool().release(browser)\n\n return `\n^XA\n^GFA,${grf}\n^FS\n^XZ`\n}\n"]}
1
+ {"version":3,"file":"label-command.js","sourceRoot":"","sources":["../../server/controllers/label-command.ts"],"names":[],"mappings":";AAAA;;GAEG;;;AAEH,6EAA8D;AAC9D,2DAAmD;AAEnD;;;;;;;;GAQG;AACI,MAAM,YAAY,GAAG,KAAK,EAAE,EACjC,EAAE,EACF,KAAK,EACL,IAAI,EACJ,WAAW,EACX,MAAM,GAAG,KAAK,EACd,UAAU,GAAG,KAAK,EAClB,OAAO,EACP,KAAK,GAAG,KAAK,EACd,EAAE,EAAE;IACH,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;IAEhC,MAAM,OAAO,GAAG,CAAC,MAAM,IAAA,4CAAe,GAAE,CAAC,OAAO,EAAE,CAAQ,CAAA;IAC1D,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAM;IACR,CAAC;IAED,IAAI,CAAC;QACH,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAA;QAExB,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,IAAA,iCAAa,EAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,CAAA;QAEjE,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,QAAQ,CAC7B,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE;YACrD,YAAY;YACZ,IAAI,CAAC,GAAG,WAAW,CAAC,KAAK,CAAC,CAAA;YAC1B,IAAI,IAAI,EAAE,CAAC;gBACT,CAAC,CAAC,IAAI,GAAG,IAAI,CAAA;YACf,CAAC;YACD,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE;gBAC3B,aAAa;gBACb,qBAAqB,CAAC,GAAG,EAAE;oBACzB,aAAa;oBACb,IAAI,GAAG,GAAG,cAAc,CAAC,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,CAAC,CAAA;oBACnE,OAAO,CAAC,GAAG,CAAC,CAAA;oBACZ,aAAa;oBACb,cAAc,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAA;oBACpC,CAAC,CAAC,OAAO,EAAE,CAAA;gBACb,CAAC,CAAC,CAAA;YACJ,CAAC,CAAC,CAAA;QACJ,CAAC,EACD,KAAK,EACL,IAAI,EACJ,WAAW,EACX,MAAM,EACN,UAAU,CACX,CAAA;QAED,OAAO;;OAEJ,GAAG;;IAEN,CAAA;IACF,CAAC;YAAS,CAAC;QACT,4BAA4B;QAC5B,IAAA,4CAAe,GAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;IACpC,CAAC;AACH,CAAC,CAAA;AAzDY,QAAA,YAAY,gBAyDxB","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\n\nimport { getHeadlessPool } from './headless-pool-for-label.js'\nimport { headlessModel } from './headless-model.js'\n\n/**\n * 라벨 출력\n *\n * @param {String} id 모델 ID\n * @param {Object} data 매핑할 데이터\n * @param {String} orientation (시계방향) N: 0, R: 90, I: 180, B: 270\n * @param {boolean} mirror 좌우반전\n * @param {boolean} upsideDown 상하반전\n */\nexport const labelcommand = async ({\n id,\n model,\n data,\n orientation,\n mirror = false,\n upsideDown = false,\n context,\n draft = false\n}) => {\n const { domain } = context.state\n\n const browser = (await getHeadlessPool().acquire()) as any\n if (!browser) {\n return\n }\n\n try {\n const { page } = browser\n\n var { model } = await headlessModel({ domain, model, id }, draft)\n\n const grf = await page.evaluate(\n async (model, data, orientation, mirror, upsideDown) => {\n //@ts-ignore\n let s = createScene(model)\n if (data) {\n s.data = data\n }\n return new Promise(resolve => {\n // @ts-ignore\n requestAnimationFrame(() => {\n // @ts-ignore\n let grf = imageDataToGrf(s, model, orientation, mirror, upsideDown)\n resolve(grf)\n // @ts-ignore\n sceneContainer.removeChild(s.target)\n s.dispose()\n })\n })\n },\n model,\n data,\n orientation,\n mirror,\n upsideDown\n )\n\n return `\n^XA\n^GFA,${grf}\n^FS\n^XZ`\n } finally {\n // 에러 발생 시에도 반드시 풀에 브라우저를 반환\n getHeadlessPool().release(browser)\n }\n}\n"]}
@@ -1,4 +1,4 @@
1
- export declare const pdf: ({ id, model, data, width: w, height: h, options, context }?: {
1
+ export declare const pdf: ({ id, model, data, width, height, options, context }?: {
2
2
  id?: string;
3
3
  model?: any;
4
4
  data?: any;
@@ -6,12 +6,4 @@ export declare const pdf: ({ id, model, data, width: w, height: h, options, cont
6
6
  height?: number;
7
7
  options?: any;
8
8
  context?: any;
9
- }) => Promise<({ id, model, data, width: w, height: h, options, context }?: {
10
- id?: string;
11
- model?: any;
12
- data?: any;
13
- width?: number;
14
- height?: number;
15
- options?: any;
16
- context?: any;
17
- }) => Promise</*elided*/ any>>;
9
+ }) => Promise<unknown>;
@@ -1,117 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.pdf = void 0;
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
- const pdf = async ({ id = '', model = null, data = null, width: w = 0, height: h = 0, options = {}, context = {} } = {}) => {
11
- const browser = (await (0, headless_pool_for_board_js_1.getHeadlessPool)().acquire());
12
- if (!browser) {
13
- return;
14
- }
15
- const { domain, user } = context.state;
16
- var { model, base } = await (0, headless_model_js_1.headlessModel)({ domain, id, model });
17
- const [fontsToUse, fontStyles] = await (0, fonts_js_1.fonts)(domain);
18
- model.fonts = fontsToUse;
19
- model.fontStyles = fontStyles;
20
- let { width, height } = model;
21
- width = Number(width);
22
- height = Number(height);
23
- if (!w) {
24
- w = width;
25
- }
26
- if (!h) {
27
- h = height;
28
- }
29
- let ratio = Math.min(w / width, h / height);
30
- width = Math.floor(width * ratio);
31
- height = Math.floor(height * ratio);
32
- const port = process.env.PORT ? `:${process.env.PORT}` : '';
33
- const url = `${protocol}://${host}${port}${path}`;
34
- const page = await browser.newPage();
35
- try {
36
- /* @remember-me width, height should be a integer */
37
- await page.setViewport({ width, height });
38
- await page.setRequestInterception(true);
39
- page.on('console', async (msg) => {
40
- const args = await Promise.all(msg.args().map(arg => arg.jsonValue().catch(() => undefined)));
41
- if (args.some(a => a !== undefined)) {
42
- console.log(`[headless ${msg.type()}]`, ...args.filter(a => a !== undefined));
43
- }
44
- else {
45
- const text = msg.text();
46
- if (text)
47
- console.log(`[headless ${msg.type()}]`, text);
48
- }
49
- });
50
- page.on('pageerror', error => {
51
- console.log(`[headless pageerror] ${error.message}`);
52
- console.log(error.stack);
53
- });
54
- page.on('error', error => {
55
- console.log(`[headless fault] ${error}`);
56
- console.log(error.stack);
57
- });
58
- page.on('requestfailed', request => {
59
- console.log('Request failed:', request.url());
60
- });
61
- const token = await user?.sign(); // TODO improve performance
62
- page.on('request', request => {
63
- if (request.url() === url) {
64
- request.continue({
65
- method: 'POST',
66
- headers: {
67
- 'Content-Type': 'application/json',
68
- 'x-things-factory-domain': domain?.subdomain,
69
- Authorization: 'Bearer ' + token
70
- },
71
- postData: JSON.stringify({
72
- model,
73
- base
74
- })
75
- });
76
- }
77
- else if (request.url().startsWith(`${protocol}://${host}${port}`)) {
78
- request.continue({
79
- headers: {
80
- ...request.headers(),
81
- 'x-things-factory-domain': domain?.subdomain,
82
- Authorization: 'Bearer ' + token
83
- }
84
- });
85
- }
86
- else {
87
- request.continue();
88
- }
89
- });
90
- await page.goto(url);
91
- await page.evaluate(async (data) => {
92
- if (data) {
93
- // @ts-ignore
94
- s.data = data;
95
- }
96
- // data 주입 후 강제 지연시킴.
97
- return new Promise(resolve => {
98
- // @ts-ignore
99
- requestAnimationFrame(() => resolve());
100
- });
101
- }, data);
102
- const pdf = await page.pdf({
103
- format: 'A4',
104
- ...options
105
- });
106
- }
107
- catch (error) {
108
- console.log(error);
109
- }
110
- finally {
111
- await page.close();
112
- (0, headless_pool_for_board_js_1.getHeadlessPool)().release(browser);
113
- }
114
- return exports.pdf;
4
+ const headless_render_js_1 = require("./headless-render.js");
5
+ const pdf = async ({ id = '', model = null, data = null, width = 0, height = 0, options = {}, context = {} } = {}) => {
6
+ return (0, headless_render_js_1.withHeadlessPage)({ id, model, data, width, height, options, context }, page => page.pdf({ format: 'A4', ...options }));
115
7
  };
116
8
  exports.pdf = pdf;
117
9
  //# sourceMappingURL=pdf.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"pdf.js","sourceRoot":"","sources":["../../server/controllers/pdf.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\nexport const pdf = async ({\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} = {}) => {\n const browser = (await getHeadlessPool().acquire()) as any\n\n if (!browser) {\n return\n }\n\n const { domain, user } = context.state\n\n var { model, base } = await headlessModel({ domain, id, model })\n const [fontsToUse, fontStyles] = await fonts(domain)\n\n model.fonts = fontsToUse\n model.fontStyles = fontStyles\n\n let { width, height } = model\n\n width = Number(width)\n height = Number(height)\n\n if (!w) {\n w = width\n }\n if (!h) {\n h = height\n }\n\n let ratio = Math.min(w / width, h / height)\n\n width = Math.floor(width * ratio)\n height = Math.floor(height * ratio)\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 try {\n /* @remember-me width, height should be a integer */\n await page.setViewport({ width, height })\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 const token = await user?.sign() // TODO improve performance\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({\n model,\n base\n })\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\n // data 주입 후 강제 지연시킴.\n return new Promise(resolve => {\n // @ts-ignore\n requestAnimationFrame(() => resolve())\n })\n }, data)\n\n const pdf = await page.pdf({\n format: 'A4',\n ...options\n })\n } catch (error) {\n console.log(error)\n } finally {\n await page.close()\n getHeadlessPool().release(browser)\n }\n\n return pdf\n}\n"]}
1
+ {"version":3,"file":"pdf.js","sourceRoot":"","sources":["../../server/controllers/pdf.ts"],"names":[],"mappings":";;;AAAA,6DAAuD;AAEhD,MAAM,GAAG,GAAG,KAAK,EAAE,EACxB,EAAE,GAAG,EAAE,EACP,KAAK,GAAG,IAAI,EACZ,IAAI,GAAG,IAAI,EACX,KAAK,GAAG,CAAC,EACT,MAAM,GAAG,CAAC,EACV,OAAO,GAAG,EAAS,EACnB,OAAO,GAAG,EAAS,KACjB,EAAE,EAAE,EAAE;IACR,OAAO,IAAA,qCAAgB,EACrB,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,EACpD,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,EAAE,CAAC,CAC/C,CAAA;AACH,CAAC,CAAA;AAbY,QAAA,GAAG,OAaf","sourcesContent":["import { withHeadlessPage } from './headless-render.js'\n\nexport const pdf = async ({\n id = '',\n model = null,\n data = null,\n width = 0,\n height = 0,\n options = {} as any,\n context = {} as any\n} = {}) => {\n return withHeadlessPage(\n { id, model, data, width, height, options, context },\n page => page.pdf({ format: 'A4', ...options })\n )\n}\n"]}
@@ -1,4 +1,4 @@
1
- export declare const screenshot: ({ id, model, data, width: w, height: h, options, isThumbnail, context, draft }?: {
1
+ export declare const screenshot: ({ id, model, data, width, height, options, isThumbnail, context, draft }?: {
2
2
  id?: string;
3
3
  model?: any;
4
4
  data?: any;
@@ -8,4 +8,4 @@ export declare const screenshot: ({ id, model, data, width: w, height: h, option
8
8
  isThumbnail?: boolean;
9
9
  context?: any;
10
10
  draft?: boolean;
11
- }) => Promise<any>;
11
+ }) => Promise<unknown>;