@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 +2 -1
- package/CHANGELOG.md +5 -0
- package/README.md +24 -0
- package/compile.js +1 -0
- package/package-lock.json +19 -12
- package/package.json +1 -1
- package/src/application.js +35 -2
- package/src/cache.js +11 -1
- package/src/cacheRefresher.js +227 -0
- package/src/client.js +69 -14
- package/test/application.test.js +2 -2
- package/test/cacheRefresher.test.js +338 -0
- package/test/caches.test.js +16 -1
- package/test/client.test.js +231 -5
- package/test/mock.js +66 -1
- package/test/soap.test.js +1 -1
package/.eslintrc.js
CHANGED
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
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.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.
|
|
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.
|
|
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",
|
|
@@ -6147,9 +6154,9 @@
|
|
|
6147
6154
|
"dev": true
|
|
6148
6155
|
},
|
|
6149
6156
|
"caniuse-lite": {
|
|
6150
|
-
"version": "1.0.
|
|
6151
|
-
"resolved": "https://
|
|
6152
|
-
"integrity": "sha512-
|
|
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
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
|
/**
|
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
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
|
-
|
|
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
|
}
|
package/test/application.test.js
CHANGED
|
@@ -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
|
+
|