@things-factory/integration-base 8.0.0-alpha.31 → 8.0.0-alpha.34

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 (36) hide show
  1. package/dist-server/engine/connector/headless-connector.d.ts +23 -0
  2. package/dist-server/engine/connector/headless-connector.js +283 -0
  3. package/dist-server/engine/connector/headless-connector.js.map +1 -0
  4. package/dist-server/engine/connector/http-connector.js +1 -1
  5. package/dist-server/engine/connector/http-connector.js.map +1 -1
  6. package/dist-server/engine/connector/index.d.ts +1 -0
  7. package/dist-server/engine/connector/index.js +1 -0
  8. package/dist-server/engine/connector/index.js.map +1 -1
  9. package/dist-server/engine/index.d.ts +1 -0
  10. package/dist-server/engine/index.js +1 -0
  11. package/dist-server/engine/index.js.map +1 -1
  12. package/dist-server/engine/resource-pool/headless-pool.d.ts +1 -0
  13. package/dist-server/engine/resource-pool/headless-pool.js +121 -0
  14. package/dist-server/engine/resource-pool/headless-pool.js.map +1 -0
  15. package/dist-server/engine/resource-pool/index.d.ts +1 -0
  16. package/dist-server/engine/resource-pool/index.js +5 -0
  17. package/dist-server/engine/resource-pool/index.js.map +1 -0
  18. package/dist-server/engine/task/headless-post.js +19 -33
  19. package/dist-server/engine/task/headless-post.js.map +1 -1
  20. package/dist-server/engine/task/headless-scrap.js +20 -13
  21. package/dist-server/engine/task/headless-scrap.js.map +1 -1
  22. package/dist-server/tsconfig.tsbuildinfo +1 -1
  23. package/package.json +3 -2
  24. package/server/engine/connector/headless-connector.ts +341 -0
  25. package/server/engine/connector/http-connector.ts +1 -1
  26. package/server/engine/connector/index.ts +1 -0
  27. package/server/engine/index.ts +1 -0
  28. package/server/engine/resource-pool/headless-pool.ts +136 -0
  29. package/server/engine/resource-pool/index.ts +1 -0
  30. package/server/engine/task/headless-post.ts +21 -40
  31. package/server/engine/task/headless-scrap.ts +21 -18
  32. package/translations/en.json +11 -4
  33. package/translations/ja.json +11 -4
  34. package/translations/ko.json +11 -4
  35. package/translations/ms.json +11 -4
  36. package/translations/zh.json +11 -4
