@enbox/dwn-sdk-js 0.4.11 → 0.4.13

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.
Files changed (142) hide show
  1. package/dist/browser.mjs +4 -1
  2. package/dist/browser.mjs.map +4 -4
  3. package/dist/esm/src/core/dwn-error.js +8 -1
  4. package/dist/esm/src/core/dwn-error.js.map +1 -1
  5. package/dist/esm/src/core/encryption-control.js +38 -2
  6. package/dist/esm/src/core/encryption-control.js.map +1 -1
  7. package/dist/esm/src/core/message-reply.js +14 -1
  8. package/dist/esm/src/core/message-reply.js.map +1 -1
  9. package/dist/esm/src/core/protocol-authorization-action.js +130 -107
  10. package/dist/esm/src/core/protocol-authorization-action.js.map +1 -1
  11. package/dist/esm/src/core/protocol-authorization.js +10 -0
  12. package/dist/esm/src/core/protocol-authorization.js.map +1 -1
  13. package/dist/esm/src/event-stream/broadcast-channel-wake-publisher.js +63 -0
  14. package/dist/esm/src/event-stream/broadcast-channel-wake-publisher.js.map +1 -0
  15. package/dist/esm/src/handlers/protocols-configure.js +2 -0
  16. package/dist/esm/src/handlers/protocols-configure.js.map +1 -1
  17. package/dist/esm/src/handlers/records-subscribe.js +73 -46
  18. package/dist/esm/src/handlers/records-subscribe.js.map +1 -1
  19. package/dist/esm/src/handlers/records-write.js +132 -73
  20. package/dist/esm/src/handlers/records-write.js.map +1 -1
  21. package/dist/esm/src/index.js +1 -0
  22. package/dist/esm/src/index.js.map +1 -1
  23. package/dist/esm/src/interfaces/protocols-configure.js +3 -5
  24. package/dist/esm/src/interfaces/protocols-configure.js.map +1 -1
  25. package/dist/esm/src/interfaces/records-write-query.js +2 -3
  26. package/dist/esm/src/interfaces/records-write-query.js.map +1 -1
  27. package/dist/esm/src/interfaces/records-write.js +50 -35
  28. package/dist/esm/src/interfaces/records-write.js.map +1 -1
  29. package/dist/esm/src/jose/jws/general/verifier.js +1 -1
  30. package/dist/esm/src/store/blockstore-mock.js +3 -0
  31. package/dist/esm/src/store/blockstore-mock.js.map +1 -1
  32. package/dist/esm/src/store/index-level-compound.js +34 -18
  33. package/dist/esm/src/store/index-level-compound.js.map +1 -1
  34. package/dist/esm/src/store/index-level.js +5 -5
  35. package/dist/esm/src/store/index-level.js.map +1 -1
  36. package/dist/esm/src/store/message-store-level.js +2 -2
  37. package/dist/esm/src/store/message-store-level.js.map +1 -1
  38. package/dist/esm/src/store/storage-controller.js +26 -17
  39. package/dist/esm/src/store/storage-controller.js.map +1 -1
  40. package/dist/esm/src/types/message-types.js.map +1 -1
  41. package/dist/esm/src/utils/filter.js +4 -8
  42. package/dist/esm/src/utils/filter.js.map +1 -1
  43. package/dist/esm/src/utils/messages.js +46 -28
  44. package/dist/esm/src/utils/messages.js.map +1 -1
  45. package/dist/esm/src/utils/protocols.js +2 -2
  46. package/dist/esm/src/utils/protocols.js.map +1 -1
  47. package/dist/esm/src/utils/records.js +49 -33
  48. package/dist/esm/src/utils/records.js.map +1 -1
  49. package/dist/esm/src/utils/secp256r1.js +1 -1
  50. package/dist/esm/src/utils/secp256r1.js.map +1 -1
  51. package/dist/esm/src/utils/url.js +1 -1
  52. package/dist/esm/src/utils/url.js.map +1 -1
  53. package/dist/esm/tests/broadcast-channel-wake-publisher.spec.js +74 -0
  54. package/dist/esm/tests/broadcast-channel-wake-publisher.spec.js.map +1 -0
  55. package/dist/esm/tests/core/message-reply.spec.js +27 -0
  56. package/dist/esm/tests/core/message-reply.spec.js.map +1 -1
  57. package/dist/esm/tests/core/process-message-parity.spec.js +3 -0
  58. package/dist/esm/tests/core/process-message-parity.spec.js.map +1 -1
  59. package/dist/esm/tests/durable-event-log.spec.js +37 -0
  60. package/dist/esm/tests/durable-event-log.spec.js.map +1 -1
  61. package/dist/esm/tests/features/records-squash.spec.js +5 -0
  62. package/dist/esm/tests/features/records-squash.spec.js.map +1 -1
  63. package/dist/esm/tests/handlers/messages-query.spec.js +14 -28
  64. package/dist/esm/tests/handlers/messages-query.spec.js.map +1 -1
  65. package/dist/esm/tests/handlers/protocols-configure.spec.js +441 -1
  66. package/dist/esm/tests/handlers/protocols-configure.spec.js.map +1 -1
  67. package/dist/esm/tests/handlers/records-write.spec.js +1 -0
  68. package/dist/esm/tests/handlers/records-write.spec.js.map +1 -1
  69. package/dist/esm/tests/test-suite.js +8 -0
  70. package/dist/esm/tests/test-suite.js.map +1 -1
  71. package/dist/types/src/core/dwn-error.d.ts +14 -1
  72. package/dist/types/src/core/dwn-error.d.ts.map +1 -1
  73. package/dist/types/src/core/encryption-control.d.ts +16 -0
  74. package/dist/types/src/core/encryption-control.d.ts.map +1 -1
  75. package/dist/types/src/core/message-reply.d.ts +5 -0
  76. package/dist/types/src/core/message-reply.d.ts.map +1 -1
  77. package/dist/types/src/core/protocol-authorization-action.d.ts +6 -1
  78. package/dist/types/src/core/protocol-authorization-action.d.ts.map +1 -1
  79. package/dist/types/src/core/protocol-authorization.d.ts.map +1 -1
  80. package/dist/types/src/event-stream/broadcast-channel-wake-publisher.d.ts +35 -0
  81. package/dist/types/src/event-stream/broadcast-channel-wake-publisher.d.ts.map +1 -0
  82. package/dist/types/src/handlers/protocols-configure.d.ts.map +1 -1
  83. package/dist/types/src/handlers/records-subscribe.d.ts +18 -0
  84. package/dist/types/src/handlers/records-subscribe.d.ts.map +1 -1
  85. package/dist/types/src/handlers/records-write.d.ts +28 -0
  86. package/dist/types/src/handlers/records-write.d.ts.map +1 -1
  87. package/dist/types/src/index.d.ts +2 -0
  88. package/dist/types/src/index.d.ts.map +1 -1
  89. package/dist/types/src/interfaces/protocols-configure.d.ts.map +1 -1
  90. package/dist/types/src/interfaces/records-write-query.d.ts.map +1 -1
  91. package/dist/types/src/interfaces/records-write.d.ts +12 -0
  92. package/dist/types/src/interfaces/records-write.d.ts.map +1 -1
  93. package/dist/types/src/store/blockstore-mock.d.ts.map +1 -1
  94. package/dist/types/src/store/index-level-compound.d.ts +4 -1
  95. package/dist/types/src/store/index-level-compound.d.ts.map +1 -1
  96. package/dist/types/src/store/index-level.d.ts.map +1 -1
  97. package/dist/types/src/store/storage-controller.d.ts +6 -0
  98. package/dist/types/src/store/storage-controller.d.ts.map +1 -1
  99. package/dist/types/src/types/jose-types.d.ts +3 -2
  100. package/dist/types/src/types/jose-types.d.ts.map +1 -1
  101. package/dist/types/src/types/message-types.d.ts +13 -0
  102. package/dist/types/src/types/message-types.d.ts.map +1 -1
  103. package/dist/types/src/utils/filter.d.ts.map +1 -1
  104. package/dist/types/src/utils/messages.d.ts +14 -0
  105. package/dist/types/src/utils/messages.d.ts.map +1 -1
  106. package/dist/types/src/utils/records.d.ts +10 -0
  107. package/dist/types/src/utils/records.d.ts.map +1 -1
  108. package/dist/types/tests/broadcast-channel-wake-publisher.spec.d.ts +2 -0
  109. package/dist/types/tests/broadcast-channel-wake-publisher.spec.d.ts.map +1 -0
  110. package/dist/types/tests/features/records-squash.spec.d.ts.map +1 -1
  111. package/dist/types/tests/handlers/messages-query.spec.d.ts.map +1 -1
  112. package/dist/types/tests/handlers/protocols-configure.spec.d.ts.map +1 -1
  113. package/dist/types/tests/handlers/records-write.spec.d.ts.map +1 -1
  114. package/dist/types/tests/test-suite.d.ts.map +1 -1
  115. package/package.json +4 -4
  116. package/src/core/dwn-error.ts +15 -1
  117. package/src/core/encryption-control.ts +66 -2
  118. package/src/core/message-reply.ts +18 -2
  119. package/src/core/protocol-authorization-action.ts +167 -117
  120. package/src/core/protocol-authorization.ts +13 -0
  121. package/src/event-stream/broadcast-channel-wake-publisher.ts +71 -0
  122. package/src/handlers/protocols-configure.ts +2 -0
  123. package/src/handlers/records-subscribe.ts +98 -47
  124. package/src/handlers/records-write.ts +177 -91
  125. package/src/index.ts +2 -0
  126. package/src/interfaces/protocols-configure.ts +6 -8
  127. package/src/interfaces/records-write-query.ts +2 -3
  128. package/src/interfaces/records-write.ts +62 -44
  129. package/src/jose/jws/general/verifier.ts +1 -1
  130. package/src/store/blockstore-mock.ts +3 -0
  131. package/src/store/index-level-compound.ts +55 -24
  132. package/src/store/index-level.ts +6 -4
  133. package/src/store/message-store-level.ts +2 -2
  134. package/src/store/storage-controller.ts +27 -16
  135. package/src/types/jose-types.ts +3 -2
  136. package/src/types/message-types.ts +16 -0
  137. package/src/utils/filter.ts +4 -8
  138. package/src/utils/messages.ts +53 -30
  139. package/src/utils/protocols.ts +2 -2
  140. package/src/utils/records.ts +76 -51
  141. package/src/utils/secp256r1.ts +1 -1
  142. package/src/utils/url.ts +1 -1
