@things-factory/integration-base 9.0.28 → 9.0.32
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/README.md +513 -2
- package/dist-server/engine/task/headless-delete.d.ts +1 -0
- package/dist-server/engine/task/headless-delete.js +95 -0
- package/dist-server/engine/task/headless-delete.js.map +1 -0
- package/dist-server/engine/task/headless-get.d.ts +1 -0
- package/dist-server/engine/task/headless-get.js +96 -0
- package/dist-server/engine/task/headless-get.js.map +1 -0
- package/dist-server/engine/task/headless-patch.d.ts +1 -0
- package/dist-server/engine/task/headless-patch.js +135 -0
- package/dist-server/engine/task/headless-patch.js.map +1 -0
- package/dist-server/engine/task/headless-post.js +27 -4
- package/dist-server/engine/task/headless-post.js.map +1 -1
- package/dist-server/engine/task/headless-put.d.ts +1 -0
- package/dist-server/engine/task/headless-put.js +135 -0
- package/dist-server/engine/task/headless-put.js.map +1 -0
- package/dist-server/engine/task/index.d.ts +4 -0
- package/dist-server/engine/task/index.js +4 -0
- package/dist-server/engine/task/index.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -3
package/README.md
CHANGED
@@ -1,2 +1,513 @@
|
|
1
|
-
# integration-
|
2
|
-
|
1
|
+
# @things-factory/integration-base
|
2
|
+
|
3
|
+
A comprehensive integration framework for the Things Factory ecosystem that enables seamless connectivity with external systems and services through scenario-driven automation.
|
4
|
+
|
5
|
+
[](https://npmjs.org/package/@things-factory/integration-base)
|
6
|
+
[](https://github.com/hatiolab/things-factory/blob/main/packages/integration-base/LICENSE)
|
7
|
+
|
8
|
+
## Overview
|
9
|
+
|
10
|
+
The Integration Base module provides a robust, scenario-driven automation engine that enables complex integration workflows through a visual, step-based approach. It serves as the central nervous system for enterprise automation within the Things Factory framework.
|
11
|
+
|
12
|
+
### Key Features
|
13
|
+
|
14
|
+
- **Scenario Engine**: Visual workflow designer with 50+ built-in task types
|
15
|
+
- **Multi-Protocol Support**: Database, HTTP/REST, GraphQL, MQTT, WebSocket integrations
|
16
|
+
- **Headless Automation**: Web scraping and browser automation via Puppeteer
|
17
|
+
- **Multi-Tenant Architecture**: Domain-based isolation with role-based access control
|
18
|
+
- **Real-Time Monitoring**: GraphQL subscriptions for live status updates
|
19
|
+
- **Scheduling System**: Cron-based automated execution with timezone support
|
20
|
+
- **Edge Computing**: Distributed execution through edge appliances
|
21
|
+
|
22
|
+
## Installation
|
23
|
+
|
24
|
+
```bash
|
25
|
+
npm install @things-factory/integration-base
|
26
|
+
```
|
27
|
+
|
28
|
+
## Quick Start
|
29
|
+
|
30
|
+
### 1. Basic Scenario Configuration
|
31
|
+
|
32
|
+
```typescript
|
33
|
+
import { ScenarioEngine } from '@things-factory/integration-base'
|
34
|
+
|
35
|
+
// Create a simple HTTP integration scenario
|
36
|
+
const scenario = {
|
37
|
+
name: 'fetch-user-data',
|
38
|
+
description: 'Fetch user data from external API',
|
39
|
+
steps: [
|
40
|
+
{
|
41
|
+
name: 'fetch-users',
|
42
|
+
task: 'http-get',
|
43
|
+
connection: 'api-server',
|
44
|
+
params: {
|
45
|
+
path: '/api/users',
|
46
|
+
headers: { 'Authorization': 'Bearer ${token}' }
|
47
|
+
}
|
48
|
+
},
|
49
|
+
{
|
50
|
+
name: 'store-data',
|
51
|
+
task: 'database-query',
|
52
|
+
connection: 'main-db',
|
53
|
+
params: {
|
54
|
+
query: 'INSERT INTO users (id, name, email) VALUES (?, ?, ?)',
|
55
|
+
bindings: '${fetch-users.data}'
|
56
|
+
}
|
57
|
+
}
|
58
|
+
]
|
59
|
+
}
|
60
|
+
```
|
61
|
+
|
62
|
+
### 2. Connection Setup
|
63
|
+
|
64
|
+
```typescript
|
65
|
+
// Database connection
|
66
|
+
const dbConnection = {
|
67
|
+
name: 'main-db',
|
68
|
+
type: 'postgresql-connector',
|
69
|
+
endpoint: 'postgresql://localhost:5432/mydb',
|
70
|
+
params: {
|
71
|
+
username: 'dbuser',
|
72
|
+
password: 'dbpass',
|
73
|
+
database: 'myapp'
|
74
|
+
}
|
75
|
+
}
|
76
|
+
|
77
|
+
// HTTP API connection
|
78
|
+
const apiConnection = {
|
79
|
+
name: 'api-server',
|
80
|
+
type: 'http-connector',
|
81
|
+
endpoint: 'https://api.example.com',
|
82
|
+
params: {
|
83
|
+
timeout: 30000,
|
84
|
+
rejectUnauthorized: true
|
85
|
+
}
|
86
|
+
}
|
87
|
+
```
|
88
|
+
|
89
|
+
## Supported Connectors
|
90
|
+
|
91
|
+
### Database Connectors
|
92
|
+
- **PostgreSQL** (`postgresql-connector`)
|
93
|
+
- **MySQL** (`mysql-connector`)
|
94
|
+
- **Microsoft SQL Server** (`mssql-connector`)
|
95
|
+
- **Oracle** (`oracle-connector`)
|
96
|
+
- **SQLite** (`sqlite-connector`)
|
97
|
+
|
98
|
+
### Communication Connectors
|
99
|
+
- **HTTP/REST** (`http-connector`)
|
100
|
+
- **GraphQL** (`graphql-connector`) - Apollo Client with OAuth2 support
|
101
|
+
- **MQTT** (`mqtt-connector`) - Async message pub/sub
|
102
|
+
- **WebSocket** (`socket-server`) - Raw TCP socket communication
|
103
|
+
|
104
|
+
### Automation Connectors
|
105
|
+
- **Headless Browser** (`headless-connector`) - Puppeteer-based web automation
|
106
|
+
- **Python Runtime** (`pyrun-connector`) - Python script execution
|
107
|
+
- **Operato Integration** (`operato-connector`) - Things Factory cross-instance
|
108
|
+
|
109
|
+
### Utility Connectors
|
110
|
+
- **Echo Service** (`echo-back-connector`) - Testing and debugging
|
111
|
+
- **Proxy Connector** (`proxy-connector`) - Edge appliance delegation
|
112
|
+
|
113
|
+
## Task Types
|
114
|
+
|
115
|
+
### Data Operations
|
116
|
+
- `database-query` - Execute SQL queries with parameter binding
|
117
|
+
- `graphql-query` / `graphql-mutate` - GraphQL operations
|
118
|
+
- `data-mapper` - Transform data structures
|
119
|
+
- `jsonata` - JSONata transformations
|
120
|
+
- `csv-readline` - CSV file processing
|
121
|
+
|
122
|
+
### Communication Tasks
|
123
|
+
- `http-get` / `http-post` / `http-put` / `http-patch` / `http-delete`
|
124
|
+
- `headless-get` / `headless-post` / `headless-put` / `headless-patch` / `headless-delete`
|
125
|
+
- `mqtt-publish` / `mqtt-subscribe`
|
126
|
+
- `socket-listener` / `publish`
|
127
|
+
|
128
|
+
### Control Flow
|
129
|
+
- `goto` / `end` - Flow control
|
130
|
+
- `switch-goto` / `switch-range-goto` - Conditional branching
|
131
|
+
- `sub-scenario` - Nested scenario execution
|
132
|
+
- `empty-check` - Validation tasks
|
133
|
+
- `variables` - Variable management
|
134
|
+
|
135
|
+
### Automation & Utility
|
136
|
+
- `headless-scrap` - Web scraping
|
137
|
+
- `pyrun-execute` - Python script execution
|
138
|
+
- `sleep` / `random` - Utility functions
|
139
|
+
- `script` - Custom JavaScript execution
|
140
|
+
|
141
|
+
## Configuration
|
142
|
+
|
143
|
+
### Environment Variables
|
144
|
+
|
145
|
+
```bash
|
146
|
+
# Database connection
|
147
|
+
DB_HOST=localhost
|
148
|
+
DB_PORT=5432
|
149
|
+
DB_NAME=integration_db
|
150
|
+
DB_USERNAME=user
|
151
|
+
DB_PASSWORD=password
|
152
|
+
|
153
|
+
# Headless browser pool
|
154
|
+
HEADLESS_POOL_MIN=2
|
155
|
+
HEADLESS_POOL_MAX=10
|
156
|
+
|
157
|
+
# Logging
|
158
|
+
DEBUG=things-factory:integration-base:*
|
159
|
+
LOG_LEVEL=info
|
160
|
+
```
|
161
|
+
|
162
|
+
### Domain Configuration
|
163
|
+
|
164
|
+
```typescript
|
165
|
+
// Domain-specific environment variables
|
166
|
+
const domainConfig = {
|
167
|
+
domain: 'tenant1.example.com',
|
168
|
+
envVars: [
|
169
|
+
{ name: 'Connection::api-server::apiKey', value: 'secret-key-123' },
|
170
|
+
{ name: 'Connection::database::password', value: 'encrypted-password' }
|
171
|
+
]
|
172
|
+
}
|
173
|
+
```
|
174
|
+
|
175
|
+
## API Reference
|
176
|
+
|
177
|
+
### GraphQL Schema
|
178
|
+
|
179
|
+
```graphql
|
180
|
+
type Scenario {
|
181
|
+
id: String!
|
182
|
+
name: String!
|
183
|
+
description: String
|
184
|
+
active: Boolean!
|
185
|
+
schedule: String
|
186
|
+
timezone: String
|
187
|
+
steps: [Step!]!
|
188
|
+
}
|
189
|
+
|
190
|
+
type Connection {
|
191
|
+
id: String!
|
192
|
+
name: String!
|
193
|
+
type: String!
|
194
|
+
endpoint: String!
|
195
|
+
active: Boolean!
|
196
|
+
state: ConnectionStatus!
|
197
|
+
}
|
198
|
+
|
199
|
+
type ScenarioInstance {
|
200
|
+
id: String!
|
201
|
+
scenario: Scenario!
|
202
|
+
status: ScenarioStatus!
|
203
|
+
startedAt: String
|
204
|
+
finishedAt: String
|
205
|
+
result: JSON
|
206
|
+
}
|
207
|
+
```
|
208
|
+
|
209
|
+
### REST API Endpoints
|
210
|
+
|
211
|
+
```
|
212
|
+
GET /api/unstable/scenarios # List scenarios
|
213
|
+
POST /api/unstable/scenarios # Create scenario
|
214
|
+
PUT /api/unstable/scenarios/:id # Update scenario
|
215
|
+
DELETE /api/unstable/scenarios/:id # Delete scenario
|
216
|
+
|
217
|
+
GET /api/unstable/scenario-instances # List instances
|
218
|
+
POST /api/unstable/run-scenario # Execute scenario
|
219
|
+
POST /api/unstable/start-scenario # Start async scenario
|
220
|
+
POST /api/unstable/stop-scenario # Stop running scenario
|
221
|
+
```
|
222
|
+
|
223
|
+
## Examples
|
224
|
+
|
225
|
+
### Database Integration
|
226
|
+
|
227
|
+
```typescript
|
228
|
+
const databaseScenario = {
|
229
|
+
name: 'sync-customer-data',
|
230
|
+
steps: [
|
231
|
+
{
|
232
|
+
name: 'fetch-customers',
|
233
|
+
task: 'database-query',
|
234
|
+
connection: 'source-db',
|
235
|
+
params: {
|
236
|
+
query: 'SELECT * FROM customers WHERE updated_at > ?',
|
237
|
+
bindings: ['${lastSync}']
|
238
|
+
}
|
239
|
+
},
|
240
|
+
{
|
241
|
+
name: 'transform-data',
|
242
|
+
task: 'data-mapper',
|
243
|
+
params: {
|
244
|
+
mapping: {
|
245
|
+
customer_id: 'id',
|
246
|
+
full_name: 'name',
|
247
|
+
email_address: 'email'
|
248
|
+
}
|
249
|
+
}
|
250
|
+
},
|
251
|
+
{
|
252
|
+
name: 'insert-customers',
|
253
|
+
task: 'database-query',
|
254
|
+
connection: 'target-db',
|
255
|
+
params: {
|
256
|
+
query: 'INSERT INTO customers (id, name, email) VALUES ${fetch-customers.data}',
|
257
|
+
batchSize: 1000
|
258
|
+
}
|
259
|
+
}
|
260
|
+
]
|
261
|
+
}
|
262
|
+
```
|
263
|
+
|
264
|
+
### Web Scraping with Headless Browser
|
265
|
+
|
266
|
+
```typescript
|
267
|
+
const scrapingScenario = {
|
268
|
+
name: 'scrape-product-prices',
|
269
|
+
steps: [
|
270
|
+
{
|
271
|
+
name: 'login-to-site',
|
272
|
+
task: 'headless-post',
|
273
|
+
connection: 'ecommerce-site',
|
274
|
+
params: {
|
275
|
+
path: '/login',
|
276
|
+
contentType: 'application/json',
|
277
|
+
accessor: 'credentials'
|
278
|
+
}
|
279
|
+
},
|
280
|
+
{
|
281
|
+
name: 'scrape-products',
|
282
|
+
task: 'headless-scrap',
|
283
|
+
connection: 'ecommerce-site',
|
284
|
+
params: {
|
285
|
+
path: '/products',
|
286
|
+
selectors: [
|
287
|
+
{ text: 'productName', value: '.product-title' },
|
288
|
+
{ text: 'price', value: '.price-display' },
|
289
|
+
{ text: 'availability', value: '.stock-status' }
|
290
|
+
],
|
291
|
+
waitForSelectors: '.product-list',
|
292
|
+
waitForTimeout: 5000
|
293
|
+
}
|
294
|
+
},
|
295
|
+
{
|
296
|
+
name: 'save-results',
|
297
|
+
task: 'database-query',
|
298
|
+
connection: 'analytics-db',
|
299
|
+
params: {
|
300
|
+
query: 'INSERT INTO product_prices (name, price, availability, scraped_at) VALUES ${scrape-products.data}'
|
301
|
+
}
|
302
|
+
}
|
303
|
+
]
|
304
|
+
}
|
305
|
+
```
|
306
|
+
|
307
|
+
### MQTT Integration
|
308
|
+
|
309
|
+
```typescript
|
310
|
+
const mqttScenario = {
|
311
|
+
name: 'iot-data-processing',
|
312
|
+
steps: [
|
313
|
+
{
|
314
|
+
name: 'subscribe-sensors',
|
315
|
+
task: 'mqtt-subscribe',
|
316
|
+
connection: 'iot-broker',
|
317
|
+
params: {
|
318
|
+
topic: 'sensors/+/temperature',
|
319
|
+
qos: 1
|
320
|
+
}
|
321
|
+
},
|
322
|
+
{
|
323
|
+
name: 'validate-data',
|
324
|
+
task: 'empty-check',
|
325
|
+
params: {
|
326
|
+
accessor: 'temperature',
|
327
|
+
throwOnEmpty: true
|
328
|
+
}
|
329
|
+
},
|
330
|
+
{
|
331
|
+
name: 'store-reading',
|
332
|
+
task: 'database-query',
|
333
|
+
connection: 'timeseries-db',
|
334
|
+
params: {
|
335
|
+
query: 'INSERT INTO sensor_readings (sensor_id, temperature, timestamp) VALUES (?, ?, NOW())',
|
336
|
+
bindings: ['${deviceId}', '${temperature}']
|
337
|
+
}
|
338
|
+
},
|
339
|
+
{
|
340
|
+
name: 'alert-if-critical',
|
341
|
+
task: 'switch-range-goto',
|
342
|
+
params: {
|
343
|
+
value: '${temperature}',
|
344
|
+
cases: [
|
345
|
+
{ min: 80, max: 999, goto: 'send-alert' }
|
346
|
+
]
|
347
|
+
}
|
348
|
+
},
|
349
|
+
{
|
350
|
+
name: 'send-alert',
|
351
|
+
task: 'http-post',
|
352
|
+
connection: 'alert-service',
|
353
|
+
params: {
|
354
|
+
path: '/alerts',
|
355
|
+
contentType: 'application/json',
|
356
|
+
accessor: 'alertData'
|
357
|
+
}
|
358
|
+
}
|
359
|
+
]
|
360
|
+
}
|
361
|
+
```
|
362
|
+
|
363
|
+
## Scheduling
|
364
|
+
|
365
|
+
Configure automated scenario execution using cron expressions:
|
366
|
+
|
367
|
+
```typescript
|
368
|
+
const scheduledScenario = {
|
369
|
+
name: 'daily-data-sync',
|
370
|
+
description: 'Sync data every day at 2 AM',
|
371
|
+
schedule: '0 2 * * *', // Cron expression
|
372
|
+
timezone: 'Asia/Seoul',
|
373
|
+
active: true,
|
374
|
+
steps: [/* scenario steps */]
|
375
|
+
}
|
376
|
+
```
|
377
|
+
|
378
|
+
## Security
|
379
|
+
|
380
|
+
### Authentication & Authorization
|
381
|
+
- **Role-based access control** (RBAC) integration
|
382
|
+
- **Domain-based isolation** for multi-tenant environments
|
383
|
+
- **OAuth2 authentication** for external GraphQL services
|
384
|
+
- **Encrypted parameter storage** for sensitive configuration
|
385
|
+
|
386
|
+
### Best Practices
|
387
|
+
- Store sensitive data in domain environment variables
|
388
|
+
- Use parameter encryption for passwords and API keys
|
389
|
+
- Implement IP-based access control where needed
|
390
|
+
- Regular security audits of scenario configurations
|
391
|
+
|
392
|
+
## Monitoring & Debugging
|
393
|
+
|
394
|
+
### Logging
|
395
|
+
```bash
|
396
|
+
# Enable debug logging
|
397
|
+
DEBUG=things-factory:integration-base:* npm start
|
398
|
+
|
399
|
+
# Specific component logging
|
400
|
+
DEBUG=things-factory:integration-base:scenario-engine npm start
|
401
|
+
DEBUG=things-factory:integration-base:connector:* npm start
|
402
|
+
```
|
403
|
+
|
404
|
+
### Real-time Monitoring
|
405
|
+
```graphql
|
406
|
+
# Subscribe to scenario status updates
|
407
|
+
subscription {
|
408
|
+
scenarioInstanceStatusUpdated {
|
409
|
+
id
|
410
|
+
status
|
411
|
+
progress
|
412
|
+
result
|
413
|
+
error
|
414
|
+
}
|
415
|
+
}
|
416
|
+
|
417
|
+
# Subscribe to connection status
|
418
|
+
subscription {
|
419
|
+
connectionStatusUpdated {
|
420
|
+
name
|
421
|
+
state
|
422
|
+
lastConnected
|
423
|
+
}
|
424
|
+
}
|
425
|
+
```
|
426
|
+
|
427
|
+
## Development
|
428
|
+
|
429
|
+
### Building from Source
|
430
|
+
|
431
|
+
```bash
|
432
|
+
# Clone the repository
|
433
|
+
git clone https://github.com/hatiolab/things-factory.git
|
434
|
+
cd things-factory/packages/integration-base
|
435
|
+
|
436
|
+
# Install dependencies
|
437
|
+
npm install
|
438
|
+
|
439
|
+
# Build the module
|
440
|
+
npm run build
|
441
|
+
|
442
|
+
# Run tests
|
443
|
+
npm test
|
444
|
+
```
|
445
|
+
|
446
|
+
### Creating Custom Connectors
|
447
|
+
|
448
|
+
```typescript
|
449
|
+
import { Connector } from '@things-factory/integration-base'
|
450
|
+
|
451
|
+
export class CustomConnector implements Connector {
|
452
|
+
async connect(connection) {
|
453
|
+
// Implementation
|
454
|
+
}
|
455
|
+
|
456
|
+
async disconnect(connection) {
|
457
|
+
// Cleanup
|
458
|
+
}
|
459
|
+
|
460
|
+
get parameterSpec() {
|
461
|
+
return [
|
462
|
+
{ type: 'string', name: 'apiKey', label: 'API Key' },
|
463
|
+
{ type: 'number', name: 'timeout', label: 'Timeout (ms)' }
|
464
|
+
]
|
465
|
+
}
|
466
|
+
|
467
|
+
get taskPrefixes() {
|
468
|
+
return ['custom']
|
469
|
+
}
|
470
|
+
}
|
471
|
+
```
|
472
|
+
|
473
|
+
### Creating Custom Tasks
|
474
|
+
|
475
|
+
```typescript
|
476
|
+
import { TaskRegistry } from '@things-factory/integration-base'
|
477
|
+
|
478
|
+
async function CustomTask(step, context) {
|
479
|
+
const { params } = step
|
480
|
+
const { logger, data, domain } = context
|
481
|
+
|
482
|
+
// Task implementation
|
483
|
+
return { data: result }
|
484
|
+
}
|
485
|
+
|
486
|
+
CustomTask.parameterSpec = [
|
487
|
+
{ type: 'string', name: 'parameter1', label: 'Parameter 1' }
|
488
|
+
]
|
489
|
+
|
490
|
+
TaskRegistry.registerTaskHandler('custom-task', CustomTask)
|
491
|
+
```
|
492
|
+
|
493
|
+
## Contributing
|
494
|
+
|
495
|
+
1. Fork the repository
|
496
|
+
2. Create a feature branch
|
497
|
+
3. Make your changes
|
498
|
+
4. Add tests for new functionality
|
499
|
+
5. Submit a pull request
|
500
|
+
|
501
|
+
## License
|
502
|
+
|
503
|
+
MIT © [Hatiolab](https://github.com/hatiolab)
|
504
|
+
|
505
|
+
## Support
|
506
|
+
|
507
|
+
- [Documentation](https://docs.things-factory.com/integration-base)
|
508
|
+
- [Issue Tracker](https://github.com/hatiolab/things-factory/issues)
|
509
|
+
- [Discussions](https://github.com/hatiolab/things-factory/discussions)
|
510
|
+
|
511
|
+
---
|
512
|
+
|
513
|
+
**Note**: This module is part of the Things Factory ecosystem. For complete setup and configuration, refer to the main [Things Factory documentation](https://docs.things-factory.com).
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -0,0 +1,95 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
const tslib_1 = require("tslib");
|
4
|
+
const https_1 = tslib_1.__importDefault(require("https"));
|
5
|
+
const url_1 = require("url");
|
6
|
+
const task_registry_1 = require("../task-registry");
|
7
|
+
const connection_manager_1 = require("../connection-manager");
|
8
|
+
async function HeadlessDelete(step, { logger, data, domain }) {
|
9
|
+
const { connection: connectionName, params: stepOptions } = step;
|
10
|
+
const { headers: requestHeaders, path, queryParams } = stepOptions || {};
|
11
|
+
const connection = await connection_manager_1.ConnectionManager.getConnectionInstanceByName(domain, connectionName);
|
12
|
+
if (!connection) {
|
13
|
+
throw new Error(`Connection '${connectionName}' is not established.`);
|
14
|
+
}
|
15
|
+
const { endpoint, params: connectionParams, acquireSessionPage, releasePage } = connection;
|
16
|
+
const headers = {
|
17
|
+
...requestHeaders
|
18
|
+
};
|
19
|
+
const options = {
|
20
|
+
method: 'DELETE',
|
21
|
+
headers
|
22
|
+
};
|
23
|
+
const { rejectUnauthorized } = connectionParams;
|
24
|
+
if (!rejectUnauthorized) {
|
25
|
+
const httpsAgent = new https_1.default.Agent({
|
26
|
+
rejectUnauthorized
|
27
|
+
});
|
28
|
+
options.agent = httpsAgent;
|
29
|
+
}
|
30
|
+
const page = await acquireSessionPage();
|
31
|
+
try {
|
32
|
+
page.on('console', async (msg) => {
|
33
|
+
console.log(`[browser ${msg.type()}] ${msg.text()}`);
|
34
|
+
});
|
35
|
+
page.on('requestfailed', request => {
|
36
|
+
console.log('Request failed:', request.url());
|
37
|
+
});
|
38
|
+
await page.goto(endpoint, { waitUntil: 'networkidle2' });
|
39
|
+
// URL 구성 - queryParams가 있으면 추가
|
40
|
+
let requestUrl = new url_1.URL(path, endpoint);
|
41
|
+
if (queryParams && typeof queryParams === 'object') {
|
42
|
+
Object.keys(queryParams).forEach(key => {
|
43
|
+
if (queryParams[key] !== null && queryParams[key] !== undefined) {
|
44
|
+
requestUrl.searchParams.append(key, String(queryParams[key]));
|
45
|
+
}
|
46
|
+
});
|
47
|
+
}
|
48
|
+
const response = await page.evaluate(async (urlString, options) => {
|
49
|
+
const response = await fetch(urlString, options);
|
50
|
+
if (!response.ok) {
|
51
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
52
|
+
}
|
53
|
+
const contentType = response.headers.get('content-type') || '';
|
54
|
+
if (contentType.includes('application/json')) {
|
55
|
+
return await response.json();
|
56
|
+
}
|
57
|
+
else if (contentType.includes('text/')) {
|
58
|
+
return await response.text();
|
59
|
+
}
|
60
|
+
else {
|
61
|
+
// 응답이 없는 경우 (204 No Content 등)
|
62
|
+
return null;
|
63
|
+
}
|
64
|
+
}, requestUrl.toString(), options);
|
65
|
+
return {
|
66
|
+
data: response
|
67
|
+
};
|
68
|
+
}
|
69
|
+
catch (error) {
|
70
|
+
logger.error('Error in HeadlessDelete:', error);
|
71
|
+
throw error;
|
72
|
+
}
|
73
|
+
finally {
|
74
|
+
await releasePage(page);
|
75
|
+
}
|
76
|
+
}
|
77
|
+
HeadlessDelete.parameterSpec = [
|
78
|
+
{
|
79
|
+
type: 'string',
|
80
|
+
name: 'path',
|
81
|
+
label: 'path'
|
82
|
+
},
|
83
|
+
{
|
84
|
+
type: 'http-headers',
|
85
|
+
name: 'headers',
|
86
|
+
label: 'headers'
|
87
|
+
},
|
88
|
+
{
|
89
|
+
type: 'options',
|
90
|
+
name: 'queryParams',
|
91
|
+
label: 'query-parameters'
|
92
|
+
}
|
93
|
+
];
|
94
|
+
task_registry_1.TaskRegistry.registerTaskHandler('headless-delete', HeadlessDelete);
|
95
|
+
//# sourceMappingURL=headless-delete.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"headless-delete.js","sourceRoot":"","sources":["../../../server/engine/task/headless-delete.ts"],"names":[],"mappings":"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","sourcesContent":["import https from 'https'\nimport { URL } from 'url'\n\nimport { TaskRegistry } from '../task-registry'\nimport { ConnectionManager } from '../connection-manager'\n\nasync function HeadlessDelete(step, { logger, data, domain }) {\n const { connection: connectionName, params: stepOptions } = step\n const { headers: requestHeaders, path, queryParams } = stepOptions || {}\n\n const connection = await ConnectionManager.getConnectionInstanceByName(domain, connectionName)\n\n if (!connection) {\n throw new Error(`Connection '${connectionName}' is not established.`)\n }\n\n const { endpoint, params: connectionParams, acquireSessionPage, releasePage } = connection\n\n const headers = {\n ...requestHeaders\n }\n\n const options = {\n method: 'DELETE',\n headers\n } as any\n\n const { rejectUnauthorized } = connectionParams\n\n if (!rejectUnauthorized) {\n const httpsAgent = new https.Agent({\n rejectUnauthorized\n })\n options.agent = httpsAgent\n }\n\n const page = await acquireSessionPage()\n\n try {\n page.on('console', async msg => {\n console.log(`[browser ${msg.type()}] ${msg.text()}`)\n })\n\n page.on('requestfailed', request => {\n console.log('Request failed:', request.url())\n })\n\n await page.goto(endpoint, { waitUntil: 'networkidle2' })\n\n // URL 구성 - queryParams가 있으면 추가\n let requestUrl = new URL(path, endpoint)\n if (queryParams && typeof queryParams === 'object') {\n Object.keys(queryParams).forEach(key => {\n if (queryParams[key] !== null && queryParams[key] !== undefined) {\n requestUrl.searchParams.append(key, String(queryParams[key]))\n }\n })\n }\n\n const response = await page.evaluate(\n async (urlString, options) => {\n const response = await fetch(urlString, options)\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`)\n }\n\n const contentType = response.headers.get('content-type') || ''\n \n if (contentType.includes('application/json')) {\n return await response.json()\n } else if (contentType.includes('text/')) {\n return await response.text()\n } else {\n // 응답이 없는 경우 (204 No Content 등)\n return null\n }\n },\n requestUrl.toString(),\n options\n )\n\n return {\n data: response\n }\n } catch (error) {\n logger.error('Error in HeadlessDelete:', error)\n throw error\n } finally {\n await releasePage(page)\n }\n}\n\nHeadlessDelete.parameterSpec = [\n {\n type: 'string',\n name: 'path',\n label: 'path'\n },\n {\n type: 'http-headers',\n name: 'headers',\n label: 'headers'\n },\n {\n type: 'options',\n name: 'queryParams',\n label: 'query-parameters'\n }\n]\n\nTaskRegistry.registerTaskHandler('headless-delete', HeadlessDelete)"]}
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|