@enbox/dwn-sdk-js 0.4.17 → 0.4.18
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/generated/precompiled-validators.js +349 -198
- package/dist/esm/generated/precompiled-validators.js.map +1 -1
- package/dist/esm/src/handlers/records-collection.js +200 -0
- package/dist/esm/src/handlers/records-collection.js.map +1 -0
- package/dist/esm/src/handlers/records-count.js +19 -196
- package/dist/esm/src/handlers/records-count.js.map +1 -1
- package/dist/esm/src/handlers/records-query.js +19 -376
- package/dist/esm/src/handlers/records-query.js.map +1 -1
- package/dist/esm/src/handlers/records-subscribe.js +21 -341
- package/dist/esm/src/handlers/records-subscribe.js.map +1 -1
- package/dist/esm/src/interfaces/records-count.js +1 -1
- package/dist/esm/src/interfaces/records-count.js.map +1 -1
- package/dist/esm/src/interfaces/records-query.js +1 -1
- package/dist/esm/src/interfaces/records-query.js.map +1 -1
- package/dist/esm/src/interfaces/records-subscribe.js +3 -1
- package/dist/esm/src/interfaces/records-subscribe.js.map +1 -1
- package/dist/esm/src/store/index-level.js +84 -32
- package/dist/esm/src/store/index-level.js.map +1 -1
- package/dist/esm/src/store/message-store-level.js +45 -11
- package/dist/esm/src/store/message-store-level.js.map +1 -1
- package/dist/esm/src/utils/record-limit-occupancy.js +5 -2
- package/dist/esm/src/utils/record-limit-occupancy.js.map +1 -1
- package/dist/esm/src/utils/records.js +14 -47
- package/dist/esm/src/utils/records.js.map +1 -1
- package/dist/esm/tests/features/records-nested-query-scope.spec.js +15 -1
- package/dist/esm/tests/features/records-nested-query-scope.spec.js.map +1 -1
- package/dist/esm/tests/features/records-record-limit.spec.js +116 -0
- package/dist/esm/tests/features/records-record-limit.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-count.spec.js +52 -1
- package/dist/esm/tests/handlers/records-count.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-subscribe.spec.js +76 -1
- package/dist/esm/tests/handlers/records-subscribe.spec.js.map +1 -1
- package/dist/esm/tests/store/index-level.spec.js +95 -1
- package/dist/esm/tests/store/index-level.spec.js.map +1 -1
- package/dist/esm/tests/store/message-store-level.spec.js +70 -1
- package/dist/esm/tests/store/message-store-level.spec.js.map +1 -1
- package/dist/types/generated/precompiled-validators.d.ts.map +1 -1
- package/dist/types/src/handlers/records-collection.d.ts +32 -0
- package/dist/types/src/handlers/records-collection.d.ts.map +1 -0
- package/dist/types/src/handlers/records-count.d.ts +0 -35
- package/dist/types/src/handlers/records-count.d.ts.map +1 -1
- package/dist/types/src/handlers/records-query.d.ts +0 -61
- package/dist/types/src/handlers/records-query.d.ts.map +1 -1
- package/dist/types/src/handlers/records-subscribe.d.ts +1 -37
- package/dist/types/src/handlers/records-subscribe.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-subscribe.d.ts.map +1 -1
- package/dist/types/src/store/index-level.d.ts +27 -3
- package/dist/types/src/store/index-level.d.ts.map +1 -1
- package/dist/types/src/store/message-store-level.d.ts +1 -0
- package/dist/types/src/store/message-store-level.d.ts.map +1 -1
- package/dist/types/src/types/message-store.d.ts +6 -1
- package/dist/types/src/types/message-store.d.ts.map +1 -1
- package/dist/types/src/types/records-types.d.ts +1 -1
- package/dist/types/src/types/records-types.d.ts.map +1 -1
- package/dist/types/src/utils/record-limit-occupancy.d.ts.map +1 -1
- package/dist/types/src/utils/records.d.ts +6 -25
- package/dist/types/src/utils/records.d.ts.map +1 -1
- package/dist/types/tests/features/records-nested-query-scope.spec.d.ts.map +1 -1
- package/dist/types/tests/features/records-record-limit.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-subscribe.spec.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/handlers/records-collection.ts +269 -0
- package/src/handlers/records-count.ts +21 -236
- package/src/handlers/records-query.ts +22 -447
- package/src/handlers/records-subscribe.ts +26 -404
- package/src/interfaces/records-count.ts +1 -1
- package/src/interfaces/records-query.ts +1 -1
- package/src/interfaces/records-subscribe.ts +5 -2
- package/src/store/index-level.ts +145 -37
- package/src/store/message-store-level.ts +46 -11
- package/src/types/message-store.ts +6 -1
- package/src/types/records-types.ts +1 -1
- package/src/utils/record-limit-occupancy.ts +5 -2
- package/src/utils/records.ts +17 -52
|
@@ -1,27 +1,22 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RecordsCollectionVisibility } from './records-collection.js';
|
|
2
2
|
import type { EventSubscription, ProgressGapInfo, ProgressToken, SubscriptionEvent, SubscriptionListener, SubscriptionMessage } from '../types/subscriptions.js';
|
|
3
3
|
import type { Filter, PaginationCursor } from '../types/query-types.js';
|
|
4
4
|
import type { HandlerDependencies, MethodHandler } from '../types/method-handler.js';
|
|
5
5
|
import type { RecordsQueryReplyEntry, RecordsSubscribeMessage, RecordsSubscribeReply } from '../types/records-types.js';
|
|
6
6
|
|
|
7
7
|
import { attachInitialWrites } from '../utils/initial-write-attachment.js';
|
|
8
|
-
import { authenticate } from '../core/auth.js';
|
|
9
|
-
import { DateSort } from '../types/records-types.js';
|
|
10
8
|
import { EncryptionControl } from '../core/encryption-control.js';
|
|
9
|
+
import { isRecordLimitOccupant } from '../utils/record-limit-occupancy.js';
|
|
11
10
|
import { Message } from '../core/message.js';
|
|
12
11
|
import { messageReplyFromError } from '../core/message-reply.js';
|
|
13
|
-
import { ProtocolAuthorization } from '../core/protocol-authorization.js';
|
|
14
12
|
import { Records } from '../utils/records.js';
|
|
15
|
-
import { RecordsGrantAuthorization } from '../core/records-grant-authorization.js';
|
|
16
13
|
import { RecordsSubscribe } from '../interfaces/records-subscribe.js';
|
|
17
|
-
import { SortDirection } from '../types/query-types.js';
|
|
18
|
-
import { DwnError, DwnErrorCode } from '../core/dwn-error.js';
|
|
19
|
-
import { DwnInterfaceName, DwnMethodName } from '../enums/dwn-interface-method.js';
|
|
20
14
|
import {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
} from '
|
|
15
|
+
buildRecordsEventFilters,
|
|
16
|
+
queryVisibleRecordsPage,
|
|
17
|
+
resolveRecordsCollectionVisibility,
|
|
18
|
+
} from './records-collection.js';
|
|
19
|
+
import { DwnError, DwnErrorCode } from '../core/dwn-error.js';
|
|
25
20
|
|
|
26
21
|
type ProjectedRecordsSubscriptionHandler = {
|
|
27
22
|
listener: SubscriptionListener;
|
|
@@ -51,16 +46,15 @@ export class RecordsSubscribeHandler implements MethodHandler {
|
|
|
51
46
|
let recordsSubscribe: RecordsSubscribe;
|
|
52
47
|
try {
|
|
53
48
|
recordsSubscribe = await RecordsSubscribe.parse(message);
|
|
54
|
-
} catch (
|
|
55
|
-
return messageReplyFromError(
|
|
49
|
+
} catch (error) {
|
|
50
|
+
return messageReplyFromError(error, 400);
|
|
56
51
|
}
|
|
57
52
|
|
|
58
|
-
const requester = Message.getRequester(recordsSubscribe.message);
|
|
59
53
|
const filterResolution = await this.resolveSubscriptionFilters(tenant, message, recordsSubscribe);
|
|
60
54
|
if ('errorReply' in filterResolution) {
|
|
61
55
|
return filterResolution.errorReply;
|
|
62
56
|
}
|
|
63
|
-
const { eventFilters,
|
|
57
|
+
const { eventFilters, visibility } = filterResolution;
|
|
64
58
|
|
|
65
59
|
const messageCid = await Message.getCid(message);
|
|
66
60
|
const { cursor: eventLogCursor } = recordsSubscribe.message.descriptor;
|
|
@@ -82,12 +76,12 @@ export class RecordsSubscribeHandler implements MethodHandler {
|
|
|
82
76
|
|
|
83
77
|
// ---- No cursor: existing behavior (initial snapshot from MessageStore) ----
|
|
84
78
|
return this.handleSnapshotSubscription(
|
|
85
|
-
tenant, messageCid, recordsSubscribe,
|
|
79
|
+
tenant, messageCid, recordsSubscribe, eventFilters, visibility, projectedSubscriptionHandler
|
|
86
80
|
);
|
|
87
81
|
}
|
|
88
82
|
|
|
89
83
|
/**
|
|
90
|
-
* Resolves the event
|
|
84
|
+
* Resolves the event filters and visibility for the subscription, performing authentication and
|
|
91
85
|
* authorization when the request is not an anonymous published-records-only subscribe.
|
|
92
86
|
* Returns the resolved filters, or an `errorReply` if authentication/authorization failed.
|
|
93
87
|
*/
|
|
@@ -95,35 +89,21 @@ export class RecordsSubscribeHandler implements MethodHandler {
|
|
|
95
89
|
tenant: string,
|
|
96
90
|
message: RecordsSubscribeMessage,
|
|
97
91
|
recordsSubscribe: RecordsSubscribe,
|
|
98
|
-
): Promise<{ eventFilters: Filter[];
|
|
99
|
-
|
|
100
|
-
if (Records.filterIncludesPublishedRecords(recordsSubscribe.message.descriptor.filter) && recordsSubscribe.author === undefined) {
|
|
101
|
-
const eventFilters = [RecordsSubscribeHandler.buildPublishedEventFilter(recordsSubscribe)];
|
|
102
|
-
const queryFilters = [RecordsSubscribeHandler.buildPublishedQueryFilter(recordsSubscribe)];
|
|
103
|
-
// delete the undefined authorization property else the code will encounter the following IPLD issue when attempting to generate CID:
|
|
104
|
-
// Error: `undefined` is not supported by the IPLD Data Model and cannot be encoded
|
|
105
|
-
delete message.authorization;
|
|
106
|
-
return { eventFilters, queryFilters };
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// authentication and authorization
|
|
92
|
+
): Promise<{ eventFilters: Filter[]; visibility: RecordsCollectionVisibility } | { errorReply: RecordsSubscribeReply }> {
|
|
93
|
+
let visibility: RecordsCollectionVisibility;
|
|
110
94
|
try {
|
|
111
|
-
await
|
|
112
|
-
await RecordsSubscribeHandler.authorizeRecordsSubscribe(tenant, recordsSubscribe, this.deps);
|
|
95
|
+
visibility = await resolveRecordsCollectionVisibility(tenant, recordsSubscribe, this.deps);
|
|
113
96
|
} catch (error) {
|
|
114
97
|
return { errorReply: messageReplyFromError(error, 401) };
|
|
115
98
|
}
|
|
116
99
|
|
|
117
|
-
if (
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
queryFilters : RecordsSubscribeHandler.buildOwnerQueryFilters(recordsSubscribe),
|
|
121
|
-
};
|
|
100
|
+
if (visibility === 'published') {
|
|
101
|
+
// Remove the undefined property before computing the subscription CID because IPLD cannot encode it.
|
|
102
|
+
delete message.authorization;
|
|
122
103
|
}
|
|
123
|
-
|
|
124
104
|
return {
|
|
125
|
-
eventFilters
|
|
126
|
-
|
|
105
|
+
eventFilters: buildRecordsEventFilters(recordsSubscribe, visibility),
|
|
106
|
+
visibility,
|
|
127
107
|
};
|
|
128
108
|
}
|
|
129
109
|
|
|
@@ -171,9 +151,8 @@ export class RecordsSubscribeHandler implements MethodHandler {
|
|
|
171
151
|
tenant: string,
|
|
172
152
|
messageCid: string,
|
|
173
153
|
recordsSubscribe: RecordsSubscribe,
|
|
174
|
-
requester: string | undefined,
|
|
175
154
|
eventFilters: Filter[],
|
|
176
|
-
|
|
155
|
+
visibility: RecordsCollectionVisibility,
|
|
177
156
|
projectedSubscriptionHandler: ProjectedRecordsSubscriptionHandler,
|
|
178
157
|
): Promise<RecordsSubscribeReply> {
|
|
179
158
|
// Step 1: Register event listener FIRST to ensure no events are missed between query and subscribe
|
|
@@ -186,16 +165,12 @@ export class RecordsSubscribeHandler implements MethodHandler {
|
|
|
186
165
|
let entries: RecordsQueryReplyEntry[];
|
|
187
166
|
let paginationCursor: PaginationCursor | undefined;
|
|
188
167
|
try {
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
const queryResult = await this.queryRecordsWithVisibleControlFiltering(
|
|
168
|
+
const queryResult = await queryVisibleRecordsPage({
|
|
169
|
+
deps : this.deps,
|
|
192
170
|
tenant,
|
|
193
|
-
recordsSubscribe,
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
messageSort,
|
|
197
|
-
pagination,
|
|
198
|
-
);
|
|
171
|
+
request : recordsSubscribe,
|
|
172
|
+
visibility,
|
|
173
|
+
});
|
|
199
174
|
|
|
200
175
|
// attach the retained initial write to every entry that is not itself an initial write
|
|
201
176
|
entries = await attachInitialWrites({
|
|
@@ -220,29 +195,6 @@ export class RecordsSubscribeHandler implements MethodHandler {
|
|
|
220
195
|
};
|
|
221
196
|
}
|
|
222
197
|
|
|
223
|
-
/**
|
|
224
|
-
* Convert an incoming DateSort to a sort type accepted by MessageStore.
|
|
225
|
-
* Defaults to `dateCreated` ascending if no sort is supplied.
|
|
226
|
-
*/
|
|
227
|
-
private static convertDateSort(dateSort?: DateSort): MessageSort {
|
|
228
|
-
switch (dateSort) {
|
|
229
|
-
case DateSort.CreatedAscending:
|
|
230
|
-
return { dateCreated: SortDirection.Ascending };
|
|
231
|
-
case DateSort.CreatedDescending:
|
|
232
|
-
return { dateCreated: SortDirection.Descending };
|
|
233
|
-
case DateSort.PublishedAscending:
|
|
234
|
-
return { datePublished: SortDirection.Ascending };
|
|
235
|
-
case DateSort.PublishedDescending:
|
|
236
|
-
return { datePublished: SortDirection.Descending };
|
|
237
|
-
case DateSort.UpdatedAscending:
|
|
238
|
-
return { messageTimestamp: SortDirection.Ascending };
|
|
239
|
-
case DateSort.UpdatedDescending:
|
|
240
|
-
return { messageTimestamp: SortDirection.Descending };
|
|
241
|
-
default:
|
|
242
|
-
return { dateCreated: SortDirection.Ascending };
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
198
|
private static createRecordLimitOccupancyGuard(input: {
|
|
247
199
|
deps: HandlerDependencies;
|
|
248
200
|
eventFilters: Filter[];
|
|
@@ -361,334 +313,4 @@ export class RecordsSubscribeHandler implements MethodHandler {
|
|
|
361
313
|
};
|
|
362
314
|
}
|
|
363
315
|
|
|
364
|
-
// =============================================
|
|
365
|
-
// Event filters (for live subscription)
|
|
366
|
-
// These match Write+Delete and do NOT use isLatestBaseState
|
|
367
|
-
// =============================================
|
|
368
|
-
|
|
369
|
-
/**
|
|
370
|
-
* Build event filters for owner: all matching Write+Delete events.
|
|
371
|
-
*/
|
|
372
|
-
private static buildOwnerEventFilters(recordsSubscribe: RecordsSubscribe): Filter[] {
|
|
373
|
-
const { filter } = recordsSubscribe.message.descriptor;
|
|
374
|
-
return [{
|
|
375
|
-
...Records.convertFilter(filter),
|
|
376
|
-
interface : DwnInterfaceName.Records,
|
|
377
|
-
method : [DwnMethodName.Write, DwnMethodName.Delete],
|
|
378
|
-
}];
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
/**
|
|
382
|
-
* Build event filters for non-owner with visibility rules.
|
|
383
|
-
*/
|
|
384
|
-
private static buildNonOwnerEventFilters(recordsSubscribe: RecordsSubscribe): Filter[] {
|
|
385
|
-
const filters: Filter[] = [];
|
|
386
|
-
const { filter } = recordsSubscribe.message.descriptor;
|
|
387
|
-
if (Records.filterIncludesPublishedRecords(filter)) {
|
|
388
|
-
filters.push(RecordsSubscribeHandler.buildPublishedEventFilter(recordsSubscribe));
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
if (Records.filterIncludesUnpublishedRecords(filter)) {
|
|
392
|
-
if (EncryptionControl.isExactAudienceFilter(filter)) {
|
|
393
|
-
filters.push({
|
|
394
|
-
...Records.convertFilter(filter),
|
|
395
|
-
interface : DwnInterfaceName.Records,
|
|
396
|
-
method : [DwnMethodName.Write, DwnMethodName.Delete],
|
|
397
|
-
published : false,
|
|
398
|
-
});
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
if (Records.shouldBuildUnpublishedAuthorFilter(filter, recordsSubscribe.author!)) {
|
|
402
|
-
filters.push({
|
|
403
|
-
...Records.convertFilter(filter),
|
|
404
|
-
author : recordsSubscribe.author!,
|
|
405
|
-
interface : DwnInterfaceName.Records,
|
|
406
|
-
method : [DwnMethodName.Write, DwnMethodName.Delete],
|
|
407
|
-
published : false,
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
if (Records.shouldProtocolAuthorize(recordsSubscribe.signaturePayload!)) {
|
|
412
|
-
filters.push({
|
|
413
|
-
...Records.convertFilter(filter),
|
|
414
|
-
interface : DwnInterfaceName.Records,
|
|
415
|
-
method : [DwnMethodName.Write, DwnMethodName.Delete],
|
|
416
|
-
published : false,
|
|
417
|
-
});
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
if (Message.getPermissionGrantId(recordsSubscribe.signaturePayload!) !== undefined) {
|
|
421
|
-
filters.push({
|
|
422
|
-
...Records.convertFilter(filter),
|
|
423
|
-
interface : DwnInterfaceName.Records,
|
|
424
|
-
method : [DwnMethodName.Write, DwnMethodName.Delete],
|
|
425
|
-
published : false,
|
|
426
|
-
});
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
if (Records.shouldBuildUnpublishedRecipientFilter(filter, recordsSubscribe.author!)) {
|
|
430
|
-
filters.push({
|
|
431
|
-
...Records.convertFilter(filter),
|
|
432
|
-
interface : DwnInterfaceName.Records,
|
|
433
|
-
method : [DwnMethodName.Write, DwnMethodName.Delete],
|
|
434
|
-
recipient : recordsSubscribe.author!,
|
|
435
|
-
published : false,
|
|
436
|
-
});
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
return filters;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
/**
|
|
443
|
-
* Build a published-only event filter (Write+Delete).
|
|
444
|
-
*/
|
|
445
|
-
private static buildPublishedEventFilter(recordsSubscribe: RecordsSubscribe): Filter {
|
|
446
|
-
return {
|
|
447
|
-
...Records.convertFilter(recordsSubscribe.message.descriptor.filter),
|
|
448
|
-
interface : DwnInterfaceName.Records,
|
|
449
|
-
method : [DwnMethodName.Write, DwnMethodName.Delete],
|
|
450
|
-
published : true,
|
|
451
|
-
};
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
// =============================================
|
|
455
|
-
// Query filters (for initial snapshot)
|
|
456
|
-
// These match Write only and use isLatestBaseState: true
|
|
457
|
-
// =============================================
|
|
458
|
-
|
|
459
|
-
/**
|
|
460
|
-
* Build query filters for owner: latest writes matching the filter.
|
|
461
|
-
*/
|
|
462
|
-
private static buildOwnerQueryFilters(recordsSubscribe: RecordsSubscribe): Filter[] {
|
|
463
|
-
const { dateSort, filter } = recordsSubscribe.message.descriptor;
|
|
464
|
-
return [{
|
|
465
|
-
...Records.convertFilter(filter, dateSort),
|
|
466
|
-
interface : DwnInterfaceName.Records,
|
|
467
|
-
method : DwnMethodName.Write,
|
|
468
|
-
isLatestBaseState : true,
|
|
469
|
-
}];
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
/**
|
|
473
|
-
* Build query filters for non-owner with visibility rules.
|
|
474
|
-
*/
|
|
475
|
-
private static buildNonOwnerQueryFilters(recordsSubscribe: RecordsSubscribe): Filter[] {
|
|
476
|
-
const filters: Filter[] = [];
|
|
477
|
-
const { dateSort, filter } = recordsSubscribe.message.descriptor;
|
|
478
|
-
if (Records.filterIncludesPublishedRecords(filter)) {
|
|
479
|
-
filters.push(RecordsSubscribeHandler.buildPublishedQueryFilter(recordsSubscribe));
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
if (Records.filterIncludesUnpublishedRecords(filter)) {
|
|
483
|
-
if (EncryptionControl.isExactAudienceFilter(filter)) {
|
|
484
|
-
filters.push(Records.buildUnpublishedControlRecordsFilter(filter, dateSort));
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
if (Records.shouldBuildUnpublishedAuthorFilter(filter, recordsSubscribe.author!)) {
|
|
488
|
-
filters.push({
|
|
489
|
-
...Records.convertFilter(filter, dateSort),
|
|
490
|
-
author : recordsSubscribe.author!,
|
|
491
|
-
interface : DwnInterfaceName.Records,
|
|
492
|
-
method : DwnMethodName.Write,
|
|
493
|
-
isLatestBaseState : true,
|
|
494
|
-
published : false,
|
|
495
|
-
});
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
if (Records.shouldProtocolAuthorize(recordsSubscribe.signaturePayload!)) {
|
|
499
|
-
filters.push({
|
|
500
|
-
...Records.convertFilter(filter, dateSort),
|
|
501
|
-
interface : DwnInterfaceName.Records,
|
|
502
|
-
method : DwnMethodName.Write,
|
|
503
|
-
isLatestBaseState : true,
|
|
504
|
-
published : false,
|
|
505
|
-
});
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
if (Message.getPermissionGrantId(recordsSubscribe.signaturePayload!) !== undefined) {
|
|
509
|
-
filters.push({
|
|
510
|
-
...Records.convertFilter(filter, dateSort),
|
|
511
|
-
interface : DwnInterfaceName.Records,
|
|
512
|
-
method : DwnMethodName.Write,
|
|
513
|
-
isLatestBaseState : true,
|
|
514
|
-
published : false,
|
|
515
|
-
});
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
if (Records.shouldBuildUnpublishedRecipientFilter(filter, recordsSubscribe.author!)) {
|
|
519
|
-
filters.push({
|
|
520
|
-
...Records.convertFilter(filter, dateSort),
|
|
521
|
-
interface : DwnInterfaceName.Records,
|
|
522
|
-
method : DwnMethodName.Write,
|
|
523
|
-
recipient : recordsSubscribe.author!,
|
|
524
|
-
isLatestBaseState : true,
|
|
525
|
-
published : false,
|
|
526
|
-
});
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
return filters;
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
/**
|
|
533
|
-
* Build a published-only query filter (latest writes).
|
|
534
|
-
*/
|
|
535
|
-
private static buildPublishedQueryFilter(recordsSubscribe: RecordsSubscribe): Filter {
|
|
536
|
-
const { dateSort, filter } = recordsSubscribe.message.descriptor;
|
|
537
|
-
return {
|
|
538
|
-
...Records.convertFilter(filter, dateSort),
|
|
539
|
-
interface : DwnInterfaceName.Records,
|
|
540
|
-
method : DwnMethodName.Write,
|
|
541
|
-
published : true,
|
|
542
|
-
isLatestBaseState : true,
|
|
543
|
-
};
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
/**
|
|
547
|
-
* @param messageStore Used to check if the grant has been revoked.
|
|
548
|
-
*/
|
|
549
|
-
public static async authorizeRecordsSubscribe(
|
|
550
|
-
tenant: string,
|
|
551
|
-
recordsSubscribe: RecordsSubscribe,
|
|
552
|
-
deps: HandlerDependencies,
|
|
553
|
-
): Promise<void> {
|
|
554
|
-
|
|
555
|
-
if (Message.isSignedByAuthorDelegate(recordsSubscribe.message) &&
|
|
556
|
-
!EncryptionControl.filterTargetsOnlyControlRecords(recordsSubscribe.message.descriptor.filter)) {
|
|
557
|
-
await recordsSubscribe.authorizeDelegate(deps.validationStateReader);
|
|
558
|
-
} else if (EncryptionControl.filterTargetsOnlyControlRecords(recordsSubscribe.message.descriptor.filter)) {
|
|
559
|
-
await EncryptionControl.authorizeControlReadRequest({
|
|
560
|
-
tenant,
|
|
561
|
-
incomingMessage : recordsSubscribe.message,
|
|
562
|
-
requester : Message.getRequester(recordsSubscribe.message),
|
|
563
|
-
validationStateReader : deps.validationStateReader,
|
|
564
|
-
});
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
const permissionGrantId = Message.getPermissionGrantId(recordsSubscribe.signaturePayload!);
|
|
568
|
-
if (permissionGrantId !== undefined) {
|
|
569
|
-
const permissionGrant = await deps.validationStateReader.fetchGrant(tenant, permissionGrantId);
|
|
570
|
-
await RecordsGrantAuthorization.authorizeQueryOrSubscribe({
|
|
571
|
-
incomingMessage : recordsSubscribe.message,
|
|
572
|
-
expectedGrantor : tenant,
|
|
573
|
-
expectedGrantee : recordsSubscribe.author!,
|
|
574
|
-
permissionGrant,
|
|
575
|
-
validationStateReader : deps.validationStateReader,
|
|
576
|
-
});
|
|
577
|
-
return;
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
// NOTE: not all RecordsSubscribe messages require protocol authorization even if the filter includes protocol-related fields,
|
|
581
|
-
// this is because we dynamically filter out records that the caller is not authorized to see.
|
|
582
|
-
// Currently only run protocol authorization if message deliberately invokes a protocol role.
|
|
583
|
-
if (Records.shouldProtocolAuthorize(recordsSubscribe.signaturePayload!)) {
|
|
584
|
-
await ProtocolAuthorization.authorizeQueryOrSubscribe(tenant, recordsSubscribe, deps.validationStateReader);
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
private async filterControlRecordsForNonOwner(
|
|
589
|
-
tenant: string,
|
|
590
|
-
recordsSubscribe: RecordsSubscribe,
|
|
591
|
-
requester: string | undefined,
|
|
592
|
-
recordsWrites: RecordsQueryReplyEntry[],
|
|
593
|
-
): Promise<RecordsQueryReplyEntry[]> {
|
|
594
|
-
return EncryptionControl.filterVisibleControlRecords({
|
|
595
|
-
tenant,
|
|
596
|
-
incomingMessage : recordsSubscribe.message,
|
|
597
|
-
requester,
|
|
598
|
-
recordsWriteMessages : recordsWrites,
|
|
599
|
-
validationStateReader : this.deps.validationStateReader,
|
|
600
|
-
});
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
private async queryRecordsWithVisibleControlFiltering(
|
|
604
|
-
tenant: string,
|
|
605
|
-
recordsSubscribe: RecordsSubscribe,
|
|
606
|
-
requester: string | undefined,
|
|
607
|
-
filters: Filter[],
|
|
608
|
-
messageSort: MessageSort,
|
|
609
|
-
pagination: { cursor?: PaginationCursor; limit?: number } | undefined,
|
|
610
|
-
): Promise<{ messages: RecordsQueryReplyEntry[], cursor?: PaginationCursor }> {
|
|
611
|
-
const recordLimit = await resolveRecordLimitOccupancy({
|
|
612
|
-
validationStateReader : this.deps.validationStateReader,
|
|
613
|
-
tenant,
|
|
614
|
-
recordsFilter : recordsSubscribe.message.descriptor.filter,
|
|
615
|
-
messageTimestamp : recordsSubscribe.message.descriptor.messageTimestamp,
|
|
616
|
-
});
|
|
617
|
-
const controlFilters = Records.buildControlRecordsFilters(filters);
|
|
618
|
-
const currentAudienceRecordIdCache = new Map<string, string | undefined>();
|
|
619
|
-
if (controlFilters.length === 0) {
|
|
620
|
-
const result = await queryRecordsWithRecordLimitOccupancy({
|
|
621
|
-
messageStore: this.deps.messageStore,
|
|
622
|
-
tenant,
|
|
623
|
-
filters,
|
|
624
|
-
recordLimit,
|
|
625
|
-
messageSort,
|
|
626
|
-
pagination,
|
|
627
|
-
});
|
|
628
|
-
return EncryptionControl.projectCurrentAudienceRecordPage({
|
|
629
|
-
messageStore: this.deps.messageStore,
|
|
630
|
-
tenant,
|
|
631
|
-
filters,
|
|
632
|
-
currentAudienceRecordIdCache,
|
|
633
|
-
result,
|
|
634
|
-
});
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
if (pagination?.limit === undefined || pagination.limit <= 0) {
|
|
638
|
-
const result = await queryRecordsWithRecordLimitOccupancy({
|
|
639
|
-
messageStore: this.deps.messageStore,
|
|
640
|
-
tenant,
|
|
641
|
-
filters,
|
|
642
|
-
recordLimit,
|
|
643
|
-
messageSort,
|
|
644
|
-
pagination,
|
|
645
|
-
});
|
|
646
|
-
const projectedResult = await EncryptionControl.projectCurrentAudienceRecordPage({
|
|
647
|
-
messageStore: this.deps.messageStore,
|
|
648
|
-
tenant,
|
|
649
|
-
filters,
|
|
650
|
-
currentAudienceRecordIdCache,
|
|
651
|
-
result,
|
|
652
|
-
});
|
|
653
|
-
return {
|
|
654
|
-
messages : await this.filterControlRecordsForNonOwner(tenant, recordsSubscribe, requester, projectedResult.messages),
|
|
655
|
-
cursor : projectedResult.cursor,
|
|
656
|
-
};
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
const visibleMessages: RecordsQueryReplyEntry[] = [];
|
|
660
|
-
let cursor = pagination.cursor;
|
|
661
|
-
let nextCursor: PaginationCursor | undefined;
|
|
662
|
-
// Keeps visible-page pagination stable until #1100 moves control visibility into indexed store filters.
|
|
663
|
-
do {
|
|
664
|
-
const remainingLimit = pagination.limit - visibleMessages.length;
|
|
665
|
-
const result = await queryRecordsWithRecordLimitOccupancy({
|
|
666
|
-
messageStore : this.deps.messageStore,
|
|
667
|
-
tenant,
|
|
668
|
-
filters,
|
|
669
|
-
recordLimit,
|
|
670
|
-
messageSort,
|
|
671
|
-
pagination : { ...pagination, cursor, limit: remainingLimit },
|
|
672
|
-
});
|
|
673
|
-
const projectedResult = await EncryptionControl.projectCurrentAudienceRecordPage({
|
|
674
|
-
messageStore: this.deps.messageStore,
|
|
675
|
-
tenant,
|
|
676
|
-
filters,
|
|
677
|
-
currentAudienceRecordIdCache,
|
|
678
|
-
result,
|
|
679
|
-
});
|
|
680
|
-
const filteredMessages = await this.filterControlRecordsForNonOwner(
|
|
681
|
-
tenant,
|
|
682
|
-
recordsSubscribe,
|
|
683
|
-
requester,
|
|
684
|
-
projectedResult.messages,
|
|
685
|
-
);
|
|
686
|
-
visibleMessages.push(...filteredMessages);
|
|
687
|
-
nextCursor = projectedResult.cursor;
|
|
688
|
-
cursor = projectedResult.cursor;
|
|
689
|
-
} while (visibleMessages.length < pagination.limit && cursor !== undefined);
|
|
690
|
-
|
|
691
|
-
return { messages: visibleMessages, cursor: nextCursor };
|
|
692
|
-
}
|
|
693
|
-
|
|
694
316
|
}
|
|
@@ -38,7 +38,7 @@ export class RecordsCount extends AbstractMessage<RecordsCountMessage> {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
await Records.validateDelegatedGrantReferentialIntegrity(message, signaturePayload);
|
|
41
|
-
Records.
|
|
41
|
+
Records.validateNestedProtocolPathScope(
|
|
42
42
|
message.descriptor.filter,
|
|
43
43
|
DwnErrorCode.RecordsCountNestedProtocolPathContextIdInvalid,
|
|
44
44
|
'RecordsCount'
|
|
@@ -52,7 +52,7 @@ export class RecordsQuery extends AbstractMessage<RecordsQueryMessage> {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
await Records.validateDelegatedGrantReferentialIntegrity(message, signaturePayload);
|
|
55
|
-
Records.
|
|
55
|
+
Records.validateNestedProtocolPathScope(
|
|
56
56
|
message.descriptor.filter,
|
|
57
57
|
DwnErrorCode.RecordsQueryNestedProtocolPathContextIdInvalid,
|
|
58
58
|
'RecordsQuery'
|
|
@@ -48,10 +48,13 @@ export class RecordsSubscribe extends AbstractMessage<RecordsSubscribeMessage> {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
await Records.validateDelegatedGrantReferentialIntegrity(message, signaturePayload);
|
|
51
|
-
Records.
|
|
51
|
+
Records.validateNestedProtocolPathScope(
|
|
52
52
|
message.descriptor.filter,
|
|
53
53
|
DwnErrorCode.RecordsSubscribeNestedProtocolPathContextIdInvalid,
|
|
54
|
-
'RecordsSubscribe'
|
|
54
|
+
'RecordsSubscribe',
|
|
55
|
+
message.descriptor.cursor === undefined
|
|
56
|
+
&& message.descriptor.pagination?.limit !== undefined
|
|
57
|
+
&& !signaturePayload?.protocolRole?.includes('/'),
|
|
55
58
|
);
|
|
56
59
|
|
|
57
60
|
if (signaturePayload?.protocolRole !== undefined) {
|