@bsb/events-rabbitmq 0.0.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.
- package/LICENSE +661 -0
- package/README.md +109 -0
- package/lib/plugins/events-rabbitmq/events/broadcast.d.ts +19 -0
- package/lib/plugins/events-rabbitmq/events/broadcast.js +105 -0
- package/lib/plugins/events-rabbitmq/events/emit.d.ts +17 -0
- package/lib/plugins/events-rabbitmq/events/emit.js +89 -0
- package/lib/plugins/events-rabbitmq/events/emitAndReturn.d.ts +20 -0
- package/lib/plugins/events-rabbitmq/events/emitAndReturn.js +207 -0
- package/lib/plugins/events-rabbitmq/events/emitStreamAndReceiveStream.d.ts +24 -0
- package/lib/plugins/events-rabbitmq/events/emitStreamAndReceiveStream.js +515 -0
- package/lib/plugins/events-rabbitmq/events/lib.d.ts +12 -0
- package/lib/plugins/events-rabbitmq/events/lib.js +59 -0
- package/lib/plugins/events-rabbitmq/index.d.ts +55 -0
- package/lib/plugins/events-rabbitmq/index.js +150 -0
- package/lib/plugins/events-rabbitmq/plugin.config.json +39 -0
- package/lib/schemas/events-rabbitmq.json +118 -0
- package/lib/schemas/events-rabbitmq.plugin.json +124 -0
- package/lib/tests/mocks.d.ts +3 -0
- package/lib/tests/mocks.js +65 -0
- package/lib/tests/plugins/events/events/broadcast.d.ts +4 -0
- package/lib/tests/plugins/events/events/broadcast.js +320 -0
- package/lib/tests/plugins/events/events/emit.d.ts +4 -0
- package/lib/tests/plugins/events/events/emit.js +316 -0
- package/lib/tests/plugins/events/events/emitAndReturn.d.ts +4 -0
- package/lib/tests/plugins/events/events/emitAndReturn.js +307 -0
- package/lib/tests/plugins/events/events/emitStreamAndReceiveStream.d.ts +4 -0
- package/lib/tests/plugins/events/events/emitStreamAndReceiveStream.js +199 -0
- package/lib/tests/plugins/events/plugin.d.ts +2 -0
- package/lib/tests/plugins/events/plugin.js +47 -0
- package/lib/tests/trace.d.ts +4 -0
- package/lib/tests/trace.js +39 -0
- package/package.json +70 -0
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
import { EventEmitter } from "events";
|
|
2
|
+
import { Readable } from "stream";
|
|
3
|
+
import { randomUUID } from "crypto";
|
|
4
|
+
import { LIB } from "./lib.js";
|
|
5
|
+
import { BSBError } from "@bsb/base";
|
|
6
|
+
export class emitStreamAndReceiveStream extends EventEmitter {
|
|
7
|
+
myEventsQueueKey() {
|
|
8
|
+
return LIB.getMyQueueKey(this.plugin, this.eventsChannelKey, this.plugin.myId);
|
|
9
|
+
}
|
|
10
|
+
myStreamQueueKey() {
|
|
11
|
+
return LIB.getMyQueueKey(this.plugin, this.streamChannelKey, this.plugin.myId);
|
|
12
|
+
}
|
|
13
|
+
cleanupSelf(streamId, key) {
|
|
14
|
+
this.removeAllListeners(this.eventsChannelKey + key + streamId);
|
|
15
|
+
this.removeAllListeners(this.streamChannelKey + key + streamId);
|
|
16
|
+
}
|
|
17
|
+
constructor(plugin) {
|
|
18
|
+
super();
|
|
19
|
+
this.staticCommsTimeout = 30000;
|
|
20
|
+
this.eventsChannelKey = "91se";
|
|
21
|
+
this.streamChannelKey = "91sd";
|
|
22
|
+
this.queueOpts = {
|
|
23
|
+
durable: false,
|
|
24
|
+
autoDelete: true,
|
|
25
|
+
messageTtl: 60 * 1000,
|
|
26
|
+
expires: 60 * 1000,
|
|
27
|
+
};
|
|
28
|
+
this.plugin = plugin;
|
|
29
|
+
}
|
|
30
|
+
dispose() {
|
|
31
|
+
this.removeAllListeners();
|
|
32
|
+
if (this.eventsChannel !== undefined) {
|
|
33
|
+
this.eventsChannel.channel.close();
|
|
34
|
+
}
|
|
35
|
+
if (this.streamChannel !== undefined) {
|
|
36
|
+
this.streamChannel.channel.close();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async setupChannel(obs, channel, channelKey, queueKeyMethod, logMessage) {
|
|
40
|
+
if (channel === undefined) {
|
|
41
|
+
const queueKey = queueKeyMethod();
|
|
42
|
+
channel = await LIB.setupChannel(this.plugin, obs, this.plugin.receiveConnection, channelKey, null, undefined, undefined, 2);
|
|
43
|
+
obs.log.debug("Ready {logMessage}: {queueKey}", { logMessage, queueKey });
|
|
44
|
+
await channel.channel.addSetup(async (iChannel) => {
|
|
45
|
+
await iChannel.assertQueue(queueKey, this.queueOpts);
|
|
46
|
+
obs.log.debug("LISTEN: [{queueKey}]", { queueKey });
|
|
47
|
+
await iChannel.consume(queueKey, async (msg) => {
|
|
48
|
+
if (msg === null) {
|
|
49
|
+
return obs.log.warn("[RECEIVED {queueKey}]... as null", {
|
|
50
|
+
queueKey,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const body = JSON.parse(msg.content.toString());
|
|
55
|
+
obs.log.debug("[RECEIVED {logMessage} {queueKey}]", {
|
|
56
|
+
logMessage,
|
|
57
|
+
queueKey,
|
|
58
|
+
});
|
|
59
|
+
this.emit(channelKey +
|
|
60
|
+
(logMessage === "stream" ? "r-" : "") +
|
|
61
|
+
msg.properties.correlationId, body, () => iChannel.ack(msg), () => iChannel.nack(msg));
|
|
62
|
+
}
|
|
63
|
+
catch (exc) {
|
|
64
|
+
obs.log.error("AMQP Consumed exception: {eMsg}", {
|
|
65
|
+
eMsg: exc.message || exc.toString(),
|
|
66
|
+
});
|
|
67
|
+
throw new Error(`AMQP consume exception: ${exc.message || exc}`);
|
|
68
|
+
}
|
|
69
|
+
}, { noAck: false });
|
|
70
|
+
obs.log.debug("LISTEN: [{queueKey}]", { queueKey });
|
|
71
|
+
obs.log.debug("Ready {logMessage} name: {queueKey} OKAY", {
|
|
72
|
+
logMessage,
|
|
73
|
+
queueKey,
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async setupChannelsIfNotSetup(obs) {
|
|
79
|
+
await this.setupChannel(obs, this.eventsChannel, this.eventsChannelKey, this.myEventsQueueKey, "events");
|
|
80
|
+
await this.setupChannel(obs, this.streamChannel, this.streamChannelKey, this.myStreamQueueKey, "stream");
|
|
81
|
+
}
|
|
82
|
+
async receiveStream(obs, pluginName, event, listener, timeoutSeconds = 5) {
|
|
83
|
+
const streamId = `${randomUUID()}-${new Date().getTime()}`;
|
|
84
|
+
let thisTimeoutMS = this.staticCommsTimeout;
|
|
85
|
+
obs.log.debug("SR: listening to {streamId}", {
|
|
86
|
+
streamId,
|
|
87
|
+
});
|
|
88
|
+
const self = this;
|
|
89
|
+
let dstEventsQueueKey;
|
|
90
|
+
let streamObs = null;
|
|
91
|
+
return new Promise(async (resolve) => {
|
|
92
|
+
await self.setupChannelsIfNotSetup(obs);
|
|
93
|
+
let stream = null;
|
|
94
|
+
let lastResponseTimeoutHandler = null;
|
|
95
|
+
let lastResponseTimeoutCount = 1;
|
|
96
|
+
let receiptTimeoutHandler;
|
|
97
|
+
let createTimeout = async (e) => {
|
|
98
|
+
throw new BSBError(obs.trace, "not setup yet : createTimeout");
|
|
99
|
+
};
|
|
100
|
+
const cleanup = () => {
|
|
101
|
+
self.cleanupSelf(streamId, "r-");
|
|
102
|
+
createTimeout = async (e) => {
|
|
103
|
+
obs.log.debug("voided timeout creator: {e}", { e });
|
|
104
|
+
};
|
|
105
|
+
obs.log.debug("Cleanup stuffR");
|
|
106
|
+
if (receiptTimeoutHandler !== null) {
|
|
107
|
+
clearTimeout(receiptTimeoutHandler);
|
|
108
|
+
}
|
|
109
|
+
receiptTimeoutHandler = null;
|
|
110
|
+
if (lastResponseTimeoutHandler !== null) {
|
|
111
|
+
clearTimeout(lastResponseTimeoutHandler);
|
|
112
|
+
}
|
|
113
|
+
lastResponseTimeoutHandler = null;
|
|
114
|
+
lastResponseTimeoutCount = -2;
|
|
115
|
+
if (stream !== null && !stream.destroyed) {
|
|
116
|
+
stream.destroy();
|
|
117
|
+
}
|
|
118
|
+
if (streamObs) {
|
|
119
|
+
streamObs.end();
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
receiptTimeoutHandler = setTimeout(async () => {
|
|
123
|
+
obs.log.debug("Receive Receipt Timeout");
|
|
124
|
+
const err = new Error("Receive Receipt Timeout");
|
|
125
|
+
cleanup();
|
|
126
|
+
if (!(await self.eventsChannel.channel.sendToQueue(dstEventsQueueKey, {
|
|
127
|
+
type: "timeout",
|
|
128
|
+
data: err,
|
|
129
|
+
trace: streamObs?.trace ?? obs.trace,
|
|
130
|
+
}, {
|
|
131
|
+
expiration: self.queueOpts.messageTtl,
|
|
132
|
+
correlationId: "s-" + streamId,
|
|
133
|
+
appId: self.plugin.myId,
|
|
134
|
+
timestamp: new Date().getTime(),
|
|
135
|
+
}))) {
|
|
136
|
+
throw `Cannot send msg to queue [${dstEventsQueueKey}]`;
|
|
137
|
+
}
|
|
138
|
+
await listener(streamObs ?? obs, err, null);
|
|
139
|
+
}, thisTimeoutMS);
|
|
140
|
+
const timeoutFunc = async () => {
|
|
141
|
+
if (lastResponseTimeoutHandler === null) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (lastResponseTimeoutCount === -2) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (lastResponseTimeoutCount > 0) {
|
|
148
|
+
lastResponseTimeoutCount--;
|
|
149
|
+
await createTimeout("timeoutFunc");
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const err = new Error("Receive Active Timeout");
|
|
153
|
+
obs.log.error("Receive Active Timeout");
|
|
154
|
+
cleanup();
|
|
155
|
+
if (!(await self.eventsChannel.channel.sendToQueue(dstEventsQueueKey, {
|
|
156
|
+
type: "timeout",
|
|
157
|
+
data: err,
|
|
158
|
+
trace: streamObs?.trace ?? obs.trace,
|
|
159
|
+
}, {
|
|
160
|
+
expiration: self.queueOpts.messageTtl,
|
|
161
|
+
correlationId: "s-" + streamId,
|
|
162
|
+
appId: self.plugin.myId,
|
|
163
|
+
timestamp: new Date().getTime(),
|
|
164
|
+
}))) {
|
|
165
|
+
throw `Cannot send msg to queue [${dstEventsQueueKey}]`;
|
|
166
|
+
}
|
|
167
|
+
await listener(streamObs ?? obs, err, null);
|
|
168
|
+
};
|
|
169
|
+
createTimeout = async () => {
|
|
170
|
+
if (lastResponseTimeoutCount === -2) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (lastResponseTimeoutHandler === null) {
|
|
174
|
+
lastResponseTimeoutHandler = setTimeout(timeoutFunc, thisTimeoutMS);
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
const updateLastResponseTimer = () => {
|
|
178
|
+
if (lastResponseTimeoutCount === -2) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
lastResponseTimeoutCount = 1;
|
|
182
|
+
createTimeout("updateLastResponseTimer");
|
|
183
|
+
};
|
|
184
|
+
const startStream = async () => {
|
|
185
|
+
obs.log.debug("START STREAM RECEIVER");
|
|
186
|
+
thisTimeoutMS = timeoutSeconds * 1000;
|
|
187
|
+
if (!(await self.eventsChannel.channel.sendToQueue(dstEventsQueueKey, { type: "receipt", timeout: thisTimeoutMS, trace: streamObs?.trace ?? obs.trace }, {
|
|
188
|
+
expiration: self.queueOpts.messageTtl,
|
|
189
|
+
correlationId: "s-" + streamId,
|
|
190
|
+
appId: self.plugin.myId,
|
|
191
|
+
timestamp: new Date().getTime(),
|
|
192
|
+
}))) {
|
|
193
|
+
throw `Cannot send msg to queue [${dstEventsQueueKey}] ${streamId}`;
|
|
194
|
+
}
|
|
195
|
+
try {
|
|
196
|
+
stream = new Readable({
|
|
197
|
+
objectMode: true,
|
|
198
|
+
async read() {
|
|
199
|
+
if (!(await self.eventsChannel.channel.sendToQueue(dstEventsQueueKey, { type: "read", trace: streamObs?.trace ?? obs.trace }, {
|
|
200
|
+
expiration: self.queueOpts.messageTtl,
|
|
201
|
+
correlationId: "s-" + streamId,
|
|
202
|
+
appId: self.plugin.myId,
|
|
203
|
+
timestamp: new Date().getTime(),
|
|
204
|
+
}))) {
|
|
205
|
+
throw `Cannot send msg to queue [${dstEventsQueueKey}] ${streamId}`;
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
obs.log.debug("[R RECEVIED {streamRefId}] {streamId}", {
|
|
210
|
+
streamRefId: dstEventsQueueKey,
|
|
211
|
+
streamId,
|
|
212
|
+
});
|
|
213
|
+
const eventsToListenTo = [
|
|
214
|
+
"error",
|
|
215
|
+
"end",
|
|
216
|
+
];
|
|
217
|
+
for (const evnt of eventsToListenTo) {
|
|
218
|
+
stream.on(evnt, async (e) => {
|
|
219
|
+
if (!(await self.eventsChannel.channel.sendToQueue(dstEventsQueueKey, {
|
|
220
|
+
type: "event",
|
|
221
|
+
event: evnt,
|
|
222
|
+
data: e || null,
|
|
223
|
+
trace: streamObs?.trace ?? obs.trace,
|
|
224
|
+
}, {
|
|
225
|
+
expiration: self.queueOpts.messageTtl,
|
|
226
|
+
correlationId: "s-" + streamId,
|
|
227
|
+
appId: self.plugin.myId,
|
|
228
|
+
timestamp: new Date().getTime(),
|
|
229
|
+
}))) {
|
|
230
|
+
throw `Cannot send msg to queue [${dstEventsQueueKey}] ${streamId}`;
|
|
231
|
+
}
|
|
232
|
+
if (evnt === "end") {
|
|
233
|
+
cleanup();
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
self.on(self.streamChannelKey + "r-" + streamId, async (data, ack, nack) => {
|
|
238
|
+
if (data === null) {
|
|
239
|
+
nack();
|
|
240
|
+
return obs.log.debug("[R RECEVIED {streamId}]... as null", {
|
|
241
|
+
streamId,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
if (!(await self.eventsChannel.channel.sendToQueue(dstEventsQueueKey, {
|
|
245
|
+
type: "receipt",
|
|
246
|
+
timeout: thisTimeoutMS,
|
|
247
|
+
trace: streamObs?.trace ?? obs.trace,
|
|
248
|
+
}, {
|
|
249
|
+
expiration: self.queueOpts.messageTtl,
|
|
250
|
+
correlationId: "s-" + streamId,
|
|
251
|
+
appId: self.plugin.myId,
|
|
252
|
+
timestamp: new Date().getTime(),
|
|
253
|
+
}))) {
|
|
254
|
+
throw `Cannot send msg to queue [${dstEventsQueueKey}] ${streamId}`;
|
|
255
|
+
}
|
|
256
|
+
if (data.type === "event") {
|
|
257
|
+
stream.emit(data.event, data.data !== undefined ? data.data : null);
|
|
258
|
+
ack();
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (data.type === "data") {
|
|
262
|
+
stream.push(Buffer.from(data.data));
|
|
263
|
+
ack();
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
nack();
|
|
267
|
+
});
|
|
268
|
+
listener(streamObs ?? obs, null, stream)
|
|
269
|
+
.then(async () => {
|
|
270
|
+
obs.log.info("stream OK");
|
|
271
|
+
})
|
|
272
|
+
.catch(async (x) => {
|
|
273
|
+
cleanup();
|
|
274
|
+
obs.log.error("Stream NOT OK: {e}", {
|
|
275
|
+
e: x.message,
|
|
276
|
+
});
|
|
277
|
+
throw new Error(`Stream processing failed: ${x.message}`);
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
catch (exc) {
|
|
281
|
+
cleanup();
|
|
282
|
+
obs.log.error("Stream NOT OK: {e}", {
|
|
283
|
+
e: exc.message || exc,
|
|
284
|
+
});
|
|
285
|
+
throw new Error(`Stream setup failed: ${exc.message || exc}`);
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
self.on(self.eventsChannelKey + "r-" + streamId, async (data, ack, nack) => {
|
|
289
|
+
if (receiptTimeoutHandler !== null) {
|
|
290
|
+
clearTimeout(receiptTimeoutHandler);
|
|
291
|
+
receiptTimeoutHandler = null;
|
|
292
|
+
}
|
|
293
|
+
updateLastResponseTimer();
|
|
294
|
+
if (data === null) {
|
|
295
|
+
return obs.log.debug(`[R RECEVIED {streamEventsRefId}]... as null`, { streamEventsRefId: dstEventsQueueKey });
|
|
296
|
+
}
|
|
297
|
+
if (data.type === "timeout") {
|
|
298
|
+
cleanup();
|
|
299
|
+
listener(streamObs ?? obs, data.data, null);
|
|
300
|
+
ack();
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (data.type === "start") {
|
|
304
|
+
const trace = data.trace ?? obs.trace;
|
|
305
|
+
streamObs = this.plugin.createObservableFromTrace(trace, {
|
|
306
|
+
pluginName,
|
|
307
|
+
event,
|
|
308
|
+
streamId,
|
|
309
|
+
}).startSpan("stream.receive", { pluginName, event, streamId });
|
|
310
|
+
obs.log.debug("Readying to stream from: {fromId}", {
|
|
311
|
+
fromId: data.myId,
|
|
312
|
+
});
|
|
313
|
+
dstEventsQueueKey = LIB.getMyQueueKey(self.plugin, this.eventsChannelKey, data.myId);
|
|
314
|
+
await startStream();
|
|
315
|
+
obs.log.debug("Starting to stream");
|
|
316
|
+
ack();
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
nack();
|
|
320
|
+
});
|
|
321
|
+
resolve(`${this.plugin.myId}||${streamId}||${timeoutSeconds}`);
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
async sendStream(obs, pluginName, event, streamIdf, stream) {
|
|
325
|
+
if (streamIdf.split("||").length !== 3) {
|
|
326
|
+
throw new BSBError(obs.trace, "invalid stream ID [{id}]", { id: streamIdf });
|
|
327
|
+
}
|
|
328
|
+
const streamReceiverId = streamIdf.split("||")[0];
|
|
329
|
+
const streamId = streamIdf.split("||")[1];
|
|
330
|
+
const streamTimeoutS = Number.parseInt(streamIdf.split("||")[2]);
|
|
331
|
+
let thisTimeoutMS = this.staticCommsTimeout;
|
|
332
|
+
const dstEventsQueueKey = LIB.getMyQueueKey(this.plugin, this.eventsChannelKey, streamReceiverId);
|
|
333
|
+
const dstStreamQueueKey = LIB.getMyQueueKey(this.plugin, this.streamChannelKey, streamReceiverId);
|
|
334
|
+
const self = this;
|
|
335
|
+
const sendObs = obs.startSpan("stream.send", { pluginName, event, streamId });
|
|
336
|
+
sendObs.log.info("SS: emitting to {dstEventsQueueKey}/{dstStreamQueueKey}", {
|
|
337
|
+
dstEventsQueueKey,
|
|
338
|
+
dstStreamQueueKey,
|
|
339
|
+
});
|
|
340
|
+
return new Promise(async (resolveI, rejectI) => {
|
|
341
|
+
await self.setupChannelsIfNotSetup(sendObs);
|
|
342
|
+
let lastResponseTimeoutHandler = null;
|
|
343
|
+
let lastResponseTimeoutCount = 1;
|
|
344
|
+
let receiptTimeoutHandler = setTimeout(() => {
|
|
345
|
+
reject(new Error("Send Receipt Timeout"));
|
|
346
|
+
}, thisTimeoutMS);
|
|
347
|
+
const cleanup = async (eType, e) => {
|
|
348
|
+
sendObs.log.debug("cleanup: {eType}", { eType });
|
|
349
|
+
self.cleanupSelf(streamId, "s-");
|
|
350
|
+
stream.destroy(e);
|
|
351
|
+
if (receiptTimeoutHandler !== null) {
|
|
352
|
+
clearTimeout(receiptTimeoutHandler);
|
|
353
|
+
}
|
|
354
|
+
if (lastResponseTimeoutHandler !== null) {
|
|
355
|
+
clearTimeout(lastResponseTimeoutHandler);
|
|
356
|
+
}
|
|
357
|
+
receiptTimeoutHandler = null;
|
|
358
|
+
lastResponseTimeoutHandler = null;
|
|
359
|
+
sendObs.end();
|
|
360
|
+
};
|
|
361
|
+
const reject = async (e) => {
|
|
362
|
+
await cleanup("reject-" + e.message, e);
|
|
363
|
+
sendObs.error(e);
|
|
364
|
+
rejectI(e);
|
|
365
|
+
};
|
|
366
|
+
const resolve = async () => {
|
|
367
|
+
await cleanup("resolved");
|
|
368
|
+
resolveI();
|
|
369
|
+
};
|
|
370
|
+
const updateLastResponseTimer = () => {
|
|
371
|
+
lastResponseTimeoutCount = 1;
|
|
372
|
+
if (lastResponseTimeoutHandler === null) {
|
|
373
|
+
let createTimeout = () => {
|
|
374
|
+
throw new BSBError(sendObs.trace, "not setup yet : createTimeout");
|
|
375
|
+
};
|
|
376
|
+
const timeoutFunc = async () => {
|
|
377
|
+
if (lastResponseTimeoutCount > 0) {
|
|
378
|
+
lastResponseTimeoutCount--;
|
|
379
|
+
createTimeout();
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
sendObs.log.debug("Receive Receipt Timeout");
|
|
383
|
+
const err = new Error("Receive Active Timeout");
|
|
384
|
+
await cleanup("active-timeout");
|
|
385
|
+
if (!(await self.eventsChannel.channel.sendToQueue(dstEventsQueueKey, {
|
|
386
|
+
type: "timeout",
|
|
387
|
+
data: err,
|
|
388
|
+
trace: sendObs.trace,
|
|
389
|
+
}, {
|
|
390
|
+
expiration: self.queueOpts.messageTtl,
|
|
391
|
+
correlationId: "r-" + streamId,
|
|
392
|
+
appId: self.plugin.myId,
|
|
393
|
+
timestamp: new Date().getTime(),
|
|
394
|
+
}))) {
|
|
395
|
+
throw `Cannot send msg to queue [${dstEventsQueueKey}]`;
|
|
396
|
+
}
|
|
397
|
+
rejectI(err);
|
|
398
|
+
};
|
|
399
|
+
createTimeout = () => {
|
|
400
|
+
lastResponseTimeoutHandler = setTimeout(timeoutFunc, thisTimeoutMS);
|
|
401
|
+
};
|
|
402
|
+
createTimeout();
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
const eventsToListenTo = [
|
|
406
|
+
"error",
|
|
407
|
+
"end",
|
|
408
|
+
];
|
|
409
|
+
for (const evnt of eventsToListenTo) {
|
|
410
|
+
stream.on(evnt, async (e, b, ack, nack) => {
|
|
411
|
+
if (!(await self.streamChannel.channel.sendToQueue(dstStreamQueueKey, { type: "event", event: evnt, data: e || null, trace: sendObs.trace }, {
|
|
412
|
+
expiration: self.queueOpts.messageTtl,
|
|
413
|
+
correlationId: streamId,
|
|
414
|
+
appId: self.plugin.myId,
|
|
415
|
+
timestamp: new Date().getTime(),
|
|
416
|
+
}))) {
|
|
417
|
+
nack();
|
|
418
|
+
throw `Cannot send msg to queue [${dstEventsQueueKey}] ${streamId}`;
|
|
419
|
+
}
|
|
420
|
+
ack();
|
|
421
|
+
if (evnt === "error") {
|
|
422
|
+
reject(e);
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
let pushingData = false;
|
|
427
|
+
let streamStarted = false;
|
|
428
|
+
const pushData = async () => {
|
|
429
|
+
if (pushingData) {
|
|
430
|
+
sendObs.log.warn("Stream tried pushing data, but not ready to push data!");
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
pushingData = true;
|
|
434
|
+
sendObs.log.warn("Switching to push data model.");
|
|
435
|
+
stream.on("data", async (data) => {
|
|
436
|
+
if (!(await self.streamChannel.channel.sendToQueue(dstStreamQueueKey, { type: "data", data, trace: sendObs.trace }, {
|
|
437
|
+
expiration: self.queueOpts.messageTtl,
|
|
438
|
+
correlationId: streamId,
|
|
439
|
+
appId: self.plugin.myId,
|
|
440
|
+
timestamp: new Date().getTime(),
|
|
441
|
+
}))) {
|
|
442
|
+
pushingData = false;
|
|
443
|
+
sendObs.log.error(`Cannot push msg to queue [{dstStreamQueueKey}] {streamId} / switch back to poll model.`, { dstStreamQueueKey, streamId });
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
};
|
|
447
|
+
self.on(self.eventsChannelKey + "s-" + streamId, async (data, ack, nack) => {
|
|
448
|
+
if (receiptTimeoutHandler !== null) {
|
|
449
|
+
clearTimeout(receiptTimeoutHandler);
|
|
450
|
+
receiptTimeoutHandler = null;
|
|
451
|
+
}
|
|
452
|
+
updateLastResponseTimer();
|
|
453
|
+
if (data === null) {
|
|
454
|
+
nack();
|
|
455
|
+
return sendObs.log.debug(`[S RECEVIED {dstEventsQueueKey}]... as null`, { dstEventsQueueKey });
|
|
456
|
+
}
|
|
457
|
+
if (data.type === "timeout") {
|
|
458
|
+
await reject(new Error("timeout-receiver"));
|
|
459
|
+
return ack();
|
|
460
|
+
}
|
|
461
|
+
if (data.type === "receipt") {
|
|
462
|
+
thisTimeoutMS = data.timeout;
|
|
463
|
+
return ack();
|
|
464
|
+
}
|
|
465
|
+
if (data.type === "event") {
|
|
466
|
+
if (data.event === "end") {
|
|
467
|
+
ack();
|
|
468
|
+
return resolve();
|
|
469
|
+
}
|
|
470
|
+
stream.emit(data.event, data.data || null, "RECEIVED");
|
|
471
|
+
return ack();
|
|
472
|
+
}
|
|
473
|
+
if (data.type === "read") {
|
|
474
|
+
if (pushingData) {
|
|
475
|
+
return ack();
|
|
476
|
+
}
|
|
477
|
+
const readData = stream.read();
|
|
478
|
+
if (!stream.readable || readData === null) {
|
|
479
|
+
sendObs.log.info("Stream no longer readable.");
|
|
480
|
+
if (!streamStarted) {
|
|
481
|
+
await pushData();
|
|
482
|
+
}
|
|
483
|
+
return ack();
|
|
484
|
+
}
|
|
485
|
+
streamStarted = true;
|
|
486
|
+
if (!(await self.streamChannel.channel.sendToQueue(dstStreamQueueKey, { type: "data", data: readData, trace: sendObs.trace }, {
|
|
487
|
+
expiration: self.queueOpts.messageTtl,
|
|
488
|
+
correlationId: streamId,
|
|
489
|
+
appId: self.plugin.myId,
|
|
490
|
+
timestamp: new Date().getTime(),
|
|
491
|
+
}))) {
|
|
492
|
+
nack();
|
|
493
|
+
throw `Cannot send msg to queue [${dstStreamQueueKey}] ${streamId}`;
|
|
494
|
+
}
|
|
495
|
+
ack();
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
ack();
|
|
499
|
+
});
|
|
500
|
+
sendObs.log.info("SS: setup, ready {streamEventsRefId}", {
|
|
501
|
+
streamEventsRefId: dstEventsQueueKey,
|
|
502
|
+
});
|
|
503
|
+
if (!(await self.eventsChannel.channel.sendToQueue(dstEventsQueueKey, { type: "start", myId: self.plugin.myId, trace: sendObs.trace }, {
|
|
504
|
+
expiration: self.queueOpts.messageTtl,
|
|
505
|
+
correlationId: "r-" + streamId,
|
|
506
|
+
appId: self.plugin.myId,
|
|
507
|
+
timestamp: new Date().getTime(),
|
|
508
|
+
}))) {
|
|
509
|
+
throw `Cannot send msg to queue [${dstEventsQueueKey}]`;
|
|
510
|
+
}
|
|
511
|
+
thisTimeoutMS = streamTimeoutS * 1000;
|
|
512
|
+
sendObs.log.info("SS: emitted {dstEventsQueueKey} with timeout of {thisTimeoutMS}", { dstEventsQueueKey, thisTimeoutMS });
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as amqplib from "amqp-connection-manager";
|
|
2
|
+
import { Plugin } from "../index.js";
|
|
3
|
+
import { Observable } from "@bsb/base";
|
|
4
|
+
export interface SetupChannel<T extends string | null = string | null> {
|
|
5
|
+
exchangeName: T;
|
|
6
|
+
channel: amqplib.ChannelWrapper;
|
|
7
|
+
}
|
|
8
|
+
export declare class LIB {
|
|
9
|
+
static getQueueKey(plugin: Plugin, channelKey: string, pluginName: string, event: string, addKey?: string): string;
|
|
10
|
+
static getMyQueueKey(plugin: Plugin, channelKey: string, id: string, addKey?: string): string;
|
|
11
|
+
static setupChannel<T extends string | null>(plugin: Plugin, obs: Observable | null, connection: amqplib.AmqpConnectionManager, queueKey: string, exchangeName: T, exType?: string, exOpts?: amqplib.Options.AssertExchange, prefetch?: number, json?: boolean): Promise<SetupChannel<T>>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const isNil = (value) => value === null || value === undefined;
|
|
2
|
+
export class LIB {
|
|
3
|
+
static getQueueKey(plugin, channelKey, pluginName, event, addKey) {
|
|
4
|
+
return `${plugin.getPlatformName(channelKey)}-${pluginName}-${event}${isNil(addKey) ? "" : `-${addKey}`}`;
|
|
5
|
+
}
|
|
6
|
+
static getMyQueueKey(plugin, channelKey, id, addKey) {
|
|
7
|
+
return `${plugin.getPlatformName(channelKey)}-${id}${isNil(addKey) ? "" : `-${addKey}`}`;
|
|
8
|
+
}
|
|
9
|
+
static async setupChannel(plugin, obs, connection, queueKey, exchangeName, exType, exOpts, prefetch, json = true) {
|
|
10
|
+
return new Promise(async (resolve) => {
|
|
11
|
+
const exName = isNil(exchangeName) || isNil(exType)
|
|
12
|
+
? null
|
|
13
|
+
: plugin.getPlatformName(exchangeName);
|
|
14
|
+
let returned = false;
|
|
15
|
+
obs?.log.debug("Create channel ({queueKey})", { queueKey });
|
|
16
|
+
const channel = await connection.createChannel({
|
|
17
|
+
json,
|
|
18
|
+
setup: async (ichannel) => {
|
|
19
|
+
if (exName !== null)
|
|
20
|
+
await ichannel.assertExchange(exName, exType, exOpts);
|
|
21
|
+
if (!isNil(prefetch)) {
|
|
22
|
+
obs?.log.debug("prefetch ({queueKey}) {prefetch}", {
|
|
23
|
+
queueKey,
|
|
24
|
+
prefetch: prefetch,
|
|
25
|
+
});
|
|
26
|
+
await ichannel.prefetch(prefetch);
|
|
27
|
+
}
|
|
28
|
+
obs?.log.debug("setup exchange ({queueKey}) OK", {
|
|
29
|
+
queueKey,
|
|
30
|
+
});
|
|
31
|
+
if (!returned) {
|
|
32
|
+
resolve({
|
|
33
|
+
exchangeName: exName,
|
|
34
|
+
channel,
|
|
35
|
+
});
|
|
36
|
+
returned = true;
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
channel.on("close", () => {
|
|
41
|
+
obs?.log.warn("AMQP channel ({queueKey}) close", { queueKey });
|
|
42
|
+
});
|
|
43
|
+
channel.on("error", (err) => {
|
|
44
|
+
obs?.log.error("AMQP channel ({queueKey}) error: {err}", {
|
|
45
|
+
queueKey,
|
|
46
|
+
err: err.message || err,
|
|
47
|
+
});
|
|
48
|
+
throw new Error(`AMQP channel (${queueKey}) error: ${err.message || err}`);
|
|
49
|
+
});
|
|
50
|
+
if (exName !== null)
|
|
51
|
+
obs?.log.debug("Assert exchange ({queueKey}) {exName} {exType}", {
|
|
52
|
+
queueKey,
|
|
53
|
+
exName,
|
|
54
|
+
exType: exType,
|
|
55
|
+
});
|
|
56
|
+
obs?.log.debug("Ready ({queueKey})", { queueKey });
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import * as amqplib from "amqp-connection-manager";
|
|
2
|
+
import { Readable } from "stream";
|
|
3
|
+
import { BSBEvents, BSBEventsConstructor, Observable, DTrace } from "@bsb/base";
|
|
4
|
+
import * as av from "anyvali";
|
|
5
|
+
export declare const Config: import("@bsb/base").BSBPluginConfigClass<av.ObjectSchema<{
|
|
6
|
+
platformKey: av.NullableSchema<av.StringSchema>;
|
|
7
|
+
fatalOnDisconnect: av.BoolSchema;
|
|
8
|
+
prefetch: av.Int32Schema;
|
|
9
|
+
endpoints: av.ArraySchema<av.StringSchema>;
|
|
10
|
+
credentials: av.ObjectSchema<{
|
|
11
|
+
username: av.StringSchema;
|
|
12
|
+
password: av.StringSchema;
|
|
13
|
+
}>;
|
|
14
|
+
uniqueId: av.NullableSchema<av.StringSchema>;
|
|
15
|
+
}>>;
|
|
16
|
+
export declare class Plugin extends BSBEvents<InstanceType<typeof Config>> {
|
|
17
|
+
static Config: import("@bsb/base").BSBPluginConfigClass<av.ObjectSchema<{
|
|
18
|
+
platformKey: av.NullableSchema<av.StringSchema>;
|
|
19
|
+
fatalOnDisconnect: av.BoolSchema;
|
|
20
|
+
prefetch: av.Int32Schema;
|
|
21
|
+
endpoints: av.ArraySchema<av.StringSchema>;
|
|
22
|
+
credentials: av.ObjectSchema<{
|
|
23
|
+
username: av.StringSchema;
|
|
24
|
+
password: av.StringSchema;
|
|
25
|
+
}>;
|
|
26
|
+
uniqueId: av.NullableSchema<av.StringSchema>;
|
|
27
|
+
}>>;
|
|
28
|
+
publishConnection: amqplib.AmqpConnectionManager;
|
|
29
|
+
receiveConnection: amqplib.AmqpConnectionManager;
|
|
30
|
+
myId: string;
|
|
31
|
+
private ear;
|
|
32
|
+
private broadcast;
|
|
33
|
+
private emit;
|
|
34
|
+
private eas;
|
|
35
|
+
constructor(config: BSBEventsConstructor<InstanceType<typeof Config>>);
|
|
36
|
+
getPlatformName(name: string): string;
|
|
37
|
+
init(obs: Observable): Promise<void>;
|
|
38
|
+
createObservableFromTrace(trace?: DTrace, attributes?: Record<string, string | number | boolean>): Observable;
|
|
39
|
+
private _connectToAMQP;
|
|
40
|
+
dispose(): void;
|
|
41
|
+
onBroadcast(obs: Observable, pluginName: string, event: string, listener: {
|
|
42
|
+
(obs: Observable, args: any[]): Promise<void>;
|
|
43
|
+
}): Promise<void>;
|
|
44
|
+
emitBroadcast(obs: Observable, pluginName: string, event: string, args: any[]): Promise<void>;
|
|
45
|
+
onEvent(obs: Observable, pluginName: string, event: string, listener: {
|
|
46
|
+
(obs: Observable, args: any[]): Promise<void>;
|
|
47
|
+
}): Promise<void>;
|
|
48
|
+
emitEvent(obs: Observable, pluginName: string, event: string, args: any[]): Promise<void>;
|
|
49
|
+
onReturnableEvent(obs: Observable, pluginName: string, event: string, listener: {
|
|
50
|
+
(obs: Observable, args: any[]): Promise<any>;
|
|
51
|
+
}): Promise<void>;
|
|
52
|
+
emitEventAndReturn(obs: Observable, pluginName: string, event: string, timeoutSeconds: number, args: any[]): Promise<any>;
|
|
53
|
+
receiveStream(obs: Observable, pluginName: string, event: string, listener: (obs: Observable, error: Error | null, stream: Readable) => Promise<void>, timeoutSeconds?: number | undefined): Promise<string>;
|
|
54
|
+
sendStream(obs: Observable, pluginName: string, event: string, streamId: string, stream: Readable): Promise<void>;
|
|
55
|
+
}
|