UNPKG

@mindconnect/mindconnect-nodejs

Version:

MindConnect Library for NodeJS (community based)

453 lines 23.2 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const debug = require("debug"); const node_fetch_1 = require("node-fetch"); const path = require("path"); require("url-search-params-polyfill"); const __1 = require(".."); const agent_auth_1 = require("./agent-auth"); const mindconnect_template_1 = require("./mindconnect-template"); const mindconnect_validators_1 = require("./mindconnect-validators"); const multipart_uploader_1 = require("./sdk/common/multipart-uploader"); const log = debug("mindconnect-agent"); /** * MindConnect Agent implements the V3 of the Mindsphere API. * * @export * @class MindConnectAgent */ class MindConnectAgent extends agent_auth_1.AgentAuth { constructor() { super(...arguments); this.uploader = new multipart_uploader_1.MultipartUploader(this); } ClientId() { return this._configuration.content.clientId || "not defined yet"; } /** *Checkis if the agent is onboarded. * * @returns {boolean} * @memberof MindConnectAgent */ IsOnBoarded() { return this._configuration.response ? true : false; } /** * Checks if the agent has a data source configuration * * @returns {boolean} * @memberof MindConnectAgent */ HasDataSourceConfiguration() { if (!this._configuration.dataSourceConfiguration) { return false; } else if (!this._configuration.dataSourceConfiguration.configurationId) { return false; } else { return true; } } /** * Checks if the agent has mappings * * @returns {boolean} * @memberof MindConnectAgent */ HasDataMappings() { return this._configuration.mappings ? true : false; } /** * Stores the configuration in the mindsphere. * * By default the eTag parameter in the provided configuration is ignored, and the agent just updates the configuration every time the put method is stored * and automatically increases the eTag. * This is why its a good idea to check if the configuration was stored before the data was posted. If the ignoreEtag is set to false then the agent just uses * the eTag which was specified in the configuration. This might throw an "already stored" exception in the mindsphere. * * @param {DataSourceConfiguration} dataSourceConfiguration * @param {boolean} [ignoreEtag=true] * @returns {Promise<DataSourceConfiguration>} * @memberof MindConnectAgent */ PutDataSourceConfiguration(dataSourceConfiguration, ignoreEtag = true) { return __awaiter(this, void 0, void 0, function* () { yield this.RenewToken(); if (!this._accessToken) throw new Error("The agent doesn't have a valid access token."); if (!this._configuration.content.clientId) throw new Error("No client id in the configuration of the agent."); let eTag = 0; if (this._configuration.dataSourceConfiguration && this._configuration.dataSourceConfiguration.eTag) { eTag = parseInt(this._configuration.dataSourceConfiguration.eTag); if (isNaN(eTag)) throw new Error("Invalid eTag in configuration!"); } if (!ignoreEtag) { if (!dataSourceConfiguration.eTag) throw new Error("There is no eTag in the provided configuration!"); eTag = parseInt(dataSourceConfiguration.eTag); if (isNaN(eTag)) throw new Error("Invalid eTag in provided configuration!"); } const headers = Object.assign(Object.assign({}, this._apiHeaders), { Authorization: `Bearer ${this._accessToken.access_token}`, "If-Match": eTag.toString() }); const url = `${this._configuration.content.baseUrl}/api/agentmanagement/v3/agents/${this._configuration.content.clientId}/dataSourceConfiguration`; log(`PutDataSourceConfiguration Headers ${JSON.stringify(headers)} Url ${url} eTag ${eTag}`); try { const response = yield node_fetch_1.default(url, { method: "PUT", body: JSON.stringify(dataSourceConfiguration), headers: headers, agent: this._proxyHttpAgent }); const json = yield response.json(); if (!response.ok) { throw new Error(`${response.statusText} ${JSON.stringify(json)}`); } if (response.status >= 200 && response.status <= 299) { this._configuration.dataSourceConfiguration = json; yield __1.retry(5, () => this.SaveConfig()); return json; } else { throw new Error(`invalid response ${JSON.stringify(response)}`); } } catch (err) { log(err); throw new Error(`Network error occured ${err.message}`); } }); } GetDataSourceConfiguration() { return __awaiter(this, void 0, void 0, function* () { yield this.RenewToken(); if (!this._accessToken) throw new Error("The agent doesn't have a valid access token."); if (!this._configuration.content.clientId) throw new Error("No client id in the configuration of the agent."); const headers = Object.assign(Object.assign({}, this._headers), { Authorization: `Bearer ${this._accessToken.access_token}` }); const url = `${this._configuration.content.baseUrl}/api/agentmanagement/v3/agents/${this._configuration.content.clientId}/dataSourceConfiguration`; log(`GetDataSourceConfiguration Headers ${JSON.stringify(headers)} Url ${url}`); try { const response = yield node_fetch_1.default(url, { method: "GET", headers: headers, agent: this._proxyHttpAgent }); if (!response.ok) { throw new Error(`${response.statusText}`); } const json = yield response.json(); if (response.status >= 200 && response.status <= 299) { this._configuration.dataSourceConfiguration = json; yield __1.retry(5, () => this.SaveConfig()); return json; } else { throw new Error(`invalid response ${JSON.stringify(response)}`); } } catch (err) { log(err); throw new Error(`Network error occured ${err.message}`); } }); } GetDataMappings() { return __awaiter(this, void 0, void 0, function* () { yield this.RenewToken(); if (!this._accessToken) throw new Error("The agent doesn't have a valid access token."); if (!this._configuration.content.clientId) throw new Error("No client id in the configuration of the agent."); const headers = Object.assign(Object.assign({}, this._headers), { Authorization: `Bearer ${this._accessToken.access_token}` }); const agentFilter = encodeURIComponent(JSON.stringify({ agentId: `${this._configuration.content.clientId}` })); const url = `${this._configuration.content.baseUrl}/api/mindconnect/v3/dataPointMappings?filter=${agentFilter}&size=2000`; log(`GetDataSourceConfiguration Headers ${JSON.stringify(headers)} Url ${url}`); try { const response = yield node_fetch_1.default(url, { method: "GET", headers: headers, agent: this._proxyHttpAgent }); const json = yield response.json(); if (!response.ok) { throw new Error(`${response.statusText} ${JSON.stringify(json)}`); } if (response.status >= 200 && response.status <= 299) { this._configuration.mappings = json; yield __1.retry(5, () => this.SaveConfig()); return json.content; } else { throw new Error(`invalid response ${JSON.stringify(response)}`); } } catch (err) { log(err); throw new Error(`Network error occured ${err.message}`); } }); } PutDataMappings(mappings) { return __awaiter(this, void 0, void 0, function* () { yield this.RenewToken(); if (!this._accessToken) throw new Error("The agent doesn't have a valid access token."); if (!this._configuration.content.clientId) throw new Error("No client id in the configuration of the agent."); const headers = Object.assign(Object.assign({}, this._apiHeaders), { Authorization: `Bearer ${this._accessToken.access_token}` }); const url = `${this._configuration.content.baseUrl}/api/mindconnect/v3/dataPointMappings`; log(`GetDataSourceConfiguration Headers ${JSON.stringify(headers)} Url ${url}`); for (const mapping of mappings) { log(`Storing mapping ${mapping}`); const response = yield node_fetch_1.default(url, { method: "POST", body: JSON.stringify(mapping), headers: headers, agent: this._proxyHttpAgent }); const json = yield response.json(); try { if (!response.ok) { throw new Error(`${response.statusText} ${JSON.stringify(json)}`); } if (!(response.status >= 200 && response.status <= 299)) { throw new Error(`invalid response ${JSON.stringify(response)}`); } } catch (err) { log(err); throw new Error(`Network error occured ${err.message}`); } this._configuration.mappings = mappings; } return true; }); } /** * Posts the Events to the Exchange Endpoint * * @see: https://developer.mindsphere.io/apis/api-advanced-eventmanagement/index.html * * @param {*} events * @param {Date} [timeStamp=new Date()] * @param {boolean} [validateModel=true] * @returns {Promise<boolean>} * @memberof MindConnectAgent */ PostEvent(event, timeStamp = new Date(), validateModel = true) { return __awaiter(this, void 0, void 0, function* () { yield this.RenewToken(); if (!this._accessToken) throw new Error("The agent doesn't have a valid access token."); const headers = Object.assign(Object.assign({}, this._apiHeaders), { Authorization: `Bearer ${this._accessToken.access_token}` }); // const url = `${this._configuration.content.baseUrl}/api/mindconnect/v3/exchange`; const url = `${this._configuration.content.baseUrl}/api/eventmanagement/v3/events`; log(`GetDataSourceConfiguration Headers ${JSON.stringify(headers)} Url ${url}`); if (!event.timestamp) { event.timestamp = timeStamp.toISOString(); } if (validateModel) { const validator = this.GetEventValidator(); const isValid = yield validator(event); if (!isValid) { throw new Error(`Data doesn't match the configuration! Errors: ${JSON.stringify(validator.errors)}`); } } const result = yield this.SendMessage("POST", url, JSON.stringify(event), headers); return result; }); } /** * Post Data Point Values to the Exchange Endpoint * * * @see: https://developer.mindsphere.io/howto/howto-upload-agent-data/index.html * * @param {DataPointValue[]} dataPoints * @param {Date} [timeStamp=new Date()] * @param {boolean} [validateModel=true] you can set this to false to speed up the things if your agent is working. * @returns {Promise<boolean>} * @memberof MindConnectAgent */ PostData(dataPoints, timeStamp = new Date(), validateModel = true) { return __awaiter(this, void 0, void 0, function* () { yield this.RenewToken(); if (!this._accessToken) throw new Error("The agent doesn't have a valid access token."); if (!this._configuration.content.clientId) throw new Error("No client id in the configuration of the agent."); if (!this._configuration.dataSourceConfiguration) throw new Error("No data source configuration for the agent."); if (!this._configuration.dataSourceConfiguration.configurationId) throw new Error("No data source configuration ID for the agent."); if (validateModel) { const validator = this.GetValidator(); const isValid = yield validator(dataPoints); if (!isValid) { throw new Error(`Data doesn't match the configuration! Errors: ${JSON.stringify(validator.errors)}`); } } const headers = Object.assign(Object.assign({}, this._multipartHeaders), { Authorization: `Bearer ${this._accessToken.access_token}` }); const url = `${this._configuration.content.baseUrl}/api/mindconnect/v3/exchange`; log(`GetDataSourceConfiguration Headers ${JSON.stringify(headers)} Url ${url}`); const dataMessage = mindconnect_template_1.dataTemplate(timeStamp, dataPoints, this._configuration.dataSourceConfiguration.configurationId); log(dataMessage); const result = yield this.SendMessage("POST", url, dataMessage, headers); return result; }); } BulkPostData(timeStampedDataPoints, validateModel = true) { return __awaiter(this, void 0, void 0, function* () { yield this.RenewToken(); if (!this._accessToken) throw new Error("The agent doesn't have a valid access token."); if (!this._configuration.content.clientId) throw new Error("No client id in the configuration of the agent."); if (!this._configuration.dataSourceConfiguration) throw new Error("No data source configuration for the agent."); if (!this._configuration.dataSourceConfiguration.configurationId) throw new Error("No data source configuration ID for the agent."); if (validateModel) { const validator = this.GetValidator(); for (let index = 0; index < timeStampedDataPoints.length; index++) { const element = timeStampedDataPoints[index]; const isValid = yield validator(element.values); if (!isValid) { throw new Error(`Data doesn't match the configuration! Errors: ${JSON.stringify(validator.errors)}`); } } } const headers = Object.assign(Object.assign({}, this._multipartHeaders), { Authorization: `Bearer ${this._accessToken.access_token}` }); const url = `${this._configuration.content.baseUrl}/api/mindconnect/v3/exchange`; log(`GetDataSourceConfiguration Headers ${JSON.stringify(headers)} Url ${url}`); const bulkDataMessage = mindconnect_template_1.bulkDataTemplate(timeStampedDataPoints, this._configuration.dataSourceConfiguration.configurationId); log(bulkDataMessage); const result = yield this.SendMessage("POST", url, bulkDataMessage, headers); return result; }); } /** * Upload file to MindSphere IOTFileService * * * This method is used to upload the files to the MindSphere. * * It supports standard and multipart upload which can be configured with the [optional.chunk] parameter. * * * The method will try to abort the multipart upload if an exception occurs. * * Multipart Upload is done in following steps: * * start multipart upload * * upload in parallel [optional.parallelUploadChunks] the file parts (retrying [optional.retry] times if configured) * * uploading last chunk. * * @param {string} entityId - asset id or agent.ClientId() for agent * @param {string} filepath - mindsphere file path * @param {(string | Buffer)} file - local path or Buffer * @param {fileUploadOptionalParameters} [optional] - optional parameters: enable chunking, define retries etc. * @param {(number | undefined)}[optional.part] multipart/upload part * @param {(Date | undefined)} [optional.timestamp] File timestamp in mindsphere. * @param {(string | undefined)} [optional.description] Description in mindsphere. * @param {(string | undefined)} [optional.type] Mime type in mindsphere. * @param {(number | undefined)} [optional.chunkSize] chunkSize. It must be bigger than 5 MB. Default 8 MB. * @param {(number | undefined)} [optional.retry] Number of retries * @param {(Function | undefined)} [optional.logFunction] log functgion is called every time a retry happens. * @param {(Function | undefined)} [optional.verboseFunction] verboseLog function. * @param {(boolean | undefined)} [optional.chunk] Set to true to enable multipart uploads * @param {(number | undefined)} [optional.parallelUploads] max paralell uploads for parts (default: 3) * @param {(number | undefined)} [optional.ifMatch] The etag for the upload. * @returns {Promise<string>} - md5 hash of the file * * @memberOf MindConnectAgent * * @example await agent.UploadFile (agent.GetClientId(), "some/mindsphere/path/file.txt", "file.txt"); * @example await agent.UploadFile (agent.GetClientId(), "some/other/path/10MB.bin", "bigFile.bin",{ chunked:true, retry:5 }); */ UploadFile(entityId, filepath, file, optional) { return __awaiter(this, void 0, void 0, function* () { const result = yield this.uploader.UploadFile(entityId, filepath, file, optional); yield __1.retry(5, () => this.SaveConfig()); return result; }); } /** * Uploads the file to mindsphere * * @deprecated please use UploadFile method instead this method will probably be removed in version 4.0.0 * * @param {string} file filename or buffer for upload * @param {string} fileType mime type (e.g. image/png) * @param {string} description description of the file * @param {boolean} [chunk=true] if this is set to false the system will only upload smaller files * @param {string} [entityId] entityid can be used to define the asset for upload, otherwise the agent is used. * @param {number} [chunkSize=8 * 1024 * 1024] - at the moment 8MB as per restriction of mindgate * @param {number} [maxSockets=3] - maxSockets for http Upload - number of parallel multipart uploads * @returns {Promise<string>} md5 hash of the uploaded file * * @memberOf MindConnectAgent */ Upload(file, fileType, description, chunk = true, entityId, chunkSize = 8 * 1024 * 1024, maxSockets = 3, filePath) { return __awaiter(this, void 0, void 0, function* () { const clientId = entityId || this.ClientId(); const filepath = filePath || (file instanceof Buffer ? "no-filepath-for-buffer" : path.basename(file)); return yield this.UploadFile(clientId, filepath, file, { type: fileType, description: description, chunk: chunk, chunkSize: chunkSize, parallelUploads: maxSockets }); }); } SendMessage(method, url, dataMessage, headers) { return __awaiter(this, void 0, void 0, function* () { try { const response = yield node_fetch_1.default(url, { method: method, body: dataMessage, headers: headers, agent: this._proxyHttpAgent }); if (!response.ok) { log({ method: method, body: dataMessage, headers: headers, agent: this._proxyHttpAgent }); log(response); throw new Error(response.statusText); } const text = yield response.text(); if (response.status >= 200 && response.status <= 299) { const etag = response.headers.get("eTag"); return etag ? etag : true; } else { throw new Error(`Error occured response status ${response.status} ${text}`); } // process body } catch (err) { log(err); throw new Error(`Network error occured ${err.message}`); } }); } GetValidator() { const model = this._configuration.dataSourceConfiguration; if (!model) { throw new Error("Invalid local configuration, Please get or crete the data source configuration."); } return mindconnect_validators_1.dataValidator(model); } GetEventValidator() { if (!this._eventValidator) { this._eventValidator = mindconnect_validators_1.eventValidator(); } return this._eventValidator; } GetMindConnectConfiguration() { return this._configuration; } } exports.MindConnectAgent = MindConnectAgent; //# sourceMappingURL=mindconnect-agent.js.map