@fedify/webfinger 2.5.0-dev.2432 → 2.5.0-dev.2446
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/lookup.test.cjs +99 -10
- package/dist/lookup.test.js +99 -10
- package/dist/mod.cjs +10 -10
- package/dist/mod.d.cts +53 -2
- package/dist/mod.d.ts +53 -2
- package/dist/mod.js +10 -10
- package/package.json +2 -2
- package/src/lookup.test.ts +165 -1
- package/src/lookup.ts +77 -7
package/deno.json
CHANGED
package/dist/lookup.test.cjs
CHANGED
|
@@ -1237,7 +1237,7 @@ var esm_default = new class FetchMock {
|
|
|
1237
1237
|
//#endregion
|
|
1238
1238
|
//#region deno.json
|
|
1239
1239
|
var name = "@fedify/webfinger";
|
|
1240
|
-
var version = "2.5.0-dev.
|
|
1240
|
+
var version = "2.5.0-dev.2446+2014cb03";
|
|
1241
1241
|
//#endregion
|
|
1242
1242
|
//#region src/body.ts
|
|
1243
1243
|
/** Decoded JSON body limit: 16 MiB. @internal */
|
|
@@ -1372,13 +1372,6 @@ const WEBFINGER_LOOKUP_SCHEME_WHITELIST = /* @__PURE__ */ new Set([
|
|
|
1372
1372
|
function getMetricResourceScheme(scheme) {
|
|
1373
1373
|
return WEBFINGER_LOOKUP_SCHEME_WHITELIST.has(scheme) ? scheme : "other";
|
|
1374
1374
|
}
|
|
1375
|
-
/**
|
|
1376
|
-
* Looks up a WebFinger resource.
|
|
1377
|
-
* @param resource The resource URL to look up.
|
|
1378
|
-
* @param options Extra options for looking up the resource.
|
|
1379
|
-
* @returns The resource descriptor, or `null` if not found.
|
|
1380
|
-
* @since 0.2.0
|
|
1381
|
-
*/
|
|
1382
1375
|
async function lookupWebFinger(resource, options = {}) {
|
|
1383
1376
|
const tracer = (options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider()).getTracer(name, version);
|
|
1384
1377
|
const scheme = getResourceScheme(resource);
|
|
@@ -1398,7 +1391,7 @@ async function lookupWebFinger(resource, options = {}) {
|
|
|
1398
1391
|
try {
|
|
1399
1392
|
outcome = await lookupWebFingerInternal(resource, options);
|
|
1400
1393
|
span.setStatus({ code: outcome.resource === null ? _opentelemetry_api.SpanStatusCode.ERROR : _opentelemetry_api.SpanStatusCode.OK });
|
|
1401
|
-
return outcome.resource;
|
|
1394
|
+
return options.withDiagnostics ? outcome : outcome.resource;
|
|
1402
1395
|
} catch (error) {
|
|
1403
1396
|
span.setStatus({
|
|
1404
1397
|
code: _opentelemetry_api.SpanStatusCode.ERROR,
|
|
@@ -1545,8 +1538,15 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
1545
1538
|
};
|
|
1546
1539
|
}
|
|
1547
1540
|
try {
|
|
1541
|
+
const parsed = JSON.parse(await readBoundedText(response, MAX_BODY_SIZE, url));
|
|
1542
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {
|
|
1543
|
+
resource: null,
|
|
1544
|
+
result: "invalid",
|
|
1545
|
+
statusCode: response.status,
|
|
1546
|
+
remoteHost
|
|
1547
|
+
};
|
|
1548
1548
|
return {
|
|
1549
|
-
resource:
|
|
1549
|
+
resource: parsed,
|
|
1550
1550
|
result: "found",
|
|
1551
1551
|
statusCode: response.status,
|
|
1552
1552
|
remoteHost
|
|
@@ -2040,4 +2040,93 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
2040
2040
|
}
|
|
2041
2041
|
});
|
|
2042
2042
|
});
|
|
2043
|
+
(0, _fedify_fixture.test)("lookupWebFinger() with withDiagnostics: true", {
|
|
2044
|
+
sanitizeOps: false,
|
|
2045
|
+
sanitizeResources: false
|
|
2046
|
+
}, async (t) => {
|
|
2047
|
+
await t.step("network_error", async () => {
|
|
2048
|
+
const outcome = await lookupWebFinger("acct:johndoe@fedify-test.internal", { withDiagnostics: true });
|
|
2049
|
+
(0, node_assert_strict.deepStrictEqual)(outcome.resource, null);
|
|
2050
|
+
(0, node_assert_strict.deepStrictEqual)(outcome.result, "network_error");
|
|
2051
|
+
(0, node_assert_strict.deepStrictEqual)("statusCode" in outcome, false, "no HTTP response means no status code");
|
|
2052
|
+
});
|
|
2053
|
+
esm_default.spyGlobal();
|
|
2054
|
+
try {
|
|
2055
|
+
await t.step("found", async () => {
|
|
2056
|
+
esm_default.removeRoutes();
|
|
2057
|
+
const expected = {
|
|
2058
|
+
subject: "acct:johndoe@example.com",
|
|
2059
|
+
links: []
|
|
2060
|
+
};
|
|
2061
|
+
esm_default.get("https://example.com/.well-known/webfinger?resource=acct%3Ajohndoe%40example.com", { body: expected });
|
|
2062
|
+
const outcome = await lookupWebFinger("acct:johndoe@example.com", { withDiagnostics: true });
|
|
2063
|
+
(0, node_assert_strict.deepStrictEqual)(outcome.resource, expected);
|
|
2064
|
+
(0, node_assert_strict.deepStrictEqual)(outcome.result, "found");
|
|
2065
|
+
(0, node_assert_strict.deepStrictEqual)(outcome.statusCode, 200);
|
|
2066
|
+
(0, node_assert_strict.deepStrictEqual)(outcome.remoteHost, "example.com");
|
|
2067
|
+
});
|
|
2068
|
+
await t.step("not_found", async () => {
|
|
2069
|
+
esm_default.removeRoutes();
|
|
2070
|
+
esm_default.get("begin:https://example.com/.well-known/webfinger?", { status: 404 });
|
|
2071
|
+
const outcome = await lookupWebFinger("acct:johndoe@example.com", { withDiagnostics: true });
|
|
2072
|
+
(0, node_assert_strict.deepStrictEqual)(outcome.resource, null);
|
|
2073
|
+
(0, node_assert_strict.deepStrictEqual)(outcome.result, "not_found");
|
|
2074
|
+
(0, node_assert_strict.deepStrictEqual)(outcome.statusCode, 404);
|
|
2075
|
+
(0, node_assert_strict.deepStrictEqual)(outcome.remoteHost, "example.com");
|
|
2076
|
+
});
|
|
2077
|
+
await t.step("invalid", async () => {
|
|
2078
|
+
esm_default.removeRoutes();
|
|
2079
|
+
esm_default.get("begin:https://example.com/.well-known/webfinger?", { body: "not json" });
|
|
2080
|
+
const outcome = await lookupWebFinger("acct:johndoe@example.com", { withDiagnostics: true });
|
|
2081
|
+
(0, node_assert_strict.deepStrictEqual)(outcome.resource, null);
|
|
2082
|
+
(0, node_assert_strict.deepStrictEqual)(outcome.result, "invalid");
|
|
2083
|
+
(0, node_assert_strict.deepStrictEqual)(outcome.statusCode, 200);
|
|
2084
|
+
(0, node_assert_strict.deepStrictEqual)(outcome.remoteHost, "example.com");
|
|
2085
|
+
});
|
|
2086
|
+
await t.step("invalid: a literal JSON null body is not a resource descriptor", async () => {
|
|
2087
|
+
esm_default.removeRoutes();
|
|
2088
|
+
esm_default.get("begin:https://example.com/.well-known/webfinger?", { body: "null" });
|
|
2089
|
+
const outcome = await lookupWebFinger("acct:johndoe@example.com", { withDiagnostics: true });
|
|
2090
|
+
(0, node_assert_strict.deepStrictEqual)(outcome.resource, null);
|
|
2091
|
+
(0, node_assert_strict.deepStrictEqual)(outcome.result, "invalid");
|
|
2092
|
+
(0, node_assert_strict.deepStrictEqual)(outcome.statusCode, 200);
|
|
2093
|
+
(0, node_assert_strict.deepStrictEqual)(outcome.remoteHost, "example.com");
|
|
2094
|
+
});
|
|
2095
|
+
await t.step("invalid: a JSON array body is not a resource descriptor", async () => {
|
|
2096
|
+
esm_default.removeRoutes();
|
|
2097
|
+
esm_default.get("begin:https://example.com/.well-known/webfinger?", { body: "[]" });
|
|
2098
|
+
const outcome = await lookupWebFinger("acct:johndoe@example.com", { withDiagnostics: true });
|
|
2099
|
+
(0, node_assert_strict.deepStrictEqual)(outcome.resource, null);
|
|
2100
|
+
(0, node_assert_strict.deepStrictEqual)(outcome.result, "invalid");
|
|
2101
|
+
(0, node_assert_strict.deepStrictEqual)(outcome.statusCode, 200);
|
|
2102
|
+
(0, node_assert_strict.deepStrictEqual)(outcome.remoteHost, "example.com");
|
|
2103
|
+
});
|
|
2104
|
+
await t.step("error", async () => {
|
|
2105
|
+
esm_default.removeRoutes();
|
|
2106
|
+
esm_default.get("begin:https://example.com/.well-known/webfinger?", { status: 500 });
|
|
2107
|
+
const outcome = await lookupWebFinger("acct:johndoe@example.com", { withDiagnostics: true });
|
|
2108
|
+
(0, node_assert_strict.deepStrictEqual)(outcome.resource, null);
|
|
2109
|
+
(0, node_assert_strict.deepStrictEqual)(outcome.result, "error");
|
|
2110
|
+
(0, node_assert_strict.deepStrictEqual)(outcome.statusCode, 500);
|
|
2111
|
+
(0, node_assert_strict.deepStrictEqual)(outcome.remoteHost, "example.com");
|
|
2112
|
+
});
|
|
2113
|
+
} finally {
|
|
2114
|
+
esm_default.removeRoutes();
|
|
2115
|
+
esm_default.hardReset();
|
|
2116
|
+
}
|
|
2117
|
+
});
|
|
2118
|
+
(0, _fedify_fixture.test)("lookupWebFinger() accepts a plain LookupWebFingerOptions-typed variable", async () => {
|
|
2119
|
+
esm_default.spyGlobal();
|
|
2120
|
+
try {
|
|
2121
|
+
const expected = {
|
|
2122
|
+
subject: "acct:johndoe@example.com",
|
|
2123
|
+
links: []
|
|
2124
|
+
};
|
|
2125
|
+
esm_default.get("https://example.com/.well-known/webfinger?resource=acct%3Ajohndoe%40example.com", { body: expected });
|
|
2126
|
+
(0, node_assert_strict.deepStrictEqual)(await lookupWebFinger("acct:johndoe@example.com", { userAgent: "test-agent" }), expected);
|
|
2127
|
+
} finally {
|
|
2128
|
+
esm_default.removeRoutes();
|
|
2129
|
+
esm_default.hardReset();
|
|
2130
|
+
}
|
|
2131
|
+
});
|
|
2043
2132
|
//#endregion
|
package/dist/lookup.test.js
CHANGED
|
@@ -1235,7 +1235,7 @@ var esm_default = new class FetchMock {
|
|
|
1235
1235
|
//#endregion
|
|
1236
1236
|
//#region deno.json
|
|
1237
1237
|
var name = "@fedify/webfinger";
|
|
1238
|
-
var version = "2.5.0-dev.
|
|
1238
|
+
var version = "2.5.0-dev.2446+2014cb03";
|
|
1239
1239
|
//#endregion
|
|
1240
1240
|
//#region src/body.ts
|
|
1241
1241
|
/** Decoded JSON body limit: 16 MiB. @internal */
|
|
@@ -1370,13 +1370,6 @@ const WEBFINGER_LOOKUP_SCHEME_WHITELIST = /* @__PURE__ */ new Set([
|
|
|
1370
1370
|
function getMetricResourceScheme(scheme) {
|
|
1371
1371
|
return WEBFINGER_LOOKUP_SCHEME_WHITELIST.has(scheme) ? scheme : "other";
|
|
1372
1372
|
}
|
|
1373
|
-
/**
|
|
1374
|
-
* Looks up a WebFinger resource.
|
|
1375
|
-
* @param resource The resource URL to look up.
|
|
1376
|
-
* @param options Extra options for looking up the resource.
|
|
1377
|
-
* @returns The resource descriptor, or `null` if not found.
|
|
1378
|
-
* @since 0.2.0
|
|
1379
|
-
*/
|
|
1380
1373
|
async function lookupWebFinger(resource, options = {}) {
|
|
1381
1374
|
const tracer = (options.tracerProvider ?? trace.getTracerProvider()).getTracer(name, version);
|
|
1382
1375
|
const scheme = getResourceScheme(resource);
|
|
@@ -1396,7 +1389,7 @@ async function lookupWebFinger(resource, options = {}) {
|
|
|
1396
1389
|
try {
|
|
1397
1390
|
outcome = await lookupWebFingerInternal(resource, options);
|
|
1398
1391
|
span.setStatus({ code: outcome.resource === null ? SpanStatusCode.ERROR : SpanStatusCode.OK });
|
|
1399
|
-
return outcome.resource;
|
|
1392
|
+
return options.withDiagnostics ? outcome : outcome.resource;
|
|
1400
1393
|
} catch (error) {
|
|
1401
1394
|
span.setStatus({
|
|
1402
1395
|
code: SpanStatusCode.ERROR,
|
|
@@ -1543,8 +1536,15 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
1543
1536
|
};
|
|
1544
1537
|
}
|
|
1545
1538
|
try {
|
|
1539
|
+
const parsed = JSON.parse(await readBoundedText(response, MAX_BODY_SIZE, url));
|
|
1540
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {
|
|
1541
|
+
resource: null,
|
|
1542
|
+
result: "invalid",
|
|
1543
|
+
statusCode: response.status,
|
|
1544
|
+
remoteHost
|
|
1545
|
+
};
|
|
1546
1546
|
return {
|
|
1547
|
-
resource:
|
|
1547
|
+
resource: parsed,
|
|
1548
1548
|
result: "found",
|
|
1549
1549
|
statusCode: response.status,
|
|
1550
1550
|
remoteHost
|
|
@@ -2038,5 +2038,94 @@ test("lookupWebFinger() logs DNS failures as such", async (t) => {
|
|
|
2038
2038
|
}
|
|
2039
2039
|
});
|
|
2040
2040
|
});
|
|
2041
|
+
test("lookupWebFinger() with withDiagnostics: true", {
|
|
2042
|
+
sanitizeOps: false,
|
|
2043
|
+
sanitizeResources: false
|
|
2044
|
+
}, async (t) => {
|
|
2045
|
+
await t.step("network_error", async () => {
|
|
2046
|
+
const outcome = await lookupWebFinger("acct:johndoe@fedify-test.internal", { withDiagnostics: true });
|
|
2047
|
+
deepStrictEqual(outcome.resource, null);
|
|
2048
|
+
deepStrictEqual(outcome.result, "network_error");
|
|
2049
|
+
deepStrictEqual("statusCode" in outcome, false, "no HTTP response means no status code");
|
|
2050
|
+
});
|
|
2051
|
+
esm_default.spyGlobal();
|
|
2052
|
+
try {
|
|
2053
|
+
await t.step("found", async () => {
|
|
2054
|
+
esm_default.removeRoutes();
|
|
2055
|
+
const expected = {
|
|
2056
|
+
subject: "acct:johndoe@example.com",
|
|
2057
|
+
links: []
|
|
2058
|
+
};
|
|
2059
|
+
esm_default.get("https://example.com/.well-known/webfinger?resource=acct%3Ajohndoe%40example.com", { body: expected });
|
|
2060
|
+
const outcome = await lookupWebFinger("acct:johndoe@example.com", { withDiagnostics: true });
|
|
2061
|
+
deepStrictEqual(outcome.resource, expected);
|
|
2062
|
+
deepStrictEqual(outcome.result, "found");
|
|
2063
|
+
deepStrictEqual(outcome.statusCode, 200);
|
|
2064
|
+
deepStrictEqual(outcome.remoteHost, "example.com");
|
|
2065
|
+
});
|
|
2066
|
+
await t.step("not_found", async () => {
|
|
2067
|
+
esm_default.removeRoutes();
|
|
2068
|
+
esm_default.get("begin:https://example.com/.well-known/webfinger?", { status: 404 });
|
|
2069
|
+
const outcome = await lookupWebFinger("acct:johndoe@example.com", { withDiagnostics: true });
|
|
2070
|
+
deepStrictEqual(outcome.resource, null);
|
|
2071
|
+
deepStrictEqual(outcome.result, "not_found");
|
|
2072
|
+
deepStrictEqual(outcome.statusCode, 404);
|
|
2073
|
+
deepStrictEqual(outcome.remoteHost, "example.com");
|
|
2074
|
+
});
|
|
2075
|
+
await t.step("invalid", async () => {
|
|
2076
|
+
esm_default.removeRoutes();
|
|
2077
|
+
esm_default.get("begin:https://example.com/.well-known/webfinger?", { body: "not json" });
|
|
2078
|
+
const outcome = await lookupWebFinger("acct:johndoe@example.com", { withDiagnostics: true });
|
|
2079
|
+
deepStrictEqual(outcome.resource, null);
|
|
2080
|
+
deepStrictEqual(outcome.result, "invalid");
|
|
2081
|
+
deepStrictEqual(outcome.statusCode, 200);
|
|
2082
|
+
deepStrictEqual(outcome.remoteHost, "example.com");
|
|
2083
|
+
});
|
|
2084
|
+
await t.step("invalid: a literal JSON null body is not a resource descriptor", async () => {
|
|
2085
|
+
esm_default.removeRoutes();
|
|
2086
|
+
esm_default.get("begin:https://example.com/.well-known/webfinger?", { body: "null" });
|
|
2087
|
+
const outcome = await lookupWebFinger("acct:johndoe@example.com", { withDiagnostics: true });
|
|
2088
|
+
deepStrictEqual(outcome.resource, null);
|
|
2089
|
+
deepStrictEqual(outcome.result, "invalid");
|
|
2090
|
+
deepStrictEqual(outcome.statusCode, 200);
|
|
2091
|
+
deepStrictEqual(outcome.remoteHost, "example.com");
|
|
2092
|
+
});
|
|
2093
|
+
await t.step("invalid: a JSON array body is not a resource descriptor", async () => {
|
|
2094
|
+
esm_default.removeRoutes();
|
|
2095
|
+
esm_default.get("begin:https://example.com/.well-known/webfinger?", { body: "[]" });
|
|
2096
|
+
const outcome = await lookupWebFinger("acct:johndoe@example.com", { withDiagnostics: true });
|
|
2097
|
+
deepStrictEqual(outcome.resource, null);
|
|
2098
|
+
deepStrictEqual(outcome.result, "invalid");
|
|
2099
|
+
deepStrictEqual(outcome.statusCode, 200);
|
|
2100
|
+
deepStrictEqual(outcome.remoteHost, "example.com");
|
|
2101
|
+
});
|
|
2102
|
+
await t.step("error", async () => {
|
|
2103
|
+
esm_default.removeRoutes();
|
|
2104
|
+
esm_default.get("begin:https://example.com/.well-known/webfinger?", { status: 500 });
|
|
2105
|
+
const outcome = await lookupWebFinger("acct:johndoe@example.com", { withDiagnostics: true });
|
|
2106
|
+
deepStrictEqual(outcome.resource, null);
|
|
2107
|
+
deepStrictEqual(outcome.result, "error");
|
|
2108
|
+
deepStrictEqual(outcome.statusCode, 500);
|
|
2109
|
+
deepStrictEqual(outcome.remoteHost, "example.com");
|
|
2110
|
+
});
|
|
2111
|
+
} finally {
|
|
2112
|
+
esm_default.removeRoutes();
|
|
2113
|
+
esm_default.hardReset();
|
|
2114
|
+
}
|
|
2115
|
+
});
|
|
2116
|
+
test("lookupWebFinger() accepts a plain LookupWebFingerOptions-typed variable", async () => {
|
|
2117
|
+
esm_default.spyGlobal();
|
|
2118
|
+
try {
|
|
2119
|
+
const expected = {
|
|
2120
|
+
subject: "acct:johndoe@example.com",
|
|
2121
|
+
links: []
|
|
2122
|
+
};
|
|
2123
|
+
esm_default.get("https://example.com/.well-known/webfinger?resource=acct%3Ajohndoe%40example.com", { body: expected });
|
|
2124
|
+
deepStrictEqual(await lookupWebFinger("acct:johndoe@example.com", { userAgent: "test-agent" }), expected);
|
|
2125
|
+
} finally {
|
|
2126
|
+
esm_default.removeRoutes();
|
|
2127
|
+
esm_default.hardReset();
|
|
2128
|
+
}
|
|
2129
|
+
});
|
|
2041
2130
|
//#endregion
|
|
2042
2131
|
export {};
|
package/dist/mod.cjs
CHANGED
|
@@ -4,7 +4,7 @@ let _logtape_logtape = require("@logtape/logtape");
|
|
|
4
4
|
let _opentelemetry_api = require("@opentelemetry/api");
|
|
5
5
|
//#region deno.json
|
|
6
6
|
var name = "@fedify/webfinger";
|
|
7
|
-
var version = "2.5.0-dev.
|
|
7
|
+
var version = "2.5.0-dev.2446+2014cb03";
|
|
8
8
|
//#endregion
|
|
9
9
|
//#region src/body.ts
|
|
10
10
|
/** Decoded JSON body limit: 16 MiB. @internal */
|
|
@@ -139,13 +139,6 @@ const WEBFINGER_LOOKUP_SCHEME_WHITELIST = /* @__PURE__ */ new Set([
|
|
|
139
139
|
function getMetricResourceScheme(scheme) {
|
|
140
140
|
return WEBFINGER_LOOKUP_SCHEME_WHITELIST.has(scheme) ? scheme : "other";
|
|
141
141
|
}
|
|
142
|
-
/**
|
|
143
|
-
* Looks up a WebFinger resource.
|
|
144
|
-
* @param resource The resource URL to look up.
|
|
145
|
-
* @param options Extra options for looking up the resource.
|
|
146
|
-
* @returns The resource descriptor, or `null` if not found.
|
|
147
|
-
* @since 0.2.0
|
|
148
|
-
*/
|
|
149
142
|
async function lookupWebFinger(resource, options = {}) {
|
|
150
143
|
const tracer = (options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider()).getTracer(name, version);
|
|
151
144
|
const scheme = getResourceScheme(resource);
|
|
@@ -165,7 +158,7 @@ async function lookupWebFinger(resource, options = {}) {
|
|
|
165
158
|
try {
|
|
166
159
|
outcome = await lookupWebFingerInternal(resource, options);
|
|
167
160
|
span.setStatus({ code: outcome.resource === null ? _opentelemetry_api.SpanStatusCode.ERROR : _opentelemetry_api.SpanStatusCode.OK });
|
|
168
|
-
return outcome.resource;
|
|
161
|
+
return options.withDiagnostics ? outcome : outcome.resource;
|
|
169
162
|
} catch (error) {
|
|
170
163
|
span.setStatus({
|
|
171
164
|
code: _opentelemetry_api.SpanStatusCode.ERROR,
|
|
@@ -312,8 +305,15 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
312
305
|
};
|
|
313
306
|
}
|
|
314
307
|
try {
|
|
308
|
+
const parsed = JSON.parse(await readBoundedText(response, MAX_BODY_SIZE, url));
|
|
309
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {
|
|
310
|
+
resource: null,
|
|
311
|
+
result: "invalid",
|
|
312
|
+
statusCode: response.status,
|
|
313
|
+
remoteHost
|
|
314
|
+
};
|
|
315
315
|
return {
|
|
316
|
-
resource:
|
|
316
|
+
resource: parsed,
|
|
317
317
|
result: "found",
|
|
318
318
|
statusCode: response.status,
|
|
319
319
|
remoteHost
|
package/dist/mod.d.cts
CHANGED
|
@@ -85,6 +85,32 @@ interface Link {
|
|
|
85
85
|
*/
|
|
86
86
|
type WebFingerLookupResult = "found" | "not_found" | "invalid" | "network_error" | "error";
|
|
87
87
|
/**
|
|
88
|
+
* The outcome of a {@link lookupWebFinger} call, returned in place of the
|
|
89
|
+
* plain {@link ResourceDescriptor} when `options.withDiagnostics` is `true`.
|
|
90
|
+
* @since 2.5.0
|
|
91
|
+
*/
|
|
92
|
+
interface WebFingerLookupOutcome {
|
|
93
|
+
/**
|
|
94
|
+
* The resource descriptor fetched from the remote WebFinger endpoint, or
|
|
95
|
+
* `null` if the lookup did not succeed.
|
|
96
|
+
*/
|
|
97
|
+
resource: ResourceDescriptor | null;
|
|
98
|
+
/**
|
|
99
|
+
* The terminal classification of the lookup. See
|
|
100
|
+
* {@link WebFingerLookupResult} for what each value means.
|
|
101
|
+
*/
|
|
102
|
+
result: WebFingerLookupResult;
|
|
103
|
+
/**
|
|
104
|
+
* The HTTP status code of the final response, if a response was received
|
|
105
|
+
* from the remote server.
|
|
106
|
+
*/
|
|
107
|
+
statusCode?: number;
|
|
108
|
+
/**
|
|
109
|
+
* The host of the final URL the lookup attempted to reach.
|
|
110
|
+
*/
|
|
111
|
+
remoteHost?: string;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
88
114
|
* Options for {@link lookupWebFinger}.
|
|
89
115
|
* @since 1.3.0
|
|
90
116
|
*/
|
|
@@ -138,6 +164,31 @@ interface LookupWebFingerOptions {
|
|
|
138
164
|
* @returns The resource descriptor, or `null` if not found.
|
|
139
165
|
* @since 0.2.0
|
|
140
166
|
*/
|
|
141
|
-
declare function lookupWebFinger(resource: URL | string, options?: LookupWebFingerOptions
|
|
167
|
+
declare function lookupWebFinger(resource: URL | string, options?: LookupWebFingerOptions & {
|
|
168
|
+
/**
|
|
169
|
+
* Whether to return a {@link WebFingerLookupOutcome} describing why
|
|
170
|
+
* the lookup failed, instead of collapsing every failure into `null`.
|
|
171
|
+
* Turned off by default.
|
|
172
|
+
* @since 2.5.0
|
|
173
|
+
*/
|
|
174
|
+
withDiagnostics?: false;
|
|
175
|
+
}): Promise<ResourceDescriptor | null>;
|
|
176
|
+
/**
|
|
177
|
+
* Looks up a WebFinger resource, returning diagnostics that explain why the
|
|
178
|
+
* lookup failed instead of collapsing every failure into `null`.
|
|
179
|
+
* @param resource The resource URL to look up.
|
|
180
|
+
* @param options Extra options for looking up the resource. Must set
|
|
181
|
+
* `withDiagnostics: true`.
|
|
182
|
+
* @returns The outcome of the lookup.
|
|
183
|
+
* @since 2.5.0
|
|
184
|
+
*/
|
|
185
|
+
declare function lookupWebFinger(resource: URL | string, options: LookupWebFingerOptions & {
|
|
186
|
+
/**
|
|
187
|
+
* Returns a {@link WebFingerLookupOutcome} instead of a plain
|
|
188
|
+
* {@link ResourceDescriptor}.
|
|
189
|
+
* @since 2.5.0
|
|
190
|
+
*/
|
|
191
|
+
withDiagnostics: true;
|
|
192
|
+
}): Promise<WebFingerLookupOutcome>;
|
|
142
193
|
//#endregion
|
|
143
|
-
export { Link, LookupWebFingerOptions, ResourceDescriptor, WebFingerLookupResult, lookupWebFinger };
|
|
194
|
+
export { Link, LookupWebFingerOptions, ResourceDescriptor, WebFingerLookupOutcome, WebFingerLookupResult, lookupWebFinger };
|
package/dist/mod.d.ts
CHANGED
|
@@ -85,6 +85,32 @@ interface Link {
|
|
|
85
85
|
*/
|
|
86
86
|
type WebFingerLookupResult = "found" | "not_found" | "invalid" | "network_error" | "error";
|
|
87
87
|
/**
|
|
88
|
+
* The outcome of a {@link lookupWebFinger} call, returned in place of the
|
|
89
|
+
* plain {@link ResourceDescriptor} when `options.withDiagnostics` is `true`.
|
|
90
|
+
* @since 2.5.0
|
|
91
|
+
*/
|
|
92
|
+
interface WebFingerLookupOutcome {
|
|
93
|
+
/**
|
|
94
|
+
* The resource descriptor fetched from the remote WebFinger endpoint, or
|
|
95
|
+
* `null` if the lookup did not succeed.
|
|
96
|
+
*/
|
|
97
|
+
resource: ResourceDescriptor | null;
|
|
98
|
+
/**
|
|
99
|
+
* The terminal classification of the lookup. See
|
|
100
|
+
* {@link WebFingerLookupResult} for what each value means.
|
|
101
|
+
*/
|
|
102
|
+
result: WebFingerLookupResult;
|
|
103
|
+
/**
|
|
104
|
+
* The HTTP status code of the final response, if a response was received
|
|
105
|
+
* from the remote server.
|
|
106
|
+
*/
|
|
107
|
+
statusCode?: number;
|
|
108
|
+
/**
|
|
109
|
+
* The host of the final URL the lookup attempted to reach.
|
|
110
|
+
*/
|
|
111
|
+
remoteHost?: string;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
88
114
|
* Options for {@link lookupWebFinger}.
|
|
89
115
|
* @since 1.3.0
|
|
90
116
|
*/
|
|
@@ -138,6 +164,31 @@ interface LookupWebFingerOptions {
|
|
|
138
164
|
* @returns The resource descriptor, or `null` if not found.
|
|
139
165
|
* @since 0.2.0
|
|
140
166
|
*/
|
|
141
|
-
declare function lookupWebFinger(resource: URL | string, options?: LookupWebFingerOptions
|
|
167
|
+
declare function lookupWebFinger(resource: URL | string, options?: LookupWebFingerOptions & {
|
|
168
|
+
/**
|
|
169
|
+
* Whether to return a {@link WebFingerLookupOutcome} describing why
|
|
170
|
+
* the lookup failed, instead of collapsing every failure into `null`.
|
|
171
|
+
* Turned off by default.
|
|
172
|
+
* @since 2.5.0
|
|
173
|
+
*/
|
|
174
|
+
withDiagnostics?: false;
|
|
175
|
+
}): Promise<ResourceDescriptor | null>;
|
|
176
|
+
/**
|
|
177
|
+
* Looks up a WebFinger resource, returning diagnostics that explain why the
|
|
178
|
+
* lookup failed instead of collapsing every failure into `null`.
|
|
179
|
+
* @param resource The resource URL to look up.
|
|
180
|
+
* @param options Extra options for looking up the resource. Must set
|
|
181
|
+
* `withDiagnostics: true`.
|
|
182
|
+
* @returns The outcome of the lookup.
|
|
183
|
+
* @since 2.5.0
|
|
184
|
+
*/
|
|
185
|
+
declare function lookupWebFinger(resource: URL | string, options: LookupWebFingerOptions & {
|
|
186
|
+
/**
|
|
187
|
+
* Returns a {@link WebFingerLookupOutcome} instead of a plain
|
|
188
|
+
* {@link ResourceDescriptor}.
|
|
189
|
+
* @since 2.5.0
|
|
190
|
+
*/
|
|
191
|
+
withDiagnostics: true;
|
|
192
|
+
}): Promise<WebFingerLookupOutcome>;
|
|
142
193
|
//#endregion
|
|
143
|
-
export { Link, LookupWebFingerOptions, ResourceDescriptor, WebFingerLookupResult, lookupWebFinger };
|
|
194
|
+
export { Link, LookupWebFingerOptions, ResourceDescriptor, WebFingerLookupOutcome, WebFingerLookupResult, lookupWebFinger };
|
package/dist/mod.js
CHANGED
|
@@ -3,7 +3,7 @@ import { getLogger } from "@logtape/logtape";
|
|
|
3
3
|
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
4
4
|
//#region deno.json
|
|
5
5
|
var name = "@fedify/webfinger";
|
|
6
|
-
var version = "2.5.0-dev.
|
|
6
|
+
var version = "2.5.0-dev.2446+2014cb03";
|
|
7
7
|
//#endregion
|
|
8
8
|
//#region src/body.ts
|
|
9
9
|
/** Decoded JSON body limit: 16 MiB. @internal */
|
|
@@ -138,13 +138,6 @@ const WEBFINGER_LOOKUP_SCHEME_WHITELIST = /* @__PURE__ */ new Set([
|
|
|
138
138
|
function getMetricResourceScheme(scheme) {
|
|
139
139
|
return WEBFINGER_LOOKUP_SCHEME_WHITELIST.has(scheme) ? scheme : "other";
|
|
140
140
|
}
|
|
141
|
-
/**
|
|
142
|
-
* Looks up a WebFinger resource.
|
|
143
|
-
* @param resource The resource URL to look up.
|
|
144
|
-
* @param options Extra options for looking up the resource.
|
|
145
|
-
* @returns The resource descriptor, or `null` if not found.
|
|
146
|
-
* @since 0.2.0
|
|
147
|
-
*/
|
|
148
141
|
async function lookupWebFinger(resource, options = {}) {
|
|
149
142
|
const tracer = (options.tracerProvider ?? trace.getTracerProvider()).getTracer(name, version);
|
|
150
143
|
const scheme = getResourceScheme(resource);
|
|
@@ -164,7 +157,7 @@ async function lookupWebFinger(resource, options = {}) {
|
|
|
164
157
|
try {
|
|
165
158
|
outcome = await lookupWebFingerInternal(resource, options);
|
|
166
159
|
span.setStatus({ code: outcome.resource === null ? SpanStatusCode.ERROR : SpanStatusCode.OK });
|
|
167
|
-
return outcome.resource;
|
|
160
|
+
return options.withDiagnostics ? outcome : outcome.resource;
|
|
168
161
|
} catch (error) {
|
|
169
162
|
span.setStatus({
|
|
170
163
|
code: SpanStatusCode.ERROR,
|
|
@@ -311,8 +304,15 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
311
304
|
};
|
|
312
305
|
}
|
|
313
306
|
try {
|
|
307
|
+
const parsed = JSON.parse(await readBoundedText(response, MAX_BODY_SIZE, url));
|
|
308
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {
|
|
309
|
+
resource: null,
|
|
310
|
+
result: "invalid",
|
|
311
|
+
statusCode: response.status,
|
|
312
|
+
remoteHost
|
|
313
|
+
};
|
|
314
314
|
return {
|
|
315
|
-
resource:
|
|
315
|
+
resource: parsed,
|
|
316
316
|
result: "found",
|
|
317
317
|
statusCode: response.status,
|
|
318
318
|
remoteHost
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fedify/webfinger",
|
|
3
|
-
"version": "2.5.0-dev.
|
|
3
|
+
"version": "2.5.0-dev.2446+2014cb03",
|
|
4
4
|
"homepage": "https://fedify.dev/",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"@logtape/logtape": "^2.2.0",
|
|
58
58
|
"@opentelemetry/api": "^1.9.1",
|
|
59
59
|
"es-toolkit": "1.46.1",
|
|
60
|
-
"@fedify/vocab-runtime": "2.5.0-dev.
|
|
60
|
+
"@fedify/vocab-runtime": "2.5.0-dev.2446+2014cb03"
|
|
61
61
|
},
|
|
62
62
|
"scripts": {
|
|
63
63
|
"build:self": "tsdown",
|
package/src/lookup.test.ts
CHANGED
|
@@ -6,7 +6,7 @@ import fetchMock from "fetch-mock";
|
|
|
6
6
|
import { deepStrictEqual, ok } from "node:assert/strict";
|
|
7
7
|
import dns from "node:dns/promises";
|
|
8
8
|
import type { ResourceDescriptor } from "./jrd.ts";
|
|
9
|
-
import { lookupWebFinger } from "./lookup.ts";
|
|
9
|
+
import { lookupWebFinger, type LookupWebFingerOptions } from "./lookup.ts";
|
|
10
10
|
|
|
11
11
|
test({
|
|
12
12
|
name: "lookupWebFinger()",
|
|
@@ -989,3 +989,167 @@ test("lookupWebFinger() logs DNS failures as such", async (t) => {
|
|
|
989
989
|
});
|
|
990
990
|
}
|
|
991
991
|
});
|
|
992
|
+
|
|
993
|
+
test("lookupWebFinger() with withDiagnostics: true", {
|
|
994
|
+
sanitizeOps: false,
|
|
995
|
+
sanitizeResources: false,
|
|
996
|
+
}, async (t) => {
|
|
997
|
+
// No fetch mock is active yet, so this reaches the real (non-existent)
|
|
998
|
+
// host and fails at the network layer, the same way the "connection
|
|
999
|
+
// refused" step above does.
|
|
1000
|
+
await t.step("network_error", async () => {
|
|
1001
|
+
const outcome = await lookupWebFinger(
|
|
1002
|
+
"acct:johndoe@fedify-test.internal",
|
|
1003
|
+
{ withDiagnostics: true },
|
|
1004
|
+
);
|
|
1005
|
+
deepStrictEqual(outcome.resource, null);
|
|
1006
|
+
deepStrictEqual(outcome.result, "network_error");
|
|
1007
|
+
deepStrictEqual(
|
|
1008
|
+
"statusCode" in outcome,
|
|
1009
|
+
false,
|
|
1010
|
+
"no HTTP response means no status code",
|
|
1011
|
+
);
|
|
1012
|
+
});
|
|
1013
|
+
|
|
1014
|
+
fetchMock.spyGlobal();
|
|
1015
|
+
try {
|
|
1016
|
+
await t.step("found", async () => {
|
|
1017
|
+
fetchMock.removeRoutes();
|
|
1018
|
+
const expected: ResourceDescriptor = {
|
|
1019
|
+
subject: "acct:johndoe@example.com",
|
|
1020
|
+
links: [],
|
|
1021
|
+
};
|
|
1022
|
+
fetchMock.get(
|
|
1023
|
+
"https://example.com/.well-known/webfinger?resource=acct%3Ajohndoe%40example.com",
|
|
1024
|
+
{ body: expected },
|
|
1025
|
+
);
|
|
1026
|
+
const outcome = await lookupWebFinger("acct:johndoe@example.com", {
|
|
1027
|
+
withDiagnostics: true,
|
|
1028
|
+
});
|
|
1029
|
+
deepStrictEqual(outcome.resource, expected);
|
|
1030
|
+
deepStrictEqual(outcome.result, "found");
|
|
1031
|
+
deepStrictEqual(outcome.statusCode, 200);
|
|
1032
|
+
deepStrictEqual(outcome.remoteHost, "example.com");
|
|
1033
|
+
});
|
|
1034
|
+
|
|
1035
|
+
await t.step("not_found", async () => {
|
|
1036
|
+
fetchMock.removeRoutes();
|
|
1037
|
+
fetchMock.get(
|
|
1038
|
+
"begin:https://example.com/.well-known/webfinger?",
|
|
1039
|
+
{ status: 404 },
|
|
1040
|
+
);
|
|
1041
|
+
const outcome = await lookupWebFinger("acct:johndoe@example.com", {
|
|
1042
|
+
withDiagnostics: true,
|
|
1043
|
+
});
|
|
1044
|
+
deepStrictEqual(outcome.resource, null);
|
|
1045
|
+
deepStrictEqual(outcome.result, "not_found");
|
|
1046
|
+
deepStrictEqual(outcome.statusCode, 404);
|
|
1047
|
+
deepStrictEqual(outcome.remoteHost, "example.com");
|
|
1048
|
+
});
|
|
1049
|
+
|
|
1050
|
+
await t.step("invalid", async () => {
|
|
1051
|
+
fetchMock.removeRoutes();
|
|
1052
|
+
fetchMock.get(
|
|
1053
|
+
"begin:https://example.com/.well-known/webfinger?",
|
|
1054
|
+
{ body: "not json" },
|
|
1055
|
+
);
|
|
1056
|
+
const outcome = await lookupWebFinger("acct:johndoe@example.com", {
|
|
1057
|
+
withDiagnostics: true,
|
|
1058
|
+
});
|
|
1059
|
+
deepStrictEqual(outcome.resource, null);
|
|
1060
|
+
deepStrictEqual(outcome.result, "invalid");
|
|
1061
|
+
deepStrictEqual(outcome.statusCode, 200);
|
|
1062
|
+
deepStrictEqual(outcome.remoteHost, "example.com");
|
|
1063
|
+
});
|
|
1064
|
+
|
|
1065
|
+
await t.step(
|
|
1066
|
+
"invalid: a literal JSON null body is not a resource descriptor",
|
|
1067
|
+
async () => {
|
|
1068
|
+
// JSON.parse("null") succeeds and yields the JS value `null`.
|
|
1069
|
+
// Regression test for a server that returns 200 with a literal
|
|
1070
|
+
// `null` body instead of an object: this must not be classified
|
|
1071
|
+
// as "found" with a null resource.
|
|
1072
|
+
fetchMock.removeRoutes();
|
|
1073
|
+
fetchMock.get(
|
|
1074
|
+
"begin:https://example.com/.well-known/webfinger?",
|
|
1075
|
+
{ body: "null" },
|
|
1076
|
+
);
|
|
1077
|
+
const outcome = await lookupWebFinger("acct:johndoe@example.com", {
|
|
1078
|
+
withDiagnostics: true,
|
|
1079
|
+
});
|
|
1080
|
+
deepStrictEqual(outcome.resource, null);
|
|
1081
|
+
deepStrictEqual(outcome.result, "invalid");
|
|
1082
|
+
deepStrictEqual(outcome.statusCode, 200);
|
|
1083
|
+
deepStrictEqual(outcome.remoteHost, "example.com");
|
|
1084
|
+
},
|
|
1085
|
+
);
|
|
1086
|
+
|
|
1087
|
+
await t.step(
|
|
1088
|
+
"invalid: a JSON array body is not a resource descriptor",
|
|
1089
|
+
async () => {
|
|
1090
|
+
// typeof [] === "object" and [] !== null, so the null-only guard
|
|
1091
|
+
// above does not catch an array body; it needs its own check.
|
|
1092
|
+
fetchMock.removeRoutes();
|
|
1093
|
+
fetchMock.get(
|
|
1094
|
+
"begin:https://example.com/.well-known/webfinger?",
|
|
1095
|
+
{ body: "[]" },
|
|
1096
|
+
);
|
|
1097
|
+
const outcome = await lookupWebFinger("acct:johndoe@example.com", {
|
|
1098
|
+
withDiagnostics: true,
|
|
1099
|
+
});
|
|
1100
|
+
deepStrictEqual(outcome.resource, null);
|
|
1101
|
+
deepStrictEqual(outcome.result, "invalid");
|
|
1102
|
+
deepStrictEqual(outcome.statusCode, 200);
|
|
1103
|
+
deepStrictEqual(outcome.remoteHost, "example.com");
|
|
1104
|
+
},
|
|
1105
|
+
);
|
|
1106
|
+
|
|
1107
|
+
await t.step("error", async () => {
|
|
1108
|
+
fetchMock.removeRoutes();
|
|
1109
|
+
fetchMock.get(
|
|
1110
|
+
"begin:https://example.com/.well-known/webfinger?",
|
|
1111
|
+
{ status: 500 },
|
|
1112
|
+
);
|
|
1113
|
+
const outcome = await lookupWebFinger("acct:johndoe@example.com", {
|
|
1114
|
+
withDiagnostics: true,
|
|
1115
|
+
});
|
|
1116
|
+
deepStrictEqual(outcome.resource, null);
|
|
1117
|
+
deepStrictEqual(outcome.result, "error");
|
|
1118
|
+
deepStrictEqual(outcome.statusCode, 500);
|
|
1119
|
+
deepStrictEqual(outcome.remoteHost, "example.com");
|
|
1120
|
+
});
|
|
1121
|
+
} finally {
|
|
1122
|
+
fetchMock.removeRoutes();
|
|
1123
|
+
fetchMock.hardReset();
|
|
1124
|
+
}
|
|
1125
|
+
});
|
|
1126
|
+
|
|
1127
|
+
test(
|
|
1128
|
+
"lookupWebFinger() accepts a plain LookupWebFingerOptions-typed variable",
|
|
1129
|
+
async () => {
|
|
1130
|
+
// Regression test: a variable typed as `LookupWebFingerOptions`, built
|
|
1131
|
+
// separately from the call site and never mentioning `withDiagnostics`,
|
|
1132
|
+
// must still match the default overload. This is a type-level
|
|
1133
|
+
// concern; the point of this test is that the file compiles at all.
|
|
1134
|
+
fetchMock.spyGlobal();
|
|
1135
|
+
try {
|
|
1136
|
+
const expected: ResourceDescriptor = {
|
|
1137
|
+
subject: "acct:johndoe@example.com",
|
|
1138
|
+
links: [],
|
|
1139
|
+
};
|
|
1140
|
+
fetchMock.get(
|
|
1141
|
+
"https://example.com/.well-known/webfinger?resource=acct%3Ajohndoe%40example.com",
|
|
1142
|
+
{ body: expected },
|
|
1143
|
+
);
|
|
1144
|
+
const options: LookupWebFingerOptions = { userAgent: "test-agent" };
|
|
1145
|
+
const result = await lookupWebFinger(
|
|
1146
|
+
"acct:johndoe@example.com",
|
|
1147
|
+
options,
|
|
1148
|
+
);
|
|
1149
|
+
deepStrictEqual(result, expected);
|
|
1150
|
+
} finally {
|
|
1151
|
+
fetchMock.removeRoutes();
|
|
1152
|
+
fetchMock.hardReset();
|
|
1153
|
+
}
|
|
1154
|
+
},
|
|
1155
|
+
);
|
package/src/lookup.ts
CHANGED
|
@@ -124,10 +124,33 @@ function getMetricResourceScheme(scheme: string): string {
|
|
|
124
124
|
return WEBFINGER_LOOKUP_SCHEME_WHITELIST.has(scheme) ? scheme : "other";
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
|
|
127
|
+
/**
|
|
128
|
+
* The outcome of a {@link lookupWebFinger} call, returned in place of the
|
|
129
|
+
* plain {@link ResourceDescriptor} when `options.withDiagnostics` is `true`.
|
|
130
|
+
* @since 2.5.0
|
|
131
|
+
*/
|
|
132
|
+
export interface WebFingerLookupOutcome {
|
|
133
|
+
/**
|
|
134
|
+
* The resource descriptor fetched from the remote WebFinger endpoint, or
|
|
135
|
+
* `null` if the lookup did not succeed.
|
|
136
|
+
*/
|
|
128
137
|
resource: ResourceDescriptor | null;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* The terminal classification of the lookup. See
|
|
141
|
+
* {@link WebFingerLookupResult} for what each value means.
|
|
142
|
+
*/
|
|
129
143
|
result: WebFingerLookupResult;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* The HTTP status code of the final response, if a response was received
|
|
147
|
+
* from the remote server.
|
|
148
|
+
*/
|
|
130
149
|
statusCode?: number;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* The host of the final URL the lookup attempted to reach.
|
|
153
|
+
*/
|
|
131
154
|
remoteHost?: string;
|
|
132
155
|
}
|
|
133
156
|
|
|
@@ -193,8 +216,42 @@ export interface LookupWebFingerOptions {
|
|
|
193
216
|
*/
|
|
194
217
|
export async function lookupWebFinger(
|
|
195
218
|
resource: URL | string,
|
|
196
|
-
options
|
|
197
|
-
|
|
219
|
+
options?: LookupWebFingerOptions & {
|
|
220
|
+
/**
|
|
221
|
+
* Whether to return a {@link WebFingerLookupOutcome} describing why
|
|
222
|
+
* the lookup failed, instead of collapsing every failure into `null`.
|
|
223
|
+
* Turned off by default.
|
|
224
|
+
* @since 2.5.0
|
|
225
|
+
*/
|
|
226
|
+
withDiagnostics?: false;
|
|
227
|
+
},
|
|
228
|
+
): Promise<ResourceDescriptor | null>;
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Looks up a WebFinger resource, returning diagnostics that explain why the
|
|
232
|
+
* lookup failed instead of collapsing every failure into `null`.
|
|
233
|
+
* @param resource The resource URL to look up.
|
|
234
|
+
* @param options Extra options for looking up the resource. Must set
|
|
235
|
+
* `withDiagnostics: true`.
|
|
236
|
+
* @returns The outcome of the lookup.
|
|
237
|
+
* @since 2.5.0
|
|
238
|
+
*/
|
|
239
|
+
export async function lookupWebFinger(
|
|
240
|
+
resource: URL | string,
|
|
241
|
+
options: LookupWebFingerOptions & {
|
|
242
|
+
/**
|
|
243
|
+
* Returns a {@link WebFingerLookupOutcome} instead of a plain
|
|
244
|
+
* {@link ResourceDescriptor}.
|
|
245
|
+
* @since 2.5.0
|
|
246
|
+
*/
|
|
247
|
+
withDiagnostics: true;
|
|
248
|
+
},
|
|
249
|
+
): Promise<WebFingerLookupOutcome>;
|
|
250
|
+
|
|
251
|
+
export async function lookupWebFinger(
|
|
252
|
+
resource: URL | string,
|
|
253
|
+
options: LookupWebFingerOptions & { withDiagnostics?: boolean } = {},
|
|
254
|
+
): Promise<ResourceDescriptor | null | WebFingerLookupOutcome> {
|
|
198
255
|
const tracerProvider = options.tracerProvider ?? trace.getTracerProvider();
|
|
199
256
|
const tracer = tracerProvider.getTracer(
|
|
200
257
|
metadata.name,
|
|
@@ -228,7 +285,7 @@ export async function lookupWebFinger(
|
|
|
228
285
|
? SpanStatusCode.ERROR
|
|
229
286
|
: SpanStatusCode.OK,
|
|
230
287
|
});
|
|
231
|
-
return outcome.resource;
|
|
288
|
+
return options.withDiagnostics ? outcome : outcome.resource;
|
|
232
289
|
} catch (error) {
|
|
233
290
|
span.setStatus({
|
|
234
291
|
code: SpanStatusCode.ERROR,
|
|
@@ -438,11 +495,24 @@ async function lookupWebFingerInternal(
|
|
|
438
495
|
};
|
|
439
496
|
}
|
|
440
497
|
try {
|
|
441
|
-
const parsed = JSON.parse(
|
|
498
|
+
const parsed: unknown = JSON.parse(
|
|
442
499
|
await readBoundedText(response, MAX_BODY_SIZE, url),
|
|
443
|
-
)
|
|
500
|
+
);
|
|
501
|
+
// A body that parses to anything other than a JSON object—such as
|
|
502
|
+
// a literal `null`, a number, or an array—is not a resource
|
|
503
|
+
// descriptor, even though JSON.parse() accepts it without error.
|
|
504
|
+
if (
|
|
505
|
+
typeof parsed !== "object" || parsed === null || Array.isArray(parsed)
|
|
506
|
+
) {
|
|
507
|
+
return {
|
|
508
|
+
resource: null,
|
|
509
|
+
result: "invalid",
|
|
510
|
+
statusCode: response.status,
|
|
511
|
+
remoteHost,
|
|
512
|
+
};
|
|
513
|
+
}
|
|
444
514
|
return {
|
|
445
|
-
resource: parsed,
|
|
515
|
+
resource: parsed as ResourceDescriptor,
|
|
446
516
|
result: "found",
|
|
447
517
|
statusCode: response.status,
|
|
448
518
|
remoteHost,
|