@@ -0,0 +1,23 @@
1
+ import { Connector } from '../types';
2
+ export declare class HeadlessConnector implements Connector {
3
+ ready(connectionConfigs: any): Promise<void>;
4
+ connect(connection: any): Promise<void>;
5
+ applyCookiesAndVerifySession(page: any, cookies: any, loginInfo: any): Promise<void>;
6
+ performLogin(page: any, uri: any, loginInfo: any): Promise<void>;
7
+ resolveShadowDom(page: any, shadowSelectors: any, targetSelector: any): Promise<any>;
8
+ disconnect(connection: any): Promise<void>;
9
+ get parameterSpec(): ({
10
+ type: string;
11
+ name: string;
12
+ label: string;
13
+ value?: undefined;
14
+ } | {
15
+ type: string;
16
+ name: string;
17
+ label: string;
18
+ value: number;
19
+ })[];
20
+ get taskPrefixes(): string[];
21
+ get description(): string;
22
+ get help(): string;
23
+ }
@@ -0,0 +1,283 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HeadlessConnector = void 0;
4
+ const connection_manager_1 = require("../connection-manager");
5
+ const headless_pool_1 = require("../resource-pool/headless-pool");
6
+ /*
7
+ Functionality of the headless-connector:
8
+ - Provides a mechanism to acquire an active session page.
9
+ - Performs login when necessary to obtain valid cookies.
10
+ - Applies these cookies to the page for session management.
11
+ - Pages are acquired from the `headlessPool`, which manages browser instances.
12
+ - During the login process, pages from the pool are used for actions like form filling and navigation.
13
+ - Valid cookies are saved after login and reused for subsequent page acquisitions.
14
+ - Users must explicitly release the page after use through the `releasePage` method.
15
+ - Released pages are returned to the `headlessPool` for reuse.
16
+ */
17
+ class HeadlessConnector {
18
+ async ready(connectionConfigs) {
19
+ await Promise.all(connectionConfigs.map(this.connect.bind(this)));
20
+ connection_manager_1.ConnectionManager.logger.info('headless-connector connections are ready');
21
+ }
22
+ async connect(connection) {
23
+ const { endpoint: uri = '1', params: { username = '', password = '', loginPath = '/login', usernameSelector = '#username', passwordSelector = '#password', submitSelector = '#submit', successSelector = null, shadowDomSelectors = '', // Comma separated shadow DOM selectors
24
+ timeout = 15000, // Default timeout for operations
25
+ retries = 3 // Default number of retries for login or page actions
26
+ } = {} } = connection;
27
+ const loginInfo = {
28
+ loginRequired: Boolean(username), // Determine if login is required
29
+ username,
30
+ password,
31
+ loginPath,
32
+ timeout,
33
+ retries,
34
+ loginSelectors: {
35
+ usernameSelector,
36
+ passwordSelector,
37
+ submitSelector,
38
+ successSelector,
39
+ shadowDomSelectors: shadowDomSelectors
40
+ .split(',')
41
+ .map(selector => selector.trim())
42
+ .filter(Boolean)
43
+ }
44
+ };
45
+ async function acquireBrowser() {
46
+ try {
47
+ const pool = (0, headless_pool_1.getHeadlessPool)();
48
+ const browser = await pool.acquire();
49
+ return browser;
50
+ }
51
+ catch (error) {
52
+ connection_manager_1.ConnectionManager.logger.error('Failed to acquire browser:', error);
53
+ throw error;
54
+ }
55
+ }
56
+ async function releaseBrowser(browser) {
57
+ try {
58
+ const pool = (0, headless_pool_1.getHeadlessPool)();
59
+ await pool.release(browser);
60
+ }
61
+ catch (error) {
62
+ connection_manager_1.ConnectionManager.logger.error('Failed to release browser:', error);
63
+ }
64
+ }
65
+ connection_manager_1.ConnectionManager.addConnectionInstance(connection, {
66
+ endpoint: connection.endpoint,
67
+ params: connection.params,
68
+ acquireSessionPage: async () => {
69
+ const browser = await acquireBrowser();
70
+ let page;
71
+ let cookies = connection.cookies;
72
+ try {
73
+ page = await browser.newPage();
74
+ page.on('console', async (msg) => {
75
+ console.log(`[browser ${msg.type()}] ${msg.text()}`);
76
+ });
77
+ await page.setRequestInterception(true);
78
+ // page.on('request', request => {
79
+ // const resourceType = request.resourceType()
80
+ // if (resourceType === 'image' || resourceType === 'stylesheet' || resourceType === 'font') {
81
+ // request.abort() // 이미지, 스타일시트, 폰트 요청 차단
82
+ // } else {
83
+ // request.continue() // 나머지 요청은 진행
84
+ // }
85
+ // })
86
+ page.on('requestfailed', request => {
87
+ var _a;
88
+ console.log('Request failed:');
89
+ console.log(`- URL: ${request.url()}`);
90
+ console.log(`- Method: ${request.method()}`);
91
+ console.log(`- Failure Text: ${(_a = request.failure()) === null || _a === void 0 ? void 0 : _a.errorText}`);
92
+ console.log(`- Headers:`, request.headers());
93
+ // POST 데이터 (필요한 경우)
94
+ if (request.postData()) {
95
+ console.log(`- Post Data: ${request.postData()}`);
96
+ }
97
+ });
98
+ if (cookies && isCookieValid(cookies)) {
99
+ await this.applyCookiesAndVerifySession(page, cookies, loginInfo);
100
+ return page;
101
+ }
102
+ if (loginInfo.loginRequired) {
103
+ await this.performLogin(page, uri, loginInfo);
104
+ cookies = await page.cookies();
105
+ connection.cookies = cookies;
106
+ }
107
+ return page;
108
+ }
109
+ catch (error) {
110
+ connection_manager_1.ConnectionManager.logger.error('Failed to acquire session page:', error);
111
+ throw error;
112
+ }
113
+ },
114
+ releasePage: async (page) => {
115
+ try {
116
+ if (page) {
117
+ const browser = page.browser();
118
+ await page.close();
119
+ await releaseBrowser(browser);
120
+ }
121
+ }
122
+ catch (error) {
123
+ connection_manager_1.ConnectionManager.logger.error('Failed to release page:', error);
124
+ }
125
+ },
126
+ acquireBrowser,
127
+ releaseBrowser
128
+ });
129
+ connection_manager_1.ConnectionManager.logger.info(`headless-connector connection(${connection.name}:${connection.endpoint}) is connected`);
130
+ }
131
+ async applyCookiesAndVerifySession(page, cookies, loginInfo) {
132
+ await page.setCookie(...cookies);
133
+ await page.reload({ waitUntil: 'networkidle2', timeout: loginInfo.timeout });
134
+ if (loginInfo.loginRequired && loginInfo.loginSelectors.successSelector) {
135
+ const success = await this.resolveShadowDom(page, loginInfo.loginSelectors.shadowDomSelectors, loginInfo.loginSelectors.successSelector);
136
+ if (!success) {
137
+ throw new Error('Session invalid, login required');
138
+ }
139
+ }
140
+ }
141
+ async performLogin(page, uri, loginInfo) {
142
+ for (let attempt = 1; attempt <= loginInfo.retries; attempt++) {
143
+ try {
144
+ await page.goto(`${uri}${loginInfo.loginPath}`, { waitUntil: 'networkidle2', timeout: loginInfo.timeout });
145
+ const usernameInput = await this.resolveShadowDom(page, loginInfo.loginSelectors.shadowDomSelectors, loginInfo.loginSelectors.usernameSelector);
146
+ const passwordInput = await this.resolveShadowDom(page, loginInfo.loginSelectors.shadowDomSelectors, loginInfo.loginSelectors.passwordSelector);
147
+ const submitButton = await this.resolveShadowDom(page, loginInfo.loginSelectors.shadowDomSelectors, loginInfo.loginSelectors.submitSelector);
148
+ if (!usernameInput || !passwordInput || !submitButton) {
149
+ throw new Error('Failed to locate input elements in shadow DOM');
150
+ }
151
+ await usernameInput.type(loginInfo.username);
152
+ await passwordInput.type(loginInfo.password);
153
+ // Capture the response of the form submission
154
+ const [response] = await Promise.all([
155
+ page.waitForNavigation({ waitUntil: 'networkidle2', timeout: loginInfo.timeout }),
156
+ submitButton.click()
157
+ ]);
158
+ // Check response status code
159
+ if (response) {
160
+ const status = response.status();
161
+ if (status >= 200 && status < 300) {
162
+ connection_manager_1.ConnectionManager.logger.info(`Login successful with status code: ${status}`);
163
+ return; // Login successful
164
+ }
165
+ else if (status >= 400) {
166
+ throw new Error(`Login failed with status code: ${status}`);
167
+ }
168
+ }
169
+ else {
170
+ throw new Error('No response received during login');
171
+ }
172
+ if (loginInfo.loginSelectors.successSelector) {
173
+ const success = await this.resolveShadowDom(page, loginInfo.loginSelectors.shadowDomSelectors, loginInfo.loginSelectors.successSelector);
174
+ if (!success) {
175
+ throw new Error('Login failed: Success selector not found');
176
+ }
177
+ }
178
+ }
179
+ catch (error) {
180
+ connection_manager_1.ConnectionManager.logger.warn(`Login attempt ${attempt} failed:`, error);
181
+ if (attempt === loginInfo.retries) {
182
+ throw new Error(`Login failed after ${loginInfo.retries} attempts`);
183
+ }
184
+ }
185
+ }
186
+ }
187
+ async resolveShadowDom(page, shadowSelectors, targetSelector) {
188
+ let context;
189
+ if (!shadowSelectors || shadowSelectors.length === 0) {
190
+ // No Shadow DOM path; use document root as the context
191
+ context = page.mainFrame(); // Puppeteer uses frames to represent document
192
+ return context.$(targetSelector); // Search directly in the document root
193
+ }
194
+ context = page; // Start with the page as the context
195
+ for (const selector of shadowSelectors) {
196
+ const shadowHost = await context.$(selector);
197
+ if (!shadowHost) {
198
+ throw new Error(`Shadow host not found: ${selector}`);
199
+ }
200
+ context = await page.evaluateHandle(host => host.shadowRoot, shadowHost);
201
+ }
202
+ return context.evaluateHandle((shadowRoot, selector) => shadowRoot.querySelector(selector), targetSelector);
203
+ }
204
+ async disconnect(connection) {
205
+ const connectionInstance = connection_manager_1.ConnectionManager.getConnectionInstance(connection);
206
+ connection_manager_1.ConnectionManager.removeConnectionInstance(connection);
207
+ connection_manager_1.ConnectionManager.logger.info(`headless-connector connection(${connection.name}) is disconnected`);
208
+ }
209
+ get parameterSpec() {
210
+ return [
211
+ {
212
+ type: 'string',
213
+ name: 'username',
214
+ label: 'username'
215
+ },
216
+ {
217
+ type: 'password',
218
+ name: 'password',
219
+ label: 'password'
220
+ },
221
+ {
222
+ type: 'string',
223
+ name: 'loginPath',
224
+ label: 'login-path'
225
+ },
226
+ {
227
+ type: 'string',
228
+ name: 'usernameSelector',
229
+ label: 'username-selector'
230
+ },
231
+ {
232
+ type: 'string',
233
+ name: 'passwordSelector',
234
+ label: 'password-selector'
235
+ },
236
+ {
237
+ type: 'string',
238
+ name: 'submitSelector',
239
+ label: 'submit-selector'
240
+ },
241
+ {
242
+ type: 'string',
243
+ name: 'successSelector',
244
+ label: 'success-selector'
245
+ },
246
+ {
247
+ type: 'string',
248
+ name: 'shadowDomSelectors',
249
+ label: 'shadow-dom-selectors'
250
+ },
251
+ {
252
+ type: 'number',
253
+ name: 'timeout',
254
+ label: 'timeout',
255
+ value: 15000
256
+ },
257
+ {
258
+ type: 'number',
259
+ name: 'retries',
260
+ label: 'maximum-retries',
261
+ value: 3
262
+ }
263
+ ];
264
+ }
265
+ get taskPrefixes() {
266
+ return ['headless'];
267
+ }
268
+ get description() {
269
+ return 'Headless Pool Connector with login capabilities';
270
+ }
271
+ get help() {
272
+ return 'integration/connector/headless-connector';
273
+ }
274
+ }
275
+ exports.HeadlessConnector = HeadlessConnector;
276
+ connection_manager_1.ConnectionManager.registerConnector('headless-connector', new HeadlessConnector());
277
+ function isCookieValid(cookies) {
278
+ if (!cookies || cookies.length === 0)
279
+ return false;
280
+ const now = Date.now() / 1000; // Current time in seconds
281
+ return cookies.some(cookie => cookie.expires && cookie.expires > now);
282
+ }
283
+ //# sourceMappingURL=headless-connector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headless-connector.js","sourceRoot":"","sources":["../../../server/engine/connector/headless-connector.ts"],"names":[],"mappings":"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","sourcesContent":["import { ConnectionManager } from '../connection-manager'\nimport { Connector } from '../types'\nimport { getHeadlessPool } from '../resource-pool/headless-pool'\nimport { Browser, Page } from 'puppeteer'\n\n/*\n Functionality of the headless-connector:\n - Provides a mechanism to acquire an active session page.\n - Performs login when necessary to obtain valid cookies.\n - Applies these cookies to the page for session management.\n - Pages are acquired from the `headlessPool`, which manages browser instances.\n - During the login process, pages from the pool are used for actions like form filling and navigation.\n - Valid cookies are saved after login and reused for subsequent page acquisitions.\n - Users must explicitly release the page after use through the `releasePage` method.\n - Released pages are returned to the `headlessPool` for reuse.\n*/\n\nexport class HeadlessConnector implements Connector {\n async ready(connectionConfigs) {\n await Promise.all(connectionConfigs.map(this.connect.bind(this)))\n ConnectionManager.logger.info('headless-connector connections are ready')\n }\n\n async connect(connection) {\n const {\n endpoint: uri = '1',\n params: {\n username = '',\n password = '',\n loginPath = '/login',\n usernameSelector = '#username',\n passwordSelector = '#password',\n submitSelector = '#submit',\n successSelector = null,\n shadowDomSelectors = '', // Comma separated shadow DOM selectors\n timeout = 15000, // Default timeout for operations\n retries = 3 // Default number of retries for login or page actions\n } = {}\n } = connection\n\n const loginInfo = {\n loginRequired: Boolean(username), // Determine if login is required\n username,\n password,\n loginPath,\n timeout,\n retries,\n loginSelectors: {\n usernameSelector,\n passwordSelector,\n submitSelector,\n successSelector,\n shadowDomSelectors: shadowDomSelectors\n .split(',')\n .map(selector => selector.trim())\n .filter(Boolean)\n }\n }\n\n async function acquireBrowser() {\n try {\n const pool = getHeadlessPool()\n const browser = await pool.acquire()\n return browser\n } catch (error) {\n ConnectionManager.logger.error('Failed to acquire browser:', error)\n throw error\n }\n }\n\n async function releaseBrowser(browser: Browser) {\n try {\n const pool = getHeadlessPool()\n await pool.release(browser)\n } catch (error) {\n ConnectionManager.logger.error('Failed to release browser:', error)\n }\n }\n\n ConnectionManager.addConnectionInstance(connection, {\n endpoint: connection.endpoint,\n params: connection.params,\n acquireSessionPage: async () => {\n const browser = await acquireBrowser()\n let page\n let cookies = connection.cookies\n\n try {\n page = await browser.newPage()\n\n page.on('console', async msg => {\n console.log(`[browser ${msg.type()}] ${msg.text()}`)\n })\n\n await page.setRequestInterception(true)\n\n // page.on('request', request => {\n // const resourceType = request.resourceType()\n // if (resourceType === 'image' || resourceType === 'stylesheet' || resourceType === 'font') {\n // request.abort() // 이미지, 스타일시트, 폰트 요청 차단\n // } else {\n // request.continue() // 나머지 요청은 진행\n // }\n // })\n\n page.on('requestfailed', request => {\n console.log('Request failed:')\n console.log(`- URL: ${request.url()}`)\n console.log(`- Method: ${request.method()}`)\n console.log(`- Failure Text: ${request.failure()?.errorText}`)\n console.log(`- Headers:`, request.headers())\n\n // POST 데이터 (필요한 경우)\n if (request.postData()) {\n console.log(`- Post Data: ${request.postData()}`)\n }\n })\n\n if (cookies && isCookieValid(cookies)) {\n await this.applyCookiesAndVerifySession(page, cookies, loginInfo)\n return page\n }\n\n if (loginInfo.loginRequired) {\n await this.performLogin(page, uri, loginInfo)\n cookies = await page.cookies()\n connection.cookies = cookies\n }\n\n return page\n } catch (error) {\n ConnectionManager.logger.error('Failed to acquire session page:', error)\n throw error\n }\n },\n releasePage: async (page: Page) => {\n try {\n if (page) {\n const browser = page.browser()\n await page.close()\n await releaseBrowser(browser)\n }\n } catch (error) {\n ConnectionManager.logger.error('Failed to release page:', error)\n }\n },\n acquireBrowser,\n releaseBrowser\n })\n\n ConnectionManager.logger.info(\n `headless-connector connection(${connection.name}:${connection.endpoint}) is connected`\n )\n }\n\n async applyCookiesAndVerifySession(page, cookies, loginInfo) {\n await page.setCookie(...cookies)\n await page.reload({ waitUntil: 'networkidle2', timeout: loginInfo.timeout })\n\n if (loginInfo.loginRequired && loginInfo.loginSelectors.successSelector) {\n const success = await this.resolveShadowDom(\n page,\n loginInfo.loginSelectors.shadowDomSelectors,\n loginInfo.loginSelectors.successSelector\n )\n if (!success) {\n throw new Error('Session invalid, login required')\n }\n }\n }\n\n async performLogin(page, uri, loginInfo) {\n for (let attempt = 1; attempt <= loginInfo.retries; attempt++) {\n try {\n await page.goto(`${uri}${loginInfo.loginPath}`, { waitUntil: 'networkidle2', timeout: loginInfo.timeout })\n\n const usernameInput = await this.resolveShadowDom(\n page,\n loginInfo.loginSelectors.shadowDomSelectors,\n loginInfo.loginSelectors.usernameSelector\n )\n const passwordInput = await this.resolveShadowDom(\n page,\n loginInfo.loginSelectors.shadowDomSelectors,\n loginInfo.loginSelectors.passwordSelector\n )\n const submitButton = await this.resolveShadowDom(\n page,\n loginInfo.loginSelectors.shadowDomSelectors,\n loginInfo.loginSelectors.submitSelector\n )\n\n if (!usernameInput || !passwordInput || !submitButton) {\n throw new Error('Failed to locate input elements in shadow DOM')\n }\n\n await usernameInput.type(loginInfo.username)\n await passwordInput.type(loginInfo.password)\n\n // Capture the response of the form submission\n const [response] = await Promise.all([\n page.waitForNavigation({ waitUntil: 'networkidle2', timeout: loginInfo.timeout }),\n submitButton.click()\n ])\n\n // Check response status code\n if (response) {\n const status = response.status()\n if (status >= 200 && status < 300) {\n ConnectionManager.logger.info(`Login successful with status code: ${status}`)\n return // Login successful\n } else if (status >= 400) {\n throw new Error(`Login failed with status code: ${status}`)\n }\n } else {\n throw new Error('No response received during login')\n }\n\n if (loginInfo.loginSelectors.successSelector) {\n const success = await this.resolveShadowDom(\n page,\n loginInfo.loginSelectors.shadowDomSelectors,\n loginInfo.loginSelectors.successSelector\n )\n if (!success) {\n throw new Error('Login failed: Success selector not found')\n }\n }\n } catch (error) {\n ConnectionManager.logger.warn(`Login attempt ${attempt} failed:`, error)\n\n if (attempt === loginInfo.retries) {\n throw new Error(`Login failed after ${loginInfo.retries} attempts`)\n }\n }\n }\n }\n\n async resolveShadowDom(page, shadowSelectors, targetSelector) {\n let context\n\n if (!shadowSelectors || shadowSelectors.length === 0) {\n // No Shadow DOM path; use document root as the context\n context = page.mainFrame() // Puppeteer uses frames to represent document\n return context.$(targetSelector) // Search directly in the document root\n }\n\n context = page // Start with the page as the context\n for (const selector of shadowSelectors) {\n const shadowHost = await context.$(selector)\n if (!shadowHost) {\n throw new Error(`Shadow host not found: ${selector}`)\n }\n context = await page.evaluateHandle(host => host.shadowRoot, shadowHost)\n }\n return context.evaluateHandle((shadowRoot, selector) => shadowRoot.querySelector(selector), targetSelector)\n }\n\n async disconnect(connection) {\n const connectionInstance = ConnectionManager.getConnectionInstance(connection)\n ConnectionManager.removeConnectionInstance(connection)\n ConnectionManager.logger.info(`headless-connector connection(${connection.name}) is disconnected`)\n }\n\n get parameterSpec() {\n return [\n {\n type: 'string',\n name: 'username',\n label: 'username'\n },\n {\n type: 'password',\n name: 'password',\n label: 'password'\n },\n {\n type: 'string',\n name: 'loginPath',\n label: 'login-path'\n },\n {\n type: 'string',\n name: 'usernameSelector',\n label: 'username-selector'\n },\n {\n type: 'string',\n name: 'passwordSelector',\n label: 'password-selector'\n },\n {\n type: 'string',\n name: 'submitSelector',\n label: 'submit-selector'\n },\n {\n type: 'string',\n name: 'successSelector',\n label: 'success-selector'\n },\n {\n type: 'string',\n name: 'shadowDomSelectors',\n label: 'shadow-dom-selectors'\n },\n {\n type: 'number',\n name: 'timeout',\n label: 'timeout',\n value: 15000\n },\n {\n type: 'number',\n name: 'retries',\n label: 'maximum-retries',\n value: 3\n }\n ]\n }\n\n get taskPrefixes() {\n return ['headless']\n }\n\n get description() {\n return 'Headless Pool Connector with login capabilities'\n }\n\n get help() {\n return 'integration/connector/headless-connector'\n }\n}\n\nConnectionManager.registerConnector('headless-connector', new HeadlessConnector())\n\nfunction isCookieValid(cookies) {\n if (!cookies || cookies.length === 0) return false\n const now = Date.now() / 1000 // Current time in seconds\n return cookies.some(cookie => cookie.expires && cookie.expires > now)\n}\n"]}
@@ -44,7 +44,7 @@ class HttpConnector {
44
44
  ];
