@fedify/interaction-controls 2.4.0-dev.0

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/mod.cjs ADDED
@@ -0,0 +1,899 @@
1
+
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ let _fedify_vocab = require("@fedify/vocab");
4
+ let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
5
+ //#region src/control.ts
6
+ function createInteractionControl(config) {
7
+ return {
8
+ name: config.name,
9
+ policyProperty: config.policyProperty,
10
+ requestTypeId: config.requestClass.typeId,
11
+ authorizationTypeId: config.authorizationClass.typeId,
12
+ verifyRequest: (context, options) => verifyRequest(context, options, config),
13
+ evaluatePolicy: (context, options) => evaluatePolicy(context, options, config),
14
+ createRequest: (options) => new config.requestClass({
15
+ id: options.id,
16
+ actor: options.actor,
17
+ object: options.object,
18
+ instrument: options.instrument,
19
+ ...audience(options)
20
+ }),
21
+ createAuthorization: (options) => new config.authorizationClass({
22
+ id: options.id,
23
+ attribution: options.attributedTo,
24
+ interactingObject: getRequiredId(options.interactingObject, "interactingObject"),
25
+ interactionTarget: getRequiredId(options.interactionTarget, "interactionTarget")
26
+ }),
27
+ verifyAuthorization: (context, options) => verifyAuthorization(context, options, config),
28
+ createAccept: (options) => createAccept(options),
29
+ createReject: (options) => createReject(options),
30
+ createRevocation: (options) => new _fedify_vocab.Delete({
31
+ id: options.id,
32
+ actor: options.actor,
33
+ object: getRequiredId(options.authorization, "authorization"),
34
+ ...audience(options)
35
+ }),
36
+ recognizeImpolite: config.recognizeImpolite,
37
+ getInteractionKey: (input) => ({
38
+ interaction: config.name,
39
+ requester: input.requester,
40
+ interactingObjectId: getRequiredId(input.interactingObject, "interactingObject"),
41
+ interactionTargetId: getRequiredId(input.interactionTarget, "interactionTarget")
42
+ }),
43
+ getAuthorizationKey: (input) => ({
44
+ interaction: config.name,
45
+ authorizationId: getRequiredId(input.authorization, "authorization")
46
+ })
47
+ };
48
+ }
49
+ function audience(options) {
50
+ const to = options.to;
51
+ const cc = options.cc;
52
+ return {
53
+ ...to == null ? {} : Array.isArray(to) ? { tos: to } : { to },
54
+ ...cc == null ? {} : Array.isArray(cc) ? { ccs: cc } : { cc }
55
+ };
56
+ }
57
+ function getRequiredId(value, name) {
58
+ if (value instanceof URL) return value;
59
+ if (value.id == null) throw new TypeError(`The ${name} must have an id.`);
60
+ return value.id;
61
+ }
62
+ function getId(value) {
63
+ return value instanceof URL ? value : value.id;
64
+ }
65
+ function idsEqual(left, right) {
66
+ return left != null && right != null && left.href === right.href;
67
+ }
68
+ function getTypeId(object) {
69
+ return object.constructor.typeId;
70
+ }
71
+ async function verifyRequest(context, options, config) {
72
+ const documentLoader = options.documentLoader ?? context.documentLoader;
73
+ const expectedRequestId = options.request instanceof URL ? options.request : null;
74
+ const requestResult = await materialize(options.request, config.requestClass, documentLoader);
75
+ if (!requestResult.ok) return {
76
+ verified: false,
77
+ failure: requestResult.failure
78
+ };
79
+ const request = requestResult.object;
80
+ if (!(request instanceof config.requestClass)) return {
81
+ verified: false,
82
+ request,
83
+ requestId: request.id ?? void 0,
84
+ failure: {
85
+ category: "invalid",
86
+ type: "wrongType",
87
+ expectedType: config.requestClass.typeId,
88
+ actualTypes: [getTypeId(request)]
89
+ }
90
+ };
91
+ if (request.id == null) return {
92
+ verified: false,
93
+ request,
94
+ failure: {
95
+ category: "invalid",
96
+ type: "missingId"
97
+ }
98
+ };
99
+ if (expectedRequestId != null && !idsEqual(request.id, expectedRequestId)) return {
100
+ verified: false,
101
+ request,
102
+ requestId: request.id,
103
+ failure: {
104
+ category: "invalid",
105
+ type: "idMismatch",
106
+ expected: expectedRequestId,
107
+ actual: request.id
108
+ }
109
+ };
110
+ const dereferenceOptions = {
111
+ documentLoader,
112
+ contextLoader: documentLoader,
113
+ suppressError: true
114
+ };
115
+ const interactionTarget = await config.getInteractionTarget(request, dereferenceOptions);
116
+ if (interactionTarget == null) return {
117
+ verified: false,
118
+ request,
119
+ requestId: request.id,
120
+ failure: {
121
+ category: "invalid",
122
+ type: "missingObject"
123
+ }
124
+ };
125
+ if (config.isInteractionTarget != null && !config.isInteractionTarget(interactionTarget)) return {
126
+ verified: false,
127
+ request,
128
+ requestId: request.id,
129
+ failure: {
130
+ category: "invalid",
131
+ type: "wrongObjectType",
132
+ expectedTypes: config.interactionTargetTypes ?? [],
133
+ actualTypes: [getTypeId(interactionTarget)]
134
+ }
135
+ };
136
+ const interactingObject = await config.getInteractingObject(request, dereferenceOptions);
137
+ if (interactingObject == null) return {
138
+ verified: false,
139
+ request,
140
+ requestId: request.id,
141
+ failure: {
142
+ category: "invalid",
143
+ type: "missingInstrument"
144
+ }
145
+ };
146
+ if (config.isInteractingObject != null && !config.isInteractingObject(interactingObject)) return {
147
+ verified: false,
148
+ request,
149
+ requestId: request.id,
150
+ failure: {
151
+ category: "invalid",
152
+ type: "wrongInstrumentType",
153
+ expectedTypes: config.interactingObjectTypes ?? [],
154
+ actualTypes: [getTypeId(interactingObject)]
155
+ }
156
+ };
157
+ const requester = request.actorId ?? config.getRequester?.(request, interactingObject, interactionTarget);
158
+ if (requester == null) return {
159
+ verified: false,
160
+ request,
161
+ requestId: request.id,
162
+ failure: {
163
+ category: "invalid",
164
+ type: "missingActor"
165
+ }
166
+ };
167
+ const interactingObjectId = getId(interactingObject);
168
+ if (interactingObjectId == null) return {
169
+ verified: false,
170
+ request,
171
+ requestId: request.id,
172
+ failure: {
173
+ category: "invalid",
174
+ type: "missingInstrumentId"
175
+ }
176
+ };
177
+ const interactionTargetId = getId(interactionTarget);
178
+ if (interactionTargetId == null) return {
179
+ verified: false,
180
+ request,
181
+ requestId: request.id,
182
+ failure: {
183
+ category: "invalid",
184
+ type: "missingObjectId"
185
+ }
186
+ };
187
+ const validation = config.validateRequest(request, interactingObject, interactionTarget, requester);
188
+ if (validation != null) return {
189
+ verified: false,
190
+ request,
191
+ requestId: request.id,
192
+ failure: validation.type === "requesterMismatch" ? {
193
+ category: "unauthorized",
194
+ type: validation.type,
195
+ expected: validation.expected,
196
+ actual: validation.actual
197
+ } : {
198
+ category: "invalid",
199
+ type: validation.type,
200
+ expected: validation.expected,
201
+ actual: validation.actual
202
+ }
203
+ };
204
+ return {
205
+ verified: true,
206
+ request,
207
+ requestId: request.id,
208
+ requester,
209
+ interactingObject,
210
+ interactingObjectId,
211
+ interactionTarget,
212
+ interactionTargetId
213
+ };
214
+ }
215
+ async function verifyAuthorization(context, options, config) {
216
+ const embeddedAuthorization = !(options.authorization instanceof URL);
217
+ const expectedAuthorizationId = options.authorization instanceof URL ? options.authorization : null;
218
+ const expectedAttribution = options.attributedTo ?? (!(options.interactionTarget instanceof URL) ? config.getSelfActor(options.interactionTarget) : null);
219
+ if (expectedAuthorizationId != null && expectedAttribution == null) return {
220
+ verified: false,
221
+ authorizationId: expectedAuthorizationId,
222
+ failure: {
223
+ category: "unauthorized",
224
+ type: "missingAttribution"
225
+ }
226
+ };
227
+ if (expectedAuthorizationId != null && expectedAttribution != null) {
228
+ const expectedOrigin = getAuthorizationOrigin(expectedAttribution);
229
+ const actualOrigin = getAuthorizationOrigin(expectedAuthorizationId);
230
+ if (expectedOrigin === "null" || actualOrigin === "null" || expectedOrigin !== actualOrigin) return {
231
+ verified: false,
232
+ authorizationId: expectedAuthorizationId,
233
+ failure: {
234
+ category: "unauthorized",
235
+ type: "originMismatch",
236
+ expectedOrigin,
237
+ actualOrigin
238
+ }
239
+ };
240
+ }
241
+ const authorizationResult = await materialize(options.authorization, config.authorizationClass, options.documentLoader ?? context.documentLoader);
242
+ if (!authorizationResult.ok) return {
243
+ verified: false,
244
+ failure: authorizationResult.failure
245
+ };
246
+ const authorization = authorizationResult.object;
247
+ if (!(authorization instanceof config.authorizationClass)) return {
248
+ verified: false,
249
+ authorization,
250
+ authorizationId: authorization.id ?? void 0,
251
+ failure: {
252
+ category: "unauthorized",
253
+ type: "wrongType",
254
+ expectedType: config.authorizationClass.typeId,
255
+ actualTypes: [getTypeId(authorization)]
256
+ }
257
+ };
258
+ if (authorization.id == null) return {
259
+ verified: false,
260
+ authorization,
261
+ failure: {
262
+ category: "unauthorized",
263
+ type: "missingId"
264
+ }
265
+ };
266
+ if (expectedAuthorizationId != null && !idsEqual(authorization.id, expectedAuthorizationId)) return {
267
+ verified: false,
268
+ authorization,
269
+ authorizationId: authorization.id,
270
+ failure: {
271
+ category: "unauthorized",
272
+ type: "idMismatch",
273
+ expected: expectedAuthorizationId,
274
+ actual: authorization.id
275
+ }
276
+ };
277
+ const revocation = await options.getRevocation?.(authorization.id, context);
278
+ if (revocation != null) return {
279
+ verified: false,
280
+ authorization,
281
+ authorizationId: authorization.id,
282
+ failure: {
283
+ category: "revoked",
284
+ type: "deleted",
285
+ ...revocation
286
+ }
287
+ };
288
+ const interactingObjectId = getId(options.interactingObject);
289
+ if (interactingObjectId == null) return {
290
+ verified: false,
291
+ authorization,
292
+ authorizationId: authorization.id,
293
+ failure: {
294
+ category: "unauthorized",
295
+ type: "missingObjectId"
296
+ }
297
+ };
298
+ const interactionTargetId = getId(options.interactionTarget);
299
+ if (interactionTargetId == null) return {
300
+ verified: false,
301
+ authorization,
302
+ authorizationId: authorization.id,
303
+ failure: {
304
+ category: "unauthorized",
305
+ type: "missingTargetId"
306
+ }
307
+ };
308
+ const actualInteractingId = authorization.interactingObjectId;
309
+ if (!idsEqual(actualInteractingId, interactingObjectId)) return {
310
+ verified: false,
311
+ authorization,
312
+ authorizationId: authorization.id,
313
+ failure: {
314
+ category: "unauthorized",
315
+ type: "objectMismatch",
316
+ expected: interactingObjectId,
317
+ actual: actualInteractingId ?? void 0
318
+ }
319
+ };
320
+ const actualTargetId = authorization.interactionTargetId;
321
+ if (!idsEqual(actualTargetId, interactionTargetId)) return {
322
+ verified: false,
323
+ authorization,
324
+ authorizationId: authorization.id,
325
+ failure: {
326
+ category: "unauthorized",
327
+ type: "targetMismatch",
328
+ expected: interactionTargetId,
329
+ actual: actualTargetId ?? void 0
330
+ }
331
+ };
332
+ if (expectedAttribution == null) return {
333
+ verified: false,
334
+ authorization,
335
+ authorizationId: authorization.id,
336
+ failure: {
337
+ category: "unauthorized",
338
+ type: "missingAttribution"
339
+ }
340
+ };
341
+ if (config.authorizationAttribution !== "optional" && !idsEqual(authorization.attributionId, expectedAttribution)) return {
342
+ verified: false,
343
+ authorization,
344
+ authorizationId: authorization.id,
345
+ failure: {
346
+ category: "unauthorized",
347
+ type: "attributionMismatch",
348
+ expected: expectedAttribution,
349
+ actual: authorization.attributionId ?? void 0
350
+ }
351
+ };
352
+ if (authorization.attributionId != null && !idsEqual(authorization.attributionId, expectedAttribution)) return {
353
+ verified: false,
354
+ authorization,
355
+ authorizationId: authorization.id,
356
+ failure: {
357
+ category: "unauthorized",
358
+ type: "attributionMismatch",
359
+ expected: expectedAttribution,
360
+ actual: authorization.attributionId
361
+ }
362
+ };
363
+ const expectedOrigin = getAuthorizationOrigin(expectedAttribution);
364
+ const actualOrigin = getAuthorizationOrigin(authorization.id);
365
+ if (expectedOrigin === "null" || actualOrigin === "null" || expectedOrigin !== actualOrigin) return {
366
+ verified: false,
367
+ authorization,
368
+ authorizationId: authorization.id,
369
+ failure: {
370
+ category: "unauthorized",
371
+ type: "originMismatch",
372
+ expectedOrigin,
373
+ actualOrigin
374
+ }
375
+ };
376
+ if (embeddedAuthorization && options.verifyAuthenticity == null) return {
377
+ verified: false,
378
+ authorization,
379
+ authorizationId: authorization.id,
380
+ failure: {
381
+ category: "unauthorized",
382
+ type: "notAuthentic",
383
+ detail: "Embedded authorizations require authenticity verification."
384
+ }
385
+ };
386
+ if (options.verifyAuthenticity != null) {
387
+ let authentic;
388
+ try {
389
+ authentic = await options.verifyAuthenticity(authorization, context);
390
+ } catch (cause) {
391
+ return {
392
+ verified: false,
393
+ authorization,
394
+ authorizationId: authorization.id,
395
+ failure: {
396
+ category: "unauthorized",
397
+ type: "notAuthentic",
398
+ cause
399
+ }
400
+ };
401
+ }
402
+ if (!authentic) return {
403
+ verified: false,
404
+ authorization,
405
+ authorizationId: authorization.id,
406
+ failure: {
407
+ category: "unauthorized",
408
+ type: "notAuthentic"
409
+ }
410
+ };
411
+ }
412
+ return {
413
+ verified: true,
414
+ authorization,
415
+ authorizationId: authorization.id
416
+ };
417
+ }
418
+ async function materialize(value, constructor, documentLoader) {
419
+ if (!(value instanceof URL)) return {
420
+ ok: true,
421
+ object: value
422
+ };
423
+ const loader = documentLoader;
424
+ if (loader == null) return {
425
+ ok: false,
426
+ failure: {
427
+ category: "unverifiable",
428
+ type: "notDereferenceable",
429
+ url: value
430
+ }
431
+ };
432
+ let remoteDocument;
433
+ try {
434
+ remoteDocument = await loader(value.href);
435
+ } catch (cause) {
436
+ return {
437
+ ok: false,
438
+ failure: {
439
+ category: "unverifiable",
440
+ type: "notDereferenceable",
441
+ url: value,
442
+ cause
443
+ }
444
+ };
445
+ }
446
+ if (remoteDocument == null) return {
447
+ ok: false,
448
+ failure: {
449
+ category: "unverifiable",
450
+ type: "notDereferenceable",
451
+ url: value
452
+ }
453
+ };
454
+ try {
455
+ return {
456
+ ok: true,
457
+ object: await constructor.fromJsonLd(remoteDocument.document, {
458
+ documentLoader: loader,
459
+ contextLoader: loader,
460
+ baseUrl: value
461
+ })
462
+ };
463
+ } catch (cause) {
464
+ return {
465
+ ok: false,
466
+ failure: {
467
+ category: "unverifiable",
468
+ type: "invalidJsonLd",
469
+ cause
470
+ }
471
+ };
472
+ }
473
+ }
474
+ async function evaluatePolicy(context, options, config) {
475
+ if (idsEqual(config.getSelfActor(options.subject), options.requester)) return {
476
+ result: "automatic",
477
+ reason: { type: "self" }
478
+ };
479
+ const implicitAutomatic = await matchImplicitAutomaticActors(options.subject, options.requester, {
480
+ documentLoader: options.documentLoader ?? context.documentLoader,
481
+ suppressError: true
482
+ }, config);
483
+ if (implicitAutomatic != null) return {
484
+ result: "automatic",
485
+ reason: implicitAutomatic
486
+ };
487
+ const policy = options.subject.interactionPolicy;
488
+ if (policy == null) return missingPolicyDecision(config);
489
+ const rule = policy[config.policyProperty];
490
+ if (rule == null) return missingRuleDecision(config);
491
+ const automaticApprovals = rule.automaticApprovals ?? [];
492
+ const manualApprovals = rule.manualApprovals ?? [];
493
+ if (automaticApprovals.length < 1 && manualApprovals.length < 1) return missingRuleDecision(config);
494
+ const automatic = await matchRule(automaticApprovals, options.requester, context, options.matchesApprovalCollection, { actorOnly: true });
495
+ if (automatic?.result === "matched") return {
496
+ result: "automatic",
497
+ reason: automatic.reason
498
+ };
499
+ const manual = await matchRule(manualApprovals, options.requester, context, options.matchesApprovalCollection, { actorOnly: true });
500
+ if (manual?.result === "matched") return {
501
+ result: "manual",
502
+ reason: manual.reason
503
+ };
504
+ const broadAutomatic = await matchRule(automaticApprovals, options.requester, context, options.matchesApprovalCollection);
505
+ const unverifiableAutomatic = broadAutomatic?.result === "unverifiableCollection" ? broadAutomatic.collection : null;
506
+ if (broadAutomatic?.result === "matched") return {
507
+ result: "automatic",
508
+ reason: broadAutomatic.reason
509
+ };
510
+ const broadManual = await matchRule(manualApprovals, options.requester, context, options.matchesApprovalCollection);
511
+ if (broadManual?.result === "unverifiableCollection") return deniedUnverifiableCollection(broadManual.collection);
512
+ else if (broadManual?.result === "matched") return {
513
+ result: "manual",
514
+ reason: broadManual.reason
515
+ };
516
+ if (unverifiableAutomatic != null) return deniedUnverifiableCollection(unverifiableAutomatic);
517
+ return {
518
+ result: "denied",
519
+ reason: { type: "noMatch" }
520
+ };
521
+ }
522
+ async function matchImplicitAutomaticActors(subject, requester, options, config) {
523
+ if (config.getImplicitAutomaticActors == null) return null;
524
+ for await (const actor of config.getImplicitAutomaticActors(subject, options)) if (idsEqual(actor, requester)) return {
525
+ type: "actor",
526
+ actor
527
+ };
528
+ return null;
529
+ }
530
+ function missingPolicyDecision(config) {
531
+ if (config.defaultMissingPolicy === "automatic") return {
532
+ result: "automatic",
533
+ reason: {
534
+ type: "default",
535
+ default: "publicAutomatic"
536
+ }
537
+ };
538
+ return {
539
+ result: "denied",
540
+ reason: { type: "missingPolicy" }
541
+ };
542
+ }
543
+ function missingRuleDecision(config) {
544
+ if (config.defaultMissingPolicy === "automatic") return {
545
+ result: "automatic",
546
+ reason: {
547
+ type: "default",
548
+ default: "publicAutomatic"
549
+ }
550
+ };
551
+ return {
552
+ result: "denied",
553
+ reason: { type: "missingRule" }
554
+ };
555
+ }
556
+ async function matchRule(entries, requester, context, matchesApprovalCollection, options = {}) {
557
+ for (const entry of entries) if (entry.href === requester.href) return {
558
+ result: "matched",
559
+ reason: {
560
+ type: "actor",
561
+ actor: entry
562
+ }
563
+ };
564
+ if (options.actorOnly) return null;
565
+ for (const entry of entries) if (entry.href === _fedify_vocab.PUBLIC_COLLECTION.href) return {
566
+ result: "matched",
567
+ reason: { type: "public" }
568
+ };
569
+ if (matchesApprovalCollection != null) {
570
+ let unverifiableCollection = null;
571
+ for (const entry of entries) {
572
+ if (entry.href === _fedify_vocab.PUBLIC_COLLECTION.href || entry.href === requester.href) continue;
573
+ let matched;
574
+ try {
575
+ matched = await matchesApprovalCollection(entry, requester, context);
576
+ } catch {
577
+ unverifiableCollection ??= entry;
578
+ continue;
579
+ }
580
+ if (matched) return {
581
+ result: "matched",
582
+ reason: {
583
+ type: "collection",
584
+ collection: entry
585
+ }
586
+ };
587
+ }
588
+ if (unverifiableCollection != null) return {
589
+ result: "unverifiableCollection",
590
+ collection: unverifiableCollection
591
+ };
592
+ }
593
+ return null;
594
+ }
595
+ function deniedUnverifiableCollection(collection) {
596
+ return {
597
+ result: "denied",
598
+ reason: {
599
+ type: "unverifiableCollection",
600
+ collection
601
+ }
602
+ };
603
+ }
604
+ function getAuthorizationOrigin(id) {
605
+ try {
606
+ return (0, _fedify_vocab_runtime.getFe34Origin)(id);
607
+ } catch {
608
+ return id.origin;
609
+ }
610
+ }
611
+ function createAccept(options) {
612
+ if (options.mode === "polite") return new _fedify_vocab.Accept({
613
+ id: options.id,
614
+ actor: options.actor,
615
+ object: options.request,
616
+ result: options.authorization,
617
+ ...audience(options)
618
+ });
619
+ return new _fedify_vocab.Accept({
620
+ id: options.id,
621
+ actor: options.actor,
622
+ object: getRequiredId(options.interactingObject, "interactingObject"),
623
+ target: getRequiredId(options.interactionTarget, "interactionTarget"),
624
+ result: getRequiredId(options.authorization, "authorization"),
625
+ ...audience(options)
626
+ });
627
+ }
628
+ function createReject(options) {
629
+ if (options.mode === "polite") return new _fedify_vocab.Reject({
630
+ id: options.id,
631
+ actor: options.actor,
632
+ object: options.request,
633
+ ...audience(options)
634
+ });
635
+ return new _fedify_vocab.Reject({
636
+ id: options.id,
637
+ actor: options.actor,
638
+ object: getRequiredId(options.interactingObject, "interactingObject"),
639
+ target: getRequiredId(options.interactionTarget, "interactionTarget"),
640
+ ...audience(options)
641
+ });
642
+ }
643
+ function recognized(values) {
644
+ if (values.requester == null || values.interactionTargetId == null) return null;
645
+ const interactingObjectId = values.interactingObject.id;
646
+ if (interactingObjectId == null) return null;
647
+ return {
648
+ requester: values.requester,
649
+ interactingObject: values.interactingObject,
650
+ interactingObjectId,
651
+ interactionTarget: values.interactionTarget,
652
+ interactionTargetId: values.interactionTargetId,
653
+ source: values.source,
654
+ evidence: values.evidence
655
+ };
656
+ }
657
+ //#endregion
658
+ //#region src/announce.ts
659
+ const announceInteraction = createInteractionControl({
660
+ name: "announce",
661
+ policyProperty: "canAnnounce",
662
+ requestClass: _fedify_vocab.AnnounceRequest,
663
+ authorizationClass: _fedify_vocab.AnnounceAuthorization,
664
+ getInteractingObject: (request, options) => request.getInstrument(options),
665
+ isInteractingObject: (object) => object instanceof _fedify_vocab.Announce,
666
+ interactingObjectTypes: [_fedify_vocab.Announce.typeId],
667
+ getInteractionTarget: (request, options) => request.getObject(options),
668
+ validateRequest: (_request, announce, target, requester) => {
669
+ const targetId = getRequiredId(target, "interactionTarget");
670
+ if (!idsEqual(announce.objectId, targetId)) return {
671
+ type: "objectMismatch",
672
+ expected: targetId,
673
+ actual: announce.objectId ?? void 0
674
+ };
675
+ if (!idsEqual(announce.actorId, requester)) return {
676
+ type: "requesterMismatch",
677
+ expected: requester,
678
+ actual: announce.actorId ?? void 0
679
+ };
680
+ return null;
681
+ },
682
+ getSelfActor: (subject) => subject.attributionId,
683
+ defaultMissingPolicy: "automatic",
684
+ recognizeImpolite: (source) => recognized({
685
+ requester: source.actorId,
686
+ interactingObject: source,
687
+ interactionTarget: void 0,
688
+ interactionTargetId: source.objectId,
689
+ source,
690
+ evidence: {
691
+ type: "activity",
692
+ activityType: _fedify_vocab.Announce.typeId
693
+ }
694
+ })
695
+ });
696
+ //#endregion
697
+ //#region src/feature.ts
698
+ const featureInteraction = createInteractionControl({
699
+ name: "feature",
700
+ policyProperty: "canFeature",
701
+ requestClass: _fedify_vocab.FeatureRequest,
702
+ authorizationClass: _fedify_vocab.FeatureAuthorization,
703
+ getInteractingObject: (request, options) => request.getInstrument(options),
704
+ isInteractingObject: (object) => object instanceof _fedify_vocab.FeaturedCollection,
705
+ interactingObjectTypes: [_fedify_vocab.FeaturedCollection.typeId],
706
+ getInteractionTarget: (request, options) => request.getObject(options),
707
+ isInteractionTarget: _fedify_vocab.isActor,
708
+ interactionTargetTypes: [
709
+ _fedify_vocab.Application.typeId,
710
+ _fedify_vocab.Group.typeId,
711
+ _fedify_vocab.Organization.typeId,
712
+ _fedify_vocab.Person.typeId,
713
+ _fedify_vocab.Service.typeId
714
+ ],
715
+ getRequester: (_request, collection) => collection.attributionId,
716
+ validateRequest: (_request, collection, _target, requester) => {
717
+ if (!idsEqual(collection.attributionId, requester)) return {
718
+ type: "requesterMismatch",
719
+ expected: requester,
720
+ actual: collection.attributionId ?? void 0
721
+ };
722
+ return null;
723
+ },
724
+ authorizationAttribution: "optional",
725
+ getSelfActor: (subject) => subject.id,
726
+ defaultMissingPolicy: "denied",
727
+ recognizeImpolite: () => null
728
+ });
729
+ //#endregion
730
+ //#region src/key.ts
731
+ function formatInteractionKey(key) {
732
+ return JSON.stringify([
733
+ key.interaction,
734
+ key.requester.href,
735
+ key.interactingObjectId.href,
736
+ key.interactionTargetId.href
737
+ ]);
738
+ }
739
+ function formatAuthorizationKey(key) {
740
+ return JSON.stringify([key.interaction, key.authorizationId.href]);
741
+ }
742
+ //#endregion
743
+ //#region src/like.ts
744
+ const likeInteraction = createInteractionControl({
745
+ name: "like",
746
+ policyProperty: "canLike",
747
+ requestClass: _fedify_vocab.LikeRequest,
748
+ authorizationClass: _fedify_vocab.LikeAuthorization,
749
+ getInteractingObject: (request, options) => request.getInstrument(options),
750
+ isInteractingObject: (object) => object instanceof _fedify_vocab.Like,
751
+ interactingObjectTypes: [_fedify_vocab.Like.typeId],
752
+ getInteractionTarget: (request, options) => request.getObject(options),
753
+ validateRequest: (_request, like, target, requester) => {
754
+ const targetId = getRequiredId(target, "interactionTarget");
755
+ if (!idsEqual(like.objectId, targetId)) return {
756
+ type: "objectMismatch",
757
+ expected: targetId,
758
+ actual: like.objectId ?? void 0
759
+ };
760
+ if (!idsEqual(like.actorId, requester)) return {
761
+ type: "requesterMismatch",
762
+ expected: requester,
763
+ actual: like.actorId ?? void 0
764
+ };
765
+ return null;
766
+ },
767
+ getSelfActor: (subject) => subject.attributionId,
768
+ defaultMissingPolicy: "automatic",
769
+ recognizeImpolite: (source) => recognized({
770
+ requester: source.actorId,
771
+ interactingObject: source,
772
+ interactionTarget: void 0,
773
+ interactionTargetId: source.objectId,
774
+ source,
775
+ evidence: {
776
+ type: "activity",
777
+ activityType: _fedify_vocab.Like.typeId
778
+ }
779
+ })
780
+ });
781
+ //#endregion
782
+ //#region src/quote.ts
783
+ const QUOTE = new URL("https://w3id.org/fep/044f#quote");
784
+ function getQuoteTargetId(quote) {
785
+ return quote.quoteId ?? quote.quoteUrl;
786
+ }
787
+ const quoteInteraction = createInteractionControl({
788
+ name: "quote",
789
+ policyProperty: "canQuote",
790
+ requestClass: _fedify_vocab.QuoteRequest,
791
+ authorizationClass: _fedify_vocab.QuoteAuthorization,
792
+ getInteractingObject: (request, options) => request.getInstrument(options),
793
+ isInteractingObject: (object) => object instanceof _fedify_vocab.Note || object instanceof _fedify_vocab.Article || object instanceof _fedify_vocab.Question || object instanceof _fedify_vocab.ChatMessage,
794
+ interactingObjectTypes: [
795
+ _fedify_vocab.Note.typeId,
796
+ _fedify_vocab.Article.typeId,
797
+ _fedify_vocab.Question.typeId,
798
+ _fedify_vocab.ChatMessage.typeId
799
+ ],
800
+ getInteractionTarget: (request, options) => request.getObject(options),
801
+ validateRequest: (_request, quote, target, requester) => {
802
+ const targetId = getRequiredId(target, "interactionTarget");
803
+ if (quote.quoteId != null && quote.quoteUrl != null && !idsEqual(quote.quoteUrl, quote.quoteId)) return {
804
+ type: "objectMismatch",
805
+ expected: quote.quoteId,
806
+ actual: quote.quoteUrl
807
+ };
808
+ const quoteTargetId = getQuoteTargetId(quote);
809
+ if (!idsEqual(quoteTargetId, targetId)) return {
810
+ type: "objectMismatch",
811
+ expected: targetId,
812
+ actual: quoteTargetId ?? void 0
813
+ };
814
+ if (!idsEqual(quote.attributionId, requester)) return {
815
+ type: "requesterMismatch",
816
+ expected: requester,
817
+ actual: quote.attributionId ?? void 0
818
+ };
819
+ return null;
820
+ },
821
+ getSelfActor: (subject) => subject.attributionId,
822
+ defaultMissingPolicy: "denied",
823
+ recognizeImpolite: (source) => {
824
+ if (source instanceof _fedify_vocab.Create) return null;
825
+ return recognized({
826
+ requester: source.attributionId,
827
+ interactingObject: source,
828
+ interactionTarget: void 0,
829
+ interactionTargetId: getQuoteTargetId(source),
830
+ source,
831
+ evidence: {
832
+ type: "property",
833
+ property: QUOTE
834
+ }
835
+ });
836
+ }
837
+ });
838
+ //#endregion
839
+ //#region src/reply.ts
840
+ const IN_REPLY_TO = new URL("https://www.w3.org/ns/activitystreams#inReplyTo");
841
+ const replyInteraction = createInteractionControl({
842
+ name: "reply",
843
+ policyProperty: "canReply",
844
+ requestClass: _fedify_vocab.ReplyRequest,
845
+ authorizationClass: _fedify_vocab.ReplyAuthorization,
846
+ getInteractingObject: (request, options) => request.getInstrument(options),
847
+ isInteractingObject: (object) => object instanceof _fedify_vocab.Note || object instanceof _fedify_vocab.Article || object instanceof _fedify_vocab.Question || object instanceof _fedify_vocab.ChatMessage,
848
+ interactingObjectTypes: [
849
+ _fedify_vocab.Note.typeId,
850
+ _fedify_vocab.Article.typeId,
851
+ _fedify_vocab.Question.typeId,
852
+ _fedify_vocab.ChatMessage.typeId
853
+ ],
854
+ getInteractionTarget: (request, options) => request.getObject(options),
855
+ validateRequest: (_request, reply, target, requester) => {
856
+ const targetId = getRequiredId(target, "interactionTarget");
857
+ if (!idsEqual(reply.replyTargetId, targetId)) return {
858
+ type: "objectMismatch",
859
+ expected: targetId,
860
+ actual: reply.replyTargetId ?? void 0
861
+ };
862
+ if (!idsEqual(reply.attributionId, requester)) return {
863
+ type: "requesterMismatch",
864
+ expected: requester,
865
+ actual: reply.attributionId ?? void 0
866
+ };
867
+ return null;
868
+ },
869
+ getSelfActor: (subject) => subject.attributionId,
870
+ getImplicitAutomaticActors: getReplyParticipants,
871
+ defaultMissingPolicy: "automatic",
872
+ recognizeImpolite: (source) => {
873
+ if (source instanceof _fedify_vocab.Create) return null;
874
+ return recognized({
875
+ requester: source.attributionId,
876
+ interactingObject: source,
877
+ interactionTarget: void 0,
878
+ interactionTargetId: source.replyTargetId,
879
+ source,
880
+ evidence: {
881
+ type: "property",
882
+ property: IN_REPLY_TO
883
+ }
884
+ });
885
+ }
886
+ });
887
+ async function* getReplyParticipants(subject, options) {
888
+ const replyTarget = await subject.getReplyTarget(options);
889
+ if (replyTarget instanceof _fedify_vocab.Object && replyTarget.attributionId != null) yield replyTarget.attributionId;
890
+ for await (const tag of subject.getTags(options)) if (tag instanceof _fedify_vocab.Mention && tag.href != null) yield tag.href;
891
+ }
892
+ //#endregion
893
+ exports.announceInteraction = announceInteraction;
894
+ exports.featureInteraction = featureInteraction;
895
+ exports.formatAuthorizationKey = formatAuthorizationKey;
896
+ exports.formatInteractionKey = formatInteractionKey;
897
+ exports.likeInteraction = likeInteraction;
898
+ exports.quoteInteraction = quoteInteraction;
899
+ exports.replyInteraction = replyInteraction;