@adobe/acc-js-sdk 1.1.0 → 1.1.3

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.
@@ -16,7 +16,7 @@ jobs:
16
16
  node-version: 10.0.0
17
17
  - name: Publish if version has been updated
18
18
  #uses: pascalgn/npm-publish-action@06e0830ea83eea10ed4a62654eeaedafb8bf50fc
19
- uses: mkiki/npm-publish-action@c4315ef5790b7bcec2cbb75b34e37681a409d78d
19
+ uses: mkiki/npm-publish-action@52879298d00c0a02781e1f02c9e917e01cff1bc7
20
20
  with: # All of theses inputs are optional
21
21
  tag_name: "v%s"
22
22
  tag_message: "v%s"
package/CHANGELOG.md CHANGED
@@ -5,6 +5,27 @@ This is a node.js SDK for Campaign API. It exposes the Campaign API exactly like
5
5
 
6
6
  # Changelog
7
7
 
8
+ ## Version 1.1.3
9
+ _2022/05/30_
10
+
11
+ * Fix a bug in client.hasPackage which was returning an incorrect result when passed a single parameter (it would always return false). Fixed the corresponding unit test too.
12
+ * Fix a bug causing API calls having a input parameter of type "int" to fail. Usually the type is described as "long", but sometimes "int" is used instead, such as, for instance, in the nms:extAccount#UpdateMCSynchWkf method.
13
+ * When using XML representations and DOMDocument method parameter type, the SDK expects to be passed an actual DOM document. Now it supports being passed a DOM element too. This is a common case when using the nms:delivery#createFromModel API followed by a xtk:session#Write API call.
14
+ * Avoid the error 'Cannot transform entity to xml because no XML root name was given' by using SOAP method parameter name as the default for XML document root when no other root is available
15
+ * Document how to set the password of an external account
16
+ * By default, SDK will send additional HTTP headers to help troubleshooting and usage tracking
17
+ * Add the ability to pass extra HTTP headers to API calls, either globally (to all HTTP headers), or locally, i.e. for a specific method
18
+
19
+ ## Version 1.1.2
20
+ _2022/03/22_
21
+
22
+ * Add support for choosing the representation (XML or JSON) at the method level using NLWS.xml or NLWS.json.
23
+
24
+ ## Version 1.1.1
25
+ _2022/03/10_
26
+
27
+ * Fixed an issue with encoding: make the default charset encoding to be UTF-8 (https://github.com/adobe/acc-js-sdk/issues/26)
28
+
8
29
  ## Version 1.1.0
9
30
  _2022/03/05_
10
31
 
package/README.md CHANGED
@@ -129,7 +129,10 @@ transport|axios|Overrides the transport layer
129
129
  noStorage|false|De-activate using of local storage
130
130
  storage|localStorage|Overrides the local storage for caches
131
131
  refreshClient|undefined|Async callback to run when the session token is expired
132
-
132
+ charset|UTF-8|The charset encoding used for http requests. In version 1.1.1 and above, the default will be UTF-8. It's possible to override (including setting an empty character set) with this option.
133
+ extraHttpHeaders|[string]:string|An optional dictionary (key/value pairs) of extra HTTP headers to pass to all API calls.
134
+ clientApp|string|An optional string describing the name and version of the SDK client application. It will be passed to the server in the ACC-SDK-Client-App HTTP header
135
+ noSDKHeaders|boolean|Can be set to disable passing ACC-SDK-* HTTP headers to the server
133
136
  ```js
134
137
  const connectionParameters = sdk.ConnectionParameters.ofUserAndPassword(
135
138
  "https://myInstance.campaign.adobe.com",
@@ -303,6 +306,15 @@ const queryDef = {
303
306
  };
304
307
  ```
305
308
 
309
+ ## Method-level representation
310
+
311
+ The client object is created with a default representation which is used for all API calls in the context of this client. Since version 1.1.2, it is also possible to set the representation at the method level, i.e. use a particular representation for a particular API call.
312
+
313
+ * `client.NLWS`: use the default representation set at the client level
314
+ * `client.NLWS.xml`: use the XML reresentation
315
+ * `client.NLWS.json`: use the SimpleJson representation
316
+
317
+
306
318
  ## SimpleJson format
307
319
  The Simple JSON format works like this:
308
320
 
@@ -323,7 +335,7 @@ XML elements are mapped to JSON objects
323
335
  * XML: `<root><item id=1/></root>`
324
336
  * JSON: `{ item: { id:1 } }`
325
337
 
326
- If the parent element tag ends with `-collecion` children are always an array, even if there are no children, or if there is just one child. The rationale is that XML/JSON conversion is ambigous : XML can have multiple elements with the same tag and when there's only one such element, it's not possible to determine if it should be represented as a JSON object or JSON array unless we have additional metadata.
338
+ If the parent element tag ends with `-collection` children are always an array, even if there are no children, or if there is just one child. The rationale is that XML/JSON conversion is ambigous : XML can have multiple elements with the same tag and when there's only one such element, it's not possible to determine if it should be represented as a JSON object or JSON array unless we have additional metadata.
327
339
 
328
340
  * XML: `<root-collection><item id=1/></root>`
329
341
  * JSON: `{ item: [ { id:1 } ] }`
@@ -566,6 +578,49 @@ const document = DomUtil.fromJSON(json, "BadgerFish");
566
578
  const json = DomUtil.toJSON(documentOrElement, "BadgerFish");
567
579
  ```
568
580
 
581
+ ## Passing XML parameters
582
+ Many Campaign APIs take arguments which are DOM documents or DOM elements. For example, the nms:delivery#DeployTriggerMessages first argument is a DOMElement which is supposed to be a `<where>` clause used as a condition to select Message Center deliveries to publish.
583
+
584
+ ```xml
585
+ <method name="DeployTriggerMessages" static="true">
586
+ <parameters>
587
+ <param inout="in" name="deliveries" type="DOMElement"/>
588
+ <param inout="in" name="localPublish" type="boolean"/>
589
+ </parameters>
590
+ </method>
591
+ ```
592
+
593
+ For example, one would want to use the following condition to republish a particular delivery
594
+
595
+ ```js
596
+ await client.NLWS.nmsDelivery.DeployTriggerMessages({
597
+ condition: [ {
598
+ expr: "@internalName='DM23'"
599
+ }]
600
+ }, false);
601
+ ```
602
+
603
+ The JSON object corresponds to the following XML
604
+ ```xml
605
+ <where>
606
+ <condition expr="@internalName='DM23'"/>
607
+ </where>
608
+ ```
609
+
610
+ Note that in XML, unlike JSON, the root element `<where>` is explicitely named "where". When converting JSON to XML, there is no way for the SDK to know which tag name to used for the root XML element. The SDK contains some code to set it for the most common situation, but will rely on the user to specify, when necessary, the name of the root elment. This can be done using the `xtkschema` (all case insensitive) attribute as follows:
611
+ ```js
612
+ await client.NLWS.nmsDelivery.DeployTriggerMessages({
613
+ xtkschema: 'xtk:where',
614
+ condition: [ {
615
+ expr: "@internalName='DM23'"
616
+ }]
617
+ }, false);
618
+ ```
619
+
620
+ When the `xtkschema` attribute is set, the part after the colon (i.e. "where" in this example) will be used as the root element, effectively generating the right XML.
621
+
622
+ In our example, the `DeployTriggerMessages` will work properly regardless of the XML root of its `deliveries` parameter, so it's not needed to actually set the `xtkschema` attribute, but it's a best practice to do so, because some APIs will actually depend on receiving the right tag name.
623
+
569
624
  ## Error Management
570
625
 
571
626
  If an API call fails (SOAP fault or HTTP error), a `CampaignException` object is thrown. This object contains the following attributes
@@ -644,6 +699,64 @@ const password = cipher.decryptPassword(encryptedPassword);
644
699
 
645
700
  > **warning** This function is deprecated in version 1.0.0 of the SDK because it may break as we deploy Vault.
646
701
 
702
+ In order to set the password of an external account, you can use the `encryptPassword` API as follow
703
+
704
+ ```js
705
+ const password = await this._client.NLWS.xtkSession.encryptPassword('password');
706
+ const account = {
707
+ xtkschema: "nms:extAccount",
708
+ name: name,
709
+ account: 'admin',
710
+ server: server,
711
+ password: password,
712
+ };
713
+ await this._client.NLWS.xtkSession.Write(account);
714
+ ```
715
+
716
+
717
+ ## HTTP Headers
718
+
719
+ ### Out-of-the-box headers
720
+ In version 1.1.3 and above, the SDK will pass additional HTTP headers automatically
721
+
722
+ Header | Description
723
+ -------|------------
724
+ SOAPAction| name of the schema and SOAP method (ex: xtk:query#ExecuteQuery)
725
+ ACC-SDK-Version| Version of the SDK making the scores
726
+ ACC-SDK-Auth| Authentification type and ACC user
727
+ ACC-SDK-Client-App| Name of the application calling the client SDK
728
+ ACC-SDK-Call-RetryCount| In case an API call is retried, indicates the number of retries
729
+ ACC-SDK-Call-Internal| Indicates that an API call is performed byt the SDK for its own purpose
730
+
731
+ The `ACC-SDK` headers can be removed using the connection parameter `noSDKHeaders`.
732
+
733
+ ### Custom HTTP headers
734
+ In version 1.1.3 and above, it is possible to pass additional HTTP headers or override HTTP headers set by the SDK. This can be done globally (i.e. for all API calls), or locally, i.e. just for a particular call, or both.
735
+
736
+ Http headers are passed through an object whose keys represent the header name and values the corresponding header value. Nothing particular is done in term of case sensitivity, headers will be passed as passed.
737
+
738
+ To set global HTTP headers for all API calls of a client, pass an http headers array in the connection parameters
739
+ ```js
740
+ const connectionParameters = sdk.ConnectionParameters.ofUserAndPassword(
741
+ "https://myInstance.campaign.adobe.com",
742
+ "admin", "admin",
743
+ { extraHttpHeaders: {
744
+ "X-ACC-JS-SDK-LBSAFE": "1",
745
+ "X-ACC-WEBUI-VERSION: "1.2"
746
+ } });
747
+ ```
748
+
749
+ Subsequent API calls will have the corresponding headers set.
750
+
751
+ To set more HTTP headers for a particular API call, use the "headers" method of the NLWS object.
752
+
753
+ ```js
754
+ const query = client.NLWS
755
+ .headers({'X-Test': 'hello'})
756
+ .xtkQueryDef.create(queryDef);
757
+ await query.executeQuery();
758
+ ```
759
+
647
760
 
648
761
 
649
762
  # Samples
@@ -1134,6 +1247,17 @@ const folder = {
1134
1247
  await NLWS.xtkSession.write(folder);
1135
1248
  ````
1136
1249
 
1250
+ Some objects, such as deliveries are created from templates. The `createFromModel` API is preferred in this case. Given a template name, and a patch object, it will return an object created from the template and the patch, applying all sort of business rules and default values. This object can be inserted using a writer.
1251
+
1252
+ In this example, an email delivery is created from the "mail" delivery template and it's label is set to "Hello".
1253
+
1254
+ Note the xtkschema attribute in the second parameter of the `createFromModel` API call which is needed for the SDK to perform the proper JSON to XML transformation.
1255
+
1256
+ ```js
1257
+ const mail = await client.NLWS.nmsDelivery.createFromModel('mail', { xtkschema:'nms:delivery', label:'Hello'});
1258
+ await client.NLWS.xtkSession.write(mail);
1259
+ ````
1260
+
1137
1261
  # Workflow API
1138
1262
 
1139
1263
  Start and stop wotkflows, passing either an id or workflow internal name
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@adobe/acc-js-sdk",
3
- "version": "1.1.0",
3
+ "version": "1.1.3",
4
4
  "lockfileVersion": 2,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@adobe/acc-js-sdk",
9
- "version": "1.1.0",
9
+ "version": "1.1.3",
10
10
  "license": "ISC",
11
11
  "dependencies": {
12
12
  "axios": "^0.25.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/acc-js-sdk",
3
- "version": "1.1.0",
3
+ "version": "1.1.3",
4
4
  "description": "ACC Javascript SDK",
5
5
  "main": "src/index.js",
6
6
  "homepage": "https://github.com/adobe/acc-js-sdk#readme",
package/samples/utils.js CHANGED
@@ -47,7 +47,9 @@ async function run(main) {
47
47
  */
48
48
  async function logon(callback) {
49
49
  var main = async () => {
50
- const connectionParameters = sdk.ConnectionParameters.ofUserAndPassword(url, user, password);
50
+ const connectionParameters = sdk.ConnectionParameters.ofUserAndPassword(url, user, password, {
51
+ clientApp: 'acc-js-sdk sample'
52
+ });
51
53
  const client = await sdk.init(connectionParameters);
52
54
  const NLWS = client.NLWS;
53
55
 
package/src/client.js CHANGED
@@ -96,65 +96,96 @@ 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) => {
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));
114
+ if (namespace == "json") return new Proxy(client, clientHandler("SimpleJson", headers));
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));
127
+ };
128
+
129
+ return new Proxy({ client:client, namespace:namespace}, {
130
+ get: function(callContext, methodName) {
131
+ callContext.representation = representation;
132
+ callContext.headers = callContext.headers || client._connectionParameters._options.extraHttpHeaders;
133
+ if (headers) {
134
+ for (let h in headers) callContext.headers[h] = headers[h];
116
135
  }
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
- });
136
+
137
+ if (methodName == ".") return callContext;
138
+
139
+ // get Schema id from namespace (find first upper case letter)
140
+ var schemaId = "";
141
+ for (var i=0; i<namespace.length; i++) {
142
+ const c = namespace[i];
143
+ if (c >='A' && c<='Z') {
144
+ schemaId = schemaId + ":" + c.toLowerCase() + namespace.substr(i+1);
145
+ break;
146
+ }
147
+ schemaId = schemaId + c;
137
148
  }
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);
149
+ callContext.schemaId = schemaId;
150
+
151
+ const caller = function(thisArg, argumentsList) {
152
+ const callContext = thisArg["."];
153
+ const namespace = callContext.namespace;
154
+ const methodNameLC = methodName.toLowerCase();
155
+ methodName = methodName.substr(0, 1).toUpperCase() + methodName.substr(1);
156
+ if (namespace == "xtkSession" && methodNameLC == "logon")
157
+ return callContext.client.logon(argumentsList[0]);
158
+ else if (namespace == "xtkSession" && methodNameLC == "logoff")
159
+ return callContext.client.logoff();
160
+ else if (namespace == "xtkSession" && methodNameLC == "getoption") {
161
+ var promise = callContext.client._callMethod(methodName, callContext, argumentsList);
162
+ return promise.then(function(optionAndValue) {
163
+ const optionName = argumentsList[0];
164
+ client._optionCache.put(optionName, optionAndValue);
165
+ return optionAndValue;
166
+ });
167
+ }
168
+ // static method
169
+ var result = callContext.client._callMethod(methodName, callContext, argumentsList);
170
+ return result;
147
171
  };
