@dwn-protocol/id-sdk 0.2.5 → 0.2.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/package.json +2 -3
- package/src/agent/app-data-store.ts +0 -365
- package/src/agent/did-manager.ts +0 -393
- package/src/agent/dwn-manager.ts +0 -548
- package/src/agent/identity-manager.ts +0 -165
- package/src/agent/index.ts +0 -19
- package/src/agent/json-rpc.ts +0 -107
- package/src/agent/key-manager.ts +0 -302
- package/src/agent/kms-local.ts +0 -412
- package/src/agent/outbox.ts +0 -128
- package/src/agent/rpc-client.ts +0 -223
- package/src/agent/store-managed-did.ts +0 -295
- package/src/agent/store-managed-identity.ts +0 -243
- package/src/agent/store-managed-key.ts +0 -754
- package/src/agent/sync-manager.ts +0 -631
- package/src/agent/test-managed-agent.ts +0 -299
- package/src/agent/types/agent.ts +0 -145
- package/src/agent/types/managed-key.ts +0 -442
- package/src/agent/utils.ts +0 -190
- package/src/common/convert.ts +0 -424
- package/src/common/index.ts +0 -9
- package/src/common/multicodec.ts +0 -176
- package/src/common/object.ts +0 -43
- package/src/common/stores.ts +0 -125
- package/src/common/stream-node.ts +0 -381
- package/src/common/stream.ts +0 -406
- package/src/common/type-utils.ts +0 -117
- package/src/common/types.ts +0 -48
- package/src/credentials/credential-bbs.ts +0 -419
- package/src/credentials/credential.ts +0 -324
- package/src/credentials/index.ts +0 -5
- package/src/credentials/presentation.ts +0 -182
- package/src/credentials/status-list.ts +0 -365
- package/src/credentials/utils.ts +0 -58
- package/src/credentials/validators.ts +0 -52
- package/src/crypto/algorithms-api/aes/base.ts +0 -49
- package/src/crypto/algorithms-api/aes/ctr.ts +0 -51
- package/src/crypto/algorithms-api/aes/index.ts +0 -2
- package/src/crypto/algorithms-api/crypto-algorithm.ts +0 -127
- package/src/crypto/algorithms-api/crypto-key.ts +0 -56
- package/src/crypto/algorithms-api/ec/base.ts +0 -39
- package/src/crypto/algorithms-api/ec/ecdh.ts +0 -53
- package/src/crypto/algorithms-api/ec/ecdsa.ts +0 -37
- package/src/crypto/algorithms-api/ec/eddsa.ts +0 -30
- package/src/crypto/algorithms-api/ec/index.ts +0 -4
- package/src/crypto/algorithms-api/errors.ts +0 -29
- package/src/crypto/algorithms-api/index.ts +0 -6
- package/src/crypto/algorithms-api/pbkdf/index.ts +0 -1
- package/src/crypto/algorithms-api/pbkdf/pbkdf2.ts +0 -91
- package/src/crypto/crypto-algorithms/aes-ctr.ts +0 -70
- package/src/crypto/crypto-algorithms/bbs.ts +0 -110
- package/src/crypto/crypto-algorithms/ecdh.ts +0 -115
- package/src/crypto/crypto-algorithms/ecdsa.ts +0 -111
- package/src/crypto/crypto-algorithms/eddsa.ts +0 -110
- package/src/crypto/crypto-algorithms/index.ts +0 -6
- package/src/crypto/crypto-algorithms/pbkdf2.ts +0 -54
- package/src/crypto/crypto-primitives/aes-ctr.ts +0 -131
- package/src/crypto/crypto-primitives/aes-gcm.ts +0 -138
- package/src/crypto/crypto-primitives/bbs.ts +0 -183
- package/src/crypto/crypto-primitives/concat-kdf.ts +0 -207
- package/src/crypto/crypto-primitives/ed25519.ts +0 -201
- package/src/crypto/crypto-primitives/index.ts +0 -10
- package/src/crypto/crypto-primitives/pbkdf2.ts +0 -78
- package/src/crypto/crypto-primitives/secp256k1.ts +0 -322
- package/src/crypto/crypto-primitives/x25519.ts +0 -101
- package/src/crypto/crypto-primitives/xchacha20-poly1305.ts +0 -46
- package/src/crypto/crypto-primitives/xchacha20.ts +0 -34
- package/src/crypto/index.ts +0 -8
- package/src/crypto/jose.ts +0 -948
- package/src/crypto/types/crypto-key.ts +0 -4
- package/src/crypto/types/iddwn-crypto.ts +0 -119
- package/src/crypto/utils.ts +0 -200
- package/src/did-api.ts +0 -72
- package/src/dids/dht.ts +0 -412
- package/src/dids/did-dht.ts +0 -436
- package/src/dids/did-ion.ts +0 -613
- package/src/dids/did-key.ts +0 -791
- package/src/dids/did-resolver.ts +0 -107
- package/src/dids/index.ts +0 -9
- package/src/dids/resolver-cache-level.ts +0 -82
- package/src/dids/resolver-cache-noop.ts +0 -25
- package/src/dids/types.ts +0 -278
- package/src/dids/utils.ts +0 -129
- package/src/dwn-api.ts +0 -584
- package/src/iddwn.ts +0 -241
- package/src/identity-agent/index.ts +0 -270
- package/src/index.ts +0 -26
- package/src/interfaces/metadata.ts +0 -163
- package/src/interfaces/queue.ts +0 -108
- package/src/interfaces/services.ts +0 -122
- package/src/interfaces/transactions.ts +0 -220
- package/src/protocol.ts +0 -68
- package/src/proxy-agent/index.ts +0 -255
- package/src/record.ts +0 -521
- package/src/service-options.ts +0 -62
- package/src/typings/decentralized-identity__ion-pow-sdk.d.ts +0 -7
- package/src/user-agent/index.ts +0 -295
- package/src/utils.ts +0 -29
- package/src/vc-api.ts +0 -505
|
@@ -1,631 +0,0 @@
|
|
|
1
|
-
import type { AbstractBatchOperation, AbstractLevel } from 'abstract-level';
|
|
2
|
-
|
|
3
|
-
import type {
|
|
4
|
-
EventsGetReply,
|
|
5
|
-
GenericMessage,
|
|
6
|
-
MessagesGetReply,
|
|
7
|
-
RecordsWriteMessage,
|
|
8
|
-
} from '@dwn-protocol/id';
|
|
9
|
-
|
|
10
|
-
import { Level } from 'level';
|
|
11
|
-
import { Convert } from '../common/index.js';
|
|
12
|
-
import { utils as didUtils } from '../dids/index.js';
|
|
13
|
-
import { DataStream } from '@dwn-protocol/id';
|
|
14
|
-
|
|
15
|
-
import type { IDManagedAgent } from './types/agent.js';
|
|
16
|
-
|
|
17
|
-
import { webReadableToIsomorphicNodeReadable } from './utils.js';
|
|
18
|
-
|
|
19
|
-
export interface SyncManager {
|
|
20
|
-
agent: IDManagedAgent;
|
|
21
|
-
registerIdentity(options: { did: string }): Promise<void>;
|
|
22
|
-
runNow(): Promise<void>;
|
|
23
|
-
startSync(options: { interval: number }): Promise<void>;
|
|
24
|
-
stopSync(): void;
|
|
25
|
-
push(): Promise<void>;
|
|
26
|
-
pull(): Promise<void>;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
type LevelDatabase = AbstractLevel<string | Buffer | Uint8Array, string, string>;
|
|
30
|
-
|
|
31
|
-
export type SyncManagerOptions = {
|
|
32
|
-
agent?: IDManagedAgent;
|
|
33
|
-
dataPath?: string;
|
|
34
|
-
db?: LevelDatabase;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
type SyncDirection = 'push' | 'pull';
|
|
38
|
-
|
|
39
|
-
type SyncState = {
|
|
40
|
-
did: string;
|
|
41
|
-
dwnUrl: string;
|
|
42
|
-
watermark: string | undefined;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
type DwnMessage = {
|
|
46
|
-
message: any;
|
|
47
|
-
data?: Blob;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
type DbBatchOperation = AbstractBatchOperation<LevelDatabase, string, string>;
|
|
51
|
-
|
|
52
|
-
const is2xx = (code: number) => code >= 200 && code <= 299;
|
|
53
|
-
const is4xx = (code: number) => code >= 400 && code <= 499;
|
|
54
|
-
|
|
55
|
-
export class SyncManagerLevel implements SyncManager {
|
|
56
|
-
/**
|
|
57
|
-
* Holds the instance of a `IDManagedAgent` that represents the current
|
|
58
|
-
* execution context for the `KeyManager`. This agent is utilized
|
|
59
|
-
* to interact with other agent components. It's vital
|
|
60
|
-
* to ensure this instance is set to correctly contextualize
|
|
61
|
-
* operations within the broader agent framework.
|
|
62
|
-
*/
|
|
63
|
-
private _agent?: IDManagedAgent;
|
|
64
|
-
private _db: LevelDatabase;
|
|
65
|
-
private _syncIntervalId?: ReturnType<typeof setInterval>;
|
|
66
|
-
|
|
67
|
-
constructor(options?: SyncManagerOptions) {
|
|
68
|
-
let { agent, dataPath = 'data/AGENT/SYNC_STORE', db } = options ?? {};
|
|
69
|
-
|
|
70
|
-
this._agent = agent;
|
|
71
|
-
this._db = (db) ? db : new Level(dataPath);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Retrieves the `IDManagedAgent` execution context.
|
|
76
|
-
* If the `agent` instance proprety is undefined, it will throw an error.
|
|
77
|
-
*
|
|
78
|
-
* @returns The `IDManagedAgent` instance that represents the current execution
|
|
79
|
-
* context.
|
|
80
|
-
*
|
|
81
|
-
* @throws Will throw an error if the `agent` instance property is undefined.
|
|
82
|
-
*/
|
|
83
|
-
get agent(): IDManagedAgent {
|
|
84
|
-
if (this._agent === undefined) {
|
|
85
|
-
throw new Error('DidManager: Unable to determine agent execution context.');
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return this._agent;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
set agent(agent: IDManagedAgent) {
|
|
92
|
-
this._agent = agent;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
public async clear(): Promise<void> {
|
|
96
|
-
await this._db.clear();
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
public async pull(): Promise<void> {
|
|
100
|
-
const syncPeerState = await this.getSyncPeerState({ syncDirection: 'pull' });
|
|
101
|
-
await this.enqueueOperations({ syncDirection: 'pull', syncPeerState });
|
|
102
|
-
|
|
103
|
-
const pullQueue = this.getPullQueue();
|
|
104
|
-
const pullJobs = await pullQueue.iterator().all();
|
|
105
|
-
|
|
106
|
-
const deleteOperations: DbBatchOperation[] = [];
|
|
107
|
-
const errored: Set<string> = new Set();
|
|
108
|
-
|
|
109
|
-
for (let job of pullJobs) {
|
|
110
|
-
const [key] = job;
|
|
111
|
-
const [did, dwnUrl, watermark, messageCid] = key.split('~');
|
|
112
|
-
|
|
113
|
-
// If a particular DWN service endpoint is unreachable, skip subsequent pull operations.
|
|
114
|
-
if (errored.has(dwnUrl)) {
|
|
115
|
-
continue;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const messageExists = await this.messageExists(did, messageCid);
|
|
119
|
-
if (messageExists) {
|
|
120
|
-
await this.setWatermark(did, dwnUrl, 'pull', watermark);
|
|
121
|
-
deleteOperations.push({ type: 'del', key: key });
|
|
122
|
-
|
|
123
|
-
continue;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const messagesGet = await this.agent.dwnManager.createMessage({
|
|
127
|
-
author : did,
|
|
128
|
-
messageType : 'MessagesGet',
|
|
129
|
-
messageOptions : {
|
|
130
|
-
messageCids: [messageCid]
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
let reply: MessagesGetReply;
|
|
135
|
-
|
|
136
|
-
try {
|
|
137
|
-
reply = await this.agent.rpcClient.sendDwnRequest({
|
|
138
|
-
dwnUrl,
|
|
139
|
-
targetDid : did,
|
|
140
|
-
message : messagesGet
|
|
141
|
-
}) as MessagesGetReply;
|
|
142
|
-
} catch(e) {
|
|
143
|
-
errored.add(dwnUrl);
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
for (let entry of reply.messages ?? []) {
|
|
148
|
-
if (entry.error || !entry.message) {
|
|
149
|
-
|
|
150
|
-
await this.setWatermark(did, dwnUrl, 'pull', watermark);
|
|
151
|
-
await this.addMessage(did, messageCid);
|
|
152
|
-
deleteOperations.push({ type: 'del', key: key });
|
|
153
|
-
|
|
154
|
-
continue;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const messageType = this.getDwnMessageType(entry.message);
|
|
158
|
-
let dataStream;
|
|
159
|
-
|
|
160
|
-
if (messageType === 'RecordsWrite') {
|
|
161
|
-
const { encodedData } = entry;
|
|
162
|
-
const message = entry.message as RecordsWriteMessage;
|
|
163
|
-
|
|
164
|
-
if (encodedData) {
|
|
165
|
-
const dataBytes = Convert.base64Url(encodedData).toUint8Array();
|
|
166
|
-
dataStream = DataStream.fromBytes(dataBytes);
|
|
167
|
-
} else {
|
|
168
|
-
const recordsRead = await this.agent.dwnManager.createMessage({
|
|
169
|
-
author : did,
|
|
170
|
-
messageType : 'RecordsRead',
|
|
171
|
-
messageOptions : {
|
|
172
|
-
filter: {
|
|
173
|
-
recordId: message.recordId
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
const recordsReadReply = await this.agent.rpcClient.sendDwnRequest({
|
|
179
|
-
dwnUrl,
|
|
180
|
-
targetDid : did,
|
|
181
|
-
message : recordsRead.message
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
const { record, status: readStatus } = recordsReadReply;
|
|
185
|
-
|
|
186
|
-
if (is2xx(readStatus.code) && record) {
|
|
187
|
-
/** If the read was successful, convert the data stream from web ReadableStream
|
|
188
|
-
* to Node.js Readable so that the DWN can process it.*/
|
|
189
|
-
dataStream = webReadableToIsomorphicNodeReadable(record.data as any);
|
|
190
|
-
|
|
191
|
-
} else if (readStatus.code >= 400) {
|
|
192
|
-
const pruneReply = await this.agent.dwnManager.writePrunedRecord({
|
|
193
|
-
targetDid: did,
|
|
194
|
-
message
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
if (pruneReply.status.code === 202 || pruneReply.status.code === 409) {
|
|
198
|
-
await this.setWatermark(did, dwnUrl, 'pull', watermark);
|
|
199
|
-
await this.addMessage(did, messageCid);
|
|
200
|
-
deleteOperations.push({ type: 'del', key: key });
|
|
201
|
-
|
|
202
|
-
continue;
|
|
203
|
-
} else {
|
|
204
|
-
throw new Error(`SyncManager: Failed to sync tombstone for message '${messageCid}'`);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const pullReply = await this.agent.dwnManager.processMessage({
|
|
211
|
-
targetDid : did,
|
|
212
|
-
message : entry.message,
|
|
213
|
-
dataStream
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
if (pullReply.status.code === 202 || pullReply.status.code === 409) {
|
|
217
|
-
await this.setWatermark(did, dwnUrl, 'pull', watermark);
|
|
218
|
-
await this.addMessage(did, messageCid);
|
|
219
|
-
deleteOperations.push({ type: 'del', key: key });
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
await pullQueue.batch(deleteOperations as any);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
public async push(): Promise<void> {
|
|
228
|
-
const syncPeerState = await this.getSyncPeerState({ syncDirection: 'push' });
|
|
229
|
-
await this.enqueueOperations({ syncDirection: 'push', syncPeerState });
|
|
230
|
-
|
|
231
|
-
const pushQueue = this.getPushQueue();
|
|
232
|
-
const pushJobs = await pushQueue.iterator().all();
|
|
233
|
-
|
|
234
|
-
const deleteOperations: DbBatchOperation[] = [];
|
|
235
|
-
const errored: Set<string> = new Set();
|
|
236
|
-
|
|
237
|
-
for (let job of pushJobs) {
|
|
238
|
-
const [key] = job;
|
|
239
|
-
const [did, dwnUrl, watermark, messageCid] = key.split('~');
|
|
240
|
-
|
|
241
|
-
// If a particular DWN service endpoint is unreachable, skip subsequent push operations.
|
|
242
|
-
if (errored.has(dwnUrl)) {
|
|
243
|
-
continue;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// If this message was already synced (e.g., pulled from remote), skip to prevent ping-pong.
|
|
247
|
-
const messageExists = await this.messageExists(did, messageCid);
|
|
248
|
-
if (messageExists) {
|
|
249
|
-
deleteOperations.push({ type: 'del', key: key });
|
|
250
|
-
await this.setWatermark(did, dwnUrl, 'push', watermark);
|
|
251
|
-
continue;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// Attempt to retrieve the message from the local DWN.
|
|
255
|
-
let dwnMessage: DwnMessage | undefined;
|
|
256
|
-
try {
|
|
257
|
-
dwnMessage = await this.getDwnMessage(did, messageCid);
|
|
258
|
-
} catch {
|
|
259
|
-
// If the message can't be retrieved, skip it and advance.
|
|
260
|
-
deleteOperations.push({ type: 'del', key: key });
|
|
261
|
-
await this.setWatermark(did, dwnUrl, 'push', watermark);
|
|
262
|
-
await this.addMessage(did, messageCid);
|
|
263
|
-
continue;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/** If the message does not exist on the local DWN, remove the sync operation from the
|
|
267
|
-
* push queue, update the push watermark for this DID/DWN endpoint combination, add the
|
|
268
|
-
* message to the local message store, and continue to the next job. */
|
|
269
|
-
if (!dwnMessage) {
|
|
270
|
-
deleteOperations.push({ type: 'del', key: key });
|
|
271
|
-
await this.setWatermark(did, dwnUrl, 'push', watermark);
|
|
272
|
-
await this.addMessage(did, messageCid);
|
|
273
|
-
|
|
274
|
-
continue;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
try {
|
|
278
|
-
const reply = await this.agent.rpcClient.sendDwnRequest({
|
|
279
|
-
dwnUrl,
|
|
280
|
-
targetDid : did,
|
|
281
|
-
data : dwnMessage.data,
|
|
282
|
-
message : dwnMessage.message
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
/** Update the watermark and add the messageCid to the Sync Message Store if either:
|
|
286
|
-
* - 202: message was successfully written to the remote DWN
|
|
287
|
-
* - 409: message was already present on the remote DWN
|
|
288
|
-
*/
|
|
289
|
-
if (reply.status.code === 202 || reply.status.code === 409) {
|
|
290
|
-
await this.setWatermark(did, dwnUrl, 'push', watermark);
|
|
291
|
-
await this.addMessage(did, messageCid);
|
|
292
|
-
deleteOperations.push({ type: 'del', key: key });
|
|
293
|
-
}
|
|
294
|
-
} catch {
|
|
295
|
-
// Error is intentionally ignored; 'errored' set is updated with 'dwnUrl'.
|
|
296
|
-
errored.add(dwnUrl);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
await pushQueue.batch(deleteOperations as any);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
public async registerIdentity(options: {
|
|
304
|
-
did: string
|
|
305
|
-
}): Promise<void> {
|
|
306
|
-
const { did } = options;
|
|
307
|
-
|
|
308
|
-
const registeredIdentities = this._db.sublevel('registeredIdentities');
|
|
309
|
-
|
|
310
|
-
await registeredIdentities.put(did, '');
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
public startSync(options: {
|
|
314
|
-
interval: number
|
|
315
|
-
}): Promise<void> {
|
|
316
|
-
const { interval = 60_000 } = options;
|
|
317
|
-
|
|
318
|
-
if (this._syncIntervalId) {
|
|
319
|
-
clearInterval(this._syncIntervalId);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
this._syncIntervalId = setInterval(async () => {
|
|
323
|
-
try {
|
|
324
|
-
await this.push();
|
|
325
|
-
} catch (error) {
|
|
326
|
-
console.error('SyncManager: push error:', error);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
try {
|
|
330
|
-
await this.pull();
|
|
331
|
-
} catch (error) {
|
|
332
|
-
console.error('SyncManager: pull error:', error);
|
|
333
|
-
}
|
|
334
|
-
}, interval);
|
|
335
|
-
|
|
336
|
-
return Promise.resolve();
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
/**
|
|
340
|
-
* Run one cycle of push, pull, and outbox drain. Used by the sync interval
|
|
341
|
-
* and by flushOutboxAndSync() for on-demand sync.
|
|
342
|
-
*/
|
|
343
|
-
public async runNow(): Promise<void> {
|
|
344
|
-
await this.push();
|
|
345
|
-
await this.pull();
|
|
346
|
-
await this.agent.outbox?.drain();
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
public stopSync(): void {
|
|
350
|
-
if (this._syncIntervalId) {
|
|
351
|
-
clearInterval(this._syncIntervalId);
|
|
352
|
-
this._syncIntervalId = undefined;
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
private async enqueueOperations(options: {
|
|
357
|
-
syncDirection: SyncDirection,
|
|
358
|
-
syncPeerState: SyncState[]
|
|
359
|
-
}) {
|
|
360
|
-
const { syncDirection, syncPeerState } = options;
|
|
361
|
-
|
|
362
|
-
for (let syncState of syncPeerState) {
|
|
363
|
-
// Get the event log from the remote DWN if pull sync, or local DWN if push sync.
|
|
364
|
-
const eventLog = await this.getDwnEventLog({
|
|
365
|
-
did : syncState.did,
|
|
366
|
-
dwnUrl : syncState.dwnUrl,
|
|
367
|
-
syncDirection,
|
|
368
|
-
watermark : syncState.watermark
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
const syncOperations: DbBatchOperation[] = [];
|
|
372
|
-
|
|
373
|
-
for (let event of eventLog) {
|
|
374
|
-
/** Use "did~dwnUrl~watermark~messageCid" as the key in the sync queue.
|
|
375
|
-
* Note: It is critical that `watermark` precedes `messageCid` to
|
|
376
|
-
* ensure that when the sync jobs are pulled off the queue, they
|
|
377
|
-
* are lexographically sorted oldest to newest. */
|
|
378
|
-
const operationKey = [
|
|
379
|
-
syncState.did,
|
|
380
|
-
syncState.dwnUrl,
|
|
381
|
-
event.watermark,
|
|
382
|
-
event.messageCid
|
|
383
|
-
].join('~');
|
|
384
|
-
|
|
385
|
-
const operation: DbBatchOperation = { type: 'put', key: operationKey, value: '' };
|
|
386
|
-
|
|
387
|
-
syncOperations.push(operation);
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
if (syncOperations.length > 0) {
|
|
391
|
-
const syncQueue = (syncDirection === 'pull')
|
|
392
|
-
? this.getPullQueue()
|
|
393
|
-
: this.getPushQueue();
|
|
394
|
-
await syncQueue.batch(syncOperations as any);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
private async getDwnEventLog(options: {
|
|
400
|
-
did: string,
|
|
401
|
-
dwnUrl: string,
|
|
402
|
-
syncDirection: SyncDirection,
|
|
403
|
-
watermark?: string
|
|
404
|
-
}) {
|
|
405
|
-
const { did, dwnUrl, syncDirection, watermark } = options;
|
|
406
|
-
|
|
407
|
-
let eventsReply = {} as EventsGetReply;
|
|
408
|
-
|
|
409
|
-
if (syncDirection === 'pull') {
|
|
410
|
-
// When sync is a pull, get the event log from the remote DWN.
|
|
411
|
-
const eventsGetMessage = await this.agent.dwnManager.createMessage({
|
|
412
|
-
author : did,
|
|
413
|
-
messageType : 'EventsGet',
|
|
414
|
-
messageOptions : { watermark }
|
|
415
|
-
});
|
|
416
|
-
|
|
417
|
-
try {
|
|
418
|
-
eventsReply = await this.agent.rpcClient.sendDwnRequest({
|
|
419
|
-
dwnUrl : dwnUrl,
|
|
420
|
-
targetDid : did,
|
|
421
|
-
message : eventsGetMessage
|
|
422
|
-
});
|
|
423
|
-
} catch {
|
|
424
|
-
// If a particular DWN service endpoint is unreachable, silently ignore.
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
} else if (syncDirection === 'push') {
|
|
428
|
-
// When sync is a push, get the event log from the local DWN.
|
|
429
|
-
({ reply: eventsReply } = await this.agent.dwnManager.processRequest({
|
|
430
|
-
author : did,
|
|
431
|
-
target : did,
|
|
432
|
-
messageType : 'EventsGet',
|
|
433
|
-
messageOptions : { watermark }
|
|
434
|
-
}));
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
const eventLog = eventsReply.events ?? [];
|
|
438
|
-
|
|
439
|
-
return eventLog;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
private async getDwnMessage(
|
|
443
|
-
author: string,
|
|
444
|
-
messageCid: string
|
|
445
|
-
): Promise<DwnMessage | undefined> {
|
|
446
|
-
let messagesGetResponse = await this.agent.dwnManager.processRequest({
|
|
447
|
-
author : author,
|
|
448
|
-
target : author,
|
|
449
|
-
messageType : 'MessagesGet',
|
|
450
|
-
messageOptions : {
|
|
451
|
-
messageCids: [messageCid]
|
|
452
|
-
}
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
const reply: MessagesGetReply = messagesGetResponse.reply;
|
|
456
|
-
|
|
457
|
-
/** Absence of a messageEntry or message within messageEntry can happen because updating a
|
|
458
|
-
* Record creates another RecordsWrite with the same recordId. Only the first and
|
|
459
|
-
* most recent RecordsWrite messages are kept for a given recordId. Any RecordsWrite messages
|
|
460
|
-
* that aren't the first or most recent are discarded by the DWN. */
|
|
461
|
-
if (!(reply.messages && reply.messages.length === 1)) {
|
|
462
|
-
return undefined;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
const [ messageEntry ] = reply.messages;
|
|
466
|
-
|
|
467
|
-
let { message } = messageEntry;
|
|
468
|
-
if (!message) {
|
|
469
|
-
return undefined;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
let dwnMessage: DwnMessage = { message };
|
|
473
|
-
const messageType = `${message.descriptor.interface}${message.descriptor.method}`;
|
|
474
|
-
|
|
475
|
-
// if the message is a RecordsWrite, either data will be present, OR we have to get it using a RecordsRead
|
|
476
|
-
if (messageType === 'RecordsWrite') {
|
|
477
|
-
const { encodedData } = messageEntry;
|
|
478
|
-
const writeMessage = message as RecordsWriteMessage;
|
|
479
|
-
|
|
480
|
-
if (encodedData) {
|
|
481
|
-
const dataBytes: any = Convert.base64Url(encodedData).toUint8Array();
|
|
482
|
-
dwnMessage.data = new Blob([dataBytes]);
|
|
483
|
-
} else {
|
|
484
|
-
let readResponse = await this.agent.dwnManager.processRequest({
|
|
485
|
-
author : author,
|
|
486
|
-
target : author,
|
|
487
|
-
messageType : 'RecordsRead',
|
|
488
|
-
messageOptions : {
|
|
489
|
-
filter: {
|
|
490
|
-
recordId: writeMessage.recordId
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
});
|
|
494
|
-
const reply = readResponse.reply;
|
|
495
|
-
|
|
496
|
-
if (is2xx(reply.status.code) && reply.record) {
|
|
497
|
-
// If status code is 200-299, return the data.
|
|
498
|
-
const dataBytes: any = await DataStream.toBytes(reply.record.data);
|
|
499
|
-
dwnMessage.data = new Blob([dataBytes]);
|
|
500
|
-
|
|
501
|
-
} else if (is4xx(reply.status.code)) {
|
|
502
|
-
/** If status code is 400-499, typically 404 indicating the data no longer exists, it is
|
|
503
|
-
* likely that a `RecordsDelete` took place. `RecordsDelete` keeps a `RecordsWrite` and
|
|
504
|
-
* deletes the associated data, effectively acting as a "tombstone." Sync still needs to
|
|
505
|
-
* _push_ this tombstone so that the `RecordsDelete` can be processed successfully. */
|
|
506
|
-
|
|
507
|
-
} else {
|
|
508
|
-
// If status code is anything else (likely 5xx), throw an error.
|
|
509
|
-
const { status } = reply;
|
|
510
|
-
throw new Error(`SyncManager: Failed to read data associated with record ${writeMessage.recordId}. (${status.code}) ${status.detail}}`);
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
return dwnMessage;
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
private async getSyncPeerState(options: {
|
|
519
|
-
syncDirection: SyncDirection
|
|
520
|
-
}): Promise<SyncState[]> {
|
|
521
|
-
const { syncDirection } = options;
|
|
522
|
-
|
|
523
|
-
// Get a list of the DIDs of all registered identities.
|
|
524
|
-
const registeredIdentities = await this._db.sublevel('registeredIdentities').keys().all();
|
|
525
|
-
|
|
526
|
-
// Array to accumulate the list of sync peers for each DID.
|
|
527
|
-
const syncPeerState: SyncState[] = [];
|
|
528
|
-
|
|
529
|
-
for (let did of registeredIdentities) {
|
|
530
|
-
// Resolve the DID to its DID document.
|
|
531
|
-
const { didDocument, didResolutionMetadata } = await this.agent.didResolver.resolve(did);
|
|
532
|
-
|
|
533
|
-
// If DID resolution fails, throw an error.
|
|
534
|
-
if (!didDocument) {
|
|
535
|
-
const errorCode = `${didResolutionMetadata?.error}: ` || '';
|
|
536
|
-
const defaultMessage = `Unable to resolve DID: ${did}`;
|
|
537
|
-
const errorMessage = didResolutionMetadata?.errorMessage ?? defaultMessage;
|
|
538
|
-
throw new Error(`SyncManager: ${errorCode}${errorMessage}`);
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
// Attempt to get the `#dwn` service entry from the DID document.
|
|
542
|
-
const [ service ] = didUtils.getServices({ didDocument, id: '#dwn' });
|
|
543
|
-
|
|
544
|
-
/** Silently ignore and do not try to perform Sync for any DID that does not have a DWN
|
|
545
|
-
* service endpoint published in its DID document. **/
|
|
546
|
-
if (!service) {
|
|
547
|
-
continue;
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
if (!didUtils.isDwnServiceEndpoint(service.serviceEndpoint)) {
|
|
551
|
-
throw new Error(`SyncManager: Malformed '#dwn' service endpoint. Expected array of node addresses.`);
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
/** Get the watermark (or undefined) for each (DID, DWN service endpoint, sync direction)
|
|
555
|
-
* combination and add it to the sync peer state array. */
|
|
556
|
-
for (let dwnUrl of service.serviceEndpoint.nodes) {
|
|
557
|
-
const watermark = await this.getWatermark(did, dwnUrl, syncDirection);
|
|
558
|
-
syncPeerState.push({ did, dwnUrl, watermark });
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
return syncPeerState;
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
private async getWatermark(did: string, dwnUrl: string, direction: SyncDirection) {
|
|
566
|
-
const wmKey = `${did}~${dwnUrl}~${direction}`;
|
|
567
|
-
const watermarkStore = this.getWatermarkStore();
|
|
568
|
-
|
|
569
|
-
try {
|
|
570
|
-
return await watermarkStore.get(wmKey);
|
|
571
|
-
} catch(error: any) {
|
|
572
|
-
// Don't throw when a key wasn't found.
|
|
573
|
-
if (error.notFound) {
|
|
574
|
-
return undefined;
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
private async setWatermark(did: string, dwnUrl: string, direction: SyncDirection, watermark: string) {
|
|
580
|
-
const wmKey = `${did}~${dwnUrl}~${direction}`;
|
|
581
|
-
const watermarkStore = this.getWatermarkStore();
|
|
582
|
-
|
|
583
|
-
await watermarkStore.put(wmKey, watermark);
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
/**
|
|
587
|
-
* The message store is used to prevent "echoes" that occur during a sync pull operation.
|
|
588
|
-
* After a message is confirmed to already be synchronized on the local DWN, its CID is added
|
|
589
|
-
* to the message store to ensure that any subsequent pull attempts are skipped.
|
|
590
|
-
*/
|
|
591
|
-
private async messageExists(did: string, messageCid: string) {
|
|
592
|
-
const messageStore = this.getMessageStore(did);
|
|
593
|
-
|
|
594
|
-
// If the `messageCid` exists in this DID's store, return true. Otherwise, return false.
|
|
595
|
-
try {
|
|
596
|
-
await messageStore.get(messageCid);
|
|
597
|
-
return true;
|
|
598
|
-
} catch (error: any) {
|
|
599
|
-
if (error.notFound) {
|
|
600
|
-
return false;
|
|
601
|
-
}
|
|
602
|
-
throw error;
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
private async addMessage(did: string, messageCid: string) {
|
|
607
|
-
const messageStore = this.getMessageStore(did);
|
|
608
|
-
|
|
609
|
-
return await messageStore.put(messageCid, '');
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
private getMessageStore(did: string) {
|
|
613
|
-
return this._db.sublevel('history').sublevel(did).sublevel('messages');
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
private getWatermarkStore() {
|
|
617
|
-
return this._db.sublevel('watermarks');
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
private getPushQueue() {
|
|
621
|
-
return this._db.sublevel('pushQueue');
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
private getPullQueue() {
|
|
625
|
-
return this._db.sublevel('pullQueue');
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
private getDwnMessageType(message: GenericMessage) {
|
|
629
|
-
return `${message.descriptor.interface}${message.descriptor.method}`;
|
|
630
|
-
}
|
|
631
|
-
}
|