@things-factory/board-service 10.0.0-beta.51 → 10.0.0-beta.53
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/headless-render.d.ts +18 -0
- package/dist-server/controllers/headless-render.js +119 -0
- package/dist-server/controllers/headless-render.js.map +1 -0
- package/dist-server/controllers/index.d.ts +2 -2
- package/dist-server/controllers/pdf.d.ts +2 -2
- package/dist-server/controllers/pdf.js +3 -112
- 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/service/board/board-mutation.js +2 -2
- package/dist-server/service/board/board-mutation.js.map +1 -1
- package/dist-server/service/board-template/board-template-mutation.js +2 -2
- package/dist-server/service/board-template/board-template-mutation.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -6
|
@@ -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,119 @@
|
|
|
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
|
+
result = await renderFn(page);
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
console.log(error);
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
await page.close();
|
|
115
|
+
(0, headless_pool_for_board_js_1.getHeadlessPool)().release(browser);
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
//# 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,4CAqGC;AA3JD,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,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 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
|
|
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<
|
|
20
|
+
}) => Promise<unknown>;
|
|
21
21
|
headlessModel: (target: any, draft?: boolean) => Promise<{
|
|
22
22
|
base: string;
|
|
23
23
|
model: any;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const pdf: ({ id, model, data, width
|
|
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,4 +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<
|
|
9
|
+
}) => Promise<unknown>;
|
|
@@ -1,118 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.pdf = void 0;
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
|
|
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
|
-
var result = null;
|
|
36
|
-
try {
|
|
37
|
-
/* @remember-me width, height should be a integer */
|
|
38
|
-
await page.setViewport({ width, height });
|
|
39
|
-
await page.setRequestInterception(true);
|
|
40
|
-
page.on('console', async (msg) => {
|
|
41
|
-
const args = await Promise.all(msg.args().map(arg => arg.jsonValue().catch(() => undefined)));
|
|
42
|
-
if (args.some(a => a !== undefined)) {
|
|
43
|
-
console.log(`[headless ${msg.type()}]`, ...args.filter(a => a !== undefined));
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
const text = msg.text();
|
|
47
|
-
if (text)
|
|
48
|
-
console.log(`[headless ${msg.type()}]`, text);
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
page.on('pageerror', error => {
|
|
52
|
-
console.log(`[headless pageerror] ${error.message}`);
|
|
53
|
-
console.log(error.stack);
|
|
54
|
-
});
|
|
55
|
-
page.on('error', error => {
|
|
56
|
-
console.log(`[headless fault] ${error}`);
|
|
57
|
-
console.log(error.stack);
|
|
58
|
-
});
|
|
59
|
-
page.on('requestfailed', request => {
|
|
60
|
-
console.log('Request failed:', request.url());
|
|
61
|
-
});
|
|
62
|
-
const token = await user?.sign(); // TODO improve performance
|
|
63
|
-
page.on('request', request => {
|
|
64
|
-
if (request.url() === url) {
|
|
65
|
-
request.continue({
|
|
66
|
-
method: 'POST',
|
|
67
|
-
headers: {
|
|
68
|
-
'Content-Type': 'application/json',
|
|
69
|
-
'x-things-factory-domain': domain?.subdomain,
|
|
70
|
-
Authorization: 'Bearer ' + token
|
|
71
|
-
},
|
|
72
|
-
postData: JSON.stringify({
|
|
73
|
-
model,
|
|
74
|
-
base
|
|
75
|
-
})
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
else if (request.url().startsWith(`${protocol}://${host}${port}`)) {
|
|
79
|
-
request.continue({
|
|
80
|
-
headers: {
|
|
81
|
-
...request.headers(),
|
|
82
|
-
'x-things-factory-domain': domain?.subdomain,
|
|
83
|
-
Authorization: 'Bearer ' + token
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
request.continue();
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
await page.goto(url);
|
|
92
|
-
await page.evaluate(async (data) => {
|
|
93
|
-
if (data) {
|
|
94
|
-
// @ts-ignore
|
|
95
|
-
s.data = data;
|
|
96
|
-
}
|
|
97
|
-
// data 주입 후 강제 지연시킴.
|
|
98
|
-
return new Promise(resolve => {
|
|
99
|
-
// @ts-ignore
|
|
100
|
-
requestAnimationFrame(() => resolve());
|
|
101
|
-
});
|
|
102
|
-
}, data);
|
|
103
|
-
result = await page.pdf({
|
|
104
|
-
format: 'A4',
|
|
105
|
-
...options
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
catch (error) {
|
|
109
|
-
console.log(error);
|
|
110
|
-
}
|
|
111
|
-
finally {
|
|
112
|
-
await page.close();
|
|
113
|
-
(0, headless_pool_for_board_js_1.getHeadlessPool)().release(browser);
|
|
114
|
-
}
|
|
115
|
-
return result;
|
|
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 }));
|
|
116
7
|
};
|
|
117
8
|
exports.pdf = pdf;
|
|
118
9
|
//# sourceMappingURL=pdf.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pdf.js","sourceRoot":"","sources":["../../server/controllers/pdf.ts"],"names":[],"mappings":";;;AAAA,
|
|
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
|
|
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<
|
|
11
|
+
}) => Promise<unknown>;
|
|
@@ -1,113 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.screenshot = void 0;
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
const protocol = 'http';
|
|
8
|
-
const host = 'localhost';
|
|
9
|
-
const path = '/internal-board-service-view';
|
|
10
|
-
const screenshot = async ({ id = '', model = null, data = null, width: w = 0, height: h = 0, options = { encoding: 'base64' }, isThumbnail = false, context = {}, draft = false } = {}) => {
|
|
11
|
-
const browser = (await (0, headless_pool_for_board_js_1.getHeadlessPool)().acquire());
|
|
12
|
-
var screenshot = null;
|
|
13
|
-
if (!browser) {
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
const { domain, user } = context.state;
|
|
17
|
-
var { model, base } = await (0, headless_model_js_1.headlessModel)({ domain, id, model }, draft);
|
|
18
|
-
const [fontsToUse, fontStyles] = await (0, fonts_js_1.fonts)(domain);
|
|
19
|
-
model.fonts = fontsToUse;
|
|
20
|
-
model.fontStyles = fontStyles;
|
|
21
|
-
var { width, height } = model;
|
|
22
|
-
if (isThumbnail) {
|
|
23
|
-
var widthRatio = 400 / width, heightRatio = 300 / height;
|
|
24
|
-
var ratio = widthRatio < heightRatio ? widthRatio : heightRatio;
|
|
25
|
-
width = width * ratio;
|
|
26
|
-
height = height * ratio;
|
|
27
|
-
}
|
|
28
|
-
width = Math.floor(w || Number(width));
|
|
29
|
-
height = Math.floor(h || Number(height));
|
|
30
|
-
const port = process.env.PORT ? `:${process.env.PORT}` : '';
|
|
31
|
-
const url = `${protocol}://${host}${port}${path}`;
|
|
32
|
-
var page = await browser.newPage();
|
|
33
|
-
try {
|
|
34
|
-
/* @remember-me width, height should be a integer */
|
|
35
|
-
await page.setViewport({ width, height });
|
|
36
|
-
await page.setRequestInterception(true);
|
|
37
|
-
await page.setDefaultTimeout(10000);
|
|
38
|
-
page.on('console', async (msg) => {
|
|
39
|
-
const args = await Promise.all(msg.args().map(arg => arg.jsonValue().catch(() => undefined)));
|
|
40
|
-
if (args.some(a => a !== undefined)) {
|
|
41
|
-
console.log(`[headless ${msg.type()}]`, ...args.filter(a => a !== undefined));
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
const text = msg.text();
|
|
45
|
-
if (text)
|
|
46
|
-
console.log(`[headless ${msg.type()}]`, text);
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
page.on('pageerror', error => {
|
|
50
|
-
console.log(`[headless pageerror] ${error.message}`);
|
|
51
|
-
console.log(error.stack);
|
|
52
|
-
});
|
|
53
|
-
page.on('error', error => {
|
|
54
|
-
console.log(`[headless fault] ${error}`);
|
|
55
|
-
console.log(error.stack);
|
|
56
|
-
});
|
|
57
|
-
page.on('requestfailed', request => {
|
|
58
|
-
console.log('Request failed:', request.url());
|
|
59
|
-
});
|
|
60
|
-
const token = await user?.sign(); // TODO improve performance
|
|
61
|
-
page.on('request', request => {
|
|
62
|
-
if (request.url() === url) {
|
|
63
|
-
request.continue({
|
|
64
|
-
method: 'POST',
|
|
65
|
-
headers: {
|
|
66
|
-
'Content-Type': 'application/json',
|
|
67
|
-
'x-things-factory-domain': domain?.subdomain,
|
|
68
|
-
Authorization: 'Bearer ' + token
|
|
69
|
-
},
|
|
70
|
-
postData: JSON.stringify({
|
|
71
|
-
model,
|
|
72
|
-
base
|
|
73
|
-
})
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
else if (request.url().startsWith(`${protocol}://${host}${port}`)) {
|
|
77
|
-
request.continue({
|
|
78
|
-
headers: {
|
|
79
|
-
...request.headers(),
|
|
80
|
-
'x-things-factory-domain': domain?.subdomain,
|
|
81
|
-
Authorization: 'Bearer ' + token
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
else {
|
|
86
|
-
request.continue();
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
await page.goto(url);
|
|
90
|
-
await page.evaluate(async (data) => {
|
|
91
|
-
if (data) {
|
|
92
|
-
// @ts-ignore
|
|
93
|
-
s.data = data;
|
|
94
|
-
}
|
|
95
|
-
// data 주입 후 강제 지연시킴.
|
|
96
|
-
return new Promise(resolve => {
|
|
97
|
-
// @ts-ignore
|
|
98
|
-
requestAnimationFrame(() => resolve());
|
|
99
|
-
});
|
|
100
|
-
}, data);
|
|
101
|
-
screenshot = await page.screenshot(options);
|
|
102
|
-
}
|
|
103
|
-
catch (error) {
|
|
104
|
-
console.log(error);
|
|
105
|
-
}
|
|
106
|
-
finally {
|
|
107
|
-
await page.close();
|
|
108
|
-
(0, headless_pool_for_board_js_1.getHeadlessPool)().release(browser);
|
|
109
|
-
}
|
|
110
|
-
return screenshot;
|
|
4
|
+
const headless_render_js_1 = require("./headless-render.js");
|
|
5
|
+
const screenshot = async ({ id = '', model = null, data = null, width = 0, height = 0, options = { encoding: 'base64' }, isThumbnail = false, context = {}, draft = false } = {}) => {
|
|
6
|
+
return (0, headless_render_js_1.withHeadlessPage)({ id, model, data, width, height, options, context, draft, isThumbnail }, page => page.screenshot(options));
|
|
111
7
|
};
|
|
112
8
|
exports.screenshot = screenshot;
|
|
113
9
|
//# sourceMappingURL=screenshot.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screenshot.js","sourceRoot":"","sources":["../../server/controllers/screenshot.ts"],"names":[],"mappings":";;;AAAA,
|
|
1
|
+
{"version":3,"file":"screenshot.js","sourceRoot":"","sources":["../../server/controllers/screenshot.ts"],"names":[],"mappings":";;;AAAA,6DAAuD;AAEhD,MAAM,UAAU,GAAG,KAAK,EAAE,EAC/B,EAAE,GAAG,EAAE,EACP,KAAK,GAAG,IAAI,EACZ,IAAI,GAAG,IAAI,EACX,KAAK,GAAG,CAAC,EACT,MAAM,GAAG,CAAC,EACV,OAAO,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAS,EACvC,WAAW,GAAG,KAAK,EACnB,OAAO,GAAG,EAAS,EACnB,KAAK,GAAG,KAAK,KACX,EAAE,EAAE,EAAE;IACR,OAAO,IAAA,qCAAgB,EACrB,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,EACxE,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CACjC,CAAA;AACH,CAAC,CAAA;AAfY,QAAA,UAAU,cAetB","sourcesContent":["import { withHeadlessPage } from './headless-render.js'\n\nexport const screenshot = async ({\n id = '',\n model = null,\n data = null,\n width = 0,\n height = 0,\n options = { encoding: 'base64' } as any,\n isThumbnail = false,\n context = {} as any,\n draft = false\n} = {}) => {\n return withHeadlessPage(\n { id, model, data, width, height, options, context, draft, isThumbnail },\n page => page.screenshot(options)\n )\n}\n"]}
|
|
@@ -165,7 +165,7 @@ let BoardMutation = class BoardMutation {
|
|
|
165
165
|
thumbnailPromise,
|
|
166
166
|
new Promise((_, reject) => setTimeout(() => reject(new Error('5 seconds timeout')), 5000))
|
|
167
167
|
]);
|
|
168
|
-
patch.thumbnail = 'data:image/png;base64,' + thumbnailBase64
|
|
168
|
+
patch.thumbnail = 'data:image/png;base64,' + thumbnailBase64;
|
|
169
169
|
}
|
|
170
170
|
catch (e) {
|
|
171
171
|
console.warn(`Failed to get thumbnail for '${board.name}' in first 5 seconds`);
|
|
@@ -181,7 +181,7 @@ let BoardMutation = class BoardMutation {
|
|
|
181
181
|
await (0, shell_1.getDataSource)().transaction(async (tx) => {
|
|
182
182
|
await (0, shell_1.getRepository)(board_js_1.Board, tx).save({
|
|
183
183
|
id: updated.id,
|
|
184
|
-
thumbnail: 'data:image/png;base64,' + thumbnailBase64
|
|
184
|
+
thumbnail: 'data:image/png;base64,' + thumbnailBase64
|
|
185
185
|
});
|
|
186
186
|
});
|
|
187
187
|
})
|