@adobe/acc-js-sdk 1.1.3 → 1.1.6

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.
@@ -0,0 +1,227 @@
1
+ /*
2
+ Copyright 2022 Adobe. All rights reserved.
3
+ This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License. You may obtain a copy
5
+ of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+
7
+ Unless required by applicable law or agreed to in writing, software distributed under
8
+ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ OF ANY KIND, either express or implied. See the License for the specific language
10
+ governing permissions and limitations under the License.
11
+ */
12
+ (function () {
13
+ "use strict";
14
+
15
+ const { DomUtil } = require("./domUtil.js");
16
+ const XtkCaster = require('./xtkCaster.js').XtkCaster;
17
+ const { Cache } = require('./cache.js');
18
+ const { CampaignException } = require('./campaign.js');
19
+
20
+ /**
21
+ * @private
22
+ * @class
23
+ * @constructor
24
+ * @memberof Campaign
25
+ */
26
+ class RefresherStateCache extends Cache {
27
+
28
+ /**
29
+ * A cache to store state of the refresher. Not intended to be used directly,
30
+ * but an internal cache for the Campaign.Client object
31
+ *
32
+ * Cached object are made of
33
+ * - the key is the propertie name
34
+ * - the value is a string
35
+ *
36
+ * @param {Storage} storage is an optional Storage object, such as localStorage or sessionStorage
37
+ * @param {string} rootKey is an optional root key to use for the storage object
38
+ * @param {number} ttl is the TTL for objects in ms. Defaults to 5 mins
39
+ */
40
+ constructor(storage, rootKey, ttl) {
41
+ super(storage, rootKey, ttl);
42
+ }
43
+
44
+ /**
45
+ * Cache a property of the refresh (buildNumber, last refresh time) and its value
46
+ *
47
+ * @param {string} name is the propertie name
48
+ * @param {string} rawValue string value
49
+ */
50
+ put(name, rawValue) {
51
+ return super.put(name, { value: rawValue });
52
+ }
53
+
54
+ /**
55
+ * Get the value of a property of the refresh (buildNumber, last refresh time)
56
+ *
57
+ * @param {string} name the propertie name
58
+ * @returns {*} the value
59
+ */
60
+ get(name) {
61
+ const option = super.get(name);
62
+ return option ? option.value : undefined;
63
+ }
64
+ }
65
+
66
+ class CacheRefresher {
67
+
68
+ /**
69
+ * A class to refresh regulary a Cache every n seconds, by sending a query to get the last modified entities.
70
+ * The refresh mechanism can be activated by calling client.startRefreshCaches().
71
+ * This mechanism depends on the xtk:session:GetModifiedEntities API which is introduced in ACC 8.4 and above.
72
+ *
73
+ * @param {Cache} cache is the cache to refresh
74
+ * @param {Client} client is the ACC API Client.
75
+ * @param {string} cacheSchemaId is the id of the schema present in the cache to be refreshed every 10 seconds
76
+ * @param {string} rootKey is used as the root key of cache items in the refresher state cache
77
+ */
78
+ constructor(cache, client, cacheSchemaId, rootKey) {
79
+ const connectionParameters = client._connectionParameters;
80
+ this._cache = cache;
81
+ this._client = client;
82
+ this._connectionParameters = connectionParameters;
83
+ this._cacheSchemaId = cacheSchemaId;
84
+
85
+ this._storage = connectionParameters._options._storage;
86
+ this._refresherStateCache = new RefresherStateCache(this._storage, `${rootKey}.RefresherStateCache`, 1000*3600);
87
+
88
+ this._lastTime = undefined;
89
+ this._buildNumber = undefined;
90
+ this._intervalId = null;
91
+ this._running = false;
92
+ }
93
+
94
+ /**
95
+ * Start auto refresh
96
+ * @param {integer} refreshFrequency frequency of the refresh in ms (default value is 10,000 ms)
97
+ */
98
+ startAutoRefresh(refreshFrequency) {
99
+ if (this._intervalId != null) {
100
+ clearInterval(this._intervalId);
101
+ }
102
+ this._intervalId = setInterval(() => {
103
+ this._safeCallAndRefresh();
104
+ }, refreshFrequency || 10000); // every 10 seconds by default
105
+ }
106
+
107
+ // Protect _callAndRefresh from reentrance
108
+ async _safeCallAndRefresh() {
109
+ if (this._running) {
110
+ // This call is already running and maybe taking a long time to complete. Do not make things
111
+ // harder and just skip this run
112
+ return;
113
+ }
114
+ this._running = true;
115
+ try {
116
+ await this._callAndRefresh();
117
+ } catch(ex) {
118
+ if (ex.errorCode === "SDK-000010") {
119
+ // client is not logged, this is not an error.
120
+ return;
121
+ }
122
+ console.warn(`Failed to refresh cache for ${this._cacheSchemaId}`, ex);
123
+ }
124
+ finally {
125
+ this._running = false;
126
+ }
127
+ }
128
+
129
+ // Get last modified entities for the Campaign server and remove from cache last modified entities
130
+ async _callAndRefresh() {
131
+ const that = this;
132
+ const soapCall = this._client._prepareSoapCall("xtk:session", "GetModifiedEntities", true, this._connectionParameters._options.extraHttpHeaders);
133
+
134
+ if (this._lastTime === undefined) {
135
+ const storedTime = this._refresherStateCache.get("time");
136
+ if (storedTime != undefined) {
137
+ this._lastTime = storedTime;
138
+ }
139
+ }
140
+ if (this._buildNumber === undefined) {
141
+ const storedBuildNumber = this._refresherStateCache.get("buildNumber");
142
+ if (storedBuildNumber != undefined) {
143
+ this._buildNumber = storedBuildNumber;
144
+ }
145
+ }
146
+
147
+ // Use Json because xtk:schema does not work directly in DomUtil.parse(`<cache buildNumber="9469" time="2022-06-30T00:00:00.000"><xtk:schema></xtk:schema></cache>`);
148
+ // due to the colon character
149
+ let jsonCache;
150
+ if (this._lastTime === undefined || this._buildNumber === undefined) {
151
+ jsonCache = {
152
+ [this._cacheSchemaId]: {}
153
+ };
154
+ } else {
155
+ jsonCache = {
156
+ buildNumber: this._buildNumber,
157
+ lastModified: this._lastTime,
158
+ [this._cacheSchemaId]: {}
159
+ };
160
+ }
161
+
162
+ const xmlDoc = DomUtil.fromJSON("cache", jsonCache, 'SimpleJson');
163
+ soapCall.writeDocument("cacheEntities", xmlDoc);
164
+
165
+ // Client is not logged: do not attempt to refresh caches at all
166
+ if (!this._client.isLogged())
167
+ throw CampaignException.NOT_LOGGED_IN(soapCall, `Cannot call GetModifiedEntities: session not connected`);
168
+
169
+ // Do a soap call GetModifiedEntities instead of xtksession.GetModifiedEnties because we don't want to go through methodCache
170
+ // which might not contain the method GetModifiedEntities just after a build updgrade from a old version of acc
171
+ return this._client._makeSoapCall(soapCall)
172
+ .then(() => {
173
+ let doc = soapCall.getNextDocument();
174
+ soapCall.checkNoMoreArgs();
175
+ doc = that._client._toRepresentation(doc, 'xml');
176
+ that._lastTime = DomUtil.getAttributeAsString(doc, "time"); // save time to be able to send it as an attribute in the next soap call
177
+ that._buildNumber = DomUtil.getAttributeAsString(doc, "buildNumber");
178
+ that._refresh(doc);
179
+ that._refresherStateCache.put("time", that._lastTime);
180
+ that._refresherStateCache.put("buildNumber", that._buildNumber);
181
+ })
182
+ .catch((ex) => {
183
+ // if the method GetModifiedEntities is not found in this acc version we disable the autoresfresh of the cache
184
+ if (soapCall.methodName == "GetModifiedEntities" && ex.errorCode == "SOP-330006") {
185
+ this.stopAutoRefresh();
186
+ } else {
187
+ throw ex;
188
+ }
189
+ });
190
+ }
191
+
192
+ // Refresh Cache : remove entities modified recently listed in xmlDoc
193
+ _refresh(xmlDoc) {
194
+ const clearCache = XtkCaster.asBoolean(DomUtil.getAttributeAsString(xmlDoc, "emptyCache"));
195
+ if (clearCache == true) {
196
+ this._cache.clear();
197
+ } else {
198
+ var child = DomUtil.getFirstChildElement(xmlDoc, "entityCache");
199
+ while (child) {
200
+ const pkSchemaId = DomUtil.getAttributeAsString(child, "pk");
201
+ const schemaId = DomUtil.getAttributeAsString(child, "schema");
202
+ if (schemaId === this._cacheSchemaId) {
203
+ this._cache.remove(pkSchemaId);
204
+ // Notify listeners to refresh in SchemaCache
205
+ if (schemaId === "xtk:schema") {
206
+ const schemaIds = pkSchemaId.split("|");
207
+ this._client._notifyCacheChangeListeners(schemaIds[1]);
208
+ }
209
+ }
210
+ child = DomUtil.getNextSiblingElement(child);
211
+ }
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Stop auto refreshing the cache
217
+ */
218
+ stopAutoRefresh() {
219
+ clearInterval(this._intervalId);
220
+ this._intervalId = null;
221
+ }
222
+ }
223
+
224
+ // Public exports
225
+ exports.CacheRefresher = CacheRefresher;
226
+
227
+ })();
package/src/client.js CHANGED
@@ -31,6 +31,7 @@ const Cipher = require('./crypto.js').Cipher;
31
31
  const DomUtil = require('./domUtil.js').DomUtil;
32
32
  const MethodCache = require('./methodCache.js').MethodCache;
33
33
  const OptionCache = require('./optionCache.js').OptionCache;
34
+ const CacheRefresher = require('./cacheRefresher.js').CacheRefresher;
34
35
  const request = require('./transport.js').request;
35
36
  const Application = require('./application.js').Application;
36
37
  const EntityAccessor = require('./entityAccessor.js').EntityAccessor;
@@ -105,13 +106,13 @@ const xtkObjectHandler = {
105
106
  * @private
106
107
  * @memberof Campaign
107
108
  */
108
- const clientHandler = (representation, headers) => {
109
+ const clientHandler = (representation, headers, pushDownOptions) => {
109
110
  return {
110
111
  get: function(client, namespace) {
111
112
 
112
113
  // 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));
114
+ if (namespace == "xml") return new Proxy(client, clientHandler("xml", headers, pushDownOptions));
115
+ if (namespace == "json") return new Proxy(client, clientHandler("SimpleJson", headers, pushDownOptions));
115
116
 
116
117
  // Override HTTP headers (NLWS.headers({...}))
117
118
  // Unlike NLWS.xml or NLWS.json, NLWS.headers returns a function. This function takes key/value
@@ -123,16 +124,30 @@ const clientHandler = (representation, headers) => {
123
124
  const newHeaders = {};
124
125
  if (headers) for (let h in headers) newHeaders[h] = headers[h];
125
126
  if (methodHeaders) for (let h in methodHeaders) newHeaders[h] = methodHeaders[h];
126
- return new Proxy(client, clientHandler(representation, newHeaders));
127
+ return new Proxy(client, clientHandler(representation, newHeaders, pushDownOptions));
128
+ };
129
+
130
+ // Pushes down addition options to the SOAP and transport layers
131
+ if (namespace == "pushDown") return (methodPushDownOptions) => {
132
+ // Build of copy of the pushDownOptions in order to accomodate
133
+ // chained calls, such as NLWS.pushDown(...).pushDown(...)
134
+ const newPushDownOptions = {};
135
+ if (pushDownOptions) for (let h in pushDownOptions) newPushDownOptions[h] = pushDownOptions[h];
136
+ if (methodPushDownOptions) for (let h in methodPushDownOptions) newPushDownOptions[h] = methodPushDownOptions[h];
137
+ return new Proxy(client, clientHandler(representation, headers, newPushDownOptions));
127
138
  };
128
139
 
129
140
  return new Proxy({ client:client, namespace:namespace}, {
130
141
  get: function(callContext, methodName) {
131
142
  callContext.representation = representation;
132
143
  callContext.headers = callContext.headers || client._connectionParameters._options.extraHttpHeaders;
144
+ callContext.pushDownOptions = {};
133
145
  if (headers) {
134
146
  for (let h in headers) callContext.headers[h] = headers[h];
135
147
  }
148
+ if (pushDownOptions) {
149
+ for (let h in pushDownOptions) callContext.pushDownOptions[h] = pushDownOptions[h];
150
+ }
136
151
 
137
152
  if (methodName == ".") return callContext;
138
153
 
@@ -270,6 +285,8 @@ class Credentials {
270
285
  * @property {{ name:string, value:string}} extraHttpHeaders - optional key/value pair of HTTP header (will override any other headers)
271
286
  * @property {string} clientApp - optional name/version of the application client of the SDK. This will be passed in HTTP headers for troubleshooting
272
287
  * @property {boolean} noSDKHeaders - set to disable "ACC-SDK" HTTP headers
288
+ * @property {boolean} noMethodInURL - Can be set to true to remove the method name from the URL
289
+ * @property {number} timeout - Can be set to change the HTTP call timeout. Value is passed in ms.
273
290
  * @memberOf Campaign
274
291
  */
275
292
 
@@ -291,7 +308,7 @@ class ConnectionParameters {
291
308
  constructor(endpoint, credentials, options) {
292
309
  // this._options will be populated with the data from "options" and with
293
310
  // default values. But the "options" parameter will not be modified
294
- this._options = {};
311
+ this._options = Object.assign({}, options);
295
312
 
296
313
  // Default value
297
314
  if (options === undefined || options === null)
@@ -340,6 +357,7 @@ class ConnectionParameters {
340
357
  }
341
358
  this._options.clientApp = options.clientApp;
342
359
  this._options.noSDKHeaders = !!options.noSDKHeaders;
360
+ this._options.noMethodInURL = !!options.noMethodInURL;
343
361
  }
344
362
 
345
363
  /**
@@ -515,13 +533,16 @@ class Client {
515
533
  const rootKey = `acc.js.sdk.${sdk.getSDKVersion().version}.${instanceKey}.cache`;
516
534
 
517
535
  this._entityCache = new XtkEntityCache(this._storage, `${rootKey}.XtkEntityCache`, connectionParameters._options.entityCacheTTL);
536
+ this._entityCacheRefresher = new CacheRefresher(this._entityCache, this, "xtk:schema", `${rootKey}.XtkEntityCache`);
518
537
  this._methodCache = new MethodCache(this._storage, `${rootKey}.MethodCache`, connectionParameters._options.methodCacheTTL);
519
538
  this._optionCache = new OptionCache(this._storage, `${rootKey}.OptionCache`, connectionParameters._options.optionCacheTTL);
539
+ this._optionCacheRefresher = new CacheRefresher(this._optionCache, this, "xtk:option", `${rootKey}.OptionCache`);
520
540
  this.NLWS = new Proxy(this, clientHandler());
521
541
 
522
542
  this._transport = connectionParameters._options.transport;
523
543
  this._traceAPICalls = connectionParameters._options.traceAPICalls;
524
544
  this._observers = [];
545
+ this._cacheChangeListeners = [];
525
546
  this._refreshClient = connectionParameters._options.refreshClient;
526
547
 
527
548
  // expose utilities
@@ -716,10 +737,11 @@ class Client {
716
737
  * @return {SOAP.SoapMethodCall} a SoapMethodCall which have been initialized with security tokens... and to which the method
717
738
  * parameters should be set
718
739
  */
719
- _prepareSoapCall(urn, method, internal, extraHttpHeaders) {
740
+ _prepareSoapCall(urn, method, internal, extraHttpHeaders, pushDownOptions) {
720
741
  const soapCall = new SoapMethodCall(this._transport, urn, method,
721
742
  this._sessionToken, this._securityToken,
722
- this._getUserAgentString(), this._connectionParameters._options.charset,
743
+ this._getUserAgentString(),
744
+ Object.assign({}, this._connectionParameters._options, pushDownOptions),
723
745
  extraHttpHeaders);
724
746
  soapCall.internal = !!internal;
725
747
  return soapCall;
@@ -831,6 +853,7 @@ class Client {
831
853
  that._sessionToken = credentials._sessionToken;
832
854
  that._securityToken = "";
833
855
  that.application = new Application(that);
856
+ that.application._registerCacheChangeListener();
834
857
  return Promise.resolve();
835
858
  }
836
859
  else if (credentials._type == "SecurityToken") {
@@ -839,6 +862,7 @@ class Client {
839
862
  that._sessionToken = "";
840
863
  that._securityToken = credentials._securityToken;
841
864
  that.application = new Application(that);
865
+ that.application._registerCacheChangeListener();
842
866
  return Promise.resolve();
843
867
  }
844
868
  else if (credentials._type == "UserPassword" || credentials._type == "BearerToken") {
@@ -890,6 +914,7 @@ class Client {
890
914
  that._securityToken = securityToken;
891
915
 
892
916
  that.application = new Application(that);
917
+ that.application._registerCacheChangeListener();
893
918
  });
894
919
  }
895
920
  else {
@@ -915,10 +940,13 @@ class Client {
915
940
  logoff() {
916
941
  var that = this;
917
942
  if (!that.isLogged()) return;
943
+ that.application._unregisterCacheChangeListener();
944
+ that._unregisterAllCacheChangeListeners();
945
+ this.stopRefreshCaches();
918
946
  const credentials = this._connectionParameters._credentials;
919
947
  if (credentials._type != "SessionToken" && credentials._type != "AnonymousUser") {
920
948
  var soapCall = that._prepareSoapCall("xtk:session", "Logoff", false, this._connectionParameters._options.extraHttpHeaders);
921
- return this._makeSoapCall(soapCall).then(function() {
949
+ return this._makeSoapCall(soapCall).then(function() {
922
950
  that._sessionToken = "";
923
951
  that._securityToken = "";
924
952
  that.application = null;
@@ -940,15 +968,15 @@ class Client {
940
968
  * @return the option value, casted in the expected data type. If the option does not exist, it will return null.
941
969
  */
942
970
  async getOption(name, useCache = true) {
943
- var value;
944
- if (useCache)
945
- value = this._optionCache.get(name);
946
- if (value === undefined) {
947
- const option = await this.NLWS.xtkSession.getOption(name);
948
- value = this._optionCache.put(name, option);
949
- this._optionCache.put(name, option);
950
- }
951
- return value;
971
+ var value;
972
+ if (useCache) {
973
+ value = this._optionCache.get(name);
974
+ }
975
+ if (value === undefined) {
976
+ const option = await this.NLWS.xtkSession.getOption(name);
977
+ value = this._optionCache.put(name, option);
978
+ }
979
+ return value;
952
980
  }
953
981
 
954
982
  /**
@@ -1014,6 +1042,50 @@ class Client {
1014
1042
  this.clearOptionCache();
1015
1043
  }
1016
1044
 
1045
+ /**
1046
+ * Start auto refresh of all caches
1047
+ * @param {integer} refreshFrequency refresh frequency in ms. 10000 ms by default.
1048
+ */
1049
+ startRefreshCaches(refreshFrequency) {
1050
+ if (refreshFrequency === undefined || refreshFrequency === null)
1051
+ refreshFrequency = 10000;
1052
+ this._optionCacheRefresher.startAutoRefresh(refreshFrequency);
1053
+ // Start auto refresh for entityCache a little later
1054
+ setTimeout(() => { this._entityCacheRefresher.startAutoRefresh(refreshFrequency); }, refreshFrequency/2);
1055
+ }
1056
+ /**
1057
+ * Stop auto refresh of all caches
1058
+ */
1059
+ stopRefreshCaches() {
1060
+ this._optionCacheRefresher.stopAutoRefresh();
1061
+ this._entityCacheRefresher.stopAutoRefresh();
1062
+ }
1063
+
1064
+ // Register a callback to be called when a schema has been modified and should be removed
1065
+ // from the caches
1066
+ _registerCacheChangeListener(listener) {
1067
+ this._cacheChangeListeners.push(listener);
1068
+ }
1069
+
1070
+ // Unregister a cache change listener
1071
+ _unregisterCacheChangeListener(listener) {
1072
+ for (var i = 0; i < this._cacheChangeListeners.length; i++) {
1073
+ if (this._cacheChangeListeners[i] == listener) {
1074
+ this._cacheChangeListeners.splice(i, 1);
1075
+ break;
1076
+ }
1077
+ }
1078
+ }
1079
+
1080
+ // Unregister all cache change listener
1081
+ _unregisterAllCacheChangeListeners() {
1082
+ this._cacheChangeListeners = [];
1083
+ }
1084
+
1085
+ _notifyCacheChangeListeners(schemaId) {
1086
+ this._cacheChangeListeners.map((listener) => listener.invalidateCacheItem(schemaId));
1087
+ }
1088
+
1017
1089
  /**
1018
1090
  * Tests if a package is installed
1019
1091
  *
@@ -1083,11 +1155,12 @@ class Client {
1083
1155
  var that = this;
1084
1156
  var entity = that._entityCache.get("xtk:schema", schemaId);
1085
1157
  if (!entity) {
1086
- entity = await that.getEntityIfMoreRecent("xtk:schema", schemaId, "xml", internal);
1087
- }
1088
- if (entity)
1158
+ entity = await that.getEntityIfMoreRecent("xtk:schema", schemaId, "xml", internal);
1159
+ if (entity) {
1089
1160
  that._entityCache.put("xtk:schema", schemaId, entity);
1090
-
1161
+ that._methodCache.put(entity);
1162
+ }
1163
+ }
1091
1164
  entity = that._toRepresentation(entity, representation);
1092
1165
  return entity;
1093
1166
  }
@@ -1162,7 +1235,7 @@ class Client {
1162
1235
  // console.log(method.toXMLString());
1163
1236
 
1164
1237
  var urn = that._methodCache.getSoapUrn(schemaId, methodName);
1165
- var soapCall = that._prepareSoapCall(urn, methodName, false, callContext.headers);
1238
+ var soapCall = that._prepareSoapCall(urn, methodName, false, callContext.headers, callContext.pushDownOptions);
1166
1239
 
1167
1240
  // If method is called with one parameter which is a function, then we assume it's a hook: the function will return
1168
1241
  // the actual list of parameters
@@ -1234,7 +1307,7 @@ class Client {
1234
1307
  if (type == "DOMDocument")
1235
1308
  soapCall.writeDocument(paramName, xmlValue);
1236
1309
  else
1237
- soapCall.writeElement(paramName, xmlValue.documentElement);
1310
+ soapCall.writeElement(paramName, xmlValue);
1238
1311
  }
1239
1312
  else
1240
1313
  throw CampaignException.BAD_SOAP_PARAMETER(soapCall, paramName, paramValue, `Unsupported parameter type '${type}' for parameter '${paramName}' of method '${methodName}' of schema '${schemaId}`);
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,14 +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
82
  * @param {{ name:string, value:string}} extraHttpHeaders key/value pair of HTTP header (will override any other headers)
83
83
  * @memberof SOAP
84
84
  */
85
85
  class SoapMethodCall {
86
86
 
87
- constructor(transport, urn, methodName, sessionToken, securityToken, userAgentString, charset, extraHttpHeaders) {
87
+ constructor(transport, urn, methodName, sessionToken, securityToken, userAgentString, pushDownOptions, extraHttpHeaders) {
88
88
  this.request = undefined; // The HTTP request (object litteral passed to the transport layer)
89
+ this.requestOptions = undefined;
89
90
  this.response = undefined; // The HTTP response object (in case of success)
90
91
 
91
92
  // Current URN and method (for error reporting)
@@ -102,7 +103,8 @@ class SoapMethodCall {
102
103
  this._sessionToken = sessionToken || "";
103
104
  this._securityToken = securityToken || "";
104
105
  this._userAgentString = userAgentString;
105
- this._charset = charset || "";
106
+ this._pushDownOptions = pushDownOptions || {};
107
+ this._charset = this._pushDownOptions.charset || '';
106
108
  this._extraHttpHeaders = extraHttpHeaders || {};
107
109
 
108
110
  // THe SOAP call being built
@@ -289,6 +291,7 @@ class SoapMethodCall {
289
291
  writeElement(tag, element) {
290
292
  const node = this._addNode(tag, "ns:Element", null, SOAP_ENCODING_XML);
291
293
  if (element !== null && element !== undefined) {
294
+ if (element.nodeType === 9) element = element.documentElement;
292
295
  const child = this._doc.importNode(element, true);
293
296
  node.appendChild(child);
294
297
  }
@@ -517,7 +520,7 @@ class SoapMethodCall {
517
520
  * @param {string} url is the Campaign SOAP endpoint (soaprouter.jsp)
518
521
  * @returns {Object} an options object describing the HTTP request, with cookies, headers and body
519
522
  */
520
- _createHTTPRequest(url) {
523
+ _createHTTPRequest(url, requestOptions) {
521
524
 
522
525
  const headers = {
523
526
  'Content-type': `application/soap+xml${this._charset ? ";charset=" + this._charset : ""}`,
@@ -533,23 +536,24 @@ class SoapMethodCall {
533
536
  if (this.internal) headers["ACC-SDK-Call-Internal"] = "1";
534
537
  }
535
538
 
536
- const options = {
539
+ const request = {
537
540
  url: url,
538
541
  method: 'POST',
539
542
  headers: headers,
540
543
  data: DomUtil.toXMLString(this._doc)
541
544
  };
542
545
  if (this._sessionToken)
543
- options.headers.Cookie = '__sessiontoken=' + this._sessionToken;
546
+ request.headers.Cookie = '__sessiontoken=' + this._sessionToken;
544
547
  if (this._userAgentString)
545
- options.headers['User-Agent'] = this._userAgentString;
548
+ request.headers['User-Agent'] = this._userAgentString;
546
549
 
547
550
  // Override http headers with custom headers
548
551
  for (let h in this._extraHttpHeaders) {
549
- options.headers[h] = this._extraHttpHeaders[h];
552
+ request.headers[h] = this._extraHttpHeaders[h];
550
553
  }
551
554
 
552
- return options;
555
+ const extraOptions = Object.assign({}, this._pushDownOptions, requestOptions);
556
+ return [ request, extraOptions ];
553
557
  }
554
558
 
555
559
  /**
@@ -595,9 +599,11 @@ class SoapMethodCall {
595
599
  sessionTokenElem.textContent = this._sessionToken;
596
600
  this._method.prepend(sessionTokenElem);
597
601
  }
598
- const options = this._createHTTPRequest(url);
602
+ const noMethodInURL = !!this._pushDownOptions.noMethodInURL;
603
+ const actualUrl = noMethodInURL ? url : `${url}?${this.urn}:${this.methodName}`;
604
+
599
605
  // Prepare request and empty response objects
600
- this.request = options;
606
+ [this.request, this.requestOptions] = this._createHTTPRequest(actualUrl);
601
607
  this.response = undefined;
602
608
  }
603
609
 
@@ -610,7 +616,7 @@ class SoapMethodCall {
610
616
  */
611
617
  async execute() {
612
618
  const that = this;
613
- const promise = this._transport(this.request);
619
+ const promise = this._transport(this.request, this.requestOptions);
614
620
  return promise.then(function(body) {
615
621
  if (body.indexOf(`XSV-350008`) != -1)
616
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) => {