@@ -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: string | number | boolean
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
- switch (typeof value) {
790
- case 'number':
792
+ if (typeof value === 'number') {
791
793
  return this.encodeNumberValue(value);
792
- default:
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 (let i = 0; i < results.length; i++) {
329
- const { messageCid } = results[i];
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 = new Map<string, GenericMessage[]>();
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.
@@ -1,6 +1,7 @@
1
- import type { Jwk, PrivateKeyJwk, PublicKeyJwk } from '@enbox/crypto';
1
+ import type { PrivateKeyJwk, PublicKeyJwk } from '@enbox/crypto';
2
2
 
3
- export type { Jwk, PrivateKeyJwk, PublicKeyJwk };
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 = {
@@ -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;
@@ -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
- // For example, the Permissions protocol injects a filter for grants/requests/revocations
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
- // for ProtocolsConfigure events. Without this, protocol metadata updates
77
- // would be excluded (ProtocolsConfigure indexes have no protocolPath).
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.
@@ -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('/').slice(-1)[0];
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 = JSON.parse(JSON.stringify(protocolDefinition)) as ProtocolDefinition;
124
+ const clone = structuredClone(protocolDefinition);
125
125
 
126
126
  const basePath = [KeyDerivationScheme.ProtocolPath, protocolDefinition.protocol];
127
127
  clone.$keyAgreement = {
@@ -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
- const delegatedGrant = message.authorization!.authorDelegatedGrant!;
475
+ if (!authorDelegatedGrantDefined) {
476
+ return;
477
+ }
463
478
 
464
- const permissionGrant = PermissionGrant.parse(delegatedGrant);
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
- const grantedTo = delegatedGrant.descriptor.recipient;
473
- const signer = Message.getSigner(message);
474
- if (grantedTo !== signer) {
475
- throw new DwnError(
476
- DwnErrorCode.RecordsAuthorDelegatedGrantGrantedToAndOwnerSignatureMismatch,
477
- `grantedTo ${grantedTo} in author delegated grant must be the same as the signer ${signer} of the message signature.`
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
- const delegateGrantCid = await Message.getCid(delegatedGrant);
482
- if (delegateGrantCid !== authorSignaturePayload!.delegatedGrantId) {
483
- throw new DwnError(
484
- DwnErrorCode.RecordsAuthorDelegatedGrantCidMismatch,
485
- `CID of the author delegated grant ${delegateGrantCid} must be the same as \
486
- the delegatedGrantId ${authorSignaturePayload!.delegatedGrantId} in the message signature.`
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
- // repeat the same checks for the owner signature below
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
- const delegatedGrant = message.authorization!.ownerDelegatedGrant!;
507
- const permissionGrant = PermissionGrant.parse(delegatedGrant);
528
+ if (!ownerDelegatedGrantDefined) {
529
+ return;
530
+ }
508
531
 
509
- if (permissionGrant.delegated !== true) {
510
- throw new DwnError(
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
- const grantedTo = delegatedGrant.descriptor.recipient;
517
- const signer = Jws.getSignerDid(message.authorization!.ownerSignature!.signatures[0]);
518
- if (grantedTo !== signer) {
519
- throw new DwnError(
520
- DwnErrorCode.RecordsOwnerDelegatedGrantGrantedToAndOwnerSignatureMismatch,
521
- `grantedTo ${grantedTo} in owner delegated grant must be the same as the signer ${signer} of the owner signature.`
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
- const delegateGrantCid = await Message.getCid(delegatedGrant);
526
- if (delegateGrantCid !== ownerSignaturePayload!.delegatedGrantId) {
527
- throw new DwnError(
528
- DwnErrorCode.RecordsOwnerDelegatedGrantCidMismatch,
529
- `CID of the owner delegated grant ${delegateGrantCid} must be the same as \
530
- the delegatedGrantId ${ownerSignaturePayload!.delegatedGrantId} in the owner signature.`
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
 
@@ -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 Promise.resolve(p256.sign(hashedContent, privateKeyBytes, { prehash: false }));
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})?[^\/].*/.test(url)) {
43
+ if (/^[^:]+:(\/{2})?[^/].*/.test(url)) {
44
44
  fullUrl = url;
45
45
  } else {
46
46
  fullUrl = `http://${url}`;