@fedify/webfinger 2.0.0-dev.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -0
- package/deno.json +26 -0
- package/dist/lookup.test.cjs +1598 -0
- package/dist/lookup.test.d.cts +1 -0
- package/dist/lookup.test.d.ts +1 -0
- package/dist/lookup.test.js +1599 -0
- package/dist/mod.cjs +183 -0
- package/dist/mod.d.cts +113 -0
- package/dist/mod.d.ts +113 -0
- package/dist/mod.js +160 -0
- package/package.json +70 -0
- package/src/jrd.ts +67 -0
- package/src/lookup.test.ts +331 -0
- package/src/lookup.ts +226 -0
- package/src/mod.ts +7 -0
- package/tsdown.config.ts +20 -0
|
@@ -0,0 +1,1599 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
import { test } from "@fedify/fixture";
|
|
3
|
+
import { withTimeout } from "es-toolkit";
|
|
4
|
+
import { deepStrictEqual } from "node:assert/strict";
|
|
5
|
+
import { UrlError, getUserAgent, validatePublicUrl } from "@fedify/vocab-runtime";
|
|
6
|
+
import { getLogger } from "@logtape/logtape";
|
|
7
|
+
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
8
|
+
|
|
9
|
+
//#region rolldown:runtime
|
|
10
|
+
var __create = Object.create;
|
|
11
|
+
var __defProp = Object.defineProperty;
|
|
12
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
13
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
14
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
15
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
16
|
+
var __commonJS = (cb, mod) => function() {
|
|
17
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
18
|
+
};
|
|
19
|
+
var __copyProps = (to, from, except, desc) => {
|
|
20
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
21
|
+
key = keys[i];
|
|
22
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
23
|
+
get: ((k) => from[k]).bind(null, key),
|
|
24
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return to;
|
|
28
|
+
};
|
|
29
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
30
|
+
value: mod,
|
|
31
|
+
enumerable: true
|
|
32
|
+
}) : target, mod));
|
|
33
|
+
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region ../../node_modules/.pnpm/glob-to-regexp@0.4.1/node_modules/glob-to-regexp/index.js
|
|
36
|
+
var require_glob_to_regexp = __commonJS({ "../../node_modules/.pnpm/glob-to-regexp@0.4.1/node_modules/glob-to-regexp/index.js"(exports, module) {
|
|
37
|
+
module.exports = function(glob$1, opts) {
|
|
38
|
+
if (typeof glob$1 !== "string") throw new TypeError("Expected a string");
|
|
39
|
+
var str = String(glob$1);
|
|
40
|
+
var reStr = "";
|
|
41
|
+
var extended = opts ? !!opts.extended : false;
|
|
42
|
+
var globstar = opts ? !!opts.globstar : false;
|
|
43
|
+
var inGroup = false;
|
|
44
|
+
var flags = opts && typeof opts.flags === "string" ? opts.flags : "";
|
|
45
|
+
var c;
|
|
46
|
+
for (var i = 0, len = str.length; i < len; i++) {
|
|
47
|
+
c = str[i];
|
|
48
|
+
switch (c) {
|
|
49
|
+
case "/":
|
|
50
|
+
case "$":
|
|
51
|
+
case "^":
|
|
52
|
+
case "+":
|
|
53
|
+
case ".":
|
|
54
|
+
case "(":
|
|
55
|
+
case ")":
|
|
56
|
+
case "=":
|
|
57
|
+
case "!":
|
|
58
|
+
case "|":
|
|
59
|
+
reStr += "\\" + c;
|
|
60
|
+
break;
|
|
61
|
+
case "?": if (extended) {
|
|
62
|
+
reStr += ".";
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
case "[":
|
|
66
|
+
case "]": if (extended) {
|
|
67
|
+
reStr += c;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
case "{": if (extended) {
|
|
71
|
+
inGroup = true;
|
|
72
|
+
reStr += "(";
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
case "}": if (extended) {
|
|
76
|
+
inGroup = false;
|
|
77
|
+
reStr += ")";
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
case ",":
|
|
81
|
+
if (inGroup) {
|
|
82
|
+
reStr += "|";
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
reStr += "\\" + c;
|
|
86
|
+
break;
|
|
87
|
+
case "*":
|
|
88
|
+
var prevChar = str[i - 1];
|
|
89
|
+
var starCount = 1;
|
|
90
|
+
while (str[i + 1] === "*") {
|
|
91
|
+
starCount++;
|
|
92
|
+
i++;
|
|
93
|
+
}
|
|
94
|
+
var nextChar = str[i + 1];
|
|
95
|
+
if (!globstar) reStr += ".*";
|
|
96
|
+
else {
|
|
97
|
+
var isGlobstar = starCount > 1 && (prevChar === "/" || prevChar === void 0) && (nextChar === "/" || nextChar === void 0);
|
|
98
|
+
if (isGlobstar) {
|
|
99
|
+
reStr += "((?:[^/]*(?:/|$))*)";
|
|
100
|
+
i++;
|
|
101
|
+
} else reStr += "([^/]*)";
|
|
102
|
+
}
|
|
103
|
+
break;
|
|
104
|
+
default: reStr += c;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (!flags || !~flags.indexOf("g")) reStr = "^" + reStr + "$";
|
|
108
|
+
return new RegExp(reStr, flags);
|
|
109
|
+
};
|
|
110
|
+
} });
|
|
111
|
+
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region ../../node_modules/.pnpm/regexparam@3.0.0/node_modules/regexparam/dist/index.mjs
|
|
114
|
+
/**
|
|
115
|
+
* @param {string|RegExp} input The route pattern
|
|
116
|
+
* @param {boolean} [loose] Allow open-ended matching. Ignored with `RegExp` input.
|
|
117
|
+
*/
|
|
118
|
+
function parse(input, loose) {
|
|
119
|
+
if (input instanceof RegExp) return {
|
|
120
|
+
keys: false,
|
|
121
|
+
pattern: input
|
|
122
|
+
};
|
|
123
|
+
var c, o, tmp, ext, keys = [], pattern = "", arr = input.split("/");
|
|
124
|
+
arr[0] || arr.shift();
|
|
125
|
+
while (tmp = arr.shift()) {
|
|
126
|
+
c = tmp[0];
|
|
127
|
+
if (c === "*") {
|
|
128
|
+
keys.push(c);
|
|
129
|
+
pattern += tmp[1] === "?" ? "(?:/(.*))?" : "/(.*)";
|
|
130
|
+
} else if (c === ":") {
|
|
131
|
+
o = tmp.indexOf("?", 1);
|
|
132
|
+
ext = tmp.indexOf(".", 1);
|
|
133
|
+
keys.push(tmp.substring(1, !!~o ? o : !!~ext ? ext : tmp.length));
|
|
134
|
+
pattern += !!~o && !~ext ? "(?:/([^/]+?))?" : "/([^/]+?)";
|
|
135
|
+
if (!!~ext) pattern += (!!~o ? "?" : "") + "\\" + tmp.substring(ext);
|
|
136
|
+
} else pattern += "/" + tmp;
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
keys,
|
|
140
|
+
pattern: new RegExp("^" + pattern + (loose ? "(?=$|/)" : "/?$"), "i")
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
//#endregion
|
|
145
|
+
//#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/TypeDescriptor.js
|
|
146
|
+
const valueTypes = new Set([
|
|
147
|
+
"boolean",
|
|
148
|
+
"number",
|
|
149
|
+
"null",
|
|
150
|
+
"string",
|
|
151
|
+
"undefined"
|
|
152
|
+
]);
|
|
153
|
+
const referenceTypes = new Set([
|
|
154
|
+
"array",
|
|
155
|
+
"function",
|
|
156
|
+
"object",
|
|
157
|
+
"symbol"
|
|
158
|
+
]);
|
|
159
|
+
const detectableTypes = new Set([
|
|
160
|
+
"boolean",
|
|
161
|
+
"function",
|
|
162
|
+
"number",
|
|
163
|
+
"string",
|
|
164
|
+
"symbol"
|
|
165
|
+
]);
|
|
166
|
+
const typeConstructors = new Set([
|
|
167
|
+
Boolean,
|
|
168
|
+
Number,
|
|
169
|
+
String
|
|
170
|
+
]);
|
|
171
|
+
var TypeDescriptor = class TypeDescriptor {
|
|
172
|
+
constructor(value) {
|
|
173
|
+
this.name = TypeDescriptor.of(value);
|
|
174
|
+
this.isValueType = TypeDescriptor.isValueType(value);
|
|
175
|
+
this.isReferenceType = TypeDescriptor.isReferenceType(value);
|
|
176
|
+
this.isArray = TypeDescriptor.isArray(value);
|
|
177
|
+
this.isBoolean = TypeDescriptor.isBoolean(value);
|
|
178
|
+
this.isFunction = TypeDescriptor.isFunction(value);
|
|
179
|
+
this.isNull = TypeDescriptor.isNull(value);
|
|
180
|
+
this.isNumber = TypeDescriptor.isNumber(value);
|
|
181
|
+
this.isObject = TypeDescriptor.isObject(value);
|
|
182
|
+
this.isString = TypeDescriptor.isString(value);
|
|
183
|
+
this.isSymbol = TypeDescriptor.isSymbol(value);
|
|
184
|
+
this.isUndefined = TypeDescriptor.isUndefined(value);
|
|
185
|
+
}
|
|
186
|
+
static of(value) {
|
|
187
|
+
if (value === null) return "null";
|
|
188
|
+
if (value === void 0) return "undefined";
|
|
189
|
+
const detectedType = typeof value;
|
|
190
|
+
if (detectableTypes.has(detectedType)) return detectedType;
|
|
191
|
+
if (detectedType === "object") {
|
|
192
|
+
if (Array.isArray(value)) return "array";
|
|
193
|
+
if (typeConstructors.has(value.constructor)) return value.constructor.name.toLowerCase();
|
|
194
|
+
return detectedType;
|
|
195
|
+
}
|
|
196
|
+
throw new Error("Failed due to an unknown type.");
|
|
197
|
+
}
|
|
198
|
+
static from(value) {
|
|
199
|
+
return new TypeDescriptor(value);
|
|
200
|
+
}
|
|
201
|
+
static isValueType(value) {
|
|
202
|
+
return valueTypes.has(TypeDescriptor.of(value));
|
|
203
|
+
}
|
|
204
|
+
static isReferenceType(value) {
|
|
205
|
+
return referenceTypes.has(TypeDescriptor.of(value));
|
|
206
|
+
}
|
|
207
|
+
static isArray(value) {
|
|
208
|
+
return TypeDescriptor.of(value) === "array";
|
|
209
|
+
}
|
|
210
|
+
static isBoolean(value) {
|
|
211
|
+
return TypeDescriptor.of(value) === "boolean";
|
|
212
|
+
}
|
|
213
|
+
static isFunction(value) {
|
|
214
|
+
return TypeDescriptor.of(value) === "function";
|
|
215
|
+
}
|
|
216
|
+
static isNull(value) {
|
|
217
|
+
return TypeDescriptor.of(value) === "null";
|
|
218
|
+
}
|
|
219
|
+
static isNumber(value) {
|
|
220
|
+
return TypeDescriptor.of(value) === "number";
|
|
221
|
+
}
|
|
222
|
+
static isObject(value) {
|
|
223
|
+
return TypeDescriptor.of(value) === "object";
|
|
224
|
+
}
|
|
225
|
+
static isString(value) {
|
|
226
|
+
return TypeDescriptor.of(value) === "string";
|
|
227
|
+
}
|
|
228
|
+
static isSymbol(value) {
|
|
229
|
+
return TypeDescriptor.of(value) === "symbol";
|
|
230
|
+
}
|
|
231
|
+
static isUndefined(value) {
|
|
232
|
+
return TypeDescriptor.of(value) === "undefined";
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
//#endregion
|
|
237
|
+
//#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/IsSubsetOf.js
|
|
238
|
+
const allowedTypes = new Set([
|
|
239
|
+
"array",
|
|
240
|
+
"object",
|
|
241
|
+
"function",
|
|
242
|
+
"null"
|
|
243
|
+
]);
|
|
244
|
+
const isSubsetOf = function(subset, superset, visited = []) {
|
|
245
|
+
const subsetType = TypeDescriptor.of(subset);
|
|
246
|
+
const supersetType = TypeDescriptor.of(superset);
|
|
247
|
+
if (!allowedTypes.has(subsetType)) throw new Error(`Type '${subsetType}' is not supported.`);
|
|
248
|
+
if (!allowedTypes.has(supersetType)) throw new Error(`Type '${supersetType}' is not supported.`);
|
|
249
|
+
if (TypeDescriptor.isFunction(subset)) {
|
|
250
|
+
if (!TypeDescriptor.isFunction(superset)) throw new Error(`Types '${subsetType}' and '${supersetType}' do not match.`);
|
|
251
|
+
return subset.toString() === superset.toString();
|
|
252
|
+
}
|
|
253
|
+
if (TypeDescriptor.isArray(subset)) {
|
|
254
|
+
if (!TypeDescriptor.isArray(superset)) throw new Error(`Types '${subsetType}' and '${supersetType}' do not match.`);
|
|
255
|
+
if (subset.length > superset.length) return false;
|
|
256
|
+
for (const subsetItem of subset) {
|
|
257
|
+
const subsetItemType = TypeDescriptor.of(subsetItem);
|
|
258
|
+
let isItemInSuperset;
|
|
259
|
+
switch (subsetItemType) {
|
|
260
|
+
case "array":
|
|
261
|
+
case "object":
|
|
262
|
+
case "function": {
|
|
263
|
+
if (visited.includes(subsetItem)) continue;
|
|
264
|
+
visited.push(subsetItem);
|
|
265
|
+
isItemInSuperset = superset.some((supersetItem) => {
|
|
266
|
+
try {
|
|
267
|
+
return isSubsetOf(subsetItem, supersetItem, visited);
|
|
268
|
+
} catch {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
default: isItemInSuperset = superset.includes(subsetItem);
|
|
275
|
+
}
|
|
276
|
+
if (!isItemInSuperset) return false;
|
|
277
|
+
}
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
if (TypeDescriptor.isObject(subset)) {
|
|
281
|
+
if (!TypeDescriptor.isObject(superset) || TypeDescriptor.isArray(superset)) throw new Error(`Types '${subsetType}' and '${supersetType}' do not match.`);
|
|
282
|
+
if (Object.keys(subset).length > Object.keys(superset).length) return false;
|
|
283
|
+
for (const [subsetKey, subsetValue] of Object.entries(subset)) {
|
|
284
|
+
const supersetValue = superset[subsetKey];
|
|
285
|
+
const subsetValueType = TypeDescriptor.of(subsetValue);
|
|
286
|
+
switch (subsetValueType) {
|
|
287
|
+
case "array":
|
|
288
|
+
case "object":
|
|
289
|
+
case "function": {
|
|
290
|
+
if (visited.includes(subsetValue)) continue;
|
|
291
|
+
visited.push(subsetValue);
|
|
292
|
+
try {
|
|
293
|
+
const isInSuperset = isSubsetOf(subsetValue, supersetValue, visited);
|
|
294
|
+
if (!isInSuperset) return false;
|
|
295
|
+
} catch {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
default: if (subsetValue !== supersetValue) return false;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
if (TypeDescriptor.isNull(subset)) {
|
|
306
|
+
if (!TypeDescriptor.isNull(superset)) throw new Error(`Types '${subsetType}' and '${supersetType}' do not match.`);
|
|
307
|
+
return true;
|
|
308
|
+
}
|
|
309
|
+
throw new Error("Invalid operation.");
|
|
310
|
+
};
|
|
311
|
+
isSubsetOf.structural = function(subset, superset, visited = []) {
|
|
312
|
+
if (!TypeDescriptor.isObject(subset)) throw new Error(`Type '${TypeDescriptor.of(subset)}' is not supported.`);
|
|
313
|
+
if (!TypeDescriptor.isObject(superset)) throw new Error(`Type '${TypeDescriptor.of(superset)}' is not supported.`);
|
|
314
|
+
for (const [subsetKey, subsetValue] of Object.entries(subset)) {
|
|
315
|
+
if (superset[subsetKey] === void 0) return false;
|
|
316
|
+
const subsetValueType = TypeDescriptor.of(subsetValue);
|
|
317
|
+
const supersetValue = superset[subsetKey];
|
|
318
|
+
if (subsetValueType === "object") {
|
|
319
|
+
if (visited.includes(subsetValue)) continue;
|
|
320
|
+
visited.push(subsetValue);
|
|
321
|
+
try {
|
|
322
|
+
const isInSuperset = isSubsetOf.structural(subsetValue, supersetValue, visited);
|
|
323
|
+
if (!isInSuperset) return false;
|
|
324
|
+
} catch {
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return true;
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
//#endregion
|
|
333
|
+
//#region ../../node_modules/.pnpm/dequal@2.0.3/node_modules/dequal/dist/index.mjs
|
|
334
|
+
var has = Object.prototype.hasOwnProperty;
|
|
335
|
+
function find(iter, tar, key) {
|
|
336
|
+
for (key of iter.keys()) if (dequal(key, tar)) return key;
|
|
337
|
+
}
|
|
338
|
+
function dequal(foo, bar) {
|
|
339
|
+
var ctor, len, tmp;
|
|
340
|
+
if (foo === bar) return true;
|
|
341
|
+
if (foo && bar && (ctor = foo.constructor) === bar.constructor) {
|
|
342
|
+
if (ctor === Date) return foo.getTime() === bar.getTime();
|
|
343
|
+
if (ctor === RegExp) return foo.toString() === bar.toString();
|
|
344
|
+
if (ctor === Array) {
|
|
345
|
+
if ((len = foo.length) === bar.length) while (len-- && dequal(foo[len], bar[len]));
|
|
346
|
+
return len === -1;
|
|
347
|
+
}
|
|
348
|
+
if (ctor === Set) {
|
|
349
|
+
if (foo.size !== bar.size) return false;
|
|
350
|
+
for (len of foo) {
|
|
351
|
+
tmp = len;
|
|
352
|
+
if (tmp && typeof tmp === "object") {
|
|
353
|
+
tmp = find(bar, tmp);
|
|
354
|
+
if (!tmp) return false;
|
|
355
|
+
}
|
|
356
|
+
if (!bar.has(tmp)) return false;
|
|
357
|
+
}
|
|
358
|
+
return true;
|
|
359
|
+
}
|
|
360
|
+
if (ctor === Map) {
|
|
361
|
+
if (foo.size !== bar.size) return false;
|
|
362
|
+
for (len of foo) {
|
|
363
|
+
tmp = len[0];
|
|
364
|
+
if (tmp && typeof tmp === "object") {
|
|
365
|
+
tmp = find(bar, tmp);
|
|
366
|
+
if (!tmp) return false;
|
|
367
|
+
}
|
|
368
|
+
if (!dequal(len[1], bar.get(tmp))) return false;
|
|
369
|
+
}
|
|
370
|
+
return true;
|
|
371
|
+
}
|
|
372
|
+
if (ctor === ArrayBuffer) {
|
|
373
|
+
foo = new Uint8Array(foo);
|
|
374
|
+
bar = new Uint8Array(bar);
|
|
375
|
+
} else if (ctor === DataView) {
|
|
376
|
+
if ((len = foo.byteLength) === bar.byteLength) while (len-- && foo.getInt8(len) === bar.getInt8(len));
|
|
377
|
+
return len === -1;
|
|
378
|
+
}
|
|
379
|
+
if (ArrayBuffer.isView(foo)) {
|
|
380
|
+
if ((len = foo.byteLength) === bar.byteLength) while (len-- && foo[len] === bar[len]);
|
|
381
|
+
return len === -1;
|
|
382
|
+
}
|
|
383
|
+
if (!ctor || typeof foo === "object") {
|
|
384
|
+
len = 0;
|
|
385
|
+
for (ctor in foo) {
|
|
386
|
+
if (has.call(foo, ctor) && ++len && !has.call(bar, ctor)) return false;
|
|
387
|
+
if (!(ctor in bar) || !dequal(foo[ctor], bar[ctor])) return false;
|
|
388
|
+
}
|
|
389
|
+
return Object.keys(bar).length === len;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return foo !== foo && bar !== bar;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
//#endregion
|
|
396
|
+
//#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/RequestUtils.js
|
|
397
|
+
const absoluteUrlRX = new RegExp("^[a-z]+://|^data:", "i");
|
|
398
|
+
const protocolRelativeUrlRX = new RegExp("^//", "i");
|
|
399
|
+
function hasCredentialsInUrl(url) {
|
|
400
|
+
const urlObject = new URL(url, !absoluteUrlRX.test(url) ? "http://dummy" : void 0);
|
|
401
|
+
return Boolean(urlObject.username || urlObject.password);
|
|
402
|
+
}
|
|
403
|
+
function normalizeUrl(url, allowRelativeUrls) {
|
|
404
|
+
if (url instanceof URL) return url.href;
|
|
405
|
+
const primitiveUrl = String(url).valueOf();
|
|
406
|
+
if (absoluteUrlRX.test(primitiveUrl)) return new URL(primitiveUrl).href;
|
|
407
|
+
if (protocolRelativeUrlRX.test(primitiveUrl)) return new URL(primitiveUrl, "http://dummy").href.replace(/^[a-z]+:/, "");
|
|
408
|
+
if ("location" in globalThis) if (primitiveUrl.startsWith("/")) return `${globalThis.location.origin}${primitiveUrl}`;
|
|
409
|
+
else return `${globalThis.location.href}/${primitiveUrl}`;
|
|
410
|
+
else if (allowRelativeUrls) {
|
|
411
|
+
const urlInstance = new URL(primitiveUrl, "http://dummy");
|
|
412
|
+
return urlInstance.pathname + urlInstance.search;
|
|
413
|
+
} else throw new Error("Relative urls are not support by default in node.js tests. Either use a utility such as jsdom to define globalThis.location or set `fetchMock.config.allowRelativeUrls = true`");
|
|
414
|
+
}
|
|
415
|
+
function createCallLogFromUrlAndOptions(url, options) {
|
|
416
|
+
const pendingPromises = [];
|
|
417
|
+
if (typeof url === "string" || url instanceof String || url instanceof URL) {
|
|
418
|
+
const normalizedUrl = normalizeUrl(url, true);
|
|
419
|
+
const derivedOptions = options ? { ...options } : {};
|
|
420
|
+
if (derivedOptions.headers) derivedOptions.headers = normalizeHeaders(derivedOptions.headers);
|
|
421
|
+
derivedOptions.method = derivedOptions.method ? derivedOptions.method.toLowerCase() : "get";
|
|
422
|
+
return {
|
|
423
|
+
args: [url, options],
|
|
424
|
+
url: normalizedUrl,
|
|
425
|
+
queryParams: new URLSearchParams(getQuery(normalizedUrl)),
|
|
426
|
+
options: derivedOptions,
|
|
427
|
+
signal: derivedOptions.signal,
|
|
428
|
+
pendingPromises
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
if (typeof url === "object") throw new TypeError("fetch-mock: Unrecognised Request object. Read the Config and Installation sections of the docs");
|
|
432
|
+
else throw new TypeError("fetch-mock: Invalid arguments passed to fetch");
|
|
433
|
+
}
|
|
434
|
+
async function createCallLogFromRequest(request, options) {
|
|
435
|
+
const pendingPromises = [];
|
|
436
|
+
const derivedOptions = { method: request.method };
|
|
437
|
+
try {
|
|
438
|
+
try {
|
|
439
|
+
derivedOptions.body = await request.clone().formData();
|
|
440
|
+
} catch {
|
|
441
|
+
derivedOptions.body = await request.clone().text();
|
|
442
|
+
}
|
|
443
|
+
} catch {}
|
|
444
|
+
if (request.headers) derivedOptions.headers = normalizeHeaders(request.headers);
|
|
445
|
+
const url = normalizeUrl(request.url, true);
|
|
446
|
+
const callLog = {
|
|
447
|
+
args: [request, options],
|
|
448
|
+
url,
|
|
449
|
+
queryParams: new URLSearchParams(getQuery(url)),
|
|
450
|
+
options: Object.assign(derivedOptions, options || {}),
|
|
451
|
+
request,
|
|
452
|
+
signal: options && options.signal || request.signal,
|
|
453
|
+
pendingPromises
|
|
454
|
+
};
|
|
455
|
+
return callLog;
|
|
456
|
+
}
|
|
457
|
+
function getPath(url) {
|
|
458
|
+
const u = absoluteUrlRX.test(url) ? new URL(url) : new URL(url, "http://dummy");
|
|
459
|
+
return u.pathname;
|
|
460
|
+
}
|
|
461
|
+
function getHost(url) {
|
|
462
|
+
if (absoluteUrlRX.test(url)) return new URL(url).host;
|
|
463
|
+
else if ("location" in globalThis) return globalThis.location.host;
|
|
464
|
+
return null;
|
|
465
|
+
}
|
|
466
|
+
function getQuery(url) {
|
|
467
|
+
const u = absoluteUrlRX.test(url) ? new URL(url) : new URL(url, "http://dummy");
|
|
468
|
+
return u.search ? u.search.substr(1) : "";
|
|
469
|
+
}
|
|
470
|
+
function normalizeHeaders(headers) {
|
|
471
|
+
let entries;
|
|
472
|
+
if (headers instanceof Headers) entries = [...headers.entries()];
|
|
473
|
+
else if (Array.isArray(headers)) entries = headers;
|
|
474
|
+
else entries = Object.entries(headers);
|
|
475
|
+
return Object.fromEntries(entries.map(([key, val]) => [key.toLowerCase(), String(val).valueOf()]));
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
//#endregion
|
|
479
|
+
//#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/Matchers.js
|
|
480
|
+
var import_glob_to_regexp = __toESM(require_glob_to_regexp(), 1);
|
|
481
|
+
const isUrlMatcher = (matcher) => matcher instanceof RegExp || typeof matcher === "string" || typeof matcher === "object" && "href" in matcher;
|
|
482
|
+
const isFunctionMatcher = (matcher) => typeof matcher === "function";
|
|
483
|
+
const stringMatchers = {
|
|
484
|
+
begin: (targetString) => ({ url }) => url.startsWith(targetString),
|
|
485
|
+
end: (targetString) => ({ url }) => url.endsWith(targetString),
|
|
486
|
+
include: (targetString) => ({ url }) => url.includes(targetString),
|
|
487
|
+
glob: (targetString) => {
|
|
488
|
+
const urlRX = (0, import_glob_to_regexp.default)(targetString);
|
|
489
|
+
return ({ url }) => urlRX.test(url);
|
|
490
|
+
},
|
|
491
|
+
express: (targetString) => {
|
|
492
|
+
const urlRX = parse(targetString);
|
|
493
|
+
return (callLog) => {
|
|
494
|
+
const vals = urlRX.pattern.exec(getPath(callLog.url));
|
|
495
|
+
if (!vals) {
|
|
496
|
+
callLog.expressParams = {};
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
499
|
+
vals.shift();
|
|
500
|
+
callLog.expressParams = urlRX.keys.reduce((map, paramName, i) => vals[i] ? Object.assign(map, { [paramName]: vals[i] }) : map, {});
|
|
501
|
+
return true;
|
|
502
|
+
};
|
|
503
|
+
},
|
|
504
|
+
path: (targetString) => {
|
|
505
|
+
const dotlessTargetString = getPath(targetString);
|
|
506
|
+
return ({ url }) => {
|
|
507
|
+
const path = getPath(url);
|
|
508
|
+
return path === targetString || path === dotlessTargetString;
|
|
509
|
+
};
|
|
510
|
+
},
|
|
511
|
+
host: (targetString) => {
|
|
512
|
+
return ({ url }) => targetString === getHost(url);
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
const getHeaderMatcher = ({ headers: expectedHeaders }) => {
|
|
516
|
+
if (!expectedHeaders) return;
|
|
517
|
+
const expectation = normalizeHeaders(expectedHeaders);
|
|
518
|
+
return ({ options: { headers = {} } }) => {
|
|
519
|
+
const lowerCaseHeaders = normalizeHeaders(headers);
|
|
520
|
+
return Object.keys(expectation).every((headerName) => lowerCaseHeaders[headerName] === expectation[headerName]);
|
|
521
|
+
};
|
|
522
|
+
};
|
|
523
|
+
const getMissingHeaderMatcher = ({ missingHeaders: expectedMissingHeaders }) => {
|
|
524
|
+
if (!expectedMissingHeaders) return;
|
|
525
|
+
const expectation = expectedMissingHeaders.map((header) => header.toLowerCase());
|
|
526
|
+
return ({ options: { headers = {} } }) => {
|
|
527
|
+
const lowerCaseHeaders = normalizeHeaders(headers);
|
|
528
|
+
return expectation.every((headerName) => !(headerName in lowerCaseHeaders));
|
|
529
|
+
};
|
|
530
|
+
};
|
|
531
|
+
const getMethodMatcher = ({ method: expectedMethod }) => {
|
|
532
|
+
if (!expectedMethod) return;
|
|
533
|
+
return ({ options: { method } = {} }) => {
|
|
534
|
+
const actualMethod = method ? method.toLowerCase() : "get";
|
|
535
|
+
return expectedMethod === actualMethod;
|
|
536
|
+
};
|
|
537
|
+
};
|
|
538
|
+
const getQueryParamsMatcher = ({ query: passedQuery }) => {
|
|
539
|
+
if (!passedQuery) return;
|
|
540
|
+
const expectedQuery = new URLSearchParams();
|
|
541
|
+
for (const [key, value] of Object.entries(passedQuery)) if (Array.isArray(value)) for (const item of value) expectedQuery.append(key, typeof item === "object" || typeof item === "undefined" ? "" : item.toString());
|
|
542
|
+
else expectedQuery.append(key, typeof value === "object" || typeof value === "undefined" ? "" : value.toString());
|
|
543
|
+
const keys = Array.from(expectedQuery.keys());
|
|
544
|
+
return ({ queryParams }) => {
|
|
545
|
+
return keys.every((key) => {
|
|
546
|
+
const expectedValues = expectedQuery.getAll(key).sort();
|
|
547
|
+
const actualValues = queryParams.getAll(key).sort();
|
|
548
|
+
if (expectedValues.length !== actualValues.length) return false;
|
|
549
|
+
if (Array.isArray(passedQuery[key])) return expectedValues.every((expected, index) => expected === actualValues[index]);
|
|
550
|
+
return dequal(actualValues, expectedValues);
|
|
551
|
+
});
|
|
552
|
+
};
|
|
553
|
+
};
|
|
554
|
+
const getExpressParamsMatcher = ({ params: expectedParams, url }) => {
|
|
555
|
+
if (!expectedParams) return;
|
|
556
|
+
if (!(typeof url === "string" && /express:/.test(url))) throw new Error("fetch-mock: matching on params is only possible when using an express: matcher");
|
|
557
|
+
const expectedKeys = Object.keys(expectedParams);
|
|
558
|
+
return ({ expressParams = {} }) => {
|
|
559
|
+
return expectedKeys.every((key) => expressParams[key] === expectedParams[key]);
|
|
560
|
+
};
|
|
561
|
+
};
|
|
562
|
+
const formDataToObject = (formData) => {
|
|
563
|
+
const fields = [...formData];
|
|
564
|
+
const result = {};
|
|
565
|
+
fields.forEach(([key, value]) => {
|
|
566
|
+
result[key] = result[key] || [];
|
|
567
|
+
result[key].push(value);
|
|
568
|
+
});
|
|
569
|
+
return result;
|
|
570
|
+
};
|
|
571
|
+
const getBodyMatcher = (route) => {
|
|
572
|
+
let { body: expectedBody } = route;
|
|
573
|
+
let expectedBodyType = "json";
|
|
574
|
+
if (!expectedBody) return;
|
|
575
|
+
if (expectedBody instanceof FormData) {
|
|
576
|
+
expectedBodyType = "formData";
|
|
577
|
+
expectedBody = formDataToObject(expectedBody);
|
|
578
|
+
}
|
|
579
|
+
return ({ options: { body, method = "get" } }) => {
|
|
580
|
+
if (["get", "head"].includes(method.toLowerCase())) return false;
|
|
581
|
+
let sentBody;
|
|
582
|
+
try {
|
|
583
|
+
if (typeof body === "string") {
|
|
584
|
+
sentBody = JSON.parse(body);
|
|
585
|
+
if (expectedBodyType !== "json") return false;
|
|
586
|
+
}
|
|
587
|
+
} catch {}
|
|
588
|
+
if (body instanceof FormData) {
|
|
589
|
+
if (expectedBodyType !== "formData") return false;
|
|
590
|
+
sentBody = formDataToObject(body);
|
|
591
|
+
}
|
|
592
|
+
return sentBody && (route.matchPartialBody ? isSubsetOf(expectedBody, sentBody) : dequal(expectedBody, sentBody));
|
|
593
|
+
};
|
|
594
|
+
};
|
|
595
|
+
const getFunctionMatcher = ({ matcherFunction }) => matcherFunction;
|
|
596
|
+
const getRegexpMatcher = (regexp) => ({ url }) => regexp.test(url);
|
|
597
|
+
const getFullUrlMatcher = (route, matcherUrl, query) => {
|
|
598
|
+
const expectedUrl = normalizeUrl(matcherUrl, route.allowRelativeUrls);
|
|
599
|
+
if (route.url === matcherUrl) route.url = expectedUrl;
|
|
600
|
+
return ({ url }) => {
|
|
601
|
+
if (query && expectedUrl.indexOf("?")) return getPath(url) === getPath(expectedUrl);
|
|
602
|
+
return normalizeUrl(url, true) === expectedUrl;
|
|
603
|
+
};
|
|
604
|
+
};
|
|
605
|
+
const getUrlMatcher = (route) => {
|
|
606
|
+
const { url: matcherUrl, query } = route;
|
|
607
|
+
if (matcherUrl === "*") return () => true;
|
|
608
|
+
if (matcherUrl instanceof RegExp) return getRegexpMatcher(matcherUrl);
|
|
609
|
+
if (matcherUrl instanceof URL) {
|
|
610
|
+
if (matcherUrl.href) return getFullUrlMatcher(route, matcherUrl.href, query);
|
|
611
|
+
}
|
|
612
|
+
if (typeof matcherUrl === "string") {
|
|
613
|
+
for (const shorthand in stringMatchers) if (matcherUrl.indexOf(`${shorthand}:`) === 0) {
|
|
614
|
+
const urlFragment = matcherUrl.replace(/* @__PURE__ */ new RegExp(`^${shorthand}:`), "");
|
|
615
|
+
return stringMatchers[shorthand](urlFragment);
|
|
616
|
+
}
|
|
617
|
+
return getFullUrlMatcher(route, matcherUrl, query);
|
|
618
|
+
}
|
|
619
|
+
if (typeof matcherUrl === "object") {
|
|
620
|
+
const matchers = Object.entries(matcherUrl).map(([key, pattern]) => {
|
|
621
|
+
if (key === "regexp") return getRegexpMatcher(pattern);
|
|
622
|
+
else if (key in stringMatchers) return stringMatchers[key](pattern);
|
|
623
|
+
else throw new Error(`unrecognised url matching pattern: ${key}`);
|
|
624
|
+
});
|
|
625
|
+
return (route$1) => matchers.every((matcher) => matcher(route$1));
|
|
626
|
+
}
|
|
627
|
+
};
|
|
628
|
+
const builtInMatchers = [
|
|
629
|
+
{
|
|
630
|
+
name: "url",
|
|
631
|
+
matcher: getUrlMatcher
|
|
632
|
+
},
|
|
633
|
+
{
|
|
634
|
+
name: "query",
|
|
635
|
+
matcher: getQueryParamsMatcher
|
|
636
|
+
},
|
|
637
|
+
{
|
|
638
|
+
name: "method",
|
|
639
|
+
matcher: getMethodMatcher
|
|
640
|
+
},
|
|
641
|
+
{
|
|
642
|
+
name: "headers",
|
|
643
|
+
matcher: getHeaderMatcher
|
|
644
|
+
},
|
|
645
|
+
{
|
|
646
|
+
name: "missingHeaders",
|
|
647
|
+
matcher: getMissingHeaderMatcher
|
|
648
|
+
},
|
|
649
|
+
{
|
|
650
|
+
name: "params",
|
|
651
|
+
matcher: getExpressParamsMatcher
|
|
652
|
+
},
|
|
653
|
+
{
|
|
654
|
+
name: "body",
|
|
655
|
+
matcher: getBodyMatcher,
|
|
656
|
+
usesBody: true
|
|
657
|
+
},
|
|
658
|
+
{
|
|
659
|
+
name: "matcherFunction",
|
|
660
|
+
matcher: getFunctionMatcher
|
|
661
|
+
}
|
|
662
|
+
];
|
|
663
|
+
|
|
664
|
+
//#endregion
|
|
665
|
+
//#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/StatusTextMap.js
|
|
666
|
+
const statusTextMap = {
|
|
667
|
+
100: "Continue",
|
|
668
|
+
101: "Switching Protocols",
|
|
669
|
+
102: "Processing",
|
|
670
|
+
200: "OK",
|
|
671
|
+
201: "Created",
|
|
672
|
+
202: "Accepted",
|
|
673
|
+
203: "Non-Authoritative Information",
|
|
674
|
+
204: "No Content",
|
|
675
|
+
205: "Reset Content",
|
|
676
|
+
206: "Partial Content",
|
|
677
|
+
207: "Multi-Status",
|
|
678
|
+
208: "Already Reported",
|
|
679
|
+
226: "IM Used",
|
|
680
|
+
300: "Multiple Choices",
|
|
681
|
+
301: "Moved Permanently",
|
|
682
|
+
302: "Found",
|
|
683
|
+
303: "See Other",
|
|
684
|
+
304: "Not Modified",
|
|
685
|
+
305: "Use Proxy",
|
|
686
|
+
307: "Temporary Redirect",
|
|
687
|
+
308: "Permanent Redirect",
|
|
688
|
+
400: "Bad Request",
|
|
689
|
+
401: "Unauthorized",
|
|
690
|
+
402: "Payment Required",
|
|
691
|
+
403: "Forbidden",
|
|
692
|
+
404: "Not Found",
|
|
693
|
+
405: "Method Not Allowed",
|
|
694
|
+
406: "Not Acceptable",
|
|
695
|
+
407: "Proxy Authentication Required",
|
|
696
|
+
408: "Request Timeout",
|
|
697
|
+
409: "Conflict",
|
|
698
|
+
410: "Gone",
|
|
699
|
+
411: "Length Required",
|
|
700
|
+
412: "Precondition Failed",
|
|
701
|
+
413: "Payload Too Large",
|
|
702
|
+
414: "URI Too Long",
|
|
703
|
+
415: "Unsupported Media Type",
|
|
704
|
+
416: "Range Not Satisfiable",
|
|
705
|
+
417: "Expectation Failed",
|
|
706
|
+
418: "I'm a teapot",
|
|
707
|
+
421: "Misdirected Request",
|
|
708
|
+
422: "Unprocessable Entity",
|
|
709
|
+
423: "Locked",
|
|
710
|
+
424: "Failed Dependency",
|
|
711
|
+
425: "Unordered Collection",
|
|
712
|
+
426: "Upgrade Required",
|
|
713
|
+
428: "Precondition Required",
|
|
714
|
+
429: "Too Many Requests",
|
|
715
|
+
431: "Request Header Fields Too Large",
|
|
716
|
+
451: "Unavailable For Legal Reasons",
|
|
717
|
+
500: "Internal Server Error",
|
|
718
|
+
501: "Not Implemented",
|
|
719
|
+
502: "Bad Gateway",
|
|
720
|
+
503: "Service Unavailable",
|
|
721
|
+
504: "Gateway Timeout",
|
|
722
|
+
505: "HTTP Version Not Supported",
|
|
723
|
+
506: "Variant Also Negotiates",
|
|
724
|
+
507: "Insufficient Storage",
|
|
725
|
+
508: "Loop Detected",
|
|
726
|
+
509: "Bandwidth Limit Exceeded",
|
|
727
|
+
510: "Not Extended",
|
|
728
|
+
511: "Network Authentication Required"
|
|
729
|
+
};
|
|
730
|
+
var StatusTextMap_default = statusTextMap;
|
|
731
|
+
|
|
732
|
+
//#endregion
|
|
733
|
+
//#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/Route.js
|
|
734
|
+
var __classPrivateFieldSet = void 0 && (void 0).__classPrivateFieldSet || function(receiver, state, value, kind, f) {
|
|
735
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
736
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
737
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
738
|
+
return kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value), value;
|
|
739
|
+
};
|
|
740
|
+
var __classPrivateFieldGet = void 0 && (void 0).__classPrivateFieldGet || function(receiver, state, kind, f) {
|
|
741
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
742
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
743
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
744
|
+
};
|
|
745
|
+
var _Route_instances, _a, _Route_responseSubscriptions, _Route_validate, _Route_sanitize, _Route_generateMatcher, _Route_limit, _Route_delayResponse;
|
|
746
|
+
var RouteConfigWrapper = class {
|
|
747
|
+
constructor(config) {
|
|
748
|
+
Object.assign(this, config);
|
|
749
|
+
}
|
|
750
|
+
};
|
|
751
|
+
function isBodyInit(body) {
|
|
752
|
+
return body instanceof Blob || body instanceof ArrayBuffer || ArrayBuffer.isView(body) || body instanceof DataView || body instanceof FormData || body instanceof ReadableStream || body instanceof URLSearchParams || body instanceof String || typeof body === "string" || body === null;
|
|
753
|
+
}
|
|
754
|
+
function sanitizeStatus(status) {
|
|
755
|
+
if (status === 0) return 200;
|
|
756
|
+
if (!status) return 200;
|
|
757
|
+
if (typeof status === "number" && parseInt(String(status), 10) !== status && status >= 200 || status < 600) return status;
|
|
758
|
+
throw new TypeError(`fetch-mock: Invalid status ${status} passed on response object.
|
|
759
|
+
To respond with a JSON object that has status as a property assign the object to body
|
|
760
|
+
e.g. {"body": {"status: "registered"}}`);
|
|
761
|
+
}
|
|
762
|
+
var Route = class {
|
|
763
|
+
constructor(config) {
|
|
764
|
+
_Route_instances.add(this);
|
|
765
|
+
_Route_responseSubscriptions.set(this, void 0);
|
|
766
|
+
this.init(config);
|
|
767
|
+
}
|
|
768
|
+
init(config) {
|
|
769
|
+
this.config = config;
|
|
770
|
+
__classPrivateFieldSet(this, _Route_responseSubscriptions, [], "f");
|
|
771
|
+
__classPrivateFieldGet(this, _Route_instances, "m", _Route_sanitize).call(this);
|
|
772
|
+
__classPrivateFieldGet(this, _Route_instances, "m", _Route_validate).call(this);
|
|
773
|
+
__classPrivateFieldGet(this, _Route_instances, "m", _Route_generateMatcher).call(this);
|
|
774
|
+
__classPrivateFieldGet(this, _Route_instances, "m", _Route_limit).call(this);
|
|
775
|
+
__classPrivateFieldGet(this, _Route_instances, "m", _Route_delayResponse).call(this);
|
|
776
|
+
}
|
|
777
|
+
reset() {}
|
|
778
|
+
waitFor(awaitedRoutes) {
|
|
779
|
+
const { response } = this.config;
|
|
780
|
+
this.config.response = Promise.all(awaitedRoutes.map((awaitedRoute) => new Promise((res) => awaitedRoute.onRespond(() => {
|
|
781
|
+
res(void 0);
|
|
782
|
+
})))).then(() => response);
|
|
783
|
+
}
|
|
784
|
+
onRespond(func) {
|
|
785
|
+
__classPrivateFieldGet(this, _Route_responseSubscriptions, "f").push(func);
|
|
786
|
+
}
|
|
787
|
+
constructResponse(responseInput) {
|
|
788
|
+
const responseOptions = this.constructResponseOptions(responseInput);
|
|
789
|
+
const body = this.constructResponseBody(responseInput, responseOptions);
|
|
790
|
+
const responsePackage = {
|
|
791
|
+
response: new this.config.Response(body, responseOptions),
|
|
792
|
+
responseOptions,
|
|
793
|
+
responseInput
|
|
794
|
+
};
|
|
795
|
+
__classPrivateFieldGet(this, _Route_responseSubscriptions, "f").forEach((func) => func());
|
|
796
|
+
return responsePackage;
|
|
797
|
+
}
|
|
798
|
+
constructResponseOptions(responseInput) {
|
|
799
|
+
const options = responseInput.options || {};
|
|
800
|
+
options.status = sanitizeStatus(responseInput.status);
|
|
801
|
+
options.statusText = StatusTextMap_default[options.status];
|
|
802
|
+
options.headers = new this.config.Headers(responseInput.headers);
|
|
803
|
+
return options;
|
|
804
|
+
}
|
|
805
|
+
constructResponseBody(responseInput, responseOptions) {
|
|
806
|
+
let body = responseInput.body;
|
|
807
|
+
const bodyIsBodyInit = isBodyInit(body);
|
|
808
|
+
if (!bodyIsBodyInit) if (typeof body === "undefined") body = null;
|
|
809
|
+
else if (typeof body === "object") {
|
|
810
|
+
body = JSON.stringify(body);
|
|
811
|
+
if (!responseOptions.headers.has("Content-Type")) responseOptions.headers.set("Content-Type", "application/json");
|
|
812
|
+
} else throw new TypeError("Invalid body provided to construct response");
|
|
813
|
+
if (this.config.includeContentLength && !responseOptions.headers.has("Content-Length") && !(body instanceof ReadableStream) && !(body instanceof FormData)) {
|
|
814
|
+
let length = 0;
|
|
815
|
+
if (body instanceof Blob) length = body.size;
|
|
816
|
+
else if (body instanceof ArrayBuffer || ArrayBuffer.isView(body) || body instanceof DataView) length = body.byteLength;
|
|
817
|
+
else if (body instanceof URLSearchParams) length = body.toString().length;
|
|
818
|
+
else if (typeof body === "string" || body instanceof String) length = body.length;
|
|
819
|
+
responseOptions.headers.set("Content-Length", length.toString());
|
|
820
|
+
}
|
|
821
|
+
return body;
|
|
822
|
+
}
|
|
823
|
+
static defineMatcher(matcher) {
|
|
824
|
+
_a.registeredMatchers.push(matcher);
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
_a = Route, _Route_responseSubscriptions = /* @__PURE__ */ new WeakMap(), _Route_instances = /* @__PURE__ */ new WeakSet(), _Route_validate = function _Route_validate$1() {
|
|
828
|
+
if (["matched", "unmatched"].includes(this.config.name)) throw new Error(`fetch-mock: Routes cannot use the reserved name \`${this.config.name}\``);
|
|
829
|
+
if (!("response" in this.config)) throw new Error("fetch-mock: Each route must define a response");
|
|
830
|
+
if (!_a.registeredMatchers.some(({ name: name$1 }) => name$1 in this.config)) throw new Error("fetch-mock: Each route must specify some criteria for matching calls to fetch. To match all calls use '*'");
|
|
831
|
+
}, _Route_sanitize = function _Route_sanitize$1() {
|
|
832
|
+
if (this.config.method) this.config.method = this.config.method.toLowerCase();
|
|
833
|
+
}, _Route_generateMatcher = function _Route_generateMatcher$1() {
|
|
834
|
+
const activeMatchers = _a.registeredMatchers.filter(({ name: name$1 }) => name$1 in this.config).map(({ matcher, usesBody }) => ({
|
|
835
|
+
matcher: matcher(this.config),
|
|
836
|
+
usesBody
|
|
837
|
+
}));
|
|
838
|
+
this.config.usesBody = activeMatchers.some(({ usesBody }) => usesBody);
|
|
839
|
+
this.matcher = (normalizedRequest) => activeMatchers.every(({ matcher }) => matcher(normalizedRequest));
|
|
840
|
+
}, _Route_limit = function _Route_limit$1() {
|
|
841
|
+
if (!this.config.repeat) return;
|
|
842
|
+
const originalMatcher = this.matcher;
|
|
843
|
+
let timesLeft = this.config.repeat;
|
|
844
|
+
this.matcher = (callLog) => {
|
|
845
|
+
const match = timesLeft && originalMatcher(callLog);
|
|
846
|
+
if (match) {
|
|
847
|
+
timesLeft--;
|
|
848
|
+
return true;
|
|
849
|
+
}
|
|
850
|
+
};
|
|
851
|
+
this.reset = () => {
|
|
852
|
+
timesLeft = this.config.repeat;
|
|
853
|
+
};
|
|
854
|
+
}, _Route_delayResponse = function _Route_delayResponse$1() {
|
|
855
|
+
if (this.config.delay) {
|
|
856
|
+
const { response } = this.config;
|
|
857
|
+
this.config.response = () => {
|
|
858
|
+
return new Promise((res) => setTimeout(() => res(response), this.config.delay));
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
};
|
|
862
|
+
Route.registeredMatchers = [];
|
|
863
|
+
builtInMatchers.forEach(Route.defineMatcher);
|
|
864
|
+
var Route_default = Route;
|
|
865
|
+
|
|
866
|
+
//#endregion
|
|
867
|
+
//#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/Router.js
|
|
868
|
+
const responseConfigProps = [
|
|
869
|
+
"body",
|
|
870
|
+
"headers",
|
|
871
|
+
"throws",
|
|
872
|
+
"status",
|
|
873
|
+
"redirectUrl"
|
|
874
|
+
];
|
|
875
|
+
function nameToOptions(options) {
|
|
876
|
+
return typeof options === "string" ? { name: options } : options;
|
|
877
|
+
}
|
|
878
|
+
function isPromise(response) {
|
|
879
|
+
return typeof response.then === "function";
|
|
880
|
+
}
|
|
881
|
+
function normalizeResponseInput(responseInput) {
|
|
882
|
+
if (typeof responseInput === "number") return { status: responseInput };
|
|
883
|
+
else if (typeof responseInput === "string" || shouldSendAsObject(responseInput)) return { body: responseInput };
|
|
884
|
+
return responseInput;
|
|
885
|
+
}
|
|
886
|
+
function shouldSendAsObject(responseInput) {
|
|
887
|
+
if (responseConfigProps.some((prop) => prop in responseInput)) {
|
|
888
|
+
if (Object.keys(responseInput).every((key) => responseConfigProps.includes(key))) return false;
|
|
889
|
+
return true;
|
|
890
|
+
}
|
|
891
|
+
return true;
|
|
892
|
+
}
|
|
893
|
+
function throwSpecExceptions({ url, options: { headers, method, body } }) {
|
|
894
|
+
if (headers) Object.entries(headers).forEach(([key]) => {
|
|
895
|
+
if (/\s/.test(key)) throw new TypeError("Invalid name");
|
|
896
|
+
});
|
|
897
|
+
if (hasCredentialsInUrl(url)) throw new TypeError(`Request cannot be constructed from a URL that includes credentials: ${url}`);
|
|
898
|
+
if (["get", "head"].includes(method) && body) throw new TypeError("Request with GET/HEAD method cannot have body.");
|
|
899
|
+
}
|
|
900
|
+
const resolveUntilResponseConfig = async (callLog) => {
|
|
901
|
+
let response = callLog.route.config.response;
|
|
902
|
+
while (true) if (typeof response === "function") response = response(callLog);
|
|
903
|
+
else if (isPromise(response)) response = await response;
|
|
904
|
+
else return response;
|
|
905
|
+
};
|
|
906
|
+
var Router = class {
|
|
907
|
+
constructor(fetchMockConfig, { routes, fallbackRoute } = {}) {
|
|
908
|
+
this.config = fetchMockConfig;
|
|
909
|
+
this.routes = routes || [];
|
|
910
|
+
this.fallbackRoute = fallbackRoute;
|
|
911
|
+
}
|
|
912
|
+
needsToReadBody(request) {
|
|
913
|
+
return Boolean(request && this.routes.some((route) => route.config.usesBody));
|
|
914
|
+
}
|
|
915
|
+
execute(callLog) {
|
|
916
|
+
throwSpecExceptions(callLog);
|
|
917
|
+
return new Promise(async (resolve, reject) => {
|
|
918
|
+
const { url, options, request, pendingPromises } = callLog;
|
|
919
|
+
let eventListenerAbortController;
|
|
920
|
+
if (callLog.signal) {
|
|
921
|
+
const abort = () => {
|
|
922
|
+
const error = new DOMException("The operation was aborted.", "AbortError");
|
|
923
|
+
const requestBody = request?.body || options?.body;
|
|
924
|
+
if (requestBody instanceof ReadableStream) if (requestBody.locked) console.warn("fetch-mock: Locked request body can't be cancelled");
|
|
925
|
+
else requestBody.cancel(error);
|
|
926
|
+
if (callLog?.response?.body) if (callLog.response.body.locked) console.warn("fetch-mock: Locked response body can't be cancelled");
|
|
927
|
+
else callLog.response.body.cancel(error);
|
|
928
|
+
reject(error);
|
|
929
|
+
};
|
|
930
|
+
if (callLog.signal.aborted) abort();
|
|
931
|
+
eventListenerAbortController = new AbortController();
|
|
932
|
+
callLog.signal.addEventListener("abort", abort, {
|
|
933
|
+
once: true,
|
|
934
|
+
signal: eventListenerAbortController.signal
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
if (this.needsToReadBody(request)) options.body = await options.body;
|
|
938
|
+
const routesToTry = this.fallbackRoute ? [...this.routes, this.fallbackRoute] : this.routes;
|
|
939
|
+
const route = routesToTry.find((route$1) => route$1.matcher(callLog));
|
|
940
|
+
if (route) try {
|
|
941
|
+
callLog.route = route;
|
|
942
|
+
const { response, responseOptions, responseInput } = await this.generateResponse(callLog);
|
|
943
|
+
const observableResponse = this.createObservableResponse(response, responseOptions, responseInput, url, pendingPromises);
|
|
944
|
+
callLog.response = response;
|
|
945
|
+
resolve(observableResponse);
|
|
946
|
+
} catch (err) {
|
|
947
|
+
reject(err);
|
|
948
|
+
} finally {
|
|
949
|
+
eventListenerAbortController?.abort();
|
|
950
|
+
}
|
|
951
|
+
else reject(/* @__PURE__ */ new Error(`fetch-mock: No response or fallback rule to cover ${options && options.method || "GET"} to ${url}`));
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
async generateResponse(callLog) {
|
|
955
|
+
const responseInput = await resolveUntilResponseConfig(callLog);
|
|
956
|
+
if (responseInput instanceof Response) return {
|
|
957
|
+
response: responseInput.clone(),
|
|
958
|
+
responseOptions: {},
|
|
959
|
+
responseInput: {}
|
|
960
|
+
};
|
|
961
|
+
const responseConfig = normalizeResponseInput(responseInput);
|
|
962
|
+
if (responseConfig.throws) throw responseConfig.throws;
|
|
963
|
+
return callLog.route.constructResponse(responseConfig);
|
|
964
|
+
}
|
|
965
|
+
createObservableResponse(response, responseConfig, responseInput, responseUrl, pendingPromises) {
|
|
966
|
+
return new Proxy(response, { get: (originalResponse, name$1) => {
|
|
967
|
+
if (responseInput.redirectUrl) {
|
|
968
|
+
if (name$1 === "url") return responseInput.redirectUrl;
|
|
969
|
+
if (name$1 === "redirected") return true;
|
|
970
|
+
} else {
|
|
971
|
+
if (name$1 === "url") return responseUrl;
|
|
972
|
+
if (name$1 === "redirected") return false;
|
|
973
|
+
}
|
|
974
|
+
if (responseInput.status === 0) {
|
|
975
|
+
if (name$1 === "status") return 0;
|
|
976
|
+
if (name$1 === "statusText") return "";
|
|
977
|
+
}
|
|
978
|
+
if (typeof response[name$1] === "function") return new Proxy(response[name$1], { apply: (func, thisArg, args) => {
|
|
979
|
+
const result = func.apply(response, args);
|
|
980
|
+
if (result.then) pendingPromises.push(result.catch(() => void 0));
|
|
981
|
+
return result;
|
|
982
|
+
} });
|
|
983
|
+
return originalResponse[name$1];
|
|
984
|
+
} });
|
|
985
|
+
}
|
|
986
|
+
addRoute(matcher, response, nameOrOptions) {
|
|
987
|
+
const config = {};
|
|
988
|
+
if (matcher instanceof RouteConfigWrapper) Object.assign(config, matcher);
|
|
989
|
+
if (isUrlMatcher(matcher)) config.url = matcher;
|
|
990
|
+
else if (isFunctionMatcher(matcher)) config.matcherFunction = matcher;
|
|
991
|
+
else Object.assign(config, matcher);
|
|
992
|
+
if (typeof response !== "undefined") config.response = response;
|
|
993
|
+
if (nameOrOptions) Object.assign(config, typeof nameOrOptions === "string" ? nameToOptions(nameOrOptions) : nameOrOptions);
|
|
994
|
+
const route = new Route_default({
|
|
995
|
+
...this.config,
|
|
996
|
+
...config
|
|
997
|
+
});
|
|
998
|
+
if (route.config.name && this.routes.some(({ config: { name: existingName } }) => route.config.name === existingName)) throw new Error("fetch-mock: Adding route with same name as existing route.");
|
|
999
|
+
if (route.config.waitFor) {
|
|
1000
|
+
const routeNamesToWaitFor = Array.isArray(route.config.waitFor) ? route.config.waitFor : [route.config.waitFor];
|
|
1001
|
+
const routesToAwait = [];
|
|
1002
|
+
routeNamesToWaitFor.forEach((routeName) => {
|
|
1003
|
+
const routeToAwait = this.routes.find(({ config: { name: existingName } }) => routeName === existingName);
|
|
1004
|
+
if (routeToAwait) routesToAwait.push(routeToAwait);
|
|
1005
|
+
else throw new Error(`Cannot wait for route \`${routeName}\`: route of that name does not exist`);
|
|
1006
|
+
});
|
|
1007
|
+
route.waitFor(routesToAwait);
|
|
1008
|
+
}
|
|
1009
|
+
this.routes.push(route);
|
|
1010
|
+
}
|
|
1011
|
+
setFallback(response) {
|
|
1012
|
+
if (this.fallbackRoute) console.warn("calling fetchMock.catch() twice - are you sure you want to overwrite the previous fallback response");
|
|
1013
|
+
this.fallbackRoute = new Route_default({
|
|
1014
|
+
matcherFunction: () => true,
|
|
1015
|
+
response: response || "ok",
|
|
1016
|
+
...this.config
|
|
1017
|
+
});
|
|
1018
|
+
this.fallbackRoute.config.isFallback = true;
|
|
1019
|
+
}
|
|
1020
|
+
removeRoutes({ names, includeSticky, includeFallback } = {}) {
|
|
1021
|
+
includeFallback = includeFallback ?? true;
|
|
1022
|
+
this.routes = this.routes.filter(({ config: { sticky, name: name$1 } }) => {
|
|
1023
|
+
if (sticky && !includeSticky) return true;
|
|
1024
|
+
if (!names) return false;
|
|
1025
|
+
return !names.includes(name$1);
|
|
1026
|
+
});
|
|
1027
|
+
if (includeFallback) delete this.fallbackRoute;
|
|
1028
|
+
}
|
|
1029
|
+
modifyRoute(routeName, options) {
|
|
1030
|
+
const route = this.routes.find(({ config: { name: name$1 } }) => name$1 === routeName);
|
|
1031
|
+
if (!route) throw new Error(`Cannot call modifyRoute() on route \`${routeName}\`: route of that name not found`);
|
|
1032
|
+
if (route.config.sticky) throw new Error(`Cannot call modifyRoute() on route \`${routeName}\`: route is sticky and cannot be modified`);
|
|
1033
|
+
if ("name" in options) throw new Error(`Cannot rename the route \`${routeName}\` as \`${options.name}\`: renaming routes is not supported`);
|
|
1034
|
+
if ("sticky" in options) throw new Error(`Altering the stickiness of route \`${routeName}\` is not supported`);
|
|
1035
|
+
const newConfig = {
|
|
1036
|
+
...route.config,
|
|
1037
|
+
...options
|
|
1038
|
+
};
|
|
1039
|
+
Object.entries(options).forEach(([key, value]) => {
|
|
1040
|
+
if (value === null) delete newConfig[key];
|
|
1041
|
+
});
|
|
1042
|
+
route.init(newConfig);
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
1045
|
+
|
|
1046
|
+
//#endregion
|
|
1047
|
+
//#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/CallHistory.js
|
|
1048
|
+
const isName = (filter) => typeof filter === "string" && /^[\da-zA-Z-]+$/.test(filter) && !["matched", "unmatched"].includes(filter);
|
|
1049
|
+
const isMatchedOrUnmatched = (filter) => typeof filter === "boolean" || ["matched", "unmatched"].includes(filter);
|
|
1050
|
+
var CallHistory = class {
|
|
1051
|
+
constructor(config, router) {
|
|
1052
|
+
this.callLogs = [];
|
|
1053
|
+
this.config = config;
|
|
1054
|
+
this.router = router;
|
|
1055
|
+
}
|
|
1056
|
+
recordCall(callLog) {
|
|
1057
|
+
this.callLogs.push(callLog);
|
|
1058
|
+
}
|
|
1059
|
+
clear() {
|
|
1060
|
+
this.callLogs.forEach(({ route }) => {
|
|
1061
|
+
if (route) route.reset();
|
|
1062
|
+
});
|
|
1063
|
+
this.callLogs = [];
|
|
1064
|
+
}
|
|
1065
|
+
async flush(waitForResponseMethods) {
|
|
1066
|
+
const queuedPromises = this.callLogs.flatMap((call) => call.pendingPromises);
|
|
1067
|
+
await Promise.allSettled(queuedPromises);
|
|
1068
|
+
if (waitForResponseMethods) {
|
|
1069
|
+
await Promise.resolve();
|
|
1070
|
+
await this.flush();
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
calls(filter, options) {
|
|
1074
|
+
let calls = [...this.callLogs];
|
|
1075
|
+
if (typeof filter === "undefined" && !options) return calls;
|
|
1076
|
+
if (isMatchedOrUnmatched(filter)) {
|
|
1077
|
+
if ([true, "matched"].includes(filter)) calls = calls.filter(({ route }) => !route?.config || !route.config.isFallback);
|
|
1078
|
+
else if ([false, "unmatched"].includes(filter)) calls = calls.filter(({ route }) => Boolean(route?.config && route.config.isFallback));
|
|
1079
|
+
if (!options) return calls;
|
|
1080
|
+
} else if (isName(filter)) {
|
|
1081
|
+
calls = calls.filter(({ route }) => route?.config?.name === filter);
|
|
1082
|
+
if (!options) return calls;
|
|
1083
|
+
} else if (isUrlMatcher(filter)) options = {
|
|
1084
|
+
url: filter,
|
|
1085
|
+
...options || {}
|
|
1086
|
+
};
|
|
1087
|
+
else options = {
|
|
1088
|
+
...filter,
|
|
1089
|
+
...options || {}
|
|
1090
|
+
};
|
|
1091
|
+
const { matcher } = new Route_default({
|
|
1092
|
+
response: "ok",
|
|
1093
|
+
...options
|
|
1094
|
+
});
|
|
1095
|
+
calls = calls.filter(({ url, options: options$1 }) => {
|
|
1096
|
+
return matcher(createCallLogFromUrlAndOptions(url, options$1));
|
|
1097
|
+
});
|
|
1098
|
+
return calls;
|
|
1099
|
+
}
|
|
1100
|
+
called(filter, options) {
|
|
1101
|
+
return Boolean(this.calls(filter, options).length);
|
|
1102
|
+
}
|
|
1103
|
+
lastCall(filter, options) {
|
|
1104
|
+
return this.calls(filter, options).pop();
|
|
1105
|
+
}
|
|
1106
|
+
done(routeNames) {
|
|
1107
|
+
let routesToCheck = this.router.routes;
|
|
1108
|
+
if (routeNames) {
|
|
1109
|
+
routeNames = Array.isArray(routeNames) ? routeNames : [routeNames];
|
|
1110
|
+
routesToCheck = this.router.routes.filter(({ config: { name: name$1 } }) => routeNames.includes(name$1));
|
|
1111
|
+
}
|
|
1112
|
+
return routesToCheck.map((route) => {
|
|
1113
|
+
const calls = this.callLogs.filter(({ route: routeApplied }) => routeApplied === route);
|
|
1114
|
+
if (!calls.length) {
|
|
1115
|
+
console.warn(`Warning: ${route.config.name} not called`);
|
|
1116
|
+
return false;
|
|
1117
|
+
}
|
|
1118
|
+
const expectedTimes = route.config.repeat;
|
|
1119
|
+
if (!expectedTimes) return true;
|
|
1120
|
+
const actualTimes = calls.length;
|
|
1121
|
+
if (expectedTimes > actualTimes) {
|
|
1122
|
+
console.warn(`Warning: ${route.config.name} only called ${actualTimes} times, but ${expectedTimes} expected`);
|
|
1123
|
+
return false;
|
|
1124
|
+
}
|
|
1125
|
+
return true;
|
|
1126
|
+
}).every((isDone) => isDone);
|
|
1127
|
+
}
|
|
1128
|
+
};
|
|
1129
|
+
var CallHistory_default = CallHistory;
|
|
1130
|
+
|
|
1131
|
+
//#endregion
|
|
1132
|
+
//#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/FetchMock.js
|
|
1133
|
+
const defaultFetchMockConfig = {
|
|
1134
|
+
includeContentLength: true,
|
|
1135
|
+
matchPartialBody: false,
|
|
1136
|
+
Request: globalThis.Request,
|
|
1137
|
+
Response: globalThis.Response,
|
|
1138
|
+
Headers: globalThis.Headers,
|
|
1139
|
+
fetch: globalThis.fetch
|
|
1140
|
+
};
|
|
1141
|
+
const defineShorthand = (shorthandOptions) => {
|
|
1142
|
+
function shorthand(matcher, response, options) {
|
|
1143
|
+
return this.route(matcher, response, Object.assign(options || {}, shorthandOptions));
|
|
1144
|
+
}
|
|
1145
|
+
return shorthand;
|
|
1146
|
+
};
|
|
1147
|
+
const defineGreedyShorthand = (shorthandOptions) => {
|
|
1148
|
+
return function(response, options) {
|
|
1149
|
+
return this.route("*", response, Object.assign(options || {}, shorthandOptions));
|
|
1150
|
+
};
|
|
1151
|
+
};
|
|
1152
|
+
var FetchMock = class FetchMock {
|
|
1153
|
+
constructor(config, router) {
|
|
1154
|
+
this.sticky = defineShorthand({ sticky: true });
|
|
1155
|
+
this.once = defineShorthand({ repeat: 1 });
|
|
1156
|
+
this.any = defineGreedyShorthand({});
|
|
1157
|
+
this.anyOnce = defineGreedyShorthand({ repeat: 1 });
|
|
1158
|
+
this.get = defineShorthand({ method: "get" });
|
|
1159
|
+
this.getOnce = defineShorthand({
|
|
1160
|
+
method: "get",
|
|
1161
|
+
repeat: 1
|
|
1162
|
+
});
|
|
1163
|
+
this.post = defineShorthand({ method: "post" });
|
|
1164
|
+
this.postOnce = defineShorthand({
|
|
1165
|
+
method: "post",
|
|
1166
|
+
repeat: 1
|
|
1167
|
+
});
|
|
1168
|
+
this.put = defineShorthand({ method: "put" });
|
|
1169
|
+
this.putOnce = defineShorthand({
|
|
1170
|
+
method: "put",
|
|
1171
|
+
repeat: 1
|
|
1172
|
+
});
|
|
1173
|
+
this.delete = defineShorthand({ method: "delete" });
|
|
1174
|
+
this.deleteOnce = defineShorthand({
|
|
1175
|
+
method: "delete",
|
|
1176
|
+
repeat: 1
|
|
1177
|
+
});
|
|
1178
|
+
this.head = defineShorthand({ method: "head" });
|
|
1179
|
+
this.headOnce = defineShorthand({
|
|
1180
|
+
method: "head",
|
|
1181
|
+
repeat: 1
|
|
1182
|
+
});
|
|
1183
|
+
this.patch = defineShorthand({ method: "patch" });
|
|
1184
|
+
this.patchOnce = defineShorthand({
|
|
1185
|
+
method: "patch",
|
|
1186
|
+
repeat: 1
|
|
1187
|
+
});
|
|
1188
|
+
this.config = config;
|
|
1189
|
+
this.router = new Router(this.config, {
|
|
1190
|
+
routes: router ? [...router.routes] : [],
|
|
1191
|
+
fallbackRoute: router ? router.fallbackRoute : null
|
|
1192
|
+
});
|
|
1193
|
+
this.callHistory = new CallHistory_default(this.config, this.router);
|
|
1194
|
+
this.fetchHandler = this.fetchHandler.bind(this);
|
|
1195
|
+
Object.assign(this.fetchHandler, { fetchMock: this });
|
|
1196
|
+
}
|
|
1197
|
+
createInstance() {
|
|
1198
|
+
return new FetchMock({ ...this.config }, this.router);
|
|
1199
|
+
}
|
|
1200
|
+
async fetchHandler(requestInput, requestInit) {
|
|
1201
|
+
let callLog;
|
|
1202
|
+
if (requestInput instanceof this.config.Request) callLog = await createCallLogFromRequest(requestInput, requestInit);
|
|
1203
|
+
else callLog = createCallLogFromUrlAndOptions(requestInput, requestInit);
|
|
1204
|
+
this.callHistory.recordCall(callLog);
|
|
1205
|
+
const responsePromise = this.router.execute(callLog);
|
|
1206
|
+
callLog.pendingPromises.push(responsePromise);
|
|
1207
|
+
return responsePromise;
|
|
1208
|
+
}
|
|
1209
|
+
route(matcher, response, options) {
|
|
1210
|
+
this.router.addRoute(matcher, response, options);
|
|
1211
|
+
return this;
|
|
1212
|
+
}
|
|
1213
|
+
catch(response) {
|
|
1214
|
+
this.router.setFallback(response);
|
|
1215
|
+
return this;
|
|
1216
|
+
}
|
|
1217
|
+
defineMatcher(matcher) {
|
|
1218
|
+
Route_default.defineMatcher(matcher);
|
|
1219
|
+
}
|
|
1220
|
+
removeRoutes(options) {
|
|
1221
|
+
this.router.removeRoutes(options);
|
|
1222
|
+
return this;
|
|
1223
|
+
}
|
|
1224
|
+
removeRoute(routeName) {
|
|
1225
|
+
this.router.removeRoutes({ names: [routeName] });
|
|
1226
|
+
return this;
|
|
1227
|
+
}
|
|
1228
|
+
modifyRoute(routeName, options) {
|
|
1229
|
+
this.router.modifyRoute(routeName, options);
|
|
1230
|
+
return this;
|
|
1231
|
+
}
|
|
1232
|
+
clearHistory() {
|
|
1233
|
+
this.callHistory.clear();
|
|
1234
|
+
return this;
|
|
1235
|
+
}
|
|
1236
|
+
mockGlobal() {
|
|
1237
|
+
globalThis.fetch = this.fetchHandler;
|
|
1238
|
+
return this;
|
|
1239
|
+
}
|
|
1240
|
+
unmockGlobal() {
|
|
1241
|
+
globalThis.fetch = this.config.fetch;
|
|
1242
|
+
return this;
|
|
1243
|
+
}
|
|
1244
|
+
hardReset(options) {
|
|
1245
|
+
this.clearHistory();
|
|
1246
|
+
this.removeRoutes(options);
|
|
1247
|
+
this.unmockGlobal();
|
|
1248
|
+
return this;
|
|
1249
|
+
}
|
|
1250
|
+
spy(matcher, name$1) {
|
|
1251
|
+
const boundFetch = this.config.fetch.bind(globalThis);
|
|
1252
|
+
if (matcher) this.route(matcher, ({ args }) => boundFetch(...args), name$1);
|
|
1253
|
+
else this.catch(({ args }) => boundFetch(...args));
|
|
1254
|
+
return this;
|
|
1255
|
+
}
|
|
1256
|
+
spyGlobal() {
|
|
1257
|
+
this.mockGlobal();
|
|
1258
|
+
return this.spy();
|
|
1259
|
+
}
|
|
1260
|
+
};
|
|
1261
|
+
const fetchMock = new FetchMock({ ...defaultFetchMockConfig });
|
|
1262
|
+
var FetchMock_default = fetchMock;
|
|
1263
|
+
|
|
1264
|
+
//#endregion
|
|
1265
|
+
//#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/index.js
|
|
1266
|
+
var esm_default = FetchMock_default;
|
|
1267
|
+
|
|
1268
|
+
//#endregion
|
|
1269
|
+
//#region deno.json
|
|
1270
|
+
var name = "@fedify/webfinger";
|
|
1271
|
+
var version = "2.0.0";
|
|
1272
|
+
var license = "MIT";
|
|
1273
|
+
var exports = { ".": "./src/mod.ts" };
|
|
1274
|
+
var description = "WebFinger client library for Fedify";
|
|
1275
|
+
var author = {
|
|
1276
|
+
"name": "Hong Minhee",
|
|
1277
|
+
"email": "hong@minhee.org",
|
|
1278
|
+
"url": "https://hongminhee.org/"
|
|
1279
|
+
};
|
|
1280
|
+
var imports = {
|
|
1281
|
+
"es-toolkit": "npm:es-toolkit@^1.42.0",
|
|
1282
|
+
"fetch-mock": "npm:fetch-mock@^12.5.4"
|
|
1283
|
+
};
|
|
1284
|
+
var exclude = ["dist", "node_modules"];
|
|
1285
|
+
var tasks = {
|
|
1286
|
+
"check": "deno fmt --check && deno lint && deno check src/*.ts",
|
|
1287
|
+
"test": "deno test"
|
|
1288
|
+
};
|
|
1289
|
+
var deno_default = {
|
|
1290
|
+
name,
|
|
1291
|
+
version,
|
|
1292
|
+
license,
|
|
1293
|
+
exports,
|
|
1294
|
+
description,
|
|
1295
|
+
author,
|
|
1296
|
+
imports,
|
|
1297
|
+
exclude,
|
|
1298
|
+
tasks
|
|
1299
|
+
};
|
|
1300
|
+
|
|
1301
|
+
//#endregion
|
|
1302
|
+
//#region src/lookup.ts
|
|
1303
|
+
const logger = getLogger([
|
|
1304
|
+
"fedify",
|
|
1305
|
+
"webfinger",
|
|
1306
|
+
"lookup"
|
|
1307
|
+
]);
|
|
1308
|
+
const DEFAULT_MAX_REDIRECTION = 5;
|
|
1309
|
+
/**
|
|
1310
|
+
* Looks up a WebFinger resource.
|
|
1311
|
+
* @param resource The resource URL to look up.
|
|
1312
|
+
* @param options Extra options for looking up the resource.
|
|
1313
|
+
* @returns The resource descriptor, or `null` if not found.
|
|
1314
|
+
* @since 0.2.0
|
|
1315
|
+
*/
|
|
1316
|
+
async function lookupWebFinger(resource, options = {}) {
|
|
1317
|
+
const tracerProvider = options.tracerProvider ?? trace.getTracerProvider();
|
|
1318
|
+
const tracer = tracerProvider.getTracer(deno_default.name, deno_default.version);
|
|
1319
|
+
return await tracer.startActiveSpan("webfinger.lookup", {
|
|
1320
|
+
kind: SpanKind.CLIENT,
|
|
1321
|
+
attributes: {
|
|
1322
|
+
"webfinger.resource": resource.toString(),
|
|
1323
|
+
"webfinger.resource.scheme": typeof resource === "string" ? resource.replace(/:.*$/, "") : resource.protocol.replace(/:$/, "")
|
|
1324
|
+
}
|
|
1325
|
+
}, async (span) => {
|
|
1326
|
+
try {
|
|
1327
|
+
const result = await lookupWebFingerInternal(resource, options);
|
|
1328
|
+
span.setStatus({ code: result === null ? SpanStatusCode.ERROR : SpanStatusCode.OK });
|
|
1329
|
+
return result;
|
|
1330
|
+
} catch (error) {
|
|
1331
|
+
span.setStatus({
|
|
1332
|
+
code: SpanStatusCode.ERROR,
|
|
1333
|
+
message: String(error)
|
|
1334
|
+
});
|
|
1335
|
+
throw error;
|
|
1336
|
+
} finally {
|
|
1337
|
+
span.end();
|
|
1338
|
+
}
|
|
1339
|
+
});
|
|
1340
|
+
}
|
|
1341
|
+
async function lookupWebFingerInternal(resource, options = {}) {
|
|
1342
|
+
if (typeof resource === "string") resource = new URL(resource);
|
|
1343
|
+
let protocol = "https:";
|
|
1344
|
+
let server;
|
|
1345
|
+
if (resource.protocol === "acct:") {
|
|
1346
|
+
const atPos = resource.pathname.lastIndexOf("@");
|
|
1347
|
+
if (atPos < 0) return null;
|
|
1348
|
+
server = resource.pathname.substring(atPos + 1);
|
|
1349
|
+
if (server === "") return null;
|
|
1350
|
+
} else {
|
|
1351
|
+
protocol = resource.protocol;
|
|
1352
|
+
server = resource.host;
|
|
1353
|
+
}
|
|
1354
|
+
let url = new URL(`${protocol}//${server}/.well-known/webfinger`);
|
|
1355
|
+
url.searchParams.set("resource", resource.href);
|
|
1356
|
+
let redirected = 0;
|
|
1357
|
+
while (true) {
|
|
1358
|
+
logger.debug("Fetching WebFinger resource descriptor from {url}...", { url: url.href });
|
|
1359
|
+
let response;
|
|
1360
|
+
if (options.allowPrivateAddress !== true) try {
|
|
1361
|
+
await validatePublicUrl(url.href);
|
|
1362
|
+
} catch (e) {
|
|
1363
|
+
if (e instanceof UrlError) {
|
|
1364
|
+
logger.error("Invalid URL for WebFinger resource descriptor: {error}", { error: e });
|
|
1365
|
+
return null;
|
|
1366
|
+
}
|
|
1367
|
+
throw e;
|
|
1368
|
+
}
|
|
1369
|
+
try {
|
|
1370
|
+
response = await fetch(url, {
|
|
1371
|
+
headers: {
|
|
1372
|
+
Accept: "application/jrd+json",
|
|
1373
|
+
"User-Agent": typeof options.userAgent === "string" ? options.userAgent : getUserAgent(options.userAgent)
|
|
1374
|
+
},
|
|
1375
|
+
redirect: "manual",
|
|
1376
|
+
signal: options.signal
|
|
1377
|
+
});
|
|
1378
|
+
} catch (error) {
|
|
1379
|
+
logger.debug("Failed to fetch WebFinger resource descriptor: {error}", {
|
|
1380
|
+
url: url.href,
|
|
1381
|
+
error
|
|
1382
|
+
});
|
|
1383
|
+
return null;
|
|
1384
|
+
}
|
|
1385
|
+
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
|
|
1386
|
+
redirected++;
|
|
1387
|
+
const maxRedirection = options.maxRedirection ?? DEFAULT_MAX_REDIRECTION;
|
|
1388
|
+
if (redirected >= maxRedirection) {
|
|
1389
|
+
logger.error("Too many redirections ({redirections}) while fetching WebFinger resource descriptor.", { redirections: redirected });
|
|
1390
|
+
return null;
|
|
1391
|
+
}
|
|
1392
|
+
const redirectedUrl = new URL(response.headers.get("Location"), response.url == null || response.url === "" ? url : response.url);
|
|
1393
|
+
if (redirectedUrl.protocol !== url.protocol) {
|
|
1394
|
+
logger.error("Redirected to a different protocol ({protocol} to {redirectedProtocol}) while fetching WebFinger resource descriptor.", {
|
|
1395
|
+
protocol: url.protocol,
|
|
1396
|
+
redirectedProtocol: redirectedUrl.protocol
|
|
1397
|
+
});
|
|
1398
|
+
return null;
|
|
1399
|
+
}
|
|
1400
|
+
url = redirectedUrl;
|
|
1401
|
+
continue;
|
|
1402
|
+
}
|
|
1403
|
+
if (!response.ok) {
|
|
1404
|
+
logger.debug("Failed to fetch WebFinger resource descriptor: {status} {statusText}.", {
|
|
1405
|
+
url: url.href,
|
|
1406
|
+
status: response.status,
|
|
1407
|
+
statusText: response.statusText
|
|
1408
|
+
});
|
|
1409
|
+
return null;
|
|
1410
|
+
}
|
|
1411
|
+
try {
|
|
1412
|
+
return await response.json();
|
|
1413
|
+
} catch (e) {
|
|
1414
|
+
if (e instanceof SyntaxError) {
|
|
1415
|
+
logger.debug("Failed to parse WebFinger resource descriptor as JSON: {error}", { error: e });
|
|
1416
|
+
return null;
|
|
1417
|
+
}
|
|
1418
|
+
throw e;
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
//#endregion
|
|
1424
|
+
//#region src/lookup.test.ts
|
|
1425
|
+
test({
|
|
1426
|
+
name: "lookupWebFinger()",
|
|
1427
|
+
sanitizeOps: false,
|
|
1428
|
+
sanitizeResources: false,
|
|
1429
|
+
async fn(t) {
|
|
1430
|
+
await t.step("invalid resource", async () => {
|
|
1431
|
+
deepStrictEqual(await lookupWebFinger("acct:johndoe"), null);
|
|
1432
|
+
deepStrictEqual(await lookupWebFinger(new URL("acct:johndoe")), null);
|
|
1433
|
+
deepStrictEqual(await lookupWebFinger("acct:johndoe@"), null);
|
|
1434
|
+
deepStrictEqual(await lookupWebFinger(new URL("acct:johndoe@")), null);
|
|
1435
|
+
});
|
|
1436
|
+
await t.step("connection refused", async () => {
|
|
1437
|
+
deepStrictEqual(await lookupWebFinger("acct:johndoe@fedify-test.internal"), null);
|
|
1438
|
+
deepStrictEqual(await lookupWebFinger("https://fedify-test.internal/foo"), null);
|
|
1439
|
+
});
|
|
1440
|
+
esm_default.spyGlobal();
|
|
1441
|
+
esm_default.get("begin:https://example.com/.well-known/webfinger?", { status: 404 });
|
|
1442
|
+
await t.step("not found", async () => {
|
|
1443
|
+
deepStrictEqual(await lookupWebFinger("acct:johndoe@example.com"), null);
|
|
1444
|
+
deepStrictEqual(await lookupWebFinger("https://example.com/foo"), null);
|
|
1445
|
+
});
|
|
1446
|
+
const expected = {
|
|
1447
|
+
subject: "acct:johndoe@example.com",
|
|
1448
|
+
links: []
|
|
1449
|
+
};
|
|
1450
|
+
esm_default.removeRoutes();
|
|
1451
|
+
esm_default.get("https://example.com/.well-known/webfinger?resource=acct%3Ajohndoe%40example.com", { body: expected });
|
|
1452
|
+
await t.step("acct", async () => {
|
|
1453
|
+
deepStrictEqual(await lookupWebFinger("acct:johndoe@example.com"), expected);
|
|
1454
|
+
});
|
|
1455
|
+
const expected2 = {
|
|
1456
|
+
subject: "https://example.com/foo",
|
|
1457
|
+
links: []
|
|
1458
|
+
};
|
|
1459
|
+
esm_default.removeRoutes();
|
|
1460
|
+
esm_default.get("https://example.com/.well-known/webfinger?resource=https%3A%2F%2Fexample.com%2Ffoo", { body: expected2 });
|
|
1461
|
+
await t.step("https", async () => {
|
|
1462
|
+
deepStrictEqual(await lookupWebFinger("https://example.com/foo"), expected2);
|
|
1463
|
+
});
|
|
1464
|
+
esm_default.removeRoutes();
|
|
1465
|
+
esm_default.get("begin:https://example.com/.well-known/webfinger?", { body: "not json" });
|
|
1466
|
+
await t.step("invalid response", async () => {
|
|
1467
|
+
deepStrictEqual(await lookupWebFinger("acct:johndoe@example.com"), null);
|
|
1468
|
+
});
|
|
1469
|
+
esm_default.removeRoutes();
|
|
1470
|
+
esm_default.get("begin:https://localhost/.well-known/webfinger?", {
|
|
1471
|
+
subject: "acct:test@localhost",
|
|
1472
|
+
links: [{
|
|
1473
|
+
rel: "self",
|
|
1474
|
+
type: "application/activity+json",
|
|
1475
|
+
href: "https://localhost/actor"
|
|
1476
|
+
}]
|
|
1477
|
+
});
|
|
1478
|
+
await t.step("private address", async () => {
|
|
1479
|
+
deepStrictEqual(await lookupWebFinger("acct:test@localhost"), null);
|
|
1480
|
+
deepStrictEqual(await lookupWebFinger("acct:test@localhost", { allowPrivateAddress: true }), {
|
|
1481
|
+
subject: "acct:test@localhost",
|
|
1482
|
+
links: [{
|
|
1483
|
+
rel: "self",
|
|
1484
|
+
type: "application/activity+json",
|
|
1485
|
+
href: "https://localhost/actor"
|
|
1486
|
+
}]
|
|
1487
|
+
});
|
|
1488
|
+
});
|
|
1489
|
+
esm_default.removeRoutes();
|
|
1490
|
+
esm_default.get("begin:https://example.com/.well-known/webfinger?", {
|
|
1491
|
+
status: 302,
|
|
1492
|
+
headers: { Location: "/.well-known/webfinger2" }
|
|
1493
|
+
});
|
|
1494
|
+
esm_default.get("begin:https://example.com/.well-known/webfinger2", { body: expected });
|
|
1495
|
+
await t.step("redirection", async () => {
|
|
1496
|
+
deepStrictEqual(await lookupWebFinger("acct:johndoe@example.com"), expected);
|
|
1497
|
+
});
|
|
1498
|
+
esm_default.removeRoutes();
|
|
1499
|
+
esm_default.get("begin:https://example.com/.well-known/webfinger?", {
|
|
1500
|
+
status: 302,
|
|
1501
|
+
headers: { Location: "/.well-known/webfinger" }
|
|
1502
|
+
});
|
|
1503
|
+
await t.step("infinite redirection", async () => {
|
|
1504
|
+
const result = await withTimeout(() => lookupWebFinger("acct:johndoe@example.com"), 2e3);
|
|
1505
|
+
deepStrictEqual(result, null);
|
|
1506
|
+
});
|
|
1507
|
+
esm_default.removeRoutes();
|
|
1508
|
+
esm_default.get("begin:https://example.com/.well-known/webfinger?", {
|
|
1509
|
+
status: 302,
|
|
1510
|
+
headers: { Location: "ftp://example.com/" }
|
|
1511
|
+
});
|
|
1512
|
+
await t.step("redirection to different protocol", async () => {
|
|
1513
|
+
deepStrictEqual(await lookupWebFinger("acct:johndoe@example.com"), null);
|
|
1514
|
+
});
|
|
1515
|
+
esm_default.removeRoutes();
|
|
1516
|
+
esm_default.get("begin:https://example.com/.well-known/webfinger?", {
|
|
1517
|
+
status: 302,
|
|
1518
|
+
headers: { Location: "https://localhost/" }
|
|
1519
|
+
});
|
|
1520
|
+
await t.step("redirection to private address", async () => {
|
|
1521
|
+
deepStrictEqual(await lookupWebFinger("acct:johndoe@example.com"), null);
|
|
1522
|
+
});
|
|
1523
|
+
esm_default.removeRoutes();
|
|
1524
|
+
let redirectCount = 0;
|
|
1525
|
+
esm_default.get("begin:https://example.com/.well-known/webfinger", () => {
|
|
1526
|
+
redirectCount++;
|
|
1527
|
+
if (redirectCount < 3) return {
|
|
1528
|
+
status: 302,
|
|
1529
|
+
headers: { Location: `/.well-known/webfinger?redirect=${redirectCount}` }
|
|
1530
|
+
};
|
|
1531
|
+
return { body: expected };
|
|
1532
|
+
});
|
|
1533
|
+
await t.step("custom maxRedirection", async () => {
|
|
1534
|
+
redirectCount = 0;
|
|
1535
|
+
deepStrictEqual(await lookupWebFinger("acct:johndoe@example.com", { maxRedirection: 2 }), null);
|
|
1536
|
+
redirectCount = 0;
|
|
1537
|
+
deepStrictEqual(await lookupWebFinger("acct:johndoe@example.com", { maxRedirection: 3 }), expected);
|
|
1538
|
+
redirectCount = 0;
|
|
1539
|
+
deepStrictEqual(await lookupWebFinger("acct:johndoe@example.com"), expected);
|
|
1540
|
+
});
|
|
1541
|
+
esm_default.removeRoutes();
|
|
1542
|
+
esm_default.get("begin:https://example.com/.well-known/webfinger?", () => new Promise((resolve) => {
|
|
1543
|
+
const timeoutId = setTimeout(() => {
|
|
1544
|
+
resolve({ body: expected });
|
|
1545
|
+
}, 1e3);
|
|
1546
|
+
return () => clearTimeout(timeoutId);
|
|
1547
|
+
}));
|
|
1548
|
+
await t.step("request cancellation", async () => {
|
|
1549
|
+
const controller = new AbortController();
|
|
1550
|
+
const promise = lookupWebFinger("acct:johndoe@example.com", { signal: controller.signal });
|
|
1551
|
+
controller.abort();
|
|
1552
|
+
deepStrictEqual(await promise, null);
|
|
1553
|
+
});
|
|
1554
|
+
esm_default.removeRoutes();
|
|
1555
|
+
let redirectCount2 = 0;
|
|
1556
|
+
esm_default.get("begin:https://example.com/.well-known/webfinger", () => {
|
|
1557
|
+
redirectCount2++;
|
|
1558
|
+
if (redirectCount2 === 1) return {
|
|
1559
|
+
status: 302,
|
|
1560
|
+
headers: { Location: "/.well-known/webfinger2" }
|
|
1561
|
+
};
|
|
1562
|
+
return new Promise((resolve) => {
|
|
1563
|
+
const timeoutId = setTimeout(() => {
|
|
1564
|
+
resolve({ body: expected });
|
|
1565
|
+
}, 1e3);
|
|
1566
|
+
return () => clearTimeout(timeoutId);
|
|
1567
|
+
});
|
|
1568
|
+
});
|
|
1569
|
+
await t.step("cancellation during redirection", async () => {
|
|
1570
|
+
const controller = new AbortController();
|
|
1571
|
+
const promise = lookupWebFinger("acct:johndoe@example.com", { signal: controller.signal });
|
|
1572
|
+
setTimeout(() => controller.abort(), 100);
|
|
1573
|
+
deepStrictEqual(await promise, null);
|
|
1574
|
+
});
|
|
1575
|
+
esm_default.removeRoutes();
|
|
1576
|
+
esm_default.get("begin:https://example.com/.well-known/webfinger?", () => new Promise((resolve) => {
|
|
1577
|
+
const timeoutId = setTimeout(() => {
|
|
1578
|
+
resolve({ body: expected });
|
|
1579
|
+
}, 500);
|
|
1580
|
+
return () => clearTimeout(timeoutId);
|
|
1581
|
+
}));
|
|
1582
|
+
await t.step("cancellation with immediate abort", async () => {
|
|
1583
|
+
const controller = new AbortController();
|
|
1584
|
+
controller.abort();
|
|
1585
|
+
const result = await lookupWebFinger("acct:johndoe@example.com", { signal: controller.signal });
|
|
1586
|
+
deepStrictEqual(result, null);
|
|
1587
|
+
});
|
|
1588
|
+
esm_default.removeRoutes();
|
|
1589
|
+
esm_default.get("begin:https://example.com/.well-known/webfinger?", { body: expected });
|
|
1590
|
+
await t.step("successful request with signal", async () => {
|
|
1591
|
+
const controller = new AbortController();
|
|
1592
|
+
const result = await lookupWebFinger("acct:johndoe@example.com", { signal: controller.signal });
|
|
1593
|
+
deepStrictEqual(result, expected);
|
|
1594
|
+
});
|
|
1595
|
+
esm_default.hardReset();
|
|
1596
|
+
}
|
|
1597
|
+
});
|
|
1598
|
+
|
|
1599
|
+
//#endregion
|