45
45
  }
46
46
  get taskPrefixes() {
47
- return ['http', 'headless-post', 'headless-scrap'];
47
+ return ['http'];
48
48
  }
49
49
  }
50
50
  exports.HttpConnector = HttpConnector;
@@ -1 +1 @@
1
- {"version":3,"file":"http-connector.js","sourceRoot":"","sources":["../../../server/engine/connector/http-connector.ts"],"names":[],"mappings":";;;AAAA,iEAA4D;AAC5D,iDAAqD;AAErD,8DAAyD;AAIzD,MAAa,aAAa;IACxB,KAAK,CAAC,KAAK,CAAC,iBAAoC;QAC9C,MAAM,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QAEjE,sCAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,sCAAsC,CAAC,CAAA;IACvE,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,UAA2B;QACvC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CAAA;QAC3B,MAAM,CAAC,kBAAkB,GAAG,CAAC,MAAM,CAAC,kBAAkB,IAAI,GAAG,CAAC,KAAK,GAAG,CAAA;QAEtE,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;YACtB,MAAM,YAAY,GAAiB,MAAM,IAAA,qBAAa,EAAC,4BAAY,CAAC,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC,CAAA;YACzG,IAAI,WAAW,GAAG,YAAY,CAAC,cAAc,EAAE,CAAA;QACjD,CAAC;QAED,sCAAiB,CAAC,qBAAqB,CAAC,UAAU,kCAC7C,UAAU,KACb,WAAW,EAAE,WAAW,IAAI,EAAE,EAC9B,MAAM,IACN,CAAA;QAEF,sCAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,6BAA6B,UAAU,CAAC,IAAI,IAAI,UAAU,CAAC,QAAQ,gBAAgB,CAAC,CAAA;IACpH,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,UAA2B;QAC1C,sCAAiB,CAAC,wBAAwB,CAAC,UAAU,CAAC,CAAA;QAEtD,sCAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,6BAA6B,UAAU,CAAC,IAAI,mBAAmB,CAAC,CAAA;IAChG,CAAC;IAED,IAAI,aAAa;QACf,OAAO;YACL;gBACE,IAAI,EAAE,iBAAiB;gBACvB,IAAI,EAAE,YAAY;gBAClB,KAAK,EAAE,aAAa;gBACpB,QAAQ,EAAE;oBACR,SAAS,EAAE,eAAe;iBAC3B;aACF;YACD;gBACE,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,oBAAoB;gBAC1B,KAAK,EAAE,qBAAqB;gBAC5B,QAAQ,EAAE;oBACR,OAAO,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC;iBACpB;aACF;SACF,CAAA;IACH,CAAC;IAED,IAAI,YAAY;QACd,OAAO,CAAC,MAAM,EAAE,eAAe,EAAE,gBAAgB,CAAC,CAAA;IACpD,CAAC;CACF;AAvDD,sCAuDC;AAED,sCAAiB,CAAC,iBAAiB,CAAC,gBAAgB,EAAE,IAAI,aAAa,EAAE,CAAC,CAAA","sourcesContent":["import { Oauth2Client } from '@things-factory/oauth2-client'\nimport { getRepository } from '@things-factory/shell'\n\nimport { ConnectionManager } from '../connection-manager'\nimport { Connector } from '../types'\nimport { InputConnection } from '../../service/connection/connection-type'\n\nexport class HttpConnector implements Connector {\n async ready(connectionConfigs: InputConnection[]) {\n await Promise.all(connectionConfigs.map(this.connect.bind(this)))\n\n ConnectionManager.logger.info('http-connector connections are ready')\n }\n\n async connect(connection: InputConnection) {\n var { params } = connection\n params.rejectUnauthorized = (params.rejectUnauthorized || 'Y') === 'Y'\n\n if (params.authClient) {\n const oauth2Client: Oauth2Client = await getRepository(Oauth2Client).findOneBy({ id: params.authClient })\n var authHeaders = oauth2Client.getAuthHeaders()\n }\n\n ConnectionManager.addConnectionInstance(connection, {\n ...connection,\n authHeaders: authHeaders || {},\n params\n })\n\n ConnectionManager.logger.info(`http-connector connection(${connection.name}:${connection.endpoint}) is connected`)\n }\n\n async disconnect(connection: InputConnection) {\n ConnectionManager.removeConnectionInstance(connection)\n\n ConnectionManager.logger.info(`http-connector connection(${connection.name}) is disconnected`)\n }\n\n get parameterSpec() {\n return [\n {\n type: 'entity-selector',\n name: 'authClient',\n label: 'auth-client',\n property: {\n queryName: 'oauth2Clients'\n }\n },\n {\n type: 'select',\n name: 'rejectUnauthorized',\n label: 'reject-unauthorized',\n property: {\n options: ['Y', 'N']\n }\n }\n ]\n }\n\n get taskPrefixes() {\n return ['http', 'headless-post', 'headless-scrap']\n }\n}\n\nConnectionManager.registerConnector('http-connector', new HttpConnector())\n"]}
1
+ {"version":3,"file":"http-connector.js","sourceRoot":"","sources":["../../../server/engine/connector/http-connector.ts"],"names":[],"mappings":";;;AAAA,iEAA4D;AAC5D,iDAAqD;AAErD,8DAAyD;AAIzD,MAAa,aAAa;IACxB,KAAK,CAAC,KAAK,CAAC,iBAAoC;QAC9C,MAAM,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QAEjE,sCAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,sCAAsC,CAAC,CAAA;IACvE,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,UAA2B;QACvC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CAAA;QAC3B,MAAM,CAAC,kBAAkB,GAAG,CAAC,MAAM,CAAC,kBAAkB,IAAI,GAAG,CAAC,KAAK,GAAG,CAAA;QAEtE,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;YACtB,MAAM,YAAY,GAAiB,MAAM,IAAA,qBAAa,EAAC,4BAAY,CAAC,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC,CAAA;YACzG,IAAI,WAAW,GAAG,YAAY,CAAC,cAAc,EAAE,CAAA;QACjD,CAAC;QAED,sCAAiB,CAAC,qBAAqB,CAAC,UAAU,kCAC7C,UAAU,KACb,WAAW,EAAE,WAAW,IAAI,EAAE,EAC9B,MAAM,IACN,CAAA;QAEF,sCAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,6BAA6B,UAAU,CAAC,IAAI,IAAI,UAAU,CAAC,QAAQ,gBAAgB,CAAC,CAAA;IACpH,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,UAA2B;QAC1C,sCAAiB,CAAC,wBAAwB,CAAC,UAAU,CAAC,CAAA;QAEtD,sCAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,6BAA6B,UAAU,CAAC,IAAI,mBAAmB,CAAC,CAAA;IAChG,CAAC;IAED,IAAI,aAAa;QACf,OAAO;YACL;gBACE,IAAI,EAAE,iBAAiB;gBACvB,IAAI,EAAE,YAAY;gBAClB,KAAK,EAAE,aAAa;gBACpB,QAAQ,EAAE;oBACR,SAAS,EAAE,eAAe;iBAC3B;aACF;YACD;gBACE,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,oBAAoB;gBAC1B,KAAK,EAAE,qBAAqB;gBAC5B,QAAQ,EAAE;oBACR,OAAO,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC;iBACpB;aACF;SACF,CAAA;IACH,CAAC;IAED,IAAI,YAAY;QACd,OAAO,CAAC,MAAM,CAAC,CAAA;IACjB,CAAC;CACF;AAvDD,sCAuDC;AAED,sCAAiB,CAAC,iBAAiB,CAAC,gBAAgB,EAAE,IAAI,aAAa,EAAE,CAAC,CAAA","sourcesContent":["import { Oauth2Client } from '@things-factory/oauth2-client'\nimport { getRepository } from '@things-factory/shell'\n\nimport { ConnectionManager } from '../connection-manager'\nimport { Connector } from '../types'\nimport { InputConnection } from '../../service/connection/connection-type'\n\nexport class HttpConnector implements Connector {\n async ready(connectionConfigs: InputConnection[]) {\n await Promise.all(connectionConfigs.map(this.connect.bind(this)))\n\n ConnectionManager.logger.info('http-connector connections are ready')\n }\n\n async connect(connection: InputConnection) {\n var { params } = connection\n params.rejectUnauthorized = (params.rejectUnauthorized || 'Y') === 'Y'\n\n if (params.authClient) {\n const oauth2Client: Oauth2Client = await getRepository(Oauth2Client).findOneBy({ id: params.authClient })\n var authHeaders = oauth2Client.getAuthHeaders()\n }\n\n ConnectionManager.addConnectionInstance(connection, {\n ...connection,\n authHeaders: authHeaders || {},\n params\n })\n\n ConnectionManager.logger.info(`http-connector connection(${connection.name}:${connection.endpoint}) is connected`)\n }\n\n async disconnect(connection: InputConnection) {\n ConnectionManager.removeConnectionInstance(connection)\n\n ConnectionManager.logger.info(`http-connector connection(${connection.name}) is disconnected`)\n }\n\n get parameterSpec() {\n return [\n {\n type: 'entity-selector',\n name: 'authClient',\n label: 'auth-client',\n property: {\n queryName: 'oauth2Clients'\n }\n },\n {\n type: 'select',\n name: 'rejectUnauthorized',\n label: 'reject-unauthorized',\n property: {\n options: ['Y', 'N']\n }\n }\n ]\n }\n\n get taskPrefixes() {\n return ['http']\n }\n}\n\nConnectionManager.registerConnector('http-connector', new HttpConnector())\n"]}
@@ -10,3 +10,4 @@ import './oracle-connector';
10
10
  import './mysql-connector';
