@things-factory/integration-base 9.0.0-beta.70 → 9.0.0-beta.76

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.
@@ -1 +1,85 @@
1
+ /**
2
+ * MQTT Subscribe Task
3
+ * ====================
4
+ *
5
+ * Pull-Based Message Consumption on MQTT:
6
+ * ---------------------------------------
7
+ * While MQTT is inherently a push-based protocol, this task implements a controlled,
8
+ * pull-style message consumption model. A message is only processed when the task is
9
+ * explicitly invoked (i.e., the consumer actively pulls one message at a time).
10
+ * This enables fine-grained control over message flow and avoids unintended buffering.
11
+ *
12
+ * Overview:
13
+ * ---------
14
+ * This task connects to a specified MQTT broker and subscribes to a given topic.
15
+ * It waits for a single message to arrive, resolves the result, and then removes
16
+ * its internal message handler. It is designed to be reentrant — each execution
17
+ * of this task handles exactly one message.
18
+ *
19
+ * This model is useful for applications where message processing must happen
20
+ * sequentially or be synchronized with external state (e.g., workflows or state machines).
21
+ *
22
+ * Key Features:
23
+ * -------------
24
+ * - One-message-per-invocation: each task call resolves with a single message.
25
+ * - Stateless message flow: the system does not queue or buffer messages locally.
26
+ * - Supports automatic re-subscription on reconnect, improving robustness in unstable networks.
27
+ * - Uses broker-level guarantees (QoS, retain) for durability and delivery control.
28
+ * - Supports multiple data formats (JSON, plain text) and adjustable QoS levels.
29
+ *
30
+ * MQTT Reconnection Handling:
31
+ * ---------------------------
32
+ * The task listens for 'connect' events on the MQTT client. When the client reconnects
33
+ * (e.g., after a dropped network), it automatically re-subscribes to all previously
34
+ * tracked topics. This ensures consistent behavior even in long-running systems or
35
+ * unstable network environments.
36
+ *
37
+ * If a disconnect occurs while a task is waiting for a message, the Promise remains
38
+ * pending until a message is received after reconnection, or until manually cleaned up
39
+ * via the task closure mechanism.
40
+ *
41
+ * How It Works:
42
+ * -------------
43
+ * 1. Retrieve an MQTT client using a named connection.
44
+ * 2. Subscribe to the specified topic (only once per topic per workflow).
45
+ * 3. Register a one-time message handler for the topic.
46
+ * 4. When a message is received:
47
+ * - It is parsed into the requested data format.
48
+ * - The task resolves with the parsed message.
49
+ * - The handler is immediately removed.
50
+ * 5. Re-subscribe to all topics upon reconnect.
51
+ * 6. Clean up all resources (subscription, handler, resolver) on task termination.
52
+ *
53
+ * Assumptions:
54
+ * ------------
55
+ * - Broker reliability is enforced via QoS ≥ 1 and/or retained messages.
56
+ * - Message loss may occur if a message arrives before re-invocation, unless the broker retains it.
57
+ * - The task caller is responsible for repeated invocations to consume additional messages.
58
+ * - MQTT clients are externally managed and assumed to reconnect automatically.
59
+ *
60
+ * Parameters:
61
+ * -----------
62
+ * - topic (string): MQTT topic to subscribe to.
63
+ * - dataFormat ('json' | 'text'): Describes how to parse the incoming message payload.
64
+ * - qos (0 | 1 | 2): Quality of Service level for the subscription (default: 1).
65
+ *
66
+ * Returns:
67
+ * --------
68
+ * - A Promise that resolves with the first received message:
69
+ * { data: any }
70
+ * - If the task is terminated before receiving a message:
71
+ * { data: null, terminated: true }
72
+ *
73
+ * Example:
74
+ * --------
75
+ * await MqttSubscribe({
76
+ * connection: 'my-mqtt',
77
+ * params: {
78
+ * topic: 'sensor/temperature',
79
+ * dataFormat: 'json',
80
+ * qos: 1
81
+ * },
82
+ * name: 'readTemperature'
83
+ * }, context)
84
+ */
1
85
  export {};
@@ -1,16 +1,98 @@
1
1
  "use strict";
