@things-factory/integration-base 8.0.0-alpha.8 → 8.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/dist-server/controllers/scenario-controller.d.ts +2 -0
  2. package/dist-server/controllers/scenario-controller.js +16 -19
  3. package/dist-server/controllers/scenario-controller.js.map +1 -1
  4. package/dist-server/engine/connection-manager.js.map +1 -1
  5. package/dist-server/engine/connector/headless-connector.d.ts +23 -0
  6. package/dist-server/engine/connector/headless-connector.js +283 -0
  7. package/dist-server/engine/connector/headless-connector.js.map +1 -0
  8. package/dist-server/engine/connector/http-connector.js +1 -1
  9. package/dist-server/engine/connector/http-connector.js.map +1 -1
  10. package/dist-server/engine/connector/index.d.ts +1 -0
  11. package/dist-server/engine/connector/index.js +1 -0
  12. package/dist-server/engine/connector/index.js.map +1 -1
  13. package/dist-server/engine/connector/operato-connector.js +3 -4
  14. package/dist-server/engine/connector/operato-connector.js.map +1 -1
  15. package/dist-server/engine/index.d.ts +1 -0
  16. package/dist-server/engine/index.js +1 -0
  17. package/dist-server/engine/index.js.map +1 -1
  18. package/dist-server/engine/resource-pool/headless-pool.d.ts +1 -0
  19. package/dist-server/engine/resource-pool/headless-pool.js +121 -0
  20. package/dist-server/engine/resource-pool/headless-pool.js.map +1 -0
  21. package/dist-server/engine/resource-pool/index.d.ts +1 -0
  22. package/dist-server/engine/resource-pool/index.js +5 -0
  23. package/dist-server/engine/resource-pool/index.js.map +1 -0
  24. package/dist-server/engine/task/headless-post.js +19 -33
  25. package/dist-server/engine/task/headless-post.js.map +1 -1
  26. package/dist-server/engine/task/headless-scrap.js +20 -13
  27. package/dist-server/engine/task/headless-scrap.js.map +1 -1
  28. package/dist-server/engine/task/utils/headless-pool-for-scenario.js +1 -1
  29. package/dist-server/engine/task/utils/headless-pool-for-scenario.js.map +1 -1
  30. package/dist-server/service/payload-log/payload-log.js +7 -4
  31. package/dist-server/service/payload-log/payload-log.js.map +1 -1
  32. package/dist-server/service/scenario/scenario-query.d.ts +2 -1
  33. package/dist-server/service/scenario/scenario-query.js +10 -0
  34. package/dist-server/service/scenario/scenario-query.js.map +1 -1
  35. package/dist-server/service/scenario/scenario-type.d.ts +3 -3
  36. package/dist-server/service/scenario/scenario-type.js +7 -7
  37. package/dist-server/service/scenario/scenario-type.js.map +1 -1
  38. package/dist-server/service/scenario/scenario.d.ts +3 -1
  39. package/dist-server/service/scenario/scenario.js +9 -0
  40. package/dist-server/service/scenario/scenario.js.map +1 -1
  41. package/dist-server/service/scenario-instance/scenario-instance-type.js.map +1 -1
  42. package/dist-server/tsconfig.tsbuildinfo +1 -1
  43. package/package.json +12 -11
  44. package/server/controllers/scenario-controller.ts +27 -36
  45. package/server/engine/connection-manager.ts +1 -1
  46. package/server/engine/connector/headless-connector.ts +341 -0
  47. package/server/engine/connector/http-connector.ts +1 -1
  48. package/server/engine/connector/index.ts +1 -0
  49. package/server/engine/connector/operato-connector.ts +4 -7
  50. package/server/engine/index.ts +1 -0
  51. package/server/engine/resource-pool/headless-pool.ts +136 -0
  52. package/server/engine/resource-pool/index.ts +1 -0
  53. package/server/engine/task/headless-post.ts +21 -40
  54. package/server/engine/task/headless-scrap.ts +21 -18
  55. package/server/engine/task/utils/headless-pool-for-scenario.ts +1 -1
  56. package/server/service/payload-log/payload-log.ts +8 -4
  57. package/server/service/scenario/scenario-query.ts +6 -1
  58. package/server/service/scenario/scenario-type.ts +5 -5
  59. package/server/service/scenario/scenario.ts +15 -19
  60. package/server/service/scenario-instance/scenario-instance-type.ts +1 -1
  61. package/translations/en.json +12 -5
  62. package/translations/ja.json +12 -5
  63. package/translations/ko.json +12 -5
  64. package/translations/ms.json +12 -5
  65. package/translations/zh.json +12 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/integration-base",
