Socket
Socket
Sign inDemoInstall

@financial-times/cmdb.js

Package Overview
Dependencies
Maintainers
14
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@financial-times/cmdb.js - npm Package Compare versions

Comparing version 3.3.0 to 3.4.0

src/lib/node-agent.js

0

.jsdoc.json

@@ -0,0 +0,0 @@ {

2

package.json
{
"name": "@financial-times/cmdb.js",
"description": "A javascript library for interacting with the CMDB v3",
"version": "3.3.0",
"version": "3.4.0",
"main": "dist/cmdb.js",

@@ -6,0 +6,0 @@ "browser": "dist/cmdb.mjs",

@@ -0,0 +0,0 @@ # cmdb.js

import querystring from 'querystring';
import fetch from 'isomorphic-unfetch';
import parseLinkHeader from './parseLinkHeader';
import parseLinkHeader from './lib/parseLinkHeader';
import required from './lib/required';
import noOpLogger from './lib/noOpLogger';
/**
* Throws an error if the required key is not set in parameters
* @private
* @function
* @example
* myFunction({
* requiredParameter = required('requiredParameter)
* })
* @param {string} key - The key to include in the error message
* @returns {undefined}
* @throws {Error} - A generic error including the given key which is missing
*/
const required = key => {
throw new Error(`The config parameter '${key}' is required`);
};
const DEFAULT_TIMEOUT = 12000;
/**
* Create a noop logger with the console API
* @private
* @function
* @returns {Object} A noop logger with the console API
*/
const createNoopLogger = () =>
Object.keys(console).reduce(
(result, key) => Object.assign({}, result, { [key]() {} }),
Object.create(null)
);
/**
* Object representing the CMDB API
* @class Cmdb
* @param {Object} config - An object of key/value pairs holding configuration
* @param {string} [config.api=https://Cmdb.ft.com/v2/] - The CMDB API endpoint to send requests to (defaults to production, change for other environments)
* @param {string} [config.api=https://cmdb.in.ft.com/v3/] - The CMDB API endpoint to send requests to (defaults to production, change for other environments)
* @param {string} config.apikey - The apikey to send to CMDB API

@@ -54,3 +30,3 @@ * @param {Object} [config.logger] - A logger objet with the Winston API

this.verbose = verbose;
this._logger = this.verbose ? logger || console : createNoopLogger();
this._logger = this.verbose ? logger || console : noOpLogger();
}

@@ -101,6 +77,61 @@

statusCode: response.status,
statusText: response.statusText,
headers: response.headers,
body: response.parsedBody,
});
/**
* Helper function for safely parsing the cmdb response body
* @method
* @private
* @param {Object} [requestOptions] - The options to use in logger calls
* @param {string} [requestOptions.path] - The path of the fetch call
* @param {string} [requestOptions.method] - The method of the fetch call
* @param {Response} response - The fetch Response object
* @returns {Promise<Object>} - The parsed JSON body
*/
Cmdb.prototype._parseResponseBody = function(requestOptions = {}, response) {
const { path, method } = requestOptions;
if (!response.ok) {
this._logger.log({
event: 'CMDB_ERROR',
path,
method,
statusCode: response.status,
statusText: response.statusText,
});
}
const contentType = response.headers.get('content-type') || '';
return response
.json()
.catch(error => {
this._logger.log(
{
event: 'CMDB_CONTENT_TYPE_MISMATCH',
path,
method,
error,
expectedContentType: contentType,
statusCode: response.status,
statusText: response.statusText,
},
`Expected ${contentType} but body was not parsable`
);
throw createResponseError(
`Received response with invalid body from CMDB`,
response
);
})
.then(responseBody => {
if (!response.ok) {
throw createResponseError(
`Received ${response.status} response from CMDB`,
Object.assign(response, { parsedBody: responseBody })
);
}
return responseBody;
});
};
/**
* Helper function for making requests to CMDB API

@@ -114,4 +145,6 @@ * @method

* @param {Object} [body] - An object to send to the API
* @param {Object} options - the request options
* @param {number} [timeout=12000] - the optional timeout period in milliseconds
* @returns {Promise<Object>} The data received from CMDB (JSON-decoded)
* @param {boolean} [parseBody=true] - whether to parse the body as JSON, or return the whole response
* @returns {Promise<Response|Object>} The data received from CMDB. JSON decoded if parseBody is true
*/

@@ -122,11 +155,6 @@ Cmdb.prototype._fetch = function _fetch(

query,
method,
method = 'GET',
body,
timeout = 12000
{ timeout = DEFAULT_TIMEOUT, parseBody = true }
) {
// HACK: CMDB decodes paths before they hit its router, so do an extra encode on the whole path here
// Check for existence of CMDBV3 variable to avoid encoding
// if (!process.env.CMDBV3) {
// path = encodeURIComponent(path);
// }
if (query && Object.keys(query).length > 0) {

@@ -139,14 +167,18 @@ path = `${path}?${querystring.stringify(query)}`;

this._getFetchCredentials(locals, { method, body, timeout })
).then(response => {
if (response.status >= 400) {
throw createResponseError(
`Received ${response.status} response from CMDB`,
response
);
}
return response.json();
});
).then(
response =>
parseBody
? this._parseResponseBody({ path, method }, response)
: response
);
};
/**
@typedef documentCount
@type {Object}
@property {number} pages The number of pages for the request
@property {number} items The number of items for the request
*/
/**
* Helper function for requested count of pages and itemsfrom CMDB API

@@ -159,3 +191,3 @@ * @method

* @param {number} [timeout=12000] - the optional timeout period in milliseconds
* @returns {Promise<Object>} The count of pages and items from CMDB (JSON-decoded)
* @returns {Promise<documentCount>} The count of pages and items from CMDB (JSON-decoded)
*/

@@ -166,3 +198,3 @@ Cmdb.prototype._fetchCount = function _fetchCount(

query,
timeout = 12000
timeout = DEFAULT_TIMEOUT
) {

@@ -173,38 +205,36 @@ if (query && Object.keys(query).length > 0) {

return fetch(
`${this.api}${path}`,
this._getFetchCredentials(locals, { timeout })
).then(response => {
// CMDB returns entirely different output when there are zero contacts
// Just return an empty array in this case.
if (response.status === 404) {
return {};
}
if (response.status !== 200) {
throw createResponseError(
`Received ${response.status} response from CMDB`,
response
);
}
return this._fetch(locals, path, query, undefined, undefined, {
timeout,
parseBody: false,
})
.then(response => {
return this._parseResponseBody({ path, query }, response).then(
body => {
// default page and items count based on a single page containing array of items
return response.json().then(body => {
// default page and items count based on a single page containing array of items
let pages = 1;
let items = body.length;
let pages = 1;
let items = body.length;
// aim to get "Count: Pages: nnn, Items: nnn"
const countstext = response.headers.get('Count');
if (countstext) {
// we now have "Pages: nnn, Items: nnn"
const counts = countstext.split(',');
if (counts.length === 2) {
// we now have "Pages: nnn" and "Items: nnn"
pages = parseInt(counts[0].split(':')[1].trim(), 10);
items = parseInt(counts[1].split(':')[1].trim(), 10);
// aim to get "Count: Pages: nnn, Items: nnn"
const countText = response.headers.get('Count');
if (countText) {
// we now have "Pages: nnn, Items: nnn"
const counts = countText.split(',');
if (counts.length === 2) {
// we now have "Pages: nnn" and "Items: nnn"
[pages, items] = counts.map(count =>
parseInt(count.split(':')[1].trim(), 10)
);
}
}
return { pages, items };
}
);
})
.catch(error => {
if (error.statusCode === 404) {
return [];
}
return { pages, items };
throw error;
});
});
};

@@ -217,3 +247,3 @@

* @param {Object} [locals] - The res.locals value from a request in express
* @param {string} url - The url of the request to make
* @param {string} path - The path of the request to make
* @param {Object} query - The query parameters to use as a javascript object

@@ -225,34 +255,32 @@ * @param {number} [timeout=12000] - the optional timeout period in milliseconds

locals,
url,
path,
query,
timeout = 12000
timeout = DEFAULT_TIMEOUT
) {
if (query && Object.keys(query).length > 0) {
url = `${url}?${querystring.stringify(query)}`;
}
return fetch(url, this._getFetchCredentials(locals, { timeout }))
.then(response => {
return this._fetch(locals, path, query, 'GET', undefined, {
timeout,
parseBody: false,
})
.then(response =>
this._parseResponseBody({ path, method: 'GET' }, response).then(
body => {
const links = parseLinkHeader(response.headers.get('link'));
if (links.next) {
return this._fetchAll(
locals,
links.next,
query,
timeout
).then(nextBody => [...body, ...nextBody]);
}
return body;
}
)
)
.catch(error => {
// CMDB returns entirely different output when there are zero contacts
// Just return an empty array in this case.
if (response.status === 404) {
if (error.statusCode === 404) {
return [];
}
if (response.status !== 200) {
throw createResponseError(
`Received ${response.status} response from CMDB`,
response
);
}
const links = parseLinkHeader(response.headers.get('link'));
if (links.next) {
return response.json().then(data => {
return this._fetchAll(locals, links.next).then(nextdata => [
...data,
...nextdata,
]);
});
}
return response.json();
})
.catch(error => {
this._logger.error(error);

@@ -276,6 +304,8 @@ throw error;

key = required('key'),
timeout = 12000
timeout = DEFAULT_TIMEOUT
) {
const path = `items/${encodeURIComponent(type)}/${encodeURIComponent(key)}`;
return this._fetch(locals, path, undefined, undefined, undefined, timeout);
return this._fetch(locals, path, undefined, undefined, undefined, {
timeout,
});
};

@@ -300,3 +330,3 @@

relatedFields,
timeout = 12000
timeout = DEFAULT_TIMEOUT
) {

@@ -311,3 +341,3 @@ const path = `items/${encodeURIComponent(type)}/${encodeURIComponent(key)}`;

}
return this._fetch(locals, path, query, undefined, undefined, timeout);
return this._fetch(locals, path, query, undefined, undefined, { timeout });
};

@@ -330,6 +360,6 @@

body = required('body'),
timeout = 12000
timeout = DEFAULT_TIMEOUT
) {
const path = `items/${encodeURIComponent(type)}/${encodeURIComponent(key)}`;
return this._fetch(locals, path, undefined, 'PUT', body, timeout);
return this._fetch(locals, path, undefined, 'PUT', body, { timeout });
};

@@ -350,6 +380,8 @@

key = required('key'),
timeout = 12000
timeout = DEFAULT_TIMEOUT
) {
const path = `items/${encodeURIComponent(type)}/${encodeURIComponent(key)}`;
return this._fetch(locals, path, undefined, 'DELETE', undefined, timeout);
return this._fetch(locals, path, undefined, 'DELETE', undefined, {
timeout,
});
};

@@ -372,6 +404,6 @@

limit,
timeout = 12000
timeout = DEFAULT_TIMEOUT
) {
const encodedTypePath = type ? `/${encodeURIComponent(type)}` : '';
const url = `${this.api}items${encodedTypePath}`;
const path = `items${encodedTypePath}`;
let query = {};

@@ -384,3 +416,3 @@ if (criteria) {

}
return this._fetchAll(locals, url, query, timeout);
return this._fetchAll(locals, path, query, timeout);
};

@@ -407,5 +439,5 @@

limit,
timeout = 12000
timeout = DEFAULT_TIMEOUT
) {
const url = `${this.api}items/${encodeURIComponent(type)}`;
const path = `items/${encodeURIComponent(type)}`;
let query = {};

@@ -424,3 +456,3 @@ if (fields) {

}
return this._fetchAll(locals, url, query, timeout);
return this._fetchAll(locals, path, query, timeout);
};

@@ -441,3 +473,3 @@

criteria,
timeout = 12000
timeout = DEFAULT_TIMEOUT
) {

@@ -476,3 +508,3 @@ const path = `items/${encodeURIComponent(type)}`;

limit,
timeout = 12000
timeout = DEFAULT_TIMEOUT
) {

@@ -525,3 +557,3 @@ let query = {

limit,
timeout = 12000
timeout = DEFAULT_TIMEOUT
) {

@@ -557,15 +589,33 @@ const path = `items/${encodeURIComponent(type)}`;

const getRelationshipPath = ({
const arrayToPath = array =>
array
.filter(item => !!item)
.map(param => encodeURIComponent(param))
.join('/');
/**
* @name Cmdb#getRelationships
* @method
* @memberof Cmdb
* @description Returns an array of relationships from an item
* @param {Object} [locals] - The res.locals value from a request in express
* @param {string} subjectType - The source item type for the relationship
* @param {string} subjectID - The source item dataItemID for the relationship
* @param {string} [relType] - The relationship type for the relationship. Optional
* @param {number} [timeout=12000] - the optional timeout period in milliseconds
* @returns {Promise<Object>} The updated data about the item held in the CMDB
*/
Cmdb.prototype.getRelationships = function(
locals,
subjectType = required('subjectType'),
subjectID = required('subjectID'),
relType = required('relType'),
objectType = required('objectType'),
objectID = required('objectID'),
} = {}) =>
[
relType,
timeout = DEFAULT_TIMEOUT
) {
const path = arrayToPath([
'relationships',
...[subjectType, subjectID, relType, objectType, objectID].map(param =>
encodeURIComponent(param)
),
].join('/');
...[subjectType, subjectID, relType],
]);
return this._fetch(locals, path, undefined, 'GET', {}, { timeout });
};

@@ -587,2 +637,18 @@ /**

Cmdb.prototype.getRelationship = function(
locals,
subjectType = required('subjectType'),
subjectID = required('subjectID'),
relType = required('relType'),
objectType = required('objectType'),
objectID = required('objectID'),
timeout = DEFAULT_TIMEOUT
) {
const path = arrayToPath([
'relationships',
...[subjectType, subjectID, relType, objectType, objectID],
]);
return this._fetch(locals, path, undefined, 'GET', {}, { timeout });
};
/**

@@ -602,3 +668,17 @@ * @name Cmdb#putRelationship

*/
Cmdb.prototype.putRelationship = function(
locals,
subjectType = required('subjectType'),
subjectID = required('subjectID'),
relType = required('relType'),
objectType = required('objectType'),
objectID = required('objectID'),
timeout = DEFAULT_TIMEOUT
) {
const path = arrayToPath([
'relationships',
...[subjectType, subjectID, relType, objectType, objectID],
]);
return this._fetch(locals, path, undefined, 'POST', {}, { timeout });
};
/**

@@ -618,27 +698,18 @@ * @name Cmdb#deleteRelationship

*/
[
{ key: 'putRelationship', method: 'POST' },
{ key: 'getRelationship', method: 'GET' },
{ key: 'deleteRelationship', method: 'DELETE' },
].forEach(({ key, method }) => {
Cmdb.prototype[key] = function(
locals,
subjectType = required('subjectType'),
subjectID = required('subjectID'),
relType = required('relType'),
objectType = required('objectType'),
objectID = required('objectID'),
timeout = 12000
) {
const path = getRelationshipPath({
subjectType,
subjectID,
relType,
objectType,
objectID,
});
return this._fetch(locals, path, undefined, method, {}, timeout);
};
});
Cmdb.prototype.deleteRelationship = function(
locals,
subjectType = required('subjectType'),
subjectID = required('subjectID'),
relType = required('relType'),
objectType = required('objectType'),
objectID = required('objectID'),
timeout = DEFAULT_TIMEOUT
) {
const path = arrayToPath([
'relationships',
...[subjectType, subjectID, relType, objectType, objectID],
]);
return this._fetch(locals, path, undefined, 'DELETE', {}, { timeout });
};
export default Cmdb;

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc