@twin.org/synchronised-storage-service 0.0.1-next.8 → 0.0.3-next.1
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/dist/es/data/verifiableStorageKeys.json +5 -0
- package/dist/es/entities/syncSnapshotEntry.js +93 -0
- package/dist/es/entities/syncSnapshotEntry.js.map +1 -0
- package/dist/es/helpers/blobStorageHelper.js +185 -0
- package/dist/es/helpers/blobStorageHelper.js.map +1 -0
- package/dist/es/helpers/changeSetHelper.js +215 -0
- package/dist/es/helpers/changeSetHelper.js.map +1 -0
- package/dist/es/helpers/localSyncStateHelper.js +384 -0
- package/dist/es/helpers/localSyncStateHelper.js.map +1 -0
- package/dist/es/helpers/remoteSyncStateHelper.js +560 -0
- package/dist/es/helpers/remoteSyncStateHelper.js.map +1 -0
- package/dist/es/helpers/versions.js +6 -0
- package/dist/es/helpers/versions.js.map +1 -0
- package/dist/es/index.js +13 -0
- package/dist/es/index.js.map +1 -0
- package/dist/es/models/ISyncPointerStore.js +4 -0
- package/dist/es/models/ISyncPointerStore.js.map +1 -0
- package/dist/es/models/ISyncSnapshot.js +4 -0
- package/dist/es/models/ISyncSnapshot.js.map +1 -0
- package/dist/es/models/ISyncState.js +2 -0
- package/dist/es/models/ISyncState.js.map +1 -0
- package/dist/es/models/ISynchronisedStorageServiceConfig.js +4 -0
- package/dist/es/models/ISynchronisedStorageServiceConfig.js.map +1 -0
- package/dist/es/models/ISynchronisedStorageServiceConstructorOptions.js +2 -0
- package/dist/es/models/ISynchronisedStorageServiceConstructorOptions.js.map +1 -0
- package/dist/es/restEntryPoints.js +10 -0
- package/dist/es/restEntryPoints.js.map +1 -0
- package/dist/es/schema.js +11 -0
- package/dist/es/schema.js.map +1 -0
- package/dist/es/synchronisedStorageRoutes.js +153 -0
- package/dist/es/synchronisedStorageRoutes.js.map +1 -0
- package/dist/es/synchronisedStorageService.js +554 -0
- package/dist/es/synchronisedStorageService.js.map +1 -0
- package/dist/types/entities/syncSnapshotEntry.d.ts +3 -3
- package/dist/types/helpers/blobStorageHelper.d.ts +3 -3
- package/dist/types/helpers/changeSetHelper.d.ts +16 -32
- package/dist/types/helpers/localSyncStateHelper.d.ts +11 -11
- package/dist/types/helpers/remoteSyncStateHelper.d.ts +18 -14
- package/dist/types/index.d.ts +10 -10
- package/dist/types/models/ISyncState.d.ts +1 -1
- package/dist/types/models/ISynchronisedStorageServiceConfig.d.ts +3 -8
- package/dist/types/models/ISynchronisedStorageServiceConstructorOptions.d.ts +7 -6
- package/dist/types/synchronisedStorageRoutes.d.ts +1 -1
- package/dist/types/synchronisedStorageService.d.ts +18 -21
- package/docs/architecture.md +168 -12
- package/docs/changelog.md +149 -0
- package/docs/open-api/spec.json +62 -57
- package/docs/reference/classes/SyncSnapshotEntry.md +4 -10
- package/docs/reference/classes/SynchronisedStorageService.md +38 -50
- package/docs/reference/interfaces/ISynchronisedStorageServiceConfig.md +3 -17
- package/docs/reference/interfaces/ISynchronisedStorageServiceConstructorOptions.md +9 -8
- package/locales/en.json +11 -16
- package/package.json +26 -9
- package/dist/cjs/index.cjs +0 -2233
- package/dist/esm/index.mjs +0 -2225
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
import { BlobStorageConnectorFactory } from "@twin.org/blob-storage-models";
|
|
2
|
+
import { ContextIdHelper, ContextIdKeys, ContextIdStore } from "@twin.org/context";
|
|
3
|
+
import { BaseError, Coerce, ComponentFactory, Converter, GeneralError, Guards, Is, UnauthorizedError } from "@twin.org/core";
|
|
4
|
+
import { EntityStorageConnectorFactory } from "@twin.org/entity-storage-models";
|
|
5
|
+
import { IdentityAuthenticationContexts, IdentityAuthenticationTypes } from "@twin.org/identity-authentication";
|
|
6
|
+
import { ActionType } from "@twin.org/standards-w3c-odrl";
|
|
7
|
+
import { SynchronisedStorageAssetTypes, SynchronisedStorageTopics } from "@twin.org/synchronised-storage-models";
|
|
8
|
+
import { VaultConnectorFactory, VaultKeyType } from "@twin.org/vault-models";
|
|
9
|
+
import { VerifiableStorageConnectorFactory } from "@twin.org/verifiable-storage-models";
|
|
10
|
+
import verifiableStorageKeys from "./data/verifiableStorageKeys.json" with { type: "json" };
|
|
11
|
+
import { BlobStorageHelper } from "./helpers/blobStorageHelper.js";
|
|
12
|
+
import { ChangeSetHelper } from "./helpers/changeSetHelper.js";
|
|
13
|
+
import { LocalSyncStateHelper } from "./helpers/localSyncStateHelper.js";
|
|
14
|
+
import { RemoteSyncStateHelper } from "./helpers/remoteSyncStateHelper.js";
|
|
15
|
+
/**
|
|
16
|
+
* Class for performing synchronised storage operations.
|
|
17
|
+
*/
|
|
18
|
+
export class SynchronisedStorageService {
|
|
19
|
+
/**
|
|
20
|
+
* Runtime name for the class.
|
|
21
|
+
*/
|
|
22
|
+
static CLASS_NAME = "SynchronisedStorageService";
|
|
23
|
+
/**
|
|
24
|
+
* The default interval to check for entity updates.
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
static _DEFAULT_ENTITY_UPDATE_INTERVAL_MINUTES = 5;
|
|
28
|
+
/**
|
|
29
|
+
* The default interval to perform consolidation.
|
|
30
|
+
* @internal
|
|
31
|
+
*/
|
|
32
|
+
static _DEFAULT_CONSOLIDATION_INTERVAL_MINUTES = 60;
|
|
33
|
+
/**
|
|
34
|
+
* The default size of a consolidation batch.
|
|
35
|
+
* @internal
|
|
36
|
+
*/
|
|
37
|
+
static _DEFAULT_CONSOLIDATION_BATCH_SIZE = 100;
|
|
38
|
+
/**
|
|
39
|
+
* The default max number of consolidations to keep in storage.
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
static _DEFAULT_MAX_CONSOLIDATIONS = 5;
|
|
43
|
+
/**
|
|
44
|
+
* The logging component to use for logging.
|
|
45
|
+
* @internal
|
|
46
|
+
*/
|
|
47
|
+
_logging;
|
|
48
|
+
/**
|
|
49
|
+
* The event bus component.
|
|
50
|
+
* @internal
|
|
51
|
+
*/
|
|
52
|
+
_eventBusComponent;
|
|
53
|
+
/**
|
|
54
|
+
* The vault connector.
|
|
55
|
+
* @internal
|
|
56
|
+
*/
|
|
57
|
+
_vaultConnector;
|
|
58
|
+
/**
|
|
59
|
+
* The storage connector for the sync snapshot entries.
|
|
60
|
+
* @internal
|
|
61
|
+
*/
|
|
62
|
+
_localSyncSnapshotEntryEntityStorage;
|
|
63
|
+
/**
|
|
64
|
+
* The blob storage connector to use for remote sync states.
|
|
65
|
+
* @internal
|
|
66
|
+
*/
|
|
67
|
+
_blobStorageConnector;
|
|
68
|
+
/**
|
|
69
|
+
* The verifiable storage connector to use for storing sync pointers.
|
|
70
|
+
* @internal
|
|
71
|
+
*/
|
|
72
|
+
_verifiableSyncPointerStorageConnector;
|
|
73
|
+
/**
|
|
74
|
+
* The task scheduler component.
|
|
75
|
+
* @internal
|
|
76
|
+
*/
|
|
77
|
+
_taskSchedulerComponent;
|
|
78
|
+
/**
|
|
79
|
+
* The synchronised storage service to use when this is not a trusted node.
|
|
80
|
+
* @internal
|
|
81
|
+
*/
|
|
82
|
+
_trustedSynchronisedStorageComponent;
|
|
83
|
+
/**
|
|
84
|
+
* The policy enforcement point component, used by trusted nodes for incoming requests.
|
|
85
|
+
* @internal
|
|
86
|
+
*/
|
|
87
|
+
_policyEnforcementPointComponent;
|
|
88
|
+
/**
|
|
89
|
+
* The blob storage helper.
|
|
90
|
+
* @internal
|
|
91
|
+
*/
|
|
92
|
+
_blobStorageHelper;
|
|
93
|
+
/**
|
|
94
|
+
* The change set helper.
|
|
95
|
+
* @internal
|
|
96
|
+
*/
|
|
97
|
+
_changeSetHelper;
|
|
98
|
+
/**
|
|
99
|
+
* The local sync state helper to use for applying changesets.
|
|
100
|
+
* @internal
|
|
101
|
+
*/
|
|
102
|
+
_localSyncStateHelper;
|
|
103
|
+
/**
|
|
104
|
+
* The remote sync state helper to use for applying changesets.
|
|
105
|
+
* @internal
|
|
106
|
+
*/
|
|
107
|
+
_remoteSyncStateHelper;
|
|
108
|
+
/**
|
|
109
|
+
* The options for the connector.
|
|
110
|
+
* @internal
|
|
111
|
+
*/
|
|
112
|
+
_config;
|
|
113
|
+
/**
|
|
114
|
+
* The synchronised storage key to use for the remote synchronised storage.
|
|
115
|
+
* @internal
|
|
116
|
+
*/
|
|
117
|
+
_synchronisedStorageKey;
|
|
118
|
+
/**
|
|
119
|
+
* The flag to determine if the service has been started.
|
|
120
|
+
* @internal
|
|
121
|
+
*/
|
|
122
|
+
_serviceStarted;
|
|
123
|
+
/**
|
|
124
|
+
* The active storage keys for the synchronised storage service.
|
|
125
|
+
* @internal
|
|
126
|
+
*/
|
|
127
|
+
_activeStorageKeys;
|
|
128
|
+
/**
|
|
129
|
+
* The identity of the node this connector is running on.
|
|
130
|
+
* @internal
|
|
131
|
+
*/
|
|
132
|
+
_nodeId;
|
|
133
|
+
/**
|
|
134
|
+
* Create a new instance of SynchronisedStorageService.
|
|
135
|
+
* @param options The options for the service.
|
|
136
|
+
*/
|
|
137
|
+
constructor(options) {
|
|
138
|
+
Guards.object(SynchronisedStorageService.CLASS_NAME, "options", options);
|
|
139
|
+
Guards.object(SynchronisedStorageService.CLASS_NAME, "options.config", options.config);
|
|
140
|
+
Guards.stringValue(SynchronisedStorageService.CLASS_NAME, "options.config.verifiableStorageKeyId", options.config.verifiableStorageKeyId);
|
|
141
|
+
this._eventBusComponent = ComponentFactory.get(options.eventBusComponentType ?? "event-bus");
|
|
142
|
+
this._logging = ComponentFactory.getIfExists(options.loggingComponentType ?? "logging");
|
|
143
|
+
this._vaultConnector = VaultConnectorFactory.get(options.vaultConnectorType ?? "vault");
|
|
144
|
+
this._localSyncSnapshotEntryEntityStorage = EntityStorageConnectorFactory.get(options.syncSnapshotStorageConnectorType ?? "sync-snapshot-entry");
|
|
145
|
+
this._verifiableSyncPointerStorageConnector = VerifiableStorageConnectorFactory.get(options.verifiableStorageConnectorType ?? "verifiable-storage");
|
|
146
|
+
this._blobStorageConnector = BlobStorageConnectorFactory.get(options.blobStorageConnectorType ?? "blob-storage");
|
|
147
|
+
this._taskSchedulerComponent = ComponentFactory.get(options.taskSchedulerComponentType ?? "task-scheduler");
|
|
148
|
+
// If this is empty we assume the local node has the rights to write to the verifiable storage.
|
|
149
|
+
let isTrustedNode = true;
|
|
150
|
+
if (!Is.empty(options.trustedSynchronisedStorageComponentType)) {
|
|
151
|
+
isTrustedNode = false;
|
|
152
|
+
// If it is set then we used the trusted component to send changesets to
|
|
153
|
+
this._trustedSynchronisedStorageComponent =
|
|
154
|
+
ComponentFactory.get(options.trustedSynchronisedStorageComponentType);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
// A trusted node must have a policy enforcement point component
|
|
158
|
+
this._policyEnforcementPointComponent = ComponentFactory.get(options?.policyEnforcementPointComponentType ?? "policy-enforcement-point");
|
|
159
|
+
}
|
|
160
|
+
this._config = {
|
|
161
|
+
entityUpdateIntervalMinutes: options.config.entityUpdateIntervalMinutes ??
|
|
162
|
+
SynchronisedStorageService._DEFAULT_ENTITY_UPDATE_INTERVAL_MINUTES,
|
|
163
|
+
consolidationIntervalMinutes: options.config.consolidationIntervalMinutes ??
|
|
164
|
+
SynchronisedStorageService._DEFAULT_CONSOLIDATION_INTERVAL_MINUTES,
|
|
165
|
+
consolidationBatchSize: options.config.consolidationBatchSize ??
|
|
166
|
+
SynchronisedStorageService._DEFAULT_CONSOLIDATION_BATCH_SIZE,
|
|
167
|
+
maxConsolidations: options.config.maxConsolidations ?? SynchronisedStorageService._DEFAULT_MAX_CONSOLIDATIONS,
|
|
168
|
+
blobStorageEncryptionKeyId: options.config.blobStorageEncryptionKeyId ?? "synchronised-storage-blob-encryption-key",
|
|
169
|
+
verifiableStorageKeyId: options.config.verifiableStorageKeyId
|
|
170
|
+
};
|
|
171
|
+
this._synchronisedStorageKey =
|
|
172
|
+
verifiableStorageKeys[options.config.verifiableStorageKeyId] ?? options.config.verifiableStorageKeyId;
|
|
173
|
+
Guards.stringValue(SynchronisedStorageService.CLASS_NAME, "synchronisedStorageKey", this._synchronisedStorageKey);
|
|
174
|
+
this._blobStorageHelper = new BlobStorageHelper(this._logging, this._vaultConnector, this._blobStorageConnector, this._config.blobStorageEncryptionKeyId, isTrustedNode);
|
|
175
|
+
this._changeSetHelper = new ChangeSetHelper(this._logging, this._eventBusComponent, this._blobStorageHelper);
|
|
176
|
+
this._localSyncStateHelper = new LocalSyncStateHelper(this._logging, this._localSyncSnapshotEntryEntityStorage, this._changeSetHelper);
|
|
177
|
+
this._remoteSyncStateHelper = new RemoteSyncStateHelper(this._logging, this._eventBusComponent, this._verifiableSyncPointerStorageConnector, this._blobStorageHelper, this._changeSetHelper, isTrustedNode, this._config.maxConsolidations);
|
|
178
|
+
this._serviceStarted = false;
|
|
179
|
+
this._activeStorageKeys = {};
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Returns the class name of the component.
|
|
183
|
+
* @returns The class name of the component.
|
|
184
|
+
*/
|
|
185
|
+
className() {
|
|
186
|
+
return SynchronisedStorageService.CLASS_NAME;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* The component needs to be started when the node is initialized.
|
|
190
|
+
* @param nodeLoggingComponentType The node logging component type.
|
|
191
|
+
* @returns Nothing.
|
|
192
|
+
*/
|
|
193
|
+
async start(nodeLoggingComponentType) {
|
|
194
|
+
const contextIds = await ContextIdStore.getContextIds();
|
|
195
|
+
ContextIdHelper.guard(contextIds, ContextIdKeys.Node);
|
|
196
|
+
this._nodeId = contextIds[ContextIdKeys.Node];
|
|
197
|
+
this._remoteSyncStateHelper.setNodeId(this._nodeId);
|
|
198
|
+
this._changeSetHelper.setNodeId(this._nodeId);
|
|
199
|
+
this._remoteSyncStateHelper.setSynchronisedStorageKey(this._synchronisedStorageKey);
|
|
200
|
+
this._serviceStarted = true;
|
|
201
|
+
// If this is not a trusted node we need to request the decryption key from a trusted node
|
|
202
|
+
if (!Is.empty(this._trustedSynchronisedStorageComponent)) {
|
|
203
|
+
const actionRequest = {
|
|
204
|
+
"@context": IdentityAuthenticationContexts.ContextRoot,
|
|
205
|
+
type: IdentityAuthenticationTypes.ActionRequest,
|
|
206
|
+
action: "get-key",
|
|
207
|
+
requester: contextIds[ContextIdKeys.Node]
|
|
208
|
+
};
|
|
209
|
+
const decryptionKey = await this._trustedSynchronisedStorageComponent.getDecryptionKey(actionRequest);
|
|
210
|
+
// If the key exists remove it and get a new one, in case the key has been rotated
|
|
211
|
+
const existingKey = await this._vaultConnector.getKey(this._config.blobStorageEncryptionKeyId);
|
|
212
|
+
if (!Is.empty(existingKey)) {
|
|
213
|
+
await this._vaultConnector.removeKey(this._config.blobStorageEncryptionKeyId);
|
|
214
|
+
}
|
|
215
|
+
await this._vaultConnector.addKey(this._config.blobStorageEncryptionKeyId, VaultKeyType.ChaCha20Poly1305, Converter.base64ToBytes(decryptionKey));
|
|
216
|
+
}
|
|
217
|
+
await this._eventBusComponent.subscribe(SynchronisedStorageTopics.RegisterStorageKey, async (event) => this.registerStorageKey(event.data));
|
|
218
|
+
await this._eventBusComponent.subscribe(SynchronisedStorageTopics.LocalItemChange, async (event) => {
|
|
219
|
+
// Make sure the change event is from this node
|
|
220
|
+
if (Is.stringValue(this._nodeId) && this._nodeId === event.data.nodeId) {
|
|
221
|
+
await this._localSyncStateHelper.addLocalChange(event.data.storageKey, event.data.operation, event.data.id);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
await this._remoteSyncStateHelper.start();
|
|
225
|
+
// If there are already storage keys registered, we need to activate them
|
|
226
|
+
for (const storageKey in this._activeStorageKeys) {
|
|
227
|
+
await this.activateStorageKey(storageKey);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* The component needs to be stopped when the node is closed.
|
|
232
|
+
* @param nodeLoggingComponentType The node logging component type.
|
|
233
|
+
* @returns Nothing.
|
|
234
|
+
*/
|
|
235
|
+
async stop(nodeLoggingComponentType) {
|
|
236
|
+
for (const storageKey in this._activeStorageKeys) {
|
|
237
|
+
this._activeStorageKeys[storageKey] = false;
|
|
238
|
+
await this._taskSchedulerComponent.removeTask(`synchronised-storage-update-${storageKey}`);
|
|
239
|
+
await this._taskSchedulerComponent.removeTask(`synchronised-storage-consolidation-${storageKey}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Get the decryption key for the synchronised storage.
|
|
244
|
+
* This is used to decrypt the data stored in the synchronised storage.
|
|
245
|
+
* @param actionRequest The action request used in the verifiable credential.
|
|
246
|
+
* @returns The decryption key.
|
|
247
|
+
*/
|
|
248
|
+
async getDecryptionKey(actionRequest) {
|
|
249
|
+
if (!Is.empty(this._trustedSynchronisedStorageComponent)) {
|
|
250
|
+
throw new GeneralError(SynchronisedStorageService.CLASS_NAME, "notTrustedNode");
|
|
251
|
+
}
|
|
252
|
+
Guards.objectValue(SynchronisedStorageService.CLASS_NAME, "actionRequest", actionRequest);
|
|
253
|
+
if (actionRequest.action !== "get-key") {
|
|
254
|
+
throw new GeneralError(SynchronisedStorageService.CLASS_NAME, "incorrectActionType", {
|
|
255
|
+
action: actionRequest.action,
|
|
256
|
+
expecting: "get-key"
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
const result = await this._policyEnforcementPointComponent?.intercept({
|
|
260
|
+
assignee: actionRequest.requester,
|
|
261
|
+
assetType: SynchronisedStorageAssetTypes.DecryptionKey,
|
|
262
|
+
action: ActionType.Read
|
|
263
|
+
});
|
|
264
|
+
if (!(Coerce.boolean(result) ?? false)) {
|
|
265
|
+
throw new UnauthorizedError(SynchronisedStorageService.CLASS_NAME, "decryptionKeyNotAllowed", {
|
|
266
|
+
nodeId: actionRequest.requester
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
await this._logging?.log({
|
|
270
|
+
level: "info",
|
|
271
|
+
source: SynchronisedStorageService.CLASS_NAME,
|
|
272
|
+
message: "decryptionKeyRequest",
|
|
273
|
+
data: {
|
|
274
|
+
nodeId: actionRequest.requester
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
const key = await this._vaultConnector.getKey(this._config.blobStorageEncryptionKeyId);
|
|
278
|
+
if (Is.undefined(key.privateKey)) {
|
|
279
|
+
throw new UnauthorizedError(SynchronisedStorageService.CLASS_NAME, "decryptionKeyNotFound");
|
|
280
|
+
}
|
|
281
|
+
return Converter.bytesToBase64(key.privateKey);
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Synchronise a set of changes from an untrusted node, assumes this is a trusted node.
|
|
285
|
+
* @param syncChangeSet The change set to synchronise.
|
|
286
|
+
* @param actionRequest The action request used in the verifiable credential.
|
|
287
|
+
* @returns Nothing.
|
|
288
|
+
*/
|
|
289
|
+
async syncChangeSet(syncChangeSet, actionRequest) {
|
|
290
|
+
if (!Is.empty(this._trustedSynchronisedStorageComponent)) {
|
|
291
|
+
throw new GeneralError(SynchronisedStorageService.CLASS_NAME, "notTrustedNode");
|
|
292
|
+
}
|
|
293
|
+
Guards.object(SynchronisedStorageService.CLASS_NAME, "syncChangeSet", syncChangeSet);
|
|
294
|
+
Guards.objectValue(SynchronisedStorageService.CLASS_NAME, "actionRequest", actionRequest);
|
|
295
|
+
if (actionRequest.action !== "sync-changeset") {
|
|
296
|
+
throw new GeneralError(SynchronisedStorageService.CLASS_NAME, "incorrectActionType", {
|
|
297
|
+
action: actionRequest.action,
|
|
298
|
+
expecting: "sync-changeset"
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
const result = await this._policyEnforcementPointComponent?.intercept({
|
|
302
|
+
assignee: actionRequest.requester,
|
|
303
|
+
assetType: SynchronisedStorageAssetTypes.ChangeSet,
|
|
304
|
+
action: ActionType.Read
|
|
305
|
+
});
|
|
306
|
+
if (!(Coerce.boolean(result) ?? false)) {
|
|
307
|
+
throw new UnauthorizedError(SynchronisedStorageService.CLASS_NAME, "changeSetNotAllowed", {
|
|
308
|
+
nodeId: actionRequest.requester,
|
|
309
|
+
changeSetStorageId: syncChangeSet.id
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
await this._logging?.log({
|
|
313
|
+
level: "info",
|
|
314
|
+
source: SynchronisedStorageService.CLASS_NAME,
|
|
315
|
+
message: "syncChangeSetForRemoteNode",
|
|
316
|
+
data: {
|
|
317
|
+
changeSetStorageId: syncChangeSet.id,
|
|
318
|
+
nodeId: actionRequest.requester
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
const copy = await this._changeSetHelper.copyChangeset(syncChangeSet);
|
|
322
|
+
if (!Is.empty(copy)) {
|
|
323
|
+
// Apply the changes to this node
|
|
324
|
+
await this._changeSetHelper.applyChangeset(copy.syncChangeSet);
|
|
325
|
+
// And update the sync state with the latest changes
|
|
326
|
+
await this._remoteSyncStateHelper.addChangeSetToSyncState(copy.syncChangeSet.storageKey, copy.changeSetStorageId);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Start the sync with further updates after an interval.
|
|
331
|
+
* @param storageKey The storage key to sync.
|
|
332
|
+
* @returns Nothing.
|
|
333
|
+
* @internal
|
|
334
|
+
*/
|
|
335
|
+
async startEntitySync(storageKey) {
|
|
336
|
+
try {
|
|
337
|
+
await this._logging?.log({
|
|
338
|
+
level: "info",
|
|
339
|
+
source: SynchronisedStorageService.CLASS_NAME,
|
|
340
|
+
message: "startEntitySync",
|
|
341
|
+
data: {
|
|
342
|
+
storageKey
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
// First we check for remote changes
|
|
346
|
+
await this.updateFromRemoteSyncState(storageKey);
|
|
347
|
+
// Now send any updates we have to the remote storage
|
|
348
|
+
await this.updateFromLocalSyncState(storageKey);
|
|
349
|
+
}
|
|
350
|
+
catch (error) {
|
|
351
|
+
await this._logging?.log({
|
|
352
|
+
level: "error",
|
|
353
|
+
source: SynchronisedStorageService.CLASS_NAME,
|
|
354
|
+
message: "entitySyncFailed",
|
|
355
|
+
error: BaseError.fromError(error)
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Check for updates in the remote storage.
|
|
361
|
+
* @param storageKey The storage key to check for updates.
|
|
362
|
+
* @returns Nothing.
|
|
363
|
+
* @internal
|
|
364
|
+
*/
|
|
365
|
+
async updateFromRemoteSyncState(storageKey) {
|
|
366
|
+
await this._logging?.log({
|
|
367
|
+
level: "info",
|
|
368
|
+
source: SynchronisedStorageService.CLASS_NAME,
|
|
369
|
+
message: "updateFromRemoteSyncState",
|
|
370
|
+
data: {
|
|
371
|
+
storageKey
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
// Get the verifiable sync pointer store from the verifiable storage
|
|
375
|
+
const verifiableSyncPointerStore = await this._remoteSyncStateHelper.getVerifiableSyncPointerStore();
|
|
376
|
+
if (!Is.empty(verifiableSyncPointerStore.syncPointers[storageKey])) {
|
|
377
|
+
// Load the sync state from the remote blob storage using the sync pointer
|
|
378
|
+
// to load the sync state
|
|
379
|
+
const remoteSyncState = await this._remoteSyncStateHelper.getSyncState(verifiableSyncPointerStore.syncPointers[storageKey]);
|
|
380
|
+
// If we got the sync state we can try and sync from it
|
|
381
|
+
if (!Is.undefined(remoteSyncState)) {
|
|
382
|
+
await this._localSyncStateHelper.applySyncState(storageKey, remoteSyncState);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Find any local updates and send them to the remote storage.
|
|
388
|
+
* @returns Nothing.
|
|
389
|
+
* @internal
|
|
390
|
+
*/
|
|
391
|
+
async updateFromLocalSyncState(storageKey) {
|
|
392
|
+
await this._logging?.log({
|
|
393
|
+
level: "info",
|
|
394
|
+
source: SynchronisedStorageService.CLASS_NAME,
|
|
395
|
+
message: "updateFromLocalSyncState",
|
|
396
|
+
data: {
|
|
397
|
+
storageKey
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
const localChangeSnapshots = await this._localSyncStateHelper.getSnapshots(storageKey, true);
|
|
401
|
+
if (localChangeSnapshots.length > 0) {
|
|
402
|
+
const localChangeSnapshot = localChangeSnapshots[0];
|
|
403
|
+
if (Is.arrayValue(localChangeSnapshot.changes)) {
|
|
404
|
+
await this._remoteSyncStateHelper.buildChangeSet(storageKey, localChangeSnapshot.changes, async (syncChangeSet, changeSetStorageId) => {
|
|
405
|
+
if (Is.empty(syncChangeSet) && Is.empty(changeSetStorageId)) {
|
|
406
|
+
await this._logging?.log({
|
|
407
|
+
level: "info",
|
|
408
|
+
source: SynchronisedStorageService.CLASS_NAME,
|
|
409
|
+
message: "builtStorageChangeSetNone",
|
|
410
|
+
data: {
|
|
411
|
+
storageKey
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
await this._logging?.log({
|
|
417
|
+
level: "info",
|
|
418
|
+
source: SynchronisedStorageService.CLASS_NAME,
|
|
419
|
+
message: "builtStorageChangeSet",
|
|
420
|
+
data: {
|
|
421
|
+
storageKey,
|
|
422
|
+
changeSetStorageId
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
// Send the local changes to the remote storage if we are a trusted node
|
|
426
|
+
if (Is.empty(this._trustedSynchronisedStorageComponent) &&
|
|
427
|
+
Is.stringValue(changeSetStorageId)) {
|
|
428
|
+
// If we are a trusted node, we can add the change set to the sync state
|
|
429
|
+
// and remove the local change snapshot
|
|
430
|
+
await this._remoteSyncStateHelper.addChangeSetToSyncState(storageKey, changeSetStorageId);
|
|
431
|
+
await this._localSyncStateHelper.removeLocalChangeSnapshot(localChangeSnapshot);
|
|
432
|
+
}
|
|
433
|
+
else if (!Is.empty(this._trustedSynchronisedStorageComponent) &&
|
|
434
|
+
Is.object(syncChangeSet) &&
|
|
435
|
+
Is.stringValue(this._nodeId)) {
|
|
436
|
+
// If we are not a trusted node, we need to send the changes to the trusted node
|
|
437
|
+
// and then remove the local change snapshot
|
|
438
|
+
await this._logging?.log({
|
|
439
|
+
level: "info",
|
|
440
|
+
source: SynchronisedStorageService.CLASS_NAME,
|
|
441
|
+
message: "sendingChangeSetToTrustedNode",
|
|
442
|
+
data: {
|
|
443
|
+
storageKey,
|
|
444
|
+
changeSetStorageId
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
const actionRequest = {
|
|
448
|
+
"@context": IdentityAuthenticationContexts.ContextRoot,
|
|
449
|
+
type: IdentityAuthenticationTypes.ActionRequest,
|
|
450
|
+
action: "sync-changeset",
|
|
451
|
+
requester: this._nodeId
|
|
452
|
+
};
|
|
453
|
+
await this._trustedSynchronisedStorageComponent.syncChangeSet(syncChangeSet, actionRequest);
|
|
454
|
+
await this._localSyncStateHelper.removeLocalChangeSnapshot(localChangeSnapshot);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
await this._logging?.log({
|
|
461
|
+
level: "info",
|
|
462
|
+
source: SynchronisedStorageService.CLASS_NAME,
|
|
463
|
+
message: "updateFromLocalSyncStateNoChanges",
|
|
464
|
+
data: {
|
|
465
|
+
storageKey
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Start the consolidation sync.
|
|
473
|
+
* @param storageKey The storage key to consolidate.
|
|
474
|
+
* @returns Nothing.
|
|
475
|
+
* @internal
|
|
476
|
+
*/
|
|
477
|
+
async startConsolidationSync(storageKey) {
|
|
478
|
+
try {
|
|
479
|
+
// If we are going to perform a consolidation first take any local updates
|
|
480
|
+
// we have and create a changeset from them, so that anybody applying
|
|
481
|
+
// just changes since a consolidation can use the changeset
|
|
482
|
+
// and skip the consolidation
|
|
483
|
+
await this.updateFromLocalSyncState(storageKey);
|
|
484
|
+
// Now start the consolidation
|
|
485
|
+
await this._remoteSyncStateHelper.consolidationStart(storageKey, this._config.consolidationBatchSize ??
|
|
486
|
+
SynchronisedStorageService._DEFAULT_CONSOLIDATION_BATCH_SIZE);
|
|
487
|
+
}
|
|
488
|
+
catch (error) {
|
|
489
|
+
await this._logging?.log({
|
|
490
|
+
level: "error",
|
|
491
|
+
source: SynchronisedStorageService.CLASS_NAME,
|
|
492
|
+
message: "consolidationSyncFailed",
|
|
493
|
+
error: BaseError.fromError(error)
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Register a new sync type.
|
|
499
|
+
* @param syncRegisterStorageKey The sync register type to register.
|
|
500
|
+
* @internal
|
|
501
|
+
*/
|
|
502
|
+
async registerStorageKey(syncRegisterStorageKey) {
|
|
503
|
+
await this._logging?.log({
|
|
504
|
+
level: "info",
|
|
505
|
+
source: SynchronisedStorageService.CLASS_NAME,
|
|
506
|
+
message: "registerStorageKey",
|
|
507
|
+
data: {
|
|
508
|
+
storageKey: syncRegisterStorageKey.storageKey
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
if (Is.empty(this._activeStorageKeys[syncRegisterStorageKey.storageKey])) {
|
|
512
|
+
this._activeStorageKeys[syncRegisterStorageKey.storageKey] = false;
|
|
513
|
+
if (this._serviceStarted) {
|
|
514
|
+
await this.activateStorageKey(syncRegisterStorageKey.storageKey);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Activate a storage key.
|
|
520
|
+
* @param storageKey The storage key to activate.
|
|
521
|
+
* @internal
|
|
522
|
+
*/
|
|
523
|
+
async activateStorageKey(storageKey) {
|
|
524
|
+
if (!Is.empty(this._activeStorageKeys[storageKey]) && !this._activeStorageKeys[storageKey]) {
|
|
525
|
+
await this._logging?.log({
|
|
526
|
+
level: "info",
|
|
527
|
+
source: SynchronisedStorageService.CLASS_NAME,
|
|
528
|
+
message: "activateStorageKey",
|
|
529
|
+
data: {
|
|
530
|
+
storageKey
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
this._activeStorageKeys[storageKey] = true;
|
|
534
|
+
if (this._config.entityUpdateIntervalMinutes > 0) {
|
|
535
|
+
await this._taskSchedulerComponent.addTask(`synchronised-storage-update-${storageKey}`, [
|
|
536
|
+
{
|
|
537
|
+
nextTriggerTime: Date.now(),
|
|
538
|
+
intervalMinutes: this._config.entityUpdateIntervalMinutes
|
|
539
|
+
}
|
|
540
|
+
], async () => this.startEntitySync(storageKey));
|
|
541
|
+
}
|
|
542
|
+
if (!Is.empty(this._trustedSynchronisedStorageComponent) &&
|
|
543
|
+
this._config.consolidationIntervalMinutes > 0) {
|
|
544
|
+
await this._taskSchedulerComponent.addTask(`synchronised-storage-consolidation-${storageKey}`, [
|
|
545
|
+
{
|
|
546
|
+
nextTriggerTime: Date.now(),
|
|
547
|
+
intervalMinutes: this._config.consolidationIntervalMinutes
|
|
548
|
+
}
|
|
549
|
+
], async () => this.startConsolidationSync(storageKey));
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
//# sourceMappingURL=synchronisedStorageService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"synchronisedStorageService.js","sourceRoot":"","sources":["../../src/synchronisedStorageService.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { ITaskSchedulerComponent } from \"@twin.org/background-task-models\";\nimport {\n\tBlobStorageConnectorFactory,\n\ttype IBlobStorageConnector\n} from \"@twin.org/blob-storage-models\";\nimport { ContextIdHelper, ContextIdKeys, ContextIdStore } from \"@twin.org/context\";\nimport {\n\tBaseError,\n\tCoerce,\n\tComponentFactory,\n\tConverter,\n\tGeneralError,\n\tGuards,\n\tIs,\n\tUnauthorizedError\n} from \"@twin.org/core\";\nimport {\n\tEntityStorageConnectorFactory,\n\ttype IEntityStorageConnector\n} from \"@twin.org/entity-storage-models\";\nimport type { IEventBusComponent } from \"@twin.org/event-bus-models\";\nimport {\n\tIdentityAuthenticationContexts,\n\tIdentityAuthenticationTypes,\n\ttype IIdentityAuthenticationActionRequest\n} from \"@twin.org/identity-authentication\";\nimport type { ILoggingComponent } from \"@twin.org/logging-models\";\nimport { nameof } from \"@twin.org/nameof\";\nimport type { IPolicyEnforcementPointComponent } from \"@twin.org/rights-management-models\";\nimport { ActionType } from \"@twin.org/standards-w3c-odrl\";\nimport {\n\ttype ISyncChangeSet,\n\ttype ISynchronisedStorageComponent,\n\ttype ISyncItemChange,\n\ttype ISyncRegisterStorageKey,\n\tSynchronisedStorageAssetTypes,\n\tSynchronisedStorageTopics\n} from \"@twin.org/synchronised-storage-models\";\nimport { type IVaultConnector, VaultConnectorFactory, VaultKeyType } from \"@twin.org/vault-models\";\nimport {\n\ttype IVerifiableStorageConnector,\n\tVerifiableStorageConnectorFactory\n} from \"@twin.org/verifiable-storage-models\";\nimport verifiableStorageKeys from \"./data/verifiableStorageKeys.json\" with { type: \"json\" };\nimport type { SyncSnapshotEntry } from \"./entities/syncSnapshotEntry.js\";\nimport { BlobStorageHelper } from \"./helpers/blobStorageHelper.js\";\nimport { ChangeSetHelper } from \"./helpers/changeSetHelper.js\";\nimport { LocalSyncStateHelper } from \"./helpers/localSyncStateHelper.js\";\nimport { RemoteSyncStateHelper } from \"./helpers/remoteSyncStateHelper.js\";\nimport type { ISynchronisedStorageServiceConfig } from \"./models/ISynchronisedStorageServiceConfig.js\";\nimport type { ISynchronisedStorageServiceConstructorOptions } from \"./models/ISynchronisedStorageServiceConstructorOptions.js\";\n\n/**\n * Class for performing synchronised storage operations.\n */\nexport class SynchronisedStorageService implements ISynchronisedStorageComponent {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<SynchronisedStorageService>();\n\n\t/**\n\t * The default interval to check for entity updates.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_ENTITY_UPDATE_INTERVAL_MINUTES: number = 5;\n\n\t/**\n\t * The default interval to perform consolidation.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_CONSOLIDATION_INTERVAL_MINUTES: number = 60;\n\n\t/**\n\t * The default size of a consolidation batch.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_CONSOLIDATION_BATCH_SIZE: number = 100;\n\n\t/**\n\t * The default max number of consolidations to keep in storage.\n\t * @internal\n\t */\n\tprivate static readonly _DEFAULT_MAX_CONSOLIDATIONS: number = 5;\n\n\t/**\n\t * The logging component to use for logging.\n\t * @internal\n\t */\n\tprivate readonly _logging?: ILoggingComponent;\n\n\t/**\n\t * The event bus component.\n\t * @internal\n\t */\n\tprivate readonly _eventBusComponent: IEventBusComponent;\n\n\t/**\n\t * The vault connector.\n\t * @internal\n\t */\n\tprivate readonly _vaultConnector: IVaultConnector;\n\n\t/**\n\t * The storage connector for the sync snapshot entries.\n\t * @internal\n\t */\n\tprivate readonly _localSyncSnapshotEntryEntityStorage: IEntityStorageConnector<SyncSnapshotEntry>;\n\n\t/**\n\t * The blob storage connector to use for remote sync states.\n\t * @internal\n\t */\n\tprivate readonly _blobStorageConnector: IBlobStorageConnector;\n\n\t/**\n\t * The verifiable storage connector to use for storing sync pointers.\n\t * @internal\n\t */\n\tprivate readonly _verifiableSyncPointerStorageConnector: IVerifiableStorageConnector;\n\n\t/**\n\t * The task scheduler component.\n\t * @internal\n\t */\n\tprivate readonly _taskSchedulerComponent: ITaskSchedulerComponent;\n\n\t/**\n\t * The synchronised storage service to use when this is not a trusted node.\n\t * @internal\n\t */\n\tprivate readonly _trustedSynchronisedStorageComponent?: ISynchronisedStorageComponent;\n\n\t/**\n\t * The policy enforcement point component, used by trusted nodes for incoming requests.\n\t * @internal\n\t */\n\tprivate readonly _policyEnforcementPointComponent?: IPolicyEnforcementPointComponent;\n\n\t/**\n\t * The blob storage helper.\n\t * @internal\n\t */\n\tprivate readonly _blobStorageHelper: BlobStorageHelper;\n\n\t/**\n\t * The change set helper.\n\t * @internal\n\t */\n\tprivate readonly _changeSetHelper: ChangeSetHelper;\n\n\t/**\n\t * The local sync state helper to use for applying changesets.\n\t * @internal\n\t */\n\tprivate readonly _localSyncStateHelper: LocalSyncStateHelper;\n\n\t/**\n\t * The remote sync state helper to use for applying changesets.\n\t * @internal\n\t */\n\tprivate readonly _remoteSyncStateHelper: RemoteSyncStateHelper;\n\n\t/**\n\t * The options for the connector.\n\t * @internal\n\t */\n\tprivate readonly _config: Required<ISynchronisedStorageServiceConfig>;\n\n\t/**\n\t * The synchronised storage key to use for the remote synchronised storage.\n\t * @internal\n\t */\n\tprivate readonly _synchronisedStorageKey: string;\n\n\t/**\n\t * The flag to determine if the service has been started.\n\t * @internal\n\t */\n\tprivate _serviceStarted: boolean;\n\n\t/**\n\t * The active storage keys for the synchronised storage service.\n\t * @internal\n\t */\n\tprivate readonly _activeStorageKeys: { [storageKey: string]: boolean };\n\n\t/**\n\t * The identity of the node this connector is running on.\n\t * @internal\n\t */\n\tprivate _nodeId?: string;\n\n\t/**\n\t * Create a new instance of SynchronisedStorageService.\n\t * @param options The options for the service.\n\t */\n\tconstructor(options: ISynchronisedStorageServiceConstructorOptions) {\n\t\tGuards.object<ISynchronisedStorageServiceConstructorOptions>(\n\t\t\tSynchronisedStorageService.CLASS_NAME,\n\t\t\tnameof(options),\n\t\t\toptions\n\t\t);\n\t\tGuards.object<ISynchronisedStorageServiceConfig>(\n\t\t\tSynchronisedStorageService.CLASS_NAME,\n\t\t\tnameof(options.config),\n\t\t\toptions.config\n\t\t);\n\t\tGuards.stringValue(\n\t\t\tSynchronisedStorageService.CLASS_NAME,\n\t\t\tnameof(options.config.verifiableStorageKeyId),\n\t\t\toptions.config.verifiableStorageKeyId\n\t\t);\n\n\t\tthis._eventBusComponent = ComponentFactory.get(options.eventBusComponentType ?? \"event-bus\");\n\t\tthis._logging = ComponentFactory.getIfExists(options.loggingComponentType ?? \"logging\");\n\t\tthis._vaultConnector = VaultConnectorFactory.get(options.vaultConnectorType ?? \"vault\");\n\n\t\tthis._localSyncSnapshotEntryEntityStorage = EntityStorageConnectorFactory.get<\n\t\t\tIEntityStorageConnector<SyncSnapshotEntry>\n\t\t>(options.syncSnapshotStorageConnectorType ?? \"sync-snapshot-entry\");\n\n\t\tthis._verifiableSyncPointerStorageConnector = VerifiableStorageConnectorFactory.get(\n\t\t\toptions.verifiableStorageConnectorType ?? \"verifiable-storage\"\n\t\t);\n\n\t\tthis._blobStorageConnector = BlobStorageConnectorFactory.get(\n\t\t\toptions.blobStorageConnectorType ?? \"blob-storage\"\n\t\t);\n\n\t\tthis._taskSchedulerComponent = ComponentFactory.get(\n\t\t\toptions.taskSchedulerComponentType ?? \"task-scheduler\"\n\t\t);\n\n\t\t// If this is empty we assume the local node has the rights to write to the verifiable storage.\n\t\tlet isTrustedNode = true;\n\t\tif (!Is.empty(options.trustedSynchronisedStorageComponentType)) {\n\t\t\tisTrustedNode = false;\n\n\t\t\t// If it is set then we used the trusted component to send changesets to\n\t\t\tthis._trustedSynchronisedStorageComponent =\n\t\t\t\tComponentFactory.get<ISynchronisedStorageComponent>(\n\t\t\t\t\toptions.trustedSynchronisedStorageComponentType\n\t\t\t\t);\n\t\t} else {\n\t\t\t// A trusted node must have a policy enforcement point component\n\t\t\tthis._policyEnforcementPointComponent = ComponentFactory.get(\n\t\t\t\toptions?.policyEnforcementPointComponentType ?? \"policy-enforcement-point\"\n\t\t\t);\n\t\t}\n\n\t\tthis._config = {\n\t\t\tentityUpdateIntervalMinutes:\n\t\t\t\toptions.config.entityUpdateIntervalMinutes ??\n\t\t\t\tSynchronisedStorageService._DEFAULT_ENTITY_UPDATE_INTERVAL_MINUTES,\n\t\t\tconsolidationIntervalMinutes:\n\t\t\t\toptions.config.consolidationIntervalMinutes ??\n\t\t\t\tSynchronisedStorageService._DEFAULT_CONSOLIDATION_INTERVAL_MINUTES,\n\t\t\tconsolidationBatchSize:\n\t\t\t\toptions.config.consolidationBatchSize ??\n\t\t\t\tSynchronisedStorageService._DEFAULT_CONSOLIDATION_BATCH_SIZE,\n\t\t\tmaxConsolidations:\n\t\t\t\toptions.config.maxConsolidations ?? SynchronisedStorageService._DEFAULT_MAX_CONSOLIDATIONS,\n\t\t\tblobStorageEncryptionKeyId:\n\t\t\t\toptions.config.blobStorageEncryptionKeyId ?? \"synchronised-storage-blob-encryption-key\",\n\t\t\tverifiableStorageKeyId: options.config.verifiableStorageKeyId\n\t\t};\n\n\t\tthis._synchronisedStorageKey =\n\t\t\tverifiableStorageKeys[\n\t\t\t\toptions.config.verifiableStorageKeyId as keyof typeof verifiableStorageKeys\n\t\t\t] ?? options.config.verifiableStorageKeyId;\n\n\t\tGuards.stringValue(\n\t\t\tSynchronisedStorageService.CLASS_NAME,\n\t\t\t\"synchronisedStorageKey\",\n\t\t\tthis._synchronisedStorageKey\n\t\t);\n\n\t\tthis._blobStorageHelper = new BlobStorageHelper(\n\t\t\tthis._logging,\n\t\t\tthis._vaultConnector,\n\t\t\tthis._blobStorageConnector,\n\t\t\tthis._config.blobStorageEncryptionKeyId,\n\t\t\tisTrustedNode\n\t\t);\n\n\t\tthis._changeSetHelper = new ChangeSetHelper(\n\t\t\tthis._logging,\n\t\t\tthis._eventBusComponent,\n\t\t\tthis._blobStorageHelper\n\t\t);\n\n\t\tthis._localSyncStateHelper = new LocalSyncStateHelper(\n\t\t\tthis._logging,\n\t\t\tthis._localSyncSnapshotEntryEntityStorage,\n\t\t\tthis._changeSetHelper\n\t\t);\n\n\t\tthis._remoteSyncStateHelper = new RemoteSyncStateHelper(\n\t\t\tthis._logging,\n\t\t\tthis._eventBusComponent,\n\t\t\tthis._verifiableSyncPointerStorageConnector,\n\t\t\tthis._blobStorageHelper,\n\t\t\tthis._changeSetHelper,\n\t\t\tisTrustedNode,\n\t\t\tthis._config.maxConsolidations\n\t\t);\n\n\t\tthis._serviceStarted = false;\n\t\tthis._activeStorageKeys = {};\n\t}\n\n\t/**\n\t * Returns the class name of the component.\n\t * @returns The class name of the component.\n\t */\n\tpublic className(): string {\n\t\treturn SynchronisedStorageService.CLASS_NAME;\n\t}\n\n\t/**\n\t * The component needs to be started when the node is initialized.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns Nothing.\n\t */\n\tpublic async start(nodeLoggingComponentType?: string): Promise<void> {\n\t\tconst contextIds = await ContextIdStore.getContextIds();\n\t\tContextIdHelper.guard(contextIds, ContextIdKeys.Node);\n\t\tthis._nodeId = contextIds[ContextIdKeys.Node];\n\n\t\tthis._remoteSyncStateHelper.setNodeId(this._nodeId);\n\t\tthis._changeSetHelper.setNodeId(this._nodeId);\n\n\t\tthis._remoteSyncStateHelper.setSynchronisedStorageKey(this._synchronisedStorageKey);\n\t\tthis._serviceStarted = true;\n\n\t\t// If this is not a trusted node we need to request the decryption key from a trusted node\n\t\tif (!Is.empty(this._trustedSynchronisedStorageComponent)) {\n\t\t\tconst actionRequest: IIdentityAuthenticationActionRequest = {\n\t\t\t\t\"@context\": IdentityAuthenticationContexts.ContextRoot,\n\t\t\t\ttype: IdentityAuthenticationTypes.ActionRequest,\n\t\t\t\taction: \"get-key\",\n\t\t\t\trequester: contextIds[ContextIdKeys.Node]\n\t\t\t};\n\n\t\t\tconst decryptionKey =\n\t\t\t\tawait this._trustedSynchronisedStorageComponent.getDecryptionKey(actionRequest);\n\n\t\t\t// If the key exists remove it and get a new one, in case the key has been rotated\n\t\t\tconst existingKey = await this._vaultConnector.getKey(\n\t\t\t\tthis._config.blobStorageEncryptionKeyId\n\t\t\t);\n\n\t\t\tif (!Is.empty(existingKey)) {\n\t\t\t\tawait this._vaultConnector.removeKey(this._config.blobStorageEncryptionKeyId);\n\t\t\t}\n\n\t\t\tawait this._vaultConnector.addKey(\n\t\t\t\tthis._config.blobStorageEncryptionKeyId,\n\t\t\t\tVaultKeyType.ChaCha20Poly1305,\n\t\t\t\tConverter.base64ToBytes(decryptionKey)\n\t\t\t);\n\t\t}\n\n\t\tawait this._eventBusComponent.subscribe<ISyncRegisterStorageKey>(\n\t\t\tSynchronisedStorageTopics.RegisterStorageKey,\n\t\t\tasync event => this.registerStorageKey(event.data)\n\t\t);\n\n\t\tawait this._eventBusComponent.subscribe<ISyncItemChange>(\n\t\t\tSynchronisedStorageTopics.LocalItemChange,\n\t\t\tasync event => {\n\t\t\t\t// Make sure the change event is from this node\n\t\t\t\tif (Is.stringValue(this._nodeId) && this._nodeId === event.data.nodeId) {\n\t\t\t\t\tawait this._localSyncStateHelper.addLocalChange(\n\t\t\t\t\t\tevent.data.storageKey,\n\t\t\t\t\t\tevent.data.operation,\n\t\t\t\t\t\tevent.data.id\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t);\n\n\t\tawait this._remoteSyncStateHelper.start();\n\n\t\t// If there are already storage keys registered, we need to activate them\n\t\tfor (const storageKey in this._activeStorageKeys) {\n\t\t\tawait this.activateStorageKey(storageKey);\n\t\t}\n\t}\n\n\t/**\n\t * The component needs to be stopped when the node is closed.\n\t * @param nodeLoggingComponentType The node logging component type.\n\t * @returns Nothing.\n\t */\n\tpublic async stop(nodeLoggingComponentType?: string): Promise<void> {\n\t\tfor (const storageKey in this._activeStorageKeys) {\n\t\t\tthis._activeStorageKeys[storageKey] = false;\n\t\t\tawait this._taskSchedulerComponent.removeTask(`synchronised-storage-update-${storageKey}`);\n\t\t\tawait this._taskSchedulerComponent.removeTask(\n\t\t\t\t`synchronised-storage-consolidation-${storageKey}`\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Get the decryption key for the synchronised storage.\n\t * This is used to decrypt the data stored in the synchronised storage.\n\t * @param actionRequest The action request used in the verifiable credential.\n\t * @returns The decryption key.\n\t */\n\tpublic async getDecryptionKey(\n\t\tactionRequest: IIdentityAuthenticationActionRequest\n\t): Promise<string> {\n\t\tif (!Is.empty(this._trustedSynchronisedStorageComponent)) {\n\t\t\tthrow new GeneralError(SynchronisedStorageService.CLASS_NAME, \"notTrustedNode\");\n\t\t}\n\n\t\tGuards.objectValue<IIdentityAuthenticationActionRequest>(\n\t\t\tSynchronisedStorageService.CLASS_NAME,\n\t\t\tnameof(actionRequest),\n\t\t\tactionRequest\n\t\t);\n\n\t\tif (actionRequest.action !== \"get-key\") {\n\t\t\tthrow new GeneralError(SynchronisedStorageService.CLASS_NAME, \"incorrectActionType\", {\n\t\t\t\taction: actionRequest.action,\n\t\t\t\texpecting: \"get-key\"\n\t\t\t});\n\t\t}\n\n\t\tconst result = await this._policyEnforcementPointComponent?.intercept({\n\t\t\tassignee: actionRequest.requester,\n\t\t\tassetType: SynchronisedStorageAssetTypes.DecryptionKey,\n\t\t\taction: ActionType.Read\n\t\t});\n\n\t\tif (!(Coerce.boolean(result) ?? false)) {\n\t\t\tthrow new UnauthorizedError(\n\t\t\t\tSynchronisedStorageService.CLASS_NAME,\n\t\t\t\t\"decryptionKeyNotAllowed\",\n\t\t\t\t{\n\t\t\t\t\tnodeId: actionRequest.requester\n\t\t\t\t}\n\t\t\t);\n\t\t}\n\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: SynchronisedStorageService.CLASS_NAME,\n\t\t\tmessage: \"decryptionKeyRequest\",\n\t\t\tdata: {\n\t\t\t\tnodeId: actionRequest.requester\n\t\t\t}\n\t\t});\n\n\t\tconst key = await this._vaultConnector.getKey(this._config.blobStorageEncryptionKeyId);\n\t\tif (Is.undefined(key.privateKey)) {\n\t\t\tthrow new UnauthorizedError(SynchronisedStorageService.CLASS_NAME, \"decryptionKeyNotFound\");\n\t\t}\n\n\t\treturn Converter.bytesToBase64(key.privateKey);\n\t}\n\n\t/**\n\t * Synchronise a set of changes from an untrusted node, assumes this is a trusted node.\n\t * @param syncChangeSet The change set to synchronise.\n\t * @param actionRequest The action request used in the verifiable credential.\n\t * @returns Nothing.\n\t */\n\tpublic async syncChangeSet(\n\t\tsyncChangeSet: ISyncChangeSet,\n\t\tactionRequest: IIdentityAuthenticationActionRequest\n\t): Promise<void> {\n\t\tif (!Is.empty(this._trustedSynchronisedStorageComponent)) {\n\t\t\tthrow new GeneralError(SynchronisedStorageService.CLASS_NAME, \"notTrustedNode\");\n\t\t}\n\n\t\tGuards.object<ISyncChangeSet>(\n\t\t\tSynchronisedStorageService.CLASS_NAME,\n\t\t\tnameof(syncChangeSet),\n\t\t\tsyncChangeSet\n\t\t);\n\t\tGuards.objectValue<IIdentityAuthenticationActionRequest>(\n\t\t\tSynchronisedStorageService.CLASS_NAME,\n\t\t\tnameof(actionRequest),\n\t\t\tactionRequest\n\t\t);\n\n\t\tif (actionRequest.action !== \"sync-changeset\") {\n\t\t\tthrow new GeneralError(SynchronisedStorageService.CLASS_NAME, \"incorrectActionType\", {\n\t\t\t\taction: actionRequest.action,\n\t\t\t\texpecting: \"sync-changeset\"\n\t\t\t});\n\t\t}\n\n\t\tconst result = await this._policyEnforcementPointComponent?.intercept({\n\t\t\tassignee: actionRequest.requester,\n\t\t\tassetType: SynchronisedStorageAssetTypes.ChangeSet,\n\t\t\taction: ActionType.Read\n\t\t});\n\n\t\tif (!(Coerce.boolean(result) ?? false)) {\n\t\t\tthrow new UnauthorizedError(SynchronisedStorageService.CLASS_NAME, \"changeSetNotAllowed\", {\n\t\t\t\tnodeId: actionRequest.requester,\n\t\t\t\tchangeSetStorageId: syncChangeSet.id\n\t\t\t});\n\t\t}\n\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: SynchronisedStorageService.CLASS_NAME,\n\t\t\tmessage: \"syncChangeSetForRemoteNode\",\n\t\t\tdata: {\n\t\t\t\tchangeSetStorageId: syncChangeSet.id,\n\t\t\t\tnodeId: actionRequest.requester\n\t\t\t}\n\t\t});\n\n\t\tconst copy = await this._changeSetHelper.copyChangeset(syncChangeSet);\n\n\t\tif (!Is.empty(copy)) {\n\t\t\t// Apply the changes to this node\n\t\t\tawait this._changeSetHelper.applyChangeset(copy.syncChangeSet);\n\n\t\t\t// And update the sync state with the latest changes\n\t\t\tawait this._remoteSyncStateHelper.addChangeSetToSyncState(\n\t\t\t\tcopy.syncChangeSet.storageKey,\n\t\t\t\tcopy.changeSetStorageId\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Start the sync with further updates after an interval.\n\t * @param storageKey The storage key to sync.\n\t * @returns Nothing.\n\t * @internal\n\t */\n\tprivate async startEntitySync(storageKey: string): Promise<void> {\n\t\ttry {\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"info\",\n\t\t\t\tsource: SynchronisedStorageService.CLASS_NAME,\n\t\t\t\tmessage: \"startEntitySync\",\n\t\t\t\tdata: {\n\t\t\t\t\tstorageKey\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// First we check for remote changes\n\t\t\tawait this.updateFromRemoteSyncState(storageKey);\n\n\t\t\t// Now send any updates we have to the remote storage\n\t\t\tawait this.updateFromLocalSyncState(storageKey);\n\t\t} catch (error) {\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"error\",\n\t\t\t\tsource: SynchronisedStorageService.CLASS_NAME,\n\t\t\t\tmessage: \"entitySyncFailed\",\n\t\t\t\terror: BaseError.fromError(error)\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Check for updates in the remote storage.\n\t * @param storageKey The storage key to check for updates.\n\t * @returns Nothing.\n\t * @internal\n\t */\n\tprivate async updateFromRemoteSyncState(storageKey: string): Promise<void> {\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: SynchronisedStorageService.CLASS_NAME,\n\t\t\tmessage: \"updateFromRemoteSyncState\",\n\t\t\tdata: {\n\t\t\t\tstorageKey\n\t\t\t}\n\t\t});\n\n\t\t// Get the verifiable sync pointer store from the verifiable storage\n\t\tconst verifiableSyncPointerStore =\n\t\t\tawait this._remoteSyncStateHelper.getVerifiableSyncPointerStore();\n\n\t\tif (!Is.empty(verifiableSyncPointerStore.syncPointers[storageKey])) {\n\t\t\t// Load the sync state from the remote blob storage using the sync pointer\n\t\t\t// to load the sync state\n\t\t\tconst remoteSyncState = await this._remoteSyncStateHelper.getSyncState(\n\t\t\t\tverifiableSyncPointerStore.syncPointers[storageKey]\n\t\t\t);\n\n\t\t\t// If we got the sync state we can try and sync from it\n\t\t\tif (!Is.undefined(remoteSyncState)) {\n\t\t\t\tawait this._localSyncStateHelper.applySyncState(storageKey, remoteSyncState);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Find any local updates and send them to the remote storage.\n\t * @returns Nothing.\n\t * @internal\n\t */\n\tprivate async updateFromLocalSyncState(storageKey: string): Promise<void> {\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: SynchronisedStorageService.CLASS_NAME,\n\t\t\tmessage: \"updateFromLocalSyncState\",\n\t\t\tdata: {\n\t\t\t\tstorageKey\n\t\t\t}\n\t\t});\n\n\t\tconst localChangeSnapshots = await this._localSyncStateHelper.getSnapshots(storageKey, true);\n\n\t\tif (localChangeSnapshots.length > 0) {\n\t\t\tconst localChangeSnapshot = localChangeSnapshots[0];\n\n\t\t\tif (Is.arrayValue(localChangeSnapshot.changes)) {\n\t\t\t\tawait this._remoteSyncStateHelper.buildChangeSet(\n\t\t\t\t\tstorageKey,\n\t\t\t\t\tlocalChangeSnapshot.changes,\n\t\t\t\t\tasync (syncChangeSet, changeSetStorageId) => {\n\t\t\t\t\t\tif (Is.empty(syncChangeSet) && Is.empty(changeSetStorageId)) {\n\t\t\t\t\t\t\tawait this._logging?.log({\n\t\t\t\t\t\t\t\tlevel: \"info\",\n\t\t\t\t\t\t\t\tsource: SynchronisedStorageService.CLASS_NAME,\n\t\t\t\t\t\t\t\tmessage: \"builtStorageChangeSetNone\",\n\t\t\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\t\t\tstorageKey\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tawait this._logging?.log({\n\t\t\t\t\t\t\t\tlevel: \"info\",\n\t\t\t\t\t\t\t\tsource: SynchronisedStorageService.CLASS_NAME,\n\t\t\t\t\t\t\t\tmessage: \"builtStorageChangeSet\",\n\t\t\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\t\t\tstorageKey,\n\t\t\t\t\t\t\t\t\tchangeSetStorageId\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t// Send the local changes to the remote storage if we are a trusted node\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tIs.empty(this._trustedSynchronisedStorageComponent) &&\n\t\t\t\t\t\t\t\tIs.stringValue(changeSetStorageId)\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t// If we are a trusted node, we can add the change set to the sync state\n\t\t\t\t\t\t\t\t// and remove the local change snapshot\n\t\t\t\t\t\t\t\tawait this._remoteSyncStateHelper.addChangeSetToSyncState(\n\t\t\t\t\t\t\t\t\tstorageKey,\n\t\t\t\t\t\t\t\t\tchangeSetStorageId\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tawait this._localSyncStateHelper.removeLocalChangeSnapshot(localChangeSnapshot);\n\t\t\t\t\t\t\t} else if (\n\t\t\t\t\t\t\t\t!Is.empty(this._trustedSynchronisedStorageComponent) &&\n\t\t\t\t\t\t\t\tIs.object(syncChangeSet) &&\n\t\t\t\t\t\t\t\tIs.stringValue(this._nodeId)\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t// If we are not a trusted node, we need to send the changes to the trusted node\n\t\t\t\t\t\t\t\t// and then remove the local change snapshot\n\t\t\t\t\t\t\t\tawait this._logging?.log({\n\t\t\t\t\t\t\t\t\tlevel: \"info\",\n\t\t\t\t\t\t\t\t\tsource: SynchronisedStorageService.CLASS_NAME,\n\t\t\t\t\t\t\t\t\tmessage: \"sendingChangeSetToTrustedNode\",\n\t\t\t\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\t\t\t\tstorageKey,\n\t\t\t\t\t\t\t\t\t\tchangeSetStorageId\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t\tconst actionRequest: IIdentityAuthenticationActionRequest = {\n\t\t\t\t\t\t\t\t\t\"@context\": IdentityAuthenticationContexts.ContextRoot,\n\t\t\t\t\t\t\t\t\ttype: IdentityAuthenticationTypes.ActionRequest,\n\t\t\t\t\t\t\t\t\taction: \"sync-changeset\",\n\t\t\t\t\t\t\t\t\trequester: this._nodeId\n\t\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\t\tawait this._trustedSynchronisedStorageComponent.syncChangeSet(\n\t\t\t\t\t\t\t\t\tsyncChangeSet,\n\t\t\t\t\t\t\t\t\tactionRequest\n\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\tawait this._localSyncStateHelper.removeLocalChangeSnapshot(localChangeSnapshot);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tawait this._logging?.log({\n\t\t\t\t\tlevel: \"info\",\n\t\t\t\t\tsource: SynchronisedStorageService.CLASS_NAME,\n\t\t\t\t\tmessage: \"updateFromLocalSyncStateNoChanges\",\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tstorageKey\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Start the consolidation sync.\n\t * @param storageKey The storage key to consolidate.\n\t * @returns Nothing.\n\t * @internal\n\t */\n\tprivate async startConsolidationSync(storageKey: string): Promise<void> {\n\t\ttry {\n\t\t\t// If we are going to perform a consolidation first take any local updates\n\t\t\t// we have and create a changeset from them, so that anybody applying\n\t\t\t// just changes since a consolidation can use the changeset\n\t\t\t// and skip the consolidation\n\t\t\tawait this.updateFromLocalSyncState(storageKey);\n\n\t\t\t// Now start the consolidation\n\t\t\tawait this._remoteSyncStateHelper.consolidationStart(\n\t\t\t\tstorageKey,\n\t\t\t\tthis._config.consolidationBatchSize ??\n\t\t\t\t\tSynchronisedStorageService._DEFAULT_CONSOLIDATION_BATCH_SIZE\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"error\",\n\t\t\t\tsource: SynchronisedStorageService.CLASS_NAME,\n\t\t\t\tmessage: \"consolidationSyncFailed\",\n\t\t\t\terror: BaseError.fromError(error)\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Register a new sync type.\n\t * @param syncRegisterStorageKey The sync register type to register.\n\t * @internal\n\t */\n\tprivate async registerStorageKey(syncRegisterStorageKey: ISyncRegisterStorageKey): Promise<void> {\n\t\tawait this._logging?.log({\n\t\t\tlevel: \"info\",\n\t\t\tsource: SynchronisedStorageService.CLASS_NAME,\n\t\t\tmessage: \"registerStorageKey\",\n\t\t\tdata: {\n\t\t\t\tstorageKey: syncRegisterStorageKey.storageKey\n\t\t\t}\n\t\t});\n\n\t\tif (Is.empty(this._activeStorageKeys[syncRegisterStorageKey.storageKey])) {\n\t\t\tthis._activeStorageKeys[syncRegisterStorageKey.storageKey] = false;\n\n\t\t\tif (this._serviceStarted) {\n\t\t\t\tawait this.activateStorageKey(syncRegisterStorageKey.storageKey);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Activate a storage key.\n\t * @param storageKey The storage key to activate.\n\t * @internal\n\t */\n\tprivate async activateStorageKey(storageKey: string): Promise<void> {\n\t\tif (!Is.empty(this._activeStorageKeys[storageKey]) && !this._activeStorageKeys[storageKey]) {\n\t\t\tawait this._logging?.log({\n\t\t\t\tlevel: \"info\",\n\t\t\t\tsource: SynchronisedStorageService.CLASS_NAME,\n\t\t\t\tmessage: \"activateStorageKey\",\n\t\t\t\tdata: {\n\t\t\t\t\tstorageKey\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tthis._activeStorageKeys[storageKey] = true;\n\n\t\t\tif (this._config.entityUpdateIntervalMinutes > 0) {\n\t\t\t\tawait this._taskSchedulerComponent.addTask(\n\t\t\t\t\t`synchronised-storage-update-${storageKey}`,\n\t\t\t\t\t[\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnextTriggerTime: Date.now(),\n\t\t\t\t\t\t\tintervalMinutes: this._config.entityUpdateIntervalMinutes\n\t\t\t\t\t\t}\n\t\t\t\t\t],\n\t\t\t\t\tasync () => this.startEntitySync(storageKey)\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (\n\t\t\t\t!Is.empty(this._trustedSynchronisedStorageComponent) &&\n\t\t\t\tthis._config.consolidationIntervalMinutes > 0\n\t\t\t) {\n\t\t\t\tawait this._taskSchedulerComponent.addTask(\n\t\t\t\t\t`synchronised-storage-consolidation-${storageKey}`,\n\t\t\t\t\t[\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnextTriggerTime: Date.now(),\n\t\t\t\t\t\t\tintervalMinutes: this._config.consolidationIntervalMinutes\n\t\t\t\t\t\t}\n\t\t\t\t\t],\n\t\t\t\t\tasync () => this.startConsolidationSync(storageKey)\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
|