@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.
- package/.eslintrc.js +2 -1
- package/CHANGELOG.md +23 -0
- package/README.md +104 -27
- package/compile.js +2 -1
- package/package-lock.json +19 -12
- package/package.json +1 -1
- package/samples/011 - basics - packages.js +60 -0
- package/src/application.js +46 -6
- package/src/cache.js +11 -1
- package/src/cacheRefresher.js +227 -0
- package/src/client.js +96 -23
- package/src/index.js +3 -1
- package/src/soap.js +18 -12
- package/src/testUtil.js +2 -2
- package/src/transport.js +17 -2
- package/test/application.test.js +13 -2
- package/test/cacheRefresher.test.js +338 -0
- package/test/caches.test.js +16 -1
- package/test/client.test.js +314 -6
- package/test/mock.js +66 -1
- package/test/soap.test.js +45 -31
- package/.vscode/launch.json +0 -22
|
@@ -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(),
|
|
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
|
-
|
|
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
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
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
|
-
|
|
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
|
|
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}
|
|
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,
|
|
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.
|
|
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
|
|
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
|
-
|
|
546
|
+
request.headers.Cookie = '__sessiontoken=' + this._sessionToken;
|
|
544
547
|
if (this._userAgentString)
|
|
545
|
-
|
|
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
|
-
|
|
552
|
+
request.headers[h] = this._extraHttpHeaders[h];
|
|
550
553
|
}
|
|
551
554
|
|
|
552
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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) => {
|