@energyweb/node-red-contrib-green-proof-worker
Version:
## Peer dependencies
153 lines (152 loc) • 6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SourceHttpApiHttpAck = void 0;
const tslib_1 = require("tslib");
const promises_1 = require("timers/promises");
const z = tslib_1.__importStar(require("zod"));
const errors_1 = require("../errors");
const node_1 = require("../node");
const nodes_config_env_1 = require("../nodes-config-env");
const InputMessage = z.union([
z.object({
topic: z.literal('finished-processing'),
payload: z.object({
httpApiMessageId: z.string(),
})
}),
z.object({
topic: z.literal('force-reset')
})
]);
const Config = z.object({
namespace: z.string(),
host: z.string(),
workerId: z.string(),
});
const HttpMessage = z.object({
id: z.string(),
content: z.any(),
});
const SourceHttpApiHttpAck = (api) => class SourceHttpApi extends node_1.Node {
constructor(config) {
super(api, config, InputMessage);
/** Used to orchestrate consumer retry */
this.isDestroying = false;
this.nextMessageWaitTime = 10000;
this.pendingProcessing = null;
const envConfig = this.getNodeEnvConfig();
this.config = (async () => {
return Config.parse({
host: config.host || nodes_config_env_1.nodesGlobalEnvConfig.SOURCE_HTTP_API_HOST || await this.getBaseUrls().then(c => c.kafka_proxy_url),
namespace: envConfig.EWX_SOLUTION_ID || nodes_config_env_1.nodesGlobalEnvConfig.VOTING_SERVICE_CONFIG_SOLUTION_NAMESPACE,
workerId: envConfig.EWX_WORKER_ADDRESS || nodes_config_env_1.nodesGlobalEnvConfig.VOTING_SERVICE_CONFIG_SOLUTION_NAMESPACE
});
})();
void this.getMessageLoop();
}
async getMessageLoop() {
this.api.log('HTTP API Source started');
while (!this.isDestroying) {
try {
const result = await this.getMessage();
if (result) {
const message = HttpMessage.parse(result);
this.api.log(`Processing message id: ${message.id}`);
this.setStatus('pending');
this
.sendBuilder({})
.addPayload({
...message.content,
httpApiMessageId: message.id,
})
.sendToOutput(0);
await new Promise((resolve, reject) => {
this.pendingProcessing = {
messageId: message.id,
resolve,
reject,
};
});
await this.ackMessage(message.id);
this.api.log(`Message processed: ${message.id}`);
}
else {
this.api.log('No message available. Waiting before next retry');
this.setStatus('waiting');
await (0, promises_1.setTimeout)(this.nextMessageWaitTime);
}
}
catch (e) {
this.api.error(e.message);
this.setStatus('error');
await (0, promises_1.setTimeout)(this.nextMessageWaitTime);
}
}
}
setStatus(status) {
switch (status) {
case 'waiting':
const waitingTime = (this.nextMessageWaitTime / 1000).toFixed(2);
this.api.status({ fill: 'green', shape: 'ring', text: `Waiting ${waitingTime} seconds` });
break;
case 'error':
const waitingTimeError = (this.nextMessageWaitTime / 1000).toFixed(2);
this.api.status({ fill: 'red', shape: 'ring', text: `Encountered error. Waiting ${waitingTimeError} seconds` });
break;
case 'pending':
this.api.status({ fill: 'yellow', shape: 'dot', text: `Waiting for message (offset ${this.pendingProcessing?.messageId}) to be processed` });
break;
}
}
/**
* Input is responsible for resolving promise that http api is waiting for
*/
onInput(message) {
if (!this.pendingProcessing) {
return;
}
if (message.topic === 'force-reset') {
this.pendingProcessing.reject(new Error('HTTP API: Force resetted'));
this.pendingProcessing = null;
return;
}
if (this.pendingProcessing.messageId !== message.payload.httpApiMessageId) {
throw new errors_1.GGPError(errors_1.ErrorCode.SourceKafkaUnexpectedMessage, {
expected: this.pendingProcessing.messageId,
received: message.payload.httpApiMessageId
});
}
this.pendingProcessing.resolve();
this.pendingProcessing = null;
}
async getMessage() {
const url = await this.buildGetMessageUrl();
const result = await fetch(url, { method: 'GET' }).then(r => r.json());
return result;
}
async buildGetMessageUrl() {
const config = await this.config;
const url = new URL('/api/message/next', config.host);
url.searchParams.set('namespace', config.namespace);
url.searchParams.set('workerId', config.workerId);
return url;
}
async ackMessage(messageId) {
const config = await this.config;
const url = new URL('/api/message/ack', config.host);
await fetch(url, {
method: 'POST',
body: JSON.stringify({ messageId, workerId: config.workerId }),
headers: {
'Content-Type': 'application/json'
}
});
}
onDestroy() {
this.isDestroying = true;
if (this.pendingProcessing) {
this.pendingProcessing.reject(new Error('HTTP API: Destroying node'));
}
}
};
exports.SourceHttpApiHttpAck = SourceHttpApiHttpAck;