@adobe/acc-js-sdk 1.1.5 → 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 CHANGED
@@ -4,7 +4,8 @@ module.exports = {
4
4
  "commonjs": true,
5
5
  "es2021": true,
6
6
  "node": true,
7
- "jest": true
7
+ "jest": true,
8
+ circus: true
8
9
  },
9
10
  "extends": "eslint:recommended",
10
11
  "parserOptions": {
package/CHANGELOG.md CHANGED
@@ -5,6 +5,11 @@ This is a node.js SDK for Campaign API. It exposes the Campaign API exactly like
5
5
 
6
6
  # Changelog
7
7
 
8
+ ## Version 1.1.6
9
+ _2022_08_19_
10
+
11
+ * New auto-refresh mechanism to keep schemas and option caches up-to-date. See `client.startRefreshCaches` and `client.stopRefreshCaches` functions.
12
+
8
13
  ## Version 1.1.5
9
14
  _2022/07/07__
10
15
 
package/README.md CHANGED
@@ -693,6 +693,30 @@ It's possible to disable persistent caches using the `noStorage` connection opti
693
693
  It is also possible to setup one's own persistent cache, by passing a `storage` object as a connection option. This object should implement 3 methods: `getItem`, `setItem`, and `removeItem` (synchronous)
694
694
 
695
695
 
696
+ ## Auto-refresh caches
697
+
698
+ The SDK includes a mechnism to maintain the schemas and options caches up-to-date by polling the Campaign server on a regular basis (10 seconds by default). The server returns the list of entities (schemas or options) which have changed since they were cached, and the client removes them from the cache. When a schema changes, the corresponding methods are also removed from the method cache.
699
+
700
+ This mechanism is not activate by default but can be activated or deactivated by the following functions
701
+
702
+ ```js
703
+ client.startRefreshCaches(30000); // activate cache auto-refresh mechanism every 30s
704
+ client.stopRefreshCaches(); // de-activate cache auto-refresh
705
+ ```
706
+
707
+ This mechanism is based on the `xtk:session#GetModifiedEntities` SOAP method which is only available in Campaign 8.4 and above only. For other builds of Campaign, the auto-refresh mechanism will not do anything.
708
+
709
+ The following changes are handled:
710
+ * If the build number has changed, the whole cache is cleared
711
+ * If more than 10 schemas or options have changed, the whole cache is cleared
712
+ * if less than 10 schemas or options have changed, only those entities are removed from the cache
713
+
714
+
715
+ The refresh mechanism includes the following guardrails
716
+ * Both xtk:option and xtk:schema caches are refreshed every n seconds. To avoid issuing two API calls at the same time to the server, the schema cache refresh call is delayed by a few seconds. In the future this delay may change.
717
+ * If the xtk:session#GetModifiedEntities API is not available, the auto refresh mechanism will silently stop automatically
718
+ * If an error occurs while trying to refresh, a warning will be logged to the JavaScript console but the auto refresh will not be stopped.
719
+
696
720
  ## Passwords
697
721
 
698
722
  External account passwords can be decrypted using a Cipher. This function is deprecated since version 1.0.0 since it's not guaranteed to work in future versions of Campaign (V8 and above)
package/compile.js CHANGED
@@ -36,6 +36,7 @@ var resources = [
36
36
  { name: "./xtkEntityCache.js" },
37
37
  { name: "./methodCache.js" },
38
38
  { name: "./optionCache.js" },
39
+ { name: "./cacheRefresher.js" },
39
40
  { name: "./soap.js" },
40
41
  { name: "./crypto.js" },
41
42
  { name: "./application.js" },
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@adobe/acc-js-sdk",
3
- "version": "1.1.5",
3
+ "version": "1.1.6",
4
4
  "lockfileVersion": 2,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@adobe/acc-js-sdk",
9
- "version": "1.1.5",
9
+ "version": "1.1.6",
10
10
  "license": "ISC",
11
11
  "dependencies": {
12
12
  "axios": "^0.25.0",
@@ -1402,14 +1402,21 @@
1402
1402
  }
1403
1403
  },
1404
1404
  "node_modules/caniuse-lite": {
1405
- "version": "1.0.30001301",
1406
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001301.tgz",
1407
- "integrity": "sha512-csfD/GpHMqgEL3V3uIgosvh+SVIQvCh43SNu9HRbP1lnxkKm1kjDG4f32PP571JplkLjfS+mg2p1gxR7MYrrIA==",
1405
+ "version": "1.0.30001378",
1406
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/caniuse-lite/-/caniuse-lite-1.0.30001378.tgz",
1407
+ "integrity": "sha512-JVQnfoO7FK7WvU4ZkBRbPjaot4+YqxogSDosHv0Hv5mWpUESmN+UubMU6L/hGz8QlQ2aY5U0vR6MOs6j/CXpNA==",
1408
1408
  "dev": true,
1409
- "funding": {
1410
- "type": "opencollective",
1411
- "url": "https://opencollective.com/browserslist"
1412
- }
1409
+ "funding": [
1410
+ {
1411
+ "type": "opencollective",
1412
+ "url": "https://opencollective.com/browserslist"
1413
+ },
1414
+ {
1415
+ "type": "tidelift",
1416
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
1417
+ }
1418
+ ],
1419
+ "license": "CC-BY-4.0"
1413
1420
  },
1414
1421
  "node_modules/catharsis": {
1415
1422
  "version": "0.9.0",
@@ -6147,9 +6154,9 @@
6147
6154
  "dev": true
6148
6155
  },
6149
6156
  "caniuse-lite": {
6150
- "version": "1.0.30001301",
6151
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001301.tgz",
6152
- "integrity": "sha512-csfD/GpHMqgEL3V3uIgosvh+SVIQvCh43SNu9HRbP1lnxkKm1kjDG4f32PP571JplkLjfS+mg2p1gxR7MYrrIA==",
6157
+ "version": "1.0.30001378",
6158
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/caniuse-lite/-/caniuse-lite-1.0.30001378.tgz",
6159
+ "integrity": "sha512-JVQnfoO7FK7WvU4ZkBRbPjaot4+YqxogSDosHv0Hv5mWpUESmN+UubMU6L/hGz8QlQ2aY5U0vR6MOs6j/CXpNA==",
6153
6160
  "dev": true
6154
6161
  },
6155
6162
  "catharsis": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/acc-js-sdk",
3
- "version": "1.1.5",
3
+ "version": "1.1.6",
4
4
  "description": "ACC Javascript SDK",
5
5
  "main": "src/index.js",
6
6
  "homepage": "https://github.com/adobe/acc-js-sdk#readme",
@@ -65,20 +65,46 @@ function propagateImplicitValues(xtkDesc, labelOnly) {
65
65
  // ========================================================================================
66
66
  // Schema Cache
67
67
  // ========================================================================================
68
+
69
+ /**
70
+ * A cache of schemas of type `XtkSchema` instead of plain XML or JSON objects
71
+ *
72
+ * @private
73
+ * @class
74
+ * @constructor
75
+ * @memberof Campaign
76
+ */
77
+
68
78
  class SchemaCache {
69
79
  constructor(client) {
70
80
  this._client = client;
71
81
  this._schemas = {};
72
82
  }
83
+
84
+ /**
85
+ * Get a schema by id from schema cache of type `XtkSchema`
86
+ *
87
+ * @param {string} schemaId
88
+ * @returns {Campaign.XtkSchema} the schema, or null if the schema was not found
89
+ */
73
90
  async getSchema(schemaId) {
74
91
  let schema = this._schemas[schemaId];
75
92
  if (schema === undefined) {
76
93
  schema = await this._client.application._getSchema(schemaId);
77
94
  if (!schema) schema = null; // null = not found
78
- this._schemas[schemaId] = schema;
95
+ this._schemas[schemaId] = schema;
79
96
  }
80
97
  return schema;
81
98
  }
99
+
100
+ /**
101
+ * Remove a schema from schema cache. The callback function when refreshing cache in cacheRefresher
102
+ *
103
+ * @param {string} schemaId
104
+ */
105
+ invalidateCacheItem(schemaId) {
106
+ this._schemas[schemaId] = undefined;
107
+ }
82
108
  }
83
109
 
84
110
  // ========================================================================================
@@ -1237,6 +1263,13 @@ class Application {
1237
1263
  }
1238
1264
  }
1239
1265
 
1266
+ _registerCacheChangeListener() {
1267
+ this.client._registerCacheChangeListener(this._schemaCache);
1268
+ }
1269
+
1270
+ _unregisterCacheChangeListener() {
1271
+ this.client._unregisterCacheChangeListener(this._schemaCache);
1272
+ }
1240
1273
  /**
1241
1274
  * Get a schema by id. This function returns an XtkSchema object or null if the schema is not found.
1242
1275
  * Using the `XtkSchema` API makes it easier to navigate schemas than using a plain XML or JSON object
@@ -1248,7 +1281,7 @@ class Application {
1248
1281
  return this._schemaCache.getSchema(schemaId);
1249
1282
  }
1250
1283
 
1251
- // Private function: get a schema without using the cache
1284
+ // Private function: get a schema without using the SchemaCache
1252
1285
  async _getSchema(schemaId) {
1253
1286
  const xml = await this.client.getSchema(schemaId, "xml");
1254
1287
  if (!xml)
package/src/cache.js CHANGED
@@ -123,6 +123,7 @@ class SafeStorage {
123
123
  } catch(ex) { /* Ignore errors in safe class */
124
124
  }