11
11
  import './socket-server';
12
12
  import './operato-connector';
13
+ import './headless-connector';
@@ -12,4 +12,5 @@ require("./oracle-connector");
12
12
  require("./mysql-connector");
13
13
  require("./socket-server");
14
14
  require("./operato-connector");
15
+ require("./headless-connector");
15
16
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/engine/connector/index.ts"],"names":[],"mappings":";;AAAA,8BAA2B;AAC3B,iCAA8B;AAC9B,4BAAyB;AACzB,+BAA4B;AAC5B,8BAA2B;AAC3B,kCAA+B;AAC/B,4BAAyB;AACzB,6BAA0B;AAC1B,8BAA2B;AAC3B,6BAA0B;AAC1B,2BAAwB;AACxB,+BAA4B","sourcesContent":["import './echo-back-server'\nimport './echo-back-connector'\nimport './http-connector'\nimport './graphql-connector'\nimport './sqlite-connector'\nimport './postgresql-connector'\nimport './mqtt-connector'\nimport './mssql-connector'\nimport './oracle-connector'\nimport './mysql-connector'\nimport './socket-server'\nimport './operato-connector'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/engine/connector/index.ts"],"names":[],"mappings":";;AAAA,8BAA2B;AAC3B,iCAA8B;AAC9B,4BAAyB;AACzB,+BAA4B;AAC5B,8BAA2B;AAC3B,kCAA+B;AAC/B,4BAAyB;AACzB,6BAA0B;AAC1B,8BAA2B;AAC3B,6BAA0B;AAC1B,2BAAwB;AACxB,+BAA4B;AAC5B,gCAA6B","sourcesContent":["import './echo-back-server'\nimport './echo-back-connector'\nimport './http-connector'\nimport './graphql-connector'\nimport './sqlite-connector'\nimport './postgresql-connector'\nimport './mqtt-connector'\nimport './mssql-connector'\nimport './oracle-connector'\nimport './mysql-connector'\nimport './socket-server'\nimport './operato-connector'\nimport './headless-connector'\n"]}
@@ -5,4 +5,5 @@ export * from './scenario-engine';
5
5
  export * from './task-registry';