2
+ /**
3
+ * MQTT Subscribe Task
4
+ * ====================
5
+ *
6
+ * Pull-Based Message Consumption on MQTT:
7
+ * ---------------------------------------
8
+ * While MQTT is inherently a push-based protocol, this task implements a controlled,
9
+ * pull-style message consumption model. A message is only processed when the task is
10
+ * explicitly invoked (i.e., the consumer actively pulls one message at a time).
11
+ * This enables fine-grained control over message flow and avoids unintended buffering.
12
+ *
13
+ * Overview:
14
+ * ---------
15
+ * This task connects to a specified MQTT broker and subscribes to a given topic.
16
+ * It waits for a single message to arrive, resolves the result, and then removes
17
+ * its internal message handler. It is designed to be reentrant — each execution
18
+ * of this task handles exactly one message.
19
+ *
20
+ * This model is useful for applications where message processing must happen
21
+ * sequentially or be synchronized with external state (e.g., workflows or state machines).
22
+ *
23
+ * Key Features:
24
+ * -------------
25
+ * - One-message-per-invocation: each task call resolves with a single message.
26
+ * - Stateless message flow: the system does not queue or buffer messages locally.
27
+ * - Supports automatic re-subscription on reconnect, improving robustness in unstable networks.
28
+ * - Uses broker-level guarantees (QoS, retain) for durability and delivery control.
29
+ * - Supports multiple data formats (JSON, plain text) and adjustable QoS levels.
30
+ *
31
+ * MQTT Reconnection Handling:
32
+ * ---------------------------
33
+ * The task listens for 'connect' events on the MQTT client. When the client reconnects
34
+ * (e.g., after a dropped network), it automatically re-subscribes to all previously
35
+ * tracked topics. This ensures consistent behavior even in long-running systems or
36
+ * unstable network environments.
37
+ *
38
+ * If a disconnect occurs while a task is waiting for a message, the Promise remains
39
+ * pending until a message is received after reconnection, or until manually cleaned up
40
+ * via the task closure mechanism.
41
+ *
42
+ * How It Works:
43
+ * -------------
44
+ * 1. Retrieve an MQTT client using a named connection.
45
+ * 2. Subscribe to the specified topic (only once per topic per workflow).
46
+ * 3. Register a one-time message handler for the topic.
47
+ * 4. When a message is received:
48
+ * - It is parsed into the requested data format.
49
+ * - The task resolves with the parsed message.
50
+ * - The handler is immediately removed.
51
+ * 5. Re-subscribe to all topics upon reconnect.
52
+ * 6. Clean up all resources (subscription, handler, resolver) on task termination.
53
+ *
54
+ * Assumptions:
55
+ * ------------
56
+ * - Broker reliability is enforced via QoS ≥ 1 and/or retained messages.
57
+ * - Message loss may occur if a message arrives before re-invocation, unless the broker retains it.
58
+ * - The task caller is responsible for repeated invocations to consume additional messages.
59
+ * - MQTT clients are externally managed and assumed to reconnect automatically.
60
+ *
61
+ * Parameters:
62
+ * -----------
63
+ * - topic (string): MQTT topic to subscribe to.
64
+ * - dataFormat ('json' | 'text'): Describes how to parse the incoming message payload.
65
+ * - qos (0 | 1 | 2): Quality of Service level for the subscription (default: 1).
66
+ *
67
+ * Returns:
68
+ * --------
69
+ * - A Promise that resolves with the first received message:
70
+ * { data: any }
71
+ * - If the task is terminated before receiving a message:
72
+ * { data: null, terminated: true }
73
+ *
74
+ * Example:
75
+ * --------
76
+ * await MqttSubscribe({
77
+ * connection: 'my-mqtt',
78
+ * params: {
79
+ * topic: 'sensor/temperature',
80
+ * dataFormat: 'json',
81
+ * qos: 1
82
+ * },
83
+ * name: 'readTemperature'
84
+ * }, context)
85
+ */
2
86
  Object.defineProperty(exports, "__esModule", { value: true });
3
- const tslib_1 = require("tslib");
4
- const async_mqtt_1 = tslib_1.__importDefault(require("async-mqtt"));
5
87
  const task_registry_js_1 = require("../task-registry.js");
6
88
  const connection_manager_js_1 = require("../connection-manager.js");