125
125
  }
126
+
126
127
  }
127
128
 
128
129
  /**
@@ -243,7 +244,7 @@ class Cache {
243
244
 
244
245
  /**
245
246
  * Put a value from the cache
246
- * @param {*} key the key or keys of the value to retreive
247
+ * @param {*} key the key or keys of the value to retrieve
247
248
  * @param {*} value the value to cache
248
249
  * @returns {CachedObject} a cached object containing the cached value
249
250
  */
@@ -266,6 +267,15 @@ class Cache {
266
267
  this._cache = {};
267
268
  this._saveLastCleared();
268
269
  }
270
+
271
+ /**
272
+ * Remove a key from the cache
273
+ * @param {string} key the key to remove
274
+ */
275
+ remove(key) {
276
+ delete this._cache[key];
277
+ this._remove(key);
278
+ }
269
279
  }
270
280
 
271
281
  // Public expots
@@ -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;
@@ -532,13 +533,16 @@ class Client {
532
533
  const rootKey = `acc.js.sdk.${sdk.getSDKVersion().version}.${instanceKey}.cache`;
533
534
 
534
535
  this._entityCache = new XtkEntityCache(this._storage, `${rootKey}.XtkEntityCache`, connectionParameters._options.entityCacheTTL);
536
+ this._entityCacheRefresher = new CacheRefresher(this._entityCache, this, "xtk:schema", `${rootKey}.XtkEntityCache`);
535
537
  this._methodCache = new MethodCache(this._storage, `${rootKey}.MethodCache`, connectionParameters._options.methodCacheTTL);
536
538
  this._optionCache = new OptionCache(this._storage, `${rootKey}.OptionCache`, connectionParameters._options.optionCacheTTL);
539
+ this._optionCacheRefresher = new CacheRefresher(this._optionCache, this, "xtk:option", `${rootKey}.OptionCache`);
537
540
  this.NLWS = new Proxy(this, clientHandler());
538
541
 
539
542
  this._transport = connectionParameters._options.transport;
540
543
  this._traceAPICalls = connectionParameters._options.traceAPICalls;
541
544
  this._observers = [];
545
+ this._cacheChangeListeners = [];
542
546
  this._refreshClient = connectionParameters._options.refreshClient;
543
547
 
544
548
  // expose utilities
@@ -849,6 +853,7 @@ class Client {
849
853
  that._sessionToken = credentials._sessionToken;
850
854
  that._securityToken = "";
851
855
  that.application = new Application(that);
856
+ that.application._registerCacheChangeListener();
852
857
  return Promise.resolve();
853
858
  }
854
859
  else if (credentials._type == "SecurityToken") {
@@ -857,6 +862,7 @@ class Client {
857
862
  that._sessionToken = "";
858
863
  that._securityToken = credentials._securityToken;
859
864
  that.application = new Application(that);
865
+ that.application._registerCacheChangeListener();
860
866
  return Promise.resolve();
861
867
  }
862
868
  else if (credentials._type == "UserPassword" || credentials._type == "BearerToken") {
@@ -908,6 +914,7 @@ class Client {
908
914
  that._securityToken = securityToken;
909
915
 
910
916
  that.application = new Application(that);
917
+ that.application._registerCacheChangeListener();
911
918
  });
912
919
  }
913
920
  else {
@@ -933,10 +940,13 @@ class Client {
933
940
  logoff() {
934
941
  var that = this;
935
942
  if (!that.isLogged()) return;
943
+ that.application._unregisterCacheChangeListener();
944
+ that._unregisterAllCacheChangeListeners();
945
+ this.stopRefreshCaches();
936
946
  const credentials = this._connectionParameters._credentials;
937
947
  if (credentials._type != "SessionToken" && credentials._type != "AnonymousUser") {
938
948
  var soapCall = that._prepareSoapCall("xtk:session", "Logoff", false, this._connectionParameters._options.extraHttpHeaders);
939
- return this._makeSoapCall(soapCall).then(function() {
949
+ return this._makeSoapCall(soapCall).then(function() {
940
950
  that._sessionToken = "";
941
951
  that._securityToken = "";
942
952
  that.application = null;
@@ -958,15 +968,15 @@ class Client {
958
968
  * @return the option value, casted in the expected data type. If the option does not exist, it will return null.
959
969
  */
960
970
  async getOption(name, useCache = true) {
961
- var value;
962
- if (useCache)
963
- value = this._optionCache.get(name);
964
- if (value === undefined) {
965
- const option = await this.NLWS.xtkSession.getOption(name);
966
- value = this._optionCache.put(name, option);
967
- this._optionCache.put(name, option);
968
- }
969
- return value;
971
+ var value;
972
+ if (useCache) {
973
+ value = this._optionCache.get(name);
974
+ }
975
+ if (value === undefined) {
976
+ const option = await this.NLWS.xtkSession.getOption(name);
977
+ value = this._optionCache.put(name, option);
978
+ }
979
+ return value;
970
980
  }
971
981
 
972
982
  /**
@@ -1032,6 +1042,50 @@ class Client {
1032
1042
  this.clearOptionCache();
1033
1043
  }
1034
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
+
1035
1089
  /**
1036
1090
  * Tests if a package is installed
1037
1091
  *
@@ -1101,11 +1155,12 @@ class Client {
1101
1155
  var that = this;
1102
1156
  var entity = that._entityCache.get("xtk:schema", schemaId);
1103
1157
  if (!entity) {
1104
- entity = await that.getEntityIfMoreRecent("xtk:schema", schemaId, "xml", internal);
1105
- }
1106
- if (entity)
1158
+ entity = await that.getEntityIfMoreRecent("xtk:schema", schemaId, "xml", internal);
1159
+ if (entity) {
1107
1160
  that._entityCache.put("xtk:schema", schemaId, entity);
1108
-
1161
+ that._methodCache.put(entity);
1162
+ }
1163
+ }
1109
1164
  entity = that._toRepresentation(entity, representation);
1110
1165
  return entity;
1111
1166
  }
@@ -1097,7 +1097,6 @@ describe('Application', () => {
1097
1097
  </GetEntityIfMoreRecentResponse>
1098
1098
  </SOAP-ENV:Body>
1099
1099
  </SOAP-ENV:Envelope>`));
1100
-
1101
1100
  const nodes = await link.joinNodes();
1102
1101
  expect(nodes).toMatchObject([]);
1103
1102
  const reverseLink = await link.reverseLink();
@@ -2156,4 +2155,5 @@ describe('Application', () => {
2156
2155
  expect(client.application.version).toBe('6.7.0');
2157
2156
  })
2158
2157
  })
2159
- });
2158
+ });
2159
+