6
6
  export * from './analyzer/analyze-integration';
7
7
  export * from './edge-client';
8
+ export * from './resource-pool';
8
9
  export { Connector, Context, TaskHandler } from './types';
@@ -8,4 +8,5 @@ tslib_1.__exportStar(require("./scenario-engine"), exports);
8
8
  tslib_1.__exportStar(require("./task-registry"), exports);
9
9
  tslib_1.__exportStar(require("./analyzer/analyze-integration"), exports);
10
10
  tslib_1.__exportStar(require("./edge-client"), exports);
11
+ tslib_1.__exportStar(require("./resource-pool"), exports);
11
12
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/engine/index.ts"],"names":[],"mappings":";;;AAAA,uBAAoB;AACpB,kBAAe;AAEf,+DAAoC;AACpC,4DAAiC;AACjC,0DAA+B;AAC/B,yEAA8C;AAC9C,wDAA6B","sourcesContent":["import './connector'\nimport './task'\n\nexport * from './connection-manager'\nexport * from './scenario-engine'\nexport * from './task-registry'\nexport * from './analyzer/analyze-integration'\nexport * from './edge-client'\n\nexport { Connector, Context, TaskHandler } from './types'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/engine/index.ts"],"names":[],"mappings":";;;AAAA,uBAAoB;AACpB,kBAAe;AAEf,+DAAoC;AACpC,4DAAiC;AACjC,0DAA+B;AAC/B,yEAA8C;AAC9C,wDAA6B;AAC7B,0DAA+B","sourcesContent":["import './connector'\nimport './task'\n\nexport * from './connection-manager'\nexport * from './scenario-engine'\nexport * from './task-registry'\nexport * from './analyzer/analyze-integration'\nexport * from './edge-client'\nexport * from './resource-pool'\n\nexport { Connector, Context, TaskHandler } from './types'\n"]}
@@ -0,0 +1 @@
1
+ export declare function getHeadlessPool(): any;
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getHeadlessPool = getHeadlessPool;
4
+ const tslib_1 = require("tslib");
5
+ const genericPool = tslib_1.__importStar(require("generic-pool"));
6
+ const env_1 = require("@things-factory/env");
7
+ try {
8
+ var puppeteer = require('puppeteer');
9
+ }
10
+ catch (err) {
11
+ env_1.logger.error(err);
12
+ }
13
+ let headlessPool;
14
+ function getHeadlessPool() {
15
+ if (!headlessPool) {
16
+ headlessPool = createHeadlessPool({ min: 2, max: 20, acquireTimeoutMillis: 15000, testOnBorrow: true });
17
+ }
18
+ return headlessPool;
19
+ }
20
+ function createHeadlessPool(options) {
21
+ return genericPool.createPool({
22
+ create() {
23
+ console.log('headless instance in headless-pool-integration about to create');
24
+ return initializeChromium();
25
+ },
26
+ validate(browser) {
27
+ return Promise.race([
28
+ new Promise(res => setTimeout(() => res(false), 1500)),
29
+ browser
30
+ //@ts-ignore
31
+ .version()
32
+ .then(() => true)
33
+ .catch(() => false)
34
+ ]);
35
+ },
36
+ destroy(browser) {
37
+ //@ts-ignore
38
+ return browser.close();
39
+ }
40
+ }, options);
41
+ }
42
+ async function destroyHeadlessPool() {
43
+ if (headlessPool) {
44
+ console.log('headless-pool-integration about to destroy');
45
+ try {
46
+ await Promise.race([
47
+ headlessPool.drain().then(() => headlessPool.clear()), // 정리 작업
48
+ new Promise((_, reject) => setTimeout(() => reject(new Error('Destroy timeout')), 5000) // 5초 타임아웃 설정
49
+ )
50
+ ]);
51
+ console.log('Headless pool destroyed');
52
+ }
53
+ catch (err) {
54
+ env_1.logger.error('Failed to destroy headless pool:', err);
55
+ }
56
+ }
57
+ }
58
+ const CHROMIUM_PATH = env_1.config.get('CHROMIUM_PATH');
59
+ async function initializeChromium() {
60
+ try {
61
+ if (!puppeteer) {
62
+ return;
63
+ }
64
+ const launchSetting = {
65
+ args: ['--hide-scrollbars', '--mute-audio', '--no-sandbox', '--use-gl=egl'],
66
+ headless: 'shell'
67
+ };
68
+ if (CHROMIUM_PATH) {
69
+ launchSetting['executablePath'] = CHROMIUM_PATH;
70
+ }
71
+ const browser = await puppeteer.launch(launchSetting);
72
+ return browser;
73
+ }
74
+ catch (err) {
75
+ env_1.logger.error(err);
76
+ }
77
+ }
78
+ // Graceful shutdown logic
79
+ function setupProcessExitHandlers() {
80
+ let isCleaningUp = false; // 중복 정리를 방지
81
+ const cleanup = async () => {
82
+ if (isCleaningUp)
83
+ return; // 이미 정리 중이면 무시
84
+ isCleaningUp = true;
85
+ console.log('Application is shutting down. Cleaning up resources...');
86
+ try {
87
+ // Pool 정리 작업 실행
88
+ await destroyHeadlessPool();
89
+ }
90
+ catch (err) {
91
+ env_1.logger.error('Error during cleanup:', err);
92
+ }
93
+ finally {
94
+ console.log('Cleanup completed.');
95
+ }
96
+ };
97
+ const onExit = async (signal) => {
98
+ console.log(`Received signal: ${signal || 'unknown'}`);
99
+ await cleanup();
100
+ // 다른 핸들러가 실행될 수 있도록 exit 호출을 지연
101
+ process.nextTick(() => process.exit(0)); // Tick 뒤에 종료
102
+ };
103
+ // Handle termination signals
104
+ process.once('SIGINT', () => onExit('SIGINT')); // Ctrl+C
105
+ process.once('SIGTERM', () => onExit('SIGTERM')); // Termination signal
106
+ // Handle uncaught exceptions
107
+ process.once('uncaughtException', async (err) => {
108
+ env_1.logger.error('Uncaught Exception:', err);
109
+ await cleanup();
110
+ process.nextTick(() => process.exit(1)); // Tick 뒤에 종료
111
+ });
112
+ // Handle unhandled promise rejections
113
+ process.once('unhandledRejection', async (reason) => {
114
+ env_1.logger.error('Unhandled Rejection:', reason);
115
+ await cleanup();
116
+ process.nextTick(() => process.exit(1)); // Tick 뒤에 종료
117
+ });
118
+ }
119
+ // Initialize process exit handlers
120
+ setupProcessExitHandlers();
121
+ //# sourceMappingURL=headless-pool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headless-pool.js","sourceRoot":"","sources":["../../../server/engine/resource-pool/headless-pool.ts"],"names":[],"mappings":"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","sourcesContent":["import * as genericPool from 'generic-pool'\nimport { config, logger } from '@things-factory/env'\n\ntry {\n var puppeteer = require('puppeteer')\n} catch (err) {\n logger.error(err)\n}\n\nlet headlessPool\n\nexport function getHeadlessPool() {\n if (!headlessPool) {\n headlessPool = createHeadlessPool({ min: 2, max: 20, acquireTimeoutMillis: 15000, testOnBorrow: true })\n }\n\n return headlessPool\n}\n\nfunction createHeadlessPool(options: genericPool.Options) {\n return genericPool.createPool(\n {\n create() {\n console.log('headless instance in headless-pool-integration about to create')\n return initializeChromium()\n },\n validate(browser) {\n return Promise.race([\n new Promise(res => setTimeout(() => res(false), 1500)),\n browser\n //@ts-ignore\n .version()\n .then(() => true)\n .catch(() => false)\n ])\n },\n destroy(browser) {\n //@ts-ignore\n return browser.close()\n }\n } as genericPool.Factory<any>,\n options\n )\n}\n\nasync function destroyHeadlessPool() {\n if (headlessPool) {\n console.log('headless-pool-integration about to destroy')\n\n try {\n await Promise.race([\n headlessPool.drain().then(() => headlessPool.clear()), // 정리 작업\n new Promise(\n (_, reject) => setTimeout(() => reject(new Error('Destroy timeout')), 5000) // 5초 타임아웃 설정\n )\n ])\n console.log('Headless pool destroyed')\n } catch (err) {\n logger.error('Failed to destroy headless pool:', err)\n }\n }\n}\n\nconst CHROMIUM_PATH = config.get('CHROMIUM_PATH')\n\nasync function initializeChromium() {\n try {\n if (!puppeteer) {\n return\n }\n\n const launchSetting = {\n args: ['--hide-scrollbars', '--mute-audio', '--no-sandbox', '--use-gl=egl'],\n headless: 'shell'\n }\n\n if (CHROMIUM_PATH) {\n launchSetting['executablePath'] = CHROMIUM_PATH\n }\n\n const browser = await puppeteer.launch(launchSetting)\n\n return browser\n } catch (err) {\n logger.error(err)\n }\n}\n\n// Graceful shutdown logic\nfunction setupProcessExitHandlers() {\n let isCleaningUp = false // 중복 정리를 방지\n\n const cleanup = async () => {\n if (isCleaningUp) return // 이미 정리 중이면 무시\n isCleaningUp = true\n\n console.log('Application is shutting down. Cleaning up resources...')\n try {\n // Pool 정리 작업 실행\n await destroyHeadlessPool()\n } catch (err) {\n logger.error('Error during cleanup:', err)\n } finally {\n console.log('Cleanup completed.')\n }\n }\n\n const onExit = async (signal?: string) => {\n console.log(`Received signal: ${signal || 'unknown'}`)\n await cleanup()\n\n // 다른 핸들러가 실행될 수 있도록 exit 호출을 지연\n process.nextTick(() => process.exit(0)) // Tick 뒤에 종료\n }\n\n // Handle termination signals\n process.once('SIGINT', () => onExit('SIGINT')) // Ctrl+C\n process.once('SIGTERM', () => onExit('SIGTERM')) // Termination signal\n\n // Handle uncaught exceptions\n process.once('uncaughtException', async err => {\n logger.error('Uncaught Exception:', err)\n await cleanup()\n process.nextTick(() => process.exit(1)) // Tick 뒤에 종료\n })\n\n // Handle unhandled promise rejections\n process.once('unhandledRejection', async reason => {\n logger.error('Unhandled Rejection:', reason)\n await cleanup()\n process.nextTick(() => process.exit(1)) // Tick 뒤에 종료\n })\n}\n\n// Initialize process exit handlers\nsetupProcessExitHandlers()\n"]}
@@ -0,0 +1 @@
1
+ export * from './headless-pool';
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ tslib_1.__exportStar(require("./headless-pool"), exports);
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/engine/resource-pool/index.ts"],"names":[],"mappings":";;;AAAA,0DAA+B","sourcesContent":["export * from './headless-pool'\n"]}