148
- }
149
172
 
150
- return new Proxy(caller, {
151
- apply: function(target, thisArg, argumentsList) {
152
- return target(thisArg, argumentsList);
153
- }
154
- });
155
- }
156
- });
157
- }
173
+ if (methodName == "create") {
174
+ return function(body) {
175
+ callContext.object = body;
176
+ return new Proxy(callContext, xtkObjectHandler);
177
+ };
178
+ }
179
+
180
+ return new Proxy(caller, {
181
+ apply: function(target, thisArg, argumentsList) {
182
+ return target(thisArg, argumentsList);
183
+ }
184
+ });
185
+ }
186
+ });
187
+ }
188
+ };
158
189
  };
159
190
 
160
191
  // ========================================================================================
@@ -234,6 +265,11 @@ class Credentials {
234
265
  * @property {Utils.Transport} transport - Overrides the transport (i.e. HTTP layer)
235
266
  * @property {boolean} noStorage - De-activate using of local storage. By default, and in addition to in-memory cache, entities, methods, and options are also persisted in local storage if there is one.
236
267
  * @property {Storage} storage - Overrides the storage interface (i.e. LocalStorage)
268
+ * @property {function} refreshClient - An async callback function with the SDK client as parameter, which will be called when the ACC session is expired
269
+ * @property {string} charset - The charset encoding used for http requests. Defaults to UTF-8 since SDK version 1.1.1
270
+ * @property {{ name:string, value:string}} extraHttpHeaders - optional key/value pair of HTTP header (will override any other headers)
271
+ * @property {string} clientApp - optional name/version of the application client of the SDK. This will be passed in HTTP headers for troubleshooting
272
+ * @property {boolean} noSDKHeaders - set to disable "ACC-SDK" HTTP headers
237
273
  * @memberOf Campaign
238
274
  */
239
275
 
@@ -297,6 +333,13 @@ class ConnectionParameters {
297
333
  }
298
334
  this._options._storage = storage;
299
335
  this._options.refreshClient = options.refreshClient;
336
+ this._options.charset = options.charset === undefined ? "UTF-8": options.charset;
337
+ this._options.extraHttpHeaders = {};
338
+ if (options.extraHttpHeaders) {
339
+ for (let h in options.extraHttpHeaders) this._options.extraHttpHeaders[h] = options.extraHttpHeaders[h];
340
+ }
341
+ this._options.clientApp = options.clientApp;
342
+ this._options.noSDKHeaders = !!options.noSDKHeaders;
300
343
  }
