@adobe/acc-js-sdk 1.1.4 → 1.1.7

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,12 +4,14 @@ 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": {
11
12
  "ecmaVersion": 12
12
13
  },
13
14
  "rules": {
15
+ "indent": ["error", 2],
14
16
  }
15
17
  };
package/CHANGELOG.md CHANGED
@@ -5,8 +5,25 @@ 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.7
9
+ _2022_08_30_
10
+
11
+ * New listener interface to be notified of internal events from the SDK. Can be used to integrate with observability frameworks. See the "Observers" section of the README file.
12
+ * Experimental file upload feature. Will require server-side changes to work, and is currently limited to be used with browsers only.
13
+
14
+
15
+ ## Version 1.1.6
16
+ _2022_08_19_
17
+
18
+ * New auto-refresh mechanism to keep schemas and option caches up-to-date. See `client.startRefreshCaches` and `client.stopRefreshCaches` functions.
19
+
20
+ ## Version 1.1.5
21
+ _2022/07/07__
22
+
23
+ * The SOAP method name was not showing up properly in the Chrome console
24
+
8
25
  ## Version 1.1.4
9
- _2022/06/xx_
26
+ _2022/07/07__
10
27
 
11
28
  * Added `application.version` which returns the server version in the format major.minor.servicePack (ex: 8.2.10)
12
29
  * Added the ability to push down parameters to the SOAP and transport layers. See the pushDown section of the readme file.
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)
@@ -794,10 +818,10 @@ NLWS.xml.pushDown({ timeout: 60000, foo: 'bar' }).xtkBuilder.installPackage(dom)
794
818
  ```
795
819
 
796
820
  ## Troubleshooting
797
- In the version 1.1.4 of the SDK, we are automatically adding the SOAP method name in the URL in order to simplify troubleshooting. Normally, all SOAP method calls are make to the soaprouter.jsp endpoint, which makes it difficult to understand which actual API call is being made.
821
+ In the version 1.1.5 of the SDK, we are automatically adding the SOAP method name in the URL in order to simplify troubleshooting. Normally, all SOAP method calls are make to the soaprouter.jsp endpoint, which makes it difficult to understand which actual API call is being made.
798
822
  In fact the SOAP call name is available via the SOAPAction HTTP header, but it's usually not immediatelly visible.
799
823
 
800
- The SOAP calls URLs are now formed like this: `http://acc-sdk:8080/nl/jsp/soaprouter.jsp?xtk:queryDef#ExecuteQuery` where the SOAP method name is added as a query parameter. Campaign server ignores this parameter.
824
+ The SOAP calls URLs are now formed like this: `http://acc-sdk:8080/nl/jsp/soaprouter.jsp?xtk:queryDef:ExecuteQuery` where the SOAP method name is added as a query parameter. Campaign server ignores this parameter.
801
825
 
802
826
  This can be disabled using the `noMethodInURL` connection parameter
803
827
 
@@ -1087,6 +1111,34 @@ The `soapCall` parameter is a `SoapMethodCall` object which describes the SOAP c
1087
1111
  * `response` is a string containing the XML result of the SOAP call if the call was successful. It may be undefined if the call was not executed yet or if the call failed
1088
1112
 
1089
1113
 
