@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 +3 -1
- package/CHANGELOG.md +18 -1
- package/README.md +54 -4
- package/compile.js +1 -0
- package/package-lock.json +22 -14
- package/package.json +1 -1
- package/src/application.js +35 -2
- package/src/cache.js +30 -1
- package/src/cacheRefresher.js +267 -0
- package/src/campaign.js +29 -28
- package/src/client.js +403 -110
- package/src/soap.js +1 -1
- package/src/transport.js +11 -10
- package/test/application.test.js +2 -2
- package/test/cacheRefresher.test.js +338 -0
- package/test/caches.test.js +22 -1
- package/test/client.test.js +501 -34
- package/test/mock.js +225 -46
- package/test/observability.test.js +267 -0
- package/test/soap.test.js +1 -1
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/
|
|
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.
|
|
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
|
|
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
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe/acc-js-sdk",
|
|
3
|
-
"version": "1.1.
|
|
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.
|
|
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.
|
|
1406
|
-
"resolved": "https://
|
|
1407
|
-
"integrity": "sha512-
|
|
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
|
-
|
|
1411
|
-
|
|
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://
|
|
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.
|
|
6151
|
-
"resolved": "https://
|
|
6152
|
-
"integrity": "sha512-
|
|
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://
|
|
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
package/src/application.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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
|
+
})();
|