301
344
 
302
345
  /**
@@ -474,7 +517,7 @@ class Client {
474
517
  this._entityCache = new XtkEntityCache(this._storage, `${rootKey}.XtkEntityCache`, connectionParameters._options.entityCacheTTL);
475
518
  this._methodCache = new MethodCache(this._storage, `${rootKey}.MethodCache`, connectionParameters._options.methodCacheTTL);
476
519
  this._optionCache = new OptionCache(this._storage, `${rootKey}.OptionCache`, connectionParameters._options.optionCacheTTL);
477
- this.NLWS = new Proxy(this, clientHandler);
520
+ this.NLWS = new Proxy(this, clientHandler());
478
521
 
479
522
  this._transport = connectionParameters._options.transport;
480
523
  this._traceAPICalls = connectionParameters._options.traceAPICalls;
@@ -669,11 +712,15 @@ class Client {
669
712
  * @private
670
713
  * @param {string} urn is the API name space, usually the schema. For instance xtk:session
671
714
  * @param {string} method is the method to call, for instance Logon
715
+ * @param {boolean} internal is a boolean indicating whether the SOAP call is performed by the SDK (internal = true) or on behalf of a user
672
716
  * @return {SOAP.SoapMethodCall} a SoapMethodCall which have been initialized with security tokens... and to which the method
673
717
  * parameters should be set
674
718
  */
