@enbox/dwn-sdk-js 0.3.7 → 0.3.8
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/browser.mjs +8 -8
- package/dist/browser.mjs.map +4 -4
- package/dist/esm/generated/precompiled-validators.js +2591 -1435
- package/dist/esm/generated/precompiled-validators.js.map +1 -1
- package/dist/esm/src/core/constants.js +20 -0
- package/dist/esm/src/core/constants.js.map +1 -1
- package/dist/esm/src/core/dwn-error.js +24 -1
- package/dist/esm/src/core/dwn-error.js.map +1 -1
- package/dist/esm/src/core/grant-authorization.js +4 -4
- package/dist/esm/src/core/grant-authorization.js.map +1 -1
- package/dist/esm/src/core/message.js +89 -4
- package/dist/esm/src/core/message.js.map +1 -1
- package/dist/esm/src/core/messages-grant-authorization.js +147 -55
- package/dist/esm/src/core/messages-grant-authorization.js.map +1 -1
- package/dist/esm/src/core/protocol-authorization.js +76 -0
- package/dist/esm/src/core/protocol-authorization.js.map +1 -1
- package/dist/esm/src/core/records-grant-authorization.js +40 -15
- package/dist/esm/src/core/records-grant-authorization.js.map +1 -1
- package/dist/esm/src/handlers/messages-read.js +5 -5
- package/dist/esm/src/handlers/messages-read.js.map +1 -1
- package/dist/esm/src/handlers/messages-subscribe.js +109 -7
- package/dist/esm/src/handlers/messages-subscribe.js.map +1 -1
- package/dist/esm/src/handlers/messages-sync.js +341 -96
- package/dist/esm/src/handlers/messages-sync.js.map +1 -1
- package/dist/esm/src/handlers/protocols-configure.js +81 -2
- package/dist/esm/src/handlers/protocols-configure.js.map +1 -1
- package/dist/esm/src/handlers/records-count.js +30 -0
- package/dist/esm/src/handlers/records-count.js.map +1 -1
- package/dist/esm/src/handlers/records-delete.js +3 -2
- package/dist/esm/src/handlers/records-delete.js.map +1 -1
- package/dist/esm/src/handlers/records-query.js +30 -0
- package/dist/esm/src/handlers/records-query.js.map +1 -1
- package/dist/esm/src/handlers/records-read.js +3 -2
- package/dist/esm/src/handlers/records-read.js.map +1 -1
- package/dist/esm/src/handlers/records-subscribe.js +31 -0
- package/dist/esm/src/handlers/records-subscribe.js.map +1 -1
- package/dist/esm/src/handlers/records-write.js +3 -2
- package/dist/esm/src/handlers/records-write.js.map +1 -1
- package/dist/esm/src/index.js +2 -0
- package/dist/esm/src/index.js.map +1 -1
- package/dist/esm/src/interfaces/messages-read.js +6 -3
- package/dist/esm/src/interfaces/messages-read.js.map +1 -1
- package/dist/esm/src/interfaces/messages-subscribe.js +6 -3
- package/dist/esm/src/interfaces/messages-subscribe.js.map +1 -1
- package/dist/esm/src/interfaces/messages-sync.js +17 -3
- package/dist/esm/src/interfaces/messages-sync.js.map +1 -1
- package/dist/esm/src/interfaces/protocols-configure.js +5 -2
- package/dist/esm/src/interfaces/protocols-configure.js.map +1 -1
- package/dist/esm/src/interfaces/protocols-query.js +8 -4
- package/dist/esm/src/interfaces/protocols-query.js.map +1 -1
- package/dist/esm/src/interfaces/records-count.js +5 -0
- package/dist/esm/src/interfaces/records-count.js.map +1 -1
- package/dist/esm/src/interfaces/records-delete.js +6 -2
- package/dist/esm/src/interfaces/records-delete.js.map +1 -1
- package/dist/esm/src/interfaces/records-query.js +5 -0
- package/dist/esm/src/interfaces/records-query.js.map +1 -1
- package/dist/esm/src/interfaces/records-read.js +6 -3
- package/dist/esm/src/interfaces/records-read.js.map +1 -1
- package/dist/esm/src/interfaces/records-subscribe.js +5 -0
- package/dist/esm/src/interfaces/records-subscribe.js.map +1 -1
- package/dist/esm/src/interfaces/records-write.js +6 -3
- package/dist/esm/src/interfaces/records-write.js.map +1 -1
- package/dist/esm/src/protocols/permissions.js +28 -7
- package/dist/esm/src/protocols/permissions.js.map +1 -1
- package/dist/esm/src/sync/records-projection.js +228 -0
- package/dist/esm/src/sync/records-projection.js.map +1 -0
- package/dist/esm/src/types/message-types.js.map +1 -1
- package/dist/esm/src/types/permission-types.js.map +1 -1
- package/dist/esm/src/utils/permission-scope.js +37 -0
- package/dist/esm/src/utils/permission-scope.js.map +1 -0
- package/dist/esm/tests/core/grant-authorization.spec.js +26 -3
- package/dist/esm/tests/core/grant-authorization.spec.js.map +1 -1
- package/dist/esm/tests/core/records-grant-authorization.spec.js +117 -0
- package/dist/esm/tests/core/records-grant-authorization.spec.js.map +1 -0
- package/dist/esm/tests/features/permissions.spec.js +126 -0
- package/dist/esm/tests/features/permissions.spec.js.map +1 -1
- package/dist/esm/tests/handlers/messages-read.spec.js +345 -12
- package/dist/esm/tests/handlers/messages-read.spec.js.map +1 -1
- package/dist/esm/tests/handlers/messages-subscribe.spec.js +326 -9
- package/dist/esm/tests/handlers/messages-subscribe.spec.js.map +1 -1
- package/dist/esm/tests/handlers/messages-sync.spec.js +1053 -7
- package/dist/esm/tests/handlers/messages-sync.spec.js.map +1 -1
- package/dist/esm/tests/handlers/protocols-configure.spec.js +361 -0
- package/dist/esm/tests/handlers/protocols-configure.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-count.spec.js +75 -2
- package/dist/esm/tests/handlers/records-count.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-query.spec.js +73 -0
- package/dist/esm/tests/handlers/records-query.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-subscribe.spec.js +75 -1
- package/dist/esm/tests/handlers/records-subscribe.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/messages-get.spec.js +107 -5
- package/dist/esm/tests/interfaces/messages-get.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/protocols-configure.spec.js +13 -0
- package/dist/esm/tests/interfaces/protocols-configure.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/records-delete.spec.js +12 -0
- package/dist/esm/tests/interfaces/records-delete.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/records-query.spec.js +10 -0
- package/dist/esm/tests/interfaces/records-query.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/records-subscribe.spec.js +10 -0
- package/dist/esm/tests/interfaces/records-subscribe.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/records-write.spec.js +33 -0
- package/dist/esm/tests/interfaces/records-write.spec.js.map +1 -1
- package/dist/esm/tests/sync/records-projection.spec.js +245 -0
- package/dist/esm/tests/sync/records-projection.spec.js.map +1 -0
- package/dist/esm/tests/test-suite.js +2 -0
- package/dist/esm/tests/test-suite.js.map +1 -1
- package/dist/esm/tests/utils/permission-scope.spec.js +66 -0
- package/dist/esm/tests/utils/permission-scope.spec.js.map +1 -0
- package/dist/esm/tests/utils/test-data-generator.js +5 -2
- package/dist/esm/tests/utils/test-data-generator.js.map +1 -1
- package/dist/types/generated/precompiled-validators.d.ts.map +1 -1
- package/dist/types/src/core/constants.d.ts +13 -0
- package/dist/types/src/core/constants.d.ts.map +1 -1
- package/dist/types/src/core/dwn-error.d.ts +24 -1
- package/dist/types/src/core/dwn-error.d.ts.map +1 -1
- package/dist/types/src/core/grant-authorization.d.ts +1 -2
- package/dist/types/src/core/grant-authorization.d.ts.map +1 -1
- package/dist/types/src/core/message.d.ts +41 -1
- package/dist/types/src/core/message.d.ts.map +1 -1
- package/dist/types/src/core/messages-grant-authorization.d.ts +36 -4
- package/dist/types/src/core/messages-grant-authorization.d.ts.map +1 -1
- package/dist/types/src/core/protocol-authorization.d.ts +12 -0
- package/dist/types/src/core/protocol-authorization.d.ts.map +1 -1
- package/dist/types/src/core/records-grant-authorization.d.ts +6 -0
- package/dist/types/src/core/records-grant-authorization.d.ts.map +1 -1
- package/dist/types/src/handlers/messages-read.d.ts.map +1 -1
- package/dist/types/src/handlers/messages-subscribe.d.ts +2 -1
- package/dist/types/src/handlers/messages-subscribe.d.ts.map +1 -1
- package/dist/types/src/handlers/messages-sync.d.ts +31 -0
- package/dist/types/src/handlers/messages-sync.d.ts.map +1 -1
- package/dist/types/src/handlers/protocols-configure.d.ts +3 -0
- package/dist/types/src/handlers/protocols-configure.d.ts.map +1 -1
- package/dist/types/src/handlers/records-count.d.ts +4 -0
- package/dist/types/src/handlers/records-count.d.ts.map +1 -1
- package/dist/types/src/handlers/records-delete.d.ts.map +1 -1
- package/dist/types/src/handlers/records-query.d.ts +4 -0
- package/dist/types/src/handlers/records-query.d.ts.map +1 -1
- package/dist/types/src/handlers/records-read.d.ts.map +1 -1
- package/dist/types/src/handlers/records-subscribe.d.ts.map +1 -1
- package/dist/types/src/handlers/records-write.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +6 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/interfaces/messages-read.d.ts +1 -1
- package/dist/types/src/interfaces/messages-read.d.ts.map +1 -1
- package/dist/types/src/interfaces/messages-subscribe.d.ts +1 -1
- package/dist/types/src/interfaces/messages-subscribe.d.ts.map +1 -1
- package/dist/types/src/interfaces/messages-sync.d.ts +4 -1
- package/dist/types/src/interfaces/messages-sync.d.ts.map +1 -1
- package/dist/types/src/interfaces/protocols-configure.d.ts.map +1 -1
- package/dist/types/src/interfaces/protocols-query.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-count.d.ts +1 -0
- package/dist/types/src/interfaces/records-count.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-delete.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-query.d.ts +1 -0
- package/dist/types/src/interfaces/records-query.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-read.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-subscribe.d.ts +1 -0
- package/dist/types/src/interfaces/records-subscribe.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-write.d.ts.map +1 -1
- package/dist/types/src/protocols/permissions.d.ts +2 -0
- package/dist/types/src/protocols/permissions.d.ts.map +1 -1
- package/dist/types/src/sync/records-projection.d.ts +98 -0
- package/dist/types/src/sync/records-projection.d.ts.map +1 -0
- package/dist/types/src/types/message-types.d.ts +1 -0
- package/dist/types/src/types/message-types.d.ts.map +1 -1
- package/dist/types/src/types/messages-types.d.ts +21 -3
- package/dist/types/src/types/messages-types.d.ts.map +1 -1
- package/dist/types/src/types/permission-types.d.ts +4 -0
- package/dist/types/src/types/permission-types.d.ts.map +1 -1
- package/dist/types/src/types/records-types.d.ts +4 -0
- package/dist/types/src/types/records-types.d.ts.map +1 -1
- package/dist/types/src/types/subscriptions.d.ts +18 -3
- package/dist/types/src/types/subscriptions.d.ts.map +1 -1
- package/dist/types/src/utils/permission-scope.d.ts +29 -0
- package/dist/types/src/utils/permission-scope.d.ts.map +1 -0
- package/dist/types/tests/core/records-grant-authorization.spec.d.ts +2 -0
- package/dist/types/tests/core/records-grant-authorization.spec.d.ts.map +1 -0
- package/dist/types/tests/features/permissions.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/messages-read.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/messages-subscribe.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/messages-sync.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/protocols-configure.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/records-count.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/records-query.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/records-subscribe.spec.d.ts.map +1 -1
- package/dist/types/tests/sync/records-projection.spec.d.ts +2 -0
- package/dist/types/tests/sync/records-projection.spec.d.ts.map +1 -0
- package/dist/types/tests/test-suite.d.ts.map +1 -1
- package/dist/types/tests/utils/permission-scope.spec.d.ts +2 -0
- package/dist/types/tests/utils/permission-scope.spec.d.ts.map +1 -0
- package/dist/types/tests/utils/test-data-generator.d.ts +5 -2
- package/dist/types/tests/utils/test-data-generator.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/core/constants.ts +24 -0
- package/src/core/dwn-error.ts +24 -1
- package/src/core/grant-authorization.ts +7 -5
- package/src/core/message.ts +153 -6
- package/src/core/messages-grant-authorization.ts +282 -70
- package/src/core/protocol-authorization.ts +130 -0
- package/src/core/records-grant-authorization.ts +64 -21
- package/src/handlers/messages-read.ts +7 -5
- package/src/handlers/messages-subscribe.ts +149 -9
- package/src/handlers/messages-sync.ts +593 -102
- package/src/handlers/protocols-configure.ts +103 -2
- package/src/handlers/records-count.ts +33 -0
- package/src/handlers/records-delete.ts +3 -2
- package/src/handlers/records-query.ts +33 -0
- package/src/handlers/records-read.ts +3 -2
- package/src/handlers/records-subscribe.ts +34 -0
- package/src/handlers/records-write.ts +3 -2
- package/src/index.ts +7 -3
- package/src/interfaces/messages-read.ts +8 -5
- package/src/interfaces/messages-subscribe.ts +12 -9
- package/src/interfaces/messages-sync.ts +33 -12
- package/src/interfaces/protocols-configure.ts +8 -4
- package/src/interfaces/protocols-query.ts +13 -9
- package/src/interfaces/records-count.ts +7 -0
- package/src/interfaces/records-delete.ts +9 -5
- package/src/interfaces/records-query.ts +7 -0
- package/src/interfaces/records-read.ts +6 -3
- package/src/interfaces/records-subscribe.ts +7 -0
- package/src/interfaces/records-write.ts +25 -17
- package/src/protocols/permissions.ts +47 -9
- package/src/sync/records-projection.ts +328 -0
- package/src/types/message-types.ts +1 -0
- package/src/types/messages-types.ts +23 -3
- package/src/types/permission-types.ts +5 -1
- package/src/types/records-types.ts +5 -1
- package/src/types/subscriptions.ts +19 -3
- package/src/utils/permission-scope.ts +55 -0
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
import type { GenericMessage } from '../types/message-types.js';
|
|
2
2
|
import type { MessageStore } from '../types/message-store.js';
|
|
3
|
+
import type { StateIndex } from '../types/state-index.js';
|
|
3
4
|
import type { HandlerDependencies, MethodHandler } from '../types/method-handler.js';
|
|
4
|
-
import type { MessagesSyncDiffEntry, MessagesSyncMessage, MessagesSyncReply } from '../types/messages-types.js';
|
|
5
|
+
import type { MessagesSyncDependencyEntry, MessagesSyncDiffEntry, MessagesSyncMessage, MessagesSyncReply } from '../types/messages-types.js';
|
|
6
|
+
import type { RecordsProjectionScope, RecordsProjectionSnapshot } from '../sync/records-projection.js';
|
|
5
7
|
|
|
6
8
|
import { authenticate } from '../core/auth.js';
|
|
7
9
|
import { DwnConstant } from '../core/dwn-constant.js';
|
|
8
10
|
import { Encoder } from '../utils/encoder.js';
|
|
9
11
|
import { hashToHex } from '../smt/smt-utils.js';
|
|
12
|
+
import { Message } from '../core/message.js';
|
|
10
13
|
import { messageReplyFromError } from '../core/message-reply.js';
|
|
11
14
|
import { MessagesGrantAuthorization } from '../core/messages-grant-authorization.js';
|
|
12
15
|
import { MessagesSync } from '../interfaces/messages-sync.js';
|
|
13
|
-
import {
|
|
16
|
+
import { Records } from '../utils/records.js';
|
|
17
|
+
import { RecordsProjection } from '../sync/records-projection.js';
|
|
18
|
+
import { RecordsWrite } from '../interfaces/records-write.js';
|
|
19
|
+
import { SortDirection } from '../types/query-types.js';
|
|
14
20
|
import { DwnError, DwnErrorCode } from '../core/dwn-error.js';
|
|
21
|
+
import { DwnInterfaceName, DwnMethodName } from '../enums/dwn-interface-method.js';
|
|
15
22
|
|
|
16
23
|
/**
|
|
17
24
|
* Maximum inline data size for diff responses — aligned with the
|
|
@@ -22,6 +29,21 @@ import { DwnError, DwnErrorCode } from '../core/dwn-error.js';
|
|
|
22
29
|
*/
|
|
23
30
|
const DEFAULT_MAX_INLINE_DATA_SIZE = DwnConstant.maxDataSizeAllowedToBeEncoded;
|
|
24
31
|
|
|
32
|
+
type StoredMessageWithEncodedData = GenericMessage & { encodedData?: string };
|
|
33
|
+
type StoredMessageWithInternalFields = GenericMessage & {
|
|
34
|
+
encodedData?: unknown;
|
|
35
|
+
initialWrite?: unknown;
|
|
36
|
+
};
|
|
37
|
+
type RecordsWriteProtocolDescriptor = GenericMessage['descriptor'] & { protocol?: unknown };
|
|
38
|
+
type RecordsWriteProtocolMetadata = { protocol: string; messageTimestamp: string };
|
|
39
|
+
type ProtocolsConfigureDefinitionDescriptor = GenericMessage['descriptor'] & {
|
|
40
|
+
definition?: {
|
|
41
|
+
protocol?: unknown;
|
|
42
|
+
uses?: Record<string, unknown>;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
type ProjectionScopes = readonly [RecordsProjectionScope, ...RecordsProjectionScope[]];
|
|
46
|
+
|
|
25
47
|
|
|
26
48
|
export class MessagesSyncHandler implements MethodHandler {
|
|
27
49
|
|
|
@@ -46,44 +68,25 @@ export class MessagesSyncHandler implements MethodHandler {
|
|
|
46
68
|
return messageReplyFromError(e, 401);
|
|
47
69
|
}
|
|
48
70
|
|
|
49
|
-
const { action
|
|
71
|
+
const { action } = message.descriptor;
|
|
72
|
+
const projectionScopes = MessagesSyncHandler.getProjectionScopes(message);
|
|
50
73
|
|
|
51
74
|
try {
|
|
52
75
|
switch (action) {
|
|
53
76
|
case 'root': {
|
|
54
|
-
|
|
55
|
-
? await this.deps.stateIndex!.getRoot(tenant)
|
|
56
|
-
: await this.deps.stateIndex!.getProtocolRoot(tenant, protocol);
|
|
57
|
-
return {
|
|
58
|
-
status : { code: 200, detail: 'OK' },
|
|
59
|
-
root : hashToHex(rootHash),
|
|
60
|
-
};
|
|
77
|
+
return await this.handleRoot(tenant, message, projectionScopes);
|
|
61
78
|
}
|
|
62
79
|
|
|
63
80
|
case 'subtree': {
|
|
64
|
-
|
|
65
|
-
const hash = protocol === undefined
|
|
66
|
-
? await this.deps.stateIndex!.getSubtreeHash(tenant, bitPath)
|
|
67
|
-
: await this.deps.stateIndex!.getProtocolSubtreeHash(tenant, protocol, bitPath);
|
|
68
|
-
return {
|
|
69
|
-
status : { code: 200, detail: 'OK' },
|
|
70
|
-
hash : hashToHex(hash),
|
|
71
|
-
};
|
|
81
|
+
return await this.handleSubtree(tenant, message, projectionScopes);
|
|
72
82
|
}
|
|
73
83
|
|
|
74
84
|
case 'leaves': {
|
|
75
|
-
|
|
76
|
-
const leaves = protocol === undefined
|
|
77
|
-
? await this.deps.stateIndex!.getLeaves(tenant, bitPath)
|
|
78
|
-
: await this.deps.stateIndex!.getProtocolLeaves(tenant, protocol, bitPath);
|
|
79
|
-
return {
|
|
80
|
-
status : { code: 200, detail: 'OK' },
|
|
81
|
-
entries : leaves,
|
|
82
|
-
};
|
|
85
|
+
return await this.handleLeaves(tenant, message, projectionScopes);
|
|
83
86
|
}
|
|
84
87
|
|
|
85
88
|
case 'diff': {
|
|
86
|
-
return this.handleDiff(tenant, message);
|
|
89
|
+
return await this.handleDiff(tenant, message);
|
|
87
90
|
}
|
|
88
91
|
|
|
89
92
|
default: {
|
|
@@ -97,6 +100,112 @@ export class MessagesSyncHandler implements MethodHandler {
|
|
|
97
100
|
}
|
|
98
101
|
}
|
|
99
102
|
|
|
103
|
+
private async handleRoot(
|
|
104
|
+
tenant: string,
|
|
105
|
+
message: MessagesSyncMessage,
|
|
106
|
+
projectionScopes: ProjectionScopes | undefined
|
|
107
|
+
): Promise<MessagesSyncReply> {
|
|
108
|
+
const root = await this.getRootHex(tenant, message.descriptor.protocol, projectionScopes);
|
|
109
|
+
return {
|
|
110
|
+
status : { code: 200, detail: 'OK' },
|
|
111
|
+
root : root,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private async getRootHex(
|
|
116
|
+
tenant: string,
|
|
117
|
+
protocol: string | undefined,
|
|
118
|
+
projectionScopes: ProjectionScopes | undefined
|
|
119
|
+
): Promise<string> {
|
|
120
|
+
if (projectionScopes === undefined) {
|
|
121
|
+
const rootHash = await this.getIndexedRootHash(tenant, protocol);
|
|
122
|
+
return hashToHex(rootHash);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return this.withProjectionSnapshot(tenant, projectionScopes, snapshot => snapshot.getRootHex());
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private async getIndexedRootHash(
|
|
129
|
+
tenant: string,
|
|
130
|
+
protocol: string | undefined
|
|
131
|
+
): Promise<Uint8Array> {
|
|
132
|
+
if (protocol === undefined) {
|
|
133
|
+
return this.deps.stateIndex!.getRoot(tenant);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return this.deps.stateIndex!.getProtocolRoot(tenant, protocol);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private async handleSubtree(
|
|
140
|
+
tenant: string,
|
|
141
|
+
message: MessagesSyncMessage,
|
|
142
|
+
projectionScopes: ProjectionScopes | undefined
|
|
143
|
+
): Promise<MessagesSyncReply> {
|
|
144
|
+
const bitPath = MessagesSyncHandler.parseBitPrefix(message.descriptor.prefix!);
|
|
145
|
+
const hash = await this.getSubtreeHash(tenant, message.descriptor.protocol, projectionScopes, bitPath);
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
status : { code: 200, detail: 'OK' },
|
|
149
|
+
hash : hashToHex(hash),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private async handleLeaves(
|
|
154
|
+
tenant: string,
|
|
155
|
+
message: MessagesSyncMessage,
|
|
156
|
+
projectionScopes: ProjectionScopes | undefined
|
|
157
|
+
): Promise<MessagesSyncReply> {
|
|
158
|
+
const bitPath = MessagesSyncHandler.parseBitPrefix(message.descriptor.prefix!);
|
|
159
|
+
const leaves = await this.getLeaves(tenant, message.descriptor.protocol, projectionScopes, bitPath);
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
status : { code: 200, detail: 'OK' },
|
|
163
|
+
entries : leaves,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private async getSubtreeHash(
|
|
168
|
+
tenant: string,
|
|
169
|
+
protocol: string | undefined,
|
|
170
|
+
projectionScopes: ProjectionScopes | undefined,
|
|
171
|
+
bitPath: boolean[]
|
|
172
|
+
): Promise<Uint8Array> {
|
|
173
|
+
if (projectionScopes === undefined) {
|
|
174
|
+
return this.getIndexedSubtreeHash(tenant, protocol, bitPath);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return this.withProjectionSnapshot(tenant, projectionScopes, snapshot => snapshot.getSubtreeHash(bitPath));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private async getLeaves(
|
|
181
|
+
tenant: string,
|
|
182
|
+
protocol: string | undefined,
|
|
183
|
+
projectionScopes: ProjectionScopes | undefined,
|
|
184
|
+
bitPath: boolean[]
|
|
185
|
+
): Promise<string[]> {
|
|
186
|
+
if (projectionScopes === undefined) {
|
|
187
|
+
return this.getIndexedLeaves(tenant, protocol, bitPath);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return this.withProjectionSnapshot(tenant, projectionScopes, snapshot => snapshot.getLeaves(bitPath));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private async getIndexedSubtreeHash(
|
|
194
|
+
tenant: string,
|
|
195
|
+
protocol: string | undefined,
|
|
196
|
+
bitPath: boolean[]
|
|
197
|
+
): Promise<Uint8Array> {
|
|
198
|
+
return MessagesSyncHandler.getIndexedSubtreeHashFromStateIndex(this.deps.stateIndex!, tenant, protocol, bitPath);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private async getIndexedLeaves(
|
|
202
|
+
tenant: string,
|
|
203
|
+
protocol: string | undefined,
|
|
204
|
+
bitPath: boolean[]
|
|
205
|
+
): Promise<string[]> {
|
|
206
|
+
return MessagesSyncHandler.getIndexedLeavesFromStateIndex(this.deps.stateIndex!, tenant, protocol, bitPath);
|
|
207
|
+
}
|
|
208
|
+
|
|
100
209
|
/**
|
|
101
210
|
* Handle the 'diff' action: the client sends its subtree hashes at a given
|
|
102
211
|
* depth, and the server compares them against its own tree to compute the
|
|
@@ -113,6 +222,7 @@ export class MessagesSyncHandler implements MethodHandler {
|
|
|
113
222
|
message: MessagesSyncMessage,
|
|
114
223
|
): Promise<MessagesSyncReply> {
|
|
115
224
|
const { protocol, hashes: clientHashes, depth } = message.descriptor;
|
|
225
|
+
const projectionScopes = MessagesSyncHandler.getProjectionScopes(message);
|
|
116
226
|
|
|
117
227
|
if (!clientHashes || depth === undefined) {
|
|
118
228
|
return {
|
|
@@ -121,77 +231,80 @@ export class MessagesSyncHandler implements MethodHandler {
|
|
|
121
231
|
}
|
|
122
232
|
|
|
123
233
|
const stateIndex = this.deps.stateIndex!;
|
|
234
|
+
const projectionSnapshot = await this.createProjectionSnapshot(tenant, projectionScopes);
|
|
124
235
|
const onlyRemoteCids: string[] = [];
|
|
125
236
|
const onlyLocalPrefixes: string[] = [];
|
|
126
237
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
238
|
+
try {
|
|
239
|
+
// Get the default (empty subtree) hash at the given depth so we can
|
|
240
|
+
// filter out client entries that represent empty subtrees.
|
|
241
|
+
const defaultHashHex = await this.getDefaultHashHex(depth);
|
|
242
|
+
|
|
243
|
+
// Build the set of all prefixes at the given depth that either side has.
|
|
244
|
+
// Filter out client prefixes whose hash equals the default (empty subtree)
|
|
245
|
+
// hash — these represent empty subtrees and should be treated the same as
|
|
246
|
+
// omitted prefixes.
|
|
247
|
+
const allPrefixes = new Set<string>();
|
|
248
|
+
for (const [pfx, hash] of Object.entries(clientHashes)) {
|
|
249
|
+
if (hash !== defaultHashHex) {
|
|
250
|
+
allPrefixes.add(pfx);
|
|
251
|
+
}
|
|
139
252
|
}
|
|
140
|
-
}
|
|
141
253
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
254
|
+
// Enumerate server-side non-empty prefixes by walking the server's
|
|
255
|
+
// tree to the given depth and collecting leaf prefixes.
|
|
256
|
+
const serverHashes = await this.collectSubtreeHashes(tenant, protocol, projectionSnapshot, depth);
|
|
257
|
+
for (const prefix of Object.keys(serverHashes)) {
|
|
258
|
+
allPrefixes.add(prefix);
|
|
259
|
+
}
|
|
148
260
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
261
|
+
// Compare each prefix's hash between client and server.
|
|
262
|
+
for (const pfx of allPrefixes) {
|
|
263
|
+
const clientHash = clientHashes[pfx]; // undefined if client has empty subtree
|
|
264
|
+
const serverHash = serverHashes[pfx]; // undefined if server has empty subtree
|
|
153
265
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
266
|
+
if (clientHash === serverHash) {
|
|
267
|
+
// Identical subtree — skip.
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
158
270
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
271
|
+
if (serverHash === undefined) {
|
|
272
|
+
// Client has entries the server doesn't.
|
|
273
|
+
onlyLocalPrefixes.push(pfx);
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
164
276
|
|
|
165
|
-
if (clientHash === undefined) {
|
|
166
|
-
// Server has entries the client doesn't — enumerate server leaves.
|
|
167
277
|
const bitPath = MessagesSyncHandler.parseBitPrefix(pfx);
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
278
|
+
const serverLeaves = await MessagesSyncHandler.getServerLeaves(
|
|
279
|
+
stateIndex,
|
|
280
|
+
tenant,
|
|
281
|
+
protocol,
|
|
282
|
+
projectionSnapshot,
|
|
283
|
+
bitPath
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
onlyRemoteCids.push(...serverLeaves);
|
|
287
|
+
if (clientHash !== undefined) {
|
|
288
|
+
// Both sides have entries but they differ. The client will enumerate
|
|
289
|
+
// its own leaves for this prefix and de-duplicate server leaves.
|
|
290
|
+
onlyLocalPrefixes.push(pfx);
|
|
291
|
+
}
|
|
173
292
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const bitPath = MessagesSyncHandler.parseBitPrefix(pfx);
|
|
177
|
-
const serverLeaves = protocol === undefined
|
|
178
|
-
? await stateIndex.getLeaves(tenant, bitPath)
|
|
179
|
-
: await stateIndex.getProtocolLeaves(tenant, protocol, bitPath);
|
|
180
|
-
|
|
181
|
-
// We don't have the client's leaves, so we report all server leaves
|
|
182
|
-
// as onlyRemote (the client will de-duplicate locally). We also
|
|
183
|
-
// report this prefix as onlyLocal so the client can check its own leaves.
|
|
184
|
-
onlyRemoteCids.push(...serverLeaves);
|
|
185
|
-
onlyLocalPrefixes.push(pfx);
|
|
293
|
+
} finally {
|
|
294
|
+
await projectionSnapshot?.close();
|
|
186
295
|
}
|
|
187
296
|
|
|
188
297
|
// Build response entries with inline message data where possible.
|
|
189
298
|
const onlyRemote = await this.buildDiffEntries(tenant, onlyRemoteCids);
|
|
299
|
+
const dependencies = projectionScopes === undefined
|
|
300
|
+
? []
|
|
301
|
+
: await this.buildProjectedDependencyEntries(tenant, onlyRemote);
|
|
190
302
|
|
|
191
303
|
return {
|
|
192
304
|
status : { code: 200, detail: 'OK' },
|
|
193
305
|
onlyRemote,
|
|
194
306
|
onlyLocal : onlyLocalPrefixes,
|
|
307
|
+
...(dependencies.length > 0 ? { dependencies } : {}),
|
|
195
308
|
};
|
|
196
309
|
}
|
|
197
310
|
|
|
@@ -202,18 +315,23 @@ export class MessagesSyncHandler implements MethodHandler {
|
|
|
202
315
|
private async collectSubtreeHashes(
|
|
203
316
|
tenant: string,
|
|
204
317
|
protocol: string | undefined,
|
|
318
|
+
projectionSnapshot: RecordsProjectionSnapshot | undefined,
|
|
205
319
|
depth: number,
|
|
206
320
|
): Promise<Record<string, string>> {
|
|
207
321
|
const stateIndex = this.deps.stateIndex!;
|
|
208
322
|
const result: Record<string, string> = {};
|
|
209
|
-
const defaultHashHex = await this.getDefaultHashHex(depth);
|
|
210
323
|
|
|
211
324
|
const walk = async (prefix: string, currentDepth: number): Promise<void> => {
|
|
212
325
|
const bitPath = MessagesSyncHandler.parseBitPrefix(prefix);
|
|
213
|
-
const hash =
|
|
214
|
-
|
|
215
|
-
|
|
326
|
+
const hash = await MessagesSyncHandler.getServerSubtreeHash(
|
|
327
|
+
stateIndex,
|
|
328
|
+
tenant,
|
|
329
|
+
protocol,
|
|
330
|
+
projectionSnapshot,
|
|
331
|
+
bitPath
|
|
332
|
+
);
|
|
216
333
|
const hexHash = hashToHex(hash);
|
|
334
|
+
const defaultHashHex = await this.getDefaultHashHex(currentDepth);
|
|
217
335
|
|
|
218
336
|
if (hexHash === defaultHashHex) {
|
|
219
337
|
// Empty subtree — don't include in the result.
|
|
@@ -237,6 +355,75 @@ export class MessagesSyncHandler implements MethodHandler {
|
|
|
237
355
|
return result;
|
|
238
356
|
}
|
|
239
357
|
|
|
358
|
+
private async createProjectionSnapshot(
|
|
359
|
+
tenant: string,
|
|
360
|
+
projectionScopes: ProjectionScopes | undefined
|
|
361
|
+
): Promise<RecordsProjectionSnapshot | undefined> {
|
|
362
|
+
if (projectionScopes === undefined) {
|
|
363
|
+
return undefined;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return RecordsProjection.createSnapshot({
|
|
367
|
+
tenant,
|
|
368
|
+
messageStore : this.deps.messageStore,
|
|
369
|
+
scopes : projectionScopes,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
private static async getServerLeaves(
|
|
374
|
+
stateIndex: StateIndex,
|
|
375
|
+
tenant: string,
|
|
376
|
+
protocol: string | undefined,
|
|
377
|
+
projectionSnapshot: RecordsProjectionSnapshot | undefined,
|
|
378
|
+
bitPath: boolean[]
|
|
379
|
+
): Promise<string[]> {
|
|
380
|
+
if (projectionSnapshot === undefined) {
|
|
381
|
+
return MessagesSyncHandler.getIndexedLeavesFromStateIndex(stateIndex, tenant, protocol, bitPath);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return projectionSnapshot.getLeaves(bitPath);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
private static async getServerSubtreeHash(
|
|
388
|
+
stateIndex: StateIndex,
|
|
389
|
+
tenant: string,
|
|
390
|
+
protocol: string | undefined,
|
|
391
|
+
projectionSnapshot: RecordsProjectionSnapshot | undefined,
|
|
392
|
+
bitPath: boolean[]
|
|
393
|
+
): Promise<Uint8Array> {
|
|
394
|
+
if (projectionSnapshot === undefined) {
|
|
395
|
+
return MessagesSyncHandler.getIndexedSubtreeHashFromStateIndex(stateIndex, tenant, protocol, bitPath);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return projectionSnapshot.getSubtreeHash(bitPath);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
private static async getIndexedLeavesFromStateIndex(
|
|
402
|
+
stateIndex: StateIndex,
|
|
403
|
+
tenant: string,
|
|
404
|
+
protocol: string | undefined,
|
|
405
|
+
bitPath: boolean[]
|
|
406
|
+
): Promise<string[]> {
|
|
407
|
+
if (protocol === undefined) {
|
|
408
|
+
return stateIndex.getLeaves(tenant, bitPath);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return stateIndex.getProtocolLeaves(tenant, protocol, bitPath);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
private static async getIndexedSubtreeHashFromStateIndex(
|
|
415
|
+
stateIndex: StateIndex,
|
|
416
|
+
tenant: string,
|
|
417
|
+
protocol: string | undefined,
|
|
418
|
+
bitPath: boolean[]
|
|
419
|
+
): Promise<Uint8Array> {
|
|
420
|
+
if (protocol === undefined) {
|
|
421
|
+
return stateIndex.getSubtreeHash(tenant, bitPath);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return stateIndex.getProtocolSubtreeHash(tenant, protocol, bitPath);
|
|
425
|
+
}
|
|
426
|
+
|
|
240
427
|
/**
|
|
241
428
|
* Get the hex-encoded default hash for a given depth. Lazily cached.
|
|
242
429
|
*/
|
|
@@ -291,6 +478,278 @@ export class MessagesSyncHandler implements MethodHandler {
|
|
|
291
478
|
return entries;
|
|
292
479
|
}
|
|
293
480
|
|
|
481
|
+
private async buildProjectedDependencyEntries(
|
|
482
|
+
tenant: string,
|
|
483
|
+
primaryEntries: MessagesSyncDiffEntry[],
|
|
484
|
+
): Promise<MessagesSyncDependencyEntry[]> {
|
|
485
|
+
const dependenciesByCid = new Map<string, MessagesSyncDependencyEntry>();
|
|
486
|
+
const configsByProtocol = new Map<string, GenericMessage[]>();
|
|
487
|
+
|
|
488
|
+
for (const primaryEntry of primaryEntries) {
|
|
489
|
+
const protocolMetadata = MessagesSyncHandler.recordsWriteProtocolMetadata(primaryEntry.message);
|
|
490
|
+
if (protocolMetadata !== undefined) {
|
|
491
|
+
await this.addProtocolConfigClosureDependencies(
|
|
492
|
+
tenant,
|
|
493
|
+
protocolMetadata.protocol,
|
|
494
|
+
protocolMetadata.messageTimestamp,
|
|
495
|
+
primaryEntry.messageCid,
|
|
496
|
+
configsByProtocol,
|
|
497
|
+
dependenciesByCid,
|
|
498
|
+
);
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const initialWrite = await this.readRecordsDeleteInitialWrite(tenant, primaryEntry.message);
|
|
503
|
+
if (initialWrite === undefined) {
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
await MessagesSyncHandler.addRecordsInitialWriteDependency(
|
|
508
|
+
primaryEntry.messageCid,
|
|
509
|
+
initialWrite,
|
|
510
|
+
dependenciesByCid,
|
|
511
|
+
);
|
|
512
|
+
|
|
513
|
+
const initialWriteMetadata = MessagesSyncHandler.recordsWriteProtocolMetadata(initialWrite);
|
|
514
|
+
if (initialWriteMetadata === undefined) {
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
await this.addProtocolConfigClosureDependencies(
|
|
519
|
+
tenant,
|
|
520
|
+
initialWriteMetadata.protocol,
|
|
521
|
+
initialWriteMetadata.messageTimestamp,
|
|
522
|
+
primaryEntry.messageCid,
|
|
523
|
+
configsByProtocol,
|
|
524
|
+
dependenciesByCid,
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
return [...dependenciesByCid.values()];
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
private async readRecordsDeleteInitialWrite(
|
|
532
|
+
tenant: string,
|
|
533
|
+
message: GenericMessage | undefined,
|
|
534
|
+
): Promise<GenericMessage | undefined> {
|
|
535
|
+
const recordId = MessagesSyncHandler.recordsDeleteRecordId(message);
|
|
536
|
+
if (recordId === undefined) {
|
|
537
|
+
return undefined;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const { messages } = await this.deps.messageStore.query(tenant, [{
|
|
541
|
+
interface : DwnInterfaceName.Records,
|
|
542
|
+
method : DwnMethodName.Write,
|
|
543
|
+
recordId,
|
|
544
|
+
}]);
|
|
545
|
+
const initialWrite = await RecordsWrite.getInitialWrite(messages);
|
|
546
|
+
return initialWrite === undefined ? undefined : MessagesSyncHandler.toWireMessage(initialWrite);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
private static async addRecordsInitialWriteDependency(
|
|
550
|
+
rootMessageCid: string,
|
|
551
|
+
dependency: GenericMessage,
|
|
552
|
+
dependenciesByCid: Map<string, MessagesSyncDependencyEntry>,
|
|
553
|
+
): Promise<void> {
|
|
554
|
+
const dependencyCid = await Message.getCid(dependency);
|
|
555
|
+
if (dependenciesByCid.has(dependencyCid)) {
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
dependenciesByCid.set(dependencyCid, {
|
|
560
|
+
dependencyClass : 'recordsInitialWrite',
|
|
561
|
+
messageCid : dependencyCid,
|
|
562
|
+
message : dependency,
|
|
563
|
+
rootMessageCid,
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
private async addProtocolConfigClosureDependencies(
|
|
568
|
+
tenant: string,
|
|
569
|
+
rootProtocol: string,
|
|
570
|
+
rootMessageTimestamp: string,
|
|
571
|
+
rootMessageCid: string,
|
|
572
|
+
configsByProtocol: Map<string, GenericMessage[]>,
|
|
573
|
+
dependenciesByCid: Map<string, MessagesSyncDependencyEntry>,
|
|
574
|
+
): Promise<void> {
|
|
575
|
+
// Dependency hints are not part of the projected root; they are advisory
|
|
576
|
+
// bootstrap data for the receiver. Bound each closure to the primary
|
|
577
|
+
// RecordsWrite timestamp because protocol authorization uses the definition
|
|
578
|
+
// active when that record was created, not whatever config is newest today.
|
|
579
|
+
const visitedProtocols = new Set<string>();
|
|
580
|
+
const pendingProtocols = [rootProtocol];
|
|
581
|
+
|
|
582
|
+
for (
|
|
583
|
+
let protocol = MessagesSyncHandler.takeNextUnvisitedProtocol(pendingProtocols, visitedProtocols);
|
|
584
|
+
protocol !== undefined;
|
|
585
|
+
protocol = MessagesSyncHandler.takeNextUnvisitedProtocol(pendingProtocols, visitedProtocols)
|
|
586
|
+
) {
|
|
587
|
+
const configs = await this.getCachedGoverningProtocolsConfigure(
|
|
588
|
+
tenant,
|
|
589
|
+
protocol,
|
|
590
|
+
rootMessageTimestamp,
|
|
591
|
+
configsByProtocol,
|
|
592
|
+
);
|
|
593
|
+
await MessagesSyncHandler.addProtocolConfigDependencies({
|
|
594
|
+
configs,
|
|
595
|
+
rootMessageCid,
|
|
596
|
+
visitedProtocols,
|
|
597
|
+
pendingProtocols,
|
|
598
|
+
dependenciesByCid,
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
private async getCachedGoverningProtocolsConfigure(
|
|
604
|
+
tenant: string,
|
|
605
|
+
protocol: string,
|
|
606
|
+
messageTimestamp: string,
|
|
607
|
+
configsByProtocol: Map<string, GenericMessage[]>,
|
|
608
|
+
): Promise<GenericMessage[]> {
|
|
609
|
+
const configCacheKey = JSON.stringify([protocol, messageTimestamp]);
|
|
610
|
+
const cachedConfigs = configsByProtocol.get(configCacheKey);
|
|
611
|
+
if (cachedConfigs !== undefined) {
|
|
612
|
+
return cachedConfigs;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
const configs = await this.readGoverningProtocolsConfigure(tenant, protocol, messageTimestamp);
|
|
616
|
+
configsByProtocol.set(configCacheKey, configs);
|
|
617
|
+
return configs;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
private static async addProtocolConfigDependencies({
|
|
621
|
+
configs,
|
|
622
|
+
rootMessageCid,
|
|
623
|
+
visitedProtocols,
|
|
624
|
+
pendingProtocols,
|
|
625
|
+
dependenciesByCid,
|
|
626
|
+
}: {
|
|
627
|
+
configs: GenericMessage[];
|
|
628
|
+
rootMessageCid: string;
|
|
629
|
+
visitedProtocols: Set<string>;
|
|
630
|
+
pendingProtocols: string[];
|
|
631
|
+
dependenciesByCid: Map<string, MessagesSyncDependencyEntry>;
|
|
632
|
+
}): Promise<void> {
|
|
633
|
+
for (const dependency of configs) {
|
|
634
|
+
await MessagesSyncHandler.addProtocolConfigDependency(rootMessageCid, dependency, dependenciesByCid);
|
|
635
|
+
MessagesSyncHandler.queueUnvisitedProtocols(
|
|
636
|
+
MessagesSyncHandler.protocolsConfigureUses(dependency),
|
|
637
|
+
visitedProtocols,
|
|
638
|
+
pendingProtocols,
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
private static async addProtocolConfigDependency(
|
|
644
|
+
rootMessageCid: string,
|
|
645
|
+
dependency: GenericMessage,
|
|
646
|
+
dependenciesByCid: Map<string, MessagesSyncDependencyEntry>,
|
|
647
|
+
): Promise<void> {
|
|
648
|
+
const dependencyCid = await Message.getCid(dependency);
|
|
649
|
+
if (dependenciesByCid.has(dependencyCid)) {
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
dependenciesByCid.set(dependencyCid, {
|
|
654
|
+
dependencyClass : 'protocolsConfigure',
|
|
655
|
+
messageCid : dependencyCid,
|
|
656
|
+
message : dependency,
|
|
657
|
+
rootMessageCid,
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
private static takeNextUnvisitedProtocol(
|
|
662
|
+
pendingProtocols: string[],
|
|
663
|
+
visitedProtocols: Set<string>,
|
|
664
|
+
): string | undefined {
|
|
665
|
+
while (pendingProtocols.length > 0) {
|
|
666
|
+
const protocol = pendingProtocols.shift()!;
|
|
667
|
+
if (visitedProtocols.has(protocol)) {
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
visitedProtocols.add(protocol);
|
|
671
|
+
return protocol;
|
|
672
|
+
}
|
|
673
|
+
return undefined;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
private static queueUnvisitedProtocols(
|
|
677
|
+
protocols: string[],
|
|
678
|
+
visitedProtocols: Set<string>,
|
|
679
|
+
pendingProtocols: string[],
|
|
680
|
+
): void {
|
|
681
|
+
for (const protocol of protocols) {
|
|
682
|
+
if (!visitedProtocols.has(protocol)) {
|
|
683
|
+
pendingProtocols.push(protocol);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
private static protocolsConfigureUses(message: GenericMessage): string[] {
|
|
689
|
+
if (
|
|
690
|
+
message.descriptor.interface !== DwnInterfaceName.Protocols ||
|
|
691
|
+
message.descriptor.method !== DwnMethodName.Configure
|
|
692
|
+
) {
|
|
693
|
+
return [];
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
const uses = (message.descriptor as ProtocolsConfigureDefinitionDescriptor).definition?.uses;
|
|
697
|
+
return uses === undefined
|
|
698
|
+
? []
|
|
699
|
+
: Object.values(uses).filter((protocol): protocol is string => typeof protocol === 'string');
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
private async readGoverningProtocolsConfigure(
|
|
703
|
+
tenant: string,
|
|
704
|
+
protocol: string,
|
|
705
|
+
messageTimestamp: string,
|
|
706
|
+
): Promise<GenericMessage[]> {
|
|
707
|
+
const { messages } = await this.deps.messageStore.query(
|
|
708
|
+
tenant,
|
|
709
|
+
[{
|
|
710
|
+
interface : DwnInterfaceName.Protocols,
|
|
711
|
+
method : DwnMethodName.Configure,
|
|
712
|
+
protocol,
|
|
713
|
+
messageTimestamp : { lte: messageTimestamp },
|
|
714
|
+
}],
|
|
715
|
+
{ messageTimestamp: SortDirection.Descending },
|
|
716
|
+
);
|
|
717
|
+
|
|
718
|
+
const governingMessage = await Message.getNewestMessage(messages);
|
|
719
|
+
return governingMessage === undefined ? [] : [governingMessage];
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
private static recordsWriteProtocolMetadata(message: GenericMessage | undefined): RecordsWriteProtocolMetadata | undefined {
|
|
723
|
+
if (
|
|
724
|
+
message?.descriptor.interface !== DwnInterfaceName.Records ||
|
|
725
|
+
message.descriptor.method !== DwnMethodName.Write
|
|
726
|
+
) {
|
|
727
|
+
return undefined;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
const protocol = (message.descriptor as RecordsWriteProtocolDescriptor).protocol;
|
|
731
|
+
return typeof protocol === 'string'
|
|
732
|
+
? { protocol, messageTimestamp: message.descriptor.messageTimestamp }
|
|
733
|
+
: undefined;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
private static recordsDeleteRecordId(message: GenericMessage | undefined): string | undefined {
|
|
737
|
+
if (
|
|
738
|
+
message?.descriptor.interface !== DwnInterfaceName.Records ||
|
|
739
|
+
message.descriptor.method !== DwnMethodName.Delete
|
|
740
|
+
) {
|
|
741
|
+
return undefined;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
const recordId = (message.descriptor as Record<string, unknown>).recordId;
|
|
745
|
+
return typeof recordId === 'string' ? recordId : undefined;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
private static toWireMessage(message: GenericMessage): GenericMessage {
|
|
749
|
+
const { encodedData: _encodedData, initialWrite: _initialWrite, ...wireMessage } = message as StoredMessageWithInternalFields;
|
|
750
|
+
return wireMessage;
|
|
751
|
+
}
|
|
752
|
+
|
|
294
753
|
/**
|
|
295
754
|
* Read a message and its data from the MessageStore + DataStore by CID.
|
|
296
755
|
*/
|
|
@@ -309,28 +768,24 @@ export class MessagesSyncHandler implements MethodHandler {
|
|
|
309
768
|
// It must not be included in the wire-format message (the recipient's
|
|
310
769
|
// DWN would reject it as an unexpected top-level property).
|
|
311
770
|
let inlineEncodedData: string | undefined;
|
|
312
|
-
if (
|
|
313
|
-
inlineEncodedData =
|
|
314
|
-
delete
|
|
771
|
+
if (MessagesSyncHandler.hasEncodedData(storedMessage)) {
|
|
772
|
+
inlineEncodedData = storedMessage.encodedData;
|
|
773
|
+
delete storedMessage.encodedData;
|
|
315
774
|
}
|
|
316
775
|
|
|
317
776
|
let data: ReadableStream<Uint8Array> | undefined;
|
|
318
777
|
|
|
319
|
-
// Check if this is a RecordsWrite with data.
|
|
320
|
-
if (
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
// Try DataStore first (for large payloads stored externally).
|
|
778
|
+
// Check if this is a RecordsWrite with data that is small enough to inline.
|
|
779
|
+
if (inlineEncodedData === undefined && Records.isRecordsWrite(storedMessage)) {
|
|
780
|
+
const { dataCid, dataSize } = storedMessage.descriptor;
|
|
781
|
+
if (dataSize <= DEFAULT_MAX_INLINE_DATA_SIZE) {
|
|
782
|
+
// Some stores may have small payload bytes in DataStore instead of encodedData.
|
|
325
783
|
if (this.deps.dataStore) {
|
|
326
|
-
// DataStore uses recordId, not messageCid. For RecordsWrite,
|
|
327
|
-
// recordId is
|
|
328
|
-
const
|
|
329
|
-
if (
|
|
330
|
-
|
|
331
|
-
if (dataResult?.dataStream) {
|
|
332
|
-
data = dataResult.dataStream;
|
|
333
|
-
}
|
|
784
|
+
// DataStore uses recordId, not messageCid. For RecordsWrite,
|
|
785
|
+
// recordId is a top-level message property.
|
|
786
|
+
const dataResult = await this.deps.dataStore.get(tenant, storedMessage.recordId, dataCid);
|
|
787
|
+
if (dataResult?.dataStream) {
|
|
788
|
+
data = dataResult.dataStream;
|
|
334
789
|
}
|
|
335
790
|
}
|
|
336
791
|
}
|
|
@@ -339,6 +794,39 @@ export class MessagesSyncHandler implements MethodHandler {
|
|
|
339
794
|
return { message: storedMessage, encodedData: inlineEncodedData, data };
|
|
340
795
|
}
|
|
341
796
|
|
|
797
|
+
private static hasEncodedData(message: GenericMessage): message is StoredMessageWithEncodedData {
|
|
798
|
+
return 'encodedData' in message && typeof message.encodedData === 'string';
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
private static getProjectionScopes(
|
|
802
|
+
message: MessagesSyncMessage,
|
|
803
|
+
): ProjectionScopes | undefined {
|
|
804
|
+
const { projectionScopes } = message.descriptor;
|
|
805
|
+
if (projectionScopes === undefined) {
|
|
806
|
+
return undefined;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
return projectionScopes as [RecordsProjectionScope, ...RecordsProjectionScope[]];
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
private async withProjectionSnapshot<T>(
|
|
813
|
+
tenant: string,
|
|
814
|
+
scopes: readonly [RecordsProjectionScope, ...RecordsProjectionScope[]],
|
|
815
|
+
fn: (snapshot: RecordsProjectionSnapshot) => Promise<T>,
|
|
816
|
+
): Promise<T> {
|
|
817
|
+
const snapshot = await RecordsProjection.createSnapshot({
|
|
818
|
+
tenant : tenant,
|
|
819
|
+
messageStore : this.deps.messageStore,
|
|
820
|
+
scopes : scopes,
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
try {
|
|
824
|
+
return await fn(snapshot);
|
|
825
|
+
} finally {
|
|
826
|
+
await snapshot.close();
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
342
830
|
/**
|
|
343
831
|
* Read a ReadableStream to completion and return the bytes.
|
|
344
832
|
*/
|
|
@@ -389,13 +877,16 @@ export class MessagesSyncHandler implements MethodHandler {
|
|
|
389
877
|
): Promise<void> {
|
|
390
878
|
if (messagesSync.author === tenant) {
|
|
391
879
|
return;
|
|
392
|
-
}
|
|
393
|
-
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
const permissionGrantIds = Message.getPermissionGrantIds(messagesSync.signaturePayload!);
|
|
883
|
+
if (messagesSync.author !== undefined && permissionGrantIds.length > 0) {
|
|
884
|
+
const permissionGrants = await MessagesGrantAuthorization.fetchPermissionGrants(tenant, messageStore, permissionGrantIds);
|
|
394
885
|
await MessagesGrantAuthorization.authorizeSubscribeOrSync({
|
|
395
886
|
incomingMessage : messagesSync.message,
|
|
396
887
|
expectedGrantor : tenant,
|
|
397
888
|
expectedGrantee : messagesSync.author,
|
|
398
|
-
|
|
889
|
+
permissionGrants,
|
|
399
890
|
messageStore
|
|
400
891
|
});
|
|
401
892
|
} else {
|