3
- "version": "8.0.0-alpha.8",
3
+ "version": "8.0.0-beta.1",
4
4
  "main": "dist-server/index.js",
5
5
  "browser": "client/index.js",
6
6
  "things-factory": true,
@@ -26,23 +26,24 @@
26
26
  },
27
27
  "dependencies": {
28
28
  "@apollo/client": "^3.6.9",
29
- "@operato/moment-timezone-es": "^8.0.0-alpha",
30
- "@things-factory/api": "^8.0.0-alpha.8",
31
- "@things-factory/auth-base": "^8.0.0-alpha.8",
32
- "@things-factory/cache-service": "^8.0.0-alpha.8",
33
- "@things-factory/env": "^8.0.0-alpha.8",
34
- "@things-factory/oauth2-client": "^8.0.0-alpha.8",
35
- "@things-factory/scheduler-client": "^8.0.0-alpha.8",
36
- "@things-factory/shell": "^8.0.0-alpha.8",
37
- "@things-factory/utils": "^8.0.0-alpha.0",
29
+ "@operato/moment-timezone-es": "^8.0.0-beta",
30
+ "@things-factory/api": "^8.0.0-beta.1",
31
+ "@things-factory/auth-base": "^8.0.0-beta.1",
32
+ "@things-factory/cache-service": "^8.0.0-beta.1",
33
+ "@things-factory/env": "^8.0.0-beta.1",
34
+ "@things-factory/oauth2-client": "^8.0.0-beta.1",
35
+ "@things-factory/scheduler-client": "^8.0.0-beta.1",
36
+ "@things-factory/shell": "^8.0.0-beta.1",
37
+ "@things-factory/utils": "^8.0.0-beta.1",
38
38
  "async-mqtt": "^2.5.0",
39
39
  "chance": "^1.1.11",
40
40
  "cross-fetch": "^3.0.4",
41
41
  "ieee754": "^1.2.1",
42
42
  "node-fetch": "^2.6.0",
43
43
  "promise-socket": "^7.0.0",
44
+ "puppeteer": "^23.0.0",
44
45
  "readline": "^1.3.0",
45
46
  "ses": "^1.5.0"
46
47
  },
47
- "gitHead": "0a9fb3ab431934982294b58c743d01b6f782a15f"
48
+ "gitHead": "36c494e587640c1490318ef7b95adab02606e0c2"
48
49
  }
@@ -1,14 +1,10 @@
1
1
  import { getRepository, Domain, GraphqlLocalClient } from '@things-factory/shell'
2
- import { PrivilegeObject, User, checkPermission } from '@things-factory/auth-base'
2
+ import { checkUserHasRole } from '@things-factory/auth-base'
3
3
  import { cacheService } from '@things-factory/cache-service'
4
4
 
5
5
  import { ScenarioEngine } from '../engine/scenario-engine'
6
6
  import { Scenario } from '../service/scenario/scenario'
7
- import {
8
- ScenarioInstance,
9
- ScenarioInstanceRunResult,
10
- ScenarioInstanceStatus
11
- } from '../service/scenario-instance/scenario-instance-type'
7
+ import { ScenarioInstance, ScenarioInstanceRunResult } from '../service/scenario-instance/scenario-instance-type'
12
8
  import { Step } from '../service/step/step-type'
13
9
 
14
10
  const debug = require('debug')('things-factory:integration-base:controller:run-scenario')
