Skip to content

Commit 2b087bf

Browse files
finishing working on proxy. added tests. fixes #89
1 parent 8fb1f3d commit 2b087bf

File tree

9 files changed

+255
-105
lines changed

9 files changed

+255
-105
lines changed

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ Please note, that "Dynamics 365" in this readme refers to Microsoft Dynamics 365
8888
* [Formatted Values and Lookup Properties](#formatted-values-and-lookup-properties)
8989
* [Using Alternate Keys](#using-alternate-keys)
9090
* [Making requests using Entity Logical Names](#making-requests-using-entity-logical-names)
91+
* [Using Proxy](#using-proxy)
9192
* [Using TypeScript Declaration Files](#using-typescript-declaration-files)
9293
* [In Progress / Feature List](#in-progress--feature-list)
9394
* [JavaScript Promises](#javascript-promises)
@@ -217,6 +218,7 @@ impersonateAAD | String | `v.1.6.12+` A String representing the GUID value for t
217218
includeAnnotations | String | Defaults Prefer header with value "odata.include-annotations=" and the specified annotation. Annotations provide additional information about lookups, options sets and other complex attribute types.
218219
maxPageSize | Number | Defaults the odata.maxpagesize preference. Use to set the number of entities returned in the response.
219220
onTokenRefresh | Function | A callback function that triggered when DynamicsWebApi requests a new OAuth token. (At this moment it is done before each call to Dynamics 365, as [recommended by Microsoft](https://msdn.microsoft.com/en-ca/library/gg327838.aspx#Anchor_2)).
221+
proxy | Object | `v.1.7.2+` Proxy configuration object. [More Info](#using-proxy)
220222
returnRepresentation | Boolean | Defaults Prefer header with value "return=representation". Use this property to return just created or updated entity in a single request.
221223
timeout | Number | Sets a number of milliseconds before a request times out.
222224
useEntityNames | Boolean | `v.1.4.0+` Indicates whether to use entity logical names instead of collection logical names during requests.
@@ -2069,6 +2071,31 @@ var collectionName = dynamicsWebApi.utility.getCollectionName('account');
20692071

20702072
Please note, everything said above will happen only if you set `useEntityNames: true` in the DynamicsWebApi config.
20712073

2074+
## Using Proxy
2075+
2076+
**Node.js Only.** Starting from v.1.7.2 DynamicsWebApi supports different types of connections through proxy. To make it possible, I added two dependencies in a `package.json`:
2077+
(http-proxy-agent)[https://github.com/TooTallNate/node-https-proxy-agent] and (https-proxy-agent)[https://github.com/TooTallNate/node-http-proxy-agent], based on a type of a protocol, DynamicsWebApi
2078+
will use one of those agents.
2079+
2080+
In order to let DynamicsWebApi know that you are using proxy you have two options:
2081+
1. add environmental variables `http_proxy` or `https_proxy` in your .env file
2082+
2. or pass parameters in DynamicsWebApi configuration, for example:
2083+
2084+
```js
2085+
const dynamicsWebApi = new DynamicsWebApi({
2086+
webApiUrl: 'https://myorg.api.crm.dynamics.com/api/data/v9.1/',
2087+
onTokenRefresh: acquireToken,
2088+
proxy: {
2089+
url: 'http://localhost:12345',
2090+
//auth is optional, you can also provide authentication in the url
2091+
auth: {
2092+
username: 'john',
2093+
password: 'doe'
2094+
}
2095+
}
2096+
});
2097+
```
2098+
20722099
## Using TypeScript Declaration Files
20732100

20742101
TypeScript declaration files `d.ts` added with v.1.5.3.
@@ -2135,6 +2162,7 @@ the config option "formatted" will enable developers to retrieve all information
21352162
- [X] Impersonate a user based on their Azure Active Directory (AAD) object id. `Added in v.1.6.12`.
21362163
- [X] File upload/download/delete for a File Field. `Added in v.1.7.0`.
21372164
- [X] Shrink size of an NPM package. `Added in v.1.7.1`.
2165+
- [X] Full proxy support. `Added in v.1.7.2`.
21382166
- [ ] Refactoring and conversion to TypeScript - coming with `v.2.0`! Stay tuned!
21392167

21402168
Many more features to come!

lib/dynamics-web-api-callbacks.js

Lines changed: 22 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -9,41 +9,6 @@ if (!String.prototype.endsWith || !String.prototype.startsWith) {
99
require("./polyfills/string-es6");
1010
}
1111

12-
/* develblock:start */
13-
var dwaExpandRequest = function () {
14-
return {
15-
select: [],
16-
filter: "",
17-
top: 0,
18-
orderBy: [],
19-
property: ""
20-
};
21-
};
22-
var dwaRequest = function () {
23-
return {
24-
type: "",
25-
id: "",
26-
select: [],
27-
expand: [],
28-
filter: "",
29-
maxPageSize: 1,
30-
count: true,
31-
top: 1,
32-
orderBy: [],
33-
includeAnnotations: "",
34-
ifmatch: "",
35-
ifnonematch: "",
36-
returnRepresentation: true,
37-
entity: {},
38-
impersonate: "",
39-
navigationProperty: "",
40-
savedQuery: "",
41-
userQuery: "",
42-
async: true
43-
};
44-
};
45-
/* develblock:end */
46-
4712
/**
4813
* Configuration object for DynamicsWebApi
4914
* @typedef {object} DWAConfig
@@ -55,6 +20,7 @@ var dwaRequest = function () {
5520
* @property {string} maxPageSize - Sets the odata.maxpagesize preference value to request the number of entities returned in the response.
5621
* @property {boolean} returnRepresentation - Sets Prefer header request with value "return=representation". Use this property to return just created or updated entity in a single request.
5722
* @property {boolean} useEntityNames - Indicates whether to use Entity Logical Names instead of Collection Logical Names.
23+
* @property {Object} proxy - Proxy configuration.
5824
*/
5925

6026
/**
@@ -112,7 +78,8 @@ function DynamicsWebApi(config) {
11278
onTokenRefresh: null,
11379
includeAnnotations: null,
11480
maxPageSize: null,
115-
returnRepresentation: null
81+
returnRepresentation: null,
82+
proxy: null
11683
};
11784

11885
var _isBatch = false;
@@ -178,7 +145,25 @@ function DynamicsWebApi(config) {
178145
if (config.useEntityNames) {
179146
ErrorHelper.boolParameterCheck(config.useEntityNames, 'DynamicsWebApi.setConfig', 'config.useEntityNames');
180147
_internalConfig.useEntityNames = config.useEntityNames;
181-
}
148+
}
149+
150+
/* develblock:start */
151+
if (config.proxy) {
152+
ErrorHelper.parameterCheck(config.proxy, 'DynamicsWebApi.setConfig', 'config.proxy');
153+
154+
if (config.proxy.url) {
155+
ErrorHelper.stringParameterCheck(config.proxy.url, 'DynamicsWebApi.setConfig', 'config.proxy.url');
156+
157+
if (config.proxy.auth) {
158+
ErrorHelper.parameterCheck(config.proxy.auth, 'DynamicsWebApi.setConfig', 'config.proxy.auth');
159+
ErrorHelper.stringParameterCheck(config.proxy.auth.username, 'DynamicsWebApi.setConfig', 'config.proxy.auth.username');
160+
ErrorHelper.stringParameterCheck(config.proxy.auth.password, 'DynamicsWebApi.setConfig', 'config.proxy.auth.password');
161+
}
162+
}
163+
164+
_internalConfig.proxy = config.proxy;
165+
}
166+
/* develblock:end */
182167
};
183168

184169
this.setConfig(config);

lib/dynamics-web-api.js

Lines changed: 21 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10,43 +10,6 @@ if (!String.prototype.endsWith || !String.prototype.startsWith) {
1010
require("./polyfills/string-es6");
1111
}
1212

13-
/* develblock:start */
14-
var dwaExpandRequest = function () {
15-
return {
16-
select: [],
17-
filter: "",
18-
top: 0,
19-
orderBy: [],
20-
property: ""
21-
};
22-
};
23-
var dwaRequest = function () {
24-
return {
25-
collection: "",
26-
id: "",
27-
key: "",
28-
duplicateDetection: true,
29-
select: [],
30-
expand: [],
31-
filter: "",
32-
maxPageSize: 1,
33-
count: true,
34-
top: 1,
35-
orderBy: [],
36-
includeAnnotations: "",
37-
ifmatch: "",
38-
ifnonematch: "",
39-
returnRepresentation: true,
40-
entity: {},
41-
impersonate: "",
42-
navigationProperty: "",
43-
savedQuery: "",
44-
userQuery: "",
45-
mergeLabels: false
46-
};
47-
};
48-
/* develblock:end */
49-
5013
/**
5114
* Configuration object for DynamicsWebApi
5215
* @typedef {object} DWAConfig
@@ -119,7 +82,8 @@ function DynamicsWebApi(config) {
11982
onTokenRefresh: null,
12083
includeAnnotations: null,
12184
maxPageSize: null,
122-
returnRepresentation: null
85+
returnRepresentation: null,
86+
proxy: null
12387
};
12488

12589
var _isBatch = false;
@@ -191,6 +155,24 @@ function DynamicsWebApi(config) {
191155
ErrorHelper.boolParameterCheck(config.useEntityNames, 'DynamicsWebApi.setConfig', 'config.useEntityNames');
192156
_internalConfig.useEntityNames = config.useEntityNames;
193157
}
158+
159+
/* develblock:start */
160+
if (config.proxy) {
161+
ErrorHelper.parameterCheck(config.proxy, 'DynamicsWebApi.setConfig', 'config.proxy');
162+
163+
if (config.proxy.url) {
164+
ErrorHelper.stringParameterCheck(config.proxy.url, 'DynamicsWebApi.setConfig', 'config.proxy.url');
165+
166+
if (config.proxy.auth) {
167+
ErrorHelper.parameterCheck(config.proxy.auth, 'DynamicsWebApi.setConfig', 'config.proxy.auth');
168+
ErrorHelper.stringParameterCheck(config.proxy.auth.username, 'DynamicsWebApi.setConfig', 'config.proxy.auth.username');
169+
ErrorHelper.stringParameterCheck(config.proxy.auth.password, 'DynamicsWebApi.setConfig', 'config.proxy.auth.password');
170+
}
171+
}
172+
173+
_internalConfig.proxy = config.proxy;
174+
}
175+
/* develblock:end */
194176
};
195177

196178
this.setConfig(config);
@@ -617,7 +599,7 @@ function DynamicsWebApi(config) {
617599
request.downloadSize = "full";
618600

619601
return _makeRequest("GET", request, "downloadFile", { parse: true })
620-
.then(function(response) {
602+
.then(function (response) {
621603
request.url = response.data.location;
622604
data += response.data.value;
623605

lib/requests/http.js

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,43 @@ var HttpsProxyAgent = require('https-proxy-agent');
66
var parseResponse = require('./helpers/parseResponse');
77
var ErrorHelper = require('../helpers/ErrorHelper');
88

9-
var proxyAgents = {};
9+
var agents = {};
1010

11-
function getProxyAgent(options, headers) {
12-
var protocol = parsedUrl.protocol.replace(':', '');
11+
function getAgent(options, protocol) {
12+
var isHttp = protocol === 'http';
1313

1414
var proxy = options.proxy;
15-
var proxyOptions = url.parse(proxy.url);
15+
var agentName = proxy ? proxy.url : protocol;
16+
17+
if (!agents[agentName]) {
18+
if (proxy) {
19+
var parsedProxyUrl = url.parse(proxy.url);
20+
var proxyAgent = isHttp ? HttpProxyAgent : HttpsProxyAgent;
1621

17-
headers.host = proxyOptions.host;
22+
var proxyOptions = {
23+
host: parsedProxyUrl.hostname,
24+
port: parsedProxyUrl.port,
25+
protocol: parsedProxyUrl.protocol
26+
}
1827

19-
if (!proxyAgents[protocol]) {
20-
var proxyAgent = protocol === 'http' ? HttpProxyAgent : HttpsProxyAgent;
28+
if (proxy.auth)
29+
proxyOptions.auth = proxy.auth.username + ':' + proxy.auth.password;
30+
else if (parsedProxyUrl.auth)
31+
proxyOptions.auth = parsedProxyUrl.auth;
2132

22-
if (proxy.auth) {
23-
var base64 = Buffer.from(proxy.auth.username + ':' + proxy.auth.password, 'utf8').toString('base64');
24-
proxyOptions.headers = { 'Proxy-Authorization': 'Basic ' + base64 };
33+
agents[agentName] = new proxyAgent(proxyOptions);
2534
}
35+
else {
36+
var protocolInterface = isHttp ? http : https;
2637

27-
proxyAgents[protocol] = new proxyAgent(proxyOptions);
38+
agents[agentName] = new protocolInterface.Agent({
39+
keepAlive: true,
40+
maxSockets: Infinity
41+
});
42+
}
2843
}
2944

30-
return proxyAgents[protocol];
45+
return agents[agentName];
3146
}
3247

3348
/**
@@ -61,9 +76,8 @@ var httpRequest = function (options) {
6176
}
6277

6378
var parsedUrl = url.parse(uri);
64-
var protocol = parsedUrl.protocol.replace(':', '');
65-
var isHttp = protocol === 'http';
66-
var protocolInterface = isHttp ? http : https;
79+
var protocol = parsedUrl.protocol.slice(0, -1);
80+
var protocolInterface = protocol === 'http' ? http : https;
6781

6882
var internalOptions = {
6983
hostname: parsedUrl.hostname,
@@ -74,15 +88,17 @@ var httpRequest = function (options) {
7488
headers: headers
7589
};
7690

77-
//support environment variables (deprecated)
91+
//support environment variables
7892
if (!proxy && process.env[`${protocol}_proxy`]) {
7993
options.proxy = {
8094
url: process.env[`${protocol}_proxy`]
8195
}
8296
}
8397

84-
if (proxy && proxy.url) {
85-
internalOptions.agent = getProxyAgent(options, headers);
98+
internalOptions.agent = getAgent(options, protocol);
99+
100+
if (proxy) {
101+
headers.host = url.parse(proxy.url).host;
86102
}
87103

88104
var request = protocolInterface.request(internalOptions, function (res) {
@@ -111,17 +127,13 @@ var httpRequest = function (options) {
111127
successCallback(response);
112128
break;
113129
}
114-
//case 206: { //Success with partial content
115-
// //true indicates continue
116-
// successCallback(true);
117-
// break;
118-
//}
119130
default: // All other statuses are error cases.
120131
var crmError;
121132
try {
122133
var errorParsed = parseResponse(rawData, res.headers, responseParams[requestId]);
123134

124135
if (Array.isArray(errorParsed)) {
136+
delete responseParams[requestId];
125137
errorCallback(errorParsed);
126138
break;
127139
}
@@ -152,7 +164,6 @@ var httpRequest = function (options) {
152164

153165
if (internalOptions.timeout) {
154166
request.setTimeout(internalOptions.timeout, function () {
155-
delete responseParams[requestId];
156167
request.abort();
157168
});
158169
}

lib/requests/sendRequest.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,9 @@ function sendRequest(method, path, config, data, additionalHeaders, responsePara
327327
errorCallback: errorCallback,
328328
isAsync: isAsync,
329329
timeout: timeout,
330+
/* develblock:start */
331+
proxy: config.proxy,
332+
/* develblock:end */
330333
requestId: requestId
331334
});
332335
};

lib/requests/xhr.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ let xhrWrapper = {
6565
var errorParsed = parseResponse(request.responseText, headers, responseParams[requestId]);
6666

6767
if (Array.isArray(errorParsed)) {
68+
delete responseParams[requestId];
6869
errorCallback(errorParsed);
6970
break;
7071
}

0 commit comments

Comments
 (0)