@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/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
- * @property {string} representation - the representation to use, i.e. "SimpleJson" (the default), "BadgerFish", or "xml"
225
- * @property {boolean} rememberMe - The Campaign `rememberMe` attribute which can be used to extend the lifetime of session tokens
226
- * @property {number} entityCacheTTL - The TTL (in milliseconds) after which cached XTK entities expire. Defaults to 5 minutes
227
- * @property {number} methodCacheTTL - The TTL (in milliseconds) after which cached XTK methods expire. Defaults to 5 minutes
228
- * @property {number} optionCacheTTL - The TTL (in milliseconds) after which cached XTK options expire. Defaults to 5 minutes
229
- * @property {boolean} traceAPICalls - Activates the tracing of all API calls
230
- * @property {Utils.Transport} transport - Overrides the transport (i.e. HTTP layer)
231
- * @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.
232
- * @property {Storage} storage - Overrides the storage interface (i.e. LocalStorage)
233
- * @memberOf Campaign
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
- var soapEndpoint = that._connectionParameters._endpoint + "/nl/jsp/soaprouter.jsp";
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
- return Promise.reject(ex);
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
- that._sessionInfo = undefined;
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 user = credentials._getUser();
721
- const password = credentials._getPassword();
722
-
723
- const soapCall = this._prepareSoapCall("xtk:session", "Logon");
724
- soapCall.writeString("login", user);
725
- soapCall.writeString("password", password);
726
- var parameters = null;
727
- if (this._connectionParameters._options.rememberMe) {
728
- parameters = soapCall.createElement("parameters");
729
- parameters.setAttribute("rememberMe", "true");
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
- soapCall.writeElement("parameters", parameters);
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
- JSDOM = require("jsdom").JSDOM;
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
- this._path = (path || "").trim();
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
  /**
@@ -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.insertAdjacentElement)
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.insertAdjacentElement)
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.insertAdjacentElement)
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.insertAdjacentElement) {
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.insertAdjacentElement) {
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;