@adobe/acc-js-sdk 1.0.5 → 1.0.8
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/.github/workflows/codeql-analysis.yml +70 -0
- package/.vscode/launch.json +0 -1
- package/CHANGELOG.md +40 -0
- package/README.md +199 -63
- package/compile.js +3 -0
- package/package-lock.json +2175 -3383
- package/package.json +6 -7
- package/samples/002 - basics - schemas.js +3 -3
- package/samples/020 - encryption.js +5 -5
- package/src/application.js +427 -77
- package/src/cache.js +275 -0
- package/src/campaign.js +4 -0
- package/src/client.js +108 -30
- package/src/domUtil.js +6 -3
- package/src/entityAccessor.js +5 -5
- package/src/index.js +58 -2
- package/src/methodCache.js +21 -2
- package/src/optionCache.js +5 -1
- package/src/soap.js +46 -19
- package/src/testUtil.js +47 -0
- package/src/util.js +0 -229
- package/src/xtkCaster.js +80 -6
- package/src/xtkEntityCache.js +19 -3
- package/test/application.test.js +684 -616
- package/test/caches.test.js +148 -2
- package/test/client.test.js +341 -14
- package/test/crypto.test.js +16 -12
- package/test/domUtil.test.js +150 -2
- package/test/escape.test.js +79 -47
- package/test/index.test.js +69 -1
- package/test/mock.js +59 -9
- package/test/soap.test.js +0 -6
- package/test/testUtil.test.js +64 -0
- package/test/util.test.js +2 -1
- package/test/xtkCaster.test.js +219 -1
package/src/cache.js
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020 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
|
+
|
|
16
|
+
/**********************************************************************************
|
|
17
|
+
*
|
|
18
|
+
* Cache utilities
|
|
19
|
+
*
|
|
20
|
+
*********************************************************************************/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @namespace Utils
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
/**********************************************************************************
|
|
28
|
+
*
|
|
29
|
+
* A simple cache for XtkEntities, options, etc.
|
|
30
|
+
*
|
|
31
|
+
*********************************************************************************/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @private
|
|
35
|
+
* @class
|
|
36
|
+
* @constructor
|
|
37
|
+
* @memberof Utils
|
|
38
|
+
*/
|
|
39
|
+
class SafeStorage {
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A wrapper to the Storage interface (LocalStorage, etc.) which is "safe", i.e.
|
|
43
|
+
*
|
|
44
|
+
* - it will never throw / support local stroage to be undefined or not accessible
|
|
45
|
+
* - Handle the notion of "root key", i.e. prefix
|
|
46
|
+
* - Set/get values as JSON only and not as strings
|
|
47
|
+
* - Silently caches all exceptions
|
|
48
|
+
* - Automatically remove from storage cached values which are not valid, expired, or cannot be parsed
|
|
49
|
+
*
|
|
50
|
+
* SafeStorage objects are created automatically by Caches
|
|
51
|
+
*
|
|
52
|
+
* @param {Storage} delegate an optional delegate options, confomring to the Storage interface (getItem, setItem, removeItem)
|
|
53
|
+
* @param {string} rootKey an optional prefix which will be prepend to all keys
|
|
54
|
+
* @param {function} serDeser serializarion & deserialization function. First parameter is the object or value to serialize
|
|
55
|
+
* or deserialize, and second parameter is true for serialization or false for deserialization
|
|
56
|
+
*/
|
|
57
|
+
constructor(delegate, rootKey, serDeser) {
|
|
58
|
+
if (!serDeser)
|
|
59
|
+
serDeser = (item, serDeser) => {
|
|
60
|
+
if (serDeser) {
|
|
61
|
+
if (!item) throw Error(`Cannot serialize falsy cached item`);
|
|
62
|
+
if (typeof item !== "object") throw Error(`Cannot serialize non-object`);
|
|
63
|
+
return JSON.stringify(item);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
if (!item) throw Error(`Cannot deserialize falsy cached item`);
|
|
67
|
+
return JSON.parse(item);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
this._delegate = delegate;
|
|
71
|
+
this._rootKey = rootKey ? `${rootKey}$` : "";
|
|
72
|
+
this._serDeser = serDeser;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get an item from storage
|
|
77
|
+
* @param {string} key the item key (relative to the root key)
|
|
78
|
+
* @returns {Utils.CachedObject} the cached object, or undefined if not found.
|
|
79
|
+
* The storage serDeser fucntion will be used to deserialize the cached value
|
|
80
|
+
*/
|
|
81
|
+
getItem(key) {
|
|
82
|
+
if (!this._delegate || this._rootKey === undefined || this._rootKey === null)
|
|
83
|
+
return;
|
|
84
|
+
const itemKey = `${this._rootKey}${key}`;
|
|
85
|
+
const raw = this._delegate.getItem(itemKey);
|
|
86
|
+
if (!raw)
|
|
87
|
+
return undefined;
|
|
88
|
+
try {
|
|
89
|
+
return this._serDeser(raw, false);
|
|
90
|
+
} catch(ex) {
|
|
91
|
+
this.removeItem(key);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Put an item into storage
|
|
97
|
+
* @param {string} key the item key (relative to the root key)
|
|
98
|
+
* @param {Utils.CachedObject} json the object to cache
|
|
99
|
+
* The storage serDeser fucntion will be used to serialize the cached value
|
|
100
|
+
*/
|
|
101
|
+
setItem(key, json) {
|
|
102
|
+
if (!this._delegate || this._rootKey === undefined || this._rootKey === null)
|
|
103
|
+
return;
|
|
104
|
+
try {
|
|
105
|
+
//if (json && typeof json === "object") {
|
|
106
|
+
const raw = this._serDeser(json, true);
|
|
107
|
+
this._delegate.setItem(`${this._rootKey}${key}`, raw);
|
|
108
|
+
return;
|
|
109
|
+
} catch(ex) { /* Ignore errors in safe class */
|
|
110
|
+
}
|
|
111
|
+
this.removeItem(key);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Removes an item from the storage
|
|
116
|
+
* @param {string} key the item key (relative to the root key)
|
|
117
|
+
*/
|
|
118
|
+
removeItem(key) {
|
|
119
|
+
if (!this._delegate || this._rootKey === undefined || this._rootKey === null)
|
|
120
|
+
return;
|
|
121
|
+
try {
|
|
122
|
+
this._delegate.removeItem(`${this._rootKey}${key}`);
|
|
123
|
+
} catch(ex) { /* Ignore errors in safe class */
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* @private
|
|
130
|
+
* @class
|
|
131
|
+
* @constructor
|
|
132
|
+
* @memberof Utils
|
|
133
|
+
*/
|
|
134
|
+
class CachedObject {
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* An object in the cache, i.e. a wrapped to a cached value and additional metadata to manage the cache.
|
|
138
|
+
* Do not create such objects directly, they are 100% managed by the Cache object
|
|
139
|
+
*
|
|
140
|
+
* @param {*} value the cached value
|
|
141
|
+
* @param {number} cachedAt the timestamp at which the value was cached
|
|
142
|
+
* @param {number} expiresAt the timestamp at which the cached value expires
|
|
143
|
+
*/
|
|
144
|
+
constructor(value, cachedAt, expiresAt) {
|
|
145
|
+
this.value = value;
|
|
146
|
+
this.cachedAt = cachedAt;
|
|
147
|
+
this.expiresAt = expiresAt;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* @private
|
|
153
|
+
* @class
|
|
154
|
+
* @constructor
|
|
155
|
+
* @memberof Utils
|
|
156
|
+
*/
|
|
157
|
+
class Cache {
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* A general purpose in-memory cache with TTL. In addition to caching in memory, the cache has the ability to delegate the caching
|
|
161
|
+
* to a persistent cache, such as the browser localStorage. The interface is 100% synchronous.
|
|
162
|
+
*
|
|
163
|
+
* By default, caches take a single parameter for the key. It is possible however to use a more complex scenario by setting a makeKeyFn.
|
|
164
|
+
* When set, such a function will take 1 or more arguments and will be responsible to create a primitive key (a string) from the arguments.
|
|
165
|
+
* The cache public APIs : get and put therefore can take a variable number of key arguments, which will be combined into the actual primitive key.
|
|
166
|
+
*
|
|
167
|
+
* @param {Storage} storage is an optional Storage object, such as localStorage or sessionStorage. This object will be wrapped into a SafeStorage object to ensure access is safe and will not throw any exceptions
|
|
168
|
+
* @param {string} rootKey is an optional root key to use for the storage object
|
|
169
|
+
* @param {number} ttl is the TTL for objects in ms. Defaults to 5 mins
|
|
170
|
+
* @param {function} makeKeyFn is an optional function which will generate a key for objects in the cache. It's passed the arguments of the cache 'get' function
|
|
171
|
+
* @param {function} serDeser serializarion & deserialization function. First parameter is the object or value to serialize
|
|
172
|
+
* or deserialize, and second parameter is true for serialization or false for deserialization
|
|
173
|
+
*/
|
|
174
|
+
constructor(storage, rootKey, ttl, makeKeyFn, serDeser) {
|
|
175
|
+
this._storage = new SafeStorage(storage, rootKey, serDeser);
|
|
176
|
+
this._ttl = ttl || 1000*300;
|
|
177
|
+
this._makeKeyFn = makeKeyFn || ((x) => x);
|
|
178
|
+
this._cache = {};
|
|
179
|
+
// timestamp at which the cache was last cleared
|
|
180
|
+
this._lastCleared = this._loadLastCleared();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Load timestamp at which the cache was last cleared
|
|
184
|
+
_loadLastCleared() {
|
|
185
|
+
const json = this._storage.getItem("lastCleared");
|
|
186
|
+
return json ? json.timestamp : undefined;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
_saveLastCleared() {
|
|
190
|
+
const now = Date.now();
|
|
191
|
+
this._lastCleared = now;
|
|
192
|
+
this._storage.setItem("lastCleared", { timestamp: now});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Load from local storage
|
|
196
|
+
_load(key) {
|
|
197
|
+
const json = this._storage.getItem(key);
|
|
198
|
+
if (!json || !json.cachedAt || json.cachedAt <= this._lastCleared) {
|
|
199
|
+
this._storage.removeItem(key);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
return json;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Save to local storage
|
|
206
|
+
_save(key, cached) {
|
|
207
|
+
this._storage.setItem(key, cached);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Remove from local storage
|
|
211
|
+
_remove(key) {
|
|
212
|
+
this._storage.removeItem(key);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
_getIfActive(key) {
|
|
216
|
+
// In memory cache?
|
|
217
|
+
var cached = this._cache[key];
|
|
218
|
+
// Local storage ?
|
|
219
|
+
if (!cached) {
|
|
220
|
+
cached = this._load(key);
|
|
221
|
+
this._cache[key] = cached;
|
|
222
|
+
}
|
|
223
|
+
if (!cached)
|
|
224
|
+
return undefined;
|
|
225
|
+
if (cached.expiresAt <= Date.now()) {
|
|
226
|
+
delete this._cache[key];
|
|
227
|
+
this._remove(key);
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
return cached.value;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Get a value from the cache
|
|
235
|
+
* @param {*} key the key or keys of the value to retreive
|
|
236
|
+
* @returns {*} the cached value, or undefined if not found
|
|
237
|
+
*/
|
|
238
|
+
get() {
|
|
239
|
+
const key = this._makeKeyFn.apply(this, arguments);
|
|
240
|
+
const cached = this._getIfActive(key);
|
|
241
|
+
return cached;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Put a value from the cache
|
|
246
|
+
* @param {*} key the key or keys of the value to retreive
|
|
247
|
+
* @param {*} value the value to cache
|
|
248
|
+
* @returns {CachedObject} a cached object containing the cached value
|
|
249
|
+
*/
|
|
250
|
+
put() {
|
|
251
|
+
const value = arguments[arguments.length -1];
|
|
252
|
+
const key = this._makeKeyFn.apply(this, arguments);
|
|
253
|
+
const now = Date.now();
|
|
254
|
+
const expiresAt = now + this._ttl;
|
|
255
|
+
const cached = new CachedObject(value, now, expiresAt);
|
|
256
|
+
this._cache[key] = cached;
|
|
257
|
+
this._save(key, cached);
|
|
258
|
+
return cached;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Removes everything from the cache. It does not directly removes data from persistent storage if there is, but it marks the cache
|
|
263
|
+
* as cleared so that subsequent get operation will not actually return any data cached in persistent storage
|
|
264
|
+
*/
|
|
265
|
+
clear() {
|
|
266
|
+
this._cache = {};
|
|
267
|
+
this._saveLastCleared();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Public expots
|
|
272
|
+
exports.SafeStorage = SafeStorage;
|
|
273
|
+
exports.Cache = Cache;
|
|
274
|
+
|
|
275
|
+
})();
|
package/src/campaign.js
CHANGED
|
@@ -37,6 +37,7 @@ const { Util } = require("./util.js");
|
|
|
37
37
|
static SOAP_UNKNOWN_METHOD(schema, method, details) { return new CampaignException(undefined, 400, 16384, `SDK-000009 Unknown method '${method}' of schema '${schema}'`, details); }
|
|
38
38
|
static NOT_LOGGED_IN(call, details) { return new CampaignException( call, 400, 16384, `SDK-000010 Cannot call API because client is not logged in`, details); }
|
|
39
39
|
static DECRYPT_ERROR(details) { return new CampaignException(undefined, 400, 16384, `SDK-000011 "Cannot decrypt password: password marker is missing`, details); }
|
|
40
|
+
static SESSION_EXPIRED() { return new CampaignException(undefined, 401, 16384, `SDK-000012 "Session has expired or is invalid. Please reconnect.`); }
|
|
40
41
|
|
|
41
42
|
|
|
42
43
|
/**
|
|
@@ -223,6 +224,9 @@ function makeCampaignException(call, err) {
|
|
|
223
224
|
faultString = err.data;
|
|
224
225
|
details = undefined;
|
|
225
226
|
}
|
|
227
|
+
// Session expiration case must return a 401
|
|
228
|
+
if (err.data && err.data.indexOf(`XSV-350008`) != -1)
|
|
229
|
+
return CampaignException.SESSION_EXPIRED();
|
|
226
230
|
return new CampaignException(call, err.statusCode, "", faultString, details, err);
|
|
227
231
|
}
|
|
228
232
|
|
package/src/client.js
CHANGED
|
@@ -178,11 +178,15 @@ class Credentials {
|
|
|
178
178
|
*/
|
|
179
179
|
constructor(type, sessionToken, securityToken) {
|
|
180
180
|
if (type != "UserPassword" && type != "ImsServiceToken" && type != "SessionToken" &&
|
|
181
|
-
type != "AnonymousUser" && type != "SecurityToken")
|
|
181
|
+
type != "AnonymousUser" && type != "SecurityToken" && type != "BearerToken")
|
|
182
182
|
throw CampaignException.INVALID_CREDENTIALS_TYPE(type);
|
|
183
183
|
this._type = type;
|
|
184
184
|
this._sessionToken = sessionToken || "";
|
|
185
185
|
this._securityToken = securityToken || "";
|
|
186
|
+
if (type == "BearerToken") {
|
|
187
|
+
this._bearerToken = sessionToken || "";
|
|
188
|
+
this._sessionToken = "";
|
|
189
|
+
}
|
|
186
190
|
}
|
|
187
191
|
|
|
188
192
|
/**
|
|
@@ -221,16 +225,16 @@ class Credentials {
|
|
|
221
225
|
|
|
222
226
|
/**
|
|
223
227
|
* @typedef {Object} ConnectionOptions
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
228
|
+
* @property {string} representation - the representation to use, i.e. "SimpleJson" (the default), "BadgerFish", or "xml"
|
|
229
|
+
* @property {boolean} rememberMe - The Campaign `rememberMe` attribute which can be used to extend the lifetime of session tokens
|
|
230
|
+
* @property {number} entityCacheTTL - The TTL (in milliseconds) after which cached XTK entities expire. Defaults to 5 minutes
|
|
231
|
+
* @property {number} methodCacheTTL - The TTL (in milliseconds) after which cached XTK methods expire. Defaults to 5 minutes
|
|
232
|
+
* @property {number} optionCacheTTL - The TTL (in milliseconds) after which cached XTK options expire. Defaults to 5 minutes
|
|
233
|
+
* @property {boolean} traceAPICalls - Activates the tracing of all API calls
|
|
234
|
+
* @property {Utils.Transport} transport - Overrides the transport (i.e. HTTP layer)
|
|
235
|
+
* @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
|
+
* @property {Storage} storage - Overrides the storage interface (i.e. LocalStorage)
|
|
237
|
+
* @memberOf Campaign
|
|
234
238
|
*/
|
|
235
239
|
|
|
236
240
|
|
|
@@ -292,6 +296,7 @@ class ConnectionParameters {
|
|
|
292
296
|
}
|
|
293
297
|
}
|
|
294
298
|
this._options._storage = storage;
|
|
299
|
+
this._options.refreshClient = options.refreshClient;
|
|
295
300
|
}
|
|
296
301
|
|
|
297
302
|
/**
|
|
@@ -308,6 +313,18 @@ class ConnectionParameters {
|
|
|
308
313
|
return new ConnectionParameters(endpoint, credentials, options);
|
|
309
314
|
}
|
|
310
315
|
|
|
316
|
+
/**
|
|
317
|
+
* Creates connection parameters for a Campaign instance from bearer token
|
|
318
|
+
*
|
|
319
|
+
* @param {string} endpoint The campaign endpoint (URL)
|
|
320
|
+
* @param {string} bearerToken IMS bearer token
|
|
321
|
+
* @param {*} options connection options
|
|
322
|
+
* @returns {ConnectionParameters} a ConnectionParameters object which can be used to create a Client
|
|
323
|
+
*/
|
|
324
|
+
static ofBearerToken(endpoint, bearerToken, options) {
|
|
325
|
+
const credentials = new Credentials("BearerToken", bearerToken);
|
|
326
|
+
return new ConnectionParameters(endpoint, credentials, options);
|
|
327
|
+
}
|
|
311
328
|
/**
|
|
312
329
|
* Creates connection parameters for a Campaign instance, using an IMS service token and a user name (the user to impersonate)
|
|
313
330
|
*
|
|
@@ -462,6 +479,7 @@ class Client {
|
|
|
462
479
|
this._transport = connectionParameters._options.transport;
|
|
463
480
|
this._traceAPICalls = connectionParameters._options.traceAPICalls;
|
|
464
481
|
this._observers = [];
|
|
482
|
+
this._refreshClient = connectionParameters._options.refreshClient;
|
|
465
483
|
|
|
466
484
|
// expose utilities
|
|
467
485
|
|
|
@@ -624,6 +642,17 @@ class Client {
|
|
|
624
642
|
if (credentialsType == "AnonymousUser")
|
|
625
643
|
return true;
|
|
626
644
|
|
|
645
|
+
// When using bearer token authentication we are considered logged only after
|
|
646
|
+
// the bearer token has been converted into session token and security token
|
|
647
|
+
// by method xtk:session#BearerTokenLogon
|
|
648
|
+
if( credentialsType == "BearerToken")
|
|
649
|
+
return this._sessionToken != undefined &&
|
|
650
|
+
this._sessionToken != null &&
|
|
651
|
+
this._sessionToken != "" &&
|
|
652
|
+
this._securityToken != undefined &&
|
|
653
|
+
this._securityToken != null &&
|
|
654
|
+
this._securityToken != "";
|
|
655
|
+
|
|
627
656
|
// with session token authentication, we do not expect a security token
|
|
628
657
|
// with security token authentication, we do not expect a session token
|
|
629
658
|
const needsSecurityToken = credentialsType != "SessionToken";
|
|
@@ -649,6 +678,38 @@ class Client {
|
|
|
649
678
|
return soapCall;
|
|
650
679
|
}
|
|
651
680
|
|
|
681
|
+
/**
|
|
682
|
+
* Retry a a SOAP call
|
|
683
|
+
*
|
|
684
|
+
* @private
|
|
685
|
+
* @return {SOAP.SoapMethodCall} a SoapMethodCall to retry
|
|
686
|
+
* parameters should be set
|
|
687
|
+
*/
|
|
688
|
+
async _retrySoapCall(soapCall) {
|
|
689
|
+
soapCall.retry = false;
|
|
690
|
+
var newClient = await this._refreshClient(this);
|
|
691
|
+
soapCall.finalize(newClient._soapEndPoint(), newClient);
|
|
692
|
+
if (this._traceAPICalls) {
|
|
693
|
+
const safeCallData = Util.trim(soapCall.request.data);
|
|
694
|
+
console.log(`RETRY SOAP//request ${safeCallData}`);
|
|
695
|
+
}
|
|
696
|
+
await soapCall.execute();
|
|
697
|
+
if (this._traceAPICalls) {
|
|
698
|
+
const safeCallResponse = Util.trim(soapCall.response);
|
|
699
|
+
console.log(`SOAP//response ${safeCallResponse}`);
|
|
700
|
+
}
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* SOAP Endpoint
|
|
706
|
+
*
|
|
707
|
+
* @private
|
|
708
|
+
* @return {string} soap call End point
|
|
709
|
+
*/
|
|
710
|
+
_soapEndPoint() {
|
|
711
|
+
return this._connectionParameters._endpoint + "/nl/jsp/soaprouter.jsp";
|
|
712
|
+
}
|
|
652
713
|
/**
|
|
653
714
|
* After a SOAP method call has been prepared with '_prepareSoapCall', and parameters have been added,
|
|
654
715
|
* this function actually executes the SOAP call
|
|
@@ -660,8 +721,7 @@ class Client {
|
|
|
660
721
|
const that = this;
|
|
661
722
|
if (soapCall.requiresLogon() && !that.isLogged())
|
|
662
723
|
throw CampaignException.NOT_LOGGED_IN(soapCall, `Cannot execute SOAP call ${soapCall.urn}#${soapCall.methodName}: you are not logged in. Use the Logon function first`);
|
|
663
|
-
|
|
664
|
-
soapCall.finalize(soapEndpoint);
|
|
724
|
+
soapCall.finalize(this._soapEndPoint());
|
|
665
725
|
|
|
666
726
|
const safeCallData = Util.trim(soapCall.request.data);
|
|
667
727
|
if (that._traceAPICalls)
|
|
@@ -680,7 +740,12 @@ class Client {
|
|
|
680
740
|
if (that._traceAPICalls)
|
|
681
741
|
console.log(`SOAP//failure ${ex.toString()}`);
|
|
682
742
|
that._notifyObservers((observer) => observer.onSOAPCallFailure && observer.onSOAPCallFailure(soapCall, ex) );
|
|
683
|
-
|
|
743
|
+
// Call session expiration callback in case of 401
|
|
744
|
+
if (ex.statusCode == 401 && that._refreshClient && soapCall.retry) {
|
|
745
|
+
return this._retrySoapCall(soapCall);
|
|
746
|
+
}
|
|
747
|
+
else
|
|
748
|
+
return Promise.reject(ex);
|
|
684
749
|
});
|
|
685
750
|
}
|
|
686
751
|
|
|
@@ -708,28 +773,34 @@ class Client {
|
|
|
708
773
|
that.application = new Application(that);
|
|
709
774
|
return Promise.resolve();
|
|
710
775
|
}
|
|
711
|
-
else if (credentials._type == "SecurityToken") {
|
|
712
|
-
|
|
776
|
+
else if (credentials._type == "SecurityToken") {
|
|
777
|
+
that._sessionInfo = undefined;
|
|
713
778
|
that._installedPackages = {};
|
|
714
779
|
that._sessionToken = "";
|
|
715
780
|
that._securityToken = credentials._securityToken;
|
|
716
781
|
that.application = new Application(that);
|
|
717
782
|
return Promise.resolve();
|
|
718
783
|
}
|
|
719
|
-
else if (credentials._type == "UserPassword") {
|
|
720
|
-
const
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
parameters =
|
|
729
|
-
|
|
784
|
+
else if (credentials._type == "UserPassword" || credentials._type == "BearerToken") {
|
|
785
|
+
const soapCall = that._prepareSoapCall("xtk:session", credentials._type === "UserPassword" ? "Logon" : "BearerTokenLogon");
|
|
786
|
+
// No retry for logon SOAP methods
|
|
787
|
+
soapCall.retry = false;
|
|
788
|
+
if (credentials._type == "UserPassword") {
|
|
789
|
+
const user = credentials._getUser();
|
|
790
|
+
const password = credentials._getPassword();
|
|
791
|
+
soapCall.writeString("login", user);
|
|
792
|
+
soapCall.writeString("password", password);
|
|
793
|
+
var parameters = null;
|
|
794
|
+
if (this._connectionParameters._options.rememberMe) {
|
|
795
|
+
parameters = soapCall.createElement("parameters");
|
|
796
|
+
parameters.setAttribute("rememberMe", "true");
|
|
797
|
+
}
|
|
798
|
+
soapCall.writeElement("parameters", parameters);
|
|
730
799
|
}
|
|
731
|
-
|
|
732
|
-
|
|
800
|
+
else {
|
|
801
|
+
const bearerToken = credentials._bearerToken;
|
|
802
|
+
soapCall.writeString("bearerToken", bearerToken);
|
|
803
|
+
}
|
|
733
804
|
return this._makeSoapCall(soapCall).then(function() {
|
|
734
805
|
const sessionToken = soapCall.getNextString();
|
|
735
806
|
|
|
@@ -782,7 +853,6 @@ class Client {
|
|
|
782
853
|
logoff() {
|
|
783
854
|
var that = this;
|
|
784
855
|
if (!that.isLogged()) return;
|
|
785
|
-
|
|
786
856
|
const credentials = this._connectionParameters._credentials;
|
|
787
857
|
if (credentials._type != "SessionToken" && credentials._type != "AnonymousUser") {
|
|
788
858
|
var soapCall = that._prepareSoapCall("xtk:session", "Logoff");
|
|
@@ -1032,6 +1102,14 @@ class Client {
|
|
|
1032
1102
|
var urn = that._methodCache.getSoapUrn(schemaId, methodName);
|
|
1033
1103
|
var soapCall = that._prepareSoapCall(urn, methodName);
|
|
1034
1104
|
|
|
1105
|
+
// If method is called with one parameter which is a function, then we assume it's a hook: the function will return
|
|
1106
|
+
// the actual list of parameters
|
|
1107
|
+
let isfunc = parameters && typeof parameters === "function";
|
|
1108
|
+
if (!isfunc && parameters && parameters.length >= 1 && typeof parameters[0] === "function")
|
|
1109
|
+
isfunc = true;
|
|
1110
|
+
if (isfunc)
|
|
1111
|
+
parameters = parameters[0](method, callContext);
|
|
1112
|
+
|
|
1035
1113
|
const isStatic = DomUtil.getAttributeAsBoolean(method, "static");
|
|
1036
1114
|
var object = callContext.object;
|
|
1037
1115
|
if (!isStatic) {
|
package/src/domUtil.js
CHANGED
|
@@ -19,7 +19,7 @@ var JSDOM;
|
|
|
19
19
|
|
|
20
20
|
/* istanbul ignore else */
|
|
21
21
|
if (!Util.isBrowser()) {
|
|
22
|
-
|
|
22
|
+
JSDOM = require("jsdom").JSDOM;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/**********************************************************************************
|
|
@@ -40,7 +40,7 @@ else {
|
|
|
40
40
|
return new XMLSerializer().serializeToString(dom);
|
|
41
41
|
};
|
|
42
42
|
};
|
|
43
|
-
|
|
43
|
+
|
|
44
44
|
JSDOM = jsdom;
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -562,7 +562,10 @@ class XPathElement {
|
|
|
562
562
|
class XPath {
|
|
563
563
|
|
|
564
564
|
constructor(path) {
|
|
565
|
-
|
|
565
|
+
path = (path || "").trim();
|
|
566
|
+
if (path && path.startsWith("[") && path.endsWith("]"))
|
|
567
|
+
path = path.substring(1, path.length - 1).trim();
|
|
568
|
+
this._path = path;
|
|
566
569
|
}
|
|
567
570
|
|
|
568
571
|
/**
|
package/src/entityAccessor.js
CHANGED
|
@@ -69,7 +69,7 @@ class EntityAccessor {
|
|
|
69
69
|
*/
|
|
70
70
|
static getAttributeAsString(entity, name) {
|
|
71
71
|
if (entity.documentElement) entity = entity.documentElement;
|
|
72
|
-
if (entity.
|
|
72
|
+
if (entity.nodeType && entity.tagName)
|
|
73
73
|
return DomUtil.getAttributeAsString(entity, name);
|
|
74
74
|
else if (entity instanceof BadgerFishObject)
|
|
75
75
|
return XtkCaster.asString(entity[`@${name}`]);
|
|
@@ -95,7 +95,7 @@ class EntityAccessor {
|
|
|
95
95
|
*/
|
|
96
96
|
static getAttributeAsLong(entity, name) {
|
|
97
97
|
if (entity.documentElement) entity = entity.documentElement;
|
|
98
|
-
if (entity.
|
|
98
|
+
if (entity.nodeType && entity.tagName)
|
|
99
99
|
return DomUtil.getAttributeAsLong(entity, name);
|
|
100
100
|
else if (entity instanceof BadgerFishObject)
|
|
101
101
|
return XtkCaster.asLong(entity[`@${name}`]);
|
|
@@ -121,7 +121,7 @@ class EntityAccessor {
|
|
|
121
121
|
*/
|
|
122
122
|
static getAttributeAsBoolean(entity, name) {
|
|
123
123
|
if (entity.documentElement) entity = entity.documentElement;
|
|
124
|
-
if (entity.
|
|
124
|
+
if (entity.nodeType && entity.tagName)
|
|
125
125
|
return DomUtil.getAttributeAsBoolean(entity, name);
|
|
126
126
|
else if (entity instanceof BadgerFishObject)
|
|
127
127
|
return XtkCaster.asBoolean(entity[`@${name}`]);
|
|
@@ -148,7 +148,7 @@ class EntityAccessor {
|
|
|
148
148
|
*/
|
|
149
149
|
static getChildElements(entity, tagName) {
|
|
150
150
|
if (entity.documentElement) entity = entity.documentElement;
|
|
151
|
-
if (entity.
|
|
151
|
+
if (entity.nodeType && entity.tagName) {
|
|
152
152
|
const elements = [];
|
|
153
153
|
var child = DomUtil.getFirstChildElement(entity);
|
|
154
154
|
while (child) {
|
|
@@ -184,7 +184,7 @@ class EntityAccessor {
|
|
|
184
184
|
*/
|
|
185
185
|
static getElement(entity, tagName) {
|
|
186
186
|
if (entity.documentElement) entity = entity.documentElement;
|
|
187
|
-
if (entity.
|
|
187
|
+
if (entity.nodeType && entity.tagName) {
|
|
188
188
|
var child = DomUtil.getFirstChildElement(entity);
|
|
189
189
|
while (child) {
|
|
190
190
|
if (tagName == child.tagName)
|
package/src/index.js
CHANGED
|
@@ -23,6 +23,7 @@ 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
27
|
|
|
27
28
|
/**
|
|
28
29
|
* Get/Set the transport function (defaults to Axios). This function is used for testing / mocking the transport layer.
|
|
@@ -127,8 +128,7 @@ class SDK {
|
|
|
127
128
|
* @example
|
|
128
129
|
* expect(sdk.escapeXtk`@name=${"Rock 'n' Roll"}`).toBe("@name='Rock \\'n\\' Roll'");
|
|
129
130
|
*/
|
|
130
|
-
escapeXtk(p1, ...p2)
|
|
131
|
-
{
|
|
131
|
+
escapeXtk(p1, ...p2) {
|
|
132
132
|
// first syntax: only one parameter which is a string => returns the escaped string.
|
|
133
133
|
// that's how the Campaign function in common.js behaves
|
|
134
134
|
if (p1 === undefined || p1 === null)
|
|
@@ -147,9 +147,65 @@ class SDK {
|
|
|
147
147
|
}
|
|
148
148
|
return str;
|
|
149
149
|
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Escapes a string of characters so that in can be used in a SQL like statement.
|
|
153
|
+
* @param {string | any} text the text to escape. If not a string, it will be casted to a xtk string first
|
|
154
|
+
* @param {boolean?} escapeXtkParams indicates that the escape text contains Xtk parameters (using $ character)
|
|
155
|
+
* @returns the escaped string
|
|
156
|
+
*/
|
|
157
|
+
escapeForLike(text, escapeXtkParams) {
|
|
158
|
+
text = XtkCaster.asString(text);
|
|
159
|
+
if (!text) return "";
|
|
160
|
+
text = text.replace(/\\/g, "\\\\")
|
|
161
|
+
.replace(/'/g, "\\'")
|
|
162
|
+
.replace(/%/g, "\\%")
|
|
163
|
+
.replace(/_/g, "\\_");
|
|
164
|
+
if (escapeXtkParams)
|
|
165
|
+
text = text.replace(/\$/g, "' + Char('36') + '");
|
|
166
|
+
return text;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Expands an xpath, i.e. enclose it with [..] brackets if necessary
|
|
171
|
+
* @param {string} xpath the xpath
|
|
172
|
+
* @returns {string} the expanded xpath
|
|
173
|
+
*/
|
|
174
|
+
expandXPath(xpath) {
|
|
175
|
+
if (!xpath) return xpath;
|
|
176
|
+
if (xpath.startsWith("[") && xpath.endsWith("]"))
|
|
177
|
+
return xpath;
|
|
178
|
+
if (xpath.indexOf('/') === -1 && xpath.indexOf('-') === -1 && xpath.indexOf(':') === -1)
|
|
179
|
+
return xpath;
|
|
180
|
+
return `[${xpath}]`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
unexpandXPath(xpath) {
|
|
184
|
+
if (!xpath) return xpath;
|
|
185
|
+
if (xpath.startsWith("[") && xpath.endsWith("]"))
|
|
186
|
+
return xpath.substring(1, xpath.length - 1);
|
|
187
|
+
return xpath;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Convert a javascript value into an xtk constant with proper quoting
|
|
192
|
+
* @param {any} value the value to convert
|
|
193
|
+
* @param {string} type the xtk type
|
|
194
|
+
* @returns
|
|
195
|
+
*/
|
|
196
|
+
xtkConstText(value, type) {
|
|
197
|
+
if (!type || type === 'string' || type === 'memo') {
|
|
198
|
+
return sdk.escapeXtk(XtkCaster.asString(value));
|
|
199
|
+
}
|
|
200
|
+
const constText = XtkCaster.asString(XtkCaster.as(value, type));
|
|
201
|
+
if (XtkCaster.isTimeType(type))
|
|
202
|
+
return `#${constText}#`;
|
|
203
|
+
return constText;
|
|
204
|
+
}
|
|
150
205
|
}
|
|
151
206
|
|
|
152
207
|
const sdk = new SDK();
|
|
208
|
+
sdk.TestUtil = TestUtil;
|
|
153
209
|
sdk.XtkCaster = XtkCaster;
|
|
154
210
|
sdk.Credentials = Credentials;
|
|
155
211
|
sdk.DomUtil = DomUtil;
|