7
89
  function convertDataFormat(data, format) {
8
- if (format == 'json') {
90
+ if (format === 'json') {
9
91
  try {
10
92
  return JSON.parse(data);
11
93
  }
12
94
  catch (e) {
13
- console.error('JSON 파싱 오류:', e.message);
95
+ console.error('JSON parse error:', e.message);
14
96
  return data.toString();
15
97
  }
16
98
  }
@@ -18,180 +100,101 @@ function convertDataFormat(data, format) {
18
100
  return data.toString();
19
101
  }
20
102
  }
21
- // MQTT 연결을 위한 브로커 관리 클래스
22
- class MqttBrokerManager {
23
- // 브로커 연결 (또는 기존 연결 반환)
24
- static async getBroker(uri, options) {
25
- const brokerKey = `${uri}_${JSON.stringify(options || {})}`;
26
- if (!this.brokers[brokerKey]) {
27
- const client = await async_mqtt_1.default.connectAsync(uri, options);
28
- this.brokers[brokerKey] = {
29
- client,
30
- topics: new Set(),
31
- messageHandlers: new Map()
32
- };
33
- // 메시지 수신 핸들러
34
- client.on('message', (topic, message) => {
35
- // 해당 토픽에 등록된 핸들러가 있으면 호출
36
- this.brokers[brokerKey].messageHandlers.forEach((handler, handlerId) => {
37
- if (handlerId.startsWith(`${topic}:`)) {
38
- handler(topic, message);
39
- }
40
- });
41
- });
42
- }
43
- return this.brokers[brokerKey];
44
- }
45
- // 토픽 구독 등록
46
- static async subscribe(brokerKey, topic) {
47
- const broker = this.brokers[brokerKey];
48
- if (!broker) {
49
- throw new Error(`브로커가 연결되지 않음: ${brokerKey}`);
50
- }
51
- // 새 토픽인 경우 구독
52
- if (!broker.topics.has(topic)) {
53
- await broker.client.subscribe(topic);
54
- broker.topics.add(topic);
55
- }
56
- }
57
- // 메시지 핸들러 등록
58
- static registerMessageHandler(brokerKey, topic, handlerId, handler) {
59
- const broker = this.brokers[brokerKey];
60
- if (!broker) {
61
- throw new Error(`브로커가 연결되지 않음: ${brokerKey}`);
62
- }
63
- // 핸들러 ID는 topic:handlerId 형식으로 저장
64
- const fullHandlerId = `${topic}:${handlerId}`;
65
- broker.messageHandlers.set(fullHandlerId, handler);
66
- return () => {
67
- // 핸들러 제거 함수 반환
68
- broker.messageHandlers.delete(fullHandlerId);
69
- };
70
- }
71
- // 연결 종료
72
- static async disconnect(brokerKey) {
73
- const broker = this.brokers[brokerKey];
74
- if (broker) {
75
- await broker.client.end();
76
- delete this.brokers[brokerKey];
77
- }
78
- }
79
- // 브로커 키 생성 유틸리티
80
- static getBrokerKey(uri, options) {
81
- return `${uri}_${JSON.stringify(options || {})}`;
82
- }
83
- }
84
- MqttBrokerManager.brokers = {};
85
103
  async function MqttSubscribe(step, context) {
86
- const { connection: connectionName, params: { topic, dataFormat }, name: stepName } = step;
104
+ var _a, _b, _c;
105
+ const { connection: connectionName, params: { topic, dataFormat, qos = 1 }, name: stepName } = step;
87
106
  const { domain, logger, closures } = context;
88
- // MQTT 브로커 접속 정보 가져오기
89
- const connectionInstance = connection_manager_js_1.ConnectionManager.getConnectionInstanceByName(domain, connectionName);
90
- const { client } = connectionInstance;
91
- if (!topic) {
92
- throw Error(`토픽이 지정되지 않음: ${connectionName}`);
93
- }
94
- // 구독자 ID 생성 (도메인, 연결명, 토픽, 스텝명 조합)
107
+ const { client } = connection_manager_js_1.ConnectionManager.getConnectionInstanceByName(domain, connectionName);
108
+ if (!client)
109
+ throw new Error(`connection not found: ${connectionName}`);
110
+ if (!topic)
111
+ throw new Error(`Missing topic for connection: ${connectionName}`);
95
112
  const subscriberId = `${domain}_${connectionName}_${topic}_${stepName}`;
96
- try {
97
- // 토픽 구독 등록
98
- await MqttBrokerManager.subscribe(client, topic);
99
- logger.info(`토픽 구독 완료: ${topic}`);
100
- // 리졸버 저장소 초기화
101
- if (!context.__mqtt_resolvers) {
102
- context.__mqtt_resolvers = new Map();
103
- }
104
- // 클로저에 연결 종료 함수 등록
105
- if (!context.__mqtt_connections) {
106
- context.__mqtt_connections = new Set();
107
- }
108
- if (!context.__mqtt_handlers) {
109
- context.__mqtt_handlers = new Map();
110
- }
111
- // 연결 추적 (중복 종료 방지)
112
- if (!context.__mqtt_connections.has(connectionName)) {
113
- context.__mqtt_connections.add(connectionName);
114
- // 연결 종료 함수 등록
115
- closures.push(async () => {
113
+ (_a = context.__mqtt_subscriptions) !== null && _a !== void 0 ? _a : (context.__mqtt_subscriptions = new Set());
114
+ (_b = context.__mqtt_resolvers) !== null && _b !== void 0 ? _b : (context.__mqtt_resolvers = new Map());
115
+ (_c = context.__mqtt_handlers) !== null && _c !== void 0 ? _c : (context.__mqtt_handlers = new Map());
116
+ // Setup MQTT client reconnect/resubscribe handlers only once
117
+ if (!context.__mqtt_event_hooked) {
118
+ client.on('connect', async () => {
119
+ logger.info('[MQTT] Reconnected. Resubscribing to topics...');
120
+ for (const t of context.__mqtt_subscriptions) {
116
121
  try {
117
- // 핸들러 모두 제거
118
- if (context.__mqtt_handlers) {
119
- context.__mqtt_handlers.forEach(removeHandler => {
120
- removeHandler();
121
- });
122
- context.__mqtt_handlers.clear();
123
- }
124
- // 대기 중인 모든 Promise 해결
125
- if (context.__mqtt_resolvers) {
126
- context.__mqtt_resolvers.forEach(resolver => {
127
- resolver({ data: null, terminated: true });
128
- });
129
- context.__mqtt_resolvers.clear();
130
- }
131
- // 브로커 연결 종료는 하지 않음
132
- logger.info(`MQTT 구독자 종료: ${connectionName}`);
122
+ await client.subscribe(t, { qos });
123
+ logger.info(`[MQTT] Resubscribed to topic: ${t}`);
133
124
  }
134
125
  catch (e) {
135
- logger.error(`MQTT 구독자 종료 오류: ${e.message}`);
126
+ logger.error(`[MQTT] Failed to resubscribe to topic ${t}: ${e.message}`);
136
127
  }
137
- });
138
- }
139
- // Promise로 메시지 수신 대기
140
- return new Promise(resolve => {
141
- var _a;
142
- // 이 태스크의 resolver 저장
143
- if (context.__mqtt_resolvers) {
144
- context.__mqtt_resolvers.set(subscriberId, resolve);
145
128
  }
146
- // 이미 등록된 핸들러가 있으면 제거
147
- if ((_a = context.__mqtt_handlers) === null || _a === void 0 ? void 0 : _a.has(subscriberId)) {
148
- const removeHandler = context.__mqtt_handlers.get(subscriberId);
149
- if (removeHandler) {
150
- removeHandler();
151
- }
129
+ });
130
+ client.on('offline', () => {
131
+ logger.warn('[MQTT] Client offline');
132
+ });
133
+ client.on('close', () => {
134
+ logger.warn('[MQTT] Connection closed');
135
+ });
136
+ client.on('error', err => {
137
+ logger.error(`[MQTT] Error: ${err.message}`);
138
+ });
139
+ context.__mqtt_event_hooked = true;
140
+ }
141
+ // Subscribe if not already done
142
+ if (!context.__mqtt_subscriptions.has(topic)) {
143
+ await client.subscribe(topic, { qos });
144
+ context.__mqtt_subscriptions.add(topic);
145
+ logger.info(`Subscribed to topic: ${topic} (QoS: ${qos})`);
146
+ }
147
+ // Remove previous handler for this step
148
+ if (context.__mqtt_handlers.has(subscriberId)) {
149
+ const removeHandler = context.__mqtt_handlers.get(subscriberId);
150
+ removeHandler === null || removeHandler === void 0 ? void 0 : removeHandler();
151
+ context.__mqtt_handlers.delete(subscriberId);
152
+ }
153
+ return new Promise(resolve => {
154
+ context.__mqtt_resolvers.set(subscriberId, resolve);
155
+ const messageHandler = (messageTopic, message) => {
156
+ if (messageTopic !== topic)
157
+ return;
158
+ const converted = convertDataFormat(message, dataFormat);
159
+ const resolver = context.__mqtt_resolvers.get(subscriberId);
160
+ if (resolver) {
161
+ context.__mqtt_resolvers.delete(subscriberId);
162
+ resolver({ data: converted });
163
+ client.removeListener('message', messageHandler);
164
+ context.__mqtt_handlers.delete(subscriberId);
152
165
  }
153
- // 새로운 메시지 핸들러 등록
154
- const removeHandler = MqttBrokerManager.registerMessageHandler(client, topic, subscriberId, (messageTopic, message) => {
155
- var _a, _b;
156
- try {
157
- // 메시지 변환
158
- const convertedMessage = convertDataFormat(message, dataFormat);
159
- // resolver 가져오기 및 삭제
160
- if ((_a = context.__mqtt_resolvers) === null || _a === void 0 ? void 0 : _a.has(subscriberId)) {
161
- const resolver = context.__mqtt_resolvers.get(subscriberId);
162
- context.__mqtt_resolvers.delete(subscriberId);
163
- // 이 태스크에 대한 핸들러 제거 (한 번만 실행되도록)
164
- if ((_b = context.__mqtt_handlers) === null || _b === void 0 ? void 0 : _b.has(subscriberId)) {
165
- const removeHandler = context.__mqtt_handlers.get(subscriberId);
166
- if (removeHandler) {
167
- removeHandler();
168
- }
169
- context.__mqtt_handlers.delete(subscriberId);
170
- }
171
- // Promise 해결
172
- if (resolver) {
173
- resolver({
174
- data: convertedMessage
175
- });
176
- }
177
- }
166
+ };
167
+ client.on('message', messageHandler);
168
+ context.__mqtt_handlers.set(subscriberId, () => {
169
+ client.removeListener('message', messageHandler);
170
+ });
171
+ closures.push(async () => {
172
+ var _a, _b, _c;
173
+ try {
174
+ if ((_a = context.__mqtt_subscriptions) === null || _a === void 0 ? void 0 : _a.has(topic)) {
175
+ await client.unsubscribe(topic);
176
+ context.__mqtt_subscriptions.delete(topic);
177
+ logger.info(`Unsubscribed from topic: ${topic}`);
178
178
  }
179
- catch (error) {
180
- logger.error(`메시지 처리 오류: ${error.message}`);
179
+ if ((_b = context.__mqtt_handlers) === null || _b === void 0 ? void 0 : _b.has(subscriberId)) {
180
+ const remove = context.__mqtt_handlers.get(subscriberId);
181
+ remove === null || remove === void 0 ? void 0 : remove();
182
+ context.__mqtt_handlers.delete(subscriberId);
181
183
  }
182
- });
183
- // 핸들러 제거 함수 저장
184
- if (context.__mqtt_handlers) {
185
- context.__mqtt_handlers.set(subscriberId, removeHandler);
184
+ if ((_c = context.__mqtt_resolvers) === null || _c === void 0 ? void 0 : _c.has(subscriberId)) {
185
+ const resolver = context.__mqtt_resolvers.get(subscriberId);
186
+ resolver === null || resolver === void 0 ? void 0 : resolver({ data: null, terminated: true });
187
+ context.__mqtt_resolvers.delete(subscriberId);
188
+ }
189
+ }
190
+ catch (e) {
191
+ logger.error(`MQTT cleanup error: ${e.message}`);
186
192
  }
187
- logger.info(`MQTT 메시지 대기 중: ${topic}`);
188
193
  });
189
- }
190
- catch (e) {
191
- logger.error(`MQTT 구독 오류: ${e.message}`);
192
- throw e;
193
- }
194
+ logger.info(`Waiting for MQTT message on topic: ${topic}`);
195
+ });
194
196
  }
197
+ // Task parameter definitions for UI or DSL support
195
198
  MqttSubscribe.parameterSpec = [
196
199
  {
197
200
  type: 'string',
@@ -204,18 +207,27 @@ MqttSubscribe.parameterSpec = [
204
207
  name: 'dataFormat',
205
208
  property: {
206
209
  options: [
207
- {
208
- display: 'Plain Text',
209
- value: 'text'
210
- },
211
- {
212
- display: 'JSON',
213
- value: 'json'
214
- }
210
+ { display: '', value: undefined },
211
+ { display: 'Plain Text', value: 'text' },
212
+ { display: 'JSON', value: 'json' }
213
+ ]
214
+ }
215
+ },
216
+ {
217
+ type: 'select',
218
+ label: 'QoS',
219
+ name: 'qos',
220
+ property: {
221
+ options: [
222
+ { display: '', value: undefined },
223
+ { display: '0 (at most once)', value: 0 },
224
+ { display: '1 (at least once)', value: 1 },
225
+ { display: '2 (exactly once)', value: 2 }
215
226
  ]
216
227
  }
217
228
  }
218
229
  ];
219
230
  MqttSubscribe.help = 'integration/task/mqtt-subscribe';
231
+ // Register task with runtime registry
220
232
  task_registry_js_1.TaskRegistry.registerTaskHandler('mqtt-subscribe', MqttSubscribe);
221
233
  //# sourceMappingURL=mqtt-subscribe.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mqtt-subscribe.js","sourceRoot":"","sources":["../../../server/engine/task/mqtt-subscribe.ts"],"names":[],"mappings":"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","sourcesContent":["import mqtt from 'async-mqtt'\n\nimport { TaskRegistry } from '../task-registry.js'\nimport { ConnectionManager } from '../connection-manager.js'\nimport { InputStep } from '../../service/step/step-type.js'\nimport { Context } from '../types.js'\n\nfunction convertDataFormat(data, format) {\n if (format == 'json') {\n try {\n return JSON.parse(data)\n } catch (e) {\n console.error('JSON 파싱 오류:', e.message)\n return data.toString()\n }\n } else {\n return data.toString()\n }\n}\n\n// MQTT 연결을 위한 브로커 관리 클래스\nclass MqttBrokerManager {\n private static brokers: Record<\n string,\n {\n client: mqtt.AsyncMqttClient\n topics: Set<string>\n messageHandlers: Map<string, (topic: string, message: Buffer) => void>\n }\n > = {}\n\n // 브로커 연결 (또는 기존 연결 반환)\n static async getBroker(uri: string, options?: mqtt.IClientOptions) {\n const brokerKey = `${uri}_${JSON.stringify(options || {})}`\n\n if (!this.brokers[brokerKey]) {\n const client = await mqtt.connectAsync(uri, options)\n\n this.brokers[brokerKey] = {\n client,\n topics: new Set<string>(),\n messageHandlers: new Map()\n }\n\n // 메시지 수신 핸들러\n client.on('message', (topic, message) => {\n // 해당 토픽에 등록된 핸들러가 있으면 호출\n this.brokers[brokerKey].messageHandlers.forEach((handler, handlerId) => {\n if (handlerId.startsWith(`${topic}:`)) {\n handler(topic, message)\n }\n })\n })\n }\n\n return this.brokers[brokerKey]\n }\n\n // 토픽 구독 등록\n static async subscribe(brokerKey: string, topic: string) {\n const broker = this.brokers[brokerKey]\n if (!broker) {\n throw new Error(`브로커가 연결되지 않음: ${brokerKey}`)\n }\n\n // 새 토픽인 경우 구독\n if (!broker.topics.has(topic)) {\n await broker.client.subscribe(topic)\n broker.topics.add(topic)\n }\n }\n\n // 메시지 핸들러 등록\n static registerMessageHandler(\n brokerKey: string,\n topic: string,\n handlerId: string,\n handler: (topic: string, message: Buffer) => void\n ) {\n const broker = this.brokers[brokerKey]\n if (!broker) {\n throw new Error(`브로커가 연결되지 않음: ${brokerKey}`)\n }\n\n // 핸들러 ID는 topic:handlerId 형식으로 저장\n const fullHandlerId = `${topic}:${handlerId}`\n broker.messageHandlers.set(fullHandlerId, handler)\n\n return () => {\n // 핸들러 제거 함수 반환\n broker.messageHandlers.delete(fullHandlerId)\n }\n }\n\n // 연결 종료\n static async disconnect(brokerKey: string) {\n const broker = this.brokers[brokerKey]\n if (broker) {\n await broker.client.end()\n delete this.brokers[brokerKey]\n }\n }\n\n // 브로커 키 생성 유틸리티\n static getBrokerKey(uri: string, options?: any) {\n return `${uri}_${JSON.stringify(options || {})}`\n }\n}\n\ninterface MqttContext extends Context {\n __mqtt_connections?: Set<string>\n __mqtt_handlers?: Map<string, () => void>\n __mqtt_resolvers?: Map<string, (result: any) => void>\n}\n\nasync function MqttSubscribe(step: InputStep, context: MqttContext) {\n const {\n connection: connectionName,\n params: { topic, dataFormat },\n name: stepName\n } = step\n\n const { domain, logger, closures } = context\n\n // MQTT 브로커 접속 정보 가져오기\n const connectionInstance = ConnectionManager.getConnectionInstanceByName(domain, connectionName)\n const { client } = connectionInstance\n\n if (!topic) {\n throw Error(`토픽이 지정되지 않음: ${connectionName}`)\n }\n\n // 구독자 ID 생성 (도메인, 연결명, 토픽, 스텝명 조합)\n const subscriberId = `${domain}_${connectionName}_${topic}_${stepName}`\n\n try {\n // 토픽 구독 등록\n await MqttBrokerManager.subscribe(client, topic)\n logger.info(`토픽 구독 완료: ${topic}`)\n\n // 리졸버 저장소 초기화\n if (!context.__mqtt_resolvers) {\n context.__mqtt_resolvers = new Map()\n }\n\n // 클로저에 연결 종료 함수 등록\n if (!context.__mqtt_connections) {\n context.__mqtt_connections = new Set()\n }\n\n if (!context.__mqtt_handlers) {\n context.__mqtt_handlers = new Map()\n }\n\n // 연결 추적 (중복 종료 방지)\n if (!context.__mqtt_connections.has(connectionName)) {\n context.__mqtt_connections.add(connectionName)\n\n // 연결 종료 함수 등록\n closures.push(async () => {\n try {\n // 핸들러 모두 제거\n if (context.__mqtt_handlers) {\n context.__mqtt_handlers.forEach(removeHandler => {\n removeHandler()\n })\n context.__mqtt_handlers.clear()\n }\n\n // 대기 중인 모든 Promise 해결\n if (context.__mqtt_resolvers) {\n context.__mqtt_resolvers.forEach(resolver => {\n resolver({ data: null, terminated: true })\n })\n context.__mqtt_resolvers.clear()\n }\n\n // 브로커 연결 종료는 하지 않음\n logger.info(`MQTT 구독자 종료: ${connectionName}`)\n } catch (e) {\n logger.error(`MQTT 구독자 종료 오류: ${e.message}`)\n }\n })\n }\n\n // Promise로 메시지 수신 대기\n return new Promise(resolve => {\n // 이 태스크의 resolver 저장\n if (context.__mqtt_resolvers) {\n context.__mqtt_resolvers.set(subscriberId, resolve)\n }\n\n // 이미 등록된 핸들러가 있으면 제거\n if (context.__mqtt_handlers?.has(subscriberId)) {\n const removeHandler = context.__mqtt_handlers.get(subscriberId)\n if (removeHandler) {\n removeHandler()\n }\n }\n\n // 새로운 메시지 핸들러 등록\n const removeHandler = MqttBrokerManager.registerMessageHandler(\n client,\n topic,\n subscriberId,\n (messageTopic, message) => {\n try {\n // 메시지 변환\n const convertedMessage = convertDataFormat(message, dataFormat)\n\n // resolver 가져오기 및 삭제\n if (context.__mqtt_resolvers?.has(subscriberId)) {\n const resolver = context.__mqtt_resolvers.get(subscriberId)\n context.__mqtt_resolvers.delete(subscriberId)\n\n // 이 태스크에 대한 핸들러 제거 (한 번만 실행되도록)\n if (context.__mqtt_handlers?.has(subscriberId)) {\n const removeHandler = context.__mqtt_handlers.get(subscriberId)\n if (removeHandler) {\n removeHandler()\n }\n context.__mqtt_handlers.delete(subscriberId)\n }\n\n // Promise 해결\n if (resolver) {\n resolver({\n data: convertedMessage\n })\n }\n }\n } catch (error) {\n logger.error(`메시지 처리 오류: ${error.message}`)\n }\n }\n )\n\n // 핸들러 제거 함수 저장\n if (context.__mqtt_handlers) {\n context.__mqtt_handlers.set(subscriberId, removeHandler)\n }\n\n logger.info(`MQTT 메시지 대기 중: ${topic}`)\n })\n } catch (e) {\n logger.error(`MQTT 구독 오류: ${e.message}`)\n throw e\n }\n}\n\nMqttSubscribe.parameterSpec = [\n {\n type: 'string',\n name: 'topic',\n label: 'topic'\n },\n {\n type: 'select',\n label: 'data-format',\n name: 'dataFormat',\n property: {\n options: [\n {\n display: 'Plain Text',\n value: 'text'\n },\n {\n display: 'JSON',\n value: 'json'\n }\n ]\n }\n }\n]\n\nMqttSubscribe.help = 'integration/task/mqtt-subscribe'\n\nTaskRegistry.registerTaskHandler('mqtt-subscribe', MqttSubscribe)\n"]}
1
+ {"version":3,"file":"mqtt-subscribe.js","sourceRoot":"","sources":["../../../server/engine/task/mqtt-subscribe.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmFG;;AAEH,0DAAkD;AAClD,oEAA4D;AAI5D,SAAS,iBAAiB,CAAC,IAAI,EAAE,MAAM;IACrC,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACtB,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QACzB,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,mBAAmB,EAAE,CAAC,CAAC,OAAO,CAAC,CAAA;YAC7C,OAAO,IAAI,CAAC,QAAQ,EAAE,CAAA;QACxB,CAAC;IACH,CAAC;SAAM,CAAC;QACN,OAAO,IAAI,CAAC,QAAQ,EAAE,CAAA;IACxB,CAAC;AACH,CAAC;AASD,KAAK,UAAU,aAAa,CAAC,IAAe,EAAE,OAAoB;;IAChE,MAAM,EACJ,UAAU,EAAE,cAAc,EAC1B,MAAM,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,GAAG,CAAC,EAAE,EACtC,IAAI,EAAE,QAAQ,EACf,GAAG,IAAI,CAAA;IAER,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAA;IAC5C,MAAM,EAAE,MAAM,EAAE,GAAG,yCAAiB,CAAC,2BAA2B,CAAC,MAAM,EAAE,cAAc,CAAC,CAAA;IACxF,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,cAAc,EAAE,CAAC,CAAA;IACvE,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,iCAAiC,cAAc,EAAE,CAAC,CAAA;IAE9E,MAAM,YAAY,GAAG,GAAG,MAAM,IAAI,cAAc,IAAI,KAAK,IAAI,QAAQ,EAAE,CAAA;IAEvE,MAAA,OAAO,CAAC,oBAAoB,oCAA5B,OAAO,CAAC,oBAAoB,GAAK,IAAI,GAAG,EAAE,EAAA;IAC1C,MAAA,OAAO,CAAC,gBAAgB,oCAAxB,OAAO,CAAC,gBAAgB,GAAK,IAAI,GAAG,EAAE,EAAA;IACtC,MAAA,OAAO,CAAC,eAAe,oCAAvB,OAAO,CAAC,eAAe,GAAK,IAAI,GAAG,EAAE,EAAA;IAErC,6DAA6D;IAC7D,IAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE,CAAC;QACjC,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,KAAK,IAAI,EAAE;YAC9B,MAAM,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAA;YAC7D,KAAK,MAAM,CAAC,IAAI,OAAO,CAAC,oBAAoB,EAAE,CAAC;gBAC7C,IAAI,CAAC;oBACH,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,CAAA;oBAClC,MAAM,CAAC,IAAI,CAAC,iCAAiC,CAAC,EAAE,CAAC,CAAA;gBACnD,CAAC;gBAAC,OAAO,CAAC,EAAE,CAAC;oBACX,MAAM,CAAC,KAAK,CAAC,yCAAyC,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAA;gBAC1E,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAA;QAEF,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;YACxB,MAAM,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAA;QACtC,CAAC,CAAC,CAAA;QAEF,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACtB,MAAM,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAA;QACzC,CAAC,CAAC,CAAA;QAEF,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE;YACvB,MAAM,CAAC,KAAK,CAAC,iBAAiB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAA;QAC9C,CAAC,CAAC,CAAA;QAEF,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAA;IACpC,CAAC;IAED,gCAAgC;IAChC,IAAI,CAAC,OAAO,CAAC,oBAAoB,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7C,MAAM,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,EAAE,GAAG,EAAE,CAAC,CAAA;QACtC,OAAO,CAAC,oBAAoB,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QACvC,MAAM,CAAC,IAAI,CAAC,wBAAwB,KAAK,UAAU,GAAG,GAAG,CAAC,CAAA;IAC5D,CAAC;IAED,wCAAwC;IACxC,IAAI,OAAO,CAAC,eAAe,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC;QAC9C,MAAM,aAAa,GAAG,OAAO,CAAC,eAAe,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;QAC/D,aAAa,aAAb,aAAa,uBAAb,aAAa,EAAI,CAAA;QACjB,OAAO,CAAC,eAAe,CAAC,MAAM,CAAC,YAAY,CAAC,CAAA;IAC9C,CAAC;IAED,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE;QAC3B,OAAO,CAAC,gBAAgB,CAAC,GAAG,CAAC,YAAY,EAAE,OAAO,CAAC,CAAA;QAEnD,MAAM,cAAc,GAAG,CAAC,YAAY,EAAE,OAAO,EAAE,EAAE;YAC/C,IAAI,YAAY,KAAK,KAAK;gBAAE,OAAM;YAElC,MAAM,SAAS,GAAG,iBAAiB,CAAC,OAAO,EAAE,UAAU,CAAC,CAAA;YACxD,MAAM,QAAQ,GAAG,OAAO,CAAC,gBAAgB,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;YAE3D,IAAI,QAAQ,EAAE,CAAC;gBACb,OAAO,CAAC,gBAAgB,CAAC,MAAM,CAAC,YAAY,CAAC,CAAA;gBAC7C,QAAQ,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAA;gBAC7B,MAAM,CAAC,cAAc,CAAC,SAAS,EAAE,cAAc,CAAC,CAAA;gBAChD,OAAO,CAAC,eAAe,CAAC,MAAM,CAAC,YAAY,CAAC,CAAA;YAC9C,CAAC;QACH,CAAC,CAAA;QAED,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,cAAc,CAAC,CAAA;QACpC,OAAO,CAAC,eAAe,CAAC,GAAG,CAAC,YAAY,EAAE,GAAG,EAAE;YAC7C,MAAM,CAAC,cAAc,CAAC,SAAS,EAAE,cAAc,CAAC,CAAA;QAClD,CAAC,CAAC,CAAA;QAEF,QAAQ,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE;;YACvB,IAAI,CAAC;gBACH,IAAI,MAAA,OAAO,CAAC,oBAAoB,0CAAE,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;oBAC7C,MAAM,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAA;oBAC/B,OAAO,CAAC,oBAAoB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;oBAC1C,MAAM,CAAC,IAAI,CAAC,4BAA4B,KAAK,EAAE,CAAC,CAAA;gBAClD,CAAC;gBAED,IAAI,MAAA,OAAO,CAAC,eAAe,0CAAE,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC;oBAC/C,MAAM,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;oBACxD,MAAM,aAAN,MAAM,uBAAN,MAAM,EAAI,CAAA;oBACV,OAAO,CAAC,eAAe,CAAC,MAAM,CAAC,YAAY,CAAC,CAAA;gBAC9C,CAAC;gBAED,IAAI,MAAA,OAAO,CAAC,gBAAgB,0CAAE,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC;oBAChD,MAAM,QAAQ,GAAG,OAAO,CAAC,gBAAgB,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;oBAC3D,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAG,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAA;oBAC5C,OAAO,CAAC,gBAAgB,CAAC,MAAM,CAAC,YAAY,CAAC,CAAA;gBAC/C,CAAC;YACH,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,MAAM,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC,OAAO,EAAE,CAAC,CAAA;YAClD,CAAC;QACH,CAAC,CAAC,CAAA;QAEF,MAAM,CAAC,IAAI,CAAC,sCAAsC,KAAK,EAAE,CAAC,CAAA;IAC5D,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,mDAAmD;AACnD,aAAa,CAAC,aAAa,GAAG;IAC5B;QACE,IAAI,EAAE,QAAQ;QACd,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,OAAO;KACf;IACD;QACE,IAAI,EAAE,QAAQ;QACd,KAAK,EAAE,aAAa;QACpB,IAAI,EAAE,YAAY;QAClB,QAAQ,EAAE;YACR,OAAO,EAAE;gBACP,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE;gBACjC,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE;gBACxC,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE;aACnC;SACF;KACF;IACD;QACE,IAAI,EAAE,QAAQ;QACd,KAAK,EAAE,KAAK;QACZ,IAAI,EAAE,KAAK;QACX,QAAQ,EAAE;YACR,OAAO,EAAE;gBACP,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE;gBACjC,EAAE,OAAO,EAAE,kBAAkB,EAAE,KAAK,EAAE,CAAC,EAAE;gBACzC,EAAE,OAAO,EAAE,mBAAmB,EAAE,KAAK,EAAE,CAAC,EAAE;gBAC1C,EAAE,OAAO,EAAE,kBAAkB,EAAE,KAAK,EAAE,CAAC,EAAE;aAC1C;SACF;KACF;CACF,CAAA;AAED,aAAa,CAAC,IAAI,GAAG,iCAAiC,CAAA;AAEtD,sCAAsC;AACtC,+BAAY,CAAC,mBAAmB,CAAC,gBAAgB,EAAE,aAAa,CAAC,CAAA","sourcesContent":["/**\n * MQTT Subscribe Task\n * ====================\n *\n * Pull-Based Message Consumption on MQTT:\n * ---------------------------------------\n * While MQTT is inherently a push-based protocol, this task implements a controlled,\n * pull-style message consumption model. A message is only processed when the task is\n * explicitly invoked (i.e., the consumer actively pulls one message at a time).\n * This enables fine-grained control over message flow and avoids unintended buffering.\n *\n * Overview:\n * ---------\n * This task connects to a specified MQTT broker and subscribes to a given topic.\n * It waits for a single message to arrive, resolves the result, and then removes\n * its internal message handler. It is designed to be reentrant — each execution\n * of this task handles exactly one message.\n *\n * This model is useful for applications where message processing must happen\n * sequentially or be synchronized with external state (e.g., workflows or state machines).\n *\n * Key Features:\n * -------------\n * - One-message-per-invocation: each task call resolves with a single message.\n * - Stateless message flow: the system does not queue or buffer messages locally.\n * - Supports automatic re-subscription on reconnect, improving robustness in unstable networks.\n * - Uses broker-level guarantees (QoS, retain) for durability and delivery control.\n * - Supports multiple data formats (JSON, plain text) and adjustable QoS levels.\n *\n * MQTT Reconnection Handling:\n * ---------------------------\n * The task listens for 'connect' events on the MQTT client. When the client reconnects\n * (e.g., after a dropped network), it automatically re-subscribes to all previously\n * tracked topics. This ensures consistent behavior even in long-running systems or\n * unstable network environments.\n *\n * If a disconnect occurs while a task is waiting for a message, the Promise remains\n * pending until a message is received after reconnection, or until manually cleaned up\n * via the task closure mechanism.\n *\n * How It Works:\n * -------------\n * 1. Retrieve an MQTT client using a named connection.\n * 2. Subscribe to the specified topic (only once per topic per workflow).\n * 3. Register a one-time message handler for the topic.\n * 4. When a message is received:\n * - It is parsed into the requested data format.\n * - The task resolves with the parsed message.\n * - The handler is immediately removed.\n * 5. Re-subscribe to all topics upon reconnect.\n * 6. Clean up all resources (subscription, handler, resolver) on task termination.\n *\n * Assumptions:\n * ------------\n * - Broker reliability is enforced via QoS ≥ 1 and/or retained messages.\n * - Message loss may occur if a message arrives before re-invocation, unless the broker retains it.\n * - The task caller is responsible for repeated invocations to consume additional messages.\n * - MQTT clients are externally managed and assumed to reconnect automatically.\n *\n * Parameters:\n * -----------\n * - topic (string): MQTT topic to subscribe to.\n * - dataFormat ('json' | 'text'): Describes how to parse the incoming message payload.\n * - qos (0 | 1 | 2): Quality of Service level for the subscription (default: 1).\n *\n * Returns:\n * --------\n * - A Promise that resolves with the first received message:\n * { data: any }\n * - If the task is terminated before receiving a message:\n * { data: null, terminated: true }\n *\n * Example:\n * --------\n * await MqttSubscribe({\n * connection: 'my-mqtt',\n * params: {\n * topic: 'sensor/temperature',\n * dataFormat: 'json',\n * qos: 1\n * },\n * name: 'readTemperature'\n * }, context)\n */\n\nimport { TaskRegistry } from '../task-registry.js'\nimport { ConnectionManager } from '../connection-manager.js'\nimport { InputStep } from '../../service/step/step-type.js'\nimport { Context } from '../types.js'\n\nfunction convertDataFormat(data, format) {\n if (format === 'json') {\n try {\n return JSON.parse(data)\n } catch (e) {\n console.error('JSON parse error:', e.message)\n return data.toString()\n }\n } else {\n return data.toString()\n }\n}\n\ninterface MqttContext extends Context {\n __mqtt_handlers?: Map<string, () => void>\n __mqtt_resolvers?: Map<string, (result: any) => void>\n __mqtt_subscriptions?: Set<string>\n __mqtt_event_hooked?: boolean\n}\n\nasync function MqttSubscribe(step: InputStep, context: MqttContext) {\n const {\n connection: connectionName,\n params: { topic, dataFormat, qos = 1 },\n name: stepName\n } = step\n\n const { domain, logger, closures } = context\n const { client } = ConnectionManager.getConnectionInstanceByName(domain, connectionName)\n if (!client) throw new Error(`connection not found: ${connectionName}`)\n if (!topic) throw new Error(`Missing topic for connection: ${connectionName}`)\n\n const subscriberId = `${domain}_${connectionName}_${topic}_${stepName}`\n\n context.__mqtt_subscriptions ??= new Set()\n context.__mqtt_resolvers ??= new Map()\n context.__mqtt_handlers ??= new Map()\n\n // Setup MQTT client reconnect/resubscribe handlers only once\n if (!context.__mqtt_event_hooked) {\n client.on('connect', async () => {\n logger.info('[MQTT] Reconnected. Resubscribing to topics...')\n for (const t of context.__mqtt_subscriptions) {\n try {\n await client.subscribe(t, { qos })\n logger.info(`[MQTT] Resubscribed to topic: ${t}`)\n } catch (e) {\n logger.error(`[MQTT] Failed to resubscribe to topic ${t}: ${e.message}`)\n }\n }\n })\n\n client.on('offline', () => {\n logger.warn('[MQTT] Client offline')\n })\n\n client.on('close', () => {\n logger.warn('[MQTT] Connection closed')\n })\n\n client.on('error', err => {\n logger.error(`[MQTT] Error: ${err.message}`)\n })\n\n context.__mqtt_event_hooked = true\n }\n\n // Subscribe if not already done\n if (!context.__mqtt_subscriptions.has(topic)) {\n await client.subscribe(topic, { qos })\n context.__mqtt_subscriptions.add(topic)\n logger.info(`Subscribed to topic: ${topic} (QoS: ${qos})`)\n }\n\n // Remove previous handler for this step\n if (context.__mqtt_handlers.has(subscriberId)) {\n const removeHandler = context.__mqtt_handlers.get(subscriberId)\n removeHandler?.()\n context.__mqtt_handlers.delete(subscriberId)\n }\n\n return new Promise(resolve => {\n context.__mqtt_resolvers.set(subscriberId, resolve)\n\n const messageHandler = (messageTopic, message) => {\n if (messageTopic !== topic) return\n\n const converted = convertDataFormat(message, dataFormat)\n const resolver = context.__mqtt_resolvers.get(subscriberId)\n\n if (resolver) {\n context.__mqtt_resolvers.delete(subscriberId)\n resolver({ data: converted })\n client.removeListener('message', messageHandler)\n context.__mqtt_handlers.delete(subscriberId)\n }\n }\n\n client.on('message', messageHandler)\n context.__mqtt_handlers.set(subscriberId, () => {\n client.removeListener('message', messageHandler)\n })\n\n closures.push(async () => {\n try {\n if (context.__mqtt_subscriptions?.has(topic)) {\n await client.unsubscribe(topic)\n context.__mqtt_subscriptions.delete(topic)\n logger.info(`Unsubscribed from topic: ${topic}`)\n }\n\n if (context.__mqtt_handlers?.has(subscriberId)) {\n const remove = context.__mqtt_handlers.get(subscriberId)\n remove?.()\n context.__mqtt_handlers.delete(subscriberId)\n }\n\n if (context.__mqtt_resolvers?.has(subscriberId)) {\n const resolver = context.__mqtt_resolvers.get(subscriberId)\n resolver?.({ data: null, terminated: true })\n context.__mqtt_resolvers.delete(subscriberId)\n }\n } catch (e) {\n logger.error(`MQTT cleanup error: ${e.message}`)\n }\n })\n\n logger.info(`Waiting for MQTT message on topic: ${topic}`)\n })\n}\n\n// Task parameter definitions for UI or DSL support\nMqttSubscribe.parameterSpec = [\n {\n type: 'string',\n name: 'topic',\n label: 'topic'\n },\n {\n type: 'select',\n label: 'data-format',\n name: 'dataFormat',\n property: {\n options: [\n { display: '', value: undefined },\n { display: 'Plain Text', value: 'text' },\n { display: 'JSON', value: 'json' }\n ]\n }\n },\n {\n type: 'select',\n label: 'QoS',\n name: 'qos',\n property: {\n options: [\n { display: '', value: undefined },\n { display: '0 (at most once)', value: 0 },\n { display: '1 (at least once)', value: 1 },\n { display: '2 (exactly once)', value: 2 }\n ]\n }\n }\n]\n\nMqttSubscribe.help = 'integration/task/mqtt-subscribe'\n\n// Register task with runtime registry\nTaskRegistry.registerTaskHandler('mqtt-subscribe', MqttSubscribe)\n"]}