@things-factory/integration-base 10.0.0-beta.10 → 10.0.0-beta.108

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 (70) hide show
  1. package/dist-server/controllers/scenario-controller.js +8 -6
  2. package/dist-server/controllers/scenario-controller.js.map +1 -1
  3. package/dist-server/engine/connection-manager.d.ts +19 -0
  4. package/dist-server/engine/connection-manager.js +148 -16
  5. package/dist-server/engine/connection-manager.js.map +1 -1
  6. package/dist-server/engine/connector/headless-connector.d.ts +12 -0
  7. package/dist-server/engine/connector/headless-connector.js +74 -31
  8. package/dist-server/engine/connector/headless-connector.js.map +1 -1
  9. package/dist-server/engine/connector/operato-connector.js +17 -6
  10. package/dist-server/engine/connector/operato-connector.js.map +1 -1
  11. package/dist-server/engine/connector/socket-server.js +12 -2
  12. package/dist-server/engine/connector/socket-server.js.map +1 -1
  13. package/dist-server/engine/evaluate-template.d.ts +22 -3
  14. package/dist-server/engine/evaluate-template.js +43 -4
  15. package/dist-server/engine/evaluate-template.js.map +1 -1
  16. package/dist-server/engine/task/script.js +25 -17
  17. package/dist-server/engine/task/script.js.map +1 -1
  18. package/dist-server/engine/task/socket-listener.js +29 -9
  19. package/dist-server/engine/task/socket-listener.js.map +1 -1
  20. package/dist-server/engine/task/utils/headless-request-with-recovery.js +29 -26
  21. package/dist-server/engine/task/utils/headless-request-with-recovery.js.map +1 -1
  22. package/dist-server/engine/types.d.ts +32 -0
  23. package/dist-server/engine/types.js.map +1 -1
  24. package/dist-server/service/connection/connection-mutation.d.ts +3 -0
  25. package/dist-server/service/connection/connection-mutation.js +25 -26
  26. package/dist-server/service/connection/connection-mutation.js.map +1 -1
  27. package/dist-server/service/connection/connection-query.js +11 -3
  28. package/dist-server/service/connection/connection-query.js.map +1 -1
  29. package/dist-server/service/connection/connection-subscription.js +13 -6
  30. package/dist-server/service/connection/connection-subscription.js.map +1 -1
  31. package/dist-server/service/connection/connection-type.d.ts +4 -1
  32. package/dist-server/service/connection/connection-type.js +21 -0
  33. package/dist-server/service/connection/connection-type.js.map +1 -1
  34. package/dist-server/service/connection/connection.d.ts +80 -2
  35. package/dist-server/service/connection/connection.js +59 -15
  36. package/dist-server/service/connection/connection.js.map +1 -1
  37. package/dist-server/service/domain-attribute/domain-attribute-query.d.ts +17 -0
  38. package/dist-server/service/domain-attribute/domain-attribute-query.js +76 -0
  39. package/dist-server/service/domain-attribute/domain-attribute-query.js.map +1 -0
  40. package/dist-server/service/domain-attribute/domain-attribute-type.d.ts +15 -0
  41. package/dist-server/service/domain-attribute/domain-attribute-type.js +46 -0
  42. package/dist-server/service/domain-attribute/domain-attribute-type.js.map +1 -0
  43. package/dist-server/service/domain-attribute/index.d.ts +3 -0
  44. package/dist-server/service/domain-attribute/index.js +8 -0
  45. package/dist-server/service/domain-attribute/index.js.map +1 -0
  46. package/dist-server/service/index.d.ts +1 -1
  47. package/dist-server/service/index.js +3 -1
  48. package/dist-server/service/index.js.map +1 -1
  49. package/dist-server/service/scenario/scenario-mutation.js +10 -0
  50. package/dist-server/service/scenario/scenario-mutation.js.map +1 -1
  51. package/dist-server/service/scenario/scenario-query.d.ts +3 -3
  52. package/dist-server/service/scenario/scenario-query.js +18 -11
  53. package/dist-server/service/scenario/scenario-query.js.map +1 -1
  54. package/dist-server/service/scenario-instance/scenario-instance-subscription.js +4 -4
  55. package/dist-server/service/scenario-instance/scenario-instance-subscription.js.map +1 -1
  56. package/dist-server/service/scenario-instance/scenario-instance-type.js +79 -0
  57. package/dist-server/service/scenario-instance/scenario-instance-type.js.map +1 -1
  58. package/dist-server/service/scenario-queue/scenario-queue-subscription.js +2 -2
  59. package/dist-server/service/scenario-queue/scenario-queue-subscription.js.map +1 -1
  60. package/dist-server/service/state-register/data-resolver.js +2 -2
  61. package/dist-server/service/state-register/data-resolver.js.map +1 -1
  62. package/dist-server/service/step/step-mutation.js +15 -0
  63. package/dist-server/service/step/step-mutation.js.map +1 -1
  64. package/dist-server/service/step/step-query.js +11 -2
  65. package/dist-server/service/step/step-query.js.map +1 -1
  66. package/dist-server/tsconfig.tsbuildinfo +1 -1
  67. package/dist-server/utils/domain-inheritance.d.ts +29 -0
  68. package/dist-server/utils/domain-inheritance.js +98 -0
  69. package/dist-server/utils/domain-inheritance.js.map +1 -0
  70. package/package.json +10 -10
@@ -15,6 +15,20 @@ const headless_pool_1 = require("../resource-pool/headless-pool");
15
15
  - Released pages are returned to the `headlessPool` for reuse.
16
16
  */