@@ -16,31 +12,48 @@ const debug = require('debug')('things-factory:integration-base:controller:run-s
16
12
  async function findScenario(
17
13
  scenarioName: string,
18
14
  domain: Domain
19
- ): Promise<{ id: string; name: string; steps: Step[]; domain: Domain; privilege?: PrivilegeObject; ttl?: number }> {
15
+ ): Promise<{
16
+ id: string
17
+ ttl: number
18
+ name: string
19
+ steps: Step[]
20
+ domain: Domain
21
+ }> {
20
22
  var repository = getRepository(Scenario)
21
23
 
22
24
  var scenario = await repository.findOne({
23
25
  where: { domain: { id: domain.id }, name: scenarioName },
24
- relations: ['domain', 'steps', 'creator', 'updater']
26
+ relations: ['domain', 'steps', 'role', 'creator', 'updater']
25
27
  })
26
28
 
27
29
  if (!scenario && domain.parentId) {
28
30
  scenario = await repository.findOne({
29
31
  where: { domain: { id: domain.parentId }, name: scenarioName },
30
- relations: ['domain', 'steps', 'creator', 'updater']
32
+ relations: ['domain', 'steps', 'role', 'creator', 'updater']
31
33
  })
32
34
  }
33
35
 
34
36
  return scenario as any
35
37
  }
36
38
 
39
+ export async function checkHasRole(scenario: Partial<Scenario>, context: ResolverContext): Promise<void> {
40
+ const { domain, user } = context.state
41
+ if (!(await checkUserHasRole(scenario.roleId, domain, user))) {
42
+ throw new Error(
43
+ context.t('error.scenario run unauthorized', {
44
+ scenario: scenario.name
45
+ })
46
+ )
47
+ }
48
+ }
49
+
37
50
  export async function runScenario(
38
51
  instanceName: string,
39
52
  scenarioName: string,
40
53
  variables: any,
41
54
  context: ResolverContext
42
55
  ): Promise<ScenarioInstanceRunResult> {
43
- const { domain, user, lng, unsafeIP, prohibitedPrivileges } = context.state
56
+ const { domain, user, lng } = context.state
44
57
 
45
58
  debug('runScenario', scenarioName, instanceName, variables)
46
59
 
@@ -54,19 +67,7 @@ export async function runScenario(
54
67
  )
55
68
  }
56
69
 
57
- if (!(await checkPermission(scenario.privilege, user, domain, unsafeIP, prohibitedPrivileges))) {
58
- const { category, privilege } = scenario.privilege || {}
59
-
60
- console.error(
61
- `Unauthorized! ${category && privilege ? category + ':' + privilege + ' privilege' : 'ownership granted'} required`
62
- )
63
-
64
- throw new Error(
65
- context.t('error.scenario run unauthorized', {
66
- scenario: scenarioName
67
- })
68
- )
69
- }
70
+ await checkHasRole(scenario, context)
70
71
 
71
72
  if (scenario.ttl > 0) {
72
73
  const cachedValue = await cacheService.getFromCache(scenario.id, { domain: domain.id, variables: variables || {} })
@@ -104,7 +105,7 @@ export async function startScenario(
104
105
  variables: any,
105
106
  context: ResolverContext
106
107
  ): Promise<ScenarioInstance> {
107
- const { domain, user, lng, unsafeIP, prohibitedPrivileges } = context.state
108
+ const { domain, user, lng } = context.state
108
109
 
109
110
  debug('startScenario', instanceName, scenarioName, variables)
110
111
 
@@ -118,12 +119,7 @@ export async function startScenario(
118
119
  )
119
120
  }
120
121
 
121
- if (!(await checkPermission(scenario.privilege, user, domain, unsafeIP, prohibitedPrivileges))) {
122
- const { category, privilege } = scenario.privilege || {}
123
- throw new Error(
124
- `Unauthorized! ${category && privilege ? category + ':' + privilege + ' privilege' : 'ownership granted'} required`
125
- )
126
- }
122
+ await checkHasRole(scenario, context)
127
123
 
128
124
  instanceName = instanceName || scenarioName
129
125
  return await ScenarioEngine.load(instanceName, scenario, { domain, user, lng, variables })
@@ -152,12 +148,7 @@ export async function stopScenario(
152
148
 
153
149
  var scenario = await findScenario(scenarioInstance.scenarioName, domain)
154
150
 
155
- if (!(await checkPermission(scenario.privilege, user, domain, unsafeIP, prohibitedPrivileges))) {
156
- const { category, privilege } = scenario.privilege || {}
157
- throw new Error(
158
- `Unauthorized! ${category && privilege ? category + ':' + privilege + ' privilege' : 'ownership granted'} required`
159
- )
160
- }
151
+ await checkHasRole(scenario, context)
161
152
 
162
153
  await ScenarioEngine.unload(domain, instanceName)
163
154
 
@@ -24,7 +24,7 @@ function getSystemTimeZone() {
24
24
  }
25
25
 
26
26
  const SYSTEM_TZ = getSystemTimeZone()
27
- const systemTimestamp = format((info, opts) => {
27
+ const systemTimestamp = format((info, opts: { tz?: string }) => {
28
28
  if (opts.tz) info.timestamp = moment().tz(opts.tz).format()
29
29
  return info
30
30
  })
@@ -0,0 +1,341 @@
1
+ import { ConnectionManager } from '../connection-manager'
2
+ import { Connector } from '../types'
3
+ import { getHeadlessPool } from '../resource-pool/headless-pool'
4
+ import { Browser, Page } from 'puppeteer'
5
+
6
+ /*
7
+ Functionality of the headless-connector:
8
+ - Provides a mechanism to acquire an active session page.
9
+ - Performs login when necessary to obtain valid cookies.
10
+ - Applies these cookies to the page for session management.
11
+ - Pages are acquired from the `headlessPool`, which manages browser instances.
12
+ - During the login process, pages from the pool are used for actions like form filling and navigation.
13
+ - Valid cookies are saved after login and reused for subsequent page acquisitions.
14
+ - Users must explicitly release the page after use through the `releasePage` method.
15
+ - Released pages are returned to the `headlessPool` for reuse.
16
+ */
17
+
18
+ export class HeadlessConnector implements Connector {
19
+ async ready(connectionConfigs) {
20
+ await Promise.all(connectionConfigs.map(this.connect.bind(this)))
21
+ ConnectionManager.logger.info('headless-connector connections are ready')
22
+ }
23
+
24
+ async connect(connection) {
25
+ const {
26
+ endpoint: uri = '1',
27
+ params: {
28
+ username = '',
29
+ password = '',
30
+ loginPath = '/login',
31
+ usernameSelector = '#username',
32
+ passwordSelector = '#password',
33
+ submitSelector = '#submit',
34
+ successSelector = null,
35
+ shadowDomSelectors = '', // Comma separated shadow DOM selectors
36
+ timeout = 15000, // Default timeout for operations
37
+ retries = 3 // Default number of retries for login or page actions
38
+ } = {}
39
+ } = connection
40
+
41
+ const loginInfo = {
42
+ loginRequired: Boolean(username), // Determine if login is required
43
+ username,
44
+ password,
45
+ loginPath,
46
+ timeout,
47
+ retries,
48
+ loginSelectors: {
49
+ usernameSelector,
50
+ passwordSelector,
51
+ submitSelector,
52
+ successSelector,
53
+ shadowDomSelectors: shadowDomSelectors
54
+ .split(',')
55
+ .map(selector => selector.trim())
56
+ .filter(Boolean)
57
+ }
58
+ }
59
+
60
+ async function acquireBrowser() {
61
+ try {
62
+ const pool = getHeadlessPool()
63
+ const browser = await pool.acquire()
64
+ return browser
65
+ } catch (error) {
66
+ ConnectionManager.logger.error('Failed to acquire browser:', error)
67
+ throw error
68
+ }
69
+ }
70
+
71
+ async function releaseBrowser(browser: Browser) {
72
+ try {
73
+ const pool = getHeadlessPool()
74
+ await pool.release(browser)
75
+ } catch (error) {
76
+ ConnectionManager.logger.error('Failed to release browser:', error)
77
+ }
78
+ }
79
+
80
+ ConnectionManager.addConnectionInstance(connection, {
81
+ endpoint: connection.endpoint,
82
+ params: connection.params,
83
+ acquireSessionPage: async () => {
84
+ const browser = await acquireBrowser()
85
+ let page
86
+ let cookies = connection.cookies
87
+
88
+ try {
89
+ page = await browser.newPage()
90
+
91
+ page.on('console', async msg => {
92
+ console.log(`[browser ${msg.type()}] ${msg.text()}`)
93
+ })
94
+
95
+ await page.setRequestInterception(true)
96
+
97
+ // page.on('request', request => {
98
+ // const resourceType = request.resourceType()
99
+ // if (resourceType === 'image' || resourceType === 'stylesheet' || resourceType === 'font') {
100
+ // request.abort() // 이미지, 스타일시트, 폰트 요청 차단
101
+ // } else {
102
+ // request.continue() // 나머지 요청은 진행
103
+ // }
104
+ // })
105
+
106
+ page.on('requestfailed', request => {
107
+ console.log('Request failed:')
108
+ console.log(`- URL: ${request.url()}`)
109
+ console.log(`- Method: ${request.method()}`)
110
+ console.log(`- Failure Text: ${request.failure()?.errorText}`)
111
+ console.log(`- Headers:`, request.headers())
112
+
113
+ // POST 데이터 (필요한 경우)
114
+ if (request.postData()) {
115
+ console.log(`- Post Data: ${request.postData()}`)
116
+ }
117
+ })
118
+
119
+ if (cookies && isCookieValid(cookies)) {
120
+ await this.applyCookiesAndVerifySession(page, cookies, loginInfo)
121
+ return page
122
+ }
123
+
124
+ if (loginInfo.loginRequired) {
125
+ await this.performLogin(page, uri, loginInfo)
126
+ cookies = await page.cookies()
127
+ connection.cookies = cookies
128
+ }
129
+
130
+ return page
131
+ } catch (error) {
132
+ ConnectionManager.logger.error('Failed to acquire session page:', error)
133
+ throw error
134
+ }
135
+ },
136
+ releasePage: async (page: Page) => {
137
+ try {
138
+ if (page) {
139
+ const browser = page.browser()
140
+ await page.close()
141
+ await releaseBrowser(browser)
142
+ }
143
+ } catch (error) {
144
+ ConnectionManager.logger.error('Failed to release page:', error)
145
+ }
146
+ },
147
+ acquireBrowser,
148
+ releaseBrowser
149
+ })
150
+
151
+ ConnectionManager.logger.info(
152
+ `headless-connector connection(${connection.name}:${connection.endpoint}) is connected`
153
+ )
154
+ }
155
+
156
+ async applyCookiesAndVerifySession(page, cookies, loginInfo) {
157
+ await page.setCookie(...cookies)
158
+ await page.reload({ waitUntil: 'networkidle2', timeout: loginInfo.timeout })
159
+
160
+ if (loginInfo.loginRequired && loginInfo.loginSelectors.successSelector) {
161
+ const success = await this.resolveShadowDom(
162
+ page,
163
+ loginInfo.loginSelectors.shadowDomSelectors,
164
+ loginInfo.loginSelectors.successSelector
165
+ )
166
+ if (!success) {
167
+ throw new Error('Session invalid, login required')
168
+ }
169
+ }
170
+ }
171
+
172
+ async performLogin(page, uri, loginInfo) {
173
+ for (let attempt = 1; attempt <= loginInfo.retries; attempt++) {
174
+ try {
175
+ await page.goto(`${uri}${loginInfo.loginPath}`, { waitUntil: 'networkidle2', timeout: loginInfo.timeout })
176
+
177
+ const usernameInput = await this.resolveShadowDom(
178
+ page,
179
+ loginInfo.loginSelectors.shadowDomSelectors,
180
+ loginInfo.loginSelectors.usernameSelector
181
+ )
182
+ const passwordInput = await this.resolveShadowDom(
183
+ page,
184
+ loginInfo.loginSelectors.shadowDomSelectors,
185
+ loginInfo.loginSelectors.passwordSelector
186
+ )
187
+ const submitButton = await this.resolveShadowDom(
188
+ page,
189
+ loginInfo.loginSelectors.shadowDomSelectors,
190
+ loginInfo.loginSelectors.submitSelector
191
+ )
192
+
193
+ if (!usernameInput || !passwordInput || !submitButton) {
194
+ throw new Error('Failed to locate input elements in shadow DOM')
195
+ }
196
+
197
+ await usernameInput.type(loginInfo.username)
198
+ await passwordInput.type(loginInfo.password)
199
+
200
+ // Capture the response of the form submission
201
+ const [response] = await Promise.all([
202
+ page.waitForNavigation({ waitUntil: 'networkidle2', timeout: loginInfo.timeout }),
203
+ submitButton.click()
204
+ ])
205
+
206
+ // Check response status code
207
+ if (response) {
208
+ const status = response.status()
209
+ if (status >= 200 && status < 300) {
210
+ ConnectionManager.logger.info(`Login successful with status code: ${status}`)
211
+ return // Login successful
212
+ } else if (status >= 400) {
213
+ throw new Error(`Login failed with status code: ${status}`)
214
+ }
215
+ } else {
216
+ throw new Error('No response received during login')
217
+ }
218
+
219
+ if (loginInfo.loginSelectors.successSelector) {
220
+ const success = await this.resolveShadowDom(
221
+ page,
222
+ loginInfo.loginSelectors.shadowDomSelectors,
223
+ loginInfo.loginSelectors.successSelector
224
+ )
225
+ if (!success) {
226
+ throw new Error('Login failed: Success selector not found')
227
+ }
228
+ }
229
+ } catch (error) {
230
+ ConnectionManager.logger.warn(`Login attempt ${attempt} failed:`, error)
231
+
232
+ if (attempt === loginInfo.retries) {
233
+ throw new Error(`Login failed after ${loginInfo.retries} attempts`)
234
+ }
235
+ }
236
+ }
237
+ }
238
+
239
+ async resolveShadowDom(page, shadowSelectors, targetSelector) {
240
+ let context
241
+
242
+ if (!shadowSelectors || shadowSelectors.length === 0) {
243
+ // No Shadow DOM path; use document root as the context
244
+ context = page.mainFrame() // Puppeteer uses frames to represent document
245
+ return context.$(targetSelector) // Search directly in the document root
246
+ }
247
+
248
+ context = page // Start with the page as the context
249
+ for (const selector of shadowSelectors) {
250
+ const shadowHost = await context.$(selector)
251
+ if (!shadowHost) {
252
+ throw new Error(`Shadow host not found: ${selector}`)
253
+ }
254
+ context = await page.evaluateHandle(host => host.shadowRoot, shadowHost)
255
+ }
256
+ return context.evaluateHandle((shadowRoot, selector) => shadowRoot.querySelector(selector), targetSelector)
257
+ }
258
+
259
+ async disconnect(connection) {
260
+ const connectionInstance = ConnectionManager.getConnectionInstance(connection)
261
+ ConnectionManager.removeConnectionInstance(connection)
262
+ ConnectionManager.logger.info(`headless-connector connection(${connection.name}) is disconnected`)
263
+ }
264
+
265
+ get parameterSpec() {
266
+ return [
267
+ {
268
+ type: 'string',
269
+ name: 'username',
270
+ label: 'username'
271
+ },
272
+ {
273
+ type: 'password',
274
+ name: 'password',
275
+ label: 'password'
276
+ },
277
+ {
278
+ type: 'string',
279
+ name: 'loginPath',
280
+ label: 'login-path'
281
+ },
282
+ {
283
+ type: 'string',
284
+ name: 'usernameSelector',
285
+ label: 'username-selector'
286
+ },
287
+ {
288
+ type: 'string',
289
+ name: 'passwordSelector',
290
+ label: 'password-selector'
291
+ },
292
+ {
293
+ type: 'string',
294
+ name: 'submitSelector',
295
+ label: 'submit-selector'
296
+ },
297
+ {
298
+ type: 'string',
299
+ name: 'successSelector',
300
+ label: 'success-selector'
301
+ },
302
+ {
303
+ type: 'string',
304
+ name: 'shadowDomSelectors',
305
+ label: 'shadow-dom-selectors'
306
+ },
307
+ {
308
+ type: 'number',
309
+ name: 'timeout',
310
+ label: 'timeout',
311
+ value: 15000
312
+ },
313
+ {
314
+ type: 'number',
315
+ name: 'retries',
316
+ label: 'maximum-retries',
317
+ value: 3
318
+ }
319
+ ]
320
+ }
321
+
322
+ get taskPrefixes() {
323
+ return ['headless']
324
+ }
325
+
326
+ get description() {
327
+ return 'Headless Pool Connector with login capabilities'
328
+ }
329
+
330
+ get help() {
331
+ return 'integration/connector/headless-connector'
332
+ }
333
+ }
334
+
335
+ ConnectionManager.registerConnector('headless-connector', new HeadlessConnector())
336
+
337
+ function isCookieValid(cookies) {
338
+ if (!cookies || cookies.length === 0) return false
339
+ const now = Date.now() / 1000 // Current time in seconds
340
+ return cookies.some(cookie => cookie.expires && cookie.expires > now)
341
+ }
@@ -58,7 +58,7 @@ export class HttpConnector implements Connector {
58
58
  }
59
59
 
60
60
  get taskPrefixes() {
61
- return ['http', 'headless-post', 'headless-scrap']
61
+ return ['http']
62
62
  }
63
63
  }
64
64
 
@@ -10,3 +10,4 @@ import './oracle-connector'
10
10
  import './mysql-connector'
11
11
  import './socket-server'
12
12
  import './operato-connector'
13
+ import './headless-connector'
@@ -17,7 +17,7 @@ import { Scenario } from '../../service/scenario/scenario'
17
17
  import { ScenarioInstance } from '../../service/scenario-instance/scenario-instance-type'
18
18
 
19
19
  import { getRepository, GraphqlLocalClient, Domain } from '@things-factory/shell'
20
- import { User, checkPermission } from '@things-factory/auth-base'
20
+ import { User, checkUserHasRole } from '@things-factory/auth-base'
21
21
 
22
22
  const debug = require('debug')('things-factory:integration-base:operato-connector')
23
23
 
@@ -198,7 +198,7 @@ export class OperatoConnector implements Connector {
198
198
  }
199
199
 
200
200
  async runScenario(subscriptions: SubscriberData[], variables: any): Promise<ScenarioInstance> {
201
- const { domain, user, unsafeIP, prohibitedPrivileges } = this.context
201
+ const { domain, user } = this.context
202
202
  const { tag } = variables
203
203
 
204
204
  if (!tag) {
@@ -210,11 +210,8 @@ export class OperatoConnector implements Connector {
210
210
  throw new Error(`scenario is not found - ${tag}`)
211
211
  }
212
212
 
213
- if (!(await checkPermission(scenario.privilege, user, domain, unsafeIP, prohibitedPrivileges))) {
214
- const { category, privilege } = scenario.privilege || {}
215
- throw new Error(
216
- `Unauthorized! ${category && privilege ? category + ':' + privilege + ' privilege' : 'ownership granted'} required`
217
- )
213
+ if (!(await checkUserHasRole(scenario.roleId, domain, user))) {
214
+ throw new Error(`Unauthorized! ${scenario.name} doesn't have required role.`)
218
215
  }
219
216
 
220
217
  /* create a scenario instance */
@@ -6,5 +6,6 @@ export * from './scenario-engine'
6
6
  export * from './task-registry'
7
7
  export * from './analyzer/analyze-integration'
8
8
  export * from './edge-client'
9
+ export * from './resource-pool'
9
10
 
10
11
  export { Connector, Context, TaskHandler } from './types'