@fedify/fedify 1.3.0-dev.577 → 1.3.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/CHANGES.md CHANGED
@@ -6,7 +6,7 @@ Fedify changelog
6
6
  Version 1.3.0
7
7
  -------------
8
8
 
9
- To be released.
9
+ Released on November 30, 2024.
10
10
 
11
11
  - `MessageQueue`s now can be differently configured for incoming and outgoing
12
12
  activities.
@@ -62,6 +62,16 @@ To be released.
62
62
  - `Context.sendActivity()` and `InboxContext.forwardActivity()` methods now
63
63
  reject when they fail to enqueue the task. [[#192]]
64
64
 
65
+ - Fedify now allows you to manually route an `Activity` to the corresponding
66
+ inbox listener. [[#193]]
67
+
68
+ - Added `Context.routeActivity()` method.
69
+ - Added `RouteActivityOptions` interface.
70
+
71
+ - `Object.toJsonLd()` without any `format` option now returns its original
72
+ JSON-LD object even if it not created from `Object.fromJsonLd()` but it is
73
+ returned from another `Object`'s `get*()` method.
74
+
65
75
  - Fedify now supports OpenTelemetry for tracing. [[#170]]
66
76
 
67
77
  - Added `Context.tracerProvider` property.
@@ -107,6 +117,7 @@ To be released.
107
117
  [#183]: https://github.com/dahlia/fedify/pull/183
108
118
  [#186]: https://github.com/dahlia/fedify/pull/186
109
119
  [#192]: https://github.com/dahlia/fedify/issues/192
120
+ [#193]: https://github.com/dahlia/fedify/issues/193
110
121
 
111
122
 
112
123
  Version 1.2.8
package/esm/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "@fedify/fedify",
3
- "version": "1.3.0-dev.577+bda6d57e",
3
+ "version": "1.3.0",
4
4
  "license": "MIT",
5
5
  "exports": {
6
6
  ".": "./mod.ts",
@@ -82,7 +82,7 @@ export default {
82
82
  "cache": "deno task codegen && deno cache mod.ts",
83
83
  "check": "deno task codegen && deno fmt --check && deno lint && deno check */*.ts",
84
84
  "codegen": "deno run --allow-read --allow-write --check codegen/main.ts vocab/ ../runtime/ > vocab/vocab.ts && deno fmt vocab/vocab.ts && deno cache vocab/vocab.ts && deno check vocab/vocab.ts",
85
- "test-without-codegen": "deno test --check --doc --allow-read --allow-write --allow-env --unstable-kv --trace-leaks",
85
+ "test-without-codegen": "deno test --check --doc --allow-read --allow-write --allow-env --unstable-kv --trace-leaks --parallel",
86
86
  "test": "deno task codegen && deno task test-without-codegen",
87
87
  "coverage": "rm -rf coverage/ && deno task test --coverage && deno coverage --html coverage",
88
88
  "bench": "deno task codegen && deno bench --allow-read --allow-write --allow-net --allow-env --allow-run --unstable-kv",
@@ -1,6 +1,5 @@
1
- import * as dntShim from "../_dnt.shims.js";
2
1
  import { getLogger } from "@logtape/logtape";
3
- import { context, propagation, SpanKind, SpanStatusCode, trace, } from "@opentelemetry/api";
2
+ import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
4
3
  import { accepts } from "../deps/jsr.io/@std/http/1.0.11/negotiation.js";
5
4
  import metadata from "../deno.js";
6
5
  import { verifyRequest } from "../sig/http.js";
@@ -8,7 +7,9 @@ import { detachSignature, verifyJsonLd } from "../sig/ld.js";
8
7
  import { doesActorOwnKey } from "../sig/owner.js";
9
8
  import { verifyObject } from "../sig/proof.js";
10
9
  import { getTypeId } from "../vocab/type.js";
11
- import { Activity, CryptographicKey, Link, Multikey, Object, OrderedCollection, OrderedCollectionPage, } from "../vocab/vocab.js";
10
+ import { Activity, Link, Object, OrderedCollection, OrderedCollectionPage, } from "../vocab/vocab.js";
11
+ import { routeActivity } from "./inbox.js";
12
+ import { KvKeyCache } from "./keycache.js";
12
13
  export function acceptsJsonLd(request) {
13
14
  const types = accepts(request);
14
15
  if (types == null)
@@ -317,42 +318,7 @@ async function handleInboxInternal(request, { recipient, context: ctx, inboxCont
317
318
  headers: { "Content-Type": "text/plain; charset=utf-8" },
318
319
  });
319
320
  }
320
- const keyCache = {
321
- nullKeys: new Set(),
322
- async get(keyId) {
323
- if (this.nullKeys.has(keyId.href))
324
- return null;
325
- const serialized = await kv.get([
326
- ...kvPrefixes.publicKey,
327
- keyId.href,
328
- ]);
329
- if (serialized == null)
330
- return undefined;
331
- let object;
332
- try {
333
- object = await Object.fromJsonLd(serialized, ctx);
334
- }
335
- catch {
336
- await kv.delete([...kvPrefixes.publicKey, keyId.href]);
337
- return undefined;
338
- }
339
- if (object instanceof CryptographicKey || object instanceof Multikey) {
340
- return object;
341
- }
342
- await kv.delete([...kvPrefixes.publicKey, keyId.href]);
343
- return undefined;
344
- },
345
- async set(keyId, key) {
346
- if (key == null) {
347
- this.nullKeys.add(keyId.href);
348
- await kv.delete([...kvPrefixes.publicKey, keyId.href]);
349
- return;
350
- }
351
- this.nullKeys.delete(keyId.href);
352
- const serialized = await key.toJsonLd(ctx);
353
- await kv.set([...kvPrefixes.publicKey, keyId.href], serialized);
354
- },
355
- };
321
+ const keyCache = new KvKeyCache(kv, kvPrefixes.publicKey, ctx);
356
322
  const ldSigVerified = await verifyJsonLd(json, {
357
323
  contextLoader: ctx.contextLoader,
358
324
  documentLoader: ctx.documentLoader,
@@ -436,143 +402,73 @@ async function handleInboxInternal(request, { recipient, context: ctx, inboxCont
436
402
  span.setAttribute("activitypub.activity.id", activity.id.href);
437
403
  }
438
404
  span.setAttribute("activitypub.activity.type", getTypeId(activity).href);
439
- const cacheKey = activity.id == null
440
- ? null
441
- : [...kvPrefixes.activityIdempotence, activity.id.href];
442
- if (cacheKey != null) {
443
- const cached = await kv.get(cacheKey);
444
- if (cached === true) {
445
- logger.debug("Activity {activityId} has already been processed.", {
446
- activityId: activity.id?.href,
447
- activity: json,
448
- recipient,
449
- });
450
- span.setStatus({
451
- code: SpanStatusCode.UNSET,
452
- message: `Activity ${activity.id?.href} has already been processed.`,
453
- });
454
- return new Response(`Activity <${activity.id}> has already been processed.`, {
455
- status: 202,
456
- headers: { "Content-Type": "text/plain; charset=utf-8" },
457
- });
458
- }
459
- }
460
- if (activity.actorId == null) {
461
- logger.error("Missing actor.", { activity: json });
462
- span.setStatus({ code: SpanStatusCode.ERROR, message: "Missing actor." });
463
- return new Response("Missing actor.", {
464
- status: 400,
465
- headers: { "Content-Type": "text/plain; charset=utf-8" },
466
- });
467
- }
468
- span.setAttribute("activitypub.actor.id", activity.actorId.href);
405
+ const routeResult = await routeActivity({
406
+ context: ctx,
407
+ json,
408
+ activity,
409
+ recipient,
410
+ inboxListeners,
411
+ inboxContextFactory,
412
+ inboxErrorHandler,
413
+ kv,
414
+ kvPrefixes,
415
+ queue,
416
+ span,
417
+ tracerProvider,
418
+ });
469
419
  if (httpSigKey != null && !await doesActorOwnKey(activity, httpSigKey, ctx)) {
470
420
  logger.error("The signer ({keyId}) and the actor ({actorId}) do not match.", {
471
421
  activity: json,
472
422
  recipient,
473
423
  keyId: httpSigKey.id?.href,
474
- actorId: activity.actorId.href,
424
+ actorId: activity.actorId?.href,
475
425
  });
476
426
  span.setStatus({
477
427
  code: SpanStatusCode.ERROR,
478
428
  message: `The signer (${httpSigKey.id?.href}) and ` +
479
- `the actor (${activity.actorId.href}) do not match.`,
429
+ `the actor (${activity.actorId?.href}) do not match.`,
480
430
  });
481
431
  return new Response("The signer and the actor do not match.", {
482
432
  status: 401,
483
433
  headers: { "Content-Type": "text/plain; charset=utf-8" },
484
434
  });
485
435
  }
486
- if (queue != null) {
487
- const carrier = {};
488
- propagation.inject(context.active(), carrier);
489
- try {
490
- await queue.enqueue({
491
- type: "inbox",
492
- id: dntShim.crypto.randomUUID(),
493
- baseUrl: request.url,
494
- activity: json,
495
- identifier: recipient,
496
- attempt: 0,
497
- started: new Date().toISOString(),
498
- traceContext: carrier,
499
- });
500
- }
501
- catch (error) {
502
- logger.error("Failed to enqueue the incoming activity {activityId}:\n{error}", { error, activityId: activity.id?.href, activity: json, recipient });
503
- span.setStatus({
504
- code: SpanStatusCode.ERROR,
505
- message: `Failed to enqueue the incoming activity ${activity.id?.href}.`,
506
- });
507
- throw error;
508
- }
509
- logger.info("Activity {activityId} is enqueued.", { activityId: activity.id?.href, activity: json, recipient });
436
+ if (routeResult === "alreadyProcessed") {
437
+ return new Response(`Activity <${activity.id}> has already been processed.`, {
438
+ status: 202,
439
+ headers: { "Content-Type": "text/plain; charset=utf-8" },
440
+ });
441
+ }
442
+ else if (routeResult === "missingActor") {
443
+ return new Response("Missing actor.", {
444
+ status: 400,
445
+ headers: { "Content-Type": "text/plain; charset=utf-8" },
446
+ });
447
+ }
448
+ else if (routeResult === "enqueued") {
510
449
  return new Response("Activity is enqueued.", {
511
450
  status: 202,
512
451
  headers: { "Content-Type": "text/plain; charset=utf-8" },
513
452
  });
514
453
  }
515
- tracerProvider = tracerProvider ?? trace.getTracerProvider();
516
- const tracer = tracerProvider.getTracer(metadata.name, metadata.version);
517
- const response = await tracer.startActiveSpan("activitypub.dispatch_inbox_listener", { kind: SpanKind.INTERNAL }, async (span) => {
518
- const dispatched = inboxListeners?.dispatchWithClass(activity);
519
- if (dispatched == null) {
520
- logger.error("Unsupported activity type:\n{activity}", { activity: json, recipient });
521
- span.setStatus({
522
- code: SpanStatusCode.UNSET,
523
- message: `Unsupported activity type: ${getTypeId(activity).href}`,
524
- });
525
- span.end();
526
- return new Response("", {
527
- status: 202,
528
- headers: { "Content-Type": "text/plain; charset=utf-8" },
529
- });
530
- }
531
- const { class: cls, listener } = dispatched;
532
- span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
533
- try {
534
- await listener(inboxContextFactory(recipient, json, activity?.id?.href, getTypeId(activity).href), activity);
535
- }
536
- catch (error) {
537
- try {
538
- await inboxErrorHandler?.(ctx, error);
539
- }
540
- catch (error) {
541
- logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
542
- error,
543
- activityId: activity.id?.href,
544
- activity: json,
545
- recipient,
546
- });
547
- }
548
- logger.error("Failed to process the incoming activity {activityId}:\n{error}", {
549
- error,
550
- activityId: activity.id?.href,
551
- activity: json,
552
- recipient,
553
- });
554
- span.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
555
- span.end();
556
- return new Response("Internal server error.", {
557
- status: 500,
558
- headers: { "Content-Type": "text/plain; charset=utf-8" },
559
- });
560
- }
561
- if (cacheKey != null) {
562
- await kv.set(cacheKey, true, {
563
- ttl: dntShim.Temporal.Duration.from({ days: 1 }),
564
- });
565
- }
566
- logger.info("Activity {activityId} has been processed.", { activityId: activity.id?.href, activity: json, recipient });
567
- span.end();
454
+ else if (routeResult === "unsupportedActivity") {
568
455
  return new Response("", {
569
456
  status: 202,
570
457
  headers: { "Content-Type": "text/plain; charset=utf-8" },
571
458
  });
572
- });
573
- if (response.status >= 500)
574
- span.setStatus({ code: SpanStatusCode.ERROR });
575
- return response;
459
+ }
460
+ else if (routeResult === "error") {
461
+ return new Response("Internal server error.", {
462
+ status: 500,
463
+ headers: { "Content-Type": "text/plain; charset=utf-8" },
464
+ });
465
+ }
466
+ else {
467
+ return new Response("", {
468
+ status: 202,
469
+ headers: { "Content-Type": "text/plain; charset=utf-8" },
470
+ });
471
+ }
576
472
  }
577
473
  /**
578
474
  * Responds with the given object in JSON-LD format.
@@ -1,3 +1,8 @@
1
+ import * as dntShim from "../_dnt.shims.js";
2
+ import { getLogger } from "@logtape/logtape";
3
+ import { context, propagation, SpanKind, SpanStatusCode, trace, } from "@opentelemetry/api";
4
+ import metadata from "../deno.js";
5
+ import { getTypeId } from "../vocab/type.js";
1
6
  import { Activity } from "../vocab/vocab.js";
2
7
  export class InboxListenerSet {
3
8
  #listeners;
@@ -35,3 +40,106 @@ export class InboxListenerSet {
35
40
  return this.dispatchWithClass(activity)?.listener ?? null;
36
41
  }
37
42
  }
43
+ export async function routeActivity({ context: ctx, json, activity, recipient, inboxListeners, inboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, tracerProvider, }) {
44
+ const logger = getLogger(["fedify", "federation", "inbox"]);
45
+ const cacheKey = activity.id == null ? null : [
46
+ ...kvPrefixes.activityIdempotence,
47
+ activity.id.href,
48
+ ];
49
+ if (cacheKey != null) {
50
+ const cached = await kv.get(cacheKey);
51
+ if (cached === true) {
52
+ logger.debug("Activity {activityId} has already been processed.", {
53
+ activityId: activity.id?.href,
54
+ activity: json,
55
+ recipient,
56
+ });
57
+ span.setStatus({
58
+ code: SpanStatusCode.UNSET,
59
+ message: `Activity ${activity.id?.href} has already been processed.`,
60
+ });
61
+ return "alreadyProcessed";
62
+ }
63
+ }
64
+ if (activity.actorId == null) {
65
+ logger.error("Missing actor.", { activity: json });
66
+ span.setStatus({ code: SpanStatusCode.ERROR, message: "Missing actor." });
67
+ return "missingActor";
68
+ }
69
+ span.setAttribute("activitypub.actor.id", activity.actorId.href);
70
+ if (queue != null) {
71
+ const carrier = {};
72
+ propagation.inject(context.active(), carrier);
73
+ try {
74
+ await queue.enqueue({
75
+ type: "inbox",
76
+ id: dntShim.crypto.randomUUID(),
77
+ baseUrl: ctx.origin,
78
+ activity: json,
79
+ identifier: recipient,
80
+ attempt: 0,
81
+ started: new Date().toISOString(),
82
+ traceContext: carrier,
83
+ });
84
+ }
85
+ catch (error) {
86
+ logger.error("Failed to enqueue the incoming activity {activityId}:\n{error}", { error, activityId: activity.id?.href, activity: json, recipient });
87
+ span.setStatus({
88
+ code: SpanStatusCode.ERROR,
89
+ message: `Failed to enqueue the incoming activity ${activity.id?.href}.`,
90
+ });
91
+ throw error;
92
+ }
93
+ logger.info("Activity {activityId} is enqueued.", { activityId: activity.id?.href, activity: json, recipient });
94
+ return "enqueued";
95
+ }
96
+ tracerProvider = tracerProvider ?? trace.getTracerProvider();
97
+ const tracer = tracerProvider.getTracer(metadata.name, metadata.version);
98
+ return await tracer.startActiveSpan("activitypub.dispatch_inbox_listener", { kind: SpanKind.INTERNAL }, async (span) => {
99
+ const dispatched = inboxListeners?.dispatchWithClass(activity);
100
+ if (dispatched == null) {
101
+ logger.error("Unsupported activity type:\n{activity}", { activity: json, recipient });
102
+ span.setStatus({
103
+ code: SpanStatusCode.UNSET,
104
+ message: `Unsupported activity type: ${getTypeId(activity).href}`,
105
+ });
106
+ span.end();
107
+ return "unsupportedActivity";
108
+ }
109
+ const { class: cls, listener } = dispatched;
110
+ span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
111
+ try {
112
+ await listener(inboxContextFactory(recipient, json, activity?.id?.href, getTypeId(activity).href), activity);
113
+ }
114
+ catch (error) {
115
+ try {
116
+ await inboxErrorHandler?.(ctx, error);
117
+ }
118
+ catch (error) {
119
+ logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
120
+ error,
121
+ activityId: activity.id?.href,
122
+ activity: json,
123
+ recipient,
124
+ });
125
+ }
126
+ logger.error("Failed to process the incoming activity {activityId}:\n{error}", {
127
+ error,
128
+ activityId: activity.id?.href,
129
+ activity: json,
130
+ recipient,
131
+ });
132
+ span.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
133
+ span.end();
134
+ return "error";
135
+ }
136
+ if (cacheKey != null) {
137
+ await kv.set(cacheKey, true, {
138
+ ttl: dntShim.Temporal.Duration.from({ days: 1 }),
139
+ });
140
+ }
141
+ logger.info("Activity {activityId} has been processed.", { activityId: activity.id?.href, activity: json, recipient });
142
+ span.end();
143
+ return "success";
144
+ });
145
+ }
@@ -0,0 +1,42 @@
1
+ import { CryptographicKey, Multikey } from "../vocab/vocab.js";
2
+ export class KvKeyCache {
3
+ kv;
4
+ prefix;
5
+ options;
6
+ nullKeys;
7
+ constructor(kv, prefix, options = {}) {
8
+ this.kv = kv;
9
+ this.prefix = prefix;
10
+ this.nullKeys = new Set();
11
+ this.options = options;
12
+ }
13
+ async get(keyId) {
14
+ if (this.nullKeys.has(keyId.href))
15
+ return null;
16
+ const serialized = await this.kv.get([...this.prefix, keyId.href]);
17
+ if (serialized == null)
18
+ return undefined;
19
+ try {
20
+ return await CryptographicKey.fromJsonLd(serialized, this.options);
21
+ }
22
+ catch {
23
+ try {
24
+ return await Multikey.fromJsonLd(serialized, this.options);
25
+ }
26
+ catch {
27
+ await this.kv.delete([...this.prefix, keyId.href]);
28
+ return undefined;
29
+ }
30
+ }
31
+ }
32
+ async set(keyId, key) {
33
+ if (key == null) {
34
+ this.nullKeys.add(keyId.href);
35
+ await this.kv.delete([...this.prefix, keyId.href]);
36
+ return;
37
+ }
38
+ this.nullKeys.delete(keyId.href);
39
+ const serialized = await key.toJsonLd(this.options);
40
+ await this.kv.set([...this.prefix, keyId.href], serialized);
41
+ }
42
+ }
@@ -1,4 +1,5 @@
1
1
  import * as dntShim from "../_dnt.shims.js";
2
+ import { verifyObject } from "../mod.js";
2
3
  import { getLogger, withContext } from "@logtape/logtape";
3
4
  import { context, propagation, SpanKind, SpanStatusCode, trace, } from "@opentelemetry/api";
4
5
  import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_RESPONSE_HEADER, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_URL_FULL, } from "@opentelemetry/semantic-conventions";
@@ -16,7 +17,8 @@ import { Activity, CryptographicKey, Multikey, } from "../vocab/vocab.js";
16
17
  import { handleWebFinger } from "../webfinger/handler.js";
17
18
  import { buildCollectionSynchronizationHeader } from "./collection.js";
18
19
  import { handleActor, handleCollection, handleInbox, handleObject, } from "./handler.js";
19
- import { InboxListenerSet } from "./inbox.js";
20
+ import { InboxListenerSet, routeActivity } from "./inbox.js";
21
+ import { KvKeyCache } from "./keycache.js";
20
22
  import { createExponentialBackoffPolicy } from "./retry.js";
21
23
  import { Router, RouterError } from "./router.js";
22
24
  import { extractInboxes, sendActivity } from "./send.js";
@@ -1946,6 +1948,118 @@ export class ContextImpl {
1946
1948
  cursor = result.nextCursor ?? null;
1947
1949
  }
1948
1950
  }
1951
+ routeActivity(recipient, activity, options = {}) {
1952
+ const tracerProvider = this.tracerProvider ?? this.tracerProvider;
1953
+ const tracer = tracerProvider.getTracer(metadata.name, metadata.version);
1954
+ return tracer.startActiveSpan("activitypub.inbox", {
1955
+ kind: this.federation.inboxQueue == null || options.immediate
1956
+ ? SpanKind.INTERNAL
1957
+ : SpanKind.PRODUCER,
1958
+ attributes: {
1959
+ "activitypub.activity.type": getTypeId(activity).href,
1960
+ },
1961
+ }, async (span) => {
1962
+ if (activity.id != null) {
1963
+ span.setAttribute("activitypub.activity.id", activity.id.href);
1964
+ }
1965
+ if (activity.toIds.length > 0) {
1966
+ span.setAttribute("activitypub.activity.to", activity.toIds.map((to) => to.href));
1967
+ }
1968
+ if (activity.ccIds.length > 0) {
1969
+ span.setAttribute("activitypub.activity.cc", activity.ccIds.map((cc) => cc.href));
1970
+ }
1971
+ if (activity.btoIds.length > 0) {
1972
+ span.setAttribute("activitypub.activity.bto", activity.btoIds.map((bto) => bto.href));
1973
+ }
1974
+ if (activity.bccIds.length > 0) {
1975
+ span.setAttribute("activitypub.activity.bcc", activity.bccIds.map((bcc) => bcc.href));
1976
+ }
1977
+ try {
1978
+ const ok = await this.routeActivityInternal(recipient, activity, options, span);
1979
+ if (ok) {
1980
+ span.setAttribute("activitypub.shared_inbox", recipient == null);
1981
+ if (recipient != null) {
1982
+ span.setAttribute("fedify.inbox.recipient", recipient);
1983
+ }
1984
+ }
1985
+ else {
1986
+ span.setStatus({ code: SpanStatusCode.ERROR });
1987
+ }
1988
+ return ok;
1989
+ }
1990
+ catch (e) {
1991
+ span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) });
1992
+ throw e;
1993
+ }
1994
+ finally {
1995
+ span.end();
1996
+ }
1997
+ });
1998
+ }
1999
+ async routeActivityInternal(recipient, activity, options = {}, span) {
2000
+ const logger = getLogger(["fedify", "federation", "inbox"]);
2001
+ const contextLoader = options.contextLoader ?? this.contextLoader;
2002
+ const json = await activity.toJsonLd({ contextLoader });
2003
+ const keyCache = new KvKeyCache(this.federation.kv, this.federation.kvPrefixes.publicKey, this);
2004
+ const verified = await verifyObject(Activity, json, {
2005
+ contextLoader,
2006
+ documentLoader: options.documentLoader ?? this.documentLoader,
2007
+ tracerProvider: options.tracerProvider ?? this.tracerProvider,
2008
+ keyCache,
2009
+ });
2010
+ if (verified == null) {
2011
+ logger.debug("Object Integrity Proofs are not verified.", { recipient, activity: json });
2012
+ if (activity.id == null) {
2013
+ logger.debug("Activity is missing an ID; unable to fetch.", { recipient, activity: json });
2014
+ return false;
2015
+ }
2016
+ const fetched = await this.lookupObject(activity.id, options);
2017
+ if (fetched == null) {
2018
+ logger.debug("Failed to fetch the remote activity object {activityId}.", { recipient, activity: json, activityId: activity.id.href });
2019
+ return false;
2020
+ }
2021
+ else if (!(fetched instanceof Activity)) {
2022
+ logger.debug("Fetched object is not an Activity.", { recipient, activity: await fetched.toJsonLd({ contextLoader }) });
2023
+ return false;
2024
+ }
2025
+ else if (fetched.id?.href !== activity.id.href) {
2026
+ logger.debug("Fetched activity object has a different ID; failed to verify.", { recipient, activity: await fetched.toJsonLd({ contextLoader }) });
2027
+ return false;
2028
+ }
2029
+ else if (fetched.actorIds.length < 1) {
2030
+ logger.debug("Fetched activity object is missing an actor; unable to verify.", { recipient, activity: await fetched.toJsonLd({ contextLoader }) });
2031
+ return false;
2032
+ }
2033
+ const activityId = fetched.id;
2034
+ if (!fetched.actorIds.every((actor) => actor.origin === activityId.origin)) {
2035
+ logger.debug("Fetched activity object has actors from different origins; " +
2036
+ "unable to verify.", { recipient, activity: await fetched.toJsonLd({ contextLoader }) });
2037
+ return false;
2038
+ }
2039
+ logger.debug("Successfully fetched the remote activity object {activityId}; " +
2040
+ "ignore the original activity and use the fetched one, which is trustworthy.");
2041
+ activity = fetched;
2042
+ }
2043
+ else {
2044
+ logger.debug("Object Integrity Proofs are verified.", { recipient, activity: json });
2045
+ }
2046
+ const routeResult = await routeActivity({
2047
+ context: this,
2048
+ json,
2049
+ activity,
2050
+ recipient,
2051
+ inboxListeners: this.federation.inboxListeners,
2052
+ inboxContextFactory: this.toInboxContext.bind(this),
2053
+ inboxErrorHandler: this.federation.inboxErrorHandler,
2054
+ kv: this.federation.kv,
2055
+ kvPrefixes: this.federation.kvPrefixes,
2056
+ queue: this.federation.inboxQueue,
2057
+ span,
2058
+ tracerProvider: options.tracerProvider ?? this.tracerProvider,
2059
+ });
2060
+ return routeResult === "alreadyProcessed" || routeResult === "enqueued" ||
2061
+ routeResult === "unsupportedActivity" || routeResult === "success";
2062
+ }
1949
2063
  }
1950
2064
  class RequestContextImpl extends ContextImpl {
1951
2065
  #invokedFromActorDispatcher;
@@ -0,0 +1,6 @@
1
+ {
2
+ "@context": "https://www.w3.org/ns/activitystreams",
3
+ "type": "Create",
4
+ "id": "https://example.com/create",
5
+ "actor": "https://example.com/person"
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "@context": "https://www.w3.org/ns/activitystreams",
3
+ "type": "Create",
4
+ "id": "https://example.com/cross-origin-actor",
5
+ "actor": "https://cross-origin.com/actor"
6
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "@context": "https://www.w3.org/ns/activitystreams",
3
+ "type": "Invite",
4
+ "id": "https://example.com/invite",
5
+ "actor": "https://example.com/person",
6
+ "object": "https://example.com/object"
7
+ }