@fedify/vocab 2.3.0-dev.1137 → 2.3.0-dev.1145
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/deno.json +1 -1
- package/dist/mod.cjs +71 -7
- package/dist/mod.d.cts +31 -1
- package/dist/mod.d.ts +31 -1
- package/dist/mod.js +71 -7
- package/dist-tests/{actor-COjhyCpy.mjs → actor-B38UQKg6.mjs} +70 -7
- package/dist-tests/actor.test.mjs +99 -2
- package/dist-tests/lookup.test.mjs +23 -1
- package/package.json +4 -4
- package/src/actor.test.ts +197 -1
- package/src/actor.ts +148 -6
- package/src/lookup.test.ts +36 -0
- package/src/lookup.ts +1 -0
package/deno.json
CHANGED
package/dist/mod.cjs
CHANGED
|
@@ -33,7 +33,7 @@ let _fedify_vocab_runtime_temporal = require("@fedify/vocab-runtime/temporal");
|
|
|
33
33
|
let es_toolkit = require("es-toolkit");
|
|
34
34
|
//#region deno.json
|
|
35
35
|
var name = "@fedify/vocab";
|
|
36
|
-
var version = "2.3.0-dev.
|
|
36
|
+
var version = "2.3.0-dev.1145+3fe78022";
|
|
37
37
|
//#endregion
|
|
38
38
|
//#region src/type.ts
|
|
39
39
|
function getTypeId(object) {
|
|
@@ -47333,6 +47333,53 @@ function getEntityTypeById(id) {
|
|
|
47333
47333
|
}
|
|
47334
47334
|
//#endregion
|
|
47335
47335
|
//#region src/actor.ts
|
|
47336
|
+
const ACTOR_DISCOVERY_HISTOGRAM_BUCKETS = [
|
|
47337
|
+
5,
|
|
47338
|
+
10,
|
|
47339
|
+
25,
|
|
47340
|
+
50,
|
|
47341
|
+
75,
|
|
47342
|
+
100,
|
|
47343
|
+
250,
|
|
47344
|
+
500,
|
|
47345
|
+
750,
|
|
47346
|
+
1e3,
|
|
47347
|
+
2500,
|
|
47348
|
+
5e3,
|
|
47349
|
+
7500,
|
|
47350
|
+
1e4
|
|
47351
|
+
];
|
|
47352
|
+
const actorDiscoveryInstruments = /* @__PURE__ */ new WeakMap();
|
|
47353
|
+
function getActorDiscoveryInstruments(meterProvider) {
|
|
47354
|
+
let instruments = actorDiscoveryInstruments.get(meterProvider);
|
|
47355
|
+
if (instruments == null) {
|
|
47356
|
+
const meter = meterProvider.getMeter(name, version);
|
|
47357
|
+
instruments = {
|
|
47358
|
+
discovery: meter.createCounter("activitypub.actor.discovery", {
|
|
47359
|
+
description: "Actor handle discovery attempts via getActorHandle(), classified by terminal outcome.",
|
|
47360
|
+
unit: "{discovery}"
|
|
47361
|
+
}),
|
|
47362
|
+
discoveryDuration: meter.createHistogram("activitypub.actor.discovery.duration", {
|
|
47363
|
+
description: "Duration of getActorHandle() actor discovery calls.",
|
|
47364
|
+
unit: "ms",
|
|
47365
|
+
advice: { explicitBucketBoundaries: [...ACTOR_DISCOVERY_HISTOGRAM_BUCKETS] }
|
|
47366
|
+
})
|
|
47367
|
+
};
|
|
47368
|
+
actorDiscoveryInstruments.set(meterProvider, instruments);
|
|
47369
|
+
}
|
|
47370
|
+
return instruments;
|
|
47371
|
+
}
|
|
47372
|
+
function getActorDiscoveryRemoteHost(actor) {
|
|
47373
|
+
const id = actor instanceof URL ? actor : actor.id;
|
|
47374
|
+
if (id == null) return void 0;
|
|
47375
|
+
return id.hostname === "" ? void 0 : id.hostname;
|
|
47376
|
+
}
|
|
47377
|
+
var ActorHandleNotFoundError = class extends TypeError {
|
|
47378
|
+
constructor() {
|
|
47379
|
+
super("Actor does not have enough information to get the handle.");
|
|
47380
|
+
this.name = "ActorHandleNotFoundError";
|
|
47381
|
+
}
|
|
47382
|
+
};
|
|
47336
47383
|
/**
|
|
47337
47384
|
* Checks if the given object is an {@link Actor}.
|
|
47338
47385
|
* @param object The object to check.
|
|
@@ -47399,15 +47446,29 @@ async function getActorHandle(actor, options = {}) {
|
|
|
47399
47446
|
if (actor.id != null) span.setAttribute("activitypub.actor.id", actor.id.href);
|
|
47400
47447
|
span.setAttribute("activitypub.actor.type", getTypeId(actor).href);
|
|
47401
47448
|
}
|
|
47449
|
+
const meterProvider = options.meterProvider;
|
|
47450
|
+
const start = meterProvider == null ? 0 : performance.now();
|
|
47451
|
+
let result = "error";
|
|
47402
47452
|
try {
|
|
47403
|
-
|
|
47453
|
+
const handle = await getActorHandleInternal(actor, options);
|
|
47454
|
+
result = "resolved";
|
|
47455
|
+
return handle;
|
|
47404
47456
|
} catch (error) {
|
|
47457
|
+
result = error instanceof ActorHandleNotFoundError ? "not_found" : "error";
|
|
47405
47458
|
span.setStatus({
|
|
47406
47459
|
code: _opentelemetry_api.SpanStatusCode.ERROR,
|
|
47407
47460
|
message: String(error)
|
|
47408
47461
|
});
|
|
47409
47462
|
throw error;
|
|
47410
47463
|
} finally {
|
|
47464
|
+
if (meterProvider != null) {
|
|
47465
|
+
const attributes = { "activitypub.actor.discovery.result": result };
|
|
47466
|
+
const host = getActorDiscoveryRemoteHost(actor);
|
|
47467
|
+
if (host != null) attributes["activitypub.remote.host"] = host;
|
|
47468
|
+
const instruments = getActorDiscoveryInstruments(meterProvider);
|
|
47469
|
+
instruments.discovery.add(1, attributes);
|
|
47470
|
+
instruments.discoveryDuration.record(Math.max(0, performance.now() - start), attributes);
|
|
47471
|
+
}
|
|
47411
47472
|
span.end();
|
|
47412
47473
|
}
|
|
47413
47474
|
});
|
|
@@ -47417,7 +47478,8 @@ async function getActorHandleInternal(actor, options = {}) {
|
|
|
47417
47478
|
if (actorId != null) {
|
|
47418
47479
|
const result = await (0, _fedify_webfinger.lookupWebFinger)(actorId, {
|
|
47419
47480
|
userAgent: options.userAgent,
|
|
47420
|
-
tracerProvider: options.tracerProvider
|
|
47481
|
+
tracerProvider: options.tracerProvider,
|
|
47482
|
+
meterProvider: options.meterProvider
|
|
47421
47483
|
});
|
|
47422
47484
|
if (result != null) {
|
|
47423
47485
|
const aliases = [...result.aliases ?? []];
|
|
@@ -47425,19 +47487,20 @@ async function getActorHandleInternal(actor, options = {}) {
|
|
|
47425
47487
|
for (const alias of aliases) {
|
|
47426
47488
|
const match = alias.match(/^acct:([^@]+)@([^@]+)$/);
|
|
47427
47489
|
if (match != null) {
|
|
47428
|
-
if (new URL(`https://${match[2]}/`).hostname !== actorId.hostname && !await verifyCrossOriginActorHandle(actorId.href, alias, options.userAgent, options.tracerProvider)) continue;
|
|
47490
|
+
if (new URL(`https://${match[2]}/`).hostname !== actorId.hostname && !await verifyCrossOriginActorHandle(actorId.href, alias, options.userAgent, options.tracerProvider, options.meterProvider)) continue;
|
|
47429
47491
|
return normalizeActorHandle(`@${match[1]}@${match[2]}`, options);
|
|
47430
47492
|
}
|
|
47431
47493
|
}
|
|
47432
47494
|
}
|
|
47433
47495
|
}
|
|
47434
47496
|
if (!(actor instanceof URL) && actor.preferredUsername != null && actor.id != null) return normalizeActorHandle(`@${actor.preferredUsername}@${actor.id.host}`, options);
|
|
47435
|
-
throw new
|
|
47497
|
+
throw new ActorHandleNotFoundError();
|
|
47436
47498
|
}
|
|
47437
|
-
async function verifyCrossOriginActorHandle(actorId, alias, userAgent, tracerProvider) {
|
|
47499
|
+
async function verifyCrossOriginActorHandle(actorId, alias, userAgent, tracerProvider, meterProvider) {
|
|
47438
47500
|
const response = await (0, _fedify_webfinger.lookupWebFinger)(alias, {
|
|
47439
47501
|
userAgent,
|
|
47440
|
-
tracerProvider
|
|
47502
|
+
tracerProvider,
|
|
47503
|
+
meterProvider
|
|
47441
47504
|
});
|
|
47442
47505
|
if (response == null) return false;
|
|
47443
47506
|
for (const alias of response.aliases ?? []) if (new URL(alias).href === actorId) return true;
|
|
@@ -47671,6 +47734,7 @@ async function lookupObjectInternal(identifier, options = {}) {
|
|
|
47671
47734
|
const jrd = await (0, _fedify_webfinger.lookupWebFinger)(identifier, {
|
|
47672
47735
|
userAgent: options.userAgent,
|
|
47673
47736
|
tracerProvider: options.tracerProvider,
|
|
47737
|
+
meterProvider: options.meterProvider,
|
|
47674
47738
|
allowPrivateAddress: "allowPrivateAddress" in options && options.allowPrivateAddress === true,
|
|
47675
47739
|
signal: options.signal
|
|
47676
47740
|
});
|
package/dist/mod.d.cts
CHANGED
|
@@ -18088,6 +18088,24 @@ declare function getEntityTypeById(id: string | URL): $EntityType | undefined;
|
|
|
18088
18088
|
//#endregion
|
|
18089
18089
|
//#region src/actor.d.ts
|
|
18090
18090
|
/**
|
|
18091
|
+
* The terminal classification of a {@link getActorHandle} call, recorded as
|
|
18092
|
+
* the `activitypub.actor.discovery.result` attribute on the
|
|
18093
|
+
* `activitypub.actor.discovery` counter and
|
|
18094
|
+
* `activitypub.actor.discovery.duration` histogram.
|
|
18095
|
+
*
|
|
18096
|
+
* - `resolved`: a handle was returned to the caller.
|
|
18097
|
+
* - `not_found`: WebFinger did not yield a usable `acct:` alias and the
|
|
18098
|
+
* `preferredUsername` fallback could not run. This corresponds to the
|
|
18099
|
+
* `TypeError("Actor does not have enough information…")` path.
|
|
18100
|
+
* - `error`: any other thrown exception bubbled up from the lookup.
|
|
18101
|
+
*
|
|
18102
|
+
* Per-WebFinger-call failure detail (HTTP status, parse failure, network
|
|
18103
|
+
* failure, etc.) lives on `webfinger.lookup` and is intentionally not
|
|
18104
|
+
* duplicated here.
|
|
18105
|
+
* @since 2.3.0
|
|
18106
|
+
*/
|
|
18107
|
+
type ActorDiscoveryResult = "resolved" | "not_found" | "error";
|
|
18108
|
+
/**
|
|
18091
18109
|
* Actor types are {@link Object} types that are capable of performing
|
|
18092
18110
|
* activities.
|
|
18093
18111
|
*/
|
|
@@ -18133,6 +18151,18 @@ interface GetActorHandleOptions extends NormalizeActorHandleOptions {
|
|
|
18133
18151
|
* @since 1.3.0
|
|
18134
18152
|
*/
|
|
18135
18153
|
tracerProvider?: TracerProvider;
|
|
18154
|
+
/**
|
|
18155
|
+
* The OpenTelemetry meter provider used to record the
|
|
18156
|
+
* `activitypub.actor.discovery` counter and
|
|
18157
|
+
* `activitypub.actor.discovery.duration` histogram. When set, the same
|
|
18158
|
+
* meter provider is also forwarded to the nested WebFinger lookups so
|
|
18159
|
+
* each discovery emits both the actor-discovery measurements and the
|
|
18160
|
+
* underlying `webfinger.lookup` measurements. If omitted, no
|
|
18161
|
+
* measurements are emitted (the helper is opt-in to avoid touching the
|
|
18162
|
+
* global meter provider for callers that do not use OpenTelemetry).
|
|
18163
|
+
* @since 2.3.0
|
|
18164
|
+
*/
|
|
18165
|
+
meterProvider?: MeterProvider;
|
|
18136
18166
|
}
|
|
18137
18167
|
/**
|
|
18138
18168
|
* Gets the actor handle, of the form `@username@domain`, from the given actor
|
|
@@ -18531,4 +18561,4 @@ declare function getTypeId(object: Object$1 | Link | null): URL | null;
|
|
|
18531
18561
|
*/
|
|
18532
18562
|
declare function getTypeId(object: Object$1 | Link | null | undefined): URL | null | undefined;
|
|
18533
18563
|
//#endregion
|
|
18534
|
-
export { $EntityType, Accept, Activity, Actor, ActorTypeName, Add, Announce, AnnounceAuthorization, AnnounceRequest, Application, Arrive, Article, Audio, Block, ChatMessage, Collection, CollectionPage, Create, CryptographicKey, DataIntegrityProof, Delete, DidService, Dislike, Document, type DocumentLoader, Emoji, EmojiReact, Endpoints, Event, Export, FediverseHandle, Flag, Follow, GetActorHandleOptions, type GetUserAgentOptions, Group, Hashtag, Ignore, Image, Intent, InteractionPolicy, InteractionRule, IntransitiveActivity, Invite, Join, LanguageString, Leave, Like, LikeAuthorization, LikeRequest, Link, Listen, LookupObjectOptions, Measure, Mention, Move, Multikey, NormalizeActorHandleOptions, Note, Object$1 as Object, ObjectLookupKind, Offer, OrderedCollection, OrderedCollectionPage, Organization, PUBLIC_COLLECTION, Page, Person, Place, Profile, PropertyValue, Proposal, Question, QuoteAuthorization, QuoteRequest, Read, Recipient, Reject, Relationship, type RemoteDocument, Remove, ReplyAuthorization, ReplyRequest, Service, Source, TentativeAccept, TentativeReject, Tombstone, Travel, TraverseCollectionOptions, Undo, Update, Video, View, getActorClassByTypeName, getActorHandle, getActorTypeName, getEntityTypeById, getTypeId, isActor, isEntityType, isFediverseHandle, lookupObject, normalizeActorHandle, parseFediverseHandle, toAcctUrl, traverseCollection };
|
|
18564
|
+
export { $EntityType, Accept, Activity, Actor, ActorDiscoveryResult, ActorTypeName, Add, Announce, AnnounceAuthorization, AnnounceRequest, Application, Arrive, Article, Audio, Block, ChatMessage, Collection, CollectionPage, Create, CryptographicKey, DataIntegrityProof, Delete, DidService, Dislike, Document, type DocumentLoader, Emoji, EmojiReact, Endpoints, Event, Export, FediverseHandle, Flag, Follow, GetActorHandleOptions, type GetUserAgentOptions, Group, Hashtag, Ignore, Image, Intent, InteractionPolicy, InteractionRule, IntransitiveActivity, Invite, Join, LanguageString, Leave, Like, LikeAuthorization, LikeRequest, Link, Listen, LookupObjectOptions, Measure, Mention, Move, Multikey, NormalizeActorHandleOptions, Note, Object$1 as Object, ObjectLookupKind, Offer, OrderedCollection, OrderedCollectionPage, Organization, PUBLIC_COLLECTION, Page, Person, Place, Profile, PropertyValue, Proposal, Question, QuoteAuthorization, QuoteRequest, Read, Recipient, Reject, Relationship, type RemoteDocument, Remove, ReplyAuthorization, ReplyRequest, Service, Source, TentativeAccept, TentativeReject, Tombstone, Travel, TraverseCollectionOptions, Undo, Update, Video, View, getActorClassByTypeName, getActorHandle, getActorTypeName, getEntityTypeById, getTypeId, isActor, isEntityType, isFediverseHandle, lookupObject, normalizeActorHandle, parseFediverseHandle, toAcctUrl, traverseCollection };
|
package/dist/mod.d.ts
CHANGED
|
@@ -18088,6 +18088,24 @@ declare function getEntityTypeById(id: string | URL): $EntityType | undefined;
|
|
|
18088
18088
|
//#endregion
|
|
18089
18089
|
//#region src/actor.d.ts
|
|
18090
18090
|
/**
|
|
18091
|
+
* The terminal classification of a {@link getActorHandle} call, recorded as
|
|
18092
|
+
* the `activitypub.actor.discovery.result` attribute on the
|
|
18093
|
+
* `activitypub.actor.discovery` counter and
|
|
18094
|
+
* `activitypub.actor.discovery.duration` histogram.
|
|
18095
|
+
*
|
|
18096
|
+
* - `resolved`: a handle was returned to the caller.
|
|
18097
|
+
* - `not_found`: WebFinger did not yield a usable `acct:` alias and the
|
|
18098
|
+
* `preferredUsername` fallback could not run. This corresponds to the
|
|
18099
|
+
* `TypeError("Actor does not have enough information…")` path.
|
|
18100
|
+
* - `error`: any other thrown exception bubbled up from the lookup.
|
|
18101
|
+
*
|
|
18102
|
+
* Per-WebFinger-call failure detail (HTTP status, parse failure, network
|
|
18103
|
+
* failure, etc.) lives on `webfinger.lookup` and is intentionally not
|
|
18104
|
+
* duplicated here.
|
|
18105
|
+
* @since 2.3.0
|
|
18106
|
+
*/
|
|
18107
|
+
type ActorDiscoveryResult = "resolved" | "not_found" | "error";
|
|
18108
|
+
/**
|
|
18091
18109
|
* Actor types are {@link Object} types that are capable of performing
|
|
18092
18110
|
* activities.
|
|
18093
18111
|
*/
|
|
@@ -18133,6 +18151,18 @@ interface GetActorHandleOptions extends NormalizeActorHandleOptions {
|
|
|
18133
18151
|
* @since 1.3.0
|
|
18134
18152
|
*/
|
|
18135
18153
|
tracerProvider?: TracerProvider;
|
|
18154
|
+
/**
|
|
18155
|
+
* The OpenTelemetry meter provider used to record the
|
|
18156
|
+
* `activitypub.actor.discovery` counter and
|
|
18157
|
+
* `activitypub.actor.discovery.duration` histogram. When set, the same
|
|
18158
|
+
* meter provider is also forwarded to the nested WebFinger lookups so
|
|
18159
|
+
* each discovery emits both the actor-discovery measurements and the
|
|
18160
|
+
* underlying `webfinger.lookup` measurements. If omitted, no
|
|
18161
|
+
* measurements are emitted (the helper is opt-in to avoid touching the
|
|
18162
|
+
* global meter provider for callers that do not use OpenTelemetry).
|
|
18163
|
+
* @since 2.3.0
|
|
18164
|
+
*/
|
|
18165
|
+
meterProvider?: MeterProvider;
|
|
18136
18166
|
}
|
|
18137
18167
|
/**
|
|
18138
18168
|
* Gets the actor handle, of the form `@username@domain`, from the given actor
|
|
@@ -18531,4 +18561,4 @@ declare function getTypeId(object: Object$1 | Link | null): URL | null;
|
|
|
18531
18561
|
*/
|
|
18532
18562
|
declare function getTypeId(object: Object$1 | Link | null | undefined): URL | null | undefined;
|
|
18533
18563
|
//#endregion
|
|
18534
|
-
export { $EntityType, Accept, Activity, Actor, ActorTypeName, Add, Announce, AnnounceAuthorization, AnnounceRequest, Application, Arrive, Article, Audio, Block, ChatMessage, Collection, CollectionPage, Create, CryptographicKey, DataIntegrityProof, Delete, DidService, Dislike, Document, type DocumentLoader, Emoji, EmojiReact, Endpoints, Event, Export, FediverseHandle, Flag, Follow, GetActorHandleOptions, type GetUserAgentOptions, Group, Hashtag, Ignore, Image, Intent, InteractionPolicy, InteractionRule, IntransitiveActivity, Invite, Join, LanguageString, Leave, Like, LikeAuthorization, LikeRequest, Link, Listen, LookupObjectOptions, Measure, Mention, Move, Multikey, NormalizeActorHandleOptions, Note, Object$1 as Object, ObjectLookupKind, Offer, OrderedCollection, OrderedCollectionPage, Organization, PUBLIC_COLLECTION, Page, Person, Place, Profile, PropertyValue, Proposal, Question, QuoteAuthorization, QuoteRequest, Read, Recipient, Reject, Relationship, type RemoteDocument, Remove, ReplyAuthorization, ReplyRequest, Service, Source, TentativeAccept, TentativeReject, Tombstone, Travel, TraverseCollectionOptions, Undo, Update, Video, View, getActorClassByTypeName, getActorHandle, getActorTypeName, getEntityTypeById, getTypeId, isActor, isEntityType, isFediverseHandle, lookupObject, normalizeActorHandle, parseFediverseHandle, toAcctUrl, traverseCollection };
|
|
18564
|
+
export { $EntityType, Accept, Activity, Actor, ActorDiscoveryResult, ActorTypeName, Add, Announce, AnnounceAuthorization, AnnounceRequest, Application, Arrive, Article, Audio, Block, ChatMessage, Collection, CollectionPage, Create, CryptographicKey, DataIntegrityProof, Delete, DidService, Dislike, Document, type DocumentLoader, Emoji, EmojiReact, Endpoints, Event, Export, FediverseHandle, Flag, Follow, GetActorHandleOptions, type GetUserAgentOptions, Group, Hashtag, Ignore, Image, Intent, InteractionPolicy, InteractionRule, IntransitiveActivity, Invite, Join, LanguageString, Leave, Like, LikeAuthorization, LikeRequest, Link, Listen, LookupObjectOptions, Measure, Mention, Move, Multikey, NormalizeActorHandleOptions, Note, Object$1 as Object, ObjectLookupKind, Offer, OrderedCollection, OrderedCollectionPage, Organization, PUBLIC_COLLECTION, Page, Person, Place, Profile, PropertyValue, Proposal, Question, QuoteAuthorization, QuoteRequest, Read, Recipient, Reject, Relationship, type RemoteDocument, Remove, ReplyAuthorization, ReplyRequest, Service, Source, TentativeAccept, TentativeReject, Tombstone, Travel, TraverseCollectionOptions, Undo, Update, Video, View, getActorClassByTypeName, getActorHandle, getActorTypeName, getEntityTypeById, getTypeId, isActor, isEntityType, isFediverseHandle, lookupObject, normalizeActorHandle, parseFediverseHandle, toAcctUrl, traverseCollection };
|
package/dist/mod.js
CHANGED
|
@@ -9,7 +9,7 @@ import { isTemporalDuration, isTemporalInstant } from "@fedify/vocab-runtime/tem
|
|
|
9
9
|
import { delay } from "es-toolkit";
|
|
10
10
|
//#region deno.json
|
|
11
11
|
var name = "@fedify/vocab";
|
|
12
|
-
var version = "2.3.0-dev.
|
|
12
|
+
var version = "2.3.0-dev.1145+3fe78022";
|
|
13
13
|
//#endregion
|
|
14
14
|
//#region src/type.ts
|
|
15
15
|
function getTypeId(object) {
|
|
@@ -47309,6 +47309,53 @@ function getEntityTypeById(id) {
|
|
|
47309
47309
|
}
|
|
47310
47310
|
//#endregion
|
|
47311
47311
|
//#region src/actor.ts
|
|
47312
|
+
const ACTOR_DISCOVERY_HISTOGRAM_BUCKETS = [
|
|
47313
|
+
5,
|
|
47314
|
+
10,
|
|
47315
|
+
25,
|
|
47316
|
+
50,
|
|
47317
|
+
75,
|
|
47318
|
+
100,
|
|
47319
|
+
250,
|
|
47320
|
+
500,
|
|
47321
|
+
750,
|
|
47322
|
+
1e3,
|
|
47323
|
+
2500,
|
|
47324
|
+
5e3,
|
|
47325
|
+
7500,
|
|
47326
|
+
1e4
|
|
47327
|
+
];
|
|
47328
|
+
const actorDiscoveryInstruments = /* @__PURE__ */ new WeakMap();
|
|
47329
|
+
function getActorDiscoveryInstruments(meterProvider) {
|
|
47330
|
+
let instruments = actorDiscoveryInstruments.get(meterProvider);
|
|
47331
|
+
if (instruments == null) {
|
|
47332
|
+
const meter = meterProvider.getMeter(name, version);
|
|
47333
|
+
instruments = {
|
|
47334
|
+
discovery: meter.createCounter("activitypub.actor.discovery", {
|
|
47335
|
+
description: "Actor handle discovery attempts via getActorHandle(), classified by terminal outcome.",
|
|
47336
|
+
unit: "{discovery}"
|
|
47337
|
+
}),
|
|
47338
|
+
discoveryDuration: meter.createHistogram("activitypub.actor.discovery.duration", {
|
|
47339
|
+
description: "Duration of getActorHandle() actor discovery calls.",
|
|
47340
|
+
unit: "ms",
|
|
47341
|
+
advice: { explicitBucketBoundaries: [...ACTOR_DISCOVERY_HISTOGRAM_BUCKETS] }
|
|
47342
|
+
})
|
|
47343
|
+
};
|
|
47344
|
+
actorDiscoveryInstruments.set(meterProvider, instruments);
|
|
47345
|
+
}
|
|
47346
|
+
return instruments;
|
|
47347
|
+
}
|
|
47348
|
+
function getActorDiscoveryRemoteHost(actor) {
|
|
47349
|
+
const id = actor instanceof URL ? actor : actor.id;
|
|
47350
|
+
if (id == null) return void 0;
|
|
47351
|
+
return id.hostname === "" ? void 0 : id.hostname;
|
|
47352
|
+
}
|
|
47353
|
+
var ActorHandleNotFoundError = class extends TypeError {
|
|
47354
|
+
constructor() {
|
|
47355
|
+
super("Actor does not have enough information to get the handle.");
|
|
47356
|
+
this.name = "ActorHandleNotFoundError";
|
|
47357
|
+
}
|
|
47358
|
+
};
|
|
47312
47359
|
/**
|
|
47313
47360
|
* Checks if the given object is an {@link Actor}.
|
|
47314
47361
|
* @param object The object to check.
|
|
@@ -47375,15 +47422,29 @@ async function getActorHandle(actor, options = {}) {
|
|
|
47375
47422
|
if (actor.id != null) span.setAttribute("activitypub.actor.id", actor.id.href);
|
|
47376
47423
|
span.setAttribute("activitypub.actor.type", getTypeId(actor).href);
|
|
47377
47424
|
}
|
|
47425
|
+
const meterProvider = options.meterProvider;
|
|
47426
|
+
const start = meterProvider == null ? 0 : performance.now();
|
|
47427
|
+
let result = "error";
|
|
47378
47428
|
try {
|
|
47379
|
-
|
|
47429
|
+
const handle = await getActorHandleInternal(actor, options);
|
|
47430
|
+
result = "resolved";
|
|
47431
|
+
return handle;
|
|
47380
47432
|
} catch (error) {
|
|
47433
|
+
result = error instanceof ActorHandleNotFoundError ? "not_found" : "error";
|
|
47381
47434
|
span.setStatus({
|
|
47382
47435
|
code: SpanStatusCode.ERROR,
|
|
47383
47436
|
message: String(error)
|
|
47384
47437
|
});
|
|
47385
47438
|
throw error;
|
|
47386
47439
|
} finally {
|
|
47440
|
+
if (meterProvider != null) {
|
|
47441
|
+
const attributes = { "activitypub.actor.discovery.result": result };
|
|
47442
|
+
const host = getActorDiscoveryRemoteHost(actor);
|
|
47443
|
+
if (host != null) attributes["activitypub.remote.host"] = host;
|
|
47444
|
+
const instruments = getActorDiscoveryInstruments(meterProvider);
|
|
47445
|
+
instruments.discovery.add(1, attributes);
|
|
47446
|
+
instruments.discoveryDuration.record(Math.max(0, performance.now() - start), attributes);
|
|
47447
|
+
}
|
|
47387
47448
|
span.end();
|
|
47388
47449
|
}
|
|
47389
47450
|
});
|
|
@@ -47393,7 +47454,8 @@ async function getActorHandleInternal(actor, options = {}) {
|
|
|
47393
47454
|
if (actorId != null) {
|
|
47394
47455
|
const result = await lookupWebFinger(actorId, {
|
|
47395
47456
|
userAgent: options.userAgent,
|
|
47396
|
-
tracerProvider: options.tracerProvider
|
|
47457
|
+
tracerProvider: options.tracerProvider,
|
|
47458
|
+
meterProvider: options.meterProvider
|
|
47397
47459
|
});
|
|
47398
47460
|
if (result != null) {
|
|
47399
47461
|
const aliases = [...result.aliases ?? []];
|
|
@@ -47401,19 +47463,20 @@ async function getActorHandleInternal(actor, options = {}) {
|
|
|
47401
47463
|
for (const alias of aliases) {
|
|
47402
47464
|
const match = alias.match(/^acct:([^@]+)@([^@]+)$/);
|
|
47403
47465
|
if (match != null) {
|
|
47404
|
-
if (new URL(`https://${match[2]}/`).hostname !== actorId.hostname && !await verifyCrossOriginActorHandle(actorId.href, alias, options.userAgent, options.tracerProvider)) continue;
|
|
47466
|
+
if (new URL(`https://${match[2]}/`).hostname !== actorId.hostname && !await verifyCrossOriginActorHandle(actorId.href, alias, options.userAgent, options.tracerProvider, options.meterProvider)) continue;
|
|
47405
47467
|
return normalizeActorHandle(`@${match[1]}@${match[2]}`, options);
|
|
47406
47468
|
}
|
|
47407
47469
|
}
|
|
47408
47470
|
}
|
|
47409
47471
|
}
|
|
47410
47472
|
if (!(actor instanceof URL) && actor.preferredUsername != null && actor.id != null) return normalizeActorHandle(`@${actor.preferredUsername}@${actor.id.host}`, options);
|
|
47411
|
-
throw new
|
|
47473
|
+
throw new ActorHandleNotFoundError();
|
|
47412
47474
|
}
|
|
47413
|
-
async function verifyCrossOriginActorHandle(actorId, alias, userAgent, tracerProvider) {
|
|
47475
|
+
async function verifyCrossOriginActorHandle(actorId, alias, userAgent, tracerProvider, meterProvider) {
|
|
47414
47476
|
const response = await lookupWebFinger(alias, {
|
|
47415
47477
|
userAgent,
|
|
47416
|
-
tracerProvider
|
|
47478
|
+
tracerProvider,
|
|
47479
|
+
meterProvider
|
|
47417
47480
|
});
|
|
47418
47481
|
if (response == null) return false;
|
|
47419
47482
|
for (const alias of response.aliases ?? []) if (new URL(alias).href === actorId) return true;
|
|
@@ -47647,6 +47710,7 @@ async function lookupObjectInternal(identifier, options = {}) {
|
|
|
47647
47710
|
const jrd = await lookupWebFinger(identifier, {
|
|
47648
47711
|
userAgent: options.userAgent,
|
|
47649
47712
|
tracerProvider: options.tracerProvider,
|
|
47713
|
+
meterProvider: options.meterProvider,
|
|
47650
47714
|
allowPrivateAddress: "allowPrivateAddress" in options && options.allowPrivateAddress === true,
|
|
47651
47715
|
signal: options.signal
|
|
47652
47716
|
});
|
|
@@ -1206,9 +1206,56 @@ var esm_default = new class FetchMock {
|
|
|
1206
1206
|
//#endregion
|
|
1207
1207
|
//#region deno.json
|
|
1208
1208
|
var name = "@fedify/vocab";
|
|
1209
|
-
var version = "2.3.0-dev.
|
|
1209
|
+
var version = "2.3.0-dev.1145+3fe78022";
|
|
1210
1210
|
//#endregion
|
|
1211
1211
|
//#region src/actor.ts
|
|
1212
|
+
const ACTOR_DISCOVERY_HISTOGRAM_BUCKETS = [
|
|
1213
|
+
5,
|
|
1214
|
+
10,
|
|
1215
|
+
25,
|
|
1216
|
+
50,
|
|
1217
|
+
75,
|
|
1218
|
+
100,
|
|
1219
|
+
250,
|
|
1220
|
+
500,
|
|
1221
|
+
750,
|
|
1222
|
+
1e3,
|
|
1223
|
+
2500,
|
|
1224
|
+
5e3,
|
|
1225
|
+
7500,
|
|
1226
|
+
1e4
|
|
1227
|
+
];
|
|
1228
|
+
const actorDiscoveryInstruments = /* @__PURE__ */ new WeakMap();
|
|
1229
|
+
function getActorDiscoveryInstruments(meterProvider) {
|
|
1230
|
+
let instruments = actorDiscoveryInstruments.get(meterProvider);
|
|
1231
|
+
if (instruments == null) {
|
|
1232
|
+
const meter = meterProvider.getMeter(name, version);
|
|
1233
|
+
instruments = {
|
|
1234
|
+
discovery: meter.createCounter("activitypub.actor.discovery", {
|
|
1235
|
+
description: "Actor handle discovery attempts via getActorHandle(), classified by terminal outcome.",
|
|
1236
|
+
unit: "{discovery}"
|
|
1237
|
+
}),
|
|
1238
|
+
discoveryDuration: meter.createHistogram("activitypub.actor.discovery.duration", {
|
|
1239
|
+
description: "Duration of getActorHandle() actor discovery calls.",
|
|
1240
|
+
unit: "ms",
|
|
1241
|
+
advice: { explicitBucketBoundaries: [...ACTOR_DISCOVERY_HISTOGRAM_BUCKETS] }
|
|
1242
|
+
})
|
|
1243
|
+
};
|
|
1244
|
+
actorDiscoveryInstruments.set(meterProvider, instruments);
|
|
1245
|
+
}
|
|
1246
|
+
return instruments;
|
|
1247
|
+
}
|
|
1248
|
+
function getActorDiscoveryRemoteHost(actor) {
|
|
1249
|
+
const id = actor instanceof URL ? actor : actor.id;
|
|
1250
|
+
if (id == null) return void 0;
|
|
1251
|
+
return id.hostname === "" ? void 0 : id.hostname;
|
|
1252
|
+
}
|
|
1253
|
+
var ActorHandleNotFoundError = class extends TypeError {
|
|
1254
|
+
constructor() {
|
|
1255
|
+
super("Actor does not have enough information to get the handle.");
|
|
1256
|
+
this.name = "ActorHandleNotFoundError";
|
|
1257
|
+
}
|
|
1258
|
+
};
|
|
1212
1259
|
/**
|
|
1213
1260
|
* Checks if the given object is an {@link Actor}.
|
|
1214
1261
|
* @param object The object to check.
|
|
@@ -1275,15 +1322,29 @@ async function getActorHandle(actor, options = {}) {
|
|
|
1275
1322
|
if (actor.id != null) span.setAttribute("activitypub.actor.id", actor.id.href);
|
|
1276
1323
|
span.setAttribute("activitypub.actor.type", getTypeId(actor).href);
|
|
1277
1324
|
}
|
|
1325
|
+
const meterProvider = options.meterProvider;
|
|
1326
|
+
const start = meterProvider == null ? 0 : performance.now();
|
|
1327
|
+
let result = "error";
|
|
1278
1328
|
try {
|
|
1279
|
-
|
|
1329
|
+
const handle = await getActorHandleInternal(actor, options);
|
|
1330
|
+
result = "resolved";
|
|
1331
|
+
return handle;
|
|
1280
1332
|
} catch (error) {
|
|
1333
|
+
result = error instanceof ActorHandleNotFoundError ? "not_found" : "error";
|
|
1281
1334
|
span.setStatus({
|
|
1282
1335
|
code: SpanStatusCode.ERROR,
|
|
1283
1336
|
message: String(error)
|
|
1284
1337
|
});
|
|
1285
1338
|
throw error;
|
|
1286
1339
|
} finally {
|
|
1340
|
+
if (meterProvider != null) {
|
|
1341
|
+
const attributes = { "activitypub.actor.discovery.result": result };
|
|
1342
|
+
const host = getActorDiscoveryRemoteHost(actor);
|
|
1343
|
+
if (host != null) attributes["activitypub.remote.host"] = host;
|
|
1344
|
+
const instruments = getActorDiscoveryInstruments(meterProvider);
|
|
1345
|
+
instruments.discovery.add(1, attributes);
|
|
1346
|
+
instruments.discoveryDuration.record(Math.max(0, performance.now() - start), attributes);
|
|
1347
|
+
}
|
|
1287
1348
|
span.end();
|
|
1288
1349
|
}
|
|
1289
1350
|
});
|
|
@@ -1293,7 +1354,8 @@ async function getActorHandleInternal(actor, options = {}) {
|
|
|
1293
1354
|
if (actorId != null) {
|
|
1294
1355
|
const result = await lookupWebFinger(actorId, {
|
|
1295
1356
|
userAgent: options.userAgent,
|
|
1296
|
-
tracerProvider: options.tracerProvider
|
|
1357
|
+
tracerProvider: options.tracerProvider,
|
|
1358
|
+
meterProvider: options.meterProvider
|
|
1297
1359
|
});
|
|
1298
1360
|
if (result != null) {
|
|
1299
1361
|
const aliases = [...result.aliases ?? []];
|
|
@@ -1301,19 +1363,20 @@ async function getActorHandleInternal(actor, options = {}) {
|
|
|
1301
1363
|
for (const alias of aliases) {
|
|
1302
1364
|
const match = alias.match(/^acct:([^@]+)@([^@]+)$/);
|
|
1303
1365
|
if (match != null) {
|
|
1304
|
-
if (new URL(`https://${match[2]}/`).hostname !== actorId.hostname && !await verifyCrossOriginActorHandle(actorId.href, alias, options.userAgent, options.tracerProvider)) continue;
|
|
1366
|
+
if (new URL(`https://${match[2]}/`).hostname !== actorId.hostname && !await verifyCrossOriginActorHandle(actorId.href, alias, options.userAgent, options.tracerProvider, options.meterProvider)) continue;
|
|
1305
1367
|
return normalizeActorHandle(`@${match[1]}@${match[2]}`, options);
|
|
1306
1368
|
}
|
|
1307
1369
|
}
|
|
1308
1370
|
}
|
|
1309
1371
|
}
|
|
1310
1372
|
if (!(actor instanceof URL) && actor.preferredUsername != null && actor.id != null) return normalizeActorHandle(`@${actor.preferredUsername}@${actor.id.host}`, options);
|
|
1311
|
-
throw new
|
|
1373
|
+
throw new ActorHandleNotFoundError();
|
|
1312
1374
|
}
|
|
1313
|
-
async function verifyCrossOriginActorHandle(actorId, alias, userAgent, tracerProvider) {
|
|
1375
|
+
async function verifyCrossOriginActorHandle(actorId, alias, userAgent, tracerProvider, meterProvider) {
|
|
1314
1376
|
const response = await lookupWebFinger(alias, {
|
|
1315
1377
|
userAgent,
|
|
1316
|
-
tracerProvider
|
|
1378
|
+
tracerProvider,
|
|
1379
|
+
meterProvider
|
|
1317
1380
|
});
|
|
1318
1381
|
if (response == null) return false;
|
|
1319
1382
|
for (const alias of response.aliases ?? []) if (new URL(alias).href === actorId) return true;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
2
|
globalThis.addEventListener = () => {};
|
|
3
3
|
import { j as __exportAll, r as Application, u as Group, v as Organization, w as Service, y as Person } from "./vocab-B0Z-tH4q.mjs";
|
|
4
|
-
import { a as normalizeActorHandle, c as esm_default, i as isActor, n as getActorHandle, r as getActorTypeName, t as getActorClassByTypeName } from "./actor-
|
|
5
|
-
import { test } from "@fedify/fixture";
|
|
4
|
+
import { a as normalizeActorHandle, c as esm_default, i as isActor, n as getActorHandle, r as getActorTypeName, t as getActorClassByTypeName } from "./actor-B38UQKg6.mjs";
|
|
5
|
+
import { createTestMeterProvider, test } from "@fedify/fixture";
|
|
6
6
|
import { deepStrictEqual, ok, rejects, strictEqual, throws } from "node:assert/strict";
|
|
7
7
|
//#region ../../node_modules/.pnpm/fast-check@3.23.2/node_modules/fast-check/lib/esm/check/precondition/PreconditionFailure.js
|
|
8
8
|
var PreconditionFailure = class PreconditionFailure extends Error {
|
|
@@ -5691,6 +5691,103 @@ test({
|
|
|
5691
5691
|
esm_default.hardReset();
|
|
5692
5692
|
}
|
|
5693
5693
|
});
|
|
5694
|
+
test("getActorHandle() records activitypub.actor.discovery counter", { permissions: {
|
|
5695
|
+
env: true,
|
|
5696
|
+
read: true
|
|
5697
|
+
} }, async (t) => {
|
|
5698
|
+
esm_default.spyGlobal();
|
|
5699
|
+
try {
|
|
5700
|
+
const actorId = new URL("https://foo.example.com/@john");
|
|
5701
|
+
const actor = new Person({
|
|
5702
|
+
id: actorId,
|
|
5703
|
+
preferredUsername: "john"
|
|
5704
|
+
});
|
|
5705
|
+
await t.step("records result=resolved on a successful lookup", async () => {
|
|
5706
|
+
esm_default.removeRoutes();
|
|
5707
|
+
esm_default.get("begin:https://foo.example.com/.well-known/webfinger?", {
|
|
5708
|
+
body: { subject: "acct:johndoe@foo.example.com" },
|
|
5709
|
+
headers: { "Content-Type": "application/jrd+json" }
|
|
5710
|
+
});
|
|
5711
|
+
const [meterProvider, recorder] = createTestMeterProvider();
|
|
5712
|
+
deepStrictEqual(await getActorHandle(actor, { meterProvider }), "@johndoe@foo.example.com");
|
|
5713
|
+
const counters = recorder.getMeasurements("activitypub.actor.discovery");
|
|
5714
|
+
deepStrictEqual(counters.length, 1);
|
|
5715
|
+
deepStrictEqual(counters[0].type, "counter");
|
|
5716
|
+
deepStrictEqual(counters[0].value, 1);
|
|
5717
|
+
deepStrictEqual(counters[0].attributes["activitypub.actor.discovery.result"], "resolved");
|
|
5718
|
+
deepStrictEqual(counters[0].attributes["activitypub.remote.host"], "foo.example.com");
|
|
5719
|
+
const durations = recorder.getMeasurements("activitypub.actor.discovery.duration");
|
|
5720
|
+
deepStrictEqual(durations.length, 1);
|
|
5721
|
+
deepStrictEqual(durations[0].type, "histogram");
|
|
5722
|
+
deepStrictEqual(durations[0].attributes["activitypub.actor.discovery.result"], "resolved");
|
|
5723
|
+
ok(typeof durations[0].value === "number" && durations[0].value >= 0);
|
|
5724
|
+
});
|
|
5725
|
+
await t.step("records result=resolved when WebFinger is missing but preferredUsername fallback succeeds", async () => {
|
|
5726
|
+
esm_default.removeRoutes();
|
|
5727
|
+
esm_default.get("begin:https://foo.example.com/.well-known/webfinger?", { status: 404 });
|
|
5728
|
+
const [meterProvider, recorder] = createTestMeterProvider();
|
|
5729
|
+
deepStrictEqual(await getActorHandle(actor, { meterProvider }), "@john@foo.example.com");
|
|
5730
|
+
const counter = recorder.getMeasurement("activitypub.actor.discovery");
|
|
5731
|
+
ok(counter != null);
|
|
5732
|
+
deepStrictEqual(counter.attributes["activitypub.actor.discovery.result"], "resolved");
|
|
5733
|
+
});
|
|
5734
|
+
await t.step("records result=not_found when neither WebFinger nor preferredUsername yields a handle", async () => {
|
|
5735
|
+
esm_default.removeRoutes();
|
|
5736
|
+
esm_default.get("begin:https://foo.example.com/.well-known/webfinger?", { status: 404 });
|
|
5737
|
+
const [meterProvider, recorder] = createTestMeterProvider();
|
|
5738
|
+
await rejects(() => getActorHandle(actorId, { meterProvider }), TypeError);
|
|
5739
|
+
const counter = recorder.getMeasurement("activitypub.actor.discovery");
|
|
5740
|
+
ok(counter != null);
|
|
5741
|
+
deepStrictEqual(counter.attributes["activitypub.actor.discovery.result"], "not_found");
|
|
5742
|
+
deepStrictEqual(counter.attributes["activitypub.remote.host"], "foo.example.com");
|
|
5743
|
+
const duration = recorder.getMeasurement("activitypub.actor.discovery.duration");
|
|
5744
|
+
ok(duration != null);
|
|
5745
|
+
deepStrictEqual(duration.attributes["activitypub.actor.discovery.result"], "not_found");
|
|
5746
|
+
});
|
|
5747
|
+
await t.step("records result=error when a malformed WebFinger alias throws TypeError", async () => {
|
|
5748
|
+
esm_default.removeRoutes();
|
|
5749
|
+
esm_default.get("begin:https://foo.example.com/.well-known/webfinger?", {
|
|
5750
|
+
body: {
|
|
5751
|
+
subject: "https://foo.example.com/@john",
|
|
5752
|
+
aliases: ["acct:john@["]
|
|
5753
|
+
},
|
|
5754
|
+
headers: { "Content-Type": "application/jrd+json" }
|
|
5755
|
+
});
|
|
5756
|
+
const [meterProvider, recorder] = createTestMeterProvider();
|
|
5757
|
+
await rejects(() => getActorHandle(actorId, { meterProvider }), TypeError);
|
|
5758
|
+
const counter = recorder.getMeasurement("activitypub.actor.discovery");
|
|
5759
|
+
ok(counter != null);
|
|
5760
|
+
deepStrictEqual(counter.attributes["activitypub.actor.discovery.result"], "error");
|
|
5761
|
+
});
|
|
5762
|
+
await t.step("propagates meterProvider into the nested webfinger.lookup", async () => {
|
|
5763
|
+
esm_default.removeRoutes();
|
|
5764
|
+
esm_default.get("begin:https://foo.example.com/.well-known/webfinger?", {
|
|
5765
|
+
body: { subject: "acct:johndoe@foo.example.com" },
|
|
5766
|
+
headers: { "Content-Type": "application/jrd+json" }
|
|
5767
|
+
});
|
|
5768
|
+
const [meterProvider, recorder] = createTestMeterProvider();
|
|
5769
|
+
await getActorHandle(actor, { meterProvider });
|
|
5770
|
+
const webFingerCounter = recorder.getMeasurement("webfinger.lookup");
|
|
5771
|
+
ok(webFingerCounter != null);
|
|
5772
|
+
deepStrictEqual(webFingerCounter.attributes["webfinger.lookup.result"], "found");
|
|
5773
|
+
});
|
|
5774
|
+
await t.step("omits measurements when no meterProvider is provided", async () => {
|
|
5775
|
+
esm_default.removeRoutes();
|
|
5776
|
+
esm_default.get("begin:https://foo.example.com/.well-known/webfinger?", {
|
|
5777
|
+
body: { subject: "acct:johndoe@foo.example.com" },
|
|
5778
|
+
headers: { "Content-Type": "application/jrd+json" }
|
|
5779
|
+
});
|
|
5780
|
+
const [_unused, recorder] = createTestMeterProvider();
|
|
5781
|
+
await getActorHandle(actor);
|
|
5782
|
+
deepStrictEqual(recorder.getMeasurements("activitypub.actor.discovery").length, 0);
|
|
5783
|
+
deepStrictEqual(recorder.getMeasurements("activitypub.actor.discovery.duration").length, 0);
|
|
5784
|
+
deepStrictEqual(recorder.getMeasurements("webfinger.lookup").length, 0);
|
|
5785
|
+
});
|
|
5786
|
+
} finally {
|
|
5787
|
+
esm_default.removeRoutes();
|
|
5788
|
+
esm_default.hardReset();
|
|
5789
|
+
}
|
|
5790
|
+
});
|
|
5694
5791
|
test("normalizeActorHandle()", () => {
|
|
5695
5792
|
deepStrictEqual(normalizeActorHandle("@foo@BAR.COM"), "@foo@bar.com");
|
|
5696
5793
|
deepStrictEqual(normalizeActorHandle("@BAZ@☃-⌘.com"), "@BAZ@☃-⌘.com");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Temporal } from "@js-temporal/polyfill";
|
|
2
2
|
globalThis.addEventListener = () => {};
|
|
3
3
|
import { g as Object$1, h as Note, i as Collection, y as Person } from "./vocab-B0Z-tH4q.mjs";
|
|
4
|
-
import { c as esm_default, i as isActor, o as name, s as version } from "./actor-
|
|
4
|
+
import { c as esm_default, i as isActor, o as name, s as version } from "./actor-B38UQKg6.mjs";
|
|
5
5
|
import { t as getTypeId } from "./type-Cf-vxmre.mjs";
|
|
6
6
|
import { t as assertInstanceOf } from "./utils-CE8Dk5hm.mjs";
|
|
7
7
|
import { createTestMeterProvider, createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
|
|
@@ -186,6 +186,7 @@ async function lookupObjectInternal(identifier, options = {}) {
|
|
|
186
186
|
const jrd = await lookupWebFinger(identifier, {
|
|
187
187
|
userAgent: options.userAgent,
|
|
188
188
|
tracerProvider: options.tracerProvider,
|
|
189
|
+
meterProvider: options.meterProvider,
|
|
189
190
|
allowPrivateAddress: "allowPrivateAddress" in options && options.allowPrivateAddress === true,
|
|
190
191
|
signal: options.signal
|
|
191
192
|
});
|
|
@@ -781,6 +782,27 @@ test("lookupObject() records activitypub.object.lookup counter", {
|
|
|
781
782
|
});
|
|
782
783
|
deepStrictEqual(recorder.getMeasurements("activitypub.object.lookup").length, 0);
|
|
783
784
|
});
|
|
785
|
+
await t.step("propagates meterProvider into the nested webfinger.lookup", async () => {
|
|
786
|
+
esm_default.removeRoutes();
|
|
787
|
+
esm_default.get("begin:https://example.com/.well-known/webfinger", {
|
|
788
|
+
subject: "acct:johndoe@example.com",
|
|
789
|
+
links: [{
|
|
790
|
+
rel: "self",
|
|
791
|
+
href: "https://example.com/person",
|
|
792
|
+
type: "application/activity+json"
|
|
793
|
+
}]
|
|
794
|
+
});
|
|
795
|
+
const [meterProvider, recorder] = createTestMeterProvider();
|
|
796
|
+
await lookupObject("@johndoe@example.com", {
|
|
797
|
+
documentLoader: mockDocumentLoader,
|
|
798
|
+
contextLoader: mockDocumentLoader,
|
|
799
|
+
meterProvider
|
|
800
|
+
});
|
|
801
|
+
const webFingerCounter = recorder.getMeasurement("webfinger.lookup");
|
|
802
|
+
ok(webFingerCounter != null);
|
|
803
|
+
deepStrictEqual(webFingerCounter.attributes["webfinger.lookup.result"], "found");
|
|
804
|
+
deepStrictEqual(webFingerCounter.attributes["activitypub.remote.host"], "example.com");
|
|
805
|
+
});
|
|
784
806
|
await t.step("extracts host from a URL acct: instance", async () => {
|
|
785
807
|
esm_default.removeRoutes();
|
|
786
808
|
esm_default.get("begin:https://example.com/.well-known/webfinger", {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fedify/vocab",
|
|
3
|
-
"version": "2.3.0-dev.
|
|
3
|
+
"version": "2.3.0-dev.1145+3fe78022",
|
|
4
4
|
"homepage": "https://fedify.dev/",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -46,9 +46,9 @@
|
|
|
46
46
|
"es-toolkit": "1.46.1",
|
|
47
47
|
"jsonld": "^9.0.0",
|
|
48
48
|
"pkijs": "^3.3.3",
|
|
49
|
-
"@fedify/
|
|
50
|
-
"@fedify/
|
|
51
|
-
"@fedify/vocab-
|
|
49
|
+
"@fedify/vocab-tools": "2.3.0-dev.1145+3fe78022",
|
|
50
|
+
"@fedify/webfinger": "2.3.0-dev.1145+3fe78022",
|
|
51
|
+
"@fedify/vocab-runtime": "2.3.0-dev.1145+3fe78022"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@types/node": "^22.17.0",
|
package/src/actor.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { test } from "@fedify/fixture";
|
|
1
|
+
import { createTestMeterProvider, test } from "@fedify/fixture";
|
|
2
2
|
import * as fc from "fast-check";
|
|
3
3
|
import fetchMock from "fetch-mock";
|
|
4
4
|
import {
|
|
@@ -192,6 +192,202 @@ test({
|
|
|
192
192
|
},
|
|
193
193
|
});
|
|
194
194
|
|
|
195
|
+
test("getActorHandle() records activitypub.actor.discovery counter", {
|
|
196
|
+
permissions: { env: true, read: true },
|
|
197
|
+
}, async (t) => {
|
|
198
|
+
fetchMock.spyGlobal();
|
|
199
|
+
try {
|
|
200
|
+
const actorId = new URL("https://foo.example.com/@john");
|
|
201
|
+
const actor = new Person({
|
|
202
|
+
id: actorId,
|
|
203
|
+
preferredUsername: "john",
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
await t.step("records result=resolved on a successful lookup", async () => {
|
|
207
|
+
fetchMock.removeRoutes();
|
|
208
|
+
fetchMock.get(
|
|
209
|
+
"begin:https://foo.example.com/.well-known/webfinger?",
|
|
210
|
+
{
|
|
211
|
+
body: { subject: "acct:johndoe@foo.example.com" },
|
|
212
|
+
headers: { "Content-Type": "application/jrd+json" },
|
|
213
|
+
},
|
|
214
|
+
);
|
|
215
|
+
const [meterProvider, recorder] = createTestMeterProvider();
|
|
216
|
+
const handle = await getActorHandle(actor, { meterProvider });
|
|
217
|
+
deepStrictEqual(handle, "@johndoe@foo.example.com");
|
|
218
|
+
|
|
219
|
+
const counters = recorder.getMeasurements(
|
|
220
|
+
"activitypub.actor.discovery",
|
|
221
|
+
);
|
|
222
|
+
deepStrictEqual(counters.length, 1);
|
|
223
|
+
deepStrictEqual(counters[0].type, "counter");
|
|
224
|
+
deepStrictEqual(counters[0].value, 1);
|
|
225
|
+
deepStrictEqual(
|
|
226
|
+
counters[0].attributes["activitypub.actor.discovery.result"],
|
|
227
|
+
"resolved",
|
|
228
|
+
);
|
|
229
|
+
deepStrictEqual(
|
|
230
|
+
counters[0].attributes["activitypub.remote.host"],
|
|
231
|
+
"foo.example.com",
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
const durations = recorder.getMeasurements(
|
|
235
|
+
"activitypub.actor.discovery.duration",
|
|
236
|
+
);
|
|
237
|
+
deepStrictEqual(durations.length, 1);
|
|
238
|
+
deepStrictEqual(durations[0].type, "histogram");
|
|
239
|
+
deepStrictEqual(
|
|
240
|
+
durations[0].attributes["activitypub.actor.discovery.result"],
|
|
241
|
+
"resolved",
|
|
242
|
+
);
|
|
243
|
+
ok(typeof durations[0].value === "number" && durations[0].value >= 0);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
await t.step(
|
|
247
|
+
"records result=resolved when WebFinger is missing but preferredUsername fallback succeeds",
|
|
248
|
+
async () => {
|
|
249
|
+
fetchMock.removeRoutes();
|
|
250
|
+
fetchMock.get(
|
|
251
|
+
"begin:https://foo.example.com/.well-known/webfinger?",
|
|
252
|
+
{ status: 404 },
|
|
253
|
+
);
|
|
254
|
+
const [meterProvider, recorder] = createTestMeterProvider();
|
|
255
|
+
const handle = await getActorHandle(actor, { meterProvider });
|
|
256
|
+
deepStrictEqual(handle, "@john@foo.example.com");
|
|
257
|
+
const counter = recorder.getMeasurement("activitypub.actor.discovery");
|
|
258
|
+
ok(counter != null);
|
|
259
|
+
deepStrictEqual(
|
|
260
|
+
counter.attributes["activitypub.actor.discovery.result"],
|
|
261
|
+
"resolved",
|
|
262
|
+
);
|
|
263
|
+
},
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
await t.step(
|
|
267
|
+
"records result=not_found when neither WebFinger nor preferredUsername yields a handle",
|
|
268
|
+
async () => {
|
|
269
|
+
fetchMock.removeRoutes();
|
|
270
|
+
fetchMock.get(
|
|
271
|
+
"begin:https://foo.example.com/.well-known/webfinger?",
|
|
272
|
+
{ status: 404 },
|
|
273
|
+
);
|
|
274
|
+
const [meterProvider, recorder] = createTestMeterProvider();
|
|
275
|
+
await rejects(
|
|
276
|
+
() => getActorHandle(actorId, { meterProvider }),
|
|
277
|
+
TypeError,
|
|
278
|
+
);
|
|
279
|
+
const counter = recorder.getMeasurement("activitypub.actor.discovery");
|
|
280
|
+
ok(counter != null);
|
|
281
|
+
deepStrictEqual(
|
|
282
|
+
counter.attributes["activitypub.actor.discovery.result"],
|
|
283
|
+
"not_found",
|
|
284
|
+
);
|
|
285
|
+
deepStrictEqual(
|
|
286
|
+
counter.attributes["activitypub.remote.host"],
|
|
287
|
+
"foo.example.com",
|
|
288
|
+
);
|
|
289
|
+
const duration = recorder.getMeasurement(
|
|
290
|
+
"activitypub.actor.discovery.duration",
|
|
291
|
+
);
|
|
292
|
+
ok(duration != null);
|
|
293
|
+
deepStrictEqual(
|
|
294
|
+
duration.attributes["activitypub.actor.discovery.result"],
|
|
295
|
+
"not_found",
|
|
296
|
+
);
|
|
297
|
+
},
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
await t.step(
|
|
301
|
+
"records result=error when a malformed WebFinger alias throws TypeError",
|
|
302
|
+
async () => {
|
|
303
|
+
// The "[" byte makes `new URL("https://[/")` throw `TypeError`
|
|
304
|
+
// when getActorHandleInternal attempts to parse the alias host.
|
|
305
|
+
// This TypeError is a malformed-remote-data failure, not the
|
|
306
|
+
// "actor lacks information" sentinel, so the metric must record
|
|
307
|
+
// `error` rather than `not_found`.
|
|
308
|
+
fetchMock.removeRoutes();
|
|
309
|
+
fetchMock.get(
|
|
310
|
+
"begin:https://foo.example.com/.well-known/webfinger?",
|
|
311
|
+
{
|
|
312
|
+
body: {
|
|
313
|
+
subject: "https://foo.example.com/@john",
|
|
314
|
+
aliases: ["acct:john@["],
|
|
315
|
+
},
|
|
316
|
+
headers: { "Content-Type": "application/jrd+json" },
|
|
317
|
+
},
|
|
318
|
+
);
|
|
319
|
+
const [meterProvider, recorder] = createTestMeterProvider();
|
|
320
|
+
await rejects(
|
|
321
|
+
() => getActorHandle(actorId, { meterProvider }),
|
|
322
|
+
TypeError,
|
|
323
|
+
);
|
|
324
|
+
const counter = recorder.getMeasurement(
|
|
325
|
+
"activitypub.actor.discovery",
|
|
326
|
+
);
|
|
327
|
+
ok(counter != null);
|
|
328
|
+
deepStrictEqual(
|
|
329
|
+
counter.attributes["activitypub.actor.discovery.result"],
|
|
330
|
+
"error",
|
|
331
|
+
);
|
|
332
|
+
},
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
await t.step(
|
|
336
|
+
"propagates meterProvider into the nested webfinger.lookup",
|
|
337
|
+
async () => {
|
|
338
|
+
fetchMock.removeRoutes();
|
|
339
|
+
fetchMock.get(
|
|
340
|
+
"begin:https://foo.example.com/.well-known/webfinger?",
|
|
341
|
+
{
|
|
342
|
+
body: { subject: "acct:johndoe@foo.example.com" },
|
|
343
|
+
headers: { "Content-Type": "application/jrd+json" },
|
|
344
|
+
},
|
|
345
|
+
);
|
|
346
|
+
const [meterProvider, recorder] = createTestMeterProvider();
|
|
347
|
+
await getActorHandle(actor, { meterProvider });
|
|
348
|
+
const webFingerCounter = recorder.getMeasurement("webfinger.lookup");
|
|
349
|
+
ok(webFingerCounter != null);
|
|
350
|
+
deepStrictEqual(
|
|
351
|
+
webFingerCounter.attributes["webfinger.lookup.result"],
|
|
352
|
+
"found",
|
|
353
|
+
);
|
|
354
|
+
},
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
await t.step(
|
|
358
|
+
"omits measurements when no meterProvider is provided",
|
|
359
|
+
async () => {
|
|
360
|
+
fetchMock.removeRoutes();
|
|
361
|
+
fetchMock.get(
|
|
362
|
+
"begin:https://foo.example.com/.well-known/webfinger?",
|
|
363
|
+
{
|
|
364
|
+
body: { subject: "acct:johndoe@foo.example.com" },
|
|
365
|
+
headers: { "Content-Type": "application/jrd+json" },
|
|
366
|
+
},
|
|
367
|
+
);
|
|
368
|
+
const [_unused, recorder] = createTestMeterProvider();
|
|
369
|
+
await getActorHandle(actor);
|
|
370
|
+
deepStrictEqual(
|
|
371
|
+
recorder.getMeasurements("activitypub.actor.discovery").length,
|
|
372
|
+
0,
|
|
373
|
+
);
|
|
374
|
+
deepStrictEqual(
|
|
375
|
+
recorder.getMeasurements("activitypub.actor.discovery.duration")
|
|
376
|
+
.length,
|
|
377
|
+
0,
|
|
378
|
+
);
|
|
379
|
+
deepStrictEqual(
|
|
380
|
+
recorder.getMeasurements("webfinger.lookup").length,
|
|
381
|
+
0,
|
|
382
|
+
);
|
|
383
|
+
},
|
|
384
|
+
);
|
|
385
|
+
} finally {
|
|
386
|
+
fetchMock.removeRoutes();
|
|
387
|
+
fetchMock.hardReset();
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
|
|
195
391
|
test("normalizeActorHandle()", () => {
|
|
196
392
|
deepStrictEqual(normalizeActorHandle("@foo@BAR.COM"), "@foo@bar.com");
|
|
197
393
|
deepStrictEqual(normalizeActorHandle("@BAZ@☃-⌘.com"), "@BAZ@☃-⌘.com");
|
package/src/actor.ts
CHANGED
|
@@ -1,11 +1,114 @@
|
|
|
1
1
|
import type { GetUserAgentOptions } from "@fedify/vocab-runtime";
|
|
2
2
|
import { lookupWebFinger } from "@fedify/webfinger";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
type Attributes,
|
|
5
|
+
type Counter,
|
|
6
|
+
type Histogram,
|
|
7
|
+
type MeterProvider,
|
|
8
|
+
SpanStatusCode,
|
|
9
|
+
trace,
|
|
10
|
+
type TracerProvider,
|
|
11
|
+
} from "@opentelemetry/api";
|
|
4
12
|
import { domainToASCII, domainToUnicode } from "node:url";
|
|
5
13
|
import metadata from "../deno.json" with { type: "json" };
|
|
6
14
|
import { getTypeId } from "./type.ts";
|
|
7
15
|
import { Application, Group, Organization, Person, Service } from "./vocab.ts";
|
|
8
16
|
|
|
17
|
+
/**
|
|
18
|
+
* The terminal classification of a {@link getActorHandle} call, recorded as
|
|
19
|
+
* the `activitypub.actor.discovery.result` attribute on the
|
|
20
|
+
* `activitypub.actor.discovery` counter and
|
|
21
|
+
* `activitypub.actor.discovery.duration` histogram.
|
|
22
|
+
*
|
|
23
|
+
* - `resolved`: a handle was returned to the caller.
|
|
24
|
+
* - `not_found`: WebFinger did not yield a usable `acct:` alias and the
|
|
25
|
+
* `preferredUsername` fallback could not run. This corresponds to the
|
|
26
|
+
* `TypeError("Actor does not have enough information…")` path.
|
|
27
|
+
* - `error`: any other thrown exception bubbled up from the lookup.
|
|
28
|
+
*
|
|
29
|
+
* Per-WebFinger-call failure detail (HTTP status, parse failure, network
|
|
30
|
+
* failure, etc.) lives on `webfinger.lookup` and is intentionally not
|
|
31
|
+
* duplicated here.
|
|
32
|
+
* @since 2.3.0
|
|
33
|
+
*/
|
|
34
|
+
export type ActorDiscoveryResult = "resolved" | "not_found" | "error";
|
|
35
|
+
|
|
36
|
+
interface ActorDiscoveryInstruments {
|
|
37
|
+
discovery: Counter;
|
|
38
|
+
discoveryDuration: Histogram;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const ACTOR_DISCOVERY_HISTOGRAM_BUCKETS: ReadonlyArray<number> = [
|
|
42
|
+
5,
|
|
43
|
+
10,
|
|
44
|
+
25,
|
|
45
|
+
50,
|
|
46
|
+
75,
|
|
47
|
+
100,
|
|
48
|
+
250,
|
|
49
|
+
500,
|
|
50
|
+
750,
|
|
51
|
+
1000,
|
|
52
|
+
2500,
|
|
53
|
+
5000,
|
|
54
|
+
7500,
|
|
55
|
+
10000,
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
const actorDiscoveryInstruments = new WeakMap<
|
|
59
|
+
MeterProvider,
|
|
60
|
+
ActorDiscoveryInstruments
|
|
61
|
+
>();
|
|
62
|
+
|
|
63
|
+
function getActorDiscoveryInstruments(
|
|
64
|
+
meterProvider: MeterProvider,
|
|
65
|
+
): ActorDiscoveryInstruments {
|
|
66
|
+
let instruments = actorDiscoveryInstruments.get(meterProvider);
|
|
67
|
+
if (instruments == null) {
|
|
68
|
+
const meter = meterProvider.getMeter(metadata.name, metadata.version);
|
|
69
|
+
instruments = {
|
|
70
|
+
discovery: meter.createCounter("activitypub.actor.discovery", {
|
|
71
|
+
description: "Actor handle discovery attempts via getActorHandle(), " +
|
|
72
|
+
"classified by terminal outcome.",
|
|
73
|
+
unit: "{discovery}",
|
|
74
|
+
}),
|
|
75
|
+
discoveryDuration: meter.createHistogram(
|
|
76
|
+
"activitypub.actor.discovery.duration",
|
|
77
|
+
{
|
|
78
|
+
description: "Duration of getActorHandle() actor discovery calls.",
|
|
79
|
+
unit: "ms",
|
|
80
|
+
advice: {
|
|
81
|
+
explicitBucketBoundaries: [...ACTOR_DISCOVERY_HISTOGRAM_BUCKETS],
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
),
|
|
85
|
+
};
|
|
86
|
+
actorDiscoveryInstruments.set(meterProvider, instruments);
|
|
87
|
+
}
|
|
88
|
+
return instruments;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function getActorDiscoveryRemoteHost(
|
|
92
|
+
actor: Actor | URL,
|
|
93
|
+
): string | undefined {
|
|
94
|
+
const id = actor instanceof URL ? actor : actor.id;
|
|
95
|
+
if (id == null) return undefined;
|
|
96
|
+
return id.hostname === "" ? undefined : id.hostname;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Subclass of TypeError that preserves the documented `throws {TypeError}`
|
|
100
|
+
// API contract while letting the actor-discovery metric distinguish the
|
|
101
|
+
// "actor lacks enough information to derive a handle" terminal path from
|
|
102
|
+
// other TypeError-shaped failures that can come from malformed remote data
|
|
103
|
+
// (e.g. an alias that does not parse as a URL, or an invalid preferred
|
|
104
|
+
// username that breaks normalizeActorHandle).
|
|
105
|
+
class ActorHandleNotFoundError extends TypeError {
|
|
106
|
+
constructor() {
|
|
107
|
+
super("Actor does not have enough information to get the handle.");
|
|
108
|
+
this.name = "ActorHandleNotFoundError";
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
9
112
|
/**
|
|
10
113
|
* Actor types are {@link Object} types that are capable of performing
|
|
11
114
|
* activities.
|
|
@@ -101,6 +204,19 @@ export interface GetActorHandleOptions extends NormalizeActorHandleOptions {
|
|
|
101
204
|
* @since 1.3.0
|
|
102
205
|
*/
|
|
103
206
|
tracerProvider?: TracerProvider;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* The OpenTelemetry meter provider used to record the
|
|
210
|
+
* `activitypub.actor.discovery` counter and
|
|
211
|
+
* `activitypub.actor.discovery.duration` histogram. When set, the same
|
|
212
|
+
* meter provider is also forwarded to the nested WebFinger lookups so
|
|
213
|
+
* each discovery emits both the actor-discovery measurements and the
|
|
214
|
+
* underlying `webfinger.lookup` measurements. If omitted, no
|
|
215
|
+
* measurements are emitted (the helper is opt-in to avoid touching the
|
|
216
|
+
* global meter provider for callers that do not use OpenTelemetry).
|
|
217
|
+
* @since 2.3.0
|
|
218
|
+
*/
|
|
219
|
+
meterProvider?: MeterProvider;
|
|
104
220
|
}
|
|
105
221
|
|
|
106
222
|
/**
|
|
@@ -145,15 +261,36 @@ export async function getActorHandle(
|
|
|
145
261
|
}
|
|
146
262
|
span.setAttribute("activitypub.actor.type", getTypeId(actor).href);
|
|
147
263
|
}
|
|
264
|
+
const meterProvider = options.meterProvider;
|
|
265
|
+
const start = meterProvider == null ? 0 : performance.now();
|
|
266
|
+
let result: ActorDiscoveryResult = "error";
|
|
148
267
|
try {
|
|
149
|
-
|
|
268
|
+
const handle = await getActorHandleInternal(actor, options);
|
|
269
|
+
result = "resolved";
|
|
270
|
+
return handle;
|
|
150
271
|
} catch (error) {
|
|
272
|
+
result = error instanceof ActorHandleNotFoundError
|
|
273
|
+
? "not_found"
|
|
274
|
+
: "error";
|
|
151
275
|
span.setStatus({
|
|
152
276
|
code: SpanStatusCode.ERROR,
|
|
153
277
|
message: String(error),
|
|
154
278
|
});
|
|
155
279
|
throw error;
|
|
156
280
|
} finally {
|
|
281
|
+
if (meterProvider != null) {
|
|
282
|
+
const attributes: Attributes = {
|
|
283
|
+
"activitypub.actor.discovery.result": result,
|
|
284
|
+
};
|
|
285
|
+
const host = getActorDiscoveryRemoteHost(actor);
|
|
286
|
+
if (host != null) attributes["activitypub.remote.host"] = host;
|
|
287
|
+
const instruments = getActorDiscoveryInstruments(meterProvider);
|
|
288
|
+
instruments.discovery.add(1, attributes);
|
|
289
|
+
instruments.discoveryDuration.record(
|
|
290
|
+
Math.max(0, performance.now() - start),
|
|
291
|
+
attributes,
|
|
292
|
+
);
|
|
293
|
+
}
|
|
157
294
|
span.end();
|
|
158
295
|
}
|
|
159
296
|
},
|
|
@@ -169,6 +306,7 @@ async function getActorHandleInternal(
|
|
|
169
306
|
const result = await lookupWebFinger(actorId, {
|
|
170
307
|
userAgent: options.userAgent,
|
|
171
308
|
tracerProvider: options.tracerProvider,
|
|
309
|
+
meterProvider: options.meterProvider,
|
|
172
310
|
});
|
|
173
311
|
if (result != null) {
|
|
174
312
|
const aliases = [...(result.aliases ?? [])];
|
|
@@ -184,6 +322,7 @@ async function getActorHandleInternal(
|
|
|
184
322
|
alias,
|
|
185
323
|
options.userAgent,
|
|
186
324
|
options.tracerProvider,
|
|
325
|
+
options.meterProvider,
|
|
187
326
|
)
|
|
188
327
|
) {
|
|
189
328
|
continue;
|
|
@@ -202,9 +341,7 @@ async function getActorHandleInternal(
|
|
|
202
341
|
options,
|
|
203
342
|
);
|
|
204
343
|
}
|
|
205
|
-
throw new
|
|
206
|
-
"Actor does not have enough information to get the handle.",
|
|
207
|
-
);
|
|
344
|
+
throw new ActorHandleNotFoundError();
|
|
208
345
|
}
|
|
209
346
|
|
|
210
347
|
async function verifyCrossOriginActorHandle(
|
|
@@ -212,8 +349,13 @@ async function verifyCrossOriginActorHandle(
|
|
|
212
349
|
alias: string,
|
|
213
350
|
userAgent: GetUserAgentOptions | string | undefined,
|
|
214
351
|
tracerProvider: TracerProvider | undefined,
|
|
352
|
+
meterProvider: MeterProvider | undefined,
|
|
215
353
|
): Promise<boolean> {
|
|
216
|
-
const response = await lookupWebFinger(alias, {
|
|
354
|
+
const response = await lookupWebFinger(alias, {
|
|
355
|
+
userAgent,
|
|
356
|
+
tracerProvider,
|
|
357
|
+
meterProvider,
|
|
358
|
+
});
|
|
217
359
|
if (response == null) return false;
|
|
218
360
|
for (const alias of response.aliases ?? []) {
|
|
219
361
|
if (new URL(alias).href === actorId) return true;
|
package/src/lookup.test.ts
CHANGED
|
@@ -811,6 +811,42 @@ test("lookupObject() records activitypub.object.lookup counter", {
|
|
|
811
811
|
},
|
|
812
812
|
);
|
|
813
813
|
|
|
814
|
+
await t.step(
|
|
815
|
+
"propagates meterProvider into the nested webfinger.lookup",
|
|
816
|
+
async () => {
|
|
817
|
+
fetchMock.removeRoutes();
|
|
818
|
+
fetchMock.get(
|
|
819
|
+
"begin:https://example.com/.well-known/webfinger",
|
|
820
|
+
{
|
|
821
|
+
subject: "acct:johndoe@example.com",
|
|
822
|
+
links: [
|
|
823
|
+
{
|
|
824
|
+
rel: "self",
|
|
825
|
+
href: "https://example.com/person",
|
|
826
|
+
type: "application/activity+json",
|
|
827
|
+
},
|
|
828
|
+
],
|
|
829
|
+
},
|
|
830
|
+
);
|
|
831
|
+
const [meterProvider, recorder] = createTestMeterProvider();
|
|
832
|
+
await lookupObject("@johndoe@example.com", {
|
|
833
|
+
documentLoader: mockDocumentLoader,
|
|
834
|
+
contextLoader: mockDocumentLoader,
|
|
835
|
+
meterProvider,
|
|
836
|
+
});
|
|
837
|
+
const webFingerCounter = recorder.getMeasurement("webfinger.lookup");
|
|
838
|
+
ok(webFingerCounter != null);
|
|
839
|
+
deepStrictEqual(
|
|
840
|
+
webFingerCounter.attributes["webfinger.lookup.result"],
|
|
841
|
+
"found",
|
|
842
|
+
);
|
|
843
|
+
deepStrictEqual(
|
|
844
|
+
webFingerCounter.attributes["activitypub.remote.host"],
|
|
845
|
+
"example.com",
|
|
846
|
+
);
|
|
847
|
+
},
|
|
848
|
+
);
|
|
849
|
+
|
|
814
850
|
await t.step(
|
|
815
851
|
"extracts host from a URL acct: instance",
|
|
816
852
|
async () => {
|
package/src/lookup.ts
CHANGED
|
@@ -286,6 +286,7 @@ async function lookupObjectInternal(
|
|
|
286
286
|
const jrd = await lookupWebFinger(identifier, {
|
|
287
287
|
userAgent: options.userAgent,
|
|
288
288
|
tracerProvider: options.tracerProvider,
|
|
289
|
+
meterProvider: options.meterProvider,
|
|
289
290
|
allowPrivateAddress: "allowPrivateAddress" in options &&
|
|
290
291
|
options.allowPrivateAddress === true,
|
|
291
292
|
signal: options.signal,
|