@things-factory/integration-base 10.0.0-beta.9 → 10.0.0-beta.92

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.
@@ -9,18 +9,20 @@ const auth_base_1 = require("@things-factory/auth-base");
9
9
  const cache_service_1 = require("@things-factory/cache-service");
10
10
  const scenario_engine_1 = require("../engine/scenario-engine");
11
11
  const scenario_1 = require("../service/scenario/scenario");
12
+ const domain_inheritance_1 = require("../utils/domain-inheritance");
12
13
  const debug = require('debug')('things-factory:integration-base:controller:run-scenario');
13
14
  async function findScenario(scenarioName, domain) {
14
15
  var repository = (0, shell_1.getRepository)(scenario_1.Scenario);
15
- var scenario = await repository.findOne({
16
- where: { domain: { id: domain.id }, name: scenarioName },
17
- relations: ['domain', 'steps', 'role', 'creator', 'updater']
18
- });
19
- if (!scenario && domain.parentId) {
16
+ // ancestor 도메인을 closest-first 로 순회하여 first hit 사용
17
+ const domainIds = await (0, domain_inheritance_1.getDomainIdsWithAncestors)(domain);
18
+ var scenario = null;
19
+ for (const did of domainIds) {
20
20
  scenario = await repository.findOne({
21
- where: { domain: { id: domain.parentId }, name: scenarioName },
21
+ where: { domain: { id: did }, name: scenarioName },
22
22
  relations: ['domain', 'steps', 'role', 'creator', 'updater']
23
23
  });
24
+ if (scenario)
25
+ break;
24
26
  }
25
27
  return scenario;
26
28
  }
@@ -1 +1 @@
1
- {"version":3,"file":"scenario-controller.js","sourceRoot":"","sources":["../../server/controllers/scenario-controller.ts"],"names":[],"mappings":"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","sourcesContent":["import { getRepository, Domain, GraphqlLocalClient } from '@things-factory/shell'\nimport { checkUserHasRole } from '@things-factory/auth-base'\nimport { cacheService } from '@things-factory/cache-service'\n\nimport { ScenarioEngine } from '../engine/scenario-engine'\nimport { Scenario } from '../service/scenario/scenario'\nimport {\n ScenarioInstance,\n ScenarioInstanceRunResult,\n ScenarioInstanceStartResult\n} from '../service/scenario-instance/scenario-instance-type'\nimport { Step } from '../service/step/step-type'\n\nconst debug = require('debug')('things-factory:integration-base:controller:run-scenario')\n\nasync function findScenario(\n scenarioName: string,\n domain: Domain\n): Promise<{\n id: string\n ttl: number\n name: string\n steps: Step[]\n domain: Domain\n}> {\n var repository = getRepository(Scenario)\n\n var scenario = await repository.findOne({\n where: { domain: { id: domain.id }, name: scenarioName },\n relations: ['domain', 'steps', 'role', 'creator', 'updater']\n })\n\n if (!scenario && domain.parentId) {\n scenario = await repository.findOne({\n where: { domain: { id: domain.parentId }, name: scenarioName },\n relations: ['domain', 'steps', 'role', 'creator', 'updater']\n })\n }\n\n return scenario as any\n}\n\nexport async function checkHasRole(scenario: Partial<Scenario>, context: ResolverContext): Promise<void> {\n const { domain, user } = context.state\n if (!(await checkUserHasRole(scenario.roleId, domain, user))) {\n throw new Error(\n context.t('error.scenario run unauthorized', {\n scenario: scenario.name\n })\n )\n }\n}\n\nexport async function runScenario(\n instanceName: string,\n scenarioName: string,\n variables: any,\n context: ResolverContext\n): Promise<ScenarioInstanceRunResult> {\n const { domain, user, lng } = context.state\n\n debug('runScenario', scenarioName, instanceName, variables)\n\n var scenario = await findScenario(scenarioName, domain)\n\n if (!scenario) {\n throw new Error(\n context.t('error.scenario not found', {\n scenario: scenarioName\n })\n )\n }\n\n await checkHasRole(scenario, context)\n\n if (scenario.ttl > 0) {\n const cachedValue = await cacheService.getFromCache(scenario.id, { domain: domain.id, variables: variables || {} })\n if (cachedValue) {\n return cachedValue.value\n }\n }\n\n /* 시나리오 인스턴스를 생성한다. */\n instanceName = instanceName || scenarioName + '-' + String(Date.now())\n\n try {\n return await ScenarioEngine.run(instanceName, scenario, { domain, user, lng, variables })\n } catch (err) {\n console.error(err)\n\n throw new Error(\n context.t('error.scenario run error', {\n scenario: scenarioName\n })\n )\n }\n}\n\nexport async function startScenario(\n instanceName: string,\n scenarioName: string,\n variables: any,\n context: ResolverContext\n): Promise<ScenarioInstanceStartResult> {\n const { domain, user, lng } = context.state\n\n debug('startScenario', instanceName, scenarioName, variables)\n\n var scenario = await findScenario(scenarioName, domain)\n\n if (!scenario) {\n throw new Error(\n context.t('error.scenario not found', {\n scenario: scenarioName\n })\n )\n }\n\n await checkHasRole(scenario, context)\n\n instanceName = instanceName || scenarioName\n return await ScenarioEngine.loadIteration(instanceName, scenario, { domain, user, lng, variables })\n}\n\nexport async function stopScenario(\n instanceName: string,\n context: ResolverContext\n): Promise<ScenarioInstance | undefined> {\n const { domain, user, unsafeIP, prohibitedPrivileges } = context.state\n\n debug('stopScenario', instanceName)\n\n var scenarioInstance = ScenarioEngine.getScenarioInstance(domain, instanceName)\n\n if (!scenarioInstance) {\n debug('stopScenario', `ScenarioInstance(${instanceName}) Not Found.`)\n throw new Error(\n context.t('error.scenario instance not found', {\n instance: instanceName\n })\n )\n }\n\n var scenario = await findScenario(scenarioInstance.scenarioName, domain)\n\n await checkHasRole(scenario, context)\n\n await ScenarioEngine.unload(domain, instanceName)\n\n return scenarioInstance\n}\n"]}
1
+ {"version":3,"file":"scenario-controller.js","sourceRoot":"","sources":["../../server/controllers/scenario-controller.ts"],"names":[],"mappings":"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","sourcesContent":["import { getRepository, Domain, GraphqlLocalClient } from '@things-factory/shell'\nimport { checkUserHasRole } from '@things-factory/auth-base'\nimport { cacheService } from '@things-factory/cache-service'\n\nimport { ScenarioEngine } from '../engine/scenario-engine'\nimport { Scenario } from '../service/scenario/scenario'\nimport { getDomainIdsWithAncestors } from '../utils/domain-inheritance'\nimport {\n ScenarioInstance,\n ScenarioInstanceRunResult,\n ScenarioInstanceStartResult\n} from '../service/scenario-instance/scenario-instance-type'\nimport { Step } from '../service/step/step-type'\n\nconst debug = require('debug')('things-factory:integration-base:controller:run-scenario')\n\nasync function findScenario(\n scenarioName: string,\n domain: Domain\n): Promise<{\n id: string\n ttl: number\n name: string\n steps: Step[]\n domain: Domain\n}> {\n var repository = getRepository(Scenario)\n\n // ancestor 도메인을 closest-first 순회하여 first hit 사용\n const domainIds = await getDomainIdsWithAncestors(domain)\n var scenario: Scenario | null = null\n for (const did of domainIds) {\n scenario = await repository.findOne({\n where: { domain: { id: did }, name: scenarioName },\n relations: ['domain', 'steps', 'role', 'creator', 'updater']\n })\n if (scenario) break\n }\n\n return scenario as any\n}\n\nexport async function checkHasRole(scenario: Partial<Scenario>, context: ResolverContext): Promise<void> {\n const { domain, user } = context.state\n if (!(await checkUserHasRole(scenario.roleId, domain, user))) {\n throw new Error(\n context.t('error.scenario run unauthorized', {\n scenario: scenario.name\n })\n )\n }\n}\n\nexport async function runScenario(\n instanceName: string,\n scenarioName: string,\n variables: any,\n context: ResolverContext\n): Promise<ScenarioInstanceRunResult> {\n const { domain, user, lng } = context.state\n\n debug('runScenario', scenarioName, instanceName, variables)\n\n var scenario = await findScenario(scenarioName, domain)\n\n if (!scenario) {\n throw new Error(\n context.t('error.scenario not found', {\n scenario: scenarioName\n })\n )\n }\n\n await checkHasRole(scenario, context)\n\n if (scenario.ttl > 0) {\n const cachedValue = await cacheService.getFromCache(scenario.id, { domain: domain.id, variables: variables || {} })\n if (cachedValue) {\n return cachedValue.value\n }\n }\n\n /* 시나리오 인스턴스를 생성한다. */\n instanceName = instanceName || scenarioName + '-' + String(Date.now())\n\n try {\n return await ScenarioEngine.run(instanceName, scenario, { domain, user, lng, variables })\n } catch (err) {\n console.error(err)\n\n throw new Error(\n context.t('error.scenario run error', {\n scenario: scenarioName\n })\n )\n }\n}\n\nexport async function startScenario(\n instanceName: string,\n scenarioName: string,\n variables: any,\n context: ResolverContext\n): Promise<ScenarioInstanceStartResult> {\n const { domain, user, lng } = context.state\n\n debug('startScenario', instanceName, scenarioName, variables)\n\n var scenario = await findScenario(scenarioName, domain)\n\n if (!scenario) {\n throw new Error(\n context.t('error.scenario not found', {\n scenario: scenarioName\n })\n )\n }\n\n await checkHasRole(scenario, context)\n\n instanceName = instanceName || scenarioName\n return await ScenarioEngine.loadIteration(instanceName, scenario, { domain, user, lng, variables })\n}\n\nexport async function stopScenario(\n instanceName: string,\n context: ResolverContext\n): Promise<ScenarioInstance | undefined> {\n const { domain, user, unsafeIP, prohibitedPrivileges } = context.state\n\n debug('stopScenario', instanceName)\n\n var scenarioInstance = ScenarioEngine.getScenarioInstance(domain, instanceName)\n\n if (!scenarioInstance) {\n debug('stopScenario', `ScenarioInstance(${instanceName}) Not Found.`)\n throw new Error(\n context.t('error.scenario instance not found', {\n instance: instanceName\n })\n )\n }\n\n var scenario = await findScenario(scenarioInstance.scenarioName, domain)\n\n await checkHasRole(scenario, context)\n\n await ScenarioEngine.unload(domain, instanceName)\n\n return scenarioInstance\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: ');
@@ -1 +1 @@
1
- {"version":3,"file":"socket-server.js","sourceRoot":"","sources":["../../../server/engine/connector/socket-server.ts"],"names":[],"mappings":"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","sourcesContent":["import net from 'net'\nimport { EventEmitter } from 'events'\n\nimport { Connector } from '../types'\nimport { ConnectionManager } from '../connection-manager'\nimport { InputConnection } from '../../service/connection/connection-type'\n\nexport class SocketServer implements Connector {\n // socket: any\n\n async ready(connectionConfigs: InputConnection[]) {\n await Promise.all(connectionConfigs.map(this.connect))\n ConnectionManager.logger.info('socket servers are ready')\n }\n\n async connect(config: InputConnection): Promise<void> {\n var em = new EventEmitter()\n var [host = '0.0.0.0', port = 8124] = config.endpoint.split(':')\n return new Promise((resolve, reject) => {\n var server = net.createServer(tcp => {\n ConnectionManager.logger.info('Client connection: ')\n ConnectionManager.logger.info(' local = %s:%s', tcp.localAddress, tcp.localPort)\n ConnectionManager.logger.info(' remote = %s:%s', tcp.remoteAddress, tcp.remotePort)\n tcp.on('data', (data: any) => {\n em['__tcp__'] = tcp\n var message = data.toString()\n message = message.replace('SB#', '')\n message = message.replace(/(\\r\\n|\\n|\\r)/gm, '')\n em.emit('socket-message-arrive', message)\n ConnectionManager.logger.info(`socket ${message}`)\n tcp.write('ok')\n })\n\n tcp.on('end', () => {\n ConnectionManager.logger.info('Client disconnected')\n })\n\n tcp.on('error', ex => {\n ConnectionManager.logger.info('Server socket error: ')\n var logger = ConnectionManager.logger\n let message = ex.stack ? ex.stack : ex\n let msg = typeof message == 'object' ? JSON.stringify(message, null, 2) : message\n logger.error(msg, ex)\n reject(ex)\n })\n })\n\n server.listen(port, async () => {\n ConnectionManager.logger.info(`tcp echo server listening on ${host}:${port}`)\n\n /* default client connection */\n try {\n em['__server__'] = server\n ConnectionManager.addConnectionInstance(config, em)\n ConnectionManager.logger.info(`socket server connection(${config.name}:${config.endpoint}) is connected`)\n\n resolve()\n } catch (err) {\n ConnectionManager.logger.error(err)\n reject(err)\n }\n })\n })\n }\n\n async disconnect(connection: InputConnection) {\n let em = ConnectionManager.removeConnectionInstance(connection)\n var tcp = em['__tcp__']\n var server = em['__server__']\n\n tcp && (await tcp.destroy())\n server && (await server.close())\n\n ConnectionManager.logger.info(`socket server connection(${connection.name}) is disconnected`)\n }\n\n get parameterSpec() {\n return []\n }\n\n get taskPrefixes() {\n return ['socket']\n }\n}\n\nConnectionManager.registerConnector('socket-server', new SocketServer())\n"]}
1
+ {"version":3,"file":"socket-server.js","sourceRoot":"","sources":["../../../server/engine/connector/socket-server.ts"],"names":[],"mappings":"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","sourcesContent":["import net from 'net'\nimport { EventEmitter } from 'events'\n\nimport { Connector } from '../types'\nimport { ConnectionManager } from '../connection-manager'\nimport { InputConnection } from '../../service/connection/connection-type'\n\nexport class SocketServer implements Connector {\n // socket: any\n\n async ready(connectionConfigs: InputConnection[]) {\n await Promise.all(connectionConfigs.map(this.connect))\n ConnectionManager.logger.info('socket servers are ready')\n }\n\n async connect(config: InputConnection): Promise<void> {\n var em = new EventEmitter()\n var [host = '0.0.0.0', port = 8124] = config.endpoint.split(':')\n var clientIPs = new Set<string>()\n return new Promise((resolve, reject) => {\n var server = net.createServer(tcp => {\n const remoteIP = `${tcp.remoteAddress}:${tcp.remotePort}`\n clientIPs.add(remoteIP)\n ConnectionManager.logger.info('Client connection: ')\n ConnectionManager.logger.info(' local = %s:%s', tcp.localAddress, tcp.localPort)\n ConnectionManager.logger.info(' remote = %s:%s', tcp.remoteAddress, tcp.remotePort)\n console.log(\n `[socket-server] ${config.name} clients: ${clientIPs.size}\\n${[...clientIPs].sort().map(ip => ` - ${ip}`).join('\\n')}`\n )\n tcp.on('data', (data: any) => {\n em['__tcp__'] = tcp\n var message = data.toString()\n message = message.replace('SB#', '')\n message = message.replace(/(\\r\\n|\\n|\\r)/gm, '')\n // IP 정보를 함께 전달한다\n em.emit('socket-message-arrive', message, tcp.remoteAddress)\n ConnectionManager.logger.info(`socket ${message}`)\n tcp.write('ok')\n })\n\n tcp.on('close', hadError => {\n ConnectionManager.logger.info('Client disconnected')\n if (hadError) {\n ConnectionManager.logger.info('Client disconnected with error: ' + hadError)\n }\n clientIPs.delete(remoteIP)\n console.log(\n `[socket-server] ${config.name} clients: ${clientIPs.size}\\n${[...clientIPs].sort().map(ip => ` - ${ip}`).join('\\n')}`\n )\n })\n\n tcp.on('error', ex => {\n ConnectionManager.logger.info('Server socket error: ')\n var logger = ConnectionManager.logger\n let message = ex.stack ? ex.stack : ex\n let msg = typeof message == 'object' ? JSON.stringify(message, null, 2) : message\n logger.error(msg, ex)\n reject(ex)\n })\n })\n\n server.listen(port, async () => {\n ConnectionManager.logger.info(`tcp echo server listening on ${host}:${port}`)\n\n /* default client connection */\n try {\n em['__server__'] = server\n ConnectionManager.addConnectionInstance(config, em)\n ConnectionManager.logger.info(`socket server connection(${config.name}:${config.endpoint}) is connected`)\n\n resolve()\n } catch (err) {\n ConnectionManager.logger.error(err)\n reject(err)\n }\n })\n })\n }\n\n async disconnect(connection: InputConnection) {\n let em = ConnectionManager.removeConnectionInstance(connection)\n var tcp = em['__tcp__']\n var server = em['__server__']\n\n tcp && (await tcp.destroy())\n server && (await server.close())\n\n ConnectionManager.logger.info(`socket server connection(${connection.name}) is disconnected`)\n }\n\n get parameterSpec() {\n return []\n }\n\n get taskPrefixes() {\n return ['socket']\n }\n}\n\nConnectionManager.registerConnector('socket-server', new SocketServer())\n"]}
@@ -3,19 +3,29 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const utils_1 = require("@things-factory/utils");
4
4
  const task_registry_1 = require("../task-registry");
5
5
  const connection_manager_1 = require("../connection-manager");
6
- function convertDataFormat(data, format) {
6
+ const WITH_IP_FORMATS = ['string-with-ip', 'json-with-ip'];
7
+ function convertDataFormat(data, format, ip) {
8
+ let converted;
7
9
  try {
8
- if (format == 'json') {
9
- return JSON.parse(data);
10
+ const baseFormat = format.replace('-with-ip', '');
11
+ if (baseFormat == 'json') {
12
+ converted = JSON.parse(data);
10
13
  }
11
- else if (format == 'csv') {
12
- return data.split(',');
14
+ else if (baseFormat == 'csv') {
15
+ converted = data.split(',');
16
+ }
17
+ else {
18
+ converted = data;
13
19
  }
14
20
  }
15
21
  catch (e) {
16
- return data.toString();
22
+ converted = data.toString();
23
+ }
24
+ // IP 포함 포맷이면 { data, ip } 형태로 반환한다
25
+ if (WITH_IP_FORMATS.includes(format) && ip) {
26
+ return { data: converted, ip };
17
27
  }
18
- return data;
28
+ return converted;
19
29
  }
20
30
  async function SocketListener(step, context) {
21
31
  //get connection data
@@ -45,13 +55,15 @@ async function SocketListener(step, context) {
45
55
  message
46
56
  };
47
57
  };
48
- await connection.addListener('socket-message-arrive', async (message) => {
58
+ await connection.addListener('socket-message-arrive', async (message, ip) => {
49
59
  logger.info(message.toString());
50
- MESSAGES.push(convertDataFormat(message.toString(), dataFormat));
60
+ MESSAGES.push(convertDataFormat(message.toString(), dataFormat, ip));
51
61
  });
52
62
  }
53
63
  catch (e) {
54
64
  logger.error(e);
65
+ delete context.__socket_listener[name];
66
+ return { data: null };
55
67
  }
56
68
  }
57
69
  var { message } = await context.__socket_listener[name]();
@@ -77,6 +89,14 @@ SocketListener.parameterSpec = [
77
89
  {
78
90
  display: 'String',
79
91
  value: 'string'
92
+ },
93
+ {
94
+ display: 'String with IP',
95
+ value: 'string-with-ip'
96
+ },
97
+ {
98
+ display: 'JSON with IP',
99
+ value: 'json-with-ip'
80
100
  }
81
101
  ]
82
102
  }
@@ -1 +1 @@
1
- {"version":3,"file":"socket-listener.js","sourceRoot":"","sources":["../../../server/engine/task/socket-listener.ts"],"names":[],"mappings":"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","sourcesContent":["import { sleep } from '@things-factory/utils'\nimport { TaskRegistry } from '../task-registry'\nimport { ConnectionManager } from '../connection-manager'\nimport { InputStep } from '../../service/step/step-type'\nimport { Context } from '../types'\n\nfunction convertDataFormat(data, format) {\n try {\n if (format == 'json') {\n return JSON.parse(data)\n } else if (format == 'csv') {\n return data.split(',')\n }\n } catch (e) {\n return data.toString()\n }\n\n return data\n}\n\nasync function SocketListener(step: InputStep, context: Context) {\n //get connection data\n const {\n connection: connectionName,\n params: { dataFormat = 'JSON' },\n name\n } = step\n\n const { domain, logger, closures, __socket_listener } = context\n logger.info('\\r\\n')\n if (!__socket_listener) {\n context.__socket_listener = {}\n }\n\n // get socket information from connection\n var connection = await ConnectionManager.getConnectionInstanceByName(domain, connectionName)\n if (!connection) {\n throw new Error(`connector '${connectionName}' is not established.`)\n }\n\n /*\n * 1. listner list에서 listner를 찾는다. 없으면, 생성한다.\n */\n if (!context.__socket_listener[name]) {\n try {\n var MESSAGES = []\n context.__socket_listener[name] = async () => {\n while (MESSAGES.length == 0) {\n await sleep(100)\n }\n var message = MESSAGES.shift()\n return {\n message\n }\n }\n\n await connection.addListener('socket-message-arrive', async message => {\n logger.info(message.toString())\n MESSAGES.push(convertDataFormat(message.toString(), dataFormat))\n })\n } catch (e) {\n logger.error(e)\n }\n }\n\n var { message } = await context.__socket_listener[name]()\n\n return {\n data: message\n }\n}\n\nSocketListener.parameterSpec = [\n {\n type: 'select',\n label: 'data-format',\n name: 'dataFormat',\n property: {\n options: [\n {\n display: 'CSV',\n value: 'csv'\n },\n {\n display: 'JSON',\n value: 'json'\n },\n {\n display: 'String',\n value: 'string'\n }\n ]\n }\n }\n]\nTaskRegistry.registerTaskHandler('socket-listener', SocketListener)\n"]}
1
+ {"version":3,"file":"socket-listener.js","sourceRoot":"","sources":["../../../server/engine/task/socket-listener.ts"],"names":[],"mappings":"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","sourcesContent":["import { sleep } from '@things-factory/utils'\nimport { TaskRegistry } from '../task-registry'\nimport { ConnectionManager } from '../connection-manager'\nimport { InputStep } from '../../service/step/step-type'\nimport { Context } from '../types'\n\nconst WITH_IP_FORMATS = ['string-with-ip', 'json-with-ip']\n\nfunction convertDataFormat(data: string, format: string, ip?: string) {\n let converted: any\n try {\n const baseFormat = format.replace('-with-ip', '')\n if (baseFormat == 'json') {\n converted = JSON.parse(data)\n } else if (baseFormat == 'csv') {\n converted = data.split(',')\n } else {\n converted = data\n }\n } catch (e) {\n converted = data.toString()\n }\n\n // IP 포함 포맷이면 { data, ip } 형태로 반환한다\n if (WITH_IP_FORMATS.includes(format) && ip) {\n return { data: converted, ip }\n }\n\n return converted\n}\n\nasync function SocketListener(step: InputStep, context: Context) {\n //get connection data\n const {\n connection: connectionName,\n params: { dataFormat = 'JSON' },\n name\n } = step\n\n const { domain, logger, closures, __socket_listener } = context\n logger.info('\\r\\n')\n if (!__socket_listener) {\n context.__socket_listener = {}\n }\n\n // get socket information from connection\n var connection = await ConnectionManager.getConnectionInstanceByName(domain, connectionName)\n if (!connection) {\n throw new Error(`connector '${connectionName}' is not established.`)\n }\n\n /*\n * 1. listner list에서 listner를 찾는다. 없으면, 생성한다.\n */\n if (!context.__socket_listener[name]) {\n try {\n var MESSAGES = []\n context.__socket_listener[name] = async () => {\n while (MESSAGES.length == 0) {\n await sleep(100)\n }\n var message = MESSAGES.shift()\n return {\n message\n }\n }\n\n await connection.addListener('socket-message-arrive', async (message: string, ip: string) => {\n logger.info(message.toString())\n MESSAGES.push(convertDataFormat(message.toString(), dataFormat, ip))\n })\n } catch (e) {\n logger.error(e)\n delete context.__socket_listener[name]\n return { data: null }\n }\n }\n\n var { message } = await context.__socket_listener[name]()\n\n return {\n data: message\n }\n}\n\nSocketListener.parameterSpec = [\n {\n type: 'select',\n label: 'data-format',\n name: 'dataFormat',\n property: {\n options: [\n {\n display: 'CSV',\n value: 'csv'\n },\n {\n display: 'JSON',\n value: 'json'\n },\n {\n display: 'String',\n value: 'string'\n },\n {\n display: 'String with IP',\n value: 'string-with-ip'\n },\n {\n display: 'JSON with IP',\n value: 'json-with-ip'\n }\n ]\n }\n }\n]\nTaskRegistry.registerTaskHandler('socket-listener', SocketListener)\n"]}
@@ -72,6 +72,12 @@ async function executeHeadlessRequestWithRecovery(connectionName, options, conte
72
72
  .catch(() => logger.warn('Page readyState timeout'));
73
73
  // JavaScript 초기화를 위한 추가 대기 (핵심 해결책)
74
74
  await new Promise(resolve => setTimeout(resolve, 2000));
75
+ // 재연결 후 페이지가 올바른 도메인에 있는지 확인
76
+ const resetPageUrl = page.url();
77
+ if (!resetPageUrl.startsWith(targetDomain)) {
78
+ logger.info(`Navigating reset page to target domain: ${targetDomain} (current: ${resetPageUrl})`);
79
+ await page.goto(targetDomain, { waitUntil: 'networkidle2' });
80
+ }
75
81
  logger.info(`Full connection reset successful for connection '${connectionName}'`);
76
82
  }
77
83
  catch (resetError) {
@@ -120,6 +126,7 @@ async function executeHeadlessRequestWithRecovery(connectionName, options, conte
120
126
  requestOptions.body = typeof requestBody === 'string' ? requestBody : JSON.stringify(requestBody);
121
127
  }
122
128
  }
129
+ logger.info(`[Fetch] Target: ${url.toString()}, Page: ${page.url()}`);
123
130
  const response = await page.evaluate(async (urlString, opts, loginPagePath) => {
124
131
  try {
125
132
  // 페이지 상태 검증 (about:blank 또는 로딩 중인 페이지에서 fetch 방지)
@@ -136,44 +143,30 @@ async function executeHeadlessRequestWithRecovery(connectionName, options, conte
136
143
  networkError: true
137
144
  };
138
145
  }
139
- const response = await fetch(urlString, { ...opts, redirect: 'manual' });
146
+ const response = await fetch(urlString, opts);
140
147
  const result = {
141
148
  ok: response.ok,
142
149
  status: response.status,
143
150
  statusText: response.statusText,
144
151
  headers: Object.fromEntries(response.headers.entries())
145
152
  };
146
- // 302 리디렉션 감지 - 로그인 페이지로의 리디렉션 체크
147
- if ([301, 302, 307, 308].includes(response.status)) {
148
- const location = response.headers.get('location') || '';
149
- // connection의 loginPagePath와 일반적인 로그인 경로들을 체크
150
- if (location.includes(loginPagePath) ||
151
- location.includes('/login') ||
152
- location.includes('/signin') ||
153
- location.includes('/auth')) {
153
+ // 리디렉션 감지 - 로그인 페이지로의 리디렉션 체크
154
+ // 브라우저 Fetch API에서 redirect: 'manual'은 opaque response(status=0) 반환하므로
155
+ // redirect: 'follow'(기본값) 사용하고 response.redirected로 감지
156
+ if (response.redirected) {
157
+ const finalUrl = response.url || '';
158
+ if (finalUrl.includes(loginPagePath) ||
159
+ finalUrl.includes('/login') ||
160
+ finalUrl.includes('/signin') ||
161
+ finalUrl.includes('/auth')) {
154
162
  return {
155
163
  ...result,
156
- error: `Redirected to login page: ${location}`,
164
+ error: `Redirected to login page: ${finalUrl}`,
157
165
  redirectedToLogin: true,
158
- location,
166
+ location: finalUrl,
159
167
  data: null
160
168
  };
161
169
  }
162
- // 다른 리디렉션은 follow
163
- const redirectResponse = await fetch(urlString, opts);
164
- return {
165
- ok: redirectResponse.ok,
166
- status: redirectResponse.status,
167
- statusText: redirectResponse.statusText,
168
- headers: Object.fromEntries(redirectResponse.headers.entries()),
169
- data: redirectResponse.ok
170
- ? await redirectResponse
171
- .json()
172
- .catch(() => redirectResponse.text())
173
- .catch(() => null)
174
- : null,
175
- error: redirectResponse.ok ? null : `HTTP ${redirectResponse.status}: ${redirectResponse.statusText}`
176
- };
177
170
  }
178
171
  // status가 0인 경우는 네트워크 레벨 에러 (CORS, 연결 실패 등)
179
172
  if (response.status === 0) {
@@ -257,6 +250,11 @@ async function executeHeadlessRequestWithRecovery(connectionName, options, conte
257
250
  if (resetUrl === 'about:blank') {
258
251
  throw new Error('Page stuck on about:blank after reset');
259
252
  }
253
+ // 재연결 후 페이지가 올바른 도메인에 있는지 확인
254
+ if (!resetUrl.startsWith(targetDomain)) {
255
+ logger.info(`Navigating reset page to target domain: ${targetDomain} (current: ${resetUrl})`);
256
+ await page.goto(targetDomain, { waitUntil: 'networkidle2' });
257
+ }
260
258
  }
261
259
  catch (resetError) {
262
260
  logger.error(`Connection reset failed after login redirect for connection '${connectionName}':`, resetError);
@@ -295,6 +293,11 @@ async function executeHeadlessRequestWithRecovery(connectionName, options, conte
295
293
  if (resetUrl === 'about:blank') {
296
294
  throw new Error('Page stuck on about:blank after reset');
297
295
  }
296
+ // 재연결 후 페이지가 올바른 도메인에 있는지 확인
297
+ if (!resetUrl.startsWith(targetDomain)) {
298
+ logger.info(`Navigating reset page to target domain: ${targetDomain} (current: ${resetUrl})`);
299
+ await page.goto(targetDomain, { waitUntil: 'networkidle2' });
300
+ }
298
301
  }
299
302
  catch (resetError) {
300
303
  logger.error(`Connection reset failed after session timeout for connection '${connectionName}':`, resetError);