@adobe/acc-js-sdk 1.1.1 → 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/client.js CHANGED
@@ -96,65 +96,110 @@ const xtkObjectHandler = {
96
96
  * <code>
97
97
  * result = await client.NLWS.xtkSession.getServerTime();
98
98
  * </code>
99
+ *
100
+ * To get a handler, call the `clientHandler` function and optionally pass a representation.
101
+ * If no representation is passed (undefined), the representation set at the client level
102
+ * will be used, which is the default behavior.
103
+ * To get a proxy with a specific representation, use NLWS.xml or NMWS.json
99
104
  *
100
105
  * @private
101
106
  * @memberof Campaign
102
107
  */
103
- const clientHandler = {
104
- get: function(client, namespace) {
105
- return new Proxy({ client:client, namespace:namespace}, {
106
- get: function(callContext, methodName) {
107
- if (methodName == ".") return callContext;
108
-
109
- // get Schema id from namespace (find first upper case letter)
110
- var schemaId = "";
111
- for (var i=0; i<namespace.length; i++) {
112
- const c = namespace[i];
113
- if (c >='A' && c<='Z') {
114
- schemaId = schemaId + ":" + c.toLowerCase() + namespace.substr(i+1);
115
- break;
108
+ const clientHandler = (representation, headers, pushDownOptions) => {
109
+ return {
110
+ get: function(client, namespace) {
111
+
112
+ // Force XML or JSON representation (NLWS.xml or NLWS.json)
113
+ if (namespace == "xml") return new Proxy(client, clientHandler("xml", headers, pushDownOptions));
114
+ if (namespace == "json") return new Proxy(client, clientHandler("SimpleJson", headers, pushDownOptions));
115
+
116
+ // Override HTTP headers (NLWS.headers({...}))
117
+ // Unlike NLWS.xml or NLWS.json, NLWS.headers returns a function. This function takes key/value
118
+ // pairs of headers, and, when called, returns a proxy object which will remember the headers
119
+ // and be able to pass them to subsequent SOAP call context
120
+ if (namespace == "headers") return (methodHeaders) => {
121
+ // Build of copy of the http headers and append new headers in order to accomodate
122
+ // chained calls, such as NLWS.headers(...).headers(...)
123
+ const newHeaders = {};
124
+ if (headers) for (let h in headers) newHeaders[h] = headers[h];
125
+ if (methodHeaders) for (let h in methodHeaders) newHeaders[h] = methodHeaders[h];
126
+ return new Proxy(client, clientHandler(representation, newHeaders, pushDownOptions));
127
+ };
128
+
129
+ // Pushes down addition options to the SOAP and transport layers
130
+ if (namespace == "pushDown") return (methodPushDownOptions) => {
131
+ // Build of copy of the pushDownOptions in order to accomodate
132
+ // chained calls, such as NLWS.pushDown(...).pushDown(...)
133
+ const newPushDownOptions = {};
134
+ if (pushDownOptions) for (let h in pushDownOptions) newPushDownOptions[h] = pushDownOptions[h];
135
+ if (methodPushDownOptions) for (let h in methodPushDownOptions) newPushDownOptions[h] = methodPushDownOptions[h];
136
+ return new Proxy(client, clientHandler(representation, headers, newPushDownOptions));
137
+ };
138
+
139
+ return new Proxy({ client:client, namespace:namespace}, {
140
+ get: function(callContext, methodName) {
141
+ callContext.representation = representation;
142
+ callContext.headers = callContext.headers || client._connectionParameters._options.extraHttpHeaders;
143
+ callContext.pushDownOptions = {};
144
+ if (headers) {
145
+ for (let h in headers) callContext.headers[h] = headers[h];
116
146
  }
117
- schemaId = schemaId + c;
118
- }
119
- callContext.schemaId = schemaId;
120
-
121
- const caller = function(thisArg, argumentsList) {
122
- const callContext = thisArg["."];
123
- const namespace = callContext.namespace;
124
- const methodNameLC = methodName.toLowerCase();
125
- methodName = methodName.substr(0, 1).toUpperCase() + methodName.substr(1);
126
- if (namespace == "xtkSession" && methodNameLC == "logon")
127
- return callContext.client.logon(argumentsList[0]);
128
- else if (namespace == "xtkSession" && methodNameLC == "logoff")
129
- return callContext.client.logoff();
130
- else if (namespace == "xtkSession" && methodNameLC == "getoption") {
131
- var promise = callContext.client._callMethod(methodName, callContext, argumentsList);
132
- return promise.then(function(optionAndValue) {
133
- const optionName = argumentsList[0];
134
- client._optionCache.put(optionName, optionAndValue);
135
- return optionAndValue;
136
- });
147
+ if (pushDownOptions) {
148
+ for (let h in pushDownOptions) callContext.pushDownOptions[h] = pushDownOptions[h];
149
+ }
150
+
151
+ if (methodName == ".") return callContext;
152
+
153
+ // get Schema id from namespace (find first upper case letter)
154
+ var schemaId = "";
155
+ for (var i=0; i<namespace.length; i++) {
156
+ const c = namespace[i];
157
+ if (c >='A' && c<='Z') {
158
+ schemaId = schemaId + ":" + c.toLowerCase() + namespace.substr(i+1);
159
+ break;
160
+ }
161
+ schemaId = schemaId + c;
137
162
  }
138
- // static method
139
- var result = callContext.client._callMethod(methodName, callContext, argumentsList);
140
- return result;
141
- };
142
-
143
- if (methodName == "create") {
144
- return function(body) {
145
- callContext.object = body;
146
- return new Proxy(callContext, xtkObjectHandler);
163
+ callContext.schemaId = schemaId;
164
+
165
+ const caller = function(thisArg, argumentsList) {
166
+ const callContext = thisArg["."];
167
+ const namespace = callContext.namespace;
168
+ const methodNameLC = methodName.toLowerCase();
169
+ methodName = methodName.substr(0, 1).toUpperCase() + methodName.substr(1);
170
+ if (namespace == "xtkSession" && methodNameLC == "logon")
171
+ return callContext.client.logon(argumentsList[0]);
172
+ else if (namespace == "xtkSession" && methodNameLC == "logoff")
173
+ return callContext.client.logoff();
174
+ else if (namespace == "xtkSession" && methodNameLC == "getoption") {
175
+ var promise = callContext.client._callMethod(methodName, callContext, argumentsList);
176
+ return promise.then(function(optionAndValue) {
177
+ const optionName = argumentsList[0];
178
+ client._optionCache.put(optionName, optionAndValue);
179
+ return optionAndValue;
180
+ });
181
+ }
182
+ // static method
183
+ var result = callContext.client._callMethod(methodName, callContext, argumentsList);
184
+ return result;
147
185
  };
148
- }
149
186
 
150
- return new Proxy(caller, {
151
- apply: function(target, thisArg, argumentsList) {
152
- return target(thisArg, argumentsList);
153
- }
154
- });
155
- }
156
- });
157
- }
187
+ if (methodName == "create") {
188
+ return function(body) {
189
+ callContext.object = body;
190
+ return new Proxy(callContext, xtkObjectHandler);
191
+ };
192
+ }
193
+
194
+ return new Proxy(caller, {
195
+ apply: function(target, thisArg, argumentsList) {
196
+ return target(thisArg, argumentsList);
197
+ }
198
+ });
199
+ }
200
+ });
201
+ }
202
+ };
158
203
  };
159
204
 
160
205
  // ========================================================================================
@@ -236,6 +281,11 @@ class Credentials {
236
281
  * @property {Storage} storage - Overrides the storage interface (i.e. LocalStorage)
237
282
  * @property {function} refreshClient - An async callback function with the SDK client as parameter, which will be called when the ACC session is expired
238
283
  * @property {string} charset - The charset encoding used for http requests. Defaults to UTF-8 since SDK version 1.1.1
284
+ * @property {{ name:string, value:string}} extraHttpHeaders - optional key/value pair of HTTP header (will override any other headers)
285
+ * @property {string} clientApp - optional name/version of the application client of the SDK. This will be passed in HTTP headers for troubleshooting
286
+ * @property {boolean} noSDKHeaders - set to disable "ACC-SDK" HTTP headers
287
+ * @property {boolean} noMethodInURL - Can be set to true to remove the method name from the URL
288
+ * @property {number} timeout - Can be set to change the HTTP call timeout. Value is passed in ms.
239
289
  * @memberOf Campaign
240
290
  */
241
291
 
@@ -257,7 +307,7 @@ class ConnectionParameters {
257
307
  constructor(endpoint, credentials, options) {
258
308
  // this._options will be populated with the data from "options" and with
259
309
  // default values. But the "options" parameter will not be modified
260
- this._options = {};
310
+ this._options = Object.assign({}, options);
261
311
 
262
312
  // Default value
263
313
  if (options === undefined || options === null)
@@ -300,6 +350,13 @@ class ConnectionParameters {
300
350
  this._options._storage = storage;
301
351
  this._options.refreshClient = options.refreshClient;
302
352
  this._options.charset = options.charset === undefined ? "UTF-8": options.charset;
353
+ this._options.extraHttpHeaders = {};
354
+ if (options.extraHttpHeaders) {
355
+ for (let h in options.extraHttpHeaders) this._options.extraHttpHeaders[h] = options.extraHttpHeaders[h];
356
+ }
357
+ this._options.clientApp = options.clientApp;
358
+ this._options.noSDKHeaders = !!options.noSDKHeaders;
359
+ this._options.noMethodInURL = !!options.noMethodInURL;
303
360
  }
304
361
 
305
362
  /**
@@ -477,7 +534,7 @@ class Client {
477
534
  this._entityCache = new XtkEntityCache(this._storage, `${rootKey}.XtkEntityCache`, connectionParameters._options.entityCacheTTL);
478
535
  this._methodCache = new MethodCache(this._storage, `${rootKey}.MethodCache`, connectionParameters._options.methodCacheTTL);
479
536
  this._optionCache = new OptionCache(this._storage, `${rootKey}.OptionCache`, connectionParameters._options.optionCacheTTL);
480
- this.NLWS = new Proxy(this, clientHandler);
537
+ this.NLWS = new Proxy(this, clientHandler());
481
538
 
482
539
  this._transport = connectionParameters._options.transport;
483
540
  this._traceAPICalls = connectionParameters._options.traceAPICalls;
@@ -672,13 +729,16 @@ class Client {
672
729
  * @private
673
730
  * @param {string} urn is the API name space, usually the schema. For instance xtk:session
674
731
  * @param {string} method is the method to call, for instance Logon
732
+ * @param {boolean} internal is a boolean indicating whether the SOAP call is performed by the SDK (internal = true) or on behalf of a user
675
733
  * @return {SOAP.SoapMethodCall} a SoapMethodCall which have been initialized with security tokens... and to which the method
676
734
  * parameters should be set
677
735
  */
678
- _prepareSoapCall(urn, method, internal) {
736
+ _prepareSoapCall(urn, method, internal, extraHttpHeaders, pushDownOptions) {
679
737
  const soapCall = new SoapMethodCall(this._transport, urn, method,
680
738
  this._sessionToken, this._securityToken,
681
- this._getUserAgentString(), this._connectionParameters._options.charset);
739
+ this._getUserAgentString(),
740
+ Object.assign({}, this._connectionParameters._options, pushDownOptions),
741
+ extraHttpHeaders);
682
742
  soapCall.internal = !!internal;
683
743
  return soapCall;
684
744
  }
@@ -692,6 +752,7 @@ class Client {
692
752
  */
693
753
  async _retrySoapCall(soapCall) {
694
754
  soapCall.retry = false;
755
+ soapCall._retryCount = soapCall._retryCount + 1;
695
756
  var newClient = await this._refreshClient(this);
696
757
  soapCall.finalize(newClient._soapEndPoint(), newClient);
697
758
  if (this._traceAPICalls) {
@@ -766,6 +827,18 @@ class Client {
766
827
  this._securityToken = "";
767
828
  const credentials = this._connectionParameters._credentials;
768
829
 
830
+ const sdkVersion = this.sdk.getSDKVersion();
831
+ const version = `${sdkVersion.name} ${sdkVersion.version}`;
832
+ const clientApp = this._connectionParameters._options.clientApp;
833
+ if (!this._connectionParameters._options.noSDKHeaders) {
834
+ this._connectionParameters._options.extraHttpHeaders["ACC-SDK-Version"] = version;
835
+ this._connectionParameters._options.extraHttpHeaders["ACC-SDK-Auth"] = `${credentials._type}`;
836
+ if (clientApp)
837
+ this._connectionParameters._options.extraHttpHeaders["ACC-SDK-Client-App"] = clientApp;
838
+ }
839
+ // See NEO-35259
840
+ this._connectionParameters._options.extraHttpHeaders['X-Query-Source'] = `${version}${clientApp? "," + clientApp : ""}`;
841
+
769
842
  // Clear session token cookie to ensure we're not inheriting an expired cookie. See NEO-26589
770
843
  if (credentials._type != "SecurityToken" && typeof document != "undefined") {
771
844
  document.cookie = '__sessiontoken=;path=/;';
@@ -778,8 +851,8 @@ class Client {
778
851
  that.application = new Application(that);
779
852
  return Promise.resolve();
780
853
  }
781
- else if (credentials._type == "SecurityToken") {
782
- that._sessionInfo = undefined;
854
+ else if (credentials._type == "SecurityToken") {
855
+ that._sessionInfo = undefined;
783
856
  that._installedPackages = {};
784
857
  that._sessionToken = "";
785
858
  that._securityToken = credentials._securityToken;
@@ -787,12 +860,14 @@ class Client {
787
860
  return Promise.resolve();
788
861
  }
789
862
  else if (credentials._type == "UserPassword" || credentials._type == "BearerToken") {
790
- const soapCall = that._prepareSoapCall("xtk:session", credentials._type === "UserPassword" ? "Logon" : "BearerTokenLogon");
863
+ const soapCall = that._prepareSoapCall("xtk:session", credentials._type === "UserPassword" ? "Logon" : "BearerTokenLogon", false, this._connectionParameters._options.extraHttpHeaders);
791
864
  // No retry for logon SOAP methods
792
865
  soapCall.retry = false;
793
866
  if (credentials._type == "UserPassword") {
794
867
  const user = credentials._getUser();
795
868
  const password = credentials._getPassword();
869
+ if (!this._connectionParameters._options.noSDKHeaders)
870
+ this._connectionParameters._options.extraHttpHeaders["ACC-SDK-Auth"] = `${credentials._type} ${user}`;
796
871
  soapCall.writeString("login", user);
797
872
  soapCall.writeString("password", password);
798
873
  var parameters = null;
@@ -860,7 +935,7 @@ class Client {
860
935
  if (!that.isLogged()) return;
861
936
  const credentials = this._connectionParameters._credentials;
862
937
  if (credentials._type != "SessionToken" && credentials._type != "AnonymousUser") {
863
- var soapCall = that._prepareSoapCall("xtk:session", "Logoff");
938
+ var soapCall = that._prepareSoapCall("xtk:session", "Logoff", false, this._connectionParameters._options.extraHttpHeaders);
864
939
  return this._makeSoapCall(soapCall).then(function() {
865
940
  that._sessionToken = "";
866
941
  that._securityToken = "";
@@ -965,7 +1040,7 @@ class Client {
965
1040
  * @returns {boolean} a boolean indicating if the package is installed or not
966
1041
  */
967
1042
  hasPackage(packageId, optionalName) {
968
- if (optionalName === undefined)
1043
+ if (optionalName !== undefined)
969
1044
  packageId = `${packageId}:${optionalName}`;
970
1045
  if (!this.isLogged())
971
1046
  throw CampaignException.NOT_LOGGED_IN(undefined, `Cannot call hasPackage: session not connected`);
@@ -979,7 +1054,7 @@ class Client {
979
1054
  * @private
980
1055
  * @deprecated since version 1.0.0
981
1056
  */
982
- async _getSecretKeyCipher() {
1057
+ async _getSecretKeyCipher() {
983
1058
  var that = this;
984
1059
  if (this._secretKeyCipher) return this._secretKeyCipher;
985
1060
  return that.getOption("XtkSecretKey").then(function(secretKey) {
@@ -1002,7 +1077,7 @@ class Client {
1002
1077
  */
1003
1078
  async getEntityIfMoreRecent(entityType, fullName, representation, internal) {
1004
1079
  const that = this;
1005
- const soapCall = this._prepareSoapCall("xtk:persist", "GetEntityIfMoreRecent", internal);
1080
+ const soapCall = this._prepareSoapCall("xtk:persist", "GetEntityIfMoreRecent", internal, this._connectionParameters._options.extraHttpHeaders);
1006
1081
  soapCall.writeString("pk", entityType + "|" + fullName);
1007
1082
  soapCall.writeString("md5", "");
1008
1083
  soapCall.writeBoolean("mustExist", false);
@@ -1105,7 +1180,7 @@ class Client {
1105
1180
  // console.log(method.toXMLString());
1106
1181
 
1107
1182
  var urn = that._methodCache.getSoapUrn(schemaId, methodName);
1108
- var soapCall = that._prepareSoapCall(urn, methodName);
1183
+ var soapCall = that._prepareSoapCall(urn, methodName, false, callContext.headers, callContext.pushDownOptions);
1109
1184
 
1110
1185
  // If method is called with one parameter which is a function, then we assume it's a hook: the function will return
1111
1186
  // the actual list of parameters
@@ -1122,7 +1197,7 @@ class Client {
1122
1197
  throw CampaignException.SOAP_UNKNOWN_METHOD(schemaId, methodName, `Cannot call non-static method '${methodName}' of schema '${schemaId}' : no object was specified`);
1123
1198
 
1124
1199
  const rootName = schemaId.substr(schemaId.indexOf(':') + 1);
1125
- object = that._fromRepresentation(rootName, object);
1200
+ object = that._fromRepresentation(rootName, object, callContext.representation);
1126
1201
  soapCall.writeDocument("document", object);
1127
1202
  }
1128
1203
 
@@ -1146,6 +1221,8 @@ class Client {
1146
1221
  soapCall.writeByte(paramName, XtkCaster.asByte(paramValue));
1147
1222
  else if (type == "short")
1148
1223
  soapCall.writeShort(paramName, XtkCaster.asShort(paramValue));
1224
+ else if (type == "int")
1225
+ soapCall.writeLong(paramName, XtkCaster.asLong(paramValue));
1149
1226
  else if (type == "long")
1150
1227
  soapCall.writeLong(paramName, XtkCaster.asLong(paramValue));
1151
1228
  else if (type == "int64")
@@ -1170,11 +1247,12 @@ class Client {
1170
1247
  const index = xtkschema.indexOf(":");
1171
1248
  docName = xtkschema.substr(index+1);
1172
1249
  }
1173
- var xmlValue = that._fromRepresentation(docName, paramValue);
1250
+ if (!docName) docName = paramName; // Use te parameter name as the XML root element
1251
+ var xmlValue = that._fromRepresentation(docName, paramValue, callContext.representation);
1174
1252
  if (type == "DOMDocument")
1175
1253
  soapCall.writeDocument(paramName, xmlValue);
1176
1254
  else
1177
- soapCall.writeElement(paramName, xmlValue.documentElement);
1255
+ soapCall.writeElement(paramName, xmlValue);
1178
1256
  }
1179
1257
  else
1180
1258
  throw CampaignException.BAD_SOAP_PARAMETER(soapCall, paramName, paramValue, `Unsupported parameter type '${type}' for parameter '${paramName}' of method '${methodName}' of schema '${schemaId}`);
@@ -1189,7 +1267,7 @@ class Client {
1189
1267
  // the method is called. This is the new version of the object (in XML form)
1190
1268
  const entity = soapCall.getEntity();
1191
1269
  if (entity) {
1192
- callContext.object = that._toRepresentation(entity);
1270
+ callContext.object = that._toRepresentation(entity, callContext.representation);
1193
1271
  }
1194
1272
  }
1195
1273
 
@@ -1220,7 +1298,7 @@ class Client {
1220
1298
  returnValue = soapCall.getNextDate();
1221
1299
  else if (type == "DOMDocument") {
1222
1300
  returnValue = soapCall.getNextDocument();
1223
- returnValue = that._toRepresentation(returnValue);
1301
+ returnValue = that._toRepresentation(returnValue, callContext.representation);
1224
1302
  if (schemaId === "xtk:queryDef" && methodName === "ExecuteQuery" && paramName === "output") {
1225
1303
  // https://github.com/adobe/acc-js-sdk/issues/3
1226
1304
  // Check if query operation is "getIfExists". The "object" variable at this point
@@ -1240,7 +1318,7 @@ class Client {
1240
1318
  }
1241
1319
  else if (type == "DOMElement") {
1242
1320
  returnValue = soapCall.getNextElement();
1243
- returnValue = that._toRepresentation(returnValue);
1321
+ returnValue = that._toRepresentation(returnValue, callContext.representation);
1244
1322
  }
1245
1323
  else {
1246
1324
  // type can reference a schema element. The naming convension is that the type name
@@ -1254,7 +1332,7 @@ class Client {
1254
1332
  if (element.getAttribute("name") == shortTypeName) {
1255
1333
  // Type found in schema: Process as a DOM element
1256
1334
  returnValue = soapCall.getNextElement();
1257
- returnValue = that._toRepresentation(returnValue);
1335
+ returnValue = that._toRepresentation(returnValue, callContext.representation);
1258
1336
  break;
1259
1337
  }
1260
1338
  element = DomUtil.getNextSiblingElement(element, "element");
@@ -1309,8 +1387,11 @@ class Client {
1309
1387
  */
1310
1388
  async test() {
1311
1389
  const request = {
1312
- url: `${this._connectionParameters._endpoint}/r/test`
1390
+ url: `${this._connectionParameters._endpoint}/r/test`,
1391
+ headers: {}
1313
1392
  };
1393
+ for (let h in this._connectionParameters._options.extraHttpHeaders)
1394
+ request.headers[h] = this._connectionParameters._options.extraHttpHeaders[h];
1314
1395
  const body = await this._makeHttpCall(request);
1315
1396
  const xml = DomUtil.parse(body);
1316
1397
  const result = this._toRepresentation(xml);
@@ -1330,6 +1411,8 @@ class Client {
1330
1411
  'Cookie': '__sessiontoken=' + this._sessionToken
1331
1412
  }
1332
1413
  };
1414
+ for (let h in this._connectionParameters._options.extraHttpHeaders)
1415
+ request.headers[h] = this._connectionParameters._options.extraHttpHeaders[h];
1333
1416
  const body = await this._makeHttpCall(request);
1334
1417
  const lines = body.split('\n');
1335
1418
  const doc = DomUtil.newDocument("ping");
@@ -1359,6 +1442,8 @@ class Client {
1359
1442
  'Cookie': '__sessiontoken=' + this._sessionToken
1360
1443
  }
1361
1444
  };
1445
+ for (let h in this._connectionParameters._options.extraHttpHeaders)
1446
+ request.headers[h] = this._connectionParameters._options.extraHttpHeaders[h];
1362
1447
  const body = await this._makeHttpCall(request);
1363
1448
  const lines = body.split('\n');
1364
1449
  const doc = DomUtil.newDocument("ping");
package/src/index.js CHANGED
@@ -23,7 +23,8 @@ const DomUtil = require('./domUtil.js').DomUtil;
23
23
  const XtkCaster = require('./xtkCaster.js').XtkCaster;
24
24
  const { Client, Credentials, ConnectionParameters } = require('./client.js');
25
25
  const request = require('./transport.js').request;
26
- const { TestUtil } = require('./testUtil');
26
+ const { TestUtil } = require('./testUtil.js');
27
+ const { HttpError } = require('./transport.js');
27
28
 
28
29
  /**
29
30
  * Get/Set the transport function (defaults to Axios). This function is used for testing / mocking the transport layer.
@@ -210,6 +211,7 @@ sdk.XtkCaster = XtkCaster;
210
211
  sdk.Credentials = Credentials;
211
212
  sdk.DomUtil = DomUtil;
212
213
  sdk.ConnectionParameters = ConnectionParameters;
214
+ sdk.HttpError = HttpError;
213
215
 
214
216
  // Public exports
215
217
  module.exports = sdk;
package/src/soap.js CHANGED
@@ -78,13 +78,15 @@ const NS_XSD = "http://www.w3.org/2001/XMLSchema";
78
78
  * @param {string} sessionToken Campaign session token
79
79
  * @param {string} securityToken Campaign security token
80
80
  * @param {string} userAgentString The user agent string to use for HTTP requests
81
- * @param {string} charset The charset encoding used for http requests, usually UTF-8
81
+ * @param {string} pushDownOptions Options to push down to the request (comes from connectionParameters._options)
82
+ * @param {{ name:string, value:string}} extraHttpHeaders key/value pair of HTTP header (will override any other headers)
82
83
  * @memberof SOAP
83
84
  */
84
85
  class SoapMethodCall {
85
86
 
86
- constructor(transport, urn, methodName, sessionToken, securityToken, userAgentString, charset) {
87
+ constructor(transport, urn, methodName, sessionToken, securityToken, userAgentString, pushDownOptions, extraHttpHeaders) {
87
88
  this.request = undefined; // The HTTP request (object litteral passed to the transport layer)
89
+ this.requestOptions = undefined;
88
90
  this.response = undefined; // The HTTP response object (in case of success)
89
91
 
90
92
  // Current URN and method (for error reporting)
@@ -96,11 +98,14 @@ class SoapMethodCall {
96
98
  this.internal = false;
97
99
  // Enable soap retry
98
100
  this.retry = true;
101
+ this._retryCount = 0;
99
102
 
100
103
  this._sessionToken = sessionToken || "";
101
104
  this._securityToken = securityToken || "";
102
105
  this._userAgentString = userAgentString;
103
- this._charset = charset || "";
106
+ this._pushDownOptions = pushDownOptions || {};
107
+ this._charset = this._pushDownOptions.charset || '';
108
+ this._extraHttpHeaders = extraHttpHeaders || {};
104
109
 
105
110
  // THe SOAP call being built
106
111
  this._doc = undefined; // XML document for SOAP call
@@ -213,7 +218,7 @@ class SoapMethodCall {
213
218
  * @param {string} tag the parameter name
214
219
  * @param {*} value the parameter value, which will be casted to a int32 according to xtk rules
215
220
  */
216
- writeLong(tag, value) {
221
+ writeLong(tag, value) {
217
222
  value = XtkCaster.asLong(value);
218
223
  this._addNode(tag, "xsd:int", XtkCaster.asString(value), SOAP_ENCODING_NATIVE);
219
224
  }
@@ -286,6 +291,7 @@ class SoapMethodCall {
286
291
  writeElement(tag, element) {
287
292
  const node = this._addNode(tag, "ns:Element", null, SOAP_ENCODING_XML);
288
293
  if (element !== null && element !== undefined) {
294
+ if (element.nodeType === 9) element = element.documentElement;
289
295
  const child = this._doc.importNode(element, true);
290
296
  node.appendChild(child);
291
297
  }
@@ -299,7 +305,8 @@ class SoapMethodCall {
299
305
  writeDocument(tag, document) {
300
306
  const node = this._addNode(tag, "", null, SOAP_ENCODING_XML);
301
307
  if (document !== null && document !== undefined) {
302
- const child = this._doc.importNode(document.documentElement, true);
308
+ const element = document.nodeType === 1 ? document : document.documentElement;
309
+ const child = this._doc.importNode(element, true);
303
310
  node.appendChild(child);
304
311
  }
305
312
  }
@@ -513,23 +520,40 @@ class SoapMethodCall {
513
520
  * @param {string} url is the Campaign SOAP endpoint (soaprouter.jsp)
514
521
  * @returns {Object} an options object describing the HTTP request, with cookies, headers and body
515
522
  */
516
- _createHTTPRequest(url) {
523
+ _createHTTPRequest(url, requestOptions) {
524
+
525
+ const headers = {
526
+ 'Content-type': `application/soap+xml${this._charset ? ";charset=" + this._charset : ""}`,
527
+ 'SoapAction': `${this.urn}#${this.methodName}`,
528
+ 'X-Security-Token': this._securityToken
529
+ };
530
+
531
+ // Add HTTP headers specific to the SOAP call for better tracing/troubleshooting
532
+ if (this._extraHttpHeaders && this._extraHttpHeaders['ACC-SDK-Version']) {
533
+ // "this.retry" means that the call can be retried, not that it is being retried. The HTTP header howerver, indicates that this
534
+ // is actually a retry of a previously failed call (expired token)
535
+ if (this._retryCount > 0) headers["ACC-SDK-Call-RetryCount"] = `${this._retryCount}`;
536
+ if (this.internal) headers["ACC-SDK-Call-Internal"] = "1";
537
+ }
517
538
 
518
- const options = {
539
+ const request = {
519
540
  url: url,
520
541
  method: 'POST',
521
- headers: {
522
- 'Content-type': `application/soap+xml${this._charset ? ";charset=" + this._charset : ""}`,
523
- 'SoapAction': `${this.urn}#${this.methodName}`,
524
- 'X-Security-Token': this._securityToken
525
- },
542
+ headers: headers,
526
543
  data: DomUtil.toXMLString(this._doc)
527
544
  };
528
545
  if (this._sessionToken)
529
- options.headers.Cookie = '__sessiontoken=' + this._sessionToken;
546
+ request.headers.Cookie = '__sessiontoken=' + this._sessionToken;
530
547
  if (this._userAgentString)
531
- options.headers['User-Agent'] = this._userAgentString;
532
- return options;
548
+ request.headers['User-Agent'] = this._userAgentString;
549
+
550
+ // Override http headers with custom headers
551
+ for (let h in this._extraHttpHeaders) {
552
+ request.headers[h] = this._extraHttpHeaders[h];
553
+ }
554
+
555
+ const extraOptions = Object.assign({}, this._pushDownOptions, requestOptions);
556
+ return [ request, extraOptions ];
533
557
  }
534
558
 
535
559
  /**
@@ -575,9 +599,11 @@ class SoapMethodCall {
575
599
  sessionTokenElem.textContent = this._sessionToken;
576
600
  this._method.prepend(sessionTokenElem);
577
601
  }
578
- const options = this._createHTTPRequest(url);
602
+ const noMethodInURL = !!this._pushDownOptions.noMethodInURL;
603
+ const actualUrl = noMethodInURL ? url : `${url}?${this.urn}#${this.methodName}`;
604
+
579
605
  // Prepare request and empty response objects
580
- this.request = options;
606
+ [this.request, this.requestOptions] = this._createHTTPRequest(actualUrl);
581
607
  this.response = undefined;
582
608
  }
583
609
 
@@ -590,7 +616,7 @@ class SoapMethodCall {
590
616
  */
591
617
  async execute() {
592
618
  const that = this;
593
- const promise = this._transport(this.request);
619
+ const promise = this._transport(this.request, this.requestOptions);
594
620
  return promise.then(function(body) {
595
621
  if (body.indexOf(`XSV-350008`) != -1)
596
622
  throw CampaignException.SESSION_EXPIRED();
package/src/testUtil.js CHANGED
@@ -1,4 +1,4 @@
1
- const { DomUtil } = require("./domUtil");
1
+ const { DomUtil } = require("./domUtil.js");
2
2
 
3
3
  /*
4
4
  Copyright 2022 Adobe. All rights reserved.
@@ -14,7 +14,7 @@ governing permissions and limitations under the License.
14
14
  (function() {
15
15
  "use strict";
16
16
 
17
- const { newSchema } = require("./application");
17
+ const { newSchema } = require("./application.js");
18
18
 
19
19
  /**********************************************************************************
20
20
  *
package/src/transport.js CHANGED
@@ -14,13 +14,27 @@ governing permissions and limitations under the License.
14
14
 
15
15
  const { Util } = require('./util.js');
16
16
 
17
+ /**
18
+ * @memberof Utils
19
+ * @class
20
+ * @constructor
21
+ */
17
22
  class HttpError {
23
+ /* Encapsulates an error from an HTTP call
24
+ * @param {string|number} statusCode - The Http status code
25
+ * @param {string?} statusText - The Http status text corresponding to the error code
26
+ * @param {any?} data - The payload of the HTTP response, which usually contains details about the error
27
+ */
18
28
  constructor(statusCode, statusText, data) {
19
29
  this.statusCode = statusCode;
20
30
  this.statusText = statusText || "";
21
31
  this.data = data;
22
32
  }
23
33
 
34
+ /**
35
+ * Returns a short description of the error
36
+ * @returns {string} a short descrption of the error
37
+ */
24
38
  toString() {
25
39
  return `${this.statusCode}${this.statusText ? " " + this.statusText : ""}`;
26
40
  }
@@ -52,13 +66,14 @@ if (!Util.isBrowser()) {
52
66
  * - request
53
67
  */
54
68
 
55
- const request = (options) => {
69
+ const request = (options, requestOptions) => {
70
+ requestOptions = requestOptions || {};
56
71
  const request = {
57
72
  method: options.method || "GET",
58
73
  url: options.url,
59
74
  headers: options.headers,
60
75
  data: options.data,
61
- timeout: 5000,
76
+ timeout: requestOptions.timeout || 5000,
62
77
  };
63
78
  return axios(request)
64
79
  .then((response) => {
@@ -2108,6 +2108,7 @@ describe('Application', () => {
2108
2108
  const application = client.application;
2109
2109
  expect(application).not.toBeNull();
2110
2110
  expect(application.buildNumber).toBeUndefined();
2111
+ expect(application.version).toBeUndefined();
2111
2112
  expect(application.instanceName).toBeUndefined();
2112
2113
  expect(application.operator).toBeUndefined();
2113
2114
  expect(application.package).toBeUndefined();
@@ -2145,4 +2146,14 @@ describe('Application', () => {
2145
2146
  expect(schema2).toBeNull();
2146
2147
  });
2147
2148
  });
2149
+
2150
+ describe("Version", () => {
2151
+ it("Should get proper version information", async () => {
2152
+ const client = await Mock.makeClient();
2153
+ client._transport.mockReturnValueOnce(Mock.LOGON_RESPONSE);
2154
+ await client.NLWS.xtkSession.logon();
2155
+ expect(client.application.buildNumber).toBe('9219');
2156
+ expect(client.application.version).toBe('6.7.0');
2157
+ })
2158
+ })
2148
2159
  });