@energyweb/node-red-contrib-green-proof-worker
Version:
126 lines (125 loc) • 5.63 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Node = exports.BaseUrlsConfig = exports.NodeEnvConfig = void 0;
const tslib_1 = require("tslib");
const z = tslib_1.__importStar(require("zod"));
const errors_1 = require("./errors");
const nodes_config_env_1 = require("./nodes-config-env");
exports.NodeEnvConfig = z.object({
EWX_SOLUTION_ID: z.string().optional(),
EWX_SOLUTION_GROUP_ID: z.string().optional(),
EWX_WORKLOGIC_ID: z.string().optional(),
EWX_SQLITE_PATH: z.string().optional(),
EWX_WORKER_ADDRESS: z.string().optional(),
/** it was supposed to be BASE_URL_CDN but other people got it wrong at some point, so it is now BASE_URL */
BASE_URL: z.string().optional(),
});
exports.BaseUrlsConfig = z.object({
kafka_url: z.union([z.string(), z.array(z.string())]).optional(),
kafka_proxy_url: z.string().optional(),
base_indexer_url: z.string().optional(),
rpc_url: z.string().optional(),
});
class Node {
constructor(api, nodeConfig, // this is NodeDef from node-red, but let's keep it unknown for convenience (we add custom types there)
messageZod, { validateMessage = true } = {}) {
this.nodeConfig = nodeConfig;
this.messageZod = messageZod;
// node-red will bind its functions to a node constructor,
// therefore if we pass `this` to API, full instance with all node-red things will be available
this.api = api.cloneAndInstantiate(this, nodeConfig);
this.api.on('input', (msg, done) => {
const validator = validateMessage ? this.messageZod : z.looseObject({});
const parseResult = validator.safeParse(msg);
if (!parseResult.success) {
this.api.error(`[${this.constructor.name}] Invalid input message .payload: ${JSON.stringify(msg)}. Errors: ${parseResult.error}`);
done(new errors_1.GGPError(errors_1.ErrorCode.InvalidPayload, { payload: JSON.stringify(msg), error: parseResult.error.message }));
return;
}
this.handleMaybePromise(() => this.onInput(parseResult.data), done);
});
this.api.on('close', (done) => {
this.handleMaybePromise(() => (this.onDestroy ? this.onDestroy() : undefined), done);
});
}
/**
* Returns base_urls from a special endpoint maintained by EWF that contains configuration
*/
async getBaseUrls() {
// Default is here, because this is a new feature, so we have time to migrate
// @TODO remove default at some point so its not confusing
const baseUrl = this.getNodeEnvConfig().BASE_URL || nodes_config_env_1.nodesGlobalEnvConfig.BASE_URL || 'https://marketplace-cdn.energyweb.org/base_urls.json';
const response = await fetch(baseUrl, { method: 'GET' }).then(response => response.json());
return exports.BaseUrlsConfig.parse(response);
}
/**
* Returns parsed `.__envConfig` from node's config.
* node's config, is basically one entry in flow.json
* Marketplace will be injecting that property to each node before node-red start
* Note, that if `.__envConfig` is injected that way, even the slightest node change (through node-red GUI) removes that property
* because it is unknown to node-red.
*/
getNodeEnvConfig() {
const envConfig = this.nodeConfig.__envConfig;
if (!envConfig) {
return {};
}
return exports.NodeEnvConfig.parse(envConfig);
}
/**
* SendBuilder hides complexity of creating proper messages, and selecting output.
* It is supposed to be used by all nodes to send messages to outputs
* to ensure correct behavior.
*
* It uses message received on node input (passed manually), and then the payload should be only
* >added< (not replaced!) to the input message. Optionally topic can be set.
*
* Note that if topic is not set explicitly, it will be removed (because topic is used
* to describe what the node receiving it should do with the message, so it's not meant to be passed around
* like message payload).
*/
sendBuilder(inputMessage) {
const internalBuilder = (message) => ({
addPayload: (payload) => internalBuilder({
...message,
payload: {
...message.payload,
...payload,
}
}),
setTopic: (topic) => internalBuilder({
...message,
topic,
}),
sendToOutput: (index) => {
const messages = new Array(index + 1).fill(null);
messages[index] = message;
this.api.send(messages);
},
getMessage: () => message,
});
const { topic, ...messageWithoutTopic } = inputMessage;
return internalBuilder(messageWithoutTopic);
}
handleMaybePromise(maybePromiseCb, done) {
try {
const maybePromise = maybePromiseCb();
if (maybePromise instanceof Promise) {
void maybePromise.then(() => {
done();
}).catch((err) => {
this.api.error(`[${this.constructor.name}] Error: ${err.message} ${err.stack}`);
done(err);
});
}
else {
done();
}
}
catch (err) {
this.api.error(`[${this.constructor.name}] Error: ${err.message} ${err.stack}`);
done(err);
}
}
}
exports.Node = Node;