@things-factory/integration-base 10.0.0-beta.92 → 10.0.0-beta.95
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-server/engine/connection-manager.d.ts +19 -0
- package/dist-server/engine/connection-manager.js +148 -16
- package/dist-server/engine/connection-manager.js.map +1 -1
- package/dist-server/engine/connector/headless-connector.d.ts +12 -0
- package/dist-server/engine/connector/headless-connector.js +74 -31
- package/dist-server/engine/connector/headless-connector.js.map +1 -1
- package/dist-server/engine/evaluate-template.d.ts +22 -3
- package/dist-server/engine/evaluate-template.js +43 -4
- package/dist-server/engine/evaluate-template.js.map +1 -1
- package/dist-server/engine/task/script.js +25 -17
- package/dist-server/engine/task/script.js.map +1 -1
- package/dist-server/engine/types.d.ts +32 -0
- package/dist-server/engine/types.js.map +1 -1
- package/dist-server/service/connection/connection-mutation.d.ts +3 -0
- package/dist-server/service/connection/connection-type.d.ts +4 -1
- package/dist-server/service/connection/connection-type.js +21 -0
- package/dist-server/service/connection/connection-type.js.map +1 -1
- package/dist-server/service/connection/connection.d.ts +80 -2
- package/dist-server/service/connection/connection.js +59 -15
- package/dist-server/service/connection/connection.js.map +1 -1
- package/dist-server/service/domain-attribute/domain-attribute-query.d.ts +17 -0
- package/dist-server/service/domain-attribute/domain-attribute-query.js +76 -0
- package/dist-server/service/domain-attribute/domain-attribute-query.js.map +1 -0
- package/dist-server/service/domain-attribute/domain-attribute-type.d.ts +15 -0
- package/dist-server/service/domain-attribute/domain-attribute-type.js +46 -0
- package/dist-server/service/domain-attribute/domain-attribute-type.js.map +1 -0
- package/dist-server/service/domain-attribute/index.d.ts +3 -0
- package/dist-server/service/domain-attribute/index.js +8 -0
- package/dist-server/service/domain-attribute/index.js.map +1 -0
- package/dist-server/service/index.d.ts +1 -1
- package/dist-server/service/index.js +3 -1
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/scenario/scenario-mutation.js +10 -0
- package/dist-server/service/scenario/scenario-mutation.js.map +1 -1
- package/dist-server/service/scenario/scenario-query.d.ts +3 -3
- package/dist-server/service/scenario/scenario-query.js +9 -9
- package/dist-server/service/scenario/scenario-query.js.map +1 -1
- package/dist-server/service/scenario-instance/scenario-instance-type.js +79 -0
- package/dist-server/service/scenario-instance/scenario-instance-type.js.map +1 -1
- package/dist-server/service/step/step-mutation.js +15 -0
- package/dist-server/service/step/step-mutation.js.map +1 -1
- package/dist-server/service/step/step-query.js +11 -2
- package/dist-server/service/step/step-query.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/dist-server/utils/domain-inheritance.d.ts +27 -0
- package/dist-server/utils/domain-inheritance.js +67 -0
- package/dist-server/utils/domain-inheritance.js.map +1 -1
- package/package.json +2 -2
|
@@ -5,6 +5,12 @@ export declare class ConnectionManager {
|
|
|
5
5
|
private static connectors;
|
|
6
6
|
private static connections;
|
|
7
7
|
private static entities;
|
|
8
|
+
/**
|
|
9
|
+
* Session 직렬화 mutex — connection name 단위 (도메인 무관 글로벌).
|
|
10
|
+
* sessionExclusive connector 의 acquireSessionPage 동시 호출이 외부 세션을 서로
|
|
11
|
+
* invalidate 하지 않도록 큐잉. release 호출 시 다음 대기자가 진행.
|
|
12
|
+
*/
|
|
13
|
+
private static sessionLocks;
|
|
8
14
|
private static logFormat;
|
|
9
15
|
static logger: import("winston").Logger;
|
|
10
16
|
static ready(): Promise<void>;
|
|
@@ -21,6 +27,19 @@ export declare class ConnectionManager {
|
|
|
21
27
|
};
|
|
22
28
|
static getEntities(): {};
|
|
23
29
|
static getConnectionInstance(connection: Connection): any;
|
|
30
|
+
/**
|
|
31
|
+
* 세션 사용 패턴 (RAII).
|
|
32
|
+
*
|
|
33
|
+
* connector 가 sessionExclusive=true 로 선언한 경우 connection.name 단위 mutex 로
|
|
34
|
+
* 직렬화. 호출자는 acquireSessionPage / releasePage 의 lifecycle 을 신경 쓸 필요 없이
|
|
35
|
+
* `withSession(connection, async page => { ... })` 형태로 사용하면 됨.
|
|
36
|
+
*
|
|
37
|
+
* stateless connector (sessionExclusive=false) 는 mutex 없이 즉시 진행.
|
|
38
|
+
*
|
|
39
|
+
* @param connection 도메인 instance (connection.type 에서 connector 조회)
|
|
40
|
+
* @param fn page 를 받아 데이터 작업하는 async 함수
|
|
41
|
+
*/
|
|
42
|
+
static withSession<T>(connection: any, fn: (page: any) => Promise<T>): Promise<T>;
|
|
24
43
|
static getConnectionInstanceByName(domain: Domain, name: string): Promise<any>;
|
|
25
44
|
static getConnectionEntityByName(domain: Domain, name: string): Promise<Connection | null>;
|
|
26
45
|
static getConnectionInstanceEntityByName(domain: Domain, name: string): any;
|
|
@@ -6,6 +6,7 @@ const moment_timezone_1 = tslib_1.__importDefault(require("moment-timezone"));
|
|
|
6
6
|
const winston_1 = require("winston");
|
|
7
7
|
const shell_1 = require("@things-factory/shell");
|
|
8
8
|
const service_1 = require("../service");
|
|
9
|
+
const domain_inheritance_1 = require("../utils/domain-inheritance");
|
|
9
10
|
const proxy_connector_1 = require("./connector/proxy-connector");
|
|
10
11
|
const { combine, splat, printf, errors } = winston_1.format;
|
|
11
12
|
const debug = require('debug')('things-factory:integration-base:connections');
|
|
@@ -32,6 +33,12 @@ class ConnectionManager {
|
|
|
32
33
|
static { this.connectors = {}; }
|
|
33
34
|
static { this.connections = {}; }
|
|
34
35
|
static { this.entities = {}; }
|
|
36
|
+
/**
|
|
37
|
+
* Session 직렬화 mutex — connection name 단위 (도메인 무관 글로벌).
|
|
38
|
+
* sessionExclusive connector 의 acquireSessionPage 동시 호출이 외부 세션을 서로
|
|
39
|
+
* invalidate 하지 않도록 큐잉. release 호출 시 다음 대기자가 진행.
|
|
40
|
+
*/
|
|
41
|
+
static { this.sessionLocks = new Map(); }
|
|
35
42
|
static { this.logFormat = printf(({ level, message, timestamp, stack }) => {
|
|
36
43
|
return `${timestamp} ${level}: ${stack || message}`;
|
|
37
44
|
}); }
|
|
@@ -114,20 +121,106 @@ class ConnectionManager {
|
|
|
114
121
|
const { domain, name } = connection;
|
|
115
122
|
return ConnectionManager.connections[domain.id]?.[name];
|
|
116
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* 세션 사용 패턴 (RAII).
|
|
126
|
+
*
|
|
127
|
+
* connector 가 sessionExclusive=true 로 선언한 경우 connection.name 단위 mutex 로
|
|
128
|
+
* 직렬화. 호출자는 acquireSessionPage / releasePage 의 lifecycle 을 신경 쓸 필요 없이
|
|
129
|
+
* `withSession(connection, async page => { ... })` 형태로 사용하면 됨.
|
|
130
|
+
*
|
|
131
|
+
* stateless connector (sessionExclusive=false) 는 mutex 없이 즉시 진행.
|
|
132
|
+
*
|
|
133
|
+
* @param connection 도메인 instance (connection.type 에서 connector 조회)
|
|
134
|
+
* @param fn page 를 받아 데이터 작업하는 async 함수
|
|
135
|
+
*/
|
|
136
|
+
static async withSession(connection, fn) {
|
|
137
|
+
const connector = ConnectionManager.getConnector(connection.type);
|
|
138
|
+
const exclusive = !!connector?.sessionExclusive;
|
|
139
|
+
const run = async () => {
|
|
140
|
+
const page = await connection.acquireSessionPage();
|
|
141
|
+
try {
|
|
142
|
+
return await fn(page);
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
// releasePage 가 page 인자를 받음 — 누락 시 내부의 page.close() 와
|
|
146
|
+
// browser 풀 반환이 스킵되어 브라우저가 leak. 풀 고갈 → 정상 종료 hang 으로 이어짐.
|
|
147
|
+
try {
|
|
148
|
+
await connection.releasePage?.(page);
|
|
149
|
+
}
|
|
150
|
+
catch (e) {
|
|
151
|
+
ConnectionManager.logger.warn(`releasePage failed for '${connection.name}':`, e);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
if (!exclusive)
|
|
156
|
+
return await run();
|
|
157
|
+
// mutex: connection name 단위로 직렬화 (도메인 무관, 같은 외부 계정 중복 로그인 방지)
|
|
158
|
+
const lockKey = connection.name;
|
|
159
|
+
const wasQueued = ConnectionManager.sessionLocks.has(lockKey);
|
|
160
|
+
const prev = ConnectionManager.sessionLocks.get(lockKey) || Promise.resolve();
|
|
161
|
+
let release;
|
|
162
|
+
const next = new Promise(r => (release = r));
|
|
163
|
+
// chain: 이전 작업이 끝난 뒤 next 가 끝날 때까지 pending — 다음 대기자가 이 chain 으로 await
|
|
164
|
+
const chain = prev.then(() => next);
|
|
165
|
+
ConnectionManager.sessionLocks.set(lockKey, chain);
|
|
166
|
+
// 큐 대기 진단 — 운영 중 "왜 작업이 stall 되는가" 추적용
|
|
167
|
+
const waitStartedAt = wasQueued ? Date.now() : 0;
|
|
168
|
+
if (wasQueued) {
|
|
169
|
+
ConnectionManager.logger.info(`[withSession] '${lockKey}' queued behind existing lock — waiting (domain='${connection.domain?.subdomain || connection.domainId}')`);
|
|
170
|
+
}
|
|
171
|
+
await prev;
|
|
172
|
+
if (wasQueued) {
|
|
173
|
+
const waited = Date.now() - waitStartedAt;
|
|
174
|
+
ConnectionManager.logger.info(`[withSession] '${lockKey}' lock acquired after ${waited}ms wait`);
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
return await run();
|
|
178
|
+
}
|
|
179
|
+
finally {
|
|
180
|
+
release();
|
|
181
|
+
// 내가 latest 면 (이 chain 뒤로 새로 들어온 대기자 없음) cleanup
|
|
182
|
+
if (ConnectionManager.sessionLocks.get(lockKey) === chain) {
|
|
183
|
+
ConnectionManager.sessionLocks.delete(lockKey);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
117
187
|
static async getConnectionInstanceByName(domain, name) {
|
|
118
188
|
const connections = ConnectionManager.connections[domain.id];
|
|
119
189
|
const connection = connections?.[name];
|
|
120
190
|
if (!connection) {
|
|
121
|
-
//
|
|
191
|
+
// 자식 도메인에 instance 가 없을 때의 세 가지 자동 처리 분기:
|
|
192
|
+
//
|
|
193
|
+
// (a) entity.__sharedFrom 있음 — SHARE 모드 inheritance
|
|
194
|
+
// → 부모 도메인의 instance 를 그대로 lookup 해서 반환. 자식 도메인에는 instance 등록 안 함.
|
|
195
|
+
// 같은 instance 가 부모·자식 모두에게 서비스 됨.
|
|
196
|
+
//
|
|
197
|
+
// (b) entity.__inheritedFrom 있음 — ISOLATE 모드 inheritance
|
|
198
|
+
// → 자식 도메인 키 아래 새 instance 자동 생성. cookies/세션 격리.
|
|
199
|
+
//
|
|
200
|
+
// (c) entity.onDemand=true — 명시적 lazy 생성 (자식 자체 record 인 경우에도 적용)
|
|
201
|
+
// → 자식 도메인 키 아래 새 instance 생성.
|
|
202
|
+
//
|
|
203
|
+
// 나머지: 자식 record 도 없고 inherit 도 안 되는 경우 → throw.
|
|
122
204
|
try {
|
|
123
205
|
const connectionEntity = await ConnectionManager.getConnectionEntityByName(domain, name);
|
|
124
|
-
if (connectionEntity?.
|
|
125
|
-
//
|
|
126
|
-
const
|
|
127
|
-
|
|
206
|
+
if (connectionEntity?.__sharedFrom) {
|
|
207
|
+
// SHARE: 부모 instance 직접 사용
|
|
208
|
+
const parentInstance = ConnectionManager.connections[connectionEntity.__sharedFrom]?.[name];
|
|
209
|
+
if (parentInstance)
|
|
210
|
+
return parentInstance;
|
|
211
|
+
throw `SHARE mode: parent instance for '${name}' (domain id '${connectionEntity.__sharedFrom}') not registered yet — startup ready() may have failed for parent`;
|
|
212
|
+
}
|
|
213
|
+
if (connectionEntity?.onDemand || connectionEntity?.__inheritedFrom) {
|
|
214
|
+
const instance = await ConnectionManager.createOnDemandConnection(connectionEntity);
|
|
215
|
+
return instance;
|
|
216
|
+
}
|
|
217
|
+
else if (connectionEntity) {
|
|
218
|
+
// entity 는 찾았는데 instance 생성 trigger (onDemand/inherit) 없음 — 운영자가 record 자체 점검 필요
|
|
219
|
+
throw `Connection '${name}' record exists but instance is not registered and no auto-create trigger (onDemand=false, no inheritance marker). Check whether parent record's active=true and ready() succeeded at startup.`;
|
|
128
220
|
}
|
|
129
221
|
else {
|
|
130
|
-
|
|
222
|
+
// entity 자체 못 찾음 — 도메인 트리 어디에도 record 없음
|
|
223
|
+
throw `Connection '${name}' not found in domain '${domain.subdomain || domain.id}' or any ancestor — verify the Connection record exists at the running domain or any of its ancestors.`;
|
|
131
224
|
}
|
|
132
225
|
}
|
|
133
226
|
catch (error) {
|
|
@@ -163,22 +256,61 @@ class ConnectionManager {
|
|
|
163
256
|
// return parentCachedEntity
|
|
164
257
|
// }
|
|
165
258
|
// }
|
|
166
|
-
// 4.
|
|
167
|
-
|
|
168
|
-
|
|
259
|
+
// 4. ancestor 도메인 트리 fallback (closest-first walk)
|
|
260
|
+
//
|
|
261
|
+
// EnvVar inheritance 와 일관되게 모든 조상 도메인을 closest-first 로 탐색.
|
|
262
|
+
// 예: SYSTEM → 시공사 → 프로젝트 트리에서 시나리오가 프로젝트 도메인에서 실행될 때
|
|
263
|
+
// Connection record 가 SYSTEM 또는 시공사 어디에 있어도 찾아냄.
|
|
264
|
+
//
|
|
265
|
+
// 매칭된 ancestor 의 inheritanceMode 에 따라 두 갈래:
|
|
266
|
+
// ISOLATE (default): 자식 도메인용 clone 생성. cookies/세션 격리, 자식 컨텍스트에서
|
|
267
|
+
// EnvVar override 해소. __inheritedFrom marker 로 후속 자동 instance.
|
|
268
|
+
// SHARE: 부모 entity 그대로 반환. params·세션·인스턴스 모두 부모 것 공유.
|
|
269
|
+
// __sharedFrom marker 로 instance lookup 이 부모 도메인을 가리키게 함.
|
|
270
|
+
//
|
|
271
|
+
// 호환성: inheritanceMode 미지정(NULL) → connector default → 폴백 ISOLATE.
|
|
272
|
+
// 기존 모든 Connection 은 이 column 추가 전 만들어졌으므로 NULL → ISOLATE = 기존 거동.
|
|
273
|
+
const ancestorIds = await (0, domain_inheritance_1.getDomainIdsWithAncestors)(domain);
|
|
274
|
+
// ancestorIds[0] = domain.id (이미 위 분기에서 시도), skip
|
|
275
|
+
const searchedAncestors = [];
|
|
276
|
+
for (let i = 1; i < ancestorIds.length; i++) {
|
|
277
|
+
const ancestorId = ancestorIds[i];
|
|
278
|
+
searchedAncestors.push(ancestorId);
|
|
279
|
+
const parentConn = await (0, shell_1.getRepository)(service_1.Connection).findOne({
|
|
169
280
|
where: {
|
|
170
|
-
domain: { id:
|
|
281
|
+
domain: { id: ancestorId },
|
|
171
282
|
name: name
|
|
172
283
|
},
|
|
173
284
|
relations: ['domain', 'edge', 'creator', 'updater']
|
|
174
285
|
});
|
|
175
|
-
if (
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
286
|
+
if (!parentConn)
|
|
287
|
+
continue; // 이 ancestor 에 없으면 다음 ancestor
|
|
288
|
+
ConnectionManager.logger.info(`[inheritance] '${name}' inherited by domain '${domain.subdomain || domain.id}' ` +
|
|
289
|
+
`from ancestor '${parentConn.domain?.subdomain || ancestorId}' ` +
|
|
290
|
+
`(walked ${i} ancestor(s))`);
|
|
291
|
+
const connectorDefault = ConnectionManager.connectors[parentConn.type]?.inheritanceMode;
|
|
292
|
+
const effectiveMode = parentConn.inheritanceMode || connectorDefault || service_1.ConnectionInheritanceMode.ISOLATE;
|
|
293
|
+
if (effectiveMode === service_1.ConnectionInheritanceMode.SHARE) {
|
|
294
|
+
// SHARE — 부모 entity 그대로 반환 (mutation 최소화).
|
|
295
|
+
const shared = parentConn;
|
|
296
|
+
shared.__sharedFrom = parentConn.domainId;
|
|
297
|
+
shared.params = await parentConn.getResolvedParameters();
|
|
298
|
+
return shared;
|
|
181
299
|
}
|
|
300
|
+
// ISOLATE (default) — clone + marker
|
|
301
|
+
const inherited = Object.assign(Object.create(Object.getPrototypeOf(parentConn)), parentConn, {
|
|
302
|
+
domain,
|
|
303
|
+
domainId: domain.id,
|
|
304
|
+
cookies: undefined,
|
|
305
|
+
__inheritedFrom: parentConn.domainId
|
|
306
|
+
});
|
|
307
|
+
inherited.params = await inherited.getResolvedParameters();
|
|
308
|
+
return inherited;
|
|
309
|
+
}
|
|
310
|
+
// 어느 ancestor 에서도 못 찾음 — 운영자가 어디까지 찾았는지 알 수 있도록 진단 로그
|
|
311
|
+
if (searchedAncestors.length > 0) {
|
|
312
|
+
ConnectionManager.logger.warn(`[inheritance] '${name}' not found at any ancestor of domain '${domain.subdomain || domain.id}' ` +
|
|
313
|
+
`— searched: [${searchedAncestors.join(', ')}]`);
|
|
182
314
|
}
|
|
183
315
|
return null;
|
|
184
316
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection-manager.js","sourceRoot":"","sources":["../../server/engine/connection-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import moment from 'moment-timezone'\nimport { createLogger, format, transports } from 'winston'\n\nimport { Domain, getRepository, pubsub, PubSubLogTransport } from '@things-factory/shell'\n\nimport { Connection, ConnectionStatus } from '../service'\nimport { Connector } from './types'\nimport { ProxyConnector } from './connector/proxy-connector'\n\nconst { combine, splat, printf, errors } = format\nconst debug = require('debug')('things-factory:integration-base:connections')\n\nfunction getSystemTimeZone() {\n try {\n const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone\n if (!timeZone) {\n throw new Error('Unable to resolve timeZone')\n }\n return timeZone\n } catch (e) {\n console.warn('Failed to get system timeZone, falling back to UTC.', e)\n return 'UTC'\n }\n}\n\nconst SYSTEM_TZ = getSystemTimeZone()\nconst systemTimestamp = format((info, opts: { tz?: string }) => {\n if (opts.tz) info.timestamp = moment().tz(opts.tz).format()\n return info\n})\n\nexport class ConnectionManager {\n private static connectors: { [propName: string]: Connector } = {}\n private static connections: { [domainId: string]: { [name: string]: any } } = {}\n private static entities = {}\n private static logFormat = printf(({ level, message, timestamp, stack }) => {\n return `${timestamp} ${level}: ${stack || message}`\n })\n\n public static logger = createLogger({\n format: combine(errors({ stack: true }), systemTimestamp({ tz: SYSTEM_TZ }), splat(), ConnectionManager.logFormat),\n transports: [\n new (transports as any).DailyRotateFile({\n filename: `logs/connections-%DATE%.log`,\n datePattern: 'YYYY-MM-DD-HH',\n zippedArchive: false,\n maxSize: '20m',\n maxFiles: '14d',\n level: 'info'\n }),\n new PubSubLogTransport({\n topic: 'connection-log'\n })\n ]\n })\n\n static async ready() {\n const CONNECTIONS = await Promise.all(\n (\n await getRepository(Connection).find({\n where: { active: true },\n relations: ['domain', 'edge', 'creator', 'updater']\n })\n ).map(async connection => {\n // 🔐 해결된 파라미터 사용\n const resolvedParams = await connection.getResolvedParameters()\n\n return {\n ...connection,\n params: resolvedParams\n }\n })\n )\n\n ConnectionManager.logger.info('Initializing ConnectionManager...')\n\n return await Promise.all(\n [...Object.keys(ConnectionManager.connectors), 'proxy-connector'].map(type => {\n const connector = type == 'proxy-connector' ? ProxyConnector.instance : ConnectionManager.getConnector(type)\n\n ConnectionManager.logger.info(`Connector '${type}' started to ready`)\n\n return connector\n .ready(\n CONNECTIONS.filter(connection => {\n if (type == 'proxy-connector') {\n return !!connection.edge\n } else {\n return !connection.edge && connection.type == type\n }\n }) as any\n )\n .catch(error => {\n ConnectionManager.logger.error(error)\n })\n .then(() => {\n ConnectionManager.logger.info(`All connector for '${type}' ready`)\n })\n })\n ).then(() => {\n ConnectionManager.logger.info('ConnectionManager initialization done:')\n Object.keys(ConnectionManager.connections).forEach(key => {\n var connections = ConnectionManager.connections[key]\n ConnectionManager.logger.info('For domain(%s) : %s', key, JSON.stringify(Object.keys(connections)))\n })\n })\n }\n\n static registerConnector(type: string, connector: Connector) {\n ConnectionManager.connectors[type] = connector\n }\n\n static getConnector(type: string): Connector {\n return ConnectionManager.connectors[type]\n }\n\n static getConnectors(): { [connectorName: string]: Connector } {\n return {\n ...ConnectionManager.connectors\n }\n }\n\n static unregisterConnector(type: string) {\n delete ConnectionManager.connectors[type]\n }\n\n static getConnections() {\n return ConnectionManager.connections\n }\n\n static getEntities() {\n return ConnectionManager.entities\n }\n\n static getConnectionInstance(connection: Connection): any {\n const { domain, name } = connection\n return ConnectionManager.connections[domain.id]?.[name]\n }\n\n static async getConnectionInstanceByName(domain: Domain, name: string) {\n const connections = ConnectionManager.connections[domain.id]\n const connection = connections?.[name]\n\n if (!connection) {\n // on-demand 커넥션인지 확인하고 생성\n try {\n const connectionEntity = await ConnectionManager.getConnectionEntityByName(domain, name)\n\n if (connectionEntity?.onDemand) {\n // on-demand 커넥션 생성\n const onDemandInstance = await ConnectionManager.createOnDemandConnection(connectionEntity)\n return onDemandInstance\n } else {\n throw `The connection with the given name(${name}) cannot be found`\n }\n } catch (error) {\n throw `The connection with the given name(${name}) cannot be found: ${error}`\n }\n }\n\n return connection\n }\n\n static async getConnectionEntityByName(domain: Domain, name: string): Promise<Connection | null> {\n // 1. 현재 도메인에서 메모리 조회 (우선)\n const cachedEntity = ConnectionManager.entities[domain.id]?.[name]\n if (cachedEntity) {\n return cachedEntity\n }\n\n // 2. 현재 도메인에서 데이터베이스 조회 (우선)\n try {\n let connection = await getRepository(Connection).findOne({\n where: {\n domain: { id: domain.id },\n name: name\n },\n relations: ['domain', 'edge', 'creator', 'updater']\n })\n\n if (connection) {\n const resolvedParams = await connection.getResolvedParameters()\n connection.params = resolvedParams\n return connection\n }\n\n // 3. 현재 도메인에서 못 찾으면 부모 도메인에서 메모리 조회 - 안된다. connection정보를 수정해야 하므로 사용할 수 없다.\n // if (domain.parentId) {\n // const parentCachedEntity = ConnectionManager.entities[domain.parentId]?.[name]\n // if (parentCachedEntity) {\n // return parentCachedEntity\n // }\n // }\n\n // 4. 부모 도메인에서 데이터베이스 조회 (fallback)\n if (domain.parentId) {\n connection = await getRepository(Connection).findOne({\n where: {\n domain: { id: domain.parentId },\n name: name\n },\n relations: ['domain', 'edge', 'creator', 'updater']\n })\n\n if (connection) {\n const resolvedParams = await connection.getResolvedParameters()\n connection.params = resolvedParams\n connection.domain = domain\n connection.domainId = domain.id\n\n return connection\n }\n }\n\n return null\n } catch (error) {\n ConnectionManager.logger.error(`Failed to get connection entity '${name}' from database:`, error)\n return null\n }\n }\n\n static getConnectionInstanceEntityByName(domain: Domain, name: string): any {\n const connection = ConnectionManager.entities[domain.id]?.[name]\n if (connection) {\n return connection\n }\n\n if (domain.parentId) {\n return ConnectionManager.entities[domain.parentId]?.[name]\n }\n }\n\n /**\n * Creates a connection on-demand and returns the instance.\n * @param connection - The connection entity to create\n * @returns The connection instance\n */\n static async createOnDemandConnection(connection: Connection): Promise<any> {\n try {\n // 커넥션 생성\n await connection.connect()\n\n ConnectionManager.logger.info(`On-demand connection '${connection.name}' created successfully`)\n return ConnectionManager.getConnectionInstance(connection)\n } catch (error) {\n ConnectionManager.logger.error(`Failed to create on-demand connection '${connection.name}':`, error)\n throw error\n }\n }\n\n /**\n * Disconnects an on-demand connection.\n * @param connection - The connection entity to disconnect\n */\n static async disconnectOnDemandConnection(connection: Connection): Promise<void> {\n try {\n await connection.disconnect()\n // ConnectionManager.removeConnectionInstance(connection)\n ConnectionManager.logger.info(`On-demand connection '${connection.name}' disconnected successfully`)\n } catch (error) {\n ConnectionManager.logger.error(`Failed to disconnect on-demand connection '${connection.name}':`, error)\n }\n }\n\n static getConnectionInstances(domain: Domain): { [connectionName: string]: any } {\n const connections = ConnectionManager.connections[domain.id]\n const parentConnections = domain.parentId && ConnectionManager.connections[domain.parentId]\n\n return {\n ...parentConnections,\n ...connections\n }\n }\n\n static getConnectionInstanceEntities(domain: Domain): { [connectionName: string]: any } {\n const connections = ConnectionManager.entities[domain.id]\n\n return {\n ...connections\n }\n }\n\n static addConnectionInstance(connection: Connection, instance: any) {\n const { domain, name } = connection\n\n var connections = ConnectionManager.connections[domain.id]\n if (!connections) {\n connections = ConnectionManager.connections[domain.id] = {}\n }\n\n var entities = ConnectionManager.entities[domain.id]\n if (!entities) {\n entities = ConnectionManager.entities[domain.id] = {}\n }\n\n connections[name] = instance\n entities[name] = connection\n\n ConnectionManager.publishState(connection, ConnectionStatus.CONNECTED)\n debug('add-connection', domain.subdomain, name)\n }\n\n static removeConnectionInstance(connection: Connection): any {\n const { domain, name } = connection\n var connections = ConnectionManager.connections[domain.id]\n var entities = ConnectionManager.entities[domain.id]\n\n var instance = connections?.[name]\n\n if (!connections || !instance) {\n debug('remove-connection', `'${name}' connection not found in domain '${domain.subdomain}'`)\n return\n }\n\n delete connections[name]\n delete entities[name]\n\n ConnectionManager.publishState(connection, ConnectionStatus.DISCONNECTED)\n debug('remove-connection', `'${name}' connection is removed from domain '${domain.subdomain}'`)\n\n return instance\n }\n\n private static async publishState(connection: Connection, state) {\n const { domain, id, name, description, type, edge } = connection\n\n pubsub.publish('connection-state', {\n connectionState: {\n domain,\n id,\n name,\n description,\n type,\n edge,\n state,\n timestamp: new Date()\n }\n })\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"connection-manager.js","sourceRoot":"","sources":["../../server/engine/connection-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import moment from 'moment-timezone'\nimport { createLogger, format, transports } from 'winston'\n\nimport { Domain, getRepository, pubsub, PubSubLogTransport } from '@things-factory/shell'\n\nimport { Connection, ConnectionInheritanceMode, ConnectionStatus } from '../service'\nimport { getDomainIdsWithAncestors } from '../utils/domain-inheritance'\nimport { Connector } from './types'\nimport { ProxyConnector } from './connector/proxy-connector'\n\nconst { combine, splat, printf, errors } = format\nconst debug = require('debug')('things-factory:integration-base:connections')\n\nfunction getSystemTimeZone() {\n try {\n const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone\n if (!timeZone) {\n throw new Error('Unable to resolve timeZone')\n }\n return timeZone\n } catch (e) {\n console.warn('Failed to get system timeZone, falling back to UTC.', e)\n return 'UTC'\n }\n}\n\nconst SYSTEM_TZ = getSystemTimeZone()\nconst systemTimestamp = format((info, opts: { tz?: string }) => {\n if (opts.tz) info.timestamp = moment().tz(opts.tz).format()\n return info\n})\n\nexport class ConnectionManager {\n private static connectors: { [propName: string]: Connector } = {}\n private static connections: { [domainId: string]: { [name: string]: any } } = {}\n private static entities = {}\n /**\n * Session 직렬화 mutex — connection name 단위 (도메인 무관 글로벌).\n * sessionExclusive connector 의 acquireSessionPage 동시 호출이 외부 세션을 서로\n * invalidate 하지 않도록 큐잉. release 호출 시 다음 대기자가 진행.\n */\n private static sessionLocks: Map<string, Promise<void>> = new Map()\n private static logFormat = printf(({ level, message, timestamp, stack }) => {\n return `${timestamp} ${level}: ${stack || message}`\n })\n\n public static logger = createLogger({\n format: combine(errors({ stack: true }), systemTimestamp({ tz: SYSTEM_TZ }), splat(), ConnectionManager.logFormat),\n transports: [\n new (transports as any).DailyRotateFile({\n filename: `logs/connections-%DATE%.log`,\n datePattern: 'YYYY-MM-DD-HH',\n zippedArchive: false,\n maxSize: '20m',\n maxFiles: '14d',\n level: 'info'\n }),\n new PubSubLogTransport({\n topic: 'connection-log'\n })\n ]\n })\n\n static async ready() {\n const CONNECTIONS = await Promise.all(\n (\n await getRepository(Connection).find({\n where: { active: true },\n relations: ['domain', 'edge', 'creator', 'updater']\n })\n ).map(async connection => {\n // 🔐 해결된 파라미터 사용\n const resolvedParams = await connection.getResolvedParameters()\n\n return {\n ...connection,\n params: resolvedParams\n }\n })\n )\n\n ConnectionManager.logger.info('Initializing ConnectionManager...')\n\n return await Promise.all(\n [...Object.keys(ConnectionManager.connectors), 'proxy-connector'].map(type => {\n const connector = type == 'proxy-connector' ? ProxyConnector.instance : ConnectionManager.getConnector(type)\n\n ConnectionManager.logger.info(`Connector '${type}' started to ready`)\n\n return connector\n .ready(\n CONNECTIONS.filter(connection => {\n if (type == 'proxy-connector') {\n return !!connection.edge\n } else {\n return !connection.edge && connection.type == type\n }\n }) as any\n )\n .catch(error => {\n ConnectionManager.logger.error(error)\n })\n .then(() => {\n ConnectionManager.logger.info(`All connector for '${type}' ready`)\n })\n })\n ).then(() => {\n ConnectionManager.logger.info('ConnectionManager initialization done:')\n Object.keys(ConnectionManager.connections).forEach(key => {\n var connections = ConnectionManager.connections[key]\n ConnectionManager.logger.info('For domain(%s) : %s', key, JSON.stringify(Object.keys(connections)))\n })\n })\n }\n\n static registerConnector(type: string, connector: Connector) {\n ConnectionManager.connectors[type] = connector\n }\n\n static getConnector(type: string): Connector {\n return ConnectionManager.connectors[type]\n }\n\n static getConnectors(): { [connectorName: string]: Connector } {\n return {\n ...ConnectionManager.connectors\n }\n }\n\n static unregisterConnector(type: string) {\n delete ConnectionManager.connectors[type]\n }\n\n static getConnections() {\n return ConnectionManager.connections\n }\n\n static getEntities() {\n return ConnectionManager.entities\n }\n\n static getConnectionInstance(connection: Connection): any {\n const { domain, name } = connection\n return ConnectionManager.connections[domain.id]?.[name]\n }\n\n /**\n * 세션 사용 패턴 (RAII).\n *\n * connector 가 sessionExclusive=true 로 선언한 경우 connection.name 단위 mutex 로\n * 직렬화. 호출자는 acquireSessionPage / releasePage 의 lifecycle 을 신경 쓸 필요 없이\n * `withSession(connection, async page => { ... })` 형태로 사용하면 됨.\n *\n * stateless connector (sessionExclusive=false) 는 mutex 없이 즉시 진행.\n *\n * @param connection 도메인 instance (connection.type 에서 connector 조회)\n * @param fn page 를 받아 데이터 작업하는 async 함수\n */\n static async withSession<T>(connection: any, fn: (page: any) => Promise<T>): Promise<T> {\n const connector = ConnectionManager.getConnector(connection.type)\n const exclusive = !!connector?.sessionExclusive\n\n const run = async (): Promise<T> => {\n const page = await connection.acquireSessionPage()\n try {\n return await fn(page)\n } finally {\n // releasePage 가 page 인자를 받음 — 누락 시 내부의 page.close() 와\n // browser 풀 반환이 스킵되어 브라우저가 leak. 풀 고갈 → 정상 종료 hang 으로 이어짐.\n try {\n await connection.releasePage?.(page)\n } catch (e) {\n ConnectionManager.logger.warn(`releasePage failed for '${connection.name}':`, e as any)\n }\n }\n }\n\n if (!exclusive) return await run()\n\n // mutex: connection name 단위로 직렬화 (도메인 무관, 같은 외부 계정 중복 로그인 방지)\n const lockKey = connection.name\n const wasQueued = ConnectionManager.sessionLocks.has(lockKey)\n const prev = ConnectionManager.sessionLocks.get(lockKey) || Promise.resolve()\n let release!: () => void\n const next = new Promise<void>(r => (release = r))\n // chain: 이전 작업이 끝난 뒤 next 가 끝날 때까지 pending — 다음 대기자가 이 chain 으로 await\n const chain = prev.then(() => next)\n ConnectionManager.sessionLocks.set(lockKey, chain)\n\n // 큐 대기 진단 — 운영 중 \"왜 작업이 stall 되는가\" 추적용\n const waitStartedAt = wasQueued ? Date.now() : 0\n if (wasQueued) {\n ConnectionManager.logger.info(\n `[withSession] '${lockKey}' queued behind existing lock — waiting (domain='${connection.domain?.subdomain || connection.domainId}')`\n )\n }\n await prev\n if (wasQueued) {\n const waited = Date.now() - waitStartedAt\n ConnectionManager.logger.info(\n `[withSession] '${lockKey}' lock acquired after ${waited}ms wait`\n )\n }\n\n try {\n return await run()\n } finally {\n release()\n // 내가 latest 면 (이 chain 뒤로 새로 들어온 대기자 없음) cleanup\n if (ConnectionManager.sessionLocks.get(lockKey) === chain) {\n ConnectionManager.sessionLocks.delete(lockKey)\n }\n }\n }\n\n static async getConnectionInstanceByName(domain: Domain, name: string) {\n const connections = ConnectionManager.connections[domain.id]\n const connection = connections?.[name]\n\n if (!connection) {\n // 자식 도메인에 instance 가 없을 때의 세 가지 자동 처리 분기:\n //\n // (a) entity.__sharedFrom 있음 — SHARE 모드 inheritance\n // → 부모 도메인의 instance 를 그대로 lookup 해서 반환. 자식 도메인에는 instance 등록 안 함.\n // 같은 instance 가 부모·자식 모두에게 서비스 됨.\n //\n // (b) entity.__inheritedFrom 있음 — ISOLATE 모드 inheritance\n // → 자식 도메인 키 아래 새 instance 자동 생성. cookies/세션 격리.\n //\n // (c) entity.onDemand=true — 명시적 lazy 생성 (자식 자체 record 인 경우에도 적용)\n // → 자식 도메인 키 아래 새 instance 생성.\n //\n // 나머지: 자식 record 도 없고 inherit 도 안 되는 경우 → throw.\n try {\n const connectionEntity = await ConnectionManager.getConnectionEntityByName(domain, name)\n\n if (connectionEntity?.__sharedFrom) {\n // SHARE: 부모 instance 직접 사용\n const parentInstance = ConnectionManager.connections[connectionEntity.__sharedFrom]?.[name]\n if (parentInstance) return parentInstance\n throw `SHARE mode: parent instance for '${name}' (domain id '${connectionEntity.__sharedFrom}') not registered yet — startup ready() may have failed for parent`\n }\n\n if (connectionEntity?.onDemand || connectionEntity?.__inheritedFrom) {\n const instance = await ConnectionManager.createOnDemandConnection(connectionEntity!)\n return instance\n } else if (connectionEntity) {\n // entity 는 찾았는데 instance 생성 trigger (onDemand/inherit) 없음 — 운영자가 record 자체 점검 필요\n throw `Connection '${name}' record exists but instance is not registered and no auto-create trigger (onDemand=false, no inheritance marker). Check whether parent record's active=true and ready() succeeded at startup.`\n } else {\n // entity 자체 못 찾음 — 도메인 트리 어디에도 record 없음\n throw `Connection '${name}' not found in domain '${domain.subdomain || domain.id}' or any ancestor — verify the Connection record exists at the running domain or any of its ancestors.`\n }\n } catch (error) {\n throw `The connection with the given name(${name}) cannot be found: ${error}`\n }\n }\n\n return connection\n }\n\n static async getConnectionEntityByName(domain: Domain, name: string): Promise<Connection | null> {\n // 1. 현재 도메인에서 메모리 조회 (우선)\n const cachedEntity = ConnectionManager.entities[domain.id]?.[name]\n if (cachedEntity) {\n return cachedEntity\n }\n\n // 2. 현재 도메인에서 데이터베이스 조회 (우선)\n try {\n let connection = await getRepository(Connection).findOne({\n where: {\n domain: { id: domain.id },\n name: name\n },\n relations: ['domain', 'edge', 'creator', 'updater']\n })\n\n if (connection) {\n const resolvedParams = await connection.getResolvedParameters()\n connection.params = resolvedParams\n return connection\n }\n\n // 3. 현재 도메인에서 못 찾으면 부모 도메인에서 메모리 조회 - 안된다. connection정보를 수정해야 하므로 사용할 수 없다.\n // if (domain.parentId) {\n // const parentCachedEntity = ConnectionManager.entities[domain.parentId]?.[name]\n // if (parentCachedEntity) {\n // return parentCachedEntity\n // }\n // }\n\n // 4. ancestor 도메인 트리 fallback (closest-first walk)\n //\n // EnvVar inheritance 와 일관되게 모든 조상 도메인을 closest-first 로 탐색.\n // 예: SYSTEM → 시공사 → 프로젝트 트리에서 시나리오가 프로젝트 도메인에서 실행될 때\n // Connection record 가 SYSTEM 또는 시공사 어디에 있어도 찾아냄.\n //\n // 매칭된 ancestor 의 inheritanceMode 에 따라 두 갈래:\n // ISOLATE (default): 자식 도메인용 clone 생성. cookies/세션 격리, 자식 컨텍스트에서\n // EnvVar override 해소. __inheritedFrom marker 로 후속 자동 instance.\n // SHARE: 부모 entity 그대로 반환. params·세션·인스턴스 모두 부모 것 공유.\n // __sharedFrom marker 로 instance lookup 이 부모 도메인을 가리키게 함.\n //\n // 호환성: inheritanceMode 미지정(NULL) → connector default → 폴백 ISOLATE.\n // 기존 모든 Connection 은 이 column 추가 전 만들어졌으므로 NULL → ISOLATE = 기존 거동.\n const ancestorIds = await getDomainIdsWithAncestors(domain)\n // ancestorIds[0] = domain.id (이미 위 분기에서 시도), skip\n const searchedAncestors: string[] = []\n for (let i = 1; i < ancestorIds.length; i++) {\n const ancestorId = ancestorIds[i]\n searchedAncestors.push(ancestorId)\n const parentConn = await getRepository(Connection).findOne({\n where: {\n domain: { id: ancestorId },\n name: name\n },\n relations: ['domain', 'edge', 'creator', 'updater']\n })\n\n if (!parentConn) continue // 이 ancestor 에 없으면 다음 ancestor\n\n ConnectionManager.logger.info(\n `[inheritance] '${name}' inherited by domain '${domain.subdomain || domain.id}' ` +\n `from ancestor '${parentConn.domain?.subdomain || ancestorId}' ` +\n `(walked ${i} ancestor(s))`\n )\n\n const connectorDefault = ConnectionManager.connectors[parentConn.type]?.inheritanceMode\n const effectiveMode =\n parentConn.inheritanceMode || connectorDefault || ConnectionInheritanceMode.ISOLATE\n\n if (effectiveMode === ConnectionInheritanceMode.SHARE) {\n // SHARE — 부모 entity 그대로 반환 (mutation 최소화).\n const shared: any = parentConn\n shared.__sharedFrom = parentConn.domainId\n shared.params = await parentConn.getResolvedParameters()\n return shared\n }\n\n // ISOLATE (default) — clone + marker\n const inherited: any = Object.assign(\n Object.create(Object.getPrototypeOf(parentConn)),\n parentConn,\n {\n domain,\n domainId: domain.id,\n cookies: undefined,\n __inheritedFrom: parentConn.domainId\n }\n )\n inherited.params = await inherited.getResolvedParameters()\n return inherited\n }\n\n // 어느 ancestor 에서도 못 찾음 — 운영자가 어디까지 찾았는지 알 수 있도록 진단 로그\n if (searchedAncestors.length > 0) {\n ConnectionManager.logger.warn(\n `[inheritance] '${name}' not found at any ancestor of domain '${domain.subdomain || domain.id}' ` +\n `— searched: [${searchedAncestors.join(', ')}]`\n )\n }\n return null\n } catch (error) {\n ConnectionManager.logger.error(`Failed to get connection entity '${name}' from database:`, error)\n return null\n }\n }\n\n static getConnectionInstanceEntityByName(domain: Domain, name: string): any {\n const connection = ConnectionManager.entities[domain.id]?.[name]\n if (connection) {\n return connection\n }\n\n if (domain.parentId) {\n return ConnectionManager.entities[domain.parentId]?.[name]\n }\n }\n\n /**\n * Creates a connection on-demand and returns the instance.\n * @param connection - The connection entity to create\n * @returns The connection instance\n */\n static async createOnDemandConnection(connection: Connection): Promise<any> {\n try {\n // 커넥션 생성\n await connection.connect()\n\n ConnectionManager.logger.info(`On-demand connection '${connection.name}' created successfully`)\n return ConnectionManager.getConnectionInstance(connection)\n } catch (error) {\n ConnectionManager.logger.error(`Failed to create on-demand connection '${connection.name}':`, error)\n throw error\n }\n }\n\n /**\n * Disconnects an on-demand connection.\n * @param connection - The connection entity to disconnect\n */\n static async disconnectOnDemandConnection(connection: Connection): Promise<void> {\n try {\n await connection.disconnect()\n // ConnectionManager.removeConnectionInstance(connection)\n ConnectionManager.logger.info(`On-demand connection '${connection.name}' disconnected successfully`)\n } catch (error) {\n ConnectionManager.logger.error(`Failed to disconnect on-demand connection '${connection.name}':`, error)\n }\n }\n\n static getConnectionInstances(domain: Domain): { [connectionName: string]: any } {\n const connections = ConnectionManager.connections[domain.id]\n const parentConnections = domain.parentId && ConnectionManager.connections[domain.parentId]\n\n return {\n ...parentConnections,\n ...connections\n }\n }\n\n static getConnectionInstanceEntities(domain: Domain): { [connectionName: string]: any } {\n const connections = ConnectionManager.entities[domain.id]\n\n return {\n ...connections\n }\n }\n\n static addConnectionInstance(connection: Connection, instance: any) {\n const { domain, name } = connection\n\n var connections = ConnectionManager.connections[domain.id]\n if (!connections) {\n connections = ConnectionManager.connections[domain.id] = {}\n }\n\n var entities = ConnectionManager.entities[domain.id]\n if (!entities) {\n entities = ConnectionManager.entities[domain.id] = {}\n }\n\n connections[name] = instance\n entities[name] = connection\n\n ConnectionManager.publishState(connection, ConnectionStatus.CONNECTED)\n debug('add-connection', domain.subdomain, name)\n }\n\n static removeConnectionInstance(connection: Connection): any {\n const { domain, name } = connection\n var connections = ConnectionManager.connections[domain.id]\n var entities = ConnectionManager.entities[domain.id]\n\n var instance = connections?.[name]\n\n if (!connections || !instance) {\n debug('remove-connection', `'${name}' connection not found in domain '${domain.subdomain}'`)\n return\n }\n\n delete connections[name]\n delete entities[name]\n\n ConnectionManager.publishState(connection, ConnectionStatus.DISCONNECTED)\n debug('remove-connection', `'${name}' connection is removed from domain '${domain.subdomain}'`)\n\n return instance\n }\n\n private static async publishState(connection: Connection, state) {\n const { domain, id, name, description, type, edge } = connection\n\n pubsub.publish('connection-state', {\n connectionState: {\n domain,\n id,\n name,\n description,\n type,\n edge,\n state,\n timestamp: new Date()\n }\n })\n }\n}\n"]}
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import { Connector } from '../types';
|
|
2
2
|
export declare class HeadlessConnector implements Connector {
|
|
3
|
+
/**
|
|
4
|
+
* 외부 사이트 로그인 세션 보유 — 같은 connection name 의 동시 acquireSessionPage
|
|
5
|
+
* 호출이 같은 외부 계정 세션을 서로 invalidate 하지 않도록 ConnectionManager 가
|
|
6
|
+
* mutex 직렬화. 상속 connector (allbaro, kiscon 등) 자동 적용.
|
|
7
|
+
*/
|
|
8
|
+
sessionExclusive: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* 자식 도메인이 부모의 Connection 정의를 inherit 할 때 자식별 새 인스턴스 생성.
|
|
11
|
+
* cookies/세션·자격증명 격리. 헤드리스 사이트 connector 는 외부 자격증명이
|
|
12
|
+
* 자식 (테넌트) 마다 다를 수 있으므로 ISOLATE 가 자연 default.
|
|
13
|
+
*/
|
|
14
|
+
inheritanceMode: "isolate";
|
|
3
15
|
ready(connectionConfigs: any): Promise<void>;
|
|
4
16
|
connect(connection: any): Promise<void>;
|
|
5
17
|
setupPage(page: any, uri: any, timeout: any): Promise<void>;
|
|
@@ -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
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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(
|
|
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) {
|