@fedify/webfinger 2.2.19 → 2.2.20
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 +440 -109
- package/dist/lookup.test.js +441 -109
- package/dist/mod.cjs +143 -84
- package/dist/mod.d.ts +1 -1
- package/dist/mod.js +144 -85
- package/package.json +3 -3
- package/src/lookup-impl.ts +234 -0
- package/src/lookup.test.ts +469 -36
- package/src/lookup.ts +2 -145
package/dist/mod.cjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let _opentelemetry_api = require("@opentelemetry/api");
|
|
2
3
|
let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
|
|
4
|
+
let _fedify_vocab_runtime_internal = require("@fedify/vocab-runtime/internal");
|
|
3
5
|
let _logtape_logtape = require("@logtape/logtape");
|
|
4
|
-
let _opentelemetry_api = require("@opentelemetry/api");
|
|
5
6
|
//#region deno.json
|
|
6
7
|
var name = "@fedify/webfinger";
|
|
7
|
-
var version = "2.2.
|
|
8
|
+
var version = "2.2.20";
|
|
8
9
|
//#endregion
|
|
9
10
|
//#region src/body.ts
|
|
10
11
|
/** Decoded JSON body limit: 16 MiB. @internal */
|
|
@@ -80,7 +81,7 @@ async function readBoundedText(message, maxBytes, url) {
|
|
|
80
81
|
}
|
|
81
82
|
}
|
|
82
83
|
//#endregion
|
|
83
|
-
//#region src/lookup.ts
|
|
84
|
+
//#region src/lookup-impl.ts
|
|
84
85
|
const logger = (0, _logtape_logtape.getLogger)([
|
|
85
86
|
"fedify",
|
|
86
87
|
"webfinger",
|
|
@@ -88,36 +89,16 @@ const logger = (0, _logtape_logtape.getLogger)([
|
|
|
88
89
|
]);
|
|
89
90
|
const DEFAULT_MAX_REDIRECTION = 5;
|
|
90
91
|
/**
|
|
91
|
-
* Looks up a WebFinger resource.
|
|
92
|
+
* Looks up a WebFinger resource, opening a fetch session per hop. This is
|
|
93
|
+
* the implementation of {@link lookupWebFinger}, which is not exported from
|
|
94
|
+
* the package entry point.
|
|
92
95
|
* @param resource The resource URL to look up.
|
|
93
96
|
* @param options Extra options for looking up the resource.
|
|
97
|
+
* @param deps The dependencies.
|
|
94
98
|
* @returns The resource descriptor, or `null` if not found.
|
|
95
|
-
* @
|
|
99
|
+
* @internal
|
|
96
100
|
*/
|
|
97
|
-
async function
|
|
98
|
-
return await (options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider()).getTracer(name, version).startActiveSpan("webfinger.lookup", {
|
|
99
|
-
kind: _opentelemetry_api.SpanKind.CLIENT,
|
|
100
|
-
attributes: {
|
|
101
|
-
"webfinger.resource": resource.toString(),
|
|
102
|
-
"webfinger.resource.scheme": typeof resource === "string" ? resource.replace(/:.*$/, "") : resource.protocol.replace(/:$/, "")
|
|
103
|
-
}
|
|
104
|
-
}, async (span) => {
|
|
105
|
-
try {
|
|
106
|
-
const result = await lookupWebFingerInternal(resource, options);
|
|
107
|
-
span.setStatus({ code: result === null ? _opentelemetry_api.SpanStatusCode.ERROR : _opentelemetry_api.SpanStatusCode.OK });
|
|
108
|
-
return result;
|
|
109
|
-
} catch (error) {
|
|
110
|
-
span.setStatus({
|
|
111
|
-
code: _opentelemetry_api.SpanStatusCode.ERROR,
|
|
112
|
-
message: String(error)
|
|
113
|
-
});
|
|
114
|
-
throw error;
|
|
115
|
-
} finally {
|
|
116
|
-
span.end();
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
async function lookupWebFingerInternal(resource, options = {}) {
|
|
101
|
+
async function lookupWebFingerInternal(resource, options = {}, deps = {}) {
|
|
121
102
|
if (typeof resource === "string") resource = new URL(resource);
|
|
122
103
|
let protocol = "https:";
|
|
123
104
|
let server;
|
|
@@ -132,76 +113,154 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
132
113
|
}
|
|
133
114
|
let url = new URL(`${protocol}//${server}/.well-known/webfinger`);
|
|
134
115
|
url.searchParams.set("resource", resource.href);
|
|
116
|
+
const openHop = deps.openHop ?? ((hopUrl) => options.allowPrivateAddress === true ? (0, _fedify_vocab_runtime_internal.openDirectFetch)(hopUrl) : (0, _fedify_vocab_runtime_internal.openPublicFetch)(hopUrl, { signal: options.signal }));
|
|
135
117
|
let redirected = 0;
|
|
136
118
|
while (true) {
|
|
137
119
|
logger.debug("Fetching WebFinger resource descriptor from {url}...", { url: url.href });
|
|
138
|
-
|
|
139
|
-
if (
|
|
140
|
-
|
|
141
|
-
} catch (e) {
|
|
142
|
-
if (e instanceof _fedify_vocab_runtime.UrlError) {
|
|
143
|
-
if (e.reason === "dns") logger.debug("DNS lookup failed for {url}", {
|
|
144
|
-
url: url.href,
|
|
145
|
-
error: e
|
|
146
|
-
});
|
|
147
|
-
else logger.error("Invalid URL for WebFinger resource descriptor: {error}", { error: e });
|
|
148
|
-
return null;
|
|
149
|
-
}
|
|
150
|
-
throw e;
|
|
151
|
-
}
|
|
120
|
+
const session = await openSession(url, openHop, options.signal);
|
|
121
|
+
if (session == null) return null;
|
|
122
|
+
let hop;
|
|
152
123
|
try {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
124
|
+
hop = await fetchHop(session, url, options);
|
|
125
|
+
} finally {
|
|
126
|
+
await session.close();
|
|
127
|
+
}
|
|
128
|
+
if ("result" in hop) return hop.result;
|
|
129
|
+
redirected++;
|
|
130
|
+
const maxRedirection = options.maxRedirection ?? DEFAULT_MAX_REDIRECTION;
|
|
131
|
+
if (redirected >= maxRedirection) {
|
|
132
|
+
logger.error("Too many redirections ({redirections}) while fetching WebFinger resource descriptor.", { redirections: redirected });
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
const redirectedUrl = new URL(hop.redirect, url);
|
|
136
|
+
if (redirectedUrl.protocol !== url.protocol) {
|
|
137
|
+
logger.error("Redirected to a different protocol ({protocol} to {redirectedProtocol}) while fetching WebFinger resource descriptor.", {
|
|
138
|
+
protocol: url.protocol,
|
|
139
|
+
redirectedProtocol: redirectedUrl.protocol
|
|
160
140
|
});
|
|
161
|
-
|
|
162
|
-
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
url = redirectedUrl;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Opens a fetch session for a hop. A disallowed URL, a failed DNS lookup,
|
|
148
|
+
* or an aborted lookup yields `null` instead of an error.
|
|
149
|
+
*/
|
|
150
|
+
async function openSession(url, openHop, signal) {
|
|
151
|
+
try {
|
|
152
|
+
return await openHop(url.href);
|
|
153
|
+
} catch (e) {
|
|
154
|
+
if (e instanceof _fedify_vocab_runtime.UrlError) {
|
|
155
|
+
if (e.reason === "dns") logger.debug("DNS lookup failed for {url}", {
|
|
163
156
|
url: url.href,
|
|
164
|
-
error
|
|
157
|
+
error: e
|
|
165
158
|
});
|
|
159
|
+
else logger.error("Invalid URL for WebFinger resource descriptor: {error}", { error: e });
|
|
166
160
|
return null;
|
|
167
161
|
}
|
|
168
|
-
if (
|
|
169
|
-
|
|
170
|
-
const maxRedirection = options.maxRedirection ?? DEFAULT_MAX_REDIRECTION;
|
|
171
|
-
if (redirected >= maxRedirection) {
|
|
172
|
-
logger.error("Too many redirections ({redirections}) while fetching WebFinger resource descriptor.", { redirections: redirected });
|
|
173
|
-
return null;
|
|
174
|
-
}
|
|
175
|
-
const redirectedUrl = new URL(response.headers.get("Location"), response.url == null || response.url === "" ? url : response.url);
|
|
176
|
-
if (redirectedUrl.protocol !== url.protocol) {
|
|
177
|
-
logger.error("Redirected to a different protocol ({protocol} to {redirectedProtocol}) while fetching WebFinger resource descriptor.", {
|
|
178
|
-
protocol: url.protocol,
|
|
179
|
-
redirectedProtocol: redirectedUrl.protocol
|
|
180
|
-
});
|
|
181
|
-
return null;
|
|
182
|
-
}
|
|
183
|
-
url = redirectedUrl;
|
|
184
|
-
continue;
|
|
185
|
-
}
|
|
186
|
-
if (!response.ok) {
|
|
187
|
-
logger.debug("Failed to fetch WebFinger resource descriptor: {status} {statusText}.", {
|
|
162
|
+
if (signal?.aborted) {
|
|
163
|
+
logger.debug("Failed to fetch WebFinger resource descriptor: {error}", {
|
|
188
164
|
url: url.href,
|
|
189
|
-
|
|
190
|
-
statusText: response.statusText
|
|
165
|
+
error: e
|
|
191
166
|
});
|
|
192
167
|
return null;
|
|
193
168
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
169
|
+
throw e;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Fetches a hop with the given session, and consumes the response body or
|
|
174
|
+
* starts cancelling it before returning. The caller closes the session.
|
|
175
|
+
*/
|
|
176
|
+
async function fetchHop(session, url, options) {
|
|
177
|
+
let response;
|
|
178
|
+
try {
|
|
179
|
+
response = await session.fetch(url, {
|
|
180
|
+
headers: {
|
|
181
|
+
Accept: "application/jrd+json",
|
|
182
|
+
"User-Agent": typeof options.userAgent === "string" ? options.userAgent : (0, _fedify_vocab_runtime.getUserAgent)(options.userAgent)
|
|
183
|
+
},
|
|
184
|
+
redirect: "manual",
|
|
185
|
+
signal: options.signal
|
|
186
|
+
});
|
|
187
|
+
} catch (error) {
|
|
188
|
+
logger.debug("Failed to fetch WebFinger resource descriptor: {error}", {
|
|
189
|
+
url: url.href,
|
|
190
|
+
error
|
|
191
|
+
});
|
|
192
|
+
return { result: null };
|
|
193
|
+
}
|
|
194
|
+
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
|
|
195
|
+
cancelBody(response, url);
|
|
196
|
+
return { redirect: response.headers.get("Location") };
|
|
197
|
+
}
|
|
198
|
+
if (!response.ok) {
|
|
199
|
+
logger.debug("Failed to fetch WebFinger resource descriptor: {status} {statusText}.", {
|
|
200
|
+
url: url.href,
|
|
201
|
+
status: response.status,
|
|
202
|
+
statusText: response.statusText
|
|
203
|
+
});
|
|
204
|
+
cancelBody(response, url);
|
|
205
|
+
return { result: null };
|
|
206
|
+
}
|
|
207
|
+
try {
|
|
208
|
+
return { result: JSON.parse(await readBoundedText(response, MAX_BODY_SIZE, url)) };
|
|
209
|
+
} catch (e) {
|
|
210
|
+
if (e instanceof BodyTooLargeError) return { result: null };
|
|
211
|
+
if (e instanceof SyntaxError) {
|
|
212
|
+
logger.debug("Failed to parse WebFinger resource descriptor as JSON: {error}", { error: e });
|
|
213
|
+
return { result: null };
|
|
203
214
|
}
|
|
215
|
+
throw e;
|
|
204
216
|
}
|
|
205
217
|
}
|
|
218
|
+
/**
|
|
219
|
+
* Starts cancelling a response body that is not read, without waiting for it
|
|
220
|
+
* to settle. The cancellation of a branch of a tee (e.g., a body cloned by
|
|
221
|
+
* a fetch mock) settles only after the other branch is cancelled too, per
|
|
222
|
+
* the Streams standard, so awaiting it could block forever. A failure is
|
|
223
|
+
* only logged.
|
|
224
|
+
*/
|
|
225
|
+
function cancelBody(response, url) {
|
|
226
|
+
response.body?.cancel().catch((error) => {
|
|
227
|
+
logger.debug("Failed to cancel the response body from {url}: {error}", {
|
|
228
|
+
url: url.href,
|
|
229
|
+
error
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
//#endregion
|
|
234
|
+
//#region src/lookup.ts
|
|
235
|
+
/**
|
|
236
|
+
* Looks up a WebFinger resource.
|
|
237
|
+
* @param resource The resource URL to look up.
|
|
238
|
+
* @param options Extra options for looking up the resource.
|
|
239
|
+
* @returns The resource descriptor, or `null` if not found.
|
|
240
|
+
* @since 0.2.0
|
|
241
|
+
*/
|
|
242
|
+
async function lookupWebFinger(resource, options = {}) {
|
|
243
|
+
return await (options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider()).getTracer(name, version).startActiveSpan("webfinger.lookup", {
|
|
244
|
+
kind: _opentelemetry_api.SpanKind.CLIENT,
|
|
245
|
+
attributes: {
|
|
246
|
+
"webfinger.resource": resource.toString(),
|
|
247
|
+
"webfinger.resource.scheme": typeof resource === "string" ? resource.replace(/:.*$/, "") : resource.protocol.replace(/:$/, "")
|
|
248
|
+
}
|
|
249
|
+
}, async (span) => {
|
|
250
|
+
try {
|
|
251
|
+
const result = await lookupWebFingerInternal(resource, options);
|
|
252
|
+
span.setStatus({ code: result === null ? _opentelemetry_api.SpanStatusCode.ERROR : _opentelemetry_api.SpanStatusCode.OK });
|
|
253
|
+
return result;
|
|
254
|
+
} catch (error) {
|
|
255
|
+
span.setStatus({
|
|
256
|
+
code: _opentelemetry_api.SpanStatusCode.ERROR,
|
|
257
|
+
message: String(error)
|
|
258
|
+
});
|
|
259
|
+
throw error;
|
|
260
|
+
} finally {
|
|
261
|
+
span.end();
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
}
|
|
206
265
|
//#endregion
|
|
207
266
|
exports.lookupWebFinger = lookupWebFinger;
|
package/dist/mod.d.ts
CHANGED
package/dist/mod.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { FetchError, UrlError, getUserAgent, validatePublicUrl } from "@fedify/vocab-runtime";
|
|
2
|
-
import { getLogger } from "@logtape/logtape";
|
|
3
1
|
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
2
|
+
import { FetchError, UrlError, getUserAgent } from "@fedify/vocab-runtime";
|
|
3
|
+
import { openDirectFetch, openPublicFetch } from "@fedify/vocab-runtime/internal";
|
|
4
|
+
import { getLogger } from "@logtape/logtape";
|
|
4
5
|
//#region deno.json
|
|
5
6
|
var name = "@fedify/webfinger";
|
|
6
|
-
var version = "2.2.
|
|
7
|
+
var version = "2.2.20";
|
|
7
8
|
//#endregion
|
|
8
9
|
//#region src/body.ts
|
|
9
10
|
/** Decoded JSON body limit: 16 MiB. @internal */
|
|
@@ -79,7 +80,7 @@ async function readBoundedText(message, maxBytes, url) {
|
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
82
|
//#endregion
|
|
82
|
-
//#region src/lookup.ts
|
|
83
|
+
//#region src/lookup-impl.ts
|
|
83
84
|
const logger = getLogger([
|
|
84
85
|
"fedify",
|
|
85
86
|
"webfinger",
|
|
@@ -87,36 +88,16 @@ const logger = getLogger([
|
|
|
87
88
|
]);
|
|
88
89
|
const DEFAULT_MAX_REDIRECTION = 5;
|
|
89
90
|
/**
|
|
90
|
-
* Looks up a WebFinger resource.
|
|
91
|
+
* Looks up a WebFinger resource, opening a fetch session per hop. This is
|
|
92
|
+
* the implementation of {@link lookupWebFinger}, which is not exported from
|
|
93
|
+
* the package entry point.
|
|
91
94
|
* @param resource The resource URL to look up.
|
|
92
95
|
* @param options Extra options for looking up the resource.
|
|
96
|
+
* @param deps The dependencies.
|
|
93
97
|
* @returns The resource descriptor, or `null` if not found.
|
|
94
|
-
* @
|
|
98
|
+
* @internal
|
|
95
99
|
*/
|
|
96
|
-
async function
|
|
97
|
-
return await (options.tracerProvider ?? trace.getTracerProvider()).getTracer(name, version).startActiveSpan("webfinger.lookup", {
|
|
98
|
-
kind: SpanKind.CLIENT,
|
|
99
|
-
attributes: {
|
|
100
|
-
"webfinger.resource": resource.toString(),
|
|
101
|
-
"webfinger.resource.scheme": typeof resource === "string" ? resource.replace(/:.*$/, "") : resource.protocol.replace(/:$/, "")
|
|
102
|
-
}
|
|
103
|
-
}, async (span) => {
|
|
104
|
-
try {
|
|
105
|
-
const result = await lookupWebFingerInternal(resource, options);
|
|
106
|
-
span.setStatus({ code: result === null ? SpanStatusCode.ERROR : SpanStatusCode.OK });
|
|
107
|
-
return result;
|
|
108
|
-
} catch (error) {
|
|
109
|
-
span.setStatus({
|
|
110
|
-
code: SpanStatusCode.ERROR,
|
|
111
|
-
message: String(error)
|
|
112
|
-
});
|
|
113
|
-
throw error;
|
|
114
|
-
} finally {
|
|
115
|
-
span.end();
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
async function lookupWebFingerInternal(resource, options = {}) {
|
|
100
|
+
async function lookupWebFingerInternal(resource, options = {}, deps = {}) {
|
|
120
101
|
if (typeof resource === "string") resource = new URL(resource);
|
|
121
102
|
let protocol = "https:";
|
|
122
103
|
let server;
|
|
@@ -131,76 +112,154 @@ async function lookupWebFingerInternal(resource, options = {}) {
|
|
|
131
112
|
}
|
|
132
113
|
let url = new URL(`${protocol}//${server}/.well-known/webfinger`);
|
|
133
114
|
url.searchParams.set("resource", resource.href);
|
|
115
|
+
const openHop = deps.openHop ?? ((hopUrl) => options.allowPrivateAddress === true ? openDirectFetch(hopUrl) : openPublicFetch(hopUrl, { signal: options.signal }));
|
|
134
116
|
let redirected = 0;
|
|
135
117
|
while (true) {
|
|
136
118
|
logger.debug("Fetching WebFinger resource descriptor from {url}...", { url: url.href });
|
|
137
|
-
|
|
138
|
-
if (
|
|
139
|
-
|
|
140
|
-
} catch (e) {
|
|
141
|
-
if (e instanceof UrlError) {
|
|
142
|
-
if (e.reason === "dns") logger.debug("DNS lookup failed for {url}", {
|
|
143
|
-
url: url.href,
|
|
144
|
-
error: e
|
|
145
|
-
});
|
|
146
|
-
else logger.error("Invalid URL for WebFinger resource descriptor: {error}", { error: e });
|
|
147
|
-
return null;
|
|
148
|
-
}
|
|
149
|
-
throw e;
|
|
150
|
-
}
|
|
119
|
+
const session = await openSession(url, openHop, options.signal);
|
|
120
|
+
if (session == null) return null;
|
|
121
|
+
let hop;
|
|
151
122
|
try {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
123
|
+
hop = await fetchHop(session, url, options);
|
|
124
|
+
} finally {
|
|
125
|
+
await session.close();
|
|
126
|
+
}
|
|
127
|
+
if ("result" in hop) return hop.result;
|
|
128
|
+
redirected++;
|
|
129
|
+
const maxRedirection = options.maxRedirection ?? DEFAULT_MAX_REDIRECTION;
|
|
130
|
+
if (redirected >= maxRedirection) {
|
|
131
|
+
logger.error("Too many redirections ({redirections}) while fetching WebFinger resource descriptor.", { redirections: redirected });
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
const redirectedUrl = new URL(hop.redirect, url);
|
|
135
|
+
if (redirectedUrl.protocol !== url.protocol) {
|
|
136
|
+
logger.error("Redirected to a different protocol ({protocol} to {redirectedProtocol}) while fetching WebFinger resource descriptor.", {
|
|
137
|
+
protocol: url.protocol,
|
|
138
|
+
redirectedProtocol: redirectedUrl.protocol
|
|
159
139
|
});
|
|
160
|
-
|
|
161
|
-
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
url = redirectedUrl;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Opens a fetch session for a hop. A disallowed URL, a failed DNS lookup,
|
|
147
|
+
* or an aborted lookup yields `null` instead of an error.
|
|
148
|
+
*/
|
|
149
|
+
async function openSession(url, openHop, signal) {
|
|
150
|
+
try {
|
|
151
|
+
return await openHop(url.href);
|
|
152
|
+
} catch (e) {
|
|
153
|
+
if (e instanceof UrlError) {
|
|
154
|
+
if (e.reason === "dns") logger.debug("DNS lookup failed for {url}", {
|
|
162
155
|
url: url.href,
|
|
163
|
-
error
|
|
156
|
+
error: e
|
|
164
157
|
});
|
|
158
|
+
else logger.error("Invalid URL for WebFinger resource descriptor: {error}", { error: e });
|
|
165
159
|
return null;
|
|
166
160
|
}
|
|
167
|
-
if (
|
|
168
|
-
|
|
169
|
-
const maxRedirection = options.maxRedirection ?? DEFAULT_MAX_REDIRECTION;
|
|
170
|
-
if (redirected >= maxRedirection) {
|
|
171
|
-
logger.error("Too many redirections ({redirections}) while fetching WebFinger resource descriptor.", { redirections: redirected });
|
|
172
|
-
return null;
|
|
173
|
-
}
|
|
174
|
-
const redirectedUrl = new URL(response.headers.get("Location"), response.url == null || response.url === "" ? url : response.url);
|
|
175
|
-
if (redirectedUrl.protocol !== url.protocol) {
|
|
176
|
-
logger.error("Redirected to a different protocol ({protocol} to {redirectedProtocol}) while fetching WebFinger resource descriptor.", {
|
|
177
|
-
protocol: url.protocol,
|
|
178
|
-
redirectedProtocol: redirectedUrl.protocol
|
|
179
|
-
});
|
|
180
|
-
return null;
|
|
181
|
-
}
|
|
182
|
-
url = redirectedUrl;
|
|
183
|
-
continue;
|
|
184
|
-
}
|
|
185
|
-
if (!response.ok) {
|
|
186
|
-
logger.debug("Failed to fetch WebFinger resource descriptor: {status} {statusText}.", {
|
|
161
|
+
if (signal?.aborted) {
|
|
162
|
+
logger.debug("Failed to fetch WebFinger resource descriptor: {error}", {
|
|
187
163
|
url: url.href,
|
|
188
|
-
|
|
189
|
-
statusText: response.statusText
|
|
164
|
+
error: e
|
|
190
165
|
});
|
|
191
166
|
return null;
|
|
192
167
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
168
|
+
throw e;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Fetches a hop with the given session, and consumes the response body or
|
|
173
|
+
* starts cancelling it before returning. The caller closes the session.
|
|
174
|
+
*/
|
|
175
|
+
async function fetchHop(session, url, options) {
|
|
176
|
+
let response;
|
|
177
|
+
try {
|
|
178
|
+
response = await session.fetch(url, {
|
|
179
|
+
headers: {
|
|
180
|
+
Accept: "application/jrd+json",
|
|
181
|
+
"User-Agent": typeof options.userAgent === "string" ? options.userAgent : getUserAgent(options.userAgent)
|
|
182
|
+
},
|
|
183
|
+
redirect: "manual",
|
|
184
|
+
signal: options.signal
|
|
185
|
+
});
|
|
186
|
+
} catch (error) {
|
|
187
|
+
logger.debug("Failed to fetch WebFinger resource descriptor: {error}", {
|
|
188
|
+
url: url.href,
|
|
189
|
+
error
|
|
190
|
+
});
|
|
191
|
+
return { result: null };
|
|
192
|
+
}
|
|
193
|
+
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
|
|
194
|
+
cancelBody(response, url);
|
|
195
|
+
return { redirect: response.headers.get("Location") };
|
|
196
|
+
}
|
|
197
|
+
if (!response.ok) {
|
|
198
|
+
logger.debug("Failed to fetch WebFinger resource descriptor: {status} {statusText}.", {
|
|
199
|
+
url: url.href,
|
|
200
|
+
status: response.status,
|
|
201
|
+
statusText: response.statusText
|
|
202
|
+
});
|
|
203
|
+
cancelBody(response, url);
|
|
204
|
+
return { result: null };
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
return { result: JSON.parse(await readBoundedText(response, MAX_BODY_SIZE, url)) };
|
|
208
|
+
} catch (e) {
|
|
209
|
+
if (e instanceof BodyTooLargeError) return { result: null };
|
|
210
|
+
if (e instanceof SyntaxError) {
|
|
211
|
+
logger.debug("Failed to parse WebFinger resource descriptor as JSON: {error}", { error: e });
|
|
212
|
+
return { result: null };
|
|
202
213
|
}
|
|
214
|
+
throw e;
|
|
203
215
|
}
|
|
204
216
|
}
|
|
217
|
+
/**
|
|
218
|
+
* Starts cancelling a response body that is not read, without waiting for it
|
|
219
|
+
* to settle. The cancellation of a branch of a tee (e.g., a body cloned by
|
|
220
|
+
* a fetch mock) settles only after the other branch is cancelled too, per
|
|
221
|
+
* the Streams standard, so awaiting it could block forever. A failure is
|
|
222
|
+
* only logged.
|
|
223
|
+
*/
|
|
224
|
+
function cancelBody(response, url) {
|
|
225
|
+
response.body?.cancel().catch((error) => {
|
|
226
|
+
logger.debug("Failed to cancel the response body from {url}: {error}", {
|
|
227
|
+
url: url.href,
|
|
228
|
+
error
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
//#endregion
|
|
233
|
+
//#region src/lookup.ts
|
|
234
|
+
/**
|
|
235
|
+
* Looks up a WebFinger resource.
|
|
236
|
+
* @param resource The resource URL to look up.
|
|
237
|
+
* @param options Extra options for looking up the resource.
|
|
238
|
+
* @returns The resource descriptor, or `null` if not found.
|
|
239
|
+
* @since 0.2.0
|
|
240
|
+
*/
|
|
241
|
+
async function lookupWebFinger(resource, options = {}) {
|
|
242
|
+
return await (options.tracerProvider ?? trace.getTracerProvider()).getTracer(name, version).startActiveSpan("webfinger.lookup", {
|
|
243
|
+
kind: SpanKind.CLIENT,
|
|
244
|
+
attributes: {
|
|
245
|
+
"webfinger.resource": resource.toString(),
|
|
246
|
+
"webfinger.resource.scheme": typeof resource === "string" ? resource.replace(/:.*$/, "") : resource.protocol.replace(/:$/, "")
|
|
247
|
+
}
|
|
248
|
+
}, async (span) => {
|
|
249
|
+
try {
|
|
250
|
+
const result = await lookupWebFingerInternal(resource, options);
|
|
251
|
+
span.setStatus({ code: result === null ? SpanStatusCode.ERROR : SpanStatusCode.OK });
|
|
252
|
+
return result;
|
|
253
|
+
} catch (error) {
|
|
254
|
+
span.setStatus({
|
|
255
|
+
code: SpanStatusCode.ERROR,
|
|
256
|
+
message: String(error)
|
|
257
|
+
});
|
|
258
|
+
throw error;
|
|
259
|
+
} finally {
|
|
260
|
+
span.end();
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
}
|
|
205
264
|
//#endregion
|
|
206
265
|
export { lookupWebFinger };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fedify/webfinger",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.20",
|
|
4
4
|
"homepage": "https://fedify.dev/",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -58,9 +58,9 @@
|
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
60
|
"@logtape/logtape": "^2.0.5",
|
|
61
|
-
"@opentelemetry/api": "
|
|
61
|
+
"@opentelemetry/api": "~1.9.0",
|
|
62
62
|
"es-toolkit": "1.43.0",
|
|
63
|
-
"@fedify/vocab-runtime": "2.2.
|
|
63
|
+
"@fedify/vocab-runtime": "2.2.20"
|
|
64
64
|
},
|
|
65
65
|
"scripts": {
|
|
66
66
|
"build:self": "tsdown",
|