@onlineapps/conn-orch-registry 1.1.23 → 1.1.25
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 +2 -2
- package/docs/REGISTRY_CLIENT_GUIDE.md +1 -1
- package/package.json +1 -1
- package/src/queueManager.js +58 -16
- package/src/registryClient.js +166 -56
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
## 🚀 Features
|
|
10
10
|
|
|
11
|
-
* Automatic queue management (`workflow`, `<serviceName>.registry`, `api_services_queuer`, `
|
|
11
|
+
* Automatic queue management (`workflow`, `<serviceName>.registry`, `api_services_queuer`, `registry.register`)
|
|
12
12
|
* Periodic heartbeat messages with metadata
|
|
13
13
|
* API description request/response flow
|
|
14
14
|
* Event-driven API using `EventEmitter`
|
|
@@ -64,7 +64,7 @@ Configuration can be provided via environment variables or constructor options:
|
|
|
64
64
|
| `SERVICE_VERSION` | Service version in SemVer format (required) | — |
|
|
65
65
|
| `HEARTBEAT_INTERVAL` | Interval in ms between heartbeats | `10000` |
|
|
66
66
|
| `API_QUEUE` | Queue name for heartbeat/API messages | `api_services_queuer` |
|
|
67
|
-
| `REGISTRY_QUEUE` | Queue name for registry requests/descriptions | `
|
|
67
|
+
| `REGISTRY_QUEUE` | Queue name for registry requests/descriptions | `registry.register` |
|
|
68
68
|
|
|
69
69
|
## 📨 Message Formats
|
|
70
70
|
|
|
@@ -124,7 +124,7 @@ async function shutdown() {
|
|
|
124
124
|
| `specificationEndpoint` | string | '/api/v1/specification' | Endpoint for API spec |
|
|
125
125
|
| `heartbeatInterval` | number | 10000 | Heartbeat interval in ms |
|
|
126
126
|
| `apiQueue` | string | 'api_services_queuer' | Queue for API traffic |
|
|
127
|
-
| `registryQueue` | string | '
|
|
127
|
+
| `registryQueue` | string | 'registry.register' | Registry queue name |
|
|
128
128
|
|
|
129
129
|
### Methods
|
|
130
130
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onlineapps/conn-orch-registry",
|
|
3
|
-
|
|
3
|
+
"version": "1.1.25",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Connector-registry-client provides the core communication mechanism for microservices in this environment. It enables them to interact with a services_registry to receive and fulfill tasks by submitting heartbeats or their API descriptions.",
|
|
6
6
|
"keywords": [
|
package/src/queueManager.js
CHANGED
|
@@ -41,12 +41,44 @@ class QueueManager {
|
|
|
41
41
|
/**
|
|
42
42
|
* Initializes the connection and channel to RabbitMQ.
|
|
43
43
|
* @returns {Promise<void>}
|
|
44
|
+
* @throws {Error} If connection or channel creation fails or times out
|
|
44
45
|
*/
|
|
45
46
|
async init() {
|
|
46
|
-
|
|
47
|
+
const CONNECT_TIMEOUT = 10000; // 10 seconds
|
|
48
|
+
const connectPromise = amqp.connect(this.amqpUrl);
|
|
49
|
+
const connectTimeoutPromise = new Promise((_, reject) => {
|
|
50
|
+
setTimeout(() => {
|
|
51
|
+
reject(new Error(`RabbitMQ connection timeout after ${CONNECT_TIMEOUT}ms. RabbitMQ may be unavailable at ${this.amqpUrl}`));
|
|
52
|
+
}, CONNECT_TIMEOUT);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
this.conn = await Promise.race([connectPromise, connectTimeoutPromise]);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
throw new Error(`[QueueManager] Failed to connect to RabbitMQ: ${error.message}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
47
61
|
// Use regular channel instead of ConfirmChannel to avoid RPC reply queue issues
|
|
48
62
|
// ConfirmChannel uses RPC pattern which requires reply queues that may not exist
|
|
49
|
-
|
|
63
|
+
const CHANNEL_TIMEOUT = 5000; // 5 seconds
|
|
64
|
+
const channelPromise = this.conn.createChannel();
|
|
65
|
+
const channelTimeoutPromise = new Promise((_, reject) => {
|
|
66
|
+
setTimeout(() => {
|
|
67
|
+
reject(new Error(`Channel creation timeout after ${CHANNEL_TIMEOUT}ms`));
|
|
68
|
+
}, CHANNEL_TIMEOUT);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
this.channel = await Promise.race([channelPromise, channelTimeoutPromise]);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
// Clean up connection on channel creation failure
|
|
75
|
+
try {
|
|
76
|
+
await this.conn.close();
|
|
77
|
+
} catch (closeErr) {
|
|
78
|
+
// Ignore close errors
|
|
79
|
+
}
|
|
80
|
+
throw new Error(`[QueueManager] Failed to create channel: ${error.message}`);
|
|
81
|
+
}
|
|
50
82
|
}
|
|
51
83
|
|
|
52
84
|
/**
|
|
@@ -70,19 +102,20 @@ class QueueManager {
|
|
|
70
102
|
// CRITICAL: Use queueConfig.js to get correct parameters (TTL, max-length, etc.)
|
|
71
103
|
// This prevents 406 PRECONDITION-FAILED errors from TTL mismatches
|
|
72
104
|
let queueOptions = { durable: true };
|
|
105
|
+
const isInfrastructureQueue = queueConfig?.isInfrastructureQueue
|
|
106
|
+
? queueConfig.isInfrastructureQueue(q)
|
|
107
|
+
: ['workflow.', 'registry.', 'infrastructure.', 'validation.', 'monitoring.', 'delivery.'].some(prefix => q.startsWith(prefix));
|
|
108
|
+
|
|
109
|
+
if (isInfrastructureQueue) {
|
|
110
|
+
const message = `[QueueManager] [REGISTRY] Refusing to assert infrastructure queue '${q}'. ` +
|
|
111
|
+
'These queues are owned by infrastructure services and must be created before business services start.';
|
|
112
|
+
console.error(message);
|
|
113
|
+
throw new Error(message);
|
|
114
|
+
}
|
|
73
115
|
|
|
74
116
|
if (queueConfig) {
|
|
75
117
|
try {
|
|
76
|
-
|
|
77
|
-
const infraConfig = queueConfig.getInfrastructureQueueConfig(q);
|
|
78
|
-
queueOptions = {
|
|
79
|
-
durable: infraConfig.durable !== false,
|
|
80
|
-
arguments: { ...infraConfig.arguments }
|
|
81
|
-
};
|
|
82
|
-
console.log(`[QueueManager] [REGISTRY] [QUEUE] Using infrastructure queue config for ${q}:`, JSON.stringify(queueOptions));
|
|
83
|
-
} else {
|
|
84
|
-
console.warn(`[QueueManager] [REGISTRY] [QUEUE] Queue ${q} is not an infrastructure queue, using default options`);
|
|
85
|
-
}
|
|
118
|
+
console.warn(`[QueueManager] [REGISTRY] [QUEUE] Queue ${q} is not an infrastructure queue, using default options`);
|
|
86
119
|
} catch (configErr) {
|
|
87
120
|
console.warn(`[QueueManager] [REGISTRY] [QUEUE] Failed to get config for ${q}, using defaults:`, configErr.message);
|
|
88
121
|
}
|
|
@@ -93,13 +126,22 @@ class QueueManager {
|
|
|
93
126
|
// Directly assert queue with canonical configuration (idempotent)
|
|
94
127
|
try {
|
|
95
128
|
const assertStartTime = Date.now();
|
|
96
|
-
|
|
129
|
+
const ASSERT_TIMEOUT = 5000; // 5 seconds
|
|
130
|
+
const assertPromise = this.channel.assertQueue(q, queueOptions);
|
|
131
|
+
const assertTimeoutPromise = new Promise((_, reject) => {
|
|
132
|
+
setTimeout(() => {
|
|
133
|
+
reject(new Error(`assertQueue timeout after ${ASSERT_TIMEOUT}ms`));
|
|
134
|
+
}, ASSERT_TIMEOUT);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
await Promise.race([assertPromise, assertTimeoutPromise]);
|
|
97
138
|
const assertEndTime = Date.now();
|
|
98
139
|
console.log(`[QueueManager] [REGISTRY] [QUEUE] ✓ Queue ${q} asserted (took ${assertEndTime - assertStartTime}ms)`);
|
|
99
140
|
} catch (assertErr) {
|
|
100
|
-
|
|
101
|
-
console.error(`[QueueManager] [REGISTRY] [QUEUE]
|
|
102
|
-
|
|
141
|
+
const errorMsg = assertErr.message || String(assertErr);
|
|
142
|
+
console.error(`[QueueManager] [REGISTRY] [QUEUE] ✗ Failed to assert queue ${q}: ${errorMsg}`);
|
|
143
|
+
console.error(`[QueueManager] [REGISTRY] [QUEUE] Error code: ${assertErr.code || 'N/A'}`);
|
|
144
|
+
throw new Error(`[QueueManager] Failed to assert queue ${q}: ${errorMsg}`);
|
|
103
145
|
}
|
|
104
146
|
}
|
|
105
147
|
}
|
package/src/registryClient.js
CHANGED
|
@@ -75,42 +75,34 @@ class ServiceRegistryClient extends EventEmitter {
|
|
|
75
75
|
console.log(`[RegistryClient] ${this.serviceName}: ⚠️ No validation proof - will use Tier 2 validation`);
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
// Create service-specific registry event queue (for certificate delivery)
|
|
78
|
+
// FÁZE 0.5: Vytvoření service-specific front
|
|
79
|
+
const queueCreationStartTime = Date.now();
|
|
82
80
|
this.serviceRegistryQueue = `${this.serviceName}.registry`;
|
|
81
|
+
console.log(`[FÁZE 0.5] Service Queue Creation - STARTING`);
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
// Ensure existence of service-specific queue only (infrastructure queues must already exist)
|
|
85
|
+
await this.queueManager.ensureQueues([this.serviceRegistryQueue]);
|
|
86
|
+
console.log(`[FÁZE 0.5] Service Queue Creation - PASSED (${Date.now() - queueCreationStartTime}ms)`);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error(`[FÁZE 0.5] Service Queue Creation - FAILED: ${error.message}`);
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
83
91
|
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
|
|
92
|
+
// FÁZE 0.6: Spuštění konzumerů
|
|
93
|
+
const consumerStartTime = Date.now();
|
|
94
|
+
console.log(`[FÁZE 0.6] Consumer Startup - STARTING`);
|
|
95
|
+
|
|
87
96
|
// CRITICAL: Before consume(), we must assertQueue with correct parameters
|
|
88
97
|
// amqplib's channel.consume() may internally call assertQueue() WITHOUT parameters
|
|
89
98
|
// This causes 406 PRECONDITION-FAILED if queue exists with different arguments
|
|
90
|
-
console.log(`[RegistryClient] [CONSUMER] About to consume from ${this.serviceResponseQueue}`);
|
|
91
|
-
console.log(`[RegistryClient] [CONSUMER] ⚠ WARNING: amqplib's channel.consume() may internally call assertQueue() WITHOUT parameters`);
|
|
92
|
-
console.log(`[RegistryClient] [CONSUMER] ⚠ WARNING: Queue should already be asserted by ensureQueues() above with correct parameters from queueConfig.js`);
|
|
93
|
-
|
|
94
|
-
// Start consuming service response queue for registry responses
|
|
95
|
-
await this.queueManager.channel.consume(
|
|
96
|
-
this.serviceResponseQueue,
|
|
97
|
-
msg => {
|
|
98
|
-
// Handle null message (queue deleted, connection closed, consumer canceled)
|
|
99
|
-
if (!msg) {
|
|
100
|
-
console.warn(`[RegistryClient] ${this.serviceName}: Received null message from ${this.serviceResponseQueue} (queue may be deleted or connection closed)`);
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
this._handleRegistryMessage(msg);
|
|
104
|
-
},
|
|
105
|
-
{ noAck: false }
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
// CRITICAL: Also listen on registry event queue for certificate delivery
|
|
109
99
|
console.log(`[RegistryClient] [CONSUMER] About to consume from ${this.serviceRegistryQueue}`);
|
|
110
100
|
console.log(`[RegistryClient] [CONSUMER] ⚠ WARNING: amqplib's channel.consume() may internally call assertQueue() WITHOUT parameters`);
|
|
111
101
|
console.log(`[RegistryClient] [CONSUMER] ⚠ WARNING: Queue should already be asserted by ensureQueues() above with correct parameters from queueConfig.js`);
|
|
112
102
|
|
|
113
|
-
|
|
103
|
+
// Start consuming service registry queue for registry responses and events
|
|
104
|
+
const CONSUME_TIMEOUT = 5000; // 5 seconds
|
|
105
|
+
const consumeRegistryPromise = this.queueManager.channel.consume(
|
|
114
106
|
this.serviceRegistryQueue,
|
|
115
107
|
msg => {
|
|
116
108
|
// Handle null message (queue deleted, connection closed, consumer canceled)
|
|
@@ -118,10 +110,33 @@ class ServiceRegistryClient extends EventEmitter {
|
|
|
118
110
|
console.warn(`[RegistryClient] ${this.serviceName}: Received null message from ${this.serviceRegistryQueue} (queue may be deleted or connection closed)`);
|
|
119
111
|
return;
|
|
120
112
|
}
|
|
121
|
-
|
|
113
|
+
try {
|
|
114
|
+
this._handleRegistryMessage(msg);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.error(`[RegistryClient] ${this.serviceName}: Error in consume callback:`, error);
|
|
117
|
+
// Nack message on error
|
|
118
|
+
try {
|
|
119
|
+
this.queueManager.channel.nack(msg, false, false);
|
|
120
|
+
} catch (nackErr) {
|
|
121
|
+
console.error(`[RegistryClient] ${this.serviceName}: Failed to nack message:`, nackErr);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
122
124
|
},
|
|
123
125
|
{ noAck: false }
|
|
124
126
|
);
|
|
127
|
+
const consumeRegistryTimeoutPromise = new Promise((_, reject) => {
|
|
128
|
+
setTimeout(() => {
|
|
129
|
+
reject(new Error(`consume() timeout for ${this.serviceRegistryQueue} after ${CONSUME_TIMEOUT}ms`));
|
|
130
|
+
}, CONSUME_TIMEOUT);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
await Promise.race([consumeRegistryPromise, consumeRegistryTimeoutPromise]);
|
|
135
|
+
console.log(`[FÁZE 0.6] Consumer Startup - PASSED (${Date.now() - consumerStartTime}ms)`);
|
|
136
|
+
} catch (consumeErr) {
|
|
137
|
+
console.error(`[FÁZE 0.6] Consumer Startup - FAILED: ${consumeErr.message}`);
|
|
138
|
+
throw new Error(`[RegistryClient] ${this.serviceName}: Failed to start consumer on ${this.serviceRegistryQueue}: ${consumeErr.message}`);
|
|
139
|
+
}
|
|
125
140
|
}
|
|
126
141
|
|
|
127
142
|
/**
|
|
@@ -138,10 +153,16 @@ class ServiceRegistryClient extends EventEmitter {
|
|
|
138
153
|
return; // Don't try to parse or ack null message
|
|
139
154
|
}
|
|
140
155
|
|
|
156
|
+
// Log which queue this message came from (if available in msg properties)
|
|
157
|
+
const queueName = msg.fields?.routingKey || msg.fields?.exchange || 'unknown';
|
|
158
|
+
console.log(`[RegistryClient] ${this.serviceName}: [MESSAGE] Processing message from queue: ${queueName}`);
|
|
159
|
+
|
|
141
160
|
let payload;
|
|
142
161
|
try {
|
|
143
162
|
payload = JSON.parse(msg.content.toString());
|
|
163
|
+
console.log(`[RegistryClient] ${this.serviceName}: [MESSAGE] Parsed payload type: ${payload.type}, requestId: ${payload.requestId}`);
|
|
144
164
|
} catch (err) {
|
|
165
|
+
console.error(`[RegistryClient] ${this.serviceName}: [MESSAGE] Failed to parse message:`, err);
|
|
145
166
|
this.emit('error', err);
|
|
146
167
|
// Only nack if message is not null
|
|
147
168
|
if (msg) {
|
|
@@ -165,22 +186,34 @@ class ServiceRegistryClient extends EventEmitter {
|
|
|
165
186
|
// Handle registration response from registry
|
|
166
187
|
// Registry sends 'register.confirmed' after validation
|
|
167
188
|
if ((payload.type === 'registerResponse' || payload.type === 'register.confirmed') && payload.requestId) {
|
|
168
|
-
console.log(`[RegistryClient] ${this.serviceName}: Received ${payload.type} message`, {
|
|
189
|
+
console.log(`[RegistryClient] ${this.serviceName}: [REGISTRATION] Received ${payload.type} message`, {
|
|
169
190
|
requestId: payload.requestId,
|
|
170
191
|
hasPendingRegistrations: !!(this.pendingRegistrations),
|
|
171
192
|
pendingCount: this.pendingRegistrations ? this.pendingRegistrations.size : 0,
|
|
172
193
|
hasMatchingRequest: !!(this.pendingRegistrations && this.pendingRegistrations.has(payload.requestId)),
|
|
173
|
-
pendingKeys: this.pendingRegistrations ? Array.from(this.pendingRegistrations.keys()) : []
|
|
194
|
+
pendingKeys: this.pendingRegistrations ? Array.from(this.pendingRegistrations.keys()) : [],
|
|
195
|
+
queueName
|
|
174
196
|
});
|
|
175
197
|
|
|
176
198
|
if (this.pendingRegistrations && this.pendingRegistrations.has(payload.requestId)) {
|
|
199
|
+
console.log(`[RegistryClient] ${this.serviceName}: [REGISTRATION] Found matching pending registration, extracting resolve function...`);
|
|
177
200
|
const { resolve } = this.pendingRegistrations.get(payload.requestId);
|
|
178
201
|
this.pendingRegistrations.delete(payload.requestId);
|
|
179
|
-
|
|
180
|
-
|
|
202
|
+
console.log(`[RegistryClient] ${this.serviceName}: [REGISTRATION] ✓ Resolve function extracted, pending registration removed`);
|
|
203
|
+
|
|
204
|
+
// CRITICAL: Resolve promise synchronously to ensure it's called before any async operations
|
|
205
|
+
console.log(`[RegistryClient] ${this.serviceName}: [RESOLVE] Starting resolve process for requestId: ${payload.requestId}`);
|
|
206
|
+
console.log(`[RegistryClient] ${this.serviceName}: [RESOLVE] Payload keys:`, Object.keys(payload));
|
|
207
|
+
console.log(`[RegistryClient] ${this.serviceName}: [RESOLVE] Payload.success:`, payload.success);
|
|
208
|
+
console.log(`[RegistryClient] ${this.serviceName}: [RESOLVE] Payload.validated:`, payload.validated);
|
|
209
|
+
console.log(`[RegistryClient] ${this.serviceName}: [RESOLVE] Payload.hasCertificate:`, !!payload.certificate);
|
|
210
|
+
if (payload.certificate) {
|
|
211
|
+
console.log(`[RegistryClient] ${this.serviceName}: [RESOLVE] Payload.certificate.id:`, payload.certificate.id);
|
|
212
|
+
console.log(`[RegistryClient] ${this.serviceName}: [RESOLVE] Payload.certificate.serviceName:`, payload.certificate.serviceName);
|
|
213
|
+
}
|
|
181
214
|
|
|
182
215
|
// Resolve the registration promise with the response
|
|
183
|
-
|
|
216
|
+
const registrationResult = {
|
|
184
217
|
success: payload.success || false,
|
|
185
218
|
message: payload.message || 'Registration processed',
|
|
186
219
|
serviceName: this.serviceName,
|
|
@@ -188,7 +221,31 @@ class ServiceRegistryClient extends EventEmitter {
|
|
|
188
221
|
registrationId: payload.registrationId,
|
|
189
222
|
validated: payload.validated || payload.success, // Consider successful registration as validated
|
|
190
223
|
certificate: payload.certificate || null // Include certificate from Registry
|
|
191
|
-
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
console.log(`[RegistryClient] ${this.serviceName}: [RESOLVE] Created registrationResult:`, JSON.stringify({
|
|
227
|
+
success: registrationResult.success,
|
|
228
|
+
validated: registrationResult.validated,
|
|
229
|
+
hasCertificate: !!registrationResult.certificate,
|
|
230
|
+
certificateId: registrationResult.certificate?.id || 'none',
|
|
231
|
+
serviceName: registrationResult.serviceName,
|
|
232
|
+
version: registrationResult.version
|
|
233
|
+
}));
|
|
234
|
+
|
|
235
|
+
console.log(`[RegistryClient] ${this.serviceName}: [RESOLVE] About to call resolve() function...`);
|
|
236
|
+
console.log(`[RegistryClient] ${this.serviceName}: [RESOLVE] resolve function type:`, typeof resolve);
|
|
237
|
+
console.log(`[RegistryClient] ${this.serviceName}: [RESOLVE] resolve function exists:`, !!resolve);
|
|
238
|
+
|
|
239
|
+
try {
|
|
240
|
+
// CRITICAL: Call resolve synchronously - this must happen immediately
|
|
241
|
+
resolve(registrationResult);
|
|
242
|
+
console.log(`[RegistryClient] ${this.serviceName}: [RESOLVE] ✓ resolve() called successfully`);
|
|
243
|
+
console.log(`[RegistryClient] ${this.serviceName}: [RESOLVE] Promise should now be resolved`);
|
|
244
|
+
} catch (resolveError) {
|
|
245
|
+
console.error(`[RegistryClient] ${this.serviceName}: [RESOLVE] ✗ Error calling resolve():`, resolveError);
|
|
246
|
+
console.error(`[RegistryClient] ${this.serviceName}: [RESOLVE] Error stack:`, resolveError.stack);
|
|
247
|
+
throw resolveError;
|
|
248
|
+
}
|
|
192
249
|
} else {
|
|
193
250
|
console.warn(`[RegistryClient] ${this.serviceName}: ⚠️ Received ${payload.type} with requestId ${payload.requestId}, but no matching pending registration found`);
|
|
194
251
|
}
|
|
@@ -225,25 +282,42 @@ class ServiceRegistryClient extends EventEmitter {
|
|
|
225
282
|
}
|
|
226
283
|
|
|
227
284
|
if (!this.queueManager?.conn) {
|
|
228
|
-
|
|
229
|
-
return;
|
|
285
|
+
throw new Error(`[RegistryClient] ${this.serviceName}: MQ connection not ready, cannot verify infrastructure queue ${queueName}. Ensure RabbitMQ is accessible.`);
|
|
230
286
|
}
|
|
231
287
|
|
|
232
|
-
|
|
288
|
+
// Add timeout to prevent hanging
|
|
289
|
+
const QUEUE_VERIFY_TIMEOUT = 10000; // 10 seconds
|
|
290
|
+
const verifyPromise = (async () => {
|
|
291
|
+
const verificationChannel = await this.queueManager.conn.createChannel();
|
|
292
|
+
try {
|
|
293
|
+
await verificationChannel.checkQueue(queueName);
|
|
294
|
+
this._verifiedInfraQueues.add(queueName);
|
|
295
|
+
} catch (error) {
|
|
296
|
+
if (error.code === 404) {
|
|
297
|
+
throw new Error(`[RegistryClient] Infrastructure queue '${queueName}' is missing. Ensure the responsible infrastructure service has initialized it.`);
|
|
298
|
+
}
|
|
299
|
+
throw error;
|
|
300
|
+
} finally {
|
|
301
|
+
try {
|
|
302
|
+
await verificationChannel.close();
|
|
303
|
+
} catch (closeErr) {
|
|
304
|
+
console.warn(`[RegistryClient] ${this.serviceName}: Failed to close verification channel for ${queueName}:`, closeErr.message);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
})();
|
|
308
|
+
|
|
309
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
310
|
+
setTimeout(() => {
|
|
311
|
+
reject(new Error(`[RegistryClient] ${this.serviceName}: Timeout verifying infrastructure queue '${queueName}' after ${QUEUE_VERIFY_TIMEOUT}ms. Queue may not exist or RabbitMQ is unresponsive.`));
|
|
312
|
+
}, QUEUE_VERIFY_TIMEOUT);
|
|
313
|
+
});
|
|
314
|
+
|
|
233
315
|
try {
|
|
234
|
-
await
|
|
235
|
-
this._verifiedInfraQueues.add(queueName);
|
|
316
|
+
await Promise.race([verifyPromise, timeoutPromise]);
|
|
236
317
|
} catch (error) {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
318
|
+
// Clear verification cache on error so we can retry
|
|
319
|
+
this._verifiedInfraQueues.delete(queueName);
|
|
240
320
|
throw error;
|
|
241
|
-
} finally {
|
|
242
|
-
try {
|
|
243
|
-
await verificationChannel.close();
|
|
244
|
-
} catch (closeErr) {
|
|
245
|
-
console.warn(`[RegistryClient] ${this.serviceName}: Failed to close verification channel for ${queueName}:`, closeErr.message);
|
|
246
|
-
}
|
|
247
321
|
}
|
|
248
322
|
}
|
|
249
323
|
|
|
@@ -282,7 +356,7 @@ class ServiceRegistryClient extends EventEmitter {
|
|
|
282
356
|
spec: serviceInfo.spec || null,
|
|
283
357
|
validationToken: serviceInfo.token || serviceInfo.validationToken,
|
|
284
358
|
tokenSecret: serviceInfo.secret || serviceInfo.tokenSecret,
|
|
285
|
-
responseQueue: this.
|
|
359
|
+
responseQueue: this.serviceRegistryQueue, // Queue for registry to send response
|
|
286
360
|
timestamp: new Date().toISOString()
|
|
287
361
|
};
|
|
288
362
|
|
|
@@ -323,21 +397,57 @@ class ServiceRegistryClient extends EventEmitter {
|
|
|
323
397
|
// CRITICAL: Use queueConfig.js to get correct parameters (TTL, max-length, etc.)
|
|
324
398
|
// This prevents 406 PRECONDITION-FAILED errors from TTL mismatches
|
|
325
399
|
console.log(`[RegistryClient] [PUBLISH] Preparing to publish to ${this.registryQueue}`);
|
|
326
|
-
|
|
400
|
+
|
|
401
|
+
try {
|
|
402
|
+
await this._ensureInfrastructureQueue(this.registryQueue);
|
|
403
|
+
} catch (queueError) {
|
|
404
|
+
const errorMsg = `[RegistryClient] ${this.serviceName}: Cannot register - infrastructure queue verification failed: ${queueError.message}`;
|
|
405
|
+
console.error(errorMsg);
|
|
406
|
+
throw new Error(errorMsg);
|
|
407
|
+
}
|
|
408
|
+
|
|
327
409
|
console.log(`[RegistryClient] ${this.serviceName}: Sending registration message to queue: ${this.registryQueue}`);
|
|
328
410
|
console.log(`[RegistryClient] ${this.serviceName}: Message type: ${msg.type}, serviceName: ${msg.serviceName}, version: ${msg.version}`);
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
411
|
+
|
|
412
|
+
// Send message synchronously (sendToQueue is synchronous, returns boolean)
|
|
413
|
+
// But check channel state first to fail fast
|
|
414
|
+
if (!this.queueManager.channel) {
|
|
415
|
+
throw new Error(`[RegistryClient] ${this.serviceName}: Cannot publish - channel not initialized. Ensure MQ connection is established.`);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (this.queueManager.channel.closed) {
|
|
419
|
+
throw new Error(`[RegistryClient] ${this.serviceName}: Cannot publish - channel is closed. MQ connection may be lost.`);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
try {
|
|
423
|
+
const sent = this.queueManager.channel.sendToQueue(
|
|
424
|
+
this.registryQueue,
|
|
425
|
+
Buffer.from(JSON.stringify(msg)),
|
|
426
|
+
{ persistent: true }
|
|
427
|
+
);
|
|
428
|
+
if (!sent) {
|
|
429
|
+
throw new Error(`[RegistryClient] ${this.serviceName}: sendToQueue returned false - queue may be full or channel backpressure active.`);
|
|
430
|
+
}
|
|
431
|
+
console.log(`[RegistryClient] ${this.serviceName}: ✓ Registration message sent, waiting for response...`);
|
|
432
|
+
} catch (sendError) {
|
|
433
|
+
const errorMsg = `[RegistryClient] ${this.serviceName}: Failed to send registration message: ${sendError.message}`;
|
|
434
|
+
console.error(errorMsg);
|
|
435
|
+
throw new Error(errorMsg);
|
|
436
|
+
}
|
|
335
437
|
|
|
336
438
|
this.emit('registerSent', msg);
|
|
337
439
|
|
|
338
440
|
// Wait for registration response from registry
|
|
339
441
|
try {
|
|
442
|
+
console.log(`[RegistryClient] ${this.serviceName}: [AWAIT] Waiting for registration response promise...`);
|
|
340
443
|
const response = await responsePromise;
|
|
444
|
+
console.log(`[RegistryClient] ${this.serviceName}: [AWAIT] ✓ Promise resolved, response received:`, {
|
|
445
|
+
hasResponse: !!response,
|
|
446
|
+
responseType: typeof response,
|
|
447
|
+
responseKeys: response ? Object.keys(response) : [],
|
|
448
|
+
hasSuccess: !!response?.success,
|
|
449
|
+
hasCertificate: !!response?.certificate
|
|
450
|
+
});
|
|
341
451
|
return response;
|
|
342
452
|
} catch (error) {
|
|
343
453
|
return {
|