@mindconnect/mindconnect-nodejs
Version:
MindConnect Library for NodeJS (community based)
453 lines • 23.2 kB
JavaScript
;
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