@enbox/dwn-sdk-js 0.4.12 → 0.4.14
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 +1 -1
- package/dist/browser.mjs.map +4 -4
- package/dist/esm/src/core/dwn-error.js +8 -1
- package/dist/esm/src/core/dwn-error.js.map +1 -1
- package/dist/esm/src/core/encryption-control.js +38 -2
- package/dist/esm/src/core/encryption-control.js.map +1 -1
- package/dist/esm/src/core/message-reply.js +14 -1
- package/dist/esm/src/core/message-reply.js.map +1 -1
- package/dist/esm/src/core/protocol-authorization-action.js +130 -107
- package/dist/esm/src/core/protocol-authorization-action.js.map +1 -1
- package/dist/esm/src/core/protocol-authorization.js +10 -0
- package/dist/esm/src/core/protocol-authorization.js.map +1 -1
- package/dist/esm/src/event-stream/broadcast-channel-wake-publisher.js +63 -0
- package/dist/esm/src/event-stream/broadcast-channel-wake-publisher.js.map +1 -0
- package/dist/esm/src/handlers/protocols-configure.js +2 -0
- package/dist/esm/src/handlers/protocols-configure.js.map +1 -1
- package/dist/esm/src/handlers/records-subscribe.js +73 -46
- package/dist/esm/src/handlers/records-subscribe.js.map +1 -1
- package/dist/esm/src/handlers/records-write.js +132 -73
- package/dist/esm/src/handlers/records-write.js.map +1 -1
- package/dist/esm/src/index.js +1 -0
- package/dist/esm/src/index.js.map +1 -1
- package/dist/esm/src/interfaces/protocols-configure.js +3 -5
- package/dist/esm/src/interfaces/protocols-configure.js.map +1 -1
- package/dist/esm/src/interfaces/records-write-query.js +2 -3
- package/dist/esm/src/interfaces/records-write-query.js.map +1 -1
- package/dist/esm/src/interfaces/records-write.js +50 -35
- package/dist/esm/src/interfaces/records-write.js.map +1 -1
- package/dist/esm/src/jose/jws/general/verifier.js +1 -1
- package/dist/esm/src/store/blockstore-mock.js +3 -0
- package/dist/esm/src/store/blockstore-mock.js.map +1 -1
- package/dist/esm/src/store/index-level-compound.js +34 -18
- package/dist/esm/src/store/index-level-compound.js.map +1 -1
- package/dist/esm/src/store/index-level.js +5 -5
- package/dist/esm/src/store/index-level.js.map +1 -1
- package/dist/esm/src/store/message-store-level.js +2 -2
- package/dist/esm/src/store/message-store-level.js.map +1 -1
- package/dist/esm/src/store/storage-controller.js +26 -17
- package/dist/esm/src/store/storage-controller.js.map +1 -1
- package/dist/esm/src/types/message-types.js.map +1 -1
- package/dist/esm/src/utils/filter.js +4 -8
- package/dist/esm/src/utils/filter.js.map +1 -1
- package/dist/esm/src/utils/messages.js +46 -28
- package/dist/esm/src/utils/messages.js.map +1 -1
- package/dist/esm/src/utils/protocols.js +2 -2
- package/dist/esm/src/utils/protocols.js.map +1 -1
- package/dist/esm/src/utils/records.js +49 -33
- package/dist/esm/src/utils/records.js.map +1 -1
- package/dist/esm/src/utils/secp256r1.js +1 -1
- package/dist/esm/src/utils/secp256r1.js.map +1 -1
- package/dist/esm/src/utils/url.js +1 -1
- package/dist/esm/src/utils/url.js.map +1 -1
- package/dist/esm/tests/broadcast-channel-wake-publisher.spec.js +74 -0
- package/dist/esm/tests/broadcast-channel-wake-publisher.spec.js.map +1 -0
- package/dist/esm/tests/core/message-reply.spec.js +27 -0
- package/dist/esm/tests/core/message-reply.spec.js.map +1 -1
- package/dist/esm/tests/core/process-message-parity.spec.js +3 -0
- package/dist/esm/tests/core/process-message-parity.spec.js.map +1 -1
- package/dist/esm/tests/durable-event-log.spec.js +37 -0
- package/dist/esm/tests/durable-event-log.spec.js.map +1 -1
- package/dist/esm/tests/features/records-squash.spec.js +5 -0
- package/dist/esm/tests/features/records-squash.spec.js.map +1 -1
- package/dist/esm/tests/handlers/messages-query.spec.js +14 -28
- package/dist/esm/tests/handlers/messages-query.spec.js.map +1 -1
- package/dist/esm/tests/handlers/protocols-configure.spec.js +441 -1
- package/dist/esm/tests/handlers/protocols-configure.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-write.spec.js +1 -0
- package/dist/esm/tests/handlers/records-write.spec.js.map +1 -1
- package/dist/esm/tests/test-suite.js +8 -0
- package/dist/esm/tests/test-suite.js.map +1 -1
- package/dist/types/src/core/dwn-error.d.ts +14 -1
- package/dist/types/src/core/dwn-error.d.ts.map +1 -1
- package/dist/types/src/core/encryption-control.d.ts +16 -0
- package/dist/types/src/core/encryption-control.d.ts.map +1 -1
- package/dist/types/src/core/message-reply.d.ts +5 -0
- package/dist/types/src/core/message-reply.d.ts.map +1 -1
- package/dist/types/src/core/protocol-authorization-action.d.ts +6 -1
- package/dist/types/src/core/protocol-authorization-action.d.ts.map +1 -1
- package/dist/types/src/core/protocol-authorization.d.ts.map +1 -1
- package/dist/types/src/event-stream/broadcast-channel-wake-publisher.d.ts +35 -0
- package/dist/types/src/event-stream/broadcast-channel-wake-publisher.d.ts.map +1 -0
- package/dist/types/src/handlers/protocols-configure.d.ts.map +1 -1
- package/dist/types/src/handlers/records-subscribe.d.ts +18 -0
- package/dist/types/src/handlers/records-subscribe.d.ts.map +1 -1
- package/dist/types/src/handlers/records-write.d.ts +28 -0
- package/dist/types/src/handlers/records-write.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +2 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/interfaces/protocols-configure.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-write-query.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-write.d.ts +12 -0
- package/dist/types/src/interfaces/records-write.d.ts.map +1 -1
- package/dist/types/src/store/blockstore-mock.d.ts.map +1 -1
- package/dist/types/src/store/index-level-compound.d.ts +4 -1
- package/dist/types/src/store/index-level-compound.d.ts.map +1 -1
- package/dist/types/src/store/index-level.d.ts.map +1 -1
- package/dist/types/src/store/storage-controller.d.ts +6 -0
- package/dist/types/src/store/storage-controller.d.ts.map +1 -1
- package/dist/types/src/types/jose-types.d.ts +3 -2
- package/dist/types/src/types/jose-types.d.ts.map +1 -1
- package/dist/types/src/types/message-types.d.ts +13 -0
- package/dist/types/src/types/message-types.d.ts.map +1 -1
- package/dist/types/src/types/subscriptions.d.ts +6 -1
- package/dist/types/src/types/subscriptions.d.ts.map +1 -1
- package/dist/types/src/utils/filter.d.ts.map +1 -1
- package/dist/types/src/utils/messages.d.ts +14 -0
- package/dist/types/src/utils/messages.d.ts.map +1 -1
- package/dist/types/src/utils/records.d.ts +10 -0
- package/dist/types/src/utils/records.d.ts.map +1 -1
- package/dist/types/tests/broadcast-channel-wake-publisher.spec.d.ts +2 -0
- package/dist/types/tests/broadcast-channel-wake-publisher.spec.d.ts.map +1 -0
- package/dist/types/tests/features/records-squash.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/messages-query.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-write.spec.d.ts.map +1 -1
- package/dist/types/tests/test-suite.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/core/dwn-error.ts +15 -1
- package/src/core/encryption-control.ts +66 -2
- package/src/core/message-reply.ts +18 -2
- package/src/core/protocol-authorization-action.ts +167 -117
- package/src/core/protocol-authorization.ts +13 -0
- package/src/event-stream/broadcast-channel-wake-publisher.ts +71 -0
- package/src/handlers/protocols-configure.ts +2 -0
- package/src/handlers/records-subscribe.ts +98 -47
- package/src/handlers/records-write.ts +177 -91
- package/src/index.ts +2 -0
- package/src/interfaces/protocols-configure.ts +6 -8
- package/src/interfaces/records-write-query.ts +2 -3
- package/src/interfaces/records-write.ts +62 -44
- package/src/jose/jws/general/verifier.ts +1 -1
- package/src/store/blockstore-mock.ts +3 -0
- package/src/store/index-level-compound.ts +55 -24
- package/src/store/index-level.ts +6 -4
- package/src/store/message-store-level.ts +2 -2
- package/src/store/storage-controller.ts +27 -16
- package/src/types/jose-types.ts +3 -2
- package/src/types/message-types.ts +16 -0
- package/src/types/subscriptions.ts +6 -1
- package/src/utils/filter.ts +4 -8
- package/src/utils/messages.ts +53 -30
- package/src/utils/protocols.ts +2 -2
- package/src/utils/records.ts +76 -51
- package/src/utils/secp256r1.ts +1 -1
- package/src/utils/url.ts +1 -1
package/src/store/index-level.ts
CHANGED
|
@@ -23,6 +23,9 @@ export type IndexLevelConfig = {
|
|
|
23
23
|
|
|
24
24
|
export type IndexedItem = { messageCid: string, indexes: KeyValues };
|
|
25
25
|
|
|
26
|
+
/** A value that can be encoded into a lexicographically sortable index key. */
|
|
27
|
+
type IndexableValue = string | number | boolean;
|
|
28
|
+
|
|
26
29
|
/**
|
|
27
30
|
* Defines a compound index that covers multiple filter properties and a sort property.
|
|
28
31
|
*
|
|
@@ -218,7 +221,7 @@ export class IndexLevel {
|
|
|
218
221
|
tenant: string,
|
|
219
222
|
item: IndexedItem,
|
|
220
223
|
indexName: string,
|
|
221
|
-
indexValue:
|
|
224
|
+
indexValue: IndexableValue
|
|
222
225
|
): Promise<LevelWrapperBatchOperation<string>> {
|
|
223
226
|
const { messageCid } = item;
|
|
224
227
|
|
|
@@ -786,10 +789,9 @@ export class IndexLevel {
|
|
|
786
789
|
* NOTE: we currently only use this for strings, numbers and booleans.
|
|
787
790
|
*/
|
|
788
791
|
static encodeValue(value: string | number | boolean): string {
|
|
789
|
-
|
|
790
|
-
case 'number':
|
|
792
|
+
if (typeof value === 'number') {
|
|
791
793
|
return this.encodeNumberValue(value);
|
|
792
|
-
|
|
794
|
+
} else {
|
|
793
795
|
return JSON.stringify(value);
|
|
794
796
|
}
|
|
795
797
|
}
|
|
@@ -325,8 +325,8 @@ export class MessageStoreLevel implements MessageStore, ReplicationFeedReader {
|
|
|
325
325
|
}
|
|
326
326
|
|
|
327
327
|
const messages: GenericMessage[] = [];
|
|
328
|
-
for (
|
|
329
|
-
const { messageCid } =
|
|
328
|
+
for (const result of results) {
|
|
329
|
+
const { messageCid } = result;
|
|
330
330
|
const message = await this.get(tenant, messageCid, options);
|
|
331
331
|
if (message) { messages.push(message); }
|
|
332
332
|
}
|
|
@@ -132,22 +132,7 @@ export class StorageController {
|
|
|
132
132
|
|
|
133
133
|
// Group messages by recordId — RecordsWrite messages use `message.recordId`,
|
|
134
134
|
// RecordsDelete messages use `message.descriptor.recordId`.
|
|
135
|
-
const recordIdToMessages =
|
|
136
|
-
for (const msg of siblingMessages) {
|
|
137
|
-
let recordId: string;
|
|
138
|
-
if (Records.isRecordsWrite(msg)) {
|
|
139
|
-
recordId = msg.recordId;
|
|
140
|
-
} else {
|
|
141
|
-
recordId = (msg as RecordsDeleteMessage).descriptor.recordId;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const existing = recordIdToMessages.get(recordId);
|
|
145
|
-
if (existing === undefined) {
|
|
146
|
-
recordIdToMessages.set(recordId, [msg]);
|
|
147
|
-
} else {
|
|
148
|
-
existing.push(msg);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
135
|
+
const recordIdToMessages = StorageController.groupSquashSiblingsByRecordId(siblingMessages);
|
|
151
136
|
|
|
152
137
|
// Delete all records whose newest message timestamp is strictly older than the squash timestamp.
|
|
153
138
|
// Skip the squash record itself.
|
|
@@ -170,6 +155,32 @@ export class StorageController {
|
|
|
170
155
|
}
|
|
171
156
|
}
|
|
172
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Groups the given messages by `recordId` — RecordsWrite messages use `message.recordId`,
|
|
160
|
+
* RecordsDelete messages use `message.descriptor.recordId`. Used by `performRecordsSquash()`
|
|
161
|
+
* to identify sibling records at the squash record's protocol path and parent context.
|
|
162
|
+
*/
|
|
163
|
+
private static groupSquashSiblingsByRecordId(messages: GenericMessage[]): Map<string, GenericMessage[]> {
|
|
164
|
+
const recordIdToMessages = new Map<string, GenericMessage[]>();
|
|
165
|
+
for (const msg of messages) {
|
|
166
|
+
let recordId: string;
|
|
167
|
+
if (Records.isRecordsWrite(msg)) {
|
|
168
|
+
recordId = msg.recordId;
|
|
169
|
+
} else {
|
|
170
|
+
recordId = (msg as RecordsDeleteMessage).descriptor.recordId;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const existing = recordIdToMessages.get(recordId);
|
|
174
|
+
if (existing === undefined) {
|
|
175
|
+
recordIdToMessages.set(recordId, [msg]);
|
|
176
|
+
} else {
|
|
177
|
+
existing.push(msg);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return recordIdToMessages;
|
|
182
|
+
}
|
|
183
|
+
|
|
173
184
|
/**
|
|
174
185
|
* Deletes the data referenced by the given message if needed.
|
|
175
186
|
* @param message The message to check if the data it references should be deleted.
|
package/src/types/jose-types.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { PrivateKeyJwk, PublicKeyJwk } from '@enbox/crypto';
|
|
2
2
|
|
|
3
|
-
export type { Jwk
|
|
3
|
+
export type { Jwk } from '@enbox/crypto';
|
|
4
|
+
export type { PrivateKeyJwk, PublicKeyJwk };
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Contains a public-private key pair and the associated key ID.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DwnErrorInfo } from '../core/dwn-error.js';
|
|
1
2
|
import type { GeneralJws } from './jws-types.js';
|
|
2
3
|
import type { ProgressToken } from './subscriptions.js';
|
|
3
4
|
import type { DwnInterfaceName, DwnMethodName } from '../enums/dwn-interface-method.js';
|
|
@@ -127,8 +128,23 @@ export type Pagination = {
|
|
|
127
128
|
};
|
|
128
129
|
|
|
129
130
|
export type Status = {
|
|
131
|
+
/** Numeric status code representing the outcome of the operation, aligned with HTTP status codes. */
|
|
130
132
|
code: number
|
|
133
|
+
|
|
134
|
+
/** Human-readable detail about the status or error. Prose only — load-bearing values belong in `info`. */
|
|
131
135
|
detail: string
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* The `DwnErrorCode` of the `DwnError` that produced this status.
|
|
139
|
+
* Only present when the failure originated from a `DwnError`; synthesized statuses (e.g. tenant gate rejections) omit it.
|
|
140
|
+
*/
|
|
141
|
+
errorCode?: string
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Structured, machine-readable error data (e.g. the squash floor timestamp for a squash backstop rejection).
|
|
145
|
+
* Only present when the originating `DwnError` carries an info bag; values mirror `DwnErrorInfo`.
|
|
146
|
+
*/
|
|
147
|
+
info?: DwnErrorInfo
|
|
132
148
|
};
|
|
133
149
|
|
|
134
150
|
export type GenericMessageReply = {
|
|
@@ -141,8 +141,13 @@ export type SubscriptionMessage = SubscriptionEvent | SubscriptionEose | Subscri
|
|
|
141
141
|
/**
|
|
142
142
|
* Callback for {@link EventLog.subscribe}. Receives events, catch-up EOSE
|
|
143
143
|
* markers, or subscription-level errors that stop delivery.
|
|
144
|
+
*
|
|
145
|
+
* A listener may return a promise to signal when the message has been fully
|
|
146
|
+
* processed; transports that support backpressure (e.g. the WebSocket client)
|
|
147
|
+
* gate their acknowledgements on it. Emitters that cannot exert backpressure
|
|
148
|
+
* simply ignore the returned value.
|
|
144
149
|
*/
|
|
145
|
-
export type SubscriptionListener = (message: SubscriptionMessage) => void
|
|
150
|
+
export type SubscriptionListener = (message: SubscriptionMessage) => void | Promise<void>;
|
|
146
151
|
|
|
147
152
|
/**
|
|
148
153
|
* Options for {@link EventLog.subscribe}.
|
package/src/utils/filter.ts
CHANGED
|
@@ -84,18 +84,14 @@ export class FilterUtility {
|
|
|
84
84
|
if (this.matchOneOf(filterValue, indexValue)) {
|
|
85
85
|
return true;
|
|
86
86
|
}
|
|
87
|
-
} else {
|
|
87
|
+
} else if (this.matchRange(filterValue, indexValue as RangeValue)) {
|
|
88
88
|
// `filterValue` is a `RangeFilter`
|
|
89
89
|
// range filters cannot range over booleans
|
|
90
|
-
if (this.matchRange(filterValue, indexValue as RangeValue)) {
|
|
91
|
-
return true;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
} else {
|
|
95
|
-
// filterValue is an EqualFilter, meaning it is a non-object primitive type
|
|
96
|
-
if (indexValue === filterValue) {
|
|
97
90
|
return true;
|
|
98
91
|
}
|
|
92
|
+
} else if (indexValue === filterValue) {
|
|
93
|
+
// filterValue is an EqualFilter, meaning it is a non-object primitive type
|
|
94
|
+
return true;
|
|
99
95
|
}
|
|
100
96
|
|
|
101
97
|
return false;
|
package/src/utils/messages.ts
CHANGED
|
@@ -59,39 +59,13 @@ export class Messages {
|
|
|
59
59
|
|
|
60
60
|
for (const filter of filters) {
|
|
61
61
|
// Ask each core protocol whether it needs an additional shadow filter for this query.
|
|
62
|
-
|
|
63
|
-
// tagged with the target protocol so they appear alongside that protocol's own records.
|
|
64
|
-
if (coreProtocols !== undefined) {
|
|
65
|
-
for (const coreProtocol of coreProtocols.all()) {
|
|
66
|
-
const additionalFilter = coreProtocol.constructAdditionalMessageFilter?.(filter);
|
|
67
|
-
if (additionalFilter !== undefined) {
|
|
68
|
-
messagesQueryFilters.push(additionalFilter);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
62
|
+
messagesQueryFilters.push(...this.constructCoreProtocolFilters(filter, coreProtocols));
|
|
72
63
|
|
|
73
64
|
messagesQueryFilters.push(this.convertFilter(filter));
|
|
74
65
|
|
|
75
|
-
// When protocolPathPrefix is used with a protocol, inject a shadow filter
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
// This mirrors the existing core-protocol additional-filter pattern above.
|
|
79
|
-
// The messageTimestamp constraint is carried over so time-bounded queries
|
|
80
|
-
// (including cursor-based subscriptions) also apply to the shadow filter.
|
|
81
|
-
if ((filter.protocolPathPrefix !== undefined || filter.contextIdPrefix !== undefined) && filter.protocol !== undefined) {
|
|
82
|
-
const metadataFilter: Filter = {
|
|
83
|
-
interface : 'Protocols',
|
|
84
|
-
method : 'Configure',
|
|
85
|
-
protocol : filter.protocol,
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
if (filter.messageTimestamp !== undefined) {
|
|
89
|
-
const timestampFilter = FilterUtility.convertRangeCriterion(filter.messageTimestamp);
|
|
90
|
-
if (timestampFilter) {
|
|
91
|
-
metadataFilter.messageTimestamp = timestampFilter;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
66
|
+
// When protocolPathPrefix is used with a protocol, inject a shadow filter for ProtocolsConfigure events.
|
|
67
|
+
const metadataFilter = this.constructProtocolConfigureShadowFilter(filter);
|
|
68
|
+
if (metadataFilter !== undefined) {
|
|
95
69
|
messagesQueryFilters.push(metadataFilter);
|
|
96
70
|
}
|
|
97
71
|
}
|
|
@@ -99,6 +73,55 @@ export class Messages {
|
|
|
99
73
|
return messagesQueryFilters;
|
|
100
74
|
}
|
|
101
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Asks each core protocol whether it needs an additional shadow filter for the given query filter.
|
|
78
|
+
* For example, the Permissions protocol injects a filter for grants/requests/revocations
|
|
79
|
+
* tagged with the target protocol so they appear alongside that protocol's own records.
|
|
80
|
+
*/
|
|
81
|
+
private static constructCoreProtocolFilters(filter: MessagesFilter, coreProtocols?: CoreProtocolRegistry): Filter[] {
|
|
82
|
+
if (coreProtocols === undefined) {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const additionalFilters: Filter[] = [];
|
|
87
|
+
for (const coreProtocol of coreProtocols.all()) {
|
|
88
|
+
const additionalFilter = coreProtocol.constructAdditionalMessageFilter?.(filter);
|
|
89
|
+
if (additionalFilter !== undefined) {
|
|
90
|
+
additionalFilters.push(additionalFilter);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return additionalFilters;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* When protocolPathPrefix or contextIdPrefix is used with a protocol, constructs a shadow filter
|
|
99
|
+
* for ProtocolsConfigure events. Without this, protocol metadata updates would be excluded
|
|
100
|
+
* (ProtocolsConfigure indexes have no protocolPath). This mirrors the core-protocol additional-filter
|
|
101
|
+
* pattern above. The messageTimestamp constraint is carried over so time-bounded queries
|
|
102
|
+
* (including cursor-based subscriptions) also apply to the shadow filter.
|
|
103
|
+
*/
|
|
104
|
+
private static constructProtocolConfigureShadowFilter(filter: MessagesFilter): Filter | undefined {
|
|
105
|
+
if ((filter.protocolPathPrefix === undefined && filter.contextIdPrefix === undefined) || filter.protocol === undefined) {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const metadataFilter: Filter = {
|
|
110
|
+
interface : 'Protocols',
|
|
111
|
+
method : 'Configure',
|
|
112
|
+
protocol : filter.protocol,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
if (filter.messageTimestamp !== undefined) {
|
|
116
|
+
const timestampFilter = FilterUtility.convertRangeCriterion(filter.messageTimestamp);
|
|
117
|
+
if (timestampFilter) {
|
|
118
|
+
metadataFilter.messageTimestamp = timestampFilter;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return metadataFilter;
|
|
123
|
+
}
|
|
124
|
+
|
|
102
125
|
/**
|
|
103
126
|
* Returns a copy of a RecordsWrite message without inline encodedData, and the
|
|
104
127
|
* detached encodedData value for wire surfaces that carry data beside the message.
|
package/src/utils/protocols.ts
CHANGED
|
@@ -46,7 +46,7 @@ export function isCrossProtocolRef(ref: string): boolean {
|
|
|
46
46
|
* e.g. `'thread/comment'` → `'comment'`, `'note'` → `'note'`.
|
|
47
47
|
*/
|
|
48
48
|
export function getTypeName(protocolPath: string): string {
|
|
49
|
-
return protocolPath.split('/').
|
|
49
|
+
return protocolPath.split('/').at(-1)!;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
/**
|
|
@@ -121,7 +121,7 @@ export class Protocols {
|
|
|
121
121
|
keyDeriver: EncryptionKeyDeriver,
|
|
122
122
|
): Promise<ProtocolDefinition> {
|
|
123
123
|
// clone before modify
|
|
124
|
-
const clone =
|
|
124
|
+
const clone = structuredClone(protocolDefinition);
|
|
125
125
|
|
|
126
126
|
const basePath = [KeyDerivationScheme.ProtocolPath, protocolDefinition.protocol];
|
|
127
127
|
clone.$keyAgreement = {
|
package/src/utils/records.ts
CHANGED
|
@@ -445,6 +445,20 @@ export class Records {
|
|
|
445
445
|
message: RecordsCountMessage | RecordsReadMessage | RecordsQueryMessage | RecordsWriteMessage | RecordsDeleteMessage | RecordsSubscribeMessage,
|
|
446
446
|
authorSignaturePayload: GenericSignaturePayload | undefined,
|
|
447
447
|
ownerSignaturePayload?: GenericSignaturePayload | undefined
|
|
448
|
+
): Promise<void> {
|
|
449
|
+
await Records.validateAuthorDelegatedGrantReferentialIntegrity(message, authorSignaturePayload);
|
|
450
|
+
|
|
451
|
+
// repeat the same checks for the owner signature below
|
|
452
|
+
await Records.validateOwnerDelegatedGrantReferentialIntegrity(message, ownerSignaturePayload);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Validates the referential integrity of the author-delegated grant, if any.
|
|
457
|
+
* Extracted from `validateDelegatedGrantReferentialIntegrity()` to keep its cognitive complexity low.
|
|
458
|
+
*/
|
|
459
|
+
private static async validateAuthorDelegatedGrantReferentialIntegrity(
|
|
460
|
+
message: RecordsCountMessage | RecordsReadMessage | RecordsQueryMessage | RecordsWriteMessage | RecordsDeleteMessage | RecordsSubscribeMessage,
|
|
461
|
+
authorSignaturePayload: GenericSignaturePayload | undefined,
|
|
448
462
|
): Promise<void> {
|
|
449
463
|
// `deletedGrantId` in the payload of the message signature and `authorDelegatedGrant` in `authorization` must both exist or be both undefined
|
|
450
464
|
const authorDelegatedGrantIdDefined = authorSignaturePayload?.delegatedGrantId !== undefined;
|
|
@@ -458,38 +472,47 @@ export class Records {
|
|
|
458
472
|
);
|
|
459
473
|
}
|
|
460
474
|
|
|
461
|
-
if (authorDelegatedGrantDefined) {
|
|
462
|
-
|
|
475
|
+
if (!authorDelegatedGrantDefined) {
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
463
478
|
|
|
464
|
-
|
|
465
|
-
if (permissionGrant.delegated !== true) {
|
|
466
|
-
throw new DwnError(
|
|
467
|
-
DwnErrorCode.RecordsAuthorDelegatedGrantNotADelegatedGrant,
|
|
468
|
-
`The owner delegated grant given is not a delegated grant.`
|
|
469
|
-
);
|
|
470
|
-
}
|
|
479
|
+
const delegatedGrant = message.authorization!.authorDelegatedGrant!;
|
|
471
480
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
}
|
|
481
|
+
const permissionGrant = PermissionGrant.parse(delegatedGrant);
|
|
482
|
+
if (permissionGrant.delegated !== true) {
|
|
483
|
+
throw new DwnError(
|
|
484
|
+
DwnErrorCode.RecordsAuthorDelegatedGrantNotADelegatedGrant,
|
|
485
|
+
`The owner delegated grant given is not a delegated grant.`
|
|
486
|
+
);
|
|
487
|
+
}
|
|
480
488
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
}
|
|
489
|
+
const grantedTo = delegatedGrant.descriptor.recipient;
|
|
490
|
+
const signer = Message.getSigner(message);
|
|
491
|
+
if (grantedTo !== signer) {
|
|
492
|
+
throw new DwnError(
|
|
493
|
+
DwnErrorCode.RecordsAuthorDelegatedGrantGrantedToAndOwnerSignatureMismatch,
|
|
494
|
+
`grantedTo ${grantedTo} in author delegated grant must be the same as the signer ${signer} of the message signature.`
|
|
495
|
+
);
|
|
489
496
|
}
|
|
490
497
|
|
|
491
|
-
|
|
498
|
+
const delegateGrantCid = await Message.getCid(delegatedGrant);
|
|
499
|
+
if (delegateGrantCid !== authorSignaturePayload!.delegatedGrantId) {
|
|
500
|
+
throw new DwnError(
|
|
501
|
+
DwnErrorCode.RecordsAuthorDelegatedGrantCidMismatch,
|
|
502
|
+
`CID of the author delegated grant ${delegateGrantCid} must be the same as \
|
|
503
|
+
the delegatedGrantId ${authorSignaturePayload!.delegatedGrantId} in the message signature.`
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
492
507
|
|
|
508
|
+
/**
|
|
509
|
+
* Validates the referential integrity of the owner-delegated grant, if any.
|
|
510
|
+
* Extracted from `validateDelegatedGrantReferentialIntegrity()` to keep its cognitive complexity low.
|
|
511
|
+
*/
|
|
512
|
+
private static async validateOwnerDelegatedGrantReferentialIntegrity(
|
|
513
|
+
message: RecordsCountMessage | RecordsReadMessage | RecordsQueryMessage | RecordsWriteMessage | RecordsDeleteMessage | RecordsSubscribeMessage,
|
|
514
|
+
ownerSignaturePayload: GenericSignaturePayload | undefined,
|
|
515
|
+
): Promise<void> {
|
|
493
516
|
// `deletedGrantId` in the payload of the owner signature and `ownerDelegatedGrant` in `authorization` must both exist or be both undefined
|
|
494
517
|
const ownerDelegatedGrantIdDefined = ownerSignaturePayload?.delegatedGrantId !== undefined;
|
|
495
518
|
const ownerDelegatedGrantDefined = message.authorization?.ownerDelegatedGrant !== undefined;
|
|
@@ -502,34 +525,36 @@ export class Records {
|
|
|
502
525
|
);
|
|
503
526
|
}
|
|
504
527
|
|
|
505
|
-
if (ownerDelegatedGrantDefined) {
|
|
506
|
-
|
|
507
|
-
|
|
528
|
+
if (!ownerDelegatedGrantDefined) {
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
508
531
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
DwnErrorCode.RecordsOwnerDelegatedGrantNotADelegatedGrant,
|
|
512
|
-
`The owner delegated grant given is not a delegated grant.`
|
|
513
|
-
);
|
|
514
|
-
}
|
|
532
|
+
const delegatedGrant = message.authorization!.ownerDelegatedGrant!;
|
|
533
|
+
const permissionGrant = PermissionGrant.parse(delegatedGrant);
|
|
515
534
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
);
|
|
523
|
-
}
|
|
535
|
+
if (permissionGrant.delegated !== true) {
|
|
536
|
+
throw new DwnError(
|
|
537
|
+
DwnErrorCode.RecordsOwnerDelegatedGrantNotADelegatedGrant,
|
|
538
|
+
`The owner delegated grant given is not a delegated grant.`
|
|
539
|
+
);
|
|
540
|
+
}
|
|
524
541
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
542
|
+
const grantedTo = delegatedGrant.descriptor.recipient;
|
|
543
|
+
const signer = Jws.getSignerDid(message.authorization!.ownerSignature!.signatures[0]);
|
|
544
|
+
if (grantedTo !== signer) {
|
|
545
|
+
throw new DwnError(
|
|
546
|
+
DwnErrorCode.RecordsOwnerDelegatedGrantGrantedToAndOwnerSignatureMismatch,
|
|
547
|
+
`grantedTo ${grantedTo} in owner delegated grant must be the same as the signer ${signer} of the owner signature.`
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
const delegateGrantCid = await Message.getCid(delegatedGrant);
|
|
552
|
+
if (delegateGrantCid !== ownerSignaturePayload!.delegatedGrantId) {
|
|
553
|
+
throw new DwnError(
|
|
554
|
+
DwnErrorCode.RecordsOwnerDelegatedGrantCidMismatch,
|
|
555
|
+
`CID of the owner delegated grant ${delegateGrantCid} must be the same as \
|
|
556
|
+
the delegatedGrantId ${ownerSignaturePayload!.delegatedGrantId} in the owner signature.`
|
|
557
|
+
);
|
|
533
558
|
}
|
|
534
559
|
}
|
|
535
560
|
|
package/src/utils/secp256r1.ts
CHANGED
|
@@ -85,7 +85,7 @@ export class Secp256r1 {
|
|
|
85
85
|
const hashedContent = await sha256.encode(content);
|
|
86
86
|
const privateKeyBytes = Secp256r1.privateJwkToBytes(privateJwk);
|
|
87
87
|
|
|
88
|
-
return
|
|
88
|
+
return p256.sign(hashedContent, privateKeyBytes, { prehash: false });
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
/**
|
package/src/utils/url.ts
CHANGED
|
@@ -40,7 +40,7 @@ export function normalizeSchemaUrl(url: string): string {
|
|
|
40
40
|
|
|
41
41
|
function normalizeUrl(url: string): string {
|
|
42
42
|
let fullUrl: string;
|
|
43
|
-
if (/^[^:]+:(\/{2})?[
|
|
43
|
+
if (/^[^:]+:(\/{2})?[^/].*/.test(url)) {
|
|
44
44
|
fullUrl = url;
|
|
45
45
|
} else {
|
|
46
46
|
fullUrl = `http://${url}`;
|