17
17
  class HeadlessConnector {
18
+ constructor() {
19
+ /**
20
+ * 외부 사이트 로그인 세션 보유 — 같은 connection name 의 동시 acquireSessionPage
21
+ * 호출이 같은 외부 계정 세션을 서로 invalidate 하지 않도록 ConnectionManager 가
22
+ * mutex 직렬화. 상속 connector (allbaro, kiscon 등) 자동 적용.
23
+ */
24
+ this.sessionExclusive = true;
25
+ /**
26
+ * 자식 도메인이 부모의 Connection 정의를 inherit 할 때 자식별 새 인스턴스 생성.
27
+ * cookies/세션·자격증명 격리. 헤드리스 사이트 connector 는 외부 자격증명이
28
+ * 자식 (테넌트) 마다 다를 수 있으므로 ISOLATE 가 자연 default.
29
+ */
30
+ this.inheritanceMode = 'isolate';
31
+ }
18
32
  async ready(connectionConfigs) {
19
33
  await Promise.all(connectionConfigs.map(this.connect.bind(this)));
20
34
  connection_manager_1.ConnectionManager.logger.info('headless-connector connections are ready');
@@ -22,10 +36,13 @@ class HeadlessConnector {
22
36
  async connect(connection) {
23
37
  const { endpoint: uri = '1', params: { username = '', password = '', loginPagePath = '/login', loginApiUrl = null, usernameSelector = '#username', passwordSelector = '#password', submitSelector = '#submit', successSelector = null, shadowDomSelectors = '', // Comma separated shadow DOM selectors
24
38
  timeout = 15000, // Default timeout for operations
25
- retries = 3 // Default number of retries for login or page actions
26
- } = {} } = connection;
39
+ retries = 3, // Default number of retries for login or page actions
40
+ // 쿠키 캐시 재사용 건너뛰고 매 acquireSessionPage 마다 로그인하도록 강제.
41
+ // 서버 세션 만료가 잦거나 검증이 비싼 사이트(예: 올바로) 에서 사용.
42
+ forceLogin = false } = {} } = connection;
27
43
  const loginInfo = {
28
44
  loginRequired: Boolean(username), // Determine if login is required
45
+ forceLogin: Boolean(forceLogin),
29
46
  username,
30
47
  password,
31
48
  loginPagePath,
@@ -135,25 +152,36 @@ class HeadlessConnector {
135
152
  page = await browser.newPage();
136
153
  await this.setupPage(page, uri, timeout);
137
154
  if (loginInfo.loginRequired) {
138
- // 먼저 기본 페이지로 이동
139
- await page.goto(uri, { waitUntil: 'networkidle2', timeout });
140
- // 현재 세션의 Authorization 헤더 확인
141
- const headers = await page.evaluate(() => {
142
- return fetch(window.location.href, { method: 'GET' })
143
- .then(response => response.headers.get('Authorization'))
144
- .catch(() => null);
145
- });
146
- if (headers) {
147
- connection_manager_1.ConnectionManager.logger.info('User is already logged in, skipping login process.');
148
- return page;
155
+ if (loginInfo.forceLogin) {
156
+ // 매번 새로 로그인 쿠키·세션 재사용 검증 모두 건너뜀.
157
+ // 세션이 서버측에서 자주 만료되는 사이트 용도.
158
+ connection_manager_1.ConnectionManager.logger.info('forceLogin=true skipping cookie reuse, performing fresh login');
159
+ connection.cookies = undefined;
160
+ await this.performLogin(page, uri, loginInfo);
161
+ cookies = await page.cookies();
162
+ connection.cookies = cookies;
149
163
  }
150
- if (cookies && isCookieValid(cookies)) {
151
- await this.applyCookiesAndVerifySession(page, cookies, loginInfo);
152
- return page;
164
+ else {
165
+ // 먼저 기본 페이지로 이동
166
+ await page.goto(uri, { waitUntil: 'networkidle2', timeout });
167
+ // 현재 세션의 Authorization 헤더 확인
168
+ const headers = await page.evaluate(() => {
169
+ return fetch(window.location.href, { method: 'GET' })
170
+ .then(response => response.headers.get('Authorization'))
171
+ .catch(() => null);
172
+ });
173
+ if (headers) {
174
+ connection_manager_1.ConnectionManager.logger.info('User is already logged in, skipping login process.');
175
+ return page;
176
+ }
177
+ if (cookies && isCookieValid(cookies)) {
178
+ await this.applyCookiesAndVerifySession(page, cookies, loginInfo);
179
+ return page;
180
+ }
181
+ await this.performLogin(page, uri, loginInfo);
182
+ cookies = await page.cookies();
183
+ connection.cookies = cookies;
153
184
  }
154
- await this.performLogin(page, uri, loginInfo);
155
- cookies = await page.cookies();
156
- connection.cookies = cookies;
157
185
  }
158
186
  else {
159
187
  // 로그인이 필요하지 않은 경우에도 기본 페이지로 이동
@@ -417,14 +445,17 @@ class HeadlessConnector {
417
445
  });
418
446
  page.on('requestfailed', request => {
419
447
  try {
420
- console.log('Request failed:');
421
- console.log(`- URL: ${request.url()}`);
422
- console.log(`- Method: ${request.method()}`);
423
- console.log(`- Failure Text: ${request.failure()?.errorText}`);
424
- console.log(`- Headers:`, request.headers());
425
- if (request.postData()) {
426
- console.log(`- Post Data: ${request.postData()}`);
427
- }
448
+ // 정적 리소스(이미지·폰트·CSS ) 의 실패는 로그인 흐름과 무관한 소음이므로 무시.
449
+ // document / xhr / fetch 같은 의미있는 요청 실패만 출력.
450
+ const type = request.resourceType();
451
+ const NOISY_TYPES = new Set(['image', 'font', 'stylesheet', 'media', 'manifest', 'other']);
452
+ if (NOISY_TYPES.has(type))
453
+ return;
454
+ // 일부 사이트의 정적 자원이 다른 type 으로 잡힐 수 있어 확장자 기반 보강 필터.
455
+ const url = request.url();
456
+ if (/\.(png|jpe?g|gif|webp|svg|ico|woff2?|ttf|otf|eot|css|map)(\?|$)/i.test(url))
457
+ return;
458
+ console.warn(`[headless] request failed (type=${type}): ${request.method()} ${url} — ${request.failure()?.errorText}`);
428
459
  }
429
460
  catch (error) {
430
461
  console.error('Error in requestfailed handler:', error);
@@ -442,6 +473,7 @@ class HeadlessConnector {
442
473
  }
443
474
  }
444
475
  async performLogin(page, uri, loginInfo) {
476
+ let lastError = null;
445
477
  for (let attempt = 1; attempt <= loginInfo.retries; attempt++) {
446
478
  try {
447
479
  await page.goto(`${uri}${loginInfo.loginPagePath}`, { waitUntil: 'networkidle2', timeout: loginInfo.timeout });
@@ -449,7 +481,12 @@ class HeadlessConnector {
449
481
  const passwordInput = await this.resolveShadowDom(page, loginInfo.loginSelectors.shadowDomSelectors, loginInfo.loginSelectors.passwordSelector);
450
482
  const submitButton = await this.resolveShadowDom(page, loginInfo.loginSelectors.shadowDomSelectors, loginInfo.loginSelectors.submitSelector);
451
483
  if (!usernameInput || !passwordInput || !submitButton) {
452
- throw new Error('Failed to locate input elements in shadow DOM');
484
+ throw new Error(`Failed to locate login form elements ` +
485
+ `(username=${!!usernameInput}, password=${!!passwordInput}, submit=${!!submitButton}). ` +
486
+ `Selectors: username='${loginInfo.loginSelectors.usernameSelector}', ` +
487
+ `password='${loginInfo.loginSelectors.passwordSelector}', ` +
488
+ `submit='${loginInfo.loginSelectors.submitSelector}'. ` +
489
+ `사이트 폼 변경 가능성 — Connection params 의 *Selector 값을 실제 폼에 맞춰 덮어쓰세요.`);
453
490
  }
454
491
  await usernameInput.type(loginInfo.username);
455
492
  await passwordInput.type(loginInfo.password);
@@ -495,6 +532,7 @@ class HeadlessConnector {
495
532
  }
496
533
  }
497
534
  catch (error) {
535
+ lastError = error;
498
536
  connection_manager_1.ConnectionManager.logger.warn(`Login attempt ${attempt} failed:`, error);
499
537
  try {
500
538
  await page.screenshot({ path: `logs/login-failure-attempt-${attempt}.png` });
@@ -503,7 +541,7 @@ class HeadlessConnector {
503
541
  connection_manager_1.ConnectionManager.logger.error('Failed to capture screenshot:', error);
504
542
  }
505
543
  if (attempt === loginInfo.retries) {
506
- throw new Error(`Login failed after ${loginInfo.retries} attempts`);
544
+ throw new Error(`Login failed after ${loginInfo.retries} attempts — last cause: ${lastError?.message || lastError}`);
507
545
  }
508
546
  }
509
547
  }
@@ -511,9 +549,14 @@ class HeadlessConnector {
511
549
  async resolveShadowDom(page, shadowSelectors, targetSelector) {
512
550
  let context;
513
551
  if (!shadowSelectors || shadowSelectors.length === 0) {
514
- // No Shadow DOM path; use document root as the context
552
+ // No Shadow DOM path; use document root as the context.
553
+ // XPath 지원 — puppeteer 22+ 의 통합 선택자: `xpath/<expression>` prefix.
554
+ // 셀렉터가 `//`, `(//`, `.//` 로 시작하면 xpath/ 로 감싸 page.$ 에 전달.
555
+ if (typeof targetSelector === 'string' && /^\s*(\(|\/\/|\.\/\/)/.test(targetSelector)) {
556
+ return await page.$('xpath/' + targetSelector.trim());
557
+ }
515
558
  context = page.mainFrame(); // Puppeteer uses frames to represent document
516
- return context.$(targetSelector); // Search directly in the document root
559
+ return context.$(targetSelector); // CSS selector — Search directly in the document root
517
560
  }
518
561
  context = page; // Start with the page as the context
519
562
  for (const selector of shadowSelectors) {
@@ -1 +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, getPoolStats } 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 loginPagePath = '/login',\n loginApiUrl = null,\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 loginPagePath,\n loginApiUrl,\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 \n // Pool 상태 모니터링 (디버깅용)\n try {\n const stats = getPoolStats()\n ConnectionManager.logger.debug(`Pool stats before acquire - borrowed: ${stats.borrowed}, available: ${stats.available}, pending: ${stats.pending}`)\n } catch (statsError) {\n ConnectionManager.logger.warn('Failed to get pool stats:', statsError)\n }\n \n const browser = await pool.acquire()\n ConnectionManager.logger.info('Browser acquired successfully from pool')\n return browser\n } catch (error) {\n ConnectionManager.logger.error('Failed to acquire browser from pool:', error)\n \n // 풀 상태 로깅으로 디버깅 도움\n try {\n const stats = getPoolStats()\n ConnectionManager.logger.error(`Pool stats during acquire error - borrowed: ${stats.borrowed}, available: ${stats.available}, pending: ${stats.pending}, max: ${stats.max}`)\n } catch (statsError) {\n ConnectionManager.logger.error('Failed to get pool stats during error:', statsError)\n }\n \n throw error\n }\n }\n\n async function releaseBrowser(browser: Browser) {\n if (!browser) {\n ConnectionManager.logger.warn('Attempted to release null/undefined browser')\n return\n }\n\n try {\n // 브라우저가 아직 연결되어 있는지 확인\n if (!browser.isConnected()) {\n ConnectionManager.logger.warn('Attempted to release disconnected browser')\n return\n }\n\n // Pool 상태 모니터링 (릴리즈 전)\n try {\n const statsBefore = getPoolStats()\n ConnectionManager.logger.debug(`Pool stats before release - borrowed: ${statsBefore.borrowed}, available: ${statsBefore.available}`)\n } catch (statsError) {\n ConnectionManager.logger.warn('Failed to get pool stats before release:', statsError)\n }\n\n const pool = getHeadlessPool()\n await pool.release(browser)\n ConnectionManager.logger.info('Browser successfully released to pool')\n \n // Pool 상태 모니터링 (릴리즈 후)\n try {\n const statsAfter = getPoolStats()\n ConnectionManager.logger.debug(`Pool stats after release - borrowed: ${statsAfter.borrowed}, available: ${statsAfter.available}`)\n } catch (statsError) {\n ConnectionManager.logger.warn('Failed to get pool stats after release:', statsError)\n }\n } catch (error) {\n ConnectionManager.logger.error('Failed to release browser to pool:', error)\n \n // 에러 발생시 풀 상태 로깅\n try {\n const stats = getPoolStats()\n ConnectionManager.logger.error(`Pool stats during release error - borrowed: ${stats.borrowed}, available: ${stats.available}, pending: ${stats.pending}`)\n } catch (statsError) {\n ConnectionManager.logger.error('Failed to get pool stats during release error:', statsError)\n }\n \n // 풀 릴리즈가 실패한 경우 브라우저 강제 종료 시도\n try {\n if (browser && browser.isConnected()) {\n await browser.close()\n ConnectionManager.logger.warn('Forcibly closed browser due to pool release failure')\n }\n } catch (forceCloseError) {\n ConnectionManager.logger.error('Failed to force close browser after pool release failure:', forceCloseError)\n }\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 await this.setupPage(page, uri, timeout)\n\n if (loginInfo.loginRequired) {\n // 먼저 기본 페이지로 이동\n await page.goto(uri, { waitUntil: 'networkidle2', timeout })\n\n // 현재 세션의 Authorization 헤더 확인\n const headers = await page.evaluate(() => {\n return fetch(window.location.href, { method: 'GET' })\n .then(response => response.headers.get('Authorization'))\n .catch(() => null)\n })\n\n if (headers) {\n ConnectionManager.logger.info('User is already logged in, skipping login process.')\n return page\n }\n\n if (cookies && isCookieValid(cookies)) {\n await this.applyCookiesAndVerifySession(page, cookies, loginInfo)\n return page\n }\n\n await this.performLogin(page, uri, loginInfo)\n cookies = await page.cookies()\n connection.cookies = cookies\n } else {\n // 로그인이 필요하지 않은 경우에도 기본 페이지로 이동\n await page.goto(uri, { waitUntil: 'networkidle2', timeout })\n }\n\n return page\n } catch (error) {\n ConnectionManager.logger.error('Failed to acquire session page:', error)\n \n // CRITICAL: 에러 발생 시 확실한 리소스 정리\n if (page) {\n try {\n await page.close()\n ConnectionManager.logger.info('Page closed during acquireSessionPage error handling')\n } catch (closeError) {\n ConnectionManager.logger.error('Failed to close page during acquireSessionPage error:', closeError)\n }\n }\n \n try {\n await releaseBrowser(browser)\n ConnectionManager.logger.info('Browser released during acquireSessionPage error handling')\n } catch (releaseError) {\n ConnectionManager.logger.error('Failed to release browser during acquireSessionPage error:', releaseError)\n }\n \n throw error\n }\n },\n releasePage: async (page: Page) => {\n try {\n if (page && !page.isClosed()) {\n const browser = page.browser()\n \n // 페이지를 먼저 닫기\n try {\n await page.close()\n ConnectionManager.logger.info('Page closed successfully')\n } catch (closeError) {\n ConnectionManager.logger.error('Failed to close page:', closeError)\n }\n \n // 브라우저를 풀에 반환\n try {\n await releaseBrowser(browser)\n ConnectionManager.logger.info('Browser released successfully')\n } catch (releaseError) {\n ConnectionManager.logger.error('Failed to release browser:', releaseError)\n // 풀 릴리즈가 실패한 경우 브라우저 강제 종료 시도\n try {\n if (browser && browser.isConnected()) {\n await browser.close()\n ConnectionManager.logger.warn('Forcibly closed browser due to pool release failure')\n }\n } catch (forceCloseError) {\n ConnectionManager.logger.error('Failed to force close browser:', forceCloseError)\n }\n }\n }\n } catch (error) {\n ConnectionManager.logger.error('Critical error in releasePage:', error)\n }\n },\n // 세션 회복을 위한 재로그인 메서드\n reAuthenticateSession: async () => {\n ConnectionManager.logger.info(`Re-authenticating session for connection: ${connection.name}`)\n \n let browser = null\n let page = null\n\n try {\n browser = await acquireBrowser()\n page = await browser.newPage()\n await this.setupPage(page, uri, loginInfo.timeout)\n \n if (loginInfo.loginRequired) {\n await this.performLogin(page, uri, loginInfo)\n const newCookies = await page.cookies()\n connection.cookies = newCookies\n ConnectionManager.logger.info(`Session re-authenticated successfully for connection: ${connection.name}`)\n }\n \n // CRITICAL: 페이지와 함께 브라우저 정보도 반환하여 정확한 릴리즈 추적\n return {\n page,\n browser,\n requiresManualRelease: true // 수동 릴리즈 필요 표시\n }\n } catch (error) {\n ConnectionManager.logger.error(`Failed to re-authenticate session for connection: ${connection.name}`, error)\n \n // 에러 시 확실한 리소스 정리\n if (page) {\n try {\n if (!page.isClosed()) {\n await page.close()\n ConnectionManager.logger.info('Page closed during reauth error handling')\n }\n } catch (closeError) {\n ConnectionManager.logger.error('Failed to close page during reauth error:', closeError)\n }\n }\n \n if (browser) {\n try {\n await releaseBrowser(browser)\n ConnectionManager.logger.info('Browser released during reauth error handling')\n } catch (releaseError) {\n ConnectionManager.logger.error('Failed to release browser during reauth error:', releaseError)\n // 풀 릴리즈가 실패한 경우 브라우저 강제 종료 시도\n try {\n if (browser.isConnected()) {\n await browser.close()\n ConnectionManager.logger.warn('Forcibly closed browser during reauth error due to pool release failure')\n }\n } catch (forceCloseError) {\n ConnectionManager.logger.error('Failed to force close browser during reauth error:', forceCloseError)\n }\n }\n }\n \n throw error\n }\n },\n // 세션 유효성 검사\n validateSession: async (page: Page) => {\n try {\n if (!loginInfo.loginRequired) {\n return true\n }\n\n // 페이지가 닫혔거나 연결이 끊어진 경우\n if (!page || page.isClosed()) {\n ConnectionManager.logger.warn(`Page is closed or null for connection: ${connection.name}`)\n return false\n }\n\n // URL 기반 로그인 리디렉션 체크\n let currentUrl: string\n try {\n currentUrl = page.url()\n } catch (urlError) {\n ConnectionManager.logger.warn(`Cannot get page URL for connection: ${connection.name}`, urlError)\n return false\n }\n \n if (currentUrl.includes(loginInfo.loginPagePath) || \n currentUrl.includes('/login') || \n currentUrl.includes('/signin') || \n currentUrl.includes('/auth')) {\n ConnectionManager.logger.info(`Redirected to login page: ${currentUrl}`)\n return false\n }\n\n // 로그인 폼 요소 기반 체크\n const needsLogin = await page.evaluate(() => {\n try {\n // 로그인 페이지 특정 요소들이 있는지 확인\n const loginIndicators = ['#username', '#password', '.login-form', '[type=\"password\"]']\n return loginIndicators.some(selector => {\n const element = document.querySelector(selector) as HTMLElement\n return element && element.offsetParent !== null // 보이는 요소만 체크\n })\n } catch (evalError) {\n return true // 에러 시 안전하게 재로그인 필요로 간주\n }\n })\n\n // 추가 세션 검증: 간단한 API 호출로 인증 상태 확인\n try {\n const testResponse = await page.evaluate(async () => {\n try {\n const response = await fetch(window.location.origin, { \n method: 'HEAD',\n credentials: 'include'\n })\n return {\n status: response.status,\n ok: response.ok\n }\n } catch (fetchError) {\n return { status: 0, ok: false }\n }\n })\n \n // 인증이 필요한 엔드포인트에서 401/403 반환시 세션 무효\n if (testResponse.status === 401 || testResponse.status === 403) {\n ConnectionManager.logger.info(`Authentication failed with status ${testResponse.status} for connection: ${connection.name}`)\n return false\n }\n } catch (apiTestError) {\n ConnectionManager.logger.warn(`Session API test failed for connection: ${connection.name}`, apiTestError)\n // API 테스트 실패는 세션 무효로 간주하지 않음 (네트워크 문제일 수 있음)\n }\n\n return !needsLogin\n } catch (error) {\n ConnectionManager.logger.warn(`Session validation failed for connection: ${connection.name}`, error)\n return false\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 setupPage(page, uri, timeout) {\n await page.setRequestInterception(true)\n\n await page.setBypassCSP(true)\n\n await page.setUserAgent(\n 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.204 Safari/537.36'\n )\n\n await page.evaluateOnNewDocument(() => {\n Object.defineProperty(navigator, 'webdriver', { get: () => false })\n })\n\n await page.setExtraHTTPHeaders({\n 'Upgrade-Insecure-Requests': '0',\n 'accept-language': 'en-US,en;q=0.9',\n referer: uri\n })\n\n await page.setDefaultNavigationTimeout(timeout)\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 ConnectionManager.logger.info(`[browser ${msg.type()}]`, ...args.filter(a => a !== undefined))\n } else {\n const text = msg.text()\n if (text) ConnectionManager.logger.info(`[browser ${msg.type()}]`, text)\n }\n })\n\n page.on('request', request => {\n if (request.isInterceptResolutionHandled()) {\n return\n }\n\n const resourceType = request.resourceType()\n\n // fetch(), XMLHttpRequest 등의 API 호출은 항상 허용\n if (resourceType === 'fetch' || resourceType === 'xhr') {\n request.continue()\n return\n }\n\n const headers = request.headers()\n if (headers['access-control-request-headers']) {\n const filteredHeaders = headers['access-control-request-headers']\n .split(',')\n .map(header => header.trim())\n .filter(header => header.toLowerCase() !== 'upgrade-insecure-requests')\n\n if (filteredHeaders.length > 0) {\n headers['access-control-request-headers'] = filteredHeaders.join(', ')\n } else {\n delete headers['access-control-request-headers']\n }\n }\n\n if (headers['upgrade-insecure-requests']) {\n delete headers['upgrade-insecure-requests']\n }\n\n // 이미지, 스타일시트, 폰트는 차단하되 API 요청은 허용\n if (['image', 'stylesheet', 'font'].includes(resourceType)) {\n request.abort()\n } else {\n request.continue()\n }\n })\n\n page.on('requestfailed', request => {\n try {\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 if (request.postData()) {\n console.log(`- Post Data: ${request.postData()}`)\n }\n } catch (error) {\n console.error('Error in requestfailed handler:', error)\n }\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.loginPagePath}`, { 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 var loginApiUrl =\n loginInfo.loginApiUrl ||\n (await page.evaluate(button => {\n const form = button.closest('form')\n return form ? form.action : null\n }, submitButton))\n\n if (loginApiUrl && !loginApiUrl.startsWith('http')) {\n loginApiUrl = `${uri}${loginApiUrl}`\n }\n\n if (!loginApiUrl) {\n throw new Error('❌ Unable to detect login API URL!')\n }\n\n console.log('✅ Detected login API URL:', loginApiUrl)\n\n const [response] = await Promise.all([\n page.waitForResponse(response => {\n const url = response.url()\n const method = response.request().method()\n\n return url === loginApiUrl && method === 'POST' && [200, 201, 204, 302, 304].includes(response.status())\n }),\n page.evaluate(button => button.click(), submitButton),\n page.waitForNavigation({ waitUntil: 'networkidle2', timeout: loginInfo.timeout }) // 로그인 후 리디렉션 감지\n ])\n\n if (response) {\n const status = response.status()\n if (status >= 200 && status < 400) {\n ConnectionManager.logger.info(`Login successful with status code: ${status}`)\n return\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 try {\n await page.screenshot({ path: `logs/login-failure-attempt-${attempt}.png` })\n } catch (error) {\n ConnectionManager.logger.error('Failed to capture screenshot:', error)\n }\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 try {\n // 연결 해제 전 리소스 정리 로그\n ConnectionManager.logger.info(`Starting disconnect process for connection: ${connection.name}`)\n \n // 풀 상태 확인 (정보성 로깅)\n try {\n const pool = getHeadlessPool()\n ConnectionManager.logger.info(`Pool status before disconnect - available resources exist: ${!!pool}`)\n } catch (poolError) {\n ConnectionManager.logger.warn(`Could not access pool during disconnect: ${poolError.message}`)\n }\n\n ConnectionManager.removeConnectionInstance(connection)\n ConnectionManager.logger.info(`headless-connector connection(${connection.name}) is disconnected`)\n } catch (error) {\n ConnectionManager.logger.error(`Error disconnecting headless-connector connection(${connection.name}):`, error)\n // Still try to remove the connection instance even if cleanup failed\n try {\n ConnectionManager.removeConnectionInstance(connection)\n } catch (removeError) {\n ConnectionManager.logger.error(`Failed to remove connection instance ${connection.name}:`, removeError)\n }\n }\n }\n\n get parameterSpec() {\n return [\n {\n type: 'string',\n name: 'username',\n label: 'username',\n useDomainAttribute: true\n },\n {\n type: 'secret',\n name: 'password',\n label: 'password',\n useDomainAttribute: true\n },\n {\n type: 'string',\n name: 'loginPagePath',\n label: 'login-page-path'\n },\n {\n type: 'string',\n name: 'loginApiUrl',\n label: 'login-api-url'\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: any[]): boolean {\n if (!cookies || cookies.length === 0) return false\n const now = Date.now() / 1000 // Current time in seconds\n return cookies.some((cookie: any) => cookie.expires && cookie.expires > now)\n}\n"]}
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, getPoolStats } 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 /**\n * 외부 사이트 로그인 세션 보유 — 같은 connection name 의 동시 acquireSessionPage\n * 호출이 같은 외부 계정 세션을 서로 invalidate 하지 않도록 ConnectionManager 가\n * mutex 직렬화. 상속 connector (allbaro, kiscon 등) 자동 적용.\n */\n sessionExclusive = true\n\n /**\n * 자식 도메인이 부모의 Connection 정의를 inherit 할 때 자식별 새 인스턴스 생성.\n * cookies/세션·자격증명 격리. 헤드리스 사이트 connector 는 외부 자격증명이\n * 자식 (테넌트) 마다 다를 수 있으므로 ISOLATE 가 자연 default.\n */\n inheritanceMode = 'isolate' as const\n\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 loginPagePath = '/login',\n loginApiUrl = null,\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 // 쿠키 캐시 재사용 건너뛰고 매 acquireSessionPage 마다 로그인하도록 강제.\n // 서버 세션 만료가 잦거나 검증이 비싼 사이트(예: 올바로) 에서 사용.\n forceLogin = false\n } = {}\n } = connection\n\n const loginInfo = {\n loginRequired: Boolean(username), // Determine if login is required\n forceLogin: Boolean(forceLogin),\n username,\n password,\n loginPagePath,\n loginApiUrl,\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 \n // Pool 상태 모니터링 (디버깅용)\n try {\n const stats = getPoolStats()\n ConnectionManager.logger.debug(`Pool stats before acquire - borrowed: ${stats.borrowed}, available: ${stats.available}, pending: ${stats.pending}`)\n } catch (statsError) {\n ConnectionManager.logger.warn('Failed to get pool stats:', statsError)\n }\n \n const browser = await pool.acquire()\n ConnectionManager.logger.info('Browser acquired successfully from pool')\n return browser\n } catch (error) {\n ConnectionManager.logger.error('Failed to acquire browser from pool:', error)\n \n // 풀 상태 로깅으로 디버깅 도움\n try {\n const stats = getPoolStats()\n ConnectionManager.logger.error(`Pool stats during acquire error - borrowed: ${stats.borrowed}, available: ${stats.available}, pending: ${stats.pending}, max: ${stats.max}`)\n } catch (statsError) {\n ConnectionManager.logger.error('Failed to get pool stats during error:', statsError)\n }\n \n throw error\n }\n }\n\n async function releaseBrowser(browser: Browser) {\n if (!browser) {\n ConnectionManager.logger.warn('Attempted to release null/undefined browser')\n return\n }\n\n try {\n // 브라우저가 아직 연결되어 있는지 확인\n if (!browser.isConnected()) {\n ConnectionManager.logger.warn('Attempted to release disconnected browser')\n return\n }\n\n // Pool 상태 모니터링 (릴리즈 전)\n try {\n const statsBefore = getPoolStats()\n ConnectionManager.logger.debug(`Pool stats before release - borrowed: ${statsBefore.borrowed}, available: ${statsBefore.available}`)\n } catch (statsError) {\n ConnectionManager.logger.warn('Failed to get pool stats before release:', statsError)\n }\n\n const pool = getHeadlessPool()\n await pool.release(browser)\n ConnectionManager.logger.info('Browser successfully released to pool')\n \n // Pool 상태 모니터링 (릴리즈 후)\n try {\n const statsAfter = getPoolStats()\n ConnectionManager.logger.debug(`Pool stats after release - borrowed: ${statsAfter.borrowed}, available: ${statsAfter.available}`)\n } catch (statsError) {\n ConnectionManager.logger.warn('Failed to get pool stats after release:', statsError)\n }\n } catch (error) {\n ConnectionManager.logger.error('Failed to release browser to pool:', error)\n \n // 에러 발생시 풀 상태 로깅\n try {\n const stats = getPoolStats()\n ConnectionManager.logger.error(`Pool stats during release error - borrowed: ${stats.borrowed}, available: ${stats.available}, pending: ${stats.pending}`)\n } catch (statsError) {\n ConnectionManager.logger.error('Failed to get pool stats during release error:', statsError)\n }\n \n // 풀 릴리즈가 실패한 경우 브라우저 강제 종료 시도\n try {\n if (browser && browser.isConnected()) {\n await browser.close()\n ConnectionManager.logger.warn('Forcibly closed browser due to pool release failure')\n }\n } catch (forceCloseError) {\n ConnectionManager.logger.error('Failed to force close browser after pool release failure:', forceCloseError)\n }\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 await this.setupPage(page, uri, timeout)\n\n if (loginInfo.loginRequired) {\n if (loginInfo.forceLogin) {\n // 매번 새로 로그인 — 쿠키·세션 재사용 검증 모두 건너뜀.\n // 세션이 서버측에서 자주 만료되는 사이트 용도.\n ConnectionManager.logger.info('forceLogin=true — skipping cookie reuse, performing fresh login')\n connection.cookies = undefined\n await this.performLogin(page, uri, loginInfo)\n cookies = await page.cookies()\n connection.cookies = cookies\n } else {\n // 먼저 기본 페이지로 이동\n await page.goto(uri, { waitUntil: 'networkidle2', timeout })\n\n // 현재 세션의 Authorization 헤더 확인\n const headers = await page.evaluate(() => {\n return fetch(window.location.href, { method: 'GET' })\n .then(response => response.headers.get('Authorization'))\n .catch(() => null)\n })\n\n if (headers) {\n ConnectionManager.logger.info('User is already logged in, skipping login process.')\n return page\n }\n\n if (cookies && isCookieValid(cookies)) {\n await this.applyCookiesAndVerifySession(page, cookies, loginInfo)\n return page\n }\n\n await this.performLogin(page, uri, loginInfo)\n cookies = await page.cookies()\n connection.cookies = cookies\n }\n } else {\n // 로그인이 필요하지 않은 경우에도 기본 페이지로 이동\n await page.goto(uri, { waitUntil: 'networkidle2', timeout })\n }\n\n return page\n } catch (error) {\n ConnectionManager.logger.error('Failed to acquire session page:', error)\n \n // CRITICAL: 에러 발생 시 확실한 리소스 정리\n if (page) {\n try {\n await page.close()\n ConnectionManager.logger.info('Page closed during acquireSessionPage error handling')\n } catch (closeError) {\n ConnectionManager.logger.error('Failed to close page during acquireSessionPage error:', closeError)\n }\n }\n \n try {\n await releaseBrowser(browser)\n ConnectionManager.logger.info('Browser released during acquireSessionPage error handling')\n } catch (releaseError) {\n ConnectionManager.logger.error('Failed to release browser during acquireSessionPage error:', releaseError)\n }\n \n throw error\n }\n },\n releasePage: async (page: Page) => {\n try {\n if (page && !page.isClosed()) {\n const browser = page.browser()\n \n // 페이지를 먼저 닫기\n try {\n await page.close()\n ConnectionManager.logger.info('Page closed successfully')\n } catch (closeError) {\n ConnectionManager.logger.error('Failed to close page:', closeError)\n }\n \n // 브라우저를 풀에 반환\n try {\n await releaseBrowser(browser)\n ConnectionManager.logger.info('Browser released successfully')\n } catch (releaseError) {\n ConnectionManager.logger.error('Failed to release browser:', releaseError)\n // 풀 릴리즈가 실패한 경우 브라우저 강제 종료 시도\n try {\n if (browser && browser.isConnected()) {\n await browser.close()\n ConnectionManager.logger.warn('Forcibly closed browser due to pool release failure')\n }\n } catch (forceCloseError) {\n ConnectionManager.logger.error('Failed to force close browser:', forceCloseError)\n }\n }\n }\n } catch (error) {\n ConnectionManager.logger.error('Critical error in releasePage:', error)\n }\n },\n // 세션 회복을 위한 재로그인 메서드\n reAuthenticateSession: async () => {\n ConnectionManager.logger.info(`Re-authenticating session for connection: ${connection.name}`)\n \n let browser = null\n let page = null\n\n try {\n browser = await acquireBrowser()\n page = await browser.newPage()\n await this.setupPage(page, uri, loginInfo.timeout)\n \n if (loginInfo.loginRequired) {\n await this.performLogin(page, uri, loginInfo)\n const newCookies = await page.cookies()\n connection.cookies = newCookies\n ConnectionManager.logger.info(`Session re-authenticated successfully for connection: ${connection.name}`)\n }\n \n // CRITICAL: 페이지와 함께 브라우저 정보도 반환하여 정확한 릴리즈 추적\n return {\n page,\n browser,\n requiresManualRelease: true // 수동 릴리즈 필요 표시\n }\n } catch (error) {\n ConnectionManager.logger.error(`Failed to re-authenticate session for connection: ${connection.name}`, error)\n \n // 에러 시 확실한 리소스 정리\n if (page) {\n try {\n if (!page.isClosed()) {\n await page.close()\n ConnectionManager.logger.info('Page closed during reauth error handling')\n }\n } catch (closeError) {\n ConnectionManager.logger.error('Failed to close page during reauth error:', closeError)\n }\n }\n \n if (browser) {\n try {\n await releaseBrowser(browser)\n ConnectionManager.logger.info('Browser released during reauth error handling')\n } catch (releaseError) {\n ConnectionManager.logger.error('Failed to release browser during reauth error:', releaseError)\n // 풀 릴리즈가 실패한 경우 브라우저 강제 종료 시도\n try {\n if (browser.isConnected()) {\n await browser.close()\n ConnectionManager.logger.warn('Forcibly closed browser during reauth error due to pool release failure')\n }\n } catch (forceCloseError) {\n ConnectionManager.logger.error('Failed to force close browser during reauth error:', forceCloseError)\n }\n }\n }\n \n throw error\n }\n },\n // 세션 유효성 검사\n validateSession: async (page: Page) => {\n try {\n if (!loginInfo.loginRequired) {\n return true\n }\n\n // 페이지가 닫혔거나 연결이 끊어진 경우\n if (!page || page.isClosed()) {\n ConnectionManager.logger.warn(`Page is closed or null for connection: ${connection.name}`)\n return false\n }\n\n // URL 기반 로그인 리디렉션 체크\n let currentUrl: string\n try {\n currentUrl = page.url()\n } catch (urlError) {\n ConnectionManager.logger.warn(`Cannot get page URL for connection: ${connection.name}`, urlError)\n return false\n }\n \n if (currentUrl.includes(loginInfo.loginPagePath) || \n currentUrl.includes('/login') || \n currentUrl.includes('/signin') || \n currentUrl.includes('/auth')) {\n ConnectionManager.logger.info(`Redirected to login page: ${currentUrl}`)\n return false\n }\n\n // 로그인 폼 요소 기반 체크\n const needsLogin = await page.evaluate(() => {\n try {\n // 로그인 페이지 특정 요소들이 있는지 확인\n const loginIndicators = ['#username', '#password', '.login-form', '[type=\"password\"]']\n return loginIndicators.some(selector => {\n const element = document.querySelector(selector) as HTMLElement\n return element && element.offsetParent !== null // 보이는 요소만 체크\n })\n } catch (evalError) {\n return true // 에러 시 안전하게 재로그인 필요로 간주\n }\n })\n\n // 추가 세션 검증: 간단한 API 호출로 인증 상태 확인\n try {\n const testResponse = await page.evaluate(async () => {\n try {\n const response = await fetch(window.location.origin, { \n method: 'HEAD',\n credentials: 'include'\n })\n return {\n status: response.status,\n ok: response.ok\n }\n } catch (fetchError) {\n return { status: 0, ok: false }\n }\n })\n \n // 인증이 필요한 엔드포인트에서 401/403 반환시 세션 무효\n if (testResponse.status === 401 || testResponse.status === 403) {\n ConnectionManager.logger.info(`Authentication failed with status ${testResponse.status} for connection: ${connection.name}`)\n return false\n }\n } catch (apiTestError) {\n ConnectionManager.logger.warn(`Session API test failed for connection: ${connection.name}`, apiTestError)\n // API 테스트 실패는 세션 무효로 간주하지 않음 (네트워크 문제일 수 있음)\n }\n\n return !needsLogin\n } catch (error) {\n ConnectionManager.logger.warn(`Session validation failed for connection: ${connection.name}`, error)\n return false\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 setupPage(page, uri, timeout) {\n await page.setRequestInterception(true)\n\n await page.setBypassCSP(true)\n\n await page.setUserAgent(\n 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.204 Safari/537.36'\n )\n\n await page.evaluateOnNewDocument(() => {\n Object.defineProperty(navigator, 'webdriver', { get: () => false })\n })\n\n await page.setExtraHTTPHeaders({\n 'Upgrade-Insecure-Requests': '0',\n 'accept-language': 'en-US,en;q=0.9',\n referer: uri\n })\n\n await page.setDefaultNavigationTimeout(timeout)\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 ConnectionManager.logger.info(`[browser ${msg.type()}]`, ...args.filter(a => a !== undefined))\n } else {\n const text = msg.text()\n if (text) ConnectionManager.logger.info(`[browser ${msg.type()}]`, text)\n }\n })\n\n page.on('request', request => {\n if (request.isInterceptResolutionHandled()) {\n return\n }\n\n const resourceType = request.resourceType()\n\n // fetch(), XMLHttpRequest 등의 API 호출은 항상 허용\n if (resourceType === 'fetch' || resourceType === 'xhr') {\n request.continue()\n return\n }\n\n const headers = request.headers()\n if (headers['access-control-request-headers']) {\n const filteredHeaders = headers['access-control-request-headers']\n .split(',')\n .map(header => header.trim())\n .filter(header => header.toLowerCase() !== 'upgrade-insecure-requests')\n\n if (filteredHeaders.length > 0) {\n headers['access-control-request-headers'] = filteredHeaders.join(', ')\n } else {\n delete headers['access-control-request-headers']\n }\n }\n\n if (headers['upgrade-insecure-requests']) {\n delete headers['upgrade-insecure-requests']\n }\n\n // 이미지, 스타일시트, 폰트는 차단하되 API 요청은 허용\n if (['image', 'stylesheet', 'font'].includes(resourceType)) {\n request.abort()\n } else {\n request.continue()\n }\n })\n\n page.on('requestfailed', request => {\n try {\n // 정적 리소스(이미지·폰트·CSS 등) 의 실패는 로그인 흐름과 무관한 소음이므로 무시.\n // document / xhr / fetch 같은 의미있는 요청 실패만 출력.\n const type = request.resourceType()\n const NOISY_TYPES = new Set(['image', 'font', 'stylesheet', 'media', 'manifest', 'other'])\n if (NOISY_TYPES.has(type)) return\n\n // 일부 사이트의 정적 자원이 다른 type 으로 잡힐 수 있어 확장자 기반 보강 필터.\n const url = request.url()\n if (/\\.(png|jpe?g|gif|webp|svg|ico|woff2?|ttf|otf|eot|css|map)(\\?|$)/i.test(url)) return\n\n console.warn(\n `[headless] request failed (type=${type}): ${request.method()} ${url} — ${request.failure()?.errorText}`\n )\n } catch (error) {\n console.error('Error in requestfailed handler:', error)\n }\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 let lastError: Error | null = null\n for (let attempt = 1; attempt <= loginInfo.retries; attempt++) {\n try {\n await page.goto(`${uri}${loginInfo.loginPagePath}`, { 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(\n `Failed to locate login form elements ` +\n `(username=${!!usernameInput}, password=${!!passwordInput}, submit=${!!submitButton}). ` +\n `Selectors: username='${loginInfo.loginSelectors.usernameSelector}', ` +\n `password='${loginInfo.loginSelectors.passwordSelector}', ` +\n `submit='${loginInfo.loginSelectors.submitSelector}'. ` +\n `사이트 폼 변경 가능성 — Connection params 의 *Selector 값을 실제 폼에 맞춰 덮어쓰세요.`\n )\n }\n\n await usernameInput.type(loginInfo.username)\n await passwordInput.type(loginInfo.password)\n\n var loginApiUrl =\n loginInfo.loginApiUrl ||\n (await page.evaluate(button => {\n const form = button.closest('form')\n return form ? form.action : null\n }, submitButton))\n\n if (loginApiUrl && !loginApiUrl.startsWith('http')) {\n loginApiUrl = `${uri}${loginApiUrl}`\n }\n\n if (!loginApiUrl) {\n throw new Error('❌ Unable to detect login API URL!')\n }\n\n console.log('✅ Detected login API URL:', loginApiUrl)\n\n const [response] = await Promise.all([\n page.waitForResponse(response => {\n const url = response.url()\n const method = response.request().method()\n\n return url === loginApiUrl && method === 'POST' && [200, 201, 204, 302, 304].includes(response.status())\n }),\n page.evaluate(button => button.click(), submitButton),\n page.waitForNavigation({ waitUntil: 'networkidle2', timeout: loginInfo.timeout }) // 로그인 후 리디렉션 감지\n ])\n\n if (response) {\n const status = response.status()\n if (status >= 200 && status < 400) {\n ConnectionManager.logger.info(`Login successful with status code: ${status}`)\n return\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: any) {\n lastError = error\n ConnectionManager.logger.warn(`Login attempt ${attempt} failed:`, error)\n\n try {\n await page.screenshot({ path: `logs/login-failure-attempt-${attempt}.png` })\n } catch (error) {\n ConnectionManager.logger.error('Failed to capture screenshot:', error)\n }\n\n if (attempt === loginInfo.retries) {\n throw new Error(\n `Login failed after ${loginInfo.retries} attempts — last cause: ${lastError?.message || lastError}`\n )\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 // XPath 지원 — puppeteer 22+ 의 통합 선택자: `xpath/<expression>` prefix.\n // 셀렉터가 `//`, `(//`, `.//` 로 시작하면 xpath/ 로 감싸 page.$ 에 전달.\n if (typeof targetSelector === 'string' && /^\\s*(\\(|\\/\\/|\\.\\/\\/)/.test(targetSelector)) {\n return await page.$('xpath/' + targetSelector.trim())\n }\n context = page.mainFrame() // Puppeteer uses frames to represent document\n return context.$(targetSelector) // CSS selector — 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 try {\n // 연결 해제 전 리소스 정리 로그\n ConnectionManager.logger.info(`Starting disconnect process for connection: ${connection.name}`)\n \n // 풀 상태 확인 (정보성 로깅)\n try {\n const pool = getHeadlessPool()\n ConnectionManager.logger.info(`Pool status before disconnect - available resources exist: ${!!pool}`)\n } catch (poolError) {\n ConnectionManager.logger.warn(`Could not access pool during disconnect: ${poolError.message}`)\n }\n\n ConnectionManager.removeConnectionInstance(connection)\n ConnectionManager.logger.info(`headless-connector connection(${connection.name}) is disconnected`)\n } catch (error) {\n ConnectionManager.logger.error(`Error disconnecting headless-connector connection(${connection.name}):`, error)\n // Still try to remove the connection instance even if cleanup failed\n try {\n ConnectionManager.removeConnectionInstance(connection)\n } catch (removeError) {\n ConnectionManager.logger.error(`Failed to remove connection instance ${connection.name}:`, removeError)\n }\n }\n }\n\n get parameterSpec() {\n return [\n {\n type: 'string',\n name: 'username',\n label: 'username',\n useDomainAttribute: true\n },\n {\n type: 'secret',\n name: 'password',\n label: 'password',\n useDomainAttribute: true\n },\n {\n type: 'string',\n name: 'loginPagePath',\n label: 'login-page-path'\n },\n {\n type: 'string',\n name: 'loginApiUrl',\n label: 'login-api-url'\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: any[]): boolean {\n if (!cookies || cookies.length === 0) return false\n const now = Date.now() / 1000 // Current time in seconds\n return cookies.some((cookie: any) => cookie.expires && cookie.expires > now)\n}\n"]}
@@ -93,9 +93,9 @@ class OperatoConnector {
93
93
  link: splitLink
94
94
  });
95
95
  const subscriptions = [];
96
- Object.keys(subscriptionHandlers).forEach(async (tag) => {
97
- if (!tag || !subscriptionHandlers[tag])
98
- return;
96
+ const tags = Object.keys(subscriptionHandlers).filter(tag => tag && subscriptionHandlers[tag]);
97
+ /* 시나리오 조회 및 subscription 등록을 순차적으로 처리 */
98
+ for (const tag of tags) {
99
99
  const scenarioName = subscriptionHandlers[tag];
100
100
  // fetch a scenario
101
101
  const selectedScenario = await (0, shell_1.getRepository)(scenario_1.Scenario).findOne({
@@ -104,6 +104,11 @@ class OperatoConnector {
104
104
  },
105
105
  relations: ['steps', 'domain']
106
106
  });
107
+ /* 시나리오가 DB에 없으면 경고 로그를 남기고 건너뜀 */
108
+ if (!selectedScenario) {
109
+ connection_manager_1.ConnectionManager.logger.warn(`(${connection.name}) scenario not found for tag "${tag}" (scenarioName: "${scenarioName}") - skipping subscription`);
110
+ continue;
111
+ }
107
112
  const subscription = client.subscribe({
108
113
  query: (0, graphql_tag_1.default) `
109
114
  subscription {
@@ -116,8 +121,14 @@ class OperatoConnector {
116
121
  });
117
122
  const subscriptionObserver = subscription.subscribe({
118
123
  next: async (data) => {
119
- debug('received pubsub msg.:', data?.data);
120
- await this.runScenario(subscriptions, data?.data?.data);
124
+ /* subscription 수신 시 에러가 발생해도 프로세스가 죽지 않도록 catch 처리 */
125
+ try {
126
+ debug('received pubsub msg.:', data?.data);
127
+ await this.runScenario(subscriptions, data?.data?.data);
128
+ }
129
+ catch (e) {
130
+ connection_manager_1.ConnectionManager.logger.error(`(${connection.name}:${tag}) runScenario failed`, e);
131
+ }
121
132
  },
122
133
  error: error => {
123
134
  connection_manager_1.ConnectionManager.logger.error(`(${connection.name}:${connection.endpoint}) subscription error`, error);
@@ -133,7 +144,7 @@ class OperatoConnector {
133
144
  subscriptionObserver
134
145
  });
135
146
  connection_manager_1.ConnectionManager.logger.info(`(${tag}:${scenarioName}) subscription closed flag: ${subscriptionObserver.closed}`);
136
- });
147
+ }
137
148
  client['subscriptions'] = subscriptions;
138
149
  connection_manager_1.ConnectionManager.addConnectionInstance(connection, client);
139
150
  connection_manager_1.ConnectionManager.logger.info(`operato-connector connection(${connection.name}:${connection.endpoint}) is connected`);
@@ -1 +1 @@
1
- {"version":3,"file":"operato-connector.js","sourceRoot":"","sources":["../../../server/engine/connector/operato-connector.ts"],"names":[],"mappings":"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","sourcesContent":["import 'cross-fetch/polyfill'\n\nimport { ApolloClient, InMemoryCache, createHttpLink, split } from '@apollo/client/core'\nimport { setContext } from '@apollo/client/link/context'\n\nimport WebSocket from 'ws'\nimport { createClient } from 'graphql-ws'\nimport { GraphQLWsLink } from '@apollo/client/link/subscriptions'\nimport { getMainDefinition } from '@apollo/client/utilities'\nimport gql from 'graphql-tag'\n\nimport { ConnectionManager } from '../connection-manager'\nimport { Connector } from '../types'\nimport { InputConnection } from '../../service/connection/connection-type'\n\nimport { Scenario } from '../../service/scenario/scenario'\nimport { ScenarioInstance } from '../../service/scenario-instance/scenario-instance-type'\n\nimport { getRepository, GraphqlLocalClient, Domain } from '@things-factory/shell'\nimport { User, checkUserHasRole } from '@things-factory/auth-base'\n\nconst debug = require('debug')('things-factory:integration-base:operato-connector')\n\nconst defaultOptions: any = {\n watchQuery: {\n fetchPolicy: 'no-cache',\n errorPolicy: 'ignore'\n },\n query: {\n fetchPolicy: 'no-cache', //'network-only'\n errorPolicy: 'all'\n },\n mutate: {\n errorPolicy: 'all'\n }\n}\n\nexport const GRAPHQL_URI = '/graphql'\nexport const SUBSCRIPTION_URI = GRAPHQL_URI\n\ninterface SubscriberData {\n tag: string\n scenario: any\n subscriptionObserver: any\n}\n\nexport class OperatoConnector implements Connector {\n private context: any\n\n async ready(connectionConfigs: InputConnection[]) {\n await Promise.all(connectionConfigs.map(this.connect.bind(this)))\n\n ConnectionManager.logger.info('operato-connector connections are ready')\n }\n\n async connect(connection: InputConnection) {\n const {\n endpoint: uri,\n params: { authKey, domain, subscriptionHandlers = {} }\n } = connection\n\n if (!authKey || !domain) {\n throw new Error('some connection paramter missing.')\n }\n\n const domainOwner = await getRepository(User).findOne({\n where: {\n id: connection.domain.owner\n }\n })\n\n this.context = {\n domain: connection.domain,\n user: domainOwner\n /* TODO: domainOwner 대신 특정 유저를 지정할 수 있도록 개선해야함. 모든 커넥션에 유저를 지정하는 기능으로 일반화할 필요가 있는 지 고민해야함 */\n }\n\n const httpLink = createHttpLink({\n uri: uri\n })\n\n /* \n CHECKPOINT: \n 1. GraphqQLWsLink를 사용하면 setContext를 통한 추가 헤더 설정이 무시됩니다.\n 따라서, GraphQLWsLink를 사용하려면, connectionParams를 통해 헤더를 설정해야 합니다.\n \n 2. 서버에서 실행시, webSocketImpl을 명시적으로 지정해야 합니다.\n */\n const wsLink = new GraphQLWsLink(\n createClient({\n url: uri.replace(/^http/, 'ws'),\n keepAlive: 10_000,\n retryAttempts: 1_000_000,\n shouldRetry: e => true,\n webSocketImpl: WebSocket,\n connectionParams: {\n headers: {\n 'x-things-factory-domain': domain,\n authorization: authKey ? `Bearer ${authKey}` : ''\n }\n }\n })\n )\n\n const splitLink = split(\n ({ query }) => {\n const def = getMainDefinition(query)\n return def.kind === 'OperationDefinition' && def.operation === 'subscription'\n },\n wsLink,\n setContext((_, { headers }) => {\n return {\n headers: {\n ...headers,\n 'x-things-factory-domain': domain,\n authorization: authKey ? `Bearer ${authKey}` : ''\n }\n }\n }).concat(httpLink)\n )\n\n const cache = new InMemoryCache()\n\n const client = new ApolloClient({\n defaultOptions,\n cache,\n link: splitLink\n })\n\n const subscriptions: SubscriberData[] = []\n Object.keys(subscriptionHandlers).forEach(async tag => {\n if (!tag || !subscriptionHandlers[tag]) return\n\n const scenarioName = subscriptionHandlers[tag]\n\n // fetch a scenario\n const selectedScenario = await getRepository(Scenario).findOne({\n where: {\n name: scenarioName\n },\n relations: ['steps', 'domain']\n })\n\n const subscription = client.subscribe({\n query: gql`\n subscription {\n data(tag: \"${tag}\") {\n tag\n data\n }\n }\n `\n })\n\n const subscriptionObserver = subscription.subscribe({\n next: async data => {\n debug('received pubsub msg.:', data?.data)\n await this.runScenario(subscriptions, data?.data?.data)\n },\n error: error => {\n ConnectionManager.logger.error(`(${connection.name}:${connection.endpoint}) subscription error`, error)\n },\n complete: () => {\n ConnectionManager.logger.info(`(${connection.name}:${connection.endpoint}) subscription complete`)\n }\n })\n\n ConnectionManager.logger.info(\n `(${connection.name}:${connection.endpoint}) subscription closed flag: ${subscriptionObserver.closed}`\n )\n\n subscriptions.push({\n tag,\n scenario: selectedScenario,\n subscriptionObserver\n })\n ConnectionManager.logger.info(`(${tag}:${scenarioName}) subscription closed flag: ${subscriptionObserver.closed}`)\n })\n\n client['subscriptions'] = subscriptions\n ConnectionManager.addConnectionInstance(connection, client)\n\n ConnectionManager.logger.info(\n `operato-connector connection(${connection.name}:${connection.endpoint}) is connected`\n )\n }\n\n async disconnect(connection: InputConnection) {\n const client = ConnectionManager.getConnectionInstance(connection)\n const subscriptions: SubscriberData[] = client['subscriptions']\n subscriptions.forEach(subscription => subscription.subscriptionObserver.unsubscribe())\n client.stop()\n ConnectionManager.removeConnectionInstance(connection)\n\n ConnectionManager.logger.info(`operato-connector connection(${connection.name}) is disconnected`)\n }\n\n async runScenario(subscriptions: SubscriberData[], variables: any): Promise<ScenarioInstance> {\n const { domain, user } = this.context\n const { tag } = variables\n\n if (!tag) {\n throw new Error(`tag is invalid - ${tag}`)\n }\n\n const scenario = subscriptions.find(subscription => subscription.tag === tag)?.scenario\n if (!scenario) {\n throw new Error(`scenario is not found - ${tag}`)\n }\n\n if (!(await checkUserHasRole(scenario.roleId, domain, user))) {\n throw new Error(`Unauthorized! ${scenario.name} doesn't have required role.`)\n }\n\n /* create a scenario instance */\n const instanceName = scenario.name + '-' + String(Date.now())\n const instance = new ScenarioInstance(instanceName, scenario, {\n user,\n domain,\n variables,\n client: GraphqlLocalClient.client\n })\n\n // run scenario\n await instance.run()\n return instance\n }\n\n get parameterSpec() {\n return [\n {\n type: 'string',\n name: 'authKey',\n label: 'auth-key',\n useDomainAttribute: true\n },\n {\n type: 'string',\n name: 'domain',\n label: 'domain'\n },\n {\n type: 'tag-scenarios',\n name: 'subscriptionHandlers',\n label: 'subscription-handlers'\n }\n ]\n }\n\n get taskPrefixes() {\n return ['graphql']\n }\n\n get help() {\n return 'integration/connector/operato-connector'\n }\n\n get description() {\n return 'Operato Graphql Connector'\n }\n}\n\nConnectionManager.registerConnector('operato-connector', new OperatoConnector())\n"]}
1
+ {"version":3,"file":"operato-connector.js","sourceRoot":"","sources":["../../../server/engine/connector/operato-connector.ts"],"names":[],"mappings":"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","sourcesContent":["import 'cross-fetch/polyfill'\n\nimport { ApolloClient, InMemoryCache, createHttpLink, split } from '@apollo/client/core'\nimport { setContext } from '@apollo/client/link/context'\n\nimport WebSocket from 'ws'\nimport { createClient } from 'graphql-ws'\nimport { GraphQLWsLink } from '@apollo/client/link/subscriptions'\nimport { getMainDefinition } from '@apollo/client/utilities'\nimport gql from 'graphql-tag'\n\nimport { ConnectionManager } from '../connection-manager'\nimport { Connector } from '../types'\nimport { InputConnection } from '../../service/connection/connection-type'\n\nimport { Scenario } from '../../service/scenario/scenario'\nimport { ScenarioInstance } from '../../service/scenario-instance/scenario-instance-type'\n\nimport { getRepository, GraphqlLocalClient, Domain } from '@things-factory/shell'\nimport { User, checkUserHasRole } from '@things-factory/auth-base'\n\nconst debug = require('debug')('things-factory:integration-base:operato-connector')\n\nconst defaultOptions: any = {\n watchQuery: {\n fetchPolicy: 'no-cache',\n errorPolicy: 'ignore'\n },\n query: {\n fetchPolicy: 'no-cache', //'network-only'\n errorPolicy: 'all'\n },\n mutate: {\n errorPolicy: 'all'\n }\n}\n\nexport const GRAPHQL_URI = '/graphql'\nexport const SUBSCRIPTION_URI = GRAPHQL_URI\n\ninterface SubscriberData {\n tag: string\n scenario: any\n subscriptionObserver: any\n}\n\nexport class OperatoConnector implements Connector {\n private context: any\n\n async ready(connectionConfigs: InputConnection[]) {\n await Promise.all(connectionConfigs.map(this.connect.bind(this)))\n\n ConnectionManager.logger.info('operato-connector connections are ready')\n }\n\n async connect(connection: InputConnection) {\n const {\n endpoint: uri,\n params: { authKey, domain, subscriptionHandlers = {} }\n } = connection\n\n if (!authKey || !domain) {\n throw new Error('some connection paramter missing.')\n }\n\n const domainOwner = await getRepository(User).findOne({\n where: {\n id: connection.domain.owner\n }\n })\n\n this.context = {\n domain: connection.domain,\n user: domainOwner\n /* TODO: domainOwner 대신 특정 유저를 지정할 수 있도록 개선해야함. 모든 커넥션에 유저를 지정하는 기능으로 일반화할 필요가 있는 지 고민해야함 */\n }\n\n const httpLink = createHttpLink({\n uri: uri\n })\n\n /* \n CHECKPOINT: \n 1. GraphqQLWsLink를 사용하면 setContext를 통한 추가 헤더 설정이 무시됩니다.\n 따라서, GraphQLWsLink를 사용하려면, connectionParams를 통해 헤더를 설정해야 합니다.\n \n 2. 서버에서 실행시, webSocketImpl을 명시적으로 지정해야 합니다.\n */\n const wsLink = new GraphQLWsLink(\n createClient({\n url: uri.replace(/^http/, 'ws'),\n keepAlive: 10_000,\n retryAttempts: 1_000_000,\n shouldRetry: e => true,\n webSocketImpl: WebSocket,\n connectionParams: {\n headers: {\n 'x-things-factory-domain': domain,\n authorization: authKey ? `Bearer ${authKey}` : ''\n }\n }\n })\n )\n\n const splitLink = split(\n ({ query }) => {\n const def = getMainDefinition(query)\n return def.kind === 'OperationDefinition' && def.operation === 'subscription'\n },\n wsLink,\n setContext((_, { headers }) => {\n return {\n headers: {\n ...headers,\n 'x-things-factory-domain': domain,\n authorization: authKey ? `Bearer ${authKey}` : ''\n }\n }\n }).concat(httpLink)\n )\n\n const cache = new InMemoryCache()\n\n const client = new ApolloClient({\n defaultOptions,\n cache,\n link: splitLink\n })\n\n const subscriptions: SubscriberData[] = []\n const tags = Object.keys(subscriptionHandlers).filter(tag => tag && subscriptionHandlers[tag])\n\n /* 시나리오 조회 및 subscription 등록을 순차적으로 처리 */\n for (const tag of tags) {\n const scenarioName = subscriptionHandlers[tag]\n\n // fetch a scenario\n const selectedScenario = await getRepository(Scenario).findOne({\n where: {\n name: scenarioName\n },\n relations: ['steps', 'domain']\n })\n\n /* 시나리오가 DB에 없으면 경고 로그를 남기고 건너뜀 */\n if (!selectedScenario) {\n ConnectionManager.logger.warn(\n `(${connection.name}) scenario not found for tag \"${tag}\" (scenarioName: \"${scenarioName}\") - skipping subscription`\n )\n continue\n }\n\n const subscription = client.subscribe({\n query: gql`\n subscription {\n data(tag: \"${tag}\") {\n tag\n data\n }\n }\n `\n })\n\n const subscriptionObserver = subscription.subscribe({\n next: async data => {\n /* subscription 수신 시 에러가 발생해도 프로세스가 죽지 않도록 catch 처리 */\n try {\n debug('received pubsub msg.:', data?.data)\n await this.runScenario(subscriptions, data?.data?.data)\n } catch (e) {\n ConnectionManager.logger.error(`(${connection.name}:${tag}) runScenario failed`, e)\n }\n },\n error: error => {\n ConnectionManager.logger.error(`(${connection.name}:${connection.endpoint}) subscription error`, error)\n },\n complete: () => {\n ConnectionManager.logger.info(`(${connection.name}:${connection.endpoint}) subscription complete`)\n }\n })\n\n ConnectionManager.logger.info(\n `(${connection.name}:${connection.endpoint}) subscription closed flag: ${subscriptionObserver.closed}`\n )\n\n subscriptions.push({\n tag,\n scenario: selectedScenario,\n subscriptionObserver\n })\n ConnectionManager.logger.info(`(${tag}:${scenarioName}) subscription closed flag: ${subscriptionObserver.closed}`)\n }\n\n client['subscriptions'] = subscriptions\n ConnectionManager.addConnectionInstance(connection, client)\n\n ConnectionManager.logger.info(\n `operato-connector connection(${connection.name}:${connection.endpoint}) is connected`\n )\n }\n\n async disconnect(connection: InputConnection) {\n const client = ConnectionManager.getConnectionInstance(connection)\n const subscriptions: SubscriberData[] = client['subscriptions']\n subscriptions.forEach(subscription => subscription.subscriptionObserver.unsubscribe())\n client.stop()\n ConnectionManager.removeConnectionInstance(connection)\n\n ConnectionManager.logger.info(`operato-connector connection(${connection.name}) is disconnected`)\n }\n\n async runScenario(subscriptions: SubscriberData[], variables: any): Promise<ScenarioInstance> {\n const { domain, user } = this.context\n const { tag } = variables\n\n if (!tag) {\n throw new Error(`tag is invalid - ${tag}`)\n }\n\n const scenario = subscriptions.find(subscription => subscription.tag === tag)?.scenario\n if (!scenario) {\n throw new Error(`scenario is not found - ${tag}`)\n }\n\n if (!(await checkUserHasRole(scenario.roleId, domain, user))) {\n throw new Error(`Unauthorized! ${scenario.name} doesn't have required role.`)\n }\n\n /* create a scenario instance */\n const instanceName = scenario.name + '-' + String(Date.now())\n const instance = new ScenarioInstance(instanceName, scenario, {\n user,\n domain,\n variables,\n client: GraphqlLocalClient.client\n })\n\n // run scenario\n await instance.run()\n return instance\n }\n\n get parameterSpec() {\n return [\n {\n type: 'string',\n name: 'authKey',\n label: 'auth-key',\n useDomainAttribute: true\n },\n {\n type: 'string',\n name: 'domain',\n label: 'domain'\n },\n {\n type: 'tag-scenarios',\n name: 'subscriptionHandlers',\n label: 'subscription-handlers'\n }\n ]\n }\n\n get taskPrefixes() {\n return ['graphql']\n }\n\n get help() {\n return 'integration/connector/operato-connector'\n }\n\n get description() {\n return 'Operato Graphql Connector'\n }\n}\n\nConnectionManager.registerConnector('operato-connector', new OperatoConnector())\n"]}
@@ -14,22 +14,32 @@ class SocketServer {
14
14
  async connect(config) {
15
15
  var em = new events_1.EventEmitter();
16
16
  var [host = '0.0.0.0', port = 8124] = config.endpoint.split(':');
17
+ var clientIPs = new Set();
17
18
  return new Promise((resolve, reject) => {
18
19
  var server = net_1.default.createServer(tcp => {
20
+ const remoteIP = `${tcp.remoteAddress}:${tcp.remotePort}`;
21
+ clientIPs.add(remoteIP);
19
22
  connection_manager_1.ConnectionManager.logger.info('Client connection: ');
20
23
  connection_manager_1.ConnectionManager.logger.info(' local = %s:%s', tcp.localAddress, tcp.localPort);
21
24
  connection_manager_1.ConnectionManager.logger.info(' remote = %s:%s', tcp.remoteAddress, tcp.remotePort);
25
+ console.log(`[socket-server] ${config.name} clients: ${clientIPs.size}\n${[...clientIPs].sort().map(ip => ` - ${ip}`).join('\n')}`);
22
26
  tcp.on('data', (data) => {
23
27
  em['__tcp__'] = tcp;
24
28
  var message = data.toString();
25
29
  message = message.replace('SB#', '');
26
30
  message = message.replace(/(\r\n|\n|\r)/gm, '');
27
- em.emit('socket-message-arrive', message);
31
+ // IP 정보를 함께 전달한다
32
+ em.emit('socket-message-arrive', message, tcp.remoteAddress);
28
33
  connection_manager_1.ConnectionManager.logger.info(`socket ${message}`);
29
34
  tcp.write('ok');
30
35
  });
31
- tcp.on('end', () => {
36
+ tcp.on('close', hadError => {
32
37
  connection_manager_1.ConnectionManager.logger.info('Client disconnected');
38
+ if (hadError) {
39
+ connection_manager_1.ConnectionManager.logger.info('Client disconnected with error: ' + hadError);
40
+ }
41
+ clientIPs.delete(remoteIP);
42
+ console.log(`[socket-server] ${config.name} clients: ${clientIPs.size}\n${[...clientIPs].sort().map(ip => ` - ${ip}`).join('\n')}`);
33
43
  });
34
44
  tcp.on('error', ex => {
35
45
  connection_manager_1.ConnectionManager.logger.info('Server socket error: ');