@aappddeevv/dynamics-client-ui
Version:
## What is it? A library to help you create great dynamics applications.
879 lines (823 loc) • 39.1 kB
text/typescript
/**
*
* Hacked CRMWebAPI from: https://github.com/davidyack/Xrm.Tools.CRMWebAPI
*/
import { DEBUG } from "BuildSettings"
export type Id = string
export type URI = string
import { fetch } from "./XMLHTTPRequest"
export interface Request {
/** POST, PUT, etc. */
method: string
/** Full url. */
url: URI
/** Extra headers in addition to what fetch may do. */
headers?: Record<string, string>
/** Payload. */
data: any
}
export interface Config {
APIUrl: URI
AccessToken?: () => string
callerId?: Id
CallerID?: Id
Log?: any
}
export type Fetcher = (config: Config, request: Request,
callback: (error: boolean, ctx: any) => void) => void
/** Provide a fetcher strategy. */
export interface FetchProvider {
fetch?: Fetcher
}
/*
type Resolve<T> = (v?: T|Promise<T>) => void
type Reject = (e?: any) => void
type PromiseArg<T> = (thunks: (res: Resolve<T>, rej: Reject) => void) => void
export interface PromiseMaker {
mkPromise?: <T>(cb: PromiseArg<T>) => Promise<T>
}
export function defaultMkPromise<T>(cb: PromiseArg<T>) {
return new Promise<T>(cb)
}
*/
/** Provide a logger. */
export interface Logger {
shouldLog?: (category: string) => boolean
log?: (category: string, message: string, ctx: any) => void
}
export interface GetListResponse<T> {
List: T[]
Count?: number
fetchXmlPagingCookie?: string
}
export interface UpdateResponse {
EntityID: Id
}
export interface Attribute {
Property: string
Type?: string
}
export interface QueryOptionBase {
FormattedValues?: boolean
Select?: string[]
Filter?: string
/** List of strings "attributename desc/asc" */
OrderBy?: string[]
Top?: number
Path?: Array<Attribute | string>
}
export interface ExpandQueryOptions extends QueryOptionBase {
Property: string
}
export interface QueryOptions extends QueryOptionBase {
Expand?: ExpandQueryOptions[]
FetchXml?: string
IncludeCount?: boolean
Skip?: number
SystemQuery?: string
UserQuery?: string
RecordAction?: (record: any) => void
PageAction?: (list: any[]) => void
BatchHeaders?: Record<string, string>
/** Debug tag. */
tag?: string
}
export interface Option {
Value: number
Label: string
}
/** General purpose dynamics web api client. */
export interface _Client_IN_PROGRESS_ {
GetList<T>(uri: URI, QueryOptions?: QueryOptions): Promise<GetListResponse<T>>
Get<T>(entityCollection: string, entityID: Id | null, QueryOptions?: QueryOptions): Promise<T>
GetCount(uri: URI, QueryOptions?: QueryOptions)
Create(entityCollection: string, data: any): Promise<Id>
Update(entityCollection: string, key: string, data: any, Upsert: boolean): Promise<void>
Delete(entityCollection: string, entityID: Id): Promise<boolean>
Associate(fromEntityCollection: string, fromEntityID: Id, navProperty: String,
toEntityCollection: string, toEntityID: Id): Promise<boolean>
DeleteAssociation(fromEntityCollection: string, fromEntityID: Id, navProperty: string,
toEntityCollection: string, toEntityID: Id): Promise<boolean>
ExecuteFunction(functionName: string, parameters: any,
entityCollection: string | null,
entityID: Id | null): Promise<boolean>
ExecuteAction(actionName: string, data: any, entityCollection: string | null,
entityID: Id | null): Promise<boolean>
batch<T>(entity: string, fetchXml: string, QueryOptions?: QueryOptions): Promise<GetListResponse<T>>
}
/**
* Defaults to XMLHTTPRequest.
*
* @todo Add the ability to issue pure http query so we can fetch full links provided
* in some odata responses.
*/
export class CRMWebAPI {
constructor(config: Config & FetchProvider & Logger) {
this.config = config
this.fp = config.fetch || fetch
this.shouldLog = config.shouldLog
this.log = config.log
//this.mkPromise = config.mkPromise || defaultMkPromise
}
protected fp: Fetcher
private config: Config
protected shouldLog?: (category: string) => boolean
protected log?: (category: string, message: string, ctx: any) => void
//protected mkPromise: <T>(cb: PromiseArg<T>) => Promise<T>
/** For now, pass in extra headers in payload.headers and data in payload.data. */
protected fetch(config: Config, method: string, url: URI, payload: any,
callback: (error: boolean, ctx: any) => void) {
if (this.fp) {
const req = {
method,
url,
data: payload.data,
headers: payload.headers
}
return this.fp(config, req, callback)
}
}
private _log(category: string, message: string, data: any = null) {
if (this.shouldLog && this.log && this.shouldLog(category)) {
this.log(category, message, data)
}
}
private _restParam(func: Function, startIndex: number | null = null) {
startIndex = startIndex == null ? func.length - 1 : +startIndex;
return function() {
var length = Math.max(arguments.length - startIndex!, 0);
var rest = Array(length);
for (var index = 0; index < length; index++) {
rest[index] = arguments[index + startIndex!]
}
switch (startIndex) {
case 0:
return func.call(this, rest);
case 1:
return func.call(this, arguments[0], rest);
}
};
}
private whilst(test, iterator, callback) {
if (test()) {
var next = this._restParam(function(err, args) {
if (err) {
callback(err)
} else if (test.apply(this, args)) {
iterator(next)
} else {
callback.apply(null, [null].concat(args))
}
})
iterator(next)
} else {
callback(null)
}
}
public GetList = async <T>(uri: URI, QueryOptions?: QueryOptions): Promise<GetListResponse<T>> => {
var self = this;
return new Promise<GetListResponse<T>>(function(resolve, reject) {
var url = self._BuildQueryURL(uri, QueryOptions ? QueryOptions : null, self.config)
self.fetch(self.config, "GET", url, {
'headers': self._BuildQueryHeaders(QueryOptions ? QueryOptions : null, self.config)
}, function(err, res) {
if (err != false) {
self._log('Errors', 'GetList Error:', res);
reject(res)
} else {
var data = JSON.parse(res.response, CRMWebAPI.prototype._DateReviver);
var nextLink = data['@odata.nextLink']
var recordCount = data['@odata.count']
var response: GetListResponse<any> = {
List: data.value,
Count: recordCount
};
if (QueryOptions && QueryOptions.RecordAction) {
response.List.forEach(record => QueryOptions.RecordAction!(record))
response.List = []
}
if (QueryOptions && QueryOptions.PageAction) {
QueryOptions.PageAction!(response.List)
response.List = []
}
// was 'undefined' a string !?!?
if (nextLink === undefined) {
resolve(response);
} else {
self.whilst(function() {
return (nextLink !== undefined)
}, function(callback) {
self.fetch(self.config, "GET", nextLink, {
'headers': self._BuildQueryHeaders(QueryOptions ? QueryOptions : null, self.config)
}, function(err, res) {
if (err == false) {
data = JSON.parse(res.response, CRMWebAPI.prototype._DateReviver)
nextLink = data['@odata.nextLink']
response.List = response.List.concat(data.value)
if (QueryOptions && QueryOptions.RecordAction) {
response.List.forEach(function(record) {
QueryOptions.RecordAction!(record)
});
response.List = []
}
if (QueryOptions && QueryOptions.PageAction) {
QueryOptions.PageAction!(response.List)
response.List = []
}
callback(null, response.List.length)
} else {
self._log('Errors', 'GetList Error2', res)
callback('err', 0)
}
});
}, function(err, n) {
resolve(response)
})
}
}
})
})
}
/** Issue a http fetch directly. The response is JSON parsed but not restructured. */
public Fetch = async(url: string, QueryOptions?: QueryOptions, method?: string): Promise<any> => {
const self = this
return new Promise((resolve, reject) => {
self.fetch(self.config, method ? method : "GET", url, {
'headers': self._BuildQueryHeaders(QueryOptions ? QueryOptions : null, self.config)
}, (err, res) => {
if (err != false) {
self._log('Errors', 'Get Error', res)
reject(res)
} else {
var data = JSON.parse(res.response, CRMWebAPI.prototype._DateReviver)
resolve(data)
}
})
})
}
/// <summary>
/// Get a collection or an instance of given entity type
/// </summary>
/// <param name="entityCollection" type="type">Entity logical name to retrieve including plural suffix</param>
/// <param name="entityID" type="type">ID of requested record, or null for collection based on QueryOptions.Filter</param>
/// <param name="QueryOptions" type="type"></param>
public Get = async <T>(entityCollection: string, entityID: Id | null, QueryOptions?: QueryOptions): Promise<T> => {
const self = this;
return new Promise<T>(function(resolve, reject) {
const url = entityID == null ?
self._BuildQueryURL(entityCollection, QueryOptions ? QueryOptions : null, self.config) :
self._BuildQueryURL(entityCollection + "(" +
entityID.toString().replace(/[{}]/g, "") + ")",
QueryOptions ? QueryOptions : null, self.config)
self.fetch(self.config, "GET", url, {
'headers': self._BuildQueryHeaders(QueryOptions ? QueryOptions : null, self.config)
}, function(err, res) {
if (err != false) {
self._log('Errors', 'Get Error', res)
reject(res)
} else {
var data = JSON.parse(res.response, CRMWebAPI.prototype._DateReviver)
resolve(data)
}
})
})
}
public GetCount = async (uri: URI, QueryOptions?: QueryOptions) => {
var self = this
return new Promise<number>(function(resolve, reject) {
var url = self._BuildQueryURL(uri + "/$count", QueryOptions ? QueryOptions : null, self.config)
self.fetch(self.config, "GET", url, {
'headers': self._BuildQueryHeaders(QueryOptions ? QueryOptions : null, self.config)
}, function(err, res) {
if (err != false) {
self._log('Errors', 'GetCount Error', res)
reject(res)
} else {
var data = parseInt(res.response);
resolve(data)
}
});
});
}
/// <summary>
/// Create a record
/// </summary>
/// <param name="entityCollection" type="type">Plural name of entity to create</param>
/// <param name="data" type="type">JSON object with attributes for the record to create</param>
public Create = async (entityCollection: string, data: any): Promise<Id> => {
var self = this;
return new Promise<Id>(function(resolve, reject) {
var url = self.config.APIUrl + entityCollection;
self._log('ODataUrl', url);
self.fetch(self.config, "POST", url, {
'data': JSON.stringify(data)
}, function(err, res) {
if (err != false) {
self._log('Errors', 'Create Error', res);
reject(res);
} else {
const r = /\(([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\)/g
resolve(r.exec(res.headers["odata-entityid"])![1])
}
});
});
}
/// <summary>
/// Update an existing record or create a new if record does not exist (Upsert)
/// </summary>
/// <param name="entityCollection" type="type">Plural name of entity to update</param>
/// <param name="key" type="type">Key to locate existing record</param>
/// <param name="data" type="type">JSON object with attributes for the record to upddate</param>
/// <param name="Upsert" type="type">Set to true to enable upsert functionality, which creates a new record if key is not found</param>
public Update = async (entityCollection: string, key: string, data: any, Upsert: boolean = false) => {
var self = this
return new Promise<any>(function(resolve, reject) {
var url = self.config.APIUrl + entityCollection + '(' + key.replace(/[{}]/g, "") + ')'
self._log('ODataUrl', url)
var payload = {
"data": JSON.stringify(data),
"headers": {}
};
if (Upsert == false) payload["headers"]["If-Match"] = "*"
self.fetch(self.config, "PATCH", url, payload, function(err, res) {
if (err != false) {
self._log('Errors', 'Update Error', res)
reject(res)
} else {
var response: any = {}
var parseEntityID = /\(([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\)/g.exec(res.headers["odata-entityid"]);
if (parseEntityID != null)
response.EntityID = parseEntityID[1]
resolve(response);
}
});
});
};
/// <summary>
/// Delete an existing record
/// </summary>
/// <param name="entityCollection" type="type">Plural name of entity to delete</param>
/// <param name="entityID" type="type">ID of record to delete</param>
public Delete = async (entityCollection: string, entityID: Id): Promise<boolean> => {
var self = this
return new Promise<boolean>(function(resolve, reject) {
var url = self.config.APIUrl + entityCollection + '(' + entityID.replace(/[{}]/g, "") + ')'
self._log('ODataUrl', url)
self.fetch(self.config, "DELETE", url, {}, function(err, res) {
if (err != false) {
self._log('Errors', 'Delete Error', res)
reject(res)
} else {
resolve(true)
}
});
});
}
public Associate = async (fromEntityCollection: string, fromEntityID: Id, navProperty: String,
toEntityCollection: string, toEntityID: Id): Promise<boolean> => {
var self = this;
return new Promise<boolean>(function(resolve, reject) {
var url = self.config.APIUrl + fromEntityCollection + '(' + fromEntityID.replace(/[{}]/g, "")
+ ')/' + navProperty + '/$ref'
self._log('ODataUrl', url)
var payload = {
'data': JSON.stringify({
'@odata.id': self.config.APIUrl + toEntityCollection + '(' +
toEntityID.replace(/[{}]/g, "") + ')'
})
};
self.fetch(self.config, 'POST', url, payload, function(err, res) {
if (err != false) {
self._log('Errors', 'Associate Error', res)
reject(res)
} else {
resolve(true)
}
});
});
}
public DeleteAssociation = async (fromEntityCollection: string, fromEntityID: Id, navProperty: string,
toEntityCollection: string, toEntityID: Id): Promise<boolean> => {
var self = this;
return new Promise<boolean>(function(resolve, reject) {
var url = self.config.APIUrl + fromEntityCollection + '(' +
fromEntityID.replace(/[{}]/g, "") + ')/' + navProperty + '/$ref'
if (toEntityCollection != null && toEntityID != null)
url += '?$id=' + self.config.APIUrl + toEntityCollection + '(' +
toEntityID.replace(/[{}]/g, "") + ')'
self._log('ODataUrl', url)
self.fetch(self.config, 'DELETE', url, {}, function(err, res) {
if (err != false) {
self._log('Errors', 'DeleteAssociation Error', res)
reject(res)
} else {
resolve(true)
}
});
});
}
public ExecuteFunction = async (functionName: string, parameters: any,
entityCollection: string | null = null,
entityID: Id | null = null) => {
var self = this;
return new Promise<any>(function(resolve, reject) {
var parmvars: string[] = []
var parmvalues: string[] = []
var parmcount: number = 1
if (parameters != null) {
Object.keys(parameters).forEach(function(key) {
var val = parameters[key]
parmvars.push(key + "=" + "@p" + parmcount.toString())
if (typeof val === 'string' || val instanceof String)
parmvalues.push("@p" + parmcount.toString() + "='" + val + "'")
else parmvalues.push("@p" + parmcount.toString() + "=" + val)
parmcount++
});
}
var url = ""
if (parameters != null) {
url = self.config.APIUrl + functionName + "(" + parmvars.join(",") + ")?" +
parmvalues.join("&");
if (entityCollection != null) url = self.config.APIUrl + entityCollection + "(" +
entityID!.toString().replace(/[{}]/g, "") + ")" +
functionName +
"(" + parmvars.join(",") + ")?" + parmvalues.join("&")
} else {
url = self.config.APIUrl + functionName + "()";
if (entityCollection != null) url = self.config.APIUrl + entityCollection + "(" +
entityID!.toString().replace(/[{}]/g, "") + ")" +
functionName + "()"
}
self._log('ODataUrl', url)
self.fetch(self.config, "GET", url, {}, function(err, res) {
if (err != false) {
self._log('Errors', 'ExecuteFunction Error', res);
reject(res)
} else {
var data = JSON.parse(res.response, CRMWebAPI.prototype._DateReviver)
resolve(data)
}
});
});
}
public ExecuteAction = async (actionName: string, data: any, entityCollection: string | null = null,
entityID: Id | null = null) => {
var self = this
return new Promise<any>(function(resolve, reject) {
var url = self.config.APIUrl + actionName
if (entityCollection != null) url = self.config.APIUrl + entityCollection + "(" +
entityID!.toString().replace(/[{}]/g, "") + ")/" + actionName
self._log('ODataUrl', url)
self.fetch(self.config, "POST", url, {
"data": JSON.stringify(data)
}, function(err, res) {
if (err != false) {
self._log('Errors', 'ExecuteAction Error', res);
reject(res)
} else {
if (res.response == "") {
resolve(undefined) //changed from null
} else {
var data = JSON.parse(res.response, CRMWebAPI.prototype._DateReviver)
resolve(data)
}
}
})
})
}
private _BuildQueryURL(uri: URI, queryOptions: QueryOptions | null, config: Config) {
const path = (queryOptions && queryOptions.Path) ?
queryOptions.Path.map(part =>
typeof part === 'string' ?
`/${part}` :
`/${part.Property}` + (part.Type ? `/${part.Type}` : "")
) : []
let fullurl = config.APIUrl + uri + (path.length > 0 ? path.join("/") : "")
const qs: string[] = []
if (queryOptions != null) {
if (queryOptions.Select != null) qs.push("$select=" + encodeURI(queryOptions.Select.join(",")));
if (queryOptions.OrderBy != null) qs.push("$orderby=" + encodeURI(queryOptions.OrderBy.join(",")));
if (queryOptions.Filter != null) qs.push("$filter=" + encodeURI(queryOptions.Filter));
if (queryOptions.Expand != null) {
var expands: string[] = [];
queryOptions.Expand.forEach(function(ex: any) {
if ((ex.Select != null) || (ex.Filter != null) || (ex.OrderBy != null) || (ex.Top != null)) {
var qsExpand: string[] = [];
if (ex.Select != null) qsExpand.push("$select=" + ex.Select.join(","));
if (ex.OrderBy != null) qsExpand.push("$orderby=" + ex.OrderBy.join(","));
if (ex.Filter != null) qsExpand.push("$filter=" + ex.Filter);
if (ex.Top > 0) qsExpand.push("$top=" + ex.Top);
expands.push(ex.Property + "(" + qsExpand.join(";") + ")");
}
else
expands.push(ex.Property);
});
qs.push("$expand=" + encodeURI(expands.join(",")));
}
if (queryOptions.IncludeCount) qs.push("$count=true");
if (queryOptions.Skip && queryOptions.Skip > 0)
qs.push("skip=" + encodeURI(queryOptions.Skip ? queryOptions.Skip.toString() : ""));
if (queryOptions.Top && queryOptions.Top > 0)
qs.push("$top=" + encodeURI(queryOptions.Top ? queryOptions.Top.toString() : ""));
if (queryOptions.SystemQuery != null) qs.push("savedQuery=" + encodeURI(queryOptions.SystemQuery));
if (queryOptions.UserQuery != null) qs.push("userQuery=" + encodeURI(queryOptions.UserQuery));
if (queryOptions.FetchXml != null) qs.push("fetchXml=" + encodeURI(queryOptions.FetchXml));
}
if (qs.length > 0) fullurl += "?" + qs.join("&")
this._log('ODataUrl', fullurl);
return fullurl;
}
private _BuildQueryHeaders(queryOptions: QueryOptions | null, config: Config) {
var headers = {};
if (queryOptions != null) {
if (queryOptions.FormattedValues == true) headers['Prefer'] = 'odata.include-annotations="OData.Community.Display.V1.FormattedValue"';
}
return headers;
};
private parseResponseHeaders(headerStr: string) {
var headers = {};
if (!headerStr) {
return headers;
}
var headerPairs = headerStr.split('\u000d\u000a');
for (var i = 0; i < headerPairs.length; i++) {
var headerPair = headerPairs[i];
// Can't use split() here because it does the wrong thing
// if the header value has the string ": " in it.
var index = headerPair.indexOf('\u003a\u0020');
if (index > 0) {
var key = headerPair.substring(0, index);
var val = headerPair.substring(index + 2);
headers[key.toLowerCase()] = val;
}
}
return headers;
}
public _DateReviver(key: string, value: any) {
var a;
if (typeof value === 'string') {
a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
if (a) {
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6]));
}
}
return value;
}
public GetOptionSetByName = async (optionSetName: string): Promise<any> => {
var self = this;
return new Promise<any>(function(resolve, reject) {
self.GetList('GlobalOptionSetDefinitions', { Select: ['Name'] }).
then((r: GetListResponse<any>) => {
r.List.forEach(set => {
if (set.Name == optionSetName) {
self.Get('GlobalOptionSetDefinitions', set.MetadataId).
then(res => resolve(res), err => console.log(err))
}
})
},
(e: Error) => {
console.log(e)
reject(e)
})
})
}
public GetOptionSetUserLabels = async (optionSetName: string): Promise<Array<Option>> => {
var self = this;
return new Promise<Array<Option>>(function(resolve, reject) {
self.GetOptionSetByName(optionSetName).
then((result: { Options: Array<any> }) => {
const displayList = result.Options.map((option: any) => {
return {
Value: option.Value,
Label: option.Label.UserLocalizedLabel.Label
}
})
resolve(displayList)
},
err => {
console.log(err)
reject(err)
}
)
})
}
public GetEntityDisplayNameList(LCID: string) {
var self = this;
return new Promise(function(resolve, reject) {
self.GetList('EntityDefinitions',
{
Filter: 'IsPrivate eq false', Select: ['MetadataId', 'EntitySetName', 'DisplayName',
'DisplayCollectionName', 'LogicalName', 'LogicalCollectionName',
'PrimaryIdAttribute']
}).
then(
function(r: any) {
var list = new Array();
r.List.forEach(function(entity: any) {
var edm: any = new Object();
edm.MetadataId = entity.MetadataId;
edm.EntitySetName = entity.EntitySetName;
edm.LogicalName = entity.LogicalName;
edm.LogicalCollectionName = entity.LogicalCollectionName;
edm.PrimaryIdAttribute = entity.PrimaryIdAttribute;
if ((entity.DisplayName.LocalizedLabels != null) && (entity.DisplayName.LocalizedLabels.length > 0)) {
edm.DisplayName = entity.DisplayName.LocalizedLabels[0].Label;
if (LCID != null)
entity.DisplayName.LocalizedLabels.forEach(function(label) {
if (label.LanguageCode == LCID) edm.DisplayName = label.Label
});
}
else
edm.DisplayName = edm.LogicalName;
if ((entity.DisplayCollectionName.LocalizedLabels != null) &&
(entity.DisplayCollectionName.LocalizedLabels.length > 0)) {
edm.DisplayCollectionName = entity.DisplayCollectionName.LocalizedLabels[0].Label;
if (LCID != null)
entity.DisplayCollectionName.LocalizedLabels.forEach(function(label) {
if (label.LanguageCode == LCID) edm.DisplayCollectionName = label.Label
});
}
else
edm.DisplayCollectionName = entity.LogicalCollectionName;
edm.LogicalDisplayName = edm.DisplayName + '(' + edm.LogicalName + ')'
edm.LogicalDisplayCollectionName = edm.DisplayCollectionName + '(' + edm.LogicalCollectionName + ')'
list.push(edm);
}
)
resolve(list);
},
function(e) {
console.log(e)
reject(e)
})
});
}
public GetAttributeDisplayNameList(entityID: string, LCID: string) {
var self = this;
return new Promise(function(resolve, reject) {
self.GetList('EntityDefinitions(' + entityID.toString() + ')/Attributes',
{ Filter: '((IsValidForRead eq true) and (AttributeOf eq null))', Select: ['MetadataId', 'DisplayName', 'LogicalName', 'SchemaName', 'AttributeType', 'IsPrimaryId'] }).then(
function(r: any) {
var list = new Array();
r.List.forEach(function(attrib) {
var edm: any = new Object();
edm.MetadataId = attrib.MetadataId;
edm.LogicalName = attrib.LogicalName;
edm.SchemaName = attrib.SchemaName;
edm.IsPrimaryId = attrib.IsPrimaryId;
edm.AttributeType = attrib.AttributeType;
if (attrib.AttributeType === "Lookup" ||
attrib.AttributeType === "Customer" ||
attrib.AttributeType === "Owner")
edm.ODataLogicalName = "_" + attrib.LogicalName + "_value";
else
edm.ODataLogicalName = attrib.LogicalName;
if ((attrib.DisplayName.LocalizedLabels != null) &&
(attrib.DisplayName.LocalizedLabels.length > 0)) {
edm.DisplayName = attrib.DisplayName.LocalizedLabels[0].Label;
if (LCID != null)
attrib.DisplayName.LocalizedLabels.forEach(function(label) {
if (label.LanguageCode == LCID) edm.DisplayName = label.Label
});
}
else
edm.DisplayName = edm.LogicalName;
edm.LogicalDisplayName = edm.DisplayName +
'(' + edm.LogicalName + ')'
list.push(edm);
}
)
resolve(list);
},
function(e) {
console.log(e)
reject(e)
})
});
}
//
// monkey patch
//
public batch<T>(entity: string, fetchXml: string, QueryOptions?: QueryOptions): Promise<GetListResponse<T>> {
const self = this
// build the body
var body = '--batch_contactfetch\n'
body += 'Content-Type: application/http\n'
body += 'Content-Transfer-Encoding: binary\n'
body += '\n'
body += 'GET ' + self.config.APIUrl + `${entity}?fetchXml=${encodeURIComponent(fetchXml)} HTTP/1.1\n`
body += 'Content-Type: application/json\n'
body += 'OData-Version: 4.0\n'
body += 'OData-MaxVersion: 4.0\n'
//if(QueryOptions && QueryOptions.BatchHeaders) {
// console.log("BATCHE EXTRA HEADERS", QueryOptions.BatchHeaders)
// const keys = Object.keys(QueryOptions!.BatchHeaders!)
// if(keys && keys.length > 0)
// body += keys.map(k => `${k}: ${QueryOptions!.BatchHeaders![k]}`).join("\n")
// odata annotation: Microsoft.Dynamics.CRM.fetchxmlpagingcookie
body += 'Prefer: odata.include-annotations="OData.Community.Display.V1.FormattedValue,Microsoft.Dynamics.CRM.*"\n'
//}
body += '\n'
body += '--batch_contactfetch--'
return new Promise(function(resolve, reject) {
const url = self._BuildQueryURL("$batch", QueryOptions ? QueryOptions : null, self.config)
self._log('ODataUrl', url)
//self.fetch(self.config, "POST", url, { // won't work, additive headers
self._hack(self.config, "POST", url, {
'data': body,
headers: {
"Content-Type": "multipart/mixed;boundary=batch_contactfetch"
}
}, function(err, res) { // callback
if (err != false) {
self._log('Errors', 'batch error', res)
reject(res)
} else {
//if(DEBUG) console.log("raw r", res)
var data = JSON.parse(self._sliceBatchResponse(res.response), self._DateReviver)
if (data.error) {
// its really an error
self._log('Errors', 'batch error', res)
reject(data.error)
return
}
//if(DEBUG) console.log("batch.raw json", data)
var nextLink = data['@odata.nextLink']
var recordCount = data['@odata.count']
var fetchXmlPagingCookie = data["@Microsoft.Dynamics.CRM.fetchxmlpagingcookie"]
var response: GetListResponse<any> = {
List: data.value,
Count: recordCount,
fetchXmlPagingCookie: fetchXmlPagingCookie
}
if (QueryOptions && QueryOptions.RecordAction) {
response!.List!.forEach(record => QueryOptions.RecordAction!(record))
response.List = [];
}
if (QueryOptions && QueryOptions.PageAction) {
QueryOptions.PageAction!(response.List)
response.List = []
}
if (!nextLink) {
resolve(response)
} else {
if (DEBUG) console.log("NEXT LINK BUT NEED TO IMPLEMENT WHILST!!!")
if (DEBUG) console.log("response", data)
resolve(response)
// the lib has page processing code here...copy and paste it!
}
}
})
})
}
private _hack(config: Config, method: string, url: URI, payload: any,
callback: (succes: boolean, ctx: any) => void) {
var self = this
var req = new XMLHttpRequest()
//req.open(method, encodeURI(url), true);
req.open(method, url, true)
if (config.AccessToken)
req.setRequestHeader("Authorization", "Bearer " + config.AccessToken())
req.setRequestHeader("Accept", "application/json")
req.setRequestHeader("OData-MaxVersion", "4.0")
req.setRequestHeader("OData-Version", "4.0")
if (config.callerId) req.setRequestHeader("MSCRMCallerID", config.callerId)
if (config.CallerID) req.setRequestHeader("MSCRMCallerID", config.CallerID)
if (['POST', 'PUT', 'PATCH'].indexOf(method) >= 0) {
// GL: Browser should set this itself
//req.setRequestHeader("Content-Length", payload.data.length);
//req.setRequestHeader("Content-Type", "application/json");
}
if (payload.headers !== 'undefined') {
for (var name in payload.headers) {
req.setRequestHeader(name, payload.headers[name])
}
}
req.onreadystatechange = function() {
if (this.readyState == 4 /* complete */) {
req.onreadystatechange = () => { }
if ((this.status >= 200) && (this.status < 300)) {
callback(false, {
'response': this.response,
'headers': self.parseResponseHeaders(this.getAllResponseHeaders())
});
} else {
callback(true, this)
}
}
}
if (['POST', 'PUT', 'PATCH'].indexOf(method) >= 0) {
req.send(payload.data);
} else {
req.send()
}
}
private _sliceBatchResponse(response: string) {
const start = response.indexOf("{")
const end = response.lastIndexOf("}") + 1
//console.log("stats", start, end, response[start], response[end-1])
//console.log("parsing", response.substring(start, end))
return response.substring(start, end)
}
}
export default CRMWebAPI