1114
+ In version 1.1.7, the observer interface is extended to listen for internal events of the SDK. The `event` function of the observer, if it exist will be call for each SDK event with 2 parameters: the event itself, and for some events, a parent event. For instance a SOAP response event will have the SOAP request for a parent event.
1115
+ ```js
1116
+ client.registerObserver({
1117
+ event: (event, parentEvent) => { ... },
1118
+ });
1119
+ ```
1120
+
1121
+ The following events are available
1122
+
1123
+ | event name | comment / description |
1124
+ |----|----|
1125
+ | SDK//logon | A client logs on |
1126
+ | SDK//logoff | A client logs off |
1127
+ | CACHE//stats | Regularly sends stats about internal caches |
1128
+ | SOAP//request | The SDK executes a SOAP request |
1129
+ | SOAP//response | The SDK processes the successful response of a SOAP request |
1130
+ | SOAP//failure | A SOAP request failed |
1131
+ | HTTP//request | The SDK executes an HTTP request |
1132
+ | HTTP//response | The SDK processes the successful response of an HTTP request |
1133
+ | HTTP//failure | An HTTP request failed |
1134
+ | CACHE_REFRESHER//start | A cache auto-refresher starts |
1135
+ | CACHE_REFRESHER//stop | A cache auto-refresher stops |
1136
+ | CACHE_REFRESHER//tick | A cache auto-refresh occurs |
1137
+ | CACHE_REFRESHER//loggedOff | The cache auto-refresh was triggered whereas the client was logged off |
1138
+ | CACHE_REFRESHER//error | The cache auto-refresh failed. Auto-refresh will continue. |
1139
+ | CACHE_REFRESHER//abort | The cache auto-refresh failed because the server does not support it. Auto-refresh will stop. |
1140
+ | CACHE_REFRESHER//response | The server responded to an auto-refresh request |
1141
+
1090
1142
  # Configuration
1091
1143
 
1092
1144
  ## Tracking all SOAP calls
@@ -1504,8 +1556,6 @@ var event = await query.executeQuery();
1504
1556
  console.log(`>> Event: ${JSON.stringify(event)}`);