675
- _prepareSoapCall(urn, method, internal) {
676
- const soapCall = new SoapMethodCall(this._transport, urn, method, this._sessionToken, this._securityToken, this._getUserAgentString());
719
+ _prepareSoapCall(urn, method, internal, extraHttpHeaders) {
720
+ const soapCall = new SoapMethodCall(this._transport, urn, method,
721
+ this._sessionToken, this._securityToken,
722
+ this._getUserAgentString(), this._connectionParameters._options.charset,
723
+ extraHttpHeaders);
677
724
  soapCall.internal = !!internal;
678
725
  return soapCall;
679
726
  }
@@ -687,6 +734,7 @@ class Client {
687
734
  */
688
735
  async _retrySoapCall(soapCall) {
689
736
  soapCall.retry = false;
737
+ soapCall._retryCount = soapCall._retryCount + 1;
690
738
  var newClient = await this._refreshClient(this);
691
739
  soapCall.finalize(newClient._soapEndPoint(), newClient);
692
740
  if (this._traceAPICalls) {
@@ -761,6 +809,18 @@ class Client {
761
809
  this._securityToken = "";
762
810
  const credentials = this._connectionParameters._credentials;
763
811
 
812
+ const sdkVersion = this.sdk.getSDKVersion();
813
+ const version = `${sdkVersion.name} ${sdkVersion.version}`;
814
+ const clientApp = this._connectionParameters._options.clientApp;
815
+ if (!this._connectionParameters._options.noSDKHeaders) {
816
+ this._connectionParameters._options.extraHttpHeaders["ACC-SDK-Version"] = version;
817
+ this._connectionParameters._options.extraHttpHeaders["ACC-SDK-Auth"] = `${credentials._type}`;
818
+ if (clientApp)
819
+ this._connectionParameters._options.extraHttpHeaders["ACC-SDK-Client-App"] = clientApp;
820
+ }
821
+ // See NEO-35259
822
+ this._connectionParameters._options.extraHttpHeaders['X-Query-Source'] = `${version}${clientApp? "," + clientApp : ""}`;
823
+
764
824
  // Clear session token cookie to ensure we're not inheriting an expired cookie. See NEO-26589
765
825
  if (credentials._type != "SecurityToken" && typeof document != "undefined") {
766
826
  document.cookie = '__sessiontoken=;path=/;';
@@ -773,8 +833,8 @@ class Client {
773
833
  that.application = new Application(that);
774
834
  return Promise.resolve();
775
835
  }
776
- else if (credentials._type == "SecurityToken") {
777
- that._sessionInfo = undefined;
836
+ else if (credentials._type == "SecurityToken") {
837
+ that._sessionInfo = undefined;
778
838
  that._installedPackages = {};
779
839
  that._sessionToken = "";
780
840
  that._securityToken = credentials._securityToken;
@@ -782,12 +842,14 @@ class Client {
782
842
  return Promise.resolve();
783
843
  }
784
844
  else if (credentials._type == "UserPassword" || credentials._type == "BearerToken") {
785
- const soapCall = that._prepareSoapCall("xtk:session", credentials._type === "UserPassword" ? "Logon" : "BearerTokenLogon");
845
+ const soapCall = that._prepareSoapCall("xtk:session", credentials._type === "UserPassword" ? "Logon" : "BearerTokenLogon", false, this._connectionParameters._options.extraHttpHeaders);
786
846
  // No retry for logon SOAP methods
787
847
  soapCall.retry = false;
788
848
  if (credentials._type == "UserPassword") {
789
849
  const user = credentials._getUser();
790
850
  const password = credentials._getPassword();
851
+ if (!this._connectionParameters._options.noSDKHeaders)
852
+ this._connectionParameters._options.extraHttpHeaders["ACC-SDK-Auth"] = `${credentials._type} ${user}`;
791
853
  soapCall.writeString("login", user);
792
854
  soapCall.writeString("password", password);
793
855
  var parameters = null;
@@ -855,7 +917,7 @@ class Client {
855
917
  if (!that.isLogged()) return;
856
918
  const credentials = this._connectionParameters._credentials;
857
919
  if (credentials._type != "SessionToken" && credentials._type != "AnonymousUser") {
858
- var soapCall = that._prepareSoapCall("xtk:session", "Logoff");
920
+ var soapCall = that._prepareSoapCall("xtk:session", "Logoff", false, this._connectionParameters._options.extraHttpHeaders);
859
921
  return this._makeSoapCall(soapCall).then(function() {
860
922
  that._sessionToken = "";
861
923
  that._securityToken = "";
@@ -960,7 +1022,7 @@ class Client {
960
1022
  * @returns {boolean} a boolean indicating if the package is installed or not
961
1023
  */
962
1024
  hasPackage(packageId, optionalName) {
963
- if (optionalName === undefined)
1025
+ if (optionalName !== undefined)
964
1026
  packageId = `${packageId}:${optionalName}`;
965
1027
  if (!this.isLogged())
966
1028
  throw CampaignException.NOT_LOGGED_IN(undefined, `Cannot call hasPackage: session not connected`);
@@ -974,7 +1036,7 @@ class Client {
974
1036
  * @private
975
1037
  * @deprecated since version 1.0.0
976
1038
  */
977
- async _getSecretKeyCipher() {
1039
+ async _getSecretKeyCipher() {
978
1040
  var that = this;
979
1041
  if (this._secretKeyCipher) return this._secretKeyCipher;
980
1042
  return that.getOption("XtkSecretKey").then(function(secretKey) {
@@ -997,7 +1059,7 @@ class Client {
997
1059
  */
998
1060
  async getEntityIfMoreRecent(entityType, fullName, representation, internal) {
999
1061
  const that = this;
1000
- const soapCall = this._prepareSoapCall("xtk:persist", "GetEntityIfMoreRecent", internal);
1062
+ const soapCall = this._prepareSoapCall("xtk:persist", "GetEntityIfMoreRecent", internal, this._connectionParameters._options.extraHttpHeaders);
1001
1063
  soapCall.writeString("pk", entityType + "|" + fullName);
1002
1064
  soapCall.writeString("md5", "");
1003
1065
  soapCall.writeBoolean("mustExist", false);
@@ -1100,7 +1162,7 @@ class Client {
1100
1162
  // console.log(method.toXMLString());
1101
1163
 
1102
1164
  var urn = that._methodCache.getSoapUrn(schemaId, methodName);
1103
- var soapCall = that._prepareSoapCall(urn, methodName);
1165
+ var soapCall = that._prepareSoapCall(urn, methodName, false, callContext.headers);
1104
1166
 
1105
1167
  // If method is called with one parameter which is a function, then we assume it's a hook: the function will return
1106
1168
  // the actual list of parameters
@@ -1117,7 +1179,7 @@ class Client {
1117
1179
  throw CampaignException.SOAP_UNKNOWN_METHOD(schemaId, methodName, `Cannot call non-static method '${methodName}' of schema '${schemaId}' : no object was specified`);
1118
1180
 
1119
1181
  const rootName = schemaId.substr(schemaId.indexOf(':') + 1);
1120
- object = that._fromRepresentation(rootName, object);
1182
+ object = that._fromRepresentation(rootName, object, callContext.representation);
1121
1183
  soapCall.writeDocument("document", object);
1122
1184
  }
1123
1185
 
@@ -1141,6 +1203,8 @@ class Client {
1141
1203
  soapCall.writeByte(paramName, XtkCaster.asByte(paramValue));
1142
1204
  else if (type == "short")
1143
1205
  soapCall.writeShort(paramName, XtkCaster.asShort(paramValue));
1206
+ else if (type == "int")
1207
+ soapCall.writeLong(paramName, XtkCaster.asLong(paramValue));
1144
1208
  else if (type == "long")
1145
1209
  soapCall.writeLong(paramName, XtkCaster.asLong(paramValue));
1146
1210
  else if (type == "int64")
@@ -1165,7 +1229,8 @@ class Client {
1165
1229
  const index = xtkschema.indexOf(":");
1166
1230
  docName = xtkschema.substr(index+1);
1167
1231
  }
1168
- var xmlValue = that._fromRepresentation(docName, paramValue);
1232
+ if (!docName) docName = paramName; // Use te parameter name as the XML root element
1233
+ var xmlValue = that._fromRepresentation(docName, paramValue, callContext.representation);
1169
1234
  if (type == "DOMDocument")
1170
1235
  soapCall.writeDocument(paramName, xmlValue);
1171
1236
  else
@@ -1184,7 +1249,7 @@ class Client {
1184
1249
  // the method is called. This is the new version of the object (in XML form)
1185
1250
  const entity = soapCall.getEntity();
1186
1251
  if (entity) {
1187
- callContext.object = that._toRepresentation(entity);
1252
+ callContext.object = that._toRepresentation(entity, callContext.representation);
1188
1253
  }
1189
1254
  }
1190
1255
 
@@ -1215,7 +1280,7 @@ class Client {
1215
1280
  returnValue = soapCall.getNextDate();
1216
1281
  else if (type == "DOMDocument") {
1217
1282
  returnValue = soapCall.getNextDocument();
1218
- returnValue = that._toRepresentation(returnValue);
1283
+ returnValue = that._toRepresentation(returnValue, callContext.representation);
1219
1284
  if (schemaId === "xtk:queryDef" && methodName === "ExecuteQuery" && paramName === "output") {
1220
1285
  // https://github.com/adobe/acc-js-sdk/issues/3
1221
1286
  // Check if query operation is "getIfExists". The "object" variable at this point
@@ -1235,7 +1300,7 @@ class Client {
1235
1300
  }
1236
1301
  else if (type == "DOMElement") {
1237
1302
  returnValue = soapCall.getNextElement();
1238
- returnValue = that._toRepresentation(returnValue);
1303
+ returnValue = that._toRepresentation(returnValue, callContext.representation);
1239
1304
  }
1240
1305
  else {
1241
1306
  // type can reference a schema element. The naming convension is that the type name
@@ -1249,7 +1314,7 @@ class Client {
1249
1314
  if (element.getAttribute("name") == shortTypeName) {
1250
1315
  // Type found in schema: Process as a DOM element
1251
1316
  returnValue = soapCall.getNextElement();
1252
- returnValue = that._toRepresentation(returnValue);
1317
+ returnValue = that._toRepresentation(returnValue, callContext.representation);
1253
1318
  break;
1254
1319
  }
1255
1320
  element = DomUtil.getNextSiblingElement(element, "element");
@@ -1304,8 +1369,11 @@ class Client {
1304
1369
  */
1305
1370
  async test() {
1306
1371
  const request = {
1307
- url: `${this._connectionParameters._endpoint}/r/test`
1372
+ url: `${this._connectionParameters._endpoint}/r/test`,
1373
+ headers: {}
1308
1374
  };
1375
+ for (let h in this._connectionParameters._options.extraHttpHeaders)
1376
+ request.headers[h] = this._connectionParameters._options.extraHttpHeaders[h];
1309
1377
  const body = await this._makeHttpCall(request);
1310
1378
  const xml = DomUtil.parse(body);
1311
1379
  const result = this._toRepresentation(xml);
@@ -1325,6 +1393,8 @@ class Client {
1325
1393
  'Cookie': '__sessiontoken=' + this._sessionToken
1326
1394
  }
1327
1395
  };
1396
+ for (let h in this._connectionParameters._options.extraHttpHeaders)
1397
+ request.headers[h] = this._connectionParameters._options.extraHttpHeaders[h];
1328
1398
  const body = await this._makeHttpCall(request);
1329
1399
  const lines = body.split('\n');
1330
1400
  const doc = DomUtil.newDocument("ping");
@@ -1354,6 +1424,8 @@ class Client {
1354
1424
  'Cookie': '__sessiontoken=' + this._sessionToken
1355
1425
  }
1356
1426
  };
1427
+ for (let h in this._connectionParameters._options.extraHttpHeaders)
1428
+ request.headers[h] = this._connectionParameters._options.extraHttpHeaders[h];
1357
1429
  const body = await this._makeHttpCall(request);
1358
1430
  const lines = body.split('\n');
1359
1431
  const doc = DomUtil.newDocument("ping");
package/src/soap.js CHANGED
@@ -78,11 +78,13 @@ 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
82
+ * @param {{ name:string, value:string}} extraHttpHeaders key/value pair of HTTP header (will override any other headers)
81
83
  * @memberof SOAP
82
84
  */
83
85
  class SoapMethodCall {
84
86
 
85
- constructor(transport, urn, methodName, sessionToken, securityToken, userAgentString) {
87
+ constructor(transport, urn, methodName, sessionToken, securityToken, userAgentString, charset, extraHttpHeaders) {
86
88
  this.request = undefined; // The HTTP request (object litteral passed to the transport layer)
87
89
  this.response = undefined; // The HTTP response object (in case of success)
88
90
 
@@ -95,10 +97,13 @@ class SoapMethodCall {
95
97
  this.internal = false;
96
98
  // Enable soap retry
97
99
  this.retry = true;
100
+ this._retryCount = 0;
98
101
 
99
102
  this._sessionToken = sessionToken || "";
100
103
  this._securityToken = securityToken || "";
101
104
  this._userAgentString = userAgentString;
105
+ this._charset = charset || "";
106
+ this._extraHttpHeaders = extraHttpHeaders || {};
102
107
 
103
108
  // THe SOAP call being built
104
109
  this._doc = undefined; // XML document for SOAP call
@@ -211,7 +216,7 @@ class SoapMethodCall {
211
216
  * @param {string} tag the parameter name
212
217
  * @param {*} value the parameter value, which will be casted to a int32 according to xtk rules
213
218
  */
214
- writeLong(tag, value) {
219
+ writeLong(tag, value) {
215
220
  value = XtkCaster.asLong(value);
216
221
  this._addNode(tag, "xsd:int", XtkCaster.asString(value), SOAP_ENCODING_NATIVE);
217
222
  }
@@ -297,7 +302,8 @@ class SoapMethodCall {
297
302
  writeDocument(tag, document) {
298
303
  const node = this._addNode(tag, "", null, SOAP_ENCODING_XML);
299
304
  if (document !== null && document !== undefined) {
300
- const child = this._doc.importNode(document.documentElement, true);
305
+ const element = document.nodeType === 1 ? document : document.documentElement;
306
+ const child = this._doc.importNode(element, true);
301
307
  node.appendChild(child);
302
308
  }
303
309
  }
@@ -512,20 +518,37 @@ class SoapMethodCall {
512
518
  * @returns {Object} an options object describing the HTTP request, with cookies, headers and body
513
519
  */
514
520
  _createHTTPRequest(url) {
521
+
522
+ const headers = {
523
+ 'Content-type': `application/soap+xml${this._charset ? ";charset=" + this._charset : ""}`,
524
+ 'SoapAction': `${this.urn}#${this.methodName}`,
525
+ 'X-Security-Token': this._securityToken
526
+ };
527
+
528
+ // Add HTTP headers specific to the SOAP call for better tracing/troubleshooting
529
+ if (this._extraHttpHeaders && this._extraHttpHeaders['ACC-SDK-Version']) {
530
+ // "this.retry" means that the call can be retried, not that it is being retried. The HTTP header howerver, indicates that this
531
+ // is actually a retry of a previously failed call (expired token)
532
+ if (this._retryCount > 0) headers["ACC-SDK-Call-RetryCount"] = `${this._retryCount}`;
533
+ if (this.internal) headers["ACC-SDK-Call-Internal"] = "1";
534
+ }
535
+
515
536
  const options = {
516
537
  url: url,
517
538
  method: 'POST',
518
- headers: {
519
- 'Content-type': 'application/soap+xml',
520
- 'SoapAction': `${this.urn}#${this.methodName}`,
521
- 'X-Security-Token': this._securityToken
522
- },
539
+ headers: headers,
523
540
  data: DomUtil.toXMLString(this._doc)
524
541
  };
525
542
  if (this._sessionToken)
526
543
  options.headers.Cookie = '__sessiontoken=' + this._sessionToken;
527
544
  if (this._userAgentString)
528
545
  options.headers['User-Agent'] = this._userAgentString;
546
+
547
+ // Override http headers with custom headers
548
+ for (let h in this._extraHttpHeaders) {
549
+ options.headers[h] = this._extraHttpHeaders[h];
550
+ }
551
+
529
552
  return options;
530
553
  }
531
554
 
@@ -56,12 +56,12 @@ describe('ACC Client (has package)', () => {
56
56
  client._transport.mockReturnValueOnce(LOGON_RESPONSE);
57
57
  await client.NLWS.xtkSession.logon();
58
58
 
59
- expect(client.hasPackage("nms:campaign"));
60
- expect(client.hasPackage("nms", "campaign"));
61
- expect(!client.hasPackage("nms:mrm"));
62
- expect(!client.hasPackage("nms", "mrm"));
63
- expect(client.hasPackage("nms:core"));
64
- expect(client.hasPackage("nms", "core"));
59
+ expect(client.hasPackage("nms:campaign")).toBeTruthy();
60
+ expect(client.hasPackage("nms", "campaign")).toBeTruthy();
61
+ expect(client.hasPackage("nms:mrm")).toBeFalsy();
62
+ expect(client.hasPackage("nms", "mrm")).toBeFalsy();
63
+ expect(client.hasPackage("nms:core")).toBeTruthy();
64
+ expect(client.hasPackage("nms", "core")).toBeTruthy();
65
65
  });
66
66
 
67
67
  it('should fail when unlogged', async () => {
@@ -2283,15 +2283,15 @@ describe('ACC Client', function () {
2283
2283
  it("Should ignore protocol for local storage root key", async () => {
2284
2284
  var connectionParameters = sdk.ConnectionParameters.ofUserAndPassword("http://acc-sdk:8080", "admin", "admin", {});
2285
2285
  var client = await sdk.init(connectionParameters);
2286
- expect(client._optionCache._storage._rootKey).toBe("acc.js.sdk.1.1.0.acc-sdk:8080.cache.OptionCache$");
2286
+ expect(client._optionCache._storage._rootKey).toBe("acc.js.sdk.1.1.3.acc-sdk:8080.cache.OptionCache$");
2287
2287
 
2288
2288
  connectionParameters = sdk.ConnectionParameters.ofUserAndPassword("https://acc-sdk:8080", "admin", "admin", {});
2289
2289
  client = await sdk.init(connectionParameters);
2290
- expect(client._optionCache._storage._rootKey).toBe("acc.js.sdk.1.1.0.acc-sdk:8080.cache.OptionCache$");
2290
+ expect(client._optionCache._storage._rootKey).toBe("acc.js.sdk.1.1.3.acc-sdk:8080.cache.OptionCache$");
2291
2291
 
2292
2292
  connectionParameters = sdk.ConnectionParameters.ofUserAndPassword("acc-sdk:8080", "admin", "admin", {});
2293
2293
  client = await sdk.init(connectionParameters);
2294
- expect(client._optionCache._storage._rootKey).toBe("acc.js.sdk.1.1.0.acc-sdk:8080.cache.OptionCache$");
2294
+ expect(client._optionCache._storage._rootKey).toBe("acc.js.sdk.1.1.3.acc-sdk:8080.cache.OptionCache$");
2295
2295
  })
2296
2296
 
2297
2297
  it("Should support no storage", async () => {
@@ -2422,4 +2422,385 @@ describe('ACC Client', function () {
2422
2422
  expect(paramsFn.mock.calls[0][1]).toMatchObject({ schemaId: "xtk:session", namespace: "xtkSession" });
2423
2423
  });
2424
2424
  });
2425
+
2426
+ describe("Method-level representation", () => {
2427
+ it("Should force an xml representation", async () => {
2428
+ const client = await Mock.makeClient();
2429
+ client._transport.mockReturnValueOnce(Mock.LOGON_RESPONSE);
2430
+ await client.NLWS.xtkSession.logon();
2431
+ client._transport.mockReturnValueOnce(Mock.GET_XTK_QUERY_SCHEMA_RESPONSE);
2432
+ client._transport.mockReturnValueOnce(Mock.GET_QUERY_EXECUTE_RESPONSE);
2433
+ const queryDef = DomUtil.parse(`
2434
+ <queryDef schema="nms:extAccount" operation="select">
2435
+ <select>
2436
+ <node expr="@id"/>
2437
+ <node expr="@name"/>
2438
+ </select>
2439
+ </queryDef>
2440
+ `);
2441
+ const query = client.NLWS.xml.xtkQueryDef.create(queryDef);
2442
+ const result = await query.executeQuery();
2443
+ const xml = DomUtil.toXMLString(result);
2444
+ expect(xml).toBe(`<extAccount-collection xmlns="urn:xtk:queryDef">
2445
+ <extAccount id="1816" name="defaultPopAccount"/>
2446
+ <extAccount id="1818" name="defaultOther"/>
2447
+ <extAccount id="1849" name="billingReport"/>
2448
+ <extAccount id="12070" name="TST_EXT_ACCOUNT_POSTGRESQL"/>
2449
+ <extAccount id="1817" name="defaultEmailBulk"/>
2450
+ <extAccount id="2087" name="ffda"/>
2451
+ <extAccount id="2088" name="defaultEmailMid"/>
2452
+ </extAccount-collection>`);
2453
+ });
2454
+
2455
+ it("Should force an json representation", async () => {
2456
+ const client = await Mock.makeClient();
2457
+ client._transport.mockReturnValueOnce(Mock.LOGON_RESPONSE);
2458
+ await client.NLWS.xtkSession.logon();
2459
+ client._transport.mockReturnValueOnce(Mock.GET_XTK_QUERY_SCHEMA_RESPONSE);
2460
+ client._transport.mockReturnValueOnce(Mock.GET_QUERY_EXECUTE_RESPONSE);
2461
+ const queryDef = {
2462
+ "schema": "nms:extAccount",
2463
+ "operation": "select",
2464
+ "select": {
2465
+ "node": [
2466
+ { "expr": "@id" },
2467
+ { "expr": "@name" }
2468
+ ]
2469
+ }
2470
+ };
2471
+ const query = client.NLWS.json.xtkQueryDef.create(queryDef);
2472
+ const result = await query.executeQuery();
2473
+ const json = JSON.stringify(result);
2474
+ expect(json).toBe('{"#text":[],"extAccount":[{"id":"1816","name":"defaultPopAccount"},{"id":"1818","name":"defaultOther"},{"id":"1849","name":"billingReport"},{"id":"12070","name":"TST_EXT_ACCOUNT_POSTGRESQL"},{"id":"1817","name":"defaultEmailBulk"},{"id":"2087","name":"ffda"},{"id":"2088","name":"defaultEmailMid"}]}');
2475
+ });
2476
+ });
2477
+
2478
+ describe('Support for int type parameters such as nms:extAccount#UpdateMCSynchWkf', () => {
2479
+ it("Should call nms:extAccount#UpdateMCSynchWkf", async () => {
2480
+ const client = await Mock.makeClient();
2481
+ client._transport.mockReturnValueOnce(Mock.LOGON_RESPONSE);
2482
+ await client.NLWS.xtkSession.logon();
2483
+
2484
+ client._transport.mockReturnValueOnce(Promise.resolve(`<?xml version='1.0'?>
2485
+ <SOAP-ENV:Envelope xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:ns='urn:wpp:default' xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/'>
2486
+ <SOAP-ENV:Body>
2487
+ <GetEntityIfMoreRecentResponse xmlns='urn:wpp:default' SOAP-ENV:encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'>
2488
+ <pdomDoc xsi:type='ns:Element' SOAP-ENV:encodingStyle='http://xml.apache.org/xml-soap/literalxml'>
2489
+ <schema name="extAccount" namespace="nms" xtkschema="xtk:schema">
2490
+ <element name="extAccount"></element>
2491
+ <methods>
2492
+ <method library="nms:messageCenter.js" name="UpdateMCSynchWkf" static="true" hidden="true">
2493
+ <parameters>
2494
+ <param name="extAccountId" type="int" desc="Message Center external account identifier"/>
2495
+ </parameters>
2496
+ </method>
2497
+ </methods>
2498
+ </schema>
2499
+ </pdomDoc>
2500
+ </GetEntityIfMoreRecentResponse>
2501
+ </SOAP-ENV:Body>
2502
+ </SOAP-ENV:Envelope>`));
2503
+
2504
+ client._transport.mockReturnValueOnce(Promise.resolve(`<?xml version='1.0'?>
2505
+ <SOAP-ENV:Envelope xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:ns='urn:nms:extAccount' xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/'>
2506
+ <SOAP-ENV:Body>
2507
+ <UpdateMCSynchWkfResponse xmlns='urn:nms:extAccount' SOAP-ENV:encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'>
2508
+ </UpdateMCSynchWkfResponse>
2509
+ </SOAP-ENV:Body>
2510
+ </SOAP-ENV:Envelope>`));
2511
+
2512
+ await client.NLWS.nmsExtAccount.updateMCSynchWkf(1);
2513
+ })
2514
+ });
2515
+
2516
+ describe("Method-level HTTP headers", () => {
2517
+ it("Should set header", async () => {
2518
+ const client = await Mock.makeClient();
2519
+ client._transport.mockReturnValueOnce(Mock.LOGON_RESPONSE);
2520
+ await client.NLWS.xtkSession.logon();
2521
+ client._transport.mockReturnValueOnce(Mock.GET_XTK_QUERY_SCHEMA_RESPONSE);
2522
+ client._transport.mockReturnValueOnce(Mock.GET_QUERY_EXECUTE_RESPONSE);
2523
+ const queryDef = {
2524
+ "schema": "nms:extAccount",
2525
+ "operation": "select",
2526
+ "select": {
2527
+ "node": [
2528
+ { "expr": "@id" },
2529
+ { "expr": "@name" }
2530
+ ]
2531
+ }
2532
+ };
2533
+ const query = client.NLWS.headers({'X-Test': 'hello'}).xtkQueryDef.create(queryDef);
2534
+
2535
+ let headers = {};
2536
+ client.registerObserver({
2537
+ onSOAPCall: (soapCall) => {
2538
+ const request = soapCall.request;
2539
+ headers = request.headers;
2540
+ }
2541
+ });
2542
+ await query.executeQuery();
2543
+ expect(headers).toMatchObject({
2544
+ "SoapAction": "xtk:queryDef#ExecuteQuery",
2545
+ "X-Test": "hello"
2546
+ });
2547
+ });
2548
+
2549
+ it("Should support global and method-level http headers", async () => {
2550
+ const client = await Mock.makeClient({
2551
+ extraHttpHeaders: {
2552
+ "X-Test": "world",
2553
+ "X-Test-Global": "global"
2554
+ }
2555
+ });
2556
+ client._transport.mockReturnValueOnce(Mock.LOGON_RESPONSE);
2557
+ await client.NLWS.xtkSession.logon();
2558
+ client._transport.mockReturnValueOnce(Mock.GET_XTK_QUERY_SCHEMA_RESPONSE);
2559
+ client._transport.mockReturnValueOnce(Mock.GET_QUERY_EXECUTE_RESPONSE);
2560
+ const queryDef = {
2561
+ "schema": "nms:extAccount",
2562
+ "operation": "select",
2563
+ "select": {
2564
+ "node": [
2565
+ { "expr": "@id" },
2566
+ { "expr": "@name" }
2567
+ ]
2568
+ }
2569
+ };
2570
+ const query = client.NLWS.headers({'X-Test': 'hello'}).xtkQueryDef.create(queryDef);
2571
+
2572
+ let headers = {};
2573
+ client.registerObserver({
2574
+ onSOAPCall: (soapCall) => {
2575
+ const request = soapCall.request;
2576
+ headers = request.headers;
2577
+ }
2578
+ });
2579
+ await query.executeQuery();
2580
+ expect(headers).toMatchObject({
2581
+ "SoapAction": "xtk:queryDef#ExecuteQuery",
2582
+ "X-Test": "hello",
2583
+ "X-Test-Global": "global"
2584
+ });
2585
+ })
2586
+
2587
+ it("Should support undefined method headers", async () => {
2588
+ const client = await Mock.makeClient({
2589
+ extraHttpHeaders: {
2590
+ "X-Test": "world",
2591
+ "X-Test-Global": "global"
2592
+ }
2593
+ });
2594
+ client._transport.mockReturnValueOnce(Mock.LOGON_RESPONSE);
2595
+ await client.NLWS.xtkSession.logon();
2596
+ client._transport.mockReturnValueOnce(Mock.GET_XTK_QUERY_SCHEMA_RESPONSE);
2597
+ client._transport.mockReturnValueOnce(Mock.GET_QUERY_EXECUTE_RESPONSE);
2598
+ const queryDef = {
2599
+ "schema": "nms:extAccount",
2600
+ "operation": "select",
2601
+ "select": {
2602
+ "node": [
2603
+ { "expr": "@id" },
2604
+ { "expr": "@name" }
2605
+ ]
2606
+ }
2607
+ };
2608
+ // missing headers
2609
+ const query = client.NLWS.headers().xtkQueryDef.create(queryDef);
2610
+
2611
+ let headers = {};
2612
+ client.registerObserver({
2613
+ onSOAPCall: (soapCall) => {
2614
+ const request = soapCall.request;
2615
+ headers = request.headers;
2616
+ }
2617
+ });
2618
+ await query.executeQuery();
2619
+ expect(headers).toMatchObject({
2620
+ "SoapAction": "xtk:queryDef#ExecuteQuery",
2621
+ "X-Test": "world",
2622
+ "X-Test-Global": "global"
2623
+ });
2624
+ })
2625
+
2626
+ it("Should set http headers with an xml representation", async () => {
2627
+ const client = await Mock.makeClient();
2628
+ client._transport.mockReturnValueOnce(Mock.LOGON_RESPONSE);
2629
+ await client.NLWS.xtkSession.logon();
2630
+ client._transport.mockReturnValueOnce(Mock.GET_XTK_QUERY_SCHEMA_RESPONSE);
2631
+ client._transport.mockReturnValueOnce(Mock.GET_QUERY_EXECUTE_RESPONSE);
2632
+ const queryDef = DomUtil.parse(`
2633
+ <queryDef schema="nms:extAccount" operation="select">
2634
+ <select>
2635
+ <node expr="@id"/>
2636
+ <node expr="@name"/>
2637
+ </select>
2638
+ </queryDef>
2639
+ `);
2640
+ const query = client.NLWS
2641
+ .headers({'X-Test': 'hello', 'X-Test-Before': 'before'})
2642
+ .xml
2643
+ .headers({'X-Test': 'world', 'X-Test-After': 'after'})
2644
+ .xtkQueryDef.create(queryDef);
2645
+ let headers = {};
2646
+ client.registerObserver({
2647
+ onSOAPCall: (soapCall) => {
2648
+ const request = soapCall.request;
2649
+ headers = request.headers;
2650
+ }
2651
+ });
2652
+ await query.executeQuery();
2653
+ console.log(headers);
2654
+ expect(headers).toMatchObject({
2655
+ "SoapAction": "xtk:queryDef#ExecuteQuery",
2656
+ "X-Test": "world",
2657
+ "X-Test-Before": "before",
2658
+ "X-Test-After": "after"
2659
+ });
2660
+ });
2661
+ });
2662
+
2663
+ describe("ACC-SDK HTTP headers", () => {
2664
+
2665
+ const collectHeaders = async (client, callback) => {
2666
+ let headers = {};
2667
+ client.registerObserver({
2668
+ onSOAPCall: (soapCall) => {
2669
+ const request = soapCall.request;
2670
+ headers = request.headers;
2671
+ },
2672
+ onHTTPCall: (request) => {
2673
+ headers = request.headers;
2674
+ }
2675
+ });
2676
+ await callback();
2677
+ return headers;
2678
+ };
2679
+
2680
+ it("Should set headers by default", async () => {
2681
+ const client = await Mock.makeClient();
2682
+ client._transport.mockReturnValueOnce(Mock.LOGON_RESPONSE);
2683
+ await client.NLWS.xtkSession.logon();
2684
+ client._transport.mockReturnValueOnce(Mock.GET_XTK_QUERY_SCHEMA_RESPONSE);
2685
+ client._transport.mockReturnValueOnce(Mock.GET_QUERY_EXECUTE_RESPONSE);
2686
+ const queryDef = {
2687
+ "schema": "nms:extAccount",
2688
+ "operation": "select",
2689
+ "select": {
2690
+ "node": [
2691
+ { "expr": "@id" },
2692
+ { "expr": "@name" }
2693
+ ]
2694
+ }
2695
+ };
2696
+ const query = client.NLWS.xtkQueryDef.create(queryDef);
2697
+
2698
+ const headers = await collectHeaders(client, async() => {
2699
+ await query.executeQuery();
2700
+ });
2701
+
2702
+ expect(headers).toMatchObject({
2703
+ "ACC-SDK-Version": `${sdk.getSDKVersion().name} ${sdk.getSDKVersion().version}`,
2704
+ "ACC-SDK-Auth": "UserPassword admin",
2705
+ "X-Query-Source": `${sdk.getSDKVersion().name} ${sdk.getSDKVersion().version}`,
2706
+ });
2707
+ // This header is only set if "clientApp" connection parameter is set
2708
+ expect(headers["ACC-SDK-Client-App"]).toBeUndefined();
2709
+ });
2710
+
2711
+ it("Should disable ACC-SDK headers", async () => {
2712
+ const client = await Mock.makeClient({
2713
+ noSDKHeaders: true
2714
+ });
2715
+ client._transport.mockReturnValueOnce(Mock.LOGON_RESPONSE);
2716
+ await client.NLWS.xtkSession.logon();
2717
+ client._transport.mockReturnValueOnce(Mock.GET_XTK_QUERY_SCHEMA_RESPONSE);
2718
+ client._transport.mockReturnValueOnce(Mock.GET_QUERY_EXECUTE_RESPONSE);
2719
+ const queryDef = {
2720
+ "schema": "nms:extAccount",
2721
+ "operation": "select",
2722
+ "select": {
2723
+ "node": [
2724
+ { "expr": "@id" },
2725
+ { "expr": "@name" }
2726
+ ]
2727
+ }
2728
+ };
2729
+ const query = client.NLWS.xtkQueryDef.create(queryDef);
2730
+
2731
+ const headers = await collectHeaders(client, async() => {
2732
+ await query.executeQuery();
2733
+ });
2734
+ expect(headers["ACC-SDK-Version"]).toBeUndefined();
2735
+ expect(headers["ACC-SDK-Auth"]).toBeUndefined();
2736
+ expect(headers["X-Query-Source"]).toBe(`${sdk.getSDKVersion().name} ${sdk.getSDKVersion().version}`);
2737
+ });
2738
+
2739
+ it("Should support ACC-SDK-Client-App header", async () => {
2740
+ const client = await Mock.makeClient({
2741
+ clientApp: 'Test client app'
2742
+ });
2743
+ client._transport.mockReturnValueOnce(Mock.LOGON_RESPONSE);
2744
+ await client.NLWS.xtkSession.logon();
2745
+ client._transport.mockReturnValueOnce(Mock.GET_XTK_QUERY_SCHEMA_RESPONSE);
2746
+ client._transport.mockReturnValueOnce(Mock.GET_QUERY_EXECUTE_RESPONSE);
2747
+ const queryDef = {
2748
+ "schema": "nms:extAccount",
2749
+ "operation": "select",
2750
+ "select": {
2751
+ "node": [
2752
+ { "expr": "@id" },
2753
+ { "expr": "@name" }
2754
+ ]
2755
+ }
2756
+ };
2757
+ const query = client.NLWS.xtkQueryDef.create(queryDef);
2758
+
2759
+ const headers = await collectHeaders(client, async() => {
2760
+ await query.executeQuery();
2761
+ });
2762
+ expect(headers).toMatchObject({
2763
+ "ACC-SDK-Version": `${sdk.getSDKVersion().name} ${sdk.getSDKVersion().version}`,
2764
+ "ACC-SDK-Auth": "UserPassword admin",
2765
+ "ACC-SDK-Client-App": "Test client app",
2766
+ "X-Query-Source": `${sdk.getSDKVersion().name} ${sdk.getSDKVersion().version},Test client app`,
2767
+ });
2768
+ });
2769
+
2770
+ it("Should set ACC-SDK headers on ping JSP", async () => {
2771
+ const client = await Mock.makeClient({
2772
+ clientApp: 'Test client app'
2773
+ });
2774
+ client._transport.mockReturnValueOnce(Mock.LOGON_RESPONSE);
2775
+ await client.NLWS.xtkSession.logon();
2776
+ const headers = await collectHeaders(client, async() => {
2777
+ client._transport.mockReturnValueOnce(Mock.PING);
2778
+ await client.ping();
2779
+ });
2780
+ expect(headers).toMatchObject({
2781
+ "ACC-SDK-Version": `${sdk.getSDKVersion().name} ${sdk.getSDKVersion().version}`,
2782
+ "ACC-SDK-Auth": "UserPassword admin",
2783
+ "ACC-SDK-Client-App": "Test client app",
2784
+ "X-Query-Source": `${sdk.getSDKVersion().name} ${sdk.getSDKVersion().version},Test client app`,
2785
+ });
2786
+ });
2787
+
2788
+ it("Should set ACC-SDK headers on mcping JSP", async () => {
2789
+ const client = await Mock.makeClient({
2790
+ clientApp: 'Test client app'
2791
+ });
2792
+ client._transport.mockReturnValueOnce(Mock.LOGON_RESPONSE);
2793
+ await client.NLWS.xtkSession.logon();
2794
+ const headers = await collectHeaders(client, async() => {
2795
+ client._transport.mockReturnValueOnce(Mock.MC_PING);
2796
+ await client.mcPing();
2797
+ });
2798
+ expect(headers).toMatchObject({
2799
+ "ACC-SDK-Version": `${sdk.getSDKVersion().name} ${sdk.getSDKVersion().version}`,
2800
+ "ACC-SDK-Auth": "UserPassword admin",
2801
+ "ACC-SDK-Client-App": "Test client app",
2802
+ "X-Query-Source": `${sdk.getSDKVersion().name} ${sdk.getSDKVersion().version},Test client app`,
2803
+ });
2804
+ });
2805
+ });
2425
2806
  });
package/test/soap.test.js CHANGED
@@ -21,11 +21,12 @@ const { SoapMethodCall } = require('../src/soap.js');
21
21
  const { CampaignException, makeCampaignException } = require('../src/campaign.js');
22
22
  const { DomUtil } = require('../src/domUtil.js');
23
23
  const assert = require('assert');
24
+ const sdk = require('../src/index.js');
24
25
 
25
26
  const URL = "https://soap-test/nl/jsp/soaprouter.jsp";
26
27
 
27
- function makeSoapMethodCall(transport, urn, methodName, sessionToken, securityToken, userAgentString) {
28
- const call = new SoapMethodCall(transport, urn, methodName, sessionToken, securityToken, userAgentString);
28
+ function makeSoapMethodCall(transport, urn, methodName, sessionToken, securityToken, userAgentString, optionalCharset, charset, extraHttpHeaders) {
29
+ const call = new SoapMethodCall(transport, urn, methodName, sessionToken, securityToken, userAgentString, optionalCharset, charset, extraHttpHeaders);
29
30
  return call;
30
31
  }
31
32
 
@@ -387,6 +388,22 @@ describe('SOAP', function() {
387
388
  expect(actualElement.getAttribute("att")).toBe("Hello");
388
389
  });
389
390
 
391
+ it('Should support passing DOM elements instead of document parameters', function() {
392
+ const xml = '<root att="Hello"><child/></root>';
393
+ const doc = DomUtil.parse(xml);
394
+
395
+ const call = makeSoapMethodCall(undefined, "xtk:session", "Document", "$session$", "$security$");
396
+ call.writeDocument("p", doc.documentElement);
397
+ const request = call._createHTTPRequest(URL);
398
+ const env = DomUtil.parse(request.data).documentElement;
399
+ const body = hasChildElement(env, "SOAP-ENV:Body");
400
+ const method = hasChildElement(body, "m:Document");
401
+ const param = hasChildElement(method, "p");
402
+ const actualElement = hasChildElement(param, "root");
403
+ expect(actualElement).toBeTruthy();
404
+ expect(actualElement.getAttribute("att")).toBe("Hello");
405
+ });
406
+
390
407
  it('Should write null document', function() {
391
408
  const call = makeSoapMethodCall(undefined, "xtk:session", "Document", "$session$", "$security$");
392
409
  call.writeDocument("p", null);
@@ -682,6 +699,56 @@ describe('SOAP', function() {
682
699
  });
683
700
  })
684
701
  });
702
+
703
+ describe("Charset encoding", function() {
704
+
705
+ it("Should support no encoding", function() {
706
+ const call = makeSoapMethodCall(undefined, "xtk:session", "Empty");
707
+ const request = call._createHTTPRequest(URL);
708
+ assert.equal(request.url, URL);
709
+ assert.equal(request.headers["Content-type"], "application/soap+xml");
710
+ });
711
+
712
+ it("Should support UTF-8 encoding", function() {
713
+ const call = makeSoapMethodCall(undefined, "xtk:session", "Empty", undefined, undefined, undefined, "UTF-8");
714
+ const request = call._createHTTPRequest(URL);
715
+ assert.equal(request.url, URL);
716
+ assert.equal(request.headers["Content-type"], "application/soap+xml;charset=UTF-8");
717
+ });
718
+
719
+ it("Default encoding should be UTF-8", async () => {
720
+ const connectionParameters = sdk.ConnectionParameters.ofSessionToken("http://acc-sdk:8080", "mc/");
721
+ const client = await sdk.init(connectionParameters);
722
+ client._transport = jest.fn();
723
+ expect(client._connectionParameters._options.charset).toBe('UTF-8');
724
+ const soapCall = client._prepareSoapCall("xtk:persist", "GetEntityIfMoreRecent", true);
725
+ expect (soapCall._charset).toBe('UTF-8');
726
+ const request = soapCall._createHTTPRequest(URL);
727
+ assert.equal(request.headers["Content-type"], "application/soap+xml;charset=UTF-8");
728
+ })
729
+
730
+ it("Default support forcing an empty encoding", async () => {
731
+ const connectionParameters = sdk.ConnectionParameters.ofSessionToken("http://acc-sdk:8080", "mc/", { charset: "" });
732
+ const client = await sdk.init(connectionParameters);
733
+ client._transport = jest.fn();
734
+ expect(client._connectionParameters._options.charset).toBe('');
735
+ const soapCall = client._prepareSoapCall("xtk:persist", "GetEntityIfMoreRecent", true);
736
+ expect (soapCall._charset).toBe('');
737
+ const request = soapCall._createHTTPRequest(URL);
738
+ assert.equal(request.headers["Content-type"], "application/soap+xml");
739
+ })
740
+
741
+ it("Default support forcing an ISO charset", async () => {
742
+ const connectionParameters = sdk.ConnectionParameters.ofSessionToken("http://acc-sdk:8080", "mc/", { charset: "ISO-8859-1" });
743
+ const client = await sdk.init(connectionParameters);
744
+ client._transport = jest.fn();
745
+ expect(client._connectionParameters._options.charset).toBe('ISO-8859-1');
746
+ const soapCall = client._prepareSoapCall("xtk:persist", "GetEntityIfMoreRecent", true);
747
+ expect (soapCall._charset).toBe('ISO-8859-1');
748
+ const request = soapCall._createHTTPRequest(URL);
749
+ assert.equal(request.headers["Content-type"], "application/soap+xml;charset=ISO-8859-1");
750
+ })
751
+ });
685
752
 
686
753
  });
687
754
 
@@ -799,5 +866,23 @@ describe("Campaign exception", () => {
799
866
  expect(req.headers["X-Security-Token"].indexOf("$security$")).toBe(-1);
800
867
  })
801
868
 
869
+ describe('Extra Http headers', () => {
870
+ it("Should take additional headers", () => {
871
+ const call = makeSoapMethodCall(undefined, "xtk:session", "Date", "$session$", "$security$", "My User Agent", undefined, { 'X-ACC-UI-Version': '1.0' });
872
+ const request = call._createHTTPRequest(URL);
873
+ expect(request.headers['User-Agent']).toBe("My User Agent");
874
+ expect(request.headers['X-ACC-UI-Version']).toBe("1.0");
875
+ expect(request.headers['SoapAction']).toBe("xtk:session#Date");
876
+ });
877
+
878
+ it("Should override default headers headers", () => {
879
+ const call = makeSoapMethodCall(undefined, "xtk:session", "Date", "$session$", "$security$", "My User Agent", undefined, { 'X-ACC-UI-Version': '1.0', 'SoapAction': 'My soap action' });
880
+ const request = call._createHTTPRequest(URL);
881
+ expect(request.headers['User-Agent']).toBe("My User Agent");
882
+ expect(request.headers['X-ACC-UI-Version']).toBe("1.0");
883
+ expect(request.headers['SoapAction']).toBe("My soap action");
884
+ });
885
+ });
886
+
802
887
  });
803
888