/* @preserve
* MIT License
*
* Copyright (c) 2016 Florian Krönert
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
/**
* This is the core functionality of Xrm-WebApi-Client
* No instantiation needed, it's a singleton.
* @module WebApiClient
*/
(function (undefined) {
"use strict";
var WebApiClient = {};
var batchName = "batch_UrlLimitExeedingRequest";
/**
* @description The API version that will be used when sending requests. Default is "8.0"
* @param {String}
* @memberof module:WebApiClient
*/
WebApiClient.ApiVersion = "8.0";
/**
* @description Informs about which version of WebApiClient you're using
* @param {string}
* @memberof module:WebApiClient
*/
WebApiClient.Version = "v0.0.0";
/**
* @description Checks for more pages when retrieving results. If set to true, all pages will be retrieved, if set to false, only the first page will be retrieved.
* @param {boolean}
* @memberof module:WebApiClient
*/
WebApiClient.ReturnAllPages = false;
/**
* @description Set to true for retrieving formatted error in style 'xhr.statusText: xhr.error.Message'. If set to false, error json will be returned.
* @param {boolean}
* @memberof module:WebApiClient
*/
WebApiClient.PrettifyErrors = true;
/**
* @description Set to false for sending all requests synchronously. True by default.
* @param {boolean}
* @memberof module:WebApiClient
*/
WebApiClient.Async = true;
/**
* @description Connection to use when being used in a single page app.
* @param {String}
* @memberof module:WebApiClient
*/
WebApiClient.ClientUrl = null;
/**
* @description Token to use for authenticating when being used in a single page app.
* @param {String}
* @memberof module:WebApiClient
*/
WebApiClient.Token = null;
// This is for ensuring that we use bluebird internally, so that calls to WebApiClient have no differing set of
// functions that can be applied to the Promise. For example Promise.finally would not be available without Bluebird.
var Promise = require("bluebird").noConflict();
function GetCrmContext() {
if (typeof (GetGlobalContext) !== "undefined") {
return GetGlobalContext();
}
if (typeof (Xrm) !== "undefined"){
return Xrm.Page.context;
}
}
function GetClientUrl () {
var context = GetCrmContext();
if(context)
{
return context.getClientUrl();
}
if (WebApiClient.ClientUrl) {
return WebApiClient.ClientUrl;
}
throw new Error("Failed to retrieve client url, is ClientGlobalContext.aspx available?");
}
function MergeResults (firstResponse, secondResponse) {
if (!firstResponse && !secondResponse) {
return null;
}
if (firstResponse && !secondResponse) {
return firstResponse;
}
if (!firstResponse && secondResponse) {
return secondResponse;
}
firstResponse.value = firstResponse.value.concat(secondResponse.value);
delete firstResponse["@odata.nextLink"];
delete firstResponse["@Microsoft.Dynamics.CRM.fetchxmlpagingcookie"];
return firstResponse;
}
function RemoveIdBrackets (id) {
if (!id) {
return id;
}
return id.replace("{", "").replace("}", "");
}
/**
* @description Builds the set name of a given entity name.
* @method GetSetName
* @param {String} entityName Logical name of the entity, such as "account"
* @param {String}[overriddenSetName] Override set name if it can't be infered from plural rules
* @memberof module:WebApiClient
* @return {String}
*/
WebApiClient.GetSetName = function (entityName, overriddenSetName) {
if (overriddenSetName) {
return overriddenSetName;
}
var ending = entityName.slice(-1);
switch(ending)
{
case 's':
return entityName + "es";
case 'y':
return entityName.substring(0, entityName.length - 1) + "ies";
default:
return entityName + "s";
}
};
var DefaultHeaders = [
{ key: "Accept", value: "application/json" },
{ key: "OData-Version", value: "4.0" },
{ key: "OData-MaxVersion", value: "4.0" },
{ key: "Content-Type", value: "application/json; charset=utf-8" }
];
/**
* @description Returns array of default headers.
* @method GetDefaultHeaders
* @return {Array<{key: String, value:String}>}
* @memberof module:WebApiClient
*/
WebApiClient.GetDefaultHeaders = function() {
return DefaultHeaders;
};
function VerifyHeader(header) {
if (!header.key || typeof(header.value) === "undefined") {
throw new Error("Each request header needs a key and a value!");
}
if(typeof(header.key) !== "string") {
throw new Error("Header key " + header.key + " is not a string");
}
if(typeof(header.value) !== "string") {
throw new Error("Header value " + header.value + " for key " + header.key + " is not a string");
}
}
/**
* @description Function for building the set name of a given entity name.
* @method AppendToDefaultHeaders
* @param {...{key:String, value:String}} var_args Headers as variable arguments
* @memberof module:WebApiClient
* @return {void}
*/
WebApiClient.AppendToDefaultHeaders = function () {
if (!arguments.length) {
return;
}
for(var i = 0; i < arguments.length; i++) {
var argument = arguments[i];
VerifyHeader(argument);
DefaultHeaders.push(argument);
}
};
function AppendHeaders(xhr, headers) {
if (headers) {
for (var i = 0; i < headers.length; i++) {
var header = headers[i];
VerifyHeader(header);
xhr.setRequestHeader(header.key, header.value);
}
}
}
function GetRecordUrl (parameters) {
var params = parameters || {};
if ((!params.entityName && !params.overriddenSetName) || (!params.entityId && !params.alternateKey)) {
throw new Error("Need entity name or overridden set name and entity id or alternate key for getting record url!");
}
var url = WebApiClient.GetApiUrl() + WebApiClient.GetSetName(params.entityName, params.overriddenSetName);
if (params.alternateKey) {
url += BuildAlternateKeyUrl(params);
} else {
url += "(" + RemoveIdBrackets(params.entityId) + ")";
}
return url;
}
function FormatError (xhr) {
if (xhr && xhr.response) {
var response = ParseResponse(xhr);
if (response instanceof WebApiClient.BatchResponse) {
var errors = "";
if (response.errors.length > 0) {
errors = response.errors.map(function(e) {
return e.code + ": " + e.message;
}).join("\n\r");
}
return xhr.status + " - " + errors;
}
var json = JSON.parse(xhr.response);
if (!WebApiClient.PrettifyErrors) {
json.xhrStatusText = xhr.statusText;
return JSON.stringify(json);
} else {
var error = "";
if (json.error) {
error = json.error.message;
}
return xhr.statusText + ": " + error;
}
}
return "";
}
function GetNextLink (response) {
return response["@odata.nextLink"];
}
function GetPagingCookie(response) {
return response["@Microsoft.Dynamics.CRM.fetchxmlpagingcookie"];
}
function SetCookie (pagingCookie, parameters) {
// Parse cookie that we retrieved with response
var parser = new DOMParser();
var cookieXml = parser.parseFromString(pagingCookie, "text/xml");
var cookie = cookieXml.documentElement;
var cookieAttribute = cookie.getAttribute("pagingcookie");
// In CRM 8.X orgs, fetch cookies where escaped twice. Since 9.X, they are only escaped once.
// Below indexOf check checks for the double escaped cookie string '<cookie page'.
// In CRM 9.X this will lead to no matches, as cookies start as '%3ccookie%20page'.
if (cookieAttribute && cookieAttribute.indexOf("%253ccookie%2520page") === 0) {
cookieAttribute = unescape(cookieAttribute);
}
var cookieValue = unescape(cookieAttribute);
var pageNumber = parseInt(/<cookie page="([\d]+)">/.exec(cookieValue)[1]) + 1;
// Parse our original fetch XML, we will inject the paging information in here
var fetchXml = parser.parseFromString(parameters.fetchXml, "text/xml");
var fetch = fetchXml.documentElement;
fetch.setAttribute("page", pageNumber);
fetch.setAttribute("paging-cookie", cookieValue);
// Serialize modified fetch with paging information
var serializer = new XMLSerializer();
return serializer.serializeToString(fetchXml);
}
function SetPreviousResponse (parameters, response) {
// Set previous response
parameters._previousResponse = response;
}
function MergeHeaders() {
var headers = [];
if (!arguments) {
return headers;
}
for(var i = 0; i < arguments.length; i++) {
var headersToAdd = arguments[i];
if (!headersToAdd || !Array.isArray(headersToAdd)) {
continue;
}
for (var j = 0; j < headersToAdd.length; j++) {
var header = headersToAdd[j];
VerifyHeader(header);
var addHeader = true;
for (var k = 0; k < headers.length; k++) {
if (headers[k].key === header.key) {
addHeader = false;
break;
}
}
if (addHeader) {
headers.push(header);
}
}
}
return headers;
}
function IsBatch(responseText) {
return responseText && /^--batchresponse_[a-fA-F0-9\-]+$/m.test(responseText);
}
function ParseResponse(xhr) {
var responseText = xhr.responseText;
// Check if it is a batch response
if (IsBatch(responseText)) {
return new WebApiClient.BatchResponse({
xhr: xhr
});
}
else {
return JSON.parse(xhr.responseText);
}
}
function IsOverlengthGet (method, url) {
return method && method.toLowerCase() === "get" && url && url.length > 2048;
}
function SendAsync(method, url, payload, parameters) {
var xhr = new XMLHttpRequest();
var promise = new Promise(function(resolve, reject) {
xhr.onload = function() {
if(xhr.readyState !== 4) {
return;
}
if(xhr.status === 200){
var response = ParseResponse(xhr);
if (response instanceof WebApiClient.BatchResponse) {
// If it was an overlength fetchXml, that was sent as batch automatically, we don't want it to behave as a batch
if (parameters.isOverLengthGet) {
response = response.batchResponses[0].payload;
}
// If we received multiple responses, but not from overlength get, it was a custom batch. Just resolve all matches
else {
resolve(response);
}
}
var nextLink = GetNextLink(response);
var pagingCookie = GetPagingCookie(response);
// Since 9.X paging cookie is always added to response, even in queryParams retrieves
// In 9.X the morerecords flag can signal whether there are more records to be found
// In 8.X the flag was not present and instead the pagingCookie was only set if more records were available
var moreRecords = "@Microsoft.Dynamics.CRM.morerecords" in response ? response["@Microsoft.Dynamics.CRM.morerecords"] : true;
response = MergeResults(parameters._previousResponse, response);
// Results are paged, we don't have all results at this point
if (moreRecords && nextLink && (WebApiClient.ReturnAllPages || parameters.returnAllPages)) {
SetPreviousResponse(parameters, response);
resolve(SendAsync("GET", nextLink, null, parameters));
}
else if (parameters.fetchXml && moreRecords && pagingCookie && (WebApiClient.ReturnAllPages || parameters.returnAllPages)) {
var nextPageFetch = SetCookie(pagingCookie, parameters);
SetPreviousResponse(parameters, response);
parameters.fetchXml = nextPageFetch;
resolve(WebApiClient.Retrieve(parameters));
}
else {
resolve(response);
}
}
else if (xhr.status === 201) {
resolve(ParseResponse(xhr));
}
else if (xhr.status === 204) {
if (method.toLowerCase() === "post") {
resolve(xhr.getResponseHeader("OData-EntityId"));
}
// No content returned for delete, update, ...
else {
resolve(xhr.statusText);
}
}
else {
reject(new Error(FormatError(xhr)));
}
};
xhr.onerror = function() {
reject(new Error(FormatError(xhr)));
};
});
var headers = [];
if (IsOverlengthGet(method, url)) {
var batch = new WebApiClient.Batch({
requests: [new WebApiClient.BatchRequest({
method: method,
url: url,
payload: payload,
headers: parameters.headers
})],
async: true,
isOverLengthGet: true
});
return WebApiClient.SendBatch(batch);
}
xhr.open(method, url, true);
headers = MergeHeaders(headers, parameters.headers, DefaultHeaders);
AppendHeaders(xhr, headers);
// Bugfix for IE. If payload is undefined, IE would send "undefined" as request body
if (payload) {
// For batch requests, we just want to send a string body
if (typeof(payload) === "string") {
xhr.send(payload);
}
else {
xhr.send(JSON.stringify(payload));
}
} else {
xhr.send();
}
return promise;
}
function SendSync(method, url, payload, parameters) {
var xhr = new XMLHttpRequest();
var response;
var headers = [];
if (IsOverlengthGet(method, url)) {
var batch = new WebApiClient.Batch({
requests: [new WebApiClient.BatchRequest({
method: method,
url: url,
payload: payload,
headers: parameters.headers
})],
async: false,
isOverLengthGet: true
});
return WebApiClient.SendBatch(batch);
}
xhr.open(method, url, false);
headers = MergeHeaders(headers, parameters.headers, DefaultHeaders);
AppendHeaders(xhr, headers);
// Bugfix for IE. If payload is undefined, IE would send "undefined" as request body
if (payload) {
// For batch requests, we just want to send a string body
if (typeof(payload) === "string") {
xhr.send(payload);
}
else {
xhr.send(JSON.stringify(payload));
}
} else {
xhr.send();
}
if(xhr.readyState !== 4) {
return;
}
if(xhr.status === 200){
response = ParseResponse(xhr);
// If we received multiple responses, it was a custom batch. Just resolve all matches
if (response instanceof WebApiClient.BatchResponse) {
// If it was an overlength fetchXml, that was sent as batch automatically, we don't want it to behave as a batch
if (parameters.isOverLengthGet) {
response = response.batchResponses[0].payload;
} else {
return response;
}
}
var nextLink = GetNextLink(response);
var pagingCookie = GetPagingCookie(response);
// Since 9.X paging cookie is always added to response, even in queryParams retrieves
// In 9.X the morerecords flag can signal whether there are more records to be found
// In 8.X the flag was not present and instead the pagingCookie was only set if more records were available
var moreRecords = "@Microsoft.Dynamics.CRM.morerecords" in response ? response["@Microsoft.Dynamics.CRM.morerecords"] : true;
response = MergeResults(parameters._previousResponse, response);
// Results are paged, we don't have all results at this point
if (moreRecords && nextLink && (WebApiClient.ReturnAllPages || parameters.returnAllPages)) {
SetPreviousResponse(parameters, response);
SendSync("GET", nextLink, null, parameters);
}
else if (parameters.fetchXml && moreRecords && pagingCookie && (WebApiClient.ReturnAllPages || parameters.returnAllPages)) {
var nextPageFetch = SetCookie(pagingCookie, parameters);
SetPreviousResponse(parameters, response);
parameters.fetchXml = nextPageFetch;
WebApiClient.Retrieve(parameters);
}
}
else if (xhr.status === 201) {
response = ParseResponse(xhr);
}
else if (xhr.status === 204) {
if (method.toLowerCase() === "post") {
response = xhr.getResponseHeader("OData-EntityId");
}
// No content returned for delete, update, ...
else {
response = xhr.statusText;
}
}
else {
throw new Error(FormatError(xhr));
}
return response;
}
function GetAsync (parameters) {
if (typeof(parameters.async) !== "undefined") {
return parameters.async;
}
return WebApiClient.Async;
}
function BuildAlternateKeyUrl (params) {
if (!params || !params.alternateKey) {
return "";
}
var url = "(";
for (var i = 0; i < params.alternateKey.length; i++) {
var key = params.alternateKey[i];
var value = key.value;
if (typeof(key.value) !== "number") {
value = "'" + key.value + "'";
}
url += key.property + "=" + value;
if (i + 1 === params.alternateKey.length) {
url += ")";
}
else {
url += ",";
}
}
return url;
}
/**
* @description Sends request using given parameters.
* @method SendRequest
* @param {String} method Method type of request to send, such as "GET"
* @param {String} url Target URL for request.
* @param {Object} [payload] Payload for request.
* @param {Object} [parameters] - Parameters for sending the request
* @param {Boolean} [parameters.async] - True for sending async, false for sync. Defaults to true.
* @param {Array<key:string,value:string>} [parameters.headers] - Headers for appending to request
* @memberof module:WebApiClient
* @return {Promise<Object>|Object}
*/
WebApiClient.SendRequest = function (method, url, payload, parameters) {
var params = parameters || {};
// Fallback for request headers array as fourth parameter
if (Array.isArray(params)) {
params = {
headers: params
};
}
if (WebApiClient.Token) {
params.headers = params.headers || [];
params.headers.push({key: "Authorization", value: "Bearer " + WebApiClient.Token});
}
if (params.asBatch) {
return new WebApiClient.BatchRequest({
method: method,
url: url,
payload: payload,
headers: params.headers
});
}
var asynchronous = GetAsync(params);
if (asynchronous) {
return SendAsync(method, url, payload, params);
} else {
return SendSync(method, url, payload, params);
}
};
/**
* @description Applies configuration to WebApiClient.
* @method Configure
* @param {Object} configuration Object with keys named after WebApiClient Members, such as "Token"s
* @memberof module:WebApiClient
* @return {void}
*/
WebApiClient.Configure = function (configuration) {
for (var property in configuration) {
if (!configuration.hasOwnProperty(property)) {
continue;
}
WebApiClient[property] = configuration[property];
}
};
/**
* @description Gets the current base API url that is used.
* @method GetApiUrl
* @memberof module:WebApiClient
* @return {String}
*/
WebApiClient.GetApiUrl = function() {
return GetClientUrl() + "/api/data/v" + WebApiClient.ApiVersion + "/";
};
/**
* @description Creates a given record in CRM.
* @method Create
* @param {Object} parameters Parameters for creating record
* @param {String} parameters.entityName Entity name of record that should be created
* @param {String} [parameters.overriddenSetName] Plural name of entity, if not according to plural rules
* @param {Object} parameters.entity Object containing record data
* @param {Boolean} [parameters.async] True for sending asynchronous, false for synchronous. Defaults to true.
* @param {Array<key:string,value:string>} [parameters.headers] Headers to attach to request
* @memberof module:WebApiClient
* @return {Promise<String>|Promise<object>|String|Object} - Returns Promise<Object> if return=representation header is set, otherwise Promise<String>. Just Object or String if sent synchronously.
*/
WebApiClient.Create = function(parameters) {
var params = parameters || {};
if ((!params.entityName && !params.overriddenSetName) || !params.entity) {
throw new Error("Entity name and entity object have to be passed!");
}
var url = WebApiClient.GetApiUrl() + WebApiClient.GetSetName(params.entityName, params.overriddenSetName);
return WebApiClient.SendRequest("POST", url, params.entity, params);
};
/**
* @description Retrieves records from CRM
* @method Retrieve
* @param {Object} parameters Parameters for retrieving records
* @param {String} parameters.entityName Entity name of records that should be retrieved
* @param {String} [parameters.overriddenSetName] Plural name of entity, if not according to plural rules
* @param {String} [parameters.queryParams] Query Parameters to append to URL, such as ?$select=*
* @param {String} [parameters.fetchXml] Fetch XML query
* @param {String} [parameters.entityId] ID of entity to retrieve, will return single record
* @param {Array<property:string,value:string>} [parameters.alternateKey] Alternate key array for retrieving single record
* @param {Boolean} [parameters.async] True for sending asynchronous, false for synchronous. Defaults to true.
* @param {Array<key:string,value:string>} [parameters.headers] Headers to attach to request
* @memberof module:WebApiClient
* @return {Promise<object>|Object} - Returns Promise<Object> if asyncj, just Object if sent synchronously.
*/
WebApiClient.Retrieve = function(parameters) {
var params = parameters || {};
if (!params.entityName && !params.overriddenSetName) {
throw new Error("Entity name has to be passed!");
}
var url = WebApiClient.GetApiUrl() + WebApiClient.GetSetName(params.entityName, params.overriddenSetName);
if (params.entityId) {
url += "(" + RemoveIdBrackets(params.entityId) + ")";
}
else if (params.fetchXml) {
url += "?fetchXml=" + escape(params.fetchXml);
}
else if (params.alternateKey) {
url += BuildAlternateKeyUrl(params);
}
if (params.queryParams) {
url += params.queryParams;
}
return WebApiClient.SendRequest("GET", url, null, params);
};
/**
* @description Updates a given record in CRM.
* @method Update
* @param {Object} parameters Parameters for updating record
* @param {String} parameters.entityName Entity name of records that should be updated
* @param {String} [parameters.overriddenSetName] Plural name of entity, if not according to plural rules
* @param {String} [parameters.entityId] ID of entity to update
* @param {Array<property:string,value:string>} [parameters.alternateKey] Alternate key array for updating record
* @param {Boolean} [parameters.async] True for sending asynchronous, false for synchronous. Defaults to true.
* @param {Array<key:string,value:string>} [parameters.headers] Headers to attach to request
* @memberof module:WebApiClient
* @return {Promise<String>|Promise<object>|String|Object} - Returns Promise<Object> if return=representation header is set, otherwise Promise<String>. Just Object or String if sent synchronously.
*/
WebApiClient.Update = function(parameters) {
var params = parameters || {};
if (!params.entity) {
throw new Error("Update object has to be passed!");
}
var url = GetRecordUrl(params);
return WebApiClient.SendRequest("PATCH", url, params.entity, params);
};
/**
* @description Deletes a given record in CRM.
* @method Delete
* @param {Object} parameters Parameters for deleting record
* @param {String} parameters.entityName Entity name of records that should be deleted
* @param {String} [parameters.overriddenSetName] Plural name of entity, if not according to plural rules
* @param {String} [parameters.entityId] ID of entity to delete
* @param {Array<property:string,value:string>} [parameters.alternateKey] Alternate key array for deleting record
* @param {Boolean} [parameters.async] True for sending asynchronous, false for synchronous. Defaults to true.
* @param {Array<key:string,value:string>} [parameters.headers] Headers to attach to request
* @memberof module:WebApiClient
* @return {Promise<String>|String} - Returns Promise<String> if async, just String if sent synchronously.
*/
WebApiClient.Delete = function(parameters) {
var params = parameters || {};
var url = GetRecordUrl(params);
if (params.queryParams) {
url += params.queryParams;
}
return WebApiClient.SendRequest("DELETE", url, null, params);
};
/**
* @description Associates given records in CRM.
* @method Associate
* @param {Object} parameters Parameters for associating records
* @param {String} parameters.relationShip Name of relation ship to use for associating
* @param {Object} parameters.source Source entity for disassociating
* @param {String} [parameters.source.overriddenSetName] Plural name of entity, if not according to plural rules
* @param {String} parameters.source.entityId ID of entity
* @param {String} parameters.source.entityName Logical name of entity, such as "account"
* @param {Object} parameters.target Target entity for disassociating
* @param {String} [parameters.target.overriddenSetName] Plural name of entity, if not according to plural rules
* @param {String} parameters.target.entityId ID of entity
* @param {String} parameters.target.entityName Logical name of entity, such as "account"
* @param {Boolean} [parameters.async] True for sending asynchronous, false for synchronous. Defaults to true.
* @param {Array<key:string,value:string>} [parameters.headers] Headers to attach to request
* @memberof module:WebApiClient
* @return {Promise<String>|String} - Returns Promise<String> if async, just String if sent synchronously.
*/
WebApiClient.Associate = function(parameters) {
var params = parameters || {};
if (!params.relationShip) {
throw new Error("Relationship has to be passed!");
}
if (!params.source || !params.target) {
throw new Error("Source and target have to be passed!");
}
var targetUrl = GetRecordUrl(params.target);
var relationShip = "/" + params.relationShip + "/$ref";
var url = targetUrl + relationShip;
var payload = { "@odata.id": GetRecordUrl(params.source) };
return WebApiClient.SendRequest("POST", url, payload, params);
};
/**
* @description Disassociates given records in CRM.
* @method Disassociate
* @param {Object} parameters Parameters for disassociating records
* @param {String} parameters.relationShip Name of relation ship to use for disassociating
* @param {Object} parameters.source Source entity for disassociating
* @param {String} [parameters.source.overriddenSetName] Plural name of entity, if not according to plural rules
* @param {String} parameters.source.entityId ID of entity
* @param {String} parameters.source.entityName Logical name of entity, such as "account"
* @param {Object} parameters.target Target entity for disassociating
* @param {String} [parameters.target.overriddenSetName] Plural name of entity, if not according to plural rules
* @param {String} parameters.target.entityId ID of entity
* @param {String} parameters.target.entityName Logical name of entity, such as "account"
* @param {Boolean} [parameters.async] True for sending asynchronous, false for synchronous. Defaults to true.
* @param {Array<key:string,value:string>} [parameters.headers] Headers to attach to request
* @memberof module:WebApiClient
* @return {Promise<String>|String} - Returns Promise<String> if async, just String if sent synchronously.
*/
WebApiClient.Disassociate = function(parameters) {
var params = parameters || {};
if (!params.relationShip) {
throw new Error("Relationship has to be passed!");
}
if (!params.source || !params.target) {
throw new Error("Source and target have to be passed!");
}
if (!params.source.entityId) {
throw new Error("Source needs entityId set!");
}
var targetUrl = GetRecordUrl(params.target);
var relationShip = "/" + params.relationShip + "(" + RemoveIdBrackets(params.source.entityId) + ")/$ref";
var url = targetUrl + relationShip;
return WebApiClient.SendRequest("DELETE", url, null, params);
};
/**
* @description Executes the given request in CRM.
* @method Execute
* @param {Object} request Request to send, must be in prototype chain of WebApiClient.Requests.Request.
* @param {Boolean} [request.async] True for sending asynchronous, false for synchronous. Defaults to true.
* @param {Array<key:string,value:string>} [request.headers] Headers to attach to request
* @memberof module:WebApiClient
* @return {Promise<Object>|Object} - Returns Promise<Object> if async, just Object if sent synchronously.
*/
WebApiClient.Execute = function(request) {
if (!request) {
throw new Error("You need to pass a request!");
}
if (!(request instanceof WebApiClient.Requests.Request)) {
throw new Error("Request for execution must be in prototype chain of WebApiClient.Request");
}
return WebApiClient.SendRequest(request.method, request.buildUrl(), request.payload, request);
};
/**
* @description Sends the given batch to CRM.
* @method SendBatch
* @param {Object} batch Batch to send to CRM
* @param {Boolean} [batch.async] True for sending asynchronous, false for synchronous. Defaults to true.
* @param {Array<key:string,value:string>} [batch.headers] Headers to attach to request
* @memberof module:WebApiClient
* @return {Promise<Object>|Object} - Returns Promise<Object> if async, just Object if sent synchronously.
*/
WebApiClient.SendBatch = function(batch) {
if (!batch) {
throw new Error("You need to pass a batch!");
}
if (!(batch instanceof WebApiClient.Batch)) {
throw new Error("Batch for execution must be a WebApiClient.Batch object");
}
var url = WebApiClient.GetApiUrl() + "$batch";
batch.headers = batch.headers || [];
batch.headers.push({key: "Content-Type", value: "multipart/mixed;boundary=" + batch.name});
var payload = batch.buildPayload();
return WebApiClient.SendRequest("POST", url, payload, batch);
};
/**
* @description Expands all odata.nextLink (deferred) properties for an array of records.
* @method Expand
* @param {Object} parameters Configuration for expanding
* @param {Array<Object>} parameters.records Array of records to expand
* @param {Boolean} [parameters.async] True for sending asynchronous, false for synchronous. Defaults to true.
* @param {Array<key:string,value:string>} [parameters.headers] Headers to attach to request
* @memberof module:WebApiClient
* @return {Promise<Object>|Object} - Returns Promise<Object> if async, just Object if sent synchronously.
*/
WebApiClient.Expand = function (parameters) {
/// <summary>Expands all odata.nextLink / deferred properties for an array of records</summary>
/// <param name="parameters" type="Object">Object that contains 'records' array or object. Optional 'headers'.</param>
/// <returns>Promise for sent request or result if sync.</returns>
var params = parameters || {};
var records = params.records;
var requests = [];
var asynchronous = GetAsync(parameters);
for (var i = 0; i < records.length; i++) {
var record = records[i];
for (var attribute in record) {
if (!record.hasOwnProperty(attribute)) {
continue;
}
var name = attribute.replace("@odata.nextLink", "");
// If nothing changed, this was not a deferred attribute
if (!name || name === attribute) {
continue;
}
record[name] = WebApiClient.SendRequest("GET", record[attribute], null, params);
// Delete @odata.nextLink property
delete record[attribute];
}
if (asynchronous) {
requests.push(Promise.props(record));
}
}
if (asynchronous) {
return Promise.all(requests);
} else {
return records;
}
};
module.exports = WebApiClient;
} ());