// core-node.js // Copyright (C) 2022 DTP Technologies, LLC // License: Apache-2.0 'use strict'; const uuidv4 = require('uuid').v4; const mongoose = require('mongoose'); const fetch = require('node-fetch'); // jshint ignore:line const CoreNode = mongoose.model('CoreNode'); const CoreNodeRequest = mongoose.model('CoreNodeRequest'); const { SiteService, SiteError } = require('../../lib/site-lib'); class CoreAddress { constructor (host, port) { this.host = host; this.port = port; } parse (host) { const tokens = host.split(':'); this.host = tokens[0]; if (tokens[1]) { this.port = parseInt(tokens[1], 10); } else { this.port = 443; } return this; } } class CoreNodeService extends SiteService { constructor (dtp) { super(dtp, module.exports); } parseCoreAddress (host) { const address = new CoreAddress(); return address.parse(host); } async getCoreByAddress (address) { const core = await CoreNode .findOne({ 'address.host': address.host, 'address.port': address.port, }) .lean(); return core; } /** * First ensures that a record exists in the local database for the Core node. * Then, calls the node's info services to resolve more metadata about the * node, it's operation, policies, and available services. * * @param {String} host hostname and optional port number of Core node to be * resolved. * @returns CoreNode document describing the Core node. */ async resolveCore (host) { const NOW = new Date(); this.log.info('resolving Core node', { host }); const address = this.parseCoreAddress(host); let core = await this.getCoreByAddress(address); if (!core) { core = new CoreNode(); core.created = NOW; core.address.host = address.host; core.address.port = address.port; await core.save(); core = core.toObject(); } const txSite = await this.sendRequest(core, { method: 'GET', url: '/core/info/site', }); const txPackage = await this.sendRequest(core, { method: 'GET', url: '/core/info/package', }); await CoreNode.updateOne( { _id: core._id }, { $set: { updated: NOW, 'meta.name': txSite.response.site.name, 'meta.description': txSite.response.site.description, 'meta.domain': txSite.response.site.domain, 'meta.domainKey': txSite.response.site.domainKey, 'meta.version': txPackage.response.package.version, 'meta.admin': txSite.response.site.admin, 'meta.supportEmail': txSite.response.site.supportEmail, }, }, ); core = await CoreNode.findOne({ _id: core._id }).lean(); this.log.info('resolved Core node', { core }); return core; } async broadcast (request) { const results = [ ]; await CoreNode .find() .cursor() .eachAsync(async (core) => { try { const response = await this.sendRequest(core, request); results.push({ coreId: core._id, request, response }); } catch (error) { this.log.error('failed to send Core Node request', { core: core._id, request: request.url, error }); } }); return results; } async sendRequest (core, request) { const coreScheme = process.env.DTP_CORE_AUTH_SCHEME || 'https'; const requestUrl = `${coreScheme}://${core.address.host}:${core.address.port}${request.url}`; try { const req = new CoreNodeRequest(); req.created = new Date(); req.core = core._id; req.token = { value: uuidv4(), claimed: false, }; req.method = request.method || 'GET'; req.url = request.url; await req.save(); this.log.info('sending Core node request', { request: req }); const response = await fetch(requestUrl, { method: req.method, body: request.body, }); const json = await response.json(); /* * capture a little inline health monitoring data, which can be used to * generate health alerts. */ const DONE = new Date(); const ELAPSED = DONE.valueOf() - req.created.valueOf(); await CoreNodeRequest.updateOne( { _id: req._id }, { $set: { 'response.received': DONE, 'response.elapsed': ELAPSED, 'response.success': json.success, }, }, ); this.log.info('Core node request complete', { request: req }); return { request: req.toObject(), response: json }; } catch (error) { this.log.error('failed to send Core Node request', { core: core._id, request: request.url, error }); throw error; } } } module.exports = { slug: 'core-node', name: 'coreNode', create: (dtp) => { return new CoreNodeService(dtp); }, };