1505
1557
  ```
1506
1558
 
1507
-
1508
-
1509
1559
  # Application
1510
1560
 
1511
1561
  The `application` object can be obtained from a client, and will mimmic the Campaing `application` object (https://docs.adobe.com/content/help/en/campaign-classic/technicalresources/api/c-Application.html)
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.4",
3
+ "version": "1.1.7",
4
4
  "lockfileVersion": 2,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@adobe/acc-js-sdk",
9
- "version": "1.1.4",
9
+ "version": "1.1.7",
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",
@@ -4802,9 +4809,10 @@
4802
4809
  },
4803
4810
  "node_modules/uuid": {
4804
4811
  "version": "8.3.2",
4805
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
4812
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/uuid/-/uuid-8.3.2.tgz",
4806
4813
  "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
4807
4814
  "dev": true,
4815
+ "license": "MIT",
4808
4816
  "bin": {
4809
4817
  "uuid": "dist/bin/uuid"
4810
4818
  }
@@ -6147,9 +6155,9 @@
6147
6155
  "dev": true
6148
6156
  },
6149
6157
  "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==",
6158
+ "version": "1.0.30001378",
6159
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/caniuse-lite/-/caniuse-lite-1.0.30001378.tgz",
6160
+ "integrity": "sha512-JVQnfoO7FK7WvU4ZkBRbPjaot4+YqxogSDosHv0Hv5mWpUESmN+UubMU6L/hGz8QlQ2aY5U0vR6MOs6j/CXpNA==",
6153
6161
  "dev": true
6154
6162
  },
6155
6163
  "catharsis": {
@@ -8744,7 +8752,7 @@
8744
8752
  },
8745
8753
  "uuid": {
8746
8754
  "version": "8.3.2",
8747
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
8755
+ "resolved": "https://artifactory.corp.adobe.com/artifactory/api/npm/npm-adobe-release/uuid/-/uuid-8.3.2.tgz",
8748
8756
  "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
8749
8757
  "dev": true
8750
8758
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/acc-js-sdk",
3
- "version": "1.1.4",
3
+ "version": "1.1.7",
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
  /**
@@ -178,6 +179,16 @@ class Cache {
178
179
  this._cache = {};
179
180
  // timestamp at which the cache was last cleared
180
181
  this._lastCleared = this._loadLastCleared();
182
+ this._stats = {
183
+ reads: 0,
184
+ writes: 0,
185
+ removals: 0,
186
+ clears: 0,
187
+ memoryHits: 0,
188
+ storageHits: 0,
189
+ loads: 0,
190
+ saves: 0,
191
+ };
181
192
  }
182
193
 
183
194
  // Load timestamp at which the cache was last cleared
@@ -194,6 +205,7 @@ class Cache {
194
205
 
195
206
  // Load from local storage
196
207
  _load(key) {
208
+ this._stats.loads = this._stats.loads + 1;
197
209
  const json = this._storage.getItem(key);
198
210
  if (!json || !json.cachedAt || json.cachedAt <= this._lastCleared) {
199
211
  this._storage.removeItem(key);
@@ -204,6 +216,7 @@ class Cache {
204
216
 
205
217
  // Save to local storage
206
218
  _save(key, cached) {
219
+ this._stats.saves = this._stats.saves + 1;
207
220
  this._storage.setItem(key, cached);
208
221
  }
209
222
 
@@ -215,6 +228,7 @@ class Cache {
215
228
  _getIfActive(key) {
216
229
  // In memory cache?
217
230
  var cached = this._cache[key];
231
+ var memoryHit = !!cached;
218
232
  // Local storage ?
219
233
  if (!cached) {
220
234
  cached = this._load(key);
@@ -227,6 +241,8 @@ class Cache {
227
241
  this._remove(key);
228
242
  return undefined;
229
243
  }
244
+ this._stats.memoryHits = this._stats.memoryHits + 1;
245
+ if (!memoryHit) this._stats.storageHits = this._stats.storageHits + 1;
230
246
  return cached.value;
231
247
  }
232
248
 
@@ -236,6 +252,7 @@ class Cache {
236
252
  * @returns {*} the cached value, or undefined if not found
237
253
  */
238
254
  get() {
255
+ this._stats.reads = this._stats.reads + 1;
239
256
  const key = this._makeKeyFn.apply(this, arguments);
240
257
  const cached = this._getIfActive(key);
241
258
  return cached;
@@ -243,11 +260,12 @@ class Cache {
243
260
 
244
261
  /**
245
262
  * Put a value from the cache
246
- * @param {*} key the key or keys of the value to retreive
263
+ * @param {*} key the key or keys of the value to retrieve
247
264
  * @param {*} value the value to cache
248
265
  * @returns {CachedObject} a cached object containing the cached value
249
266
  */
250
267
  put() {
268
+ this._stats.writes = this._stats.writes + 1;
251
269
  const value = arguments[arguments.length -1];
252
270
  const key = this._makeKeyFn.apply(this, arguments);
253
271
  const now = Date.now();
@@ -263,9 +281,20 @@ class Cache {
263
281
  * as cleared so that subsequent get operation will not actually return any data cached in persistent storage
264
282
  */
265
283
  clear() {
284
+ this._stats.clears = this._stats.clears + 1;
266
285
  this._cache = {};
267
286
  this._saveLastCleared();
268
287
  }
288
+
289
+ /**
290
+ * Remove a key from the cache
291
+ * @param {string} key the key to remove
292
+ */
293
+ remove(key) {
294
+ this._stats.removals = this._stats.removals + 1;
295
+ delete this._cache[key];
296
+ this._remove(key);
297
+ }
269
298
  }
270
299
 
271
300
  // Public expots
@@ -0,0 +1,267 @@
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
+ /**
67
+ * A class to refresh regulary a Cache every n seconds, by sending a query to get the last modified entities.
68
+ * The refresh mechanism can be activated by calling client.startRefreshCaches().
69
+ * This mechanism depends on the xtk:session:GetModifiedEntities API which is introduced in ACC 8.4 and above.
70
+ *
71
+ * @class
72
+ * @constructor
73
+ * @memberof Campaign
74
+ * @param {Cache} cache is the cache to refresh
75
+ * @param {Client} client is the ACC API Client.
76
+ * @param {string} cacheSchemaId is the id of the schema present in the cache to be refreshed every 10 seconds
77
+ * @param {string} rootKey is used as the root key of cache items in the refresher state cache
78
+ */
79
+ class CacheRefresher {
80
+
81
+ constructor(cache, client, cacheSchemaId, rootKey) {
82
+ const connectionParameters = client._connectionParameters;
83
+ this._cache = cache;
84
+ this._client = client;
85
+ this._connectionParameters = connectionParameters;
86
+ this._cacheSchemaId = cacheSchemaId;
87
+
88
+ this._storage = connectionParameters._options._storage;
89
+ this._refresherStateCache = new RefresherStateCache(this._storage, `${rootKey}.RefresherStateCache`, 1000*3600);
90
+
91
+ this._lastTime = undefined;
92
+ this._buildNumber = undefined;
93
+ this._intervalId = null;
94
+ this._running = false;
95
+ }
96
+
97
+ /**
98
+ * Start auto refresh
99
+ * @param {integer} refreshFrequency frequency of the refresh in ms (default value is 10,000 ms)
100
+ */
101
+ startAutoRefresh(refreshFrequency) {
102
+ this._client._trackEvent('CACHE_REFRESHER//start', undefined, {
103
+ cacheSchemaId: this._cacheSchemaId,
104
+ refreshFrequency: refreshFrequency
105
+ });
106
+ if (this._intervalId != null) {
107
+ clearInterval(this._intervalId);
108
+ }
109
+ this._intervalId = setInterval(() => {
110
+ this._safeCallAndRefresh();
111
+ }, refreshFrequency || 10000); // every 10 seconds by default
112
+ }
113
+
114
+ // Protect _callAndRefresh from reentrance
115
+ async _safeCallAndRefresh() {
116
+ if (this._running) {
117
+ // This call is already running and maybe taking a long time to complete. Do not make things
118
+ // harder and just skip this run
119
+ this._client._trackEvent('CACHE_REFRESHER//skip', undefined, {
120
+ cacheSchemaId: this._cacheSchemaId,
121
+ });
122
+ return;
123
+ }
124
+ this._running = true;
125
+ try {
126
+ await this._callAndRefresh();
127
+ } catch(ex) {
128
+ if (ex.errorCode === "SDK-000010") {
129
+ // client is not logged, this is not an error.
130
+ this._client._trackEvent('CACHE_REFRESHER//loggedOff', undefined, {
131
+ cacheSchemaId: this._cacheSchemaId,
132
+ });
133
+ return;
134
+ }
135
+ this._client._trackEvent('CACHE_REFRESHER//error', undefined, {
136
+ cacheSchemaId: this._cacheSchemaId,
137
+ error: ex,
138
+ });
139
+ console.warn(`Failed to refresh cache for ${this._cacheSchemaId}`, ex);
140
+ }
141
+ finally {
142
+ this._running = false;
143
+ }
144
+ }
145
+
146
+ // Get last modified entities for the Campaign server and remove from cache last modified entities
147
+ async _callAndRefresh() {
148
+ const that = this;
149
+ const soapCall = this._client._prepareSoapCall("xtk:session", "GetModifiedEntities", true, this._connectionParameters._options.extraHttpHeaders);
150
+
151
+ if (this._lastTime === undefined) {
152
+ const storedTime = this._refresherStateCache.get("time");
153
+ if (storedTime != undefined) {
154
+ this._lastTime = storedTime;
155
+ }
156
+ }
157
+ if (this._buildNumber === undefined) {
158
+ const storedBuildNumber = this._refresherStateCache.get("buildNumber");
159
+ if (storedBuildNumber != undefined) {
160
+ this._buildNumber = storedBuildNumber;
161
+ }
162
+ }
163
+
164
+ // 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>`);
165
+ // due to the colon character
166
+ let jsonCache;
167
+ if (this._lastTime === undefined || this._buildNumber === undefined) {
168
+ jsonCache = {
169
+ [this._cacheSchemaId]: {}
170
+ };
171
+ } else {
172
+ jsonCache = {
173
+ buildNumber: this._buildNumber,
174
+ lastModified: this._lastTime,
175
+ [this._cacheSchemaId]: {}
176
+ };
177
+ }
178
+
179
+ const xmlDoc = DomUtil.fromJSON("cache", jsonCache, 'SimpleJson');
180
+ soapCall.writeDocument("cacheEntities", xmlDoc);
181
+
182
+ // Client is not logged: do not attempt to refresh caches at all
183
+ if (!this._client.isLogged())
184
+ throw CampaignException.NOT_LOGGED_IN(soapCall, `Cannot call GetModifiedEntities: session not connected`);
185
+
186
+ const event = this._client._trackEvent('CACHE_REFRESHER//tick', undefined, {
187
+ cacheSchemaId: this._cacheSchemaId,
188
+ lastTime: this._lastTime,
189
+ buildNumber: this._buildNumber
190
+ });
191
+
192
+ // Do a soap call GetModifiedEntities instead of xtksession.GetModifiedEnties because we don't want to go through methodCache
193
+ // which might not contain the method GetModifiedEntities just after a build updgrade from a old version of acc
194
+ return this._client._makeSoapCall(soapCall)
195
+ .then(() => {
196
+ let doc = soapCall.getNextDocument();
197
+ soapCall.checkNoMoreArgs();
198
+ doc = that._client._toRepresentation(doc, 'xml');
199
+ that._lastTime = DomUtil.getAttributeAsString(doc, "time"); // save time to be able to send it as an attribute in the next soap call
200
+ that._buildNumber = DomUtil.getAttributeAsString(doc, "buildNumber");
201
+ that._refresh(doc, event);
202
+ that._refresherStateCache.put("time", that._lastTime);
203
+ that._refresherStateCache.put("buildNumber", that._buildNumber);
204
+ })
205
+ .catch((ex) => {
206
+ // if the method GetModifiedEntities is not found in this acc version we disable the autoresfresh of the cache
207
+ if (soapCall.methodName == "GetModifiedEntities" && ex.errorCode == "SOP-330006") {
208
+ this._client._trackEvent('CACHE_REFRESHER//abort', undefined, {
209
+ cacheSchemaId: this._cacheSchemaId,
210
+ error: ex,
211
+ });
212
+ this.stopAutoRefresh();
213
+ } else {
214
+ throw ex;
215
+ }
216
+ });
217
+ }
218
+
219
+ // Refresh Cache : remove entities modified recently listed in xmlDoc
220
+ _refresh(xmlDoc, event) {
221
+ const clearCache = XtkCaster.asBoolean(DomUtil.getAttributeAsString(xmlDoc, "emptyCache"));
222
+ const evicted = [];
223
+ if (clearCache == true) {
224
+ this._cache.clear();
225
+ } else {
226
+ var child = DomUtil.getFirstChildElement(xmlDoc, "entityCache");
227
+ while (child) {
228
+ const pkSchemaId = DomUtil.getAttributeAsString(child, "pk");
229
+ const schemaId = DomUtil.getAttributeAsString(child, "schema");
230
+ if (schemaId === this._cacheSchemaId) {
231
+ evicted.push(schemaId);
232
+ this._cache.remove(pkSchemaId);
233
+ // Notify listeners to refresh in SchemaCache
234
+ if (schemaId === "xtk:schema") {
235
+ const schemaIds = pkSchemaId.split("|");
236
+ this._client._notifyCacheChangeListeners(schemaIds[1]);
237
+ }
238
+ }
239
+ child = DomUtil.getNextSiblingElement(child);
240
+ }
241
+ }
242
+
243
+ this._client._trackEvent('CACHE_REFRESHER//response', event, {
244
+ cacheSchemaId: this._cacheSchemaId,
245
+ clearCache: clearCache,
246
+ evicted: evicted
247
+ });
248
+ }
249
+
250
+ /**
251
+ * Stop auto refreshing the cache
252
+ */
253
+ stopAutoRefresh() {
254
+ if (this._intervalId) {
255
+ this._client._trackEvent('CACHE_REFRESHER//stop', undefined, {
256
+ cacheSchemaId: this._cacheSchemaId,
257
+ });
258
+ }
259
+ clearInterval(this._intervalId);
260
+ this._intervalId = null;
261
+ }
262
+ }
263
+
264
+ // Public exports
265
+ exports.CacheRefresher = CacheRefresher;
266
+
267
+ })();