@fedify/express 2.3.12-dev.2365 → 2.3.13
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/dist/index.cjs +90 -4
- package/dist/index.js +90 -4
- package/package.json +4 -3
package/dist/index.cjs
CHANGED
|
@@ -6,9 +6,10 @@ function integrateFederation(federation, contextDataFactory) {
|
|
|
6
6
|
const body = req.method === "GET" || req.method === "HEAD" ? void 0 : createRequestBody(req);
|
|
7
7
|
const request = fromERequest(req, body?.stream);
|
|
8
8
|
const contextData = contextDataFactory(req);
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
const contextDataPromise = contextData instanceof Promise ? contextData : Promise.resolve(contextData);
|
|
10
|
+
let notFound = false;
|
|
11
|
+
let notAcceptable = false;
|
|
12
|
+
contextDataPromise.then(async (contextData) => {
|
|
12
13
|
const response = await federation.fetch(request, {
|
|
13
14
|
contextData,
|
|
14
15
|
onNotFound: () => {
|
|
@@ -19,6 +20,7 @@ function integrateFederation(federation, contextDataFactory) {
|
|
|
19
20
|
},
|
|
20
21
|
onNotAcceptable: () => {
|
|
21
22
|
notAcceptable = true;
|
|
23
|
+
installNotAcceptableFallback(req, res);
|
|
22
24
|
body?.restore();
|
|
23
25
|
next();
|
|
24
26
|
return new Response("Not acceptable", {
|
|
@@ -30,7 +32,7 @@ function integrateFederation(federation, contextDataFactory) {
|
|
|
30
32
|
});
|
|
31
33
|
}
|
|
32
34
|
});
|
|
33
|
-
if (notFound || notAcceptable
|
|
35
|
+
if (notFound || notAcceptable) return;
|
|
34
36
|
await setEResponse(res, response);
|
|
35
37
|
res.end();
|
|
36
38
|
res.status = () => res;
|
|
@@ -39,6 +41,9 @@ function integrateFederation(federation, contextDataFactory) {
|
|
|
39
41
|
res.json = () => res;
|
|
40
42
|
res.removeHeader = () => res;
|
|
41
43
|
res.setHeader = () => res;
|
|
44
|
+
}).catch((error) => {
|
|
45
|
+
if (notFound || notAcceptable) throw error;
|
|
46
|
+
next(error === "route" || error === "router" ? new Error(error) : error || /* @__PURE__ */ new Error("The federation middleware promise was rejected."));
|
|
42
47
|
});
|
|
43
48
|
};
|
|
44
49
|
}
|
|
@@ -153,5 +158,86 @@ function setEResponse(res, response) {
|
|
|
153
158
|
});
|
|
154
159
|
});
|
|
155
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Waits for downstream handling to finish before replacing an unsent 404.
|
|
163
|
+
* A matched route can still fall through, and next() does not wait for an
|
|
164
|
+
* asynchronous handler. Already committed responses belong to the application.
|
|
165
|
+
*/
|
|
166
|
+
function installNotAcceptableFallback(req, res) {
|
|
167
|
+
const end = res.end;
|
|
168
|
+
const send = res.send;
|
|
169
|
+
let pending = true;
|
|
170
|
+
let applicationResponse = false;
|
|
171
|
+
const wrappedSend = function(body) {
|
|
172
|
+
applicationResponse = true;
|
|
173
|
+
return send.call(this, body);
|
|
174
|
+
};
|
|
175
|
+
res.send = wrappedSend;
|
|
176
|
+
const wrapEnd = (end) => function(...args) {
|
|
177
|
+
if (pending) {
|
|
178
|
+
Object.defineProperty(this, "end", {
|
|
179
|
+
configurable: true,
|
|
180
|
+
enumerable: true,
|
|
181
|
+
writable: true,
|
|
182
|
+
value: end
|
|
183
|
+
});
|
|
184
|
+
if (this.send === wrappedSend) this.send = send;
|
|
185
|
+
}
|
|
186
|
+
if (!pending || applicationResponse || this.headersSent || this.statusCode !== 404 || !isDefaultNotFound(req, this, args[0])) {
|
|
187
|
+
pending = false;
|
|
188
|
+
return end.apply(this, args);
|
|
189
|
+
}
|
|
190
|
+
pending = false;
|
|
191
|
+
const callback = args.find((arg) => typeof arg === "function");
|
|
192
|
+
const body = "Not acceptable";
|
|
193
|
+
this.statusCode = 406;
|
|
194
|
+
this.statusMessage = "Not Acceptable";
|
|
195
|
+
for (const header of [
|
|
196
|
+
"Content-Encoding",
|
|
197
|
+
"Content-Language",
|
|
198
|
+
"Content-Range",
|
|
199
|
+
"ETag",
|
|
200
|
+
"Last-Modified",
|
|
201
|
+
"Transfer-Encoding"
|
|
202
|
+
]) this.removeHeader(header);
|
|
203
|
+
this.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
204
|
+
this.setHeader("Content-Length", node_buffer.Buffer.byteLength(body));
|
|
205
|
+
this.vary("Accept");
|
|
206
|
+
return end.call(this, req.method === "HEAD" ? void 0 : body, "utf8", callback);
|
|
207
|
+
};
|
|
208
|
+
let wrappedEnd = wrapEnd(end);
|
|
209
|
+
Object.defineProperty(res, "end", {
|
|
210
|
+
configurable: true,
|
|
211
|
+
enumerable: true,
|
|
212
|
+
get: () => wrappedEnd,
|
|
213
|
+
set: (value) => {
|
|
214
|
+
wrappedEnd = wrapEnd(value);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Express offers no public router-exhaustion callback to middleware. Recognize
|
|
220
|
+
* finalhandler's default page, failing safe when its format changes. An app
|
|
221
|
+
* response identical to that default page cannot be distinguished from it.
|
|
222
|
+
*/
|
|
223
|
+
function isDefaultNotFound(req, res, chunk) {
|
|
224
|
+
if (res.getHeader("Content-Security-Policy") !== "default-src 'none'" || res.getHeader("X-Content-Type-Options") !== "nosniff" || res.getHeader("Content-Type") !== "text/html; charset=utf-8") return false;
|
|
225
|
+
const encodedPath = (req.originalUrl ?? req.url).split("?")[0].replace(/(^|[^\uD800-\uDBFF])[\uDC00-\uDFFF]|[\uD800-\uDBFF]([^\uDC00-\uDFFF]|$)/g, "$1�$2").replace(/(?:[^\x21\x23-\x3B\x3D\x3F-\x5F\x61-\x7A\x7C\x7E]|%(?:[^0-9A-Fa-f]|[0-9A-Fa-f][^0-9A-Fa-f]|$))+/g, encodeURI);
|
|
226
|
+
const page = `<!DOCTYPE html>
|
|
227
|
+
<html lang="en">
|
|
228
|
+
<head>
|
|
229
|
+
<meta charset="utf-8">
|
|
230
|
+
<title>Error</title>
|
|
231
|
+
</head>
|
|
232
|
+
<body>
|
|
233
|
+
<pre>${`Cannot ${req.method} ${encodedPath}`.replace(/[&<>"']/g, (character) => ({
|
|
234
|
+
"&": "&",
|
|
235
|
+
"<": "<",
|
|
236
|
+
">": ">",
|
|
237
|
+
"\"": """,
|
|
238
|
+
"'": "'"
|
|
239
|
+
})[character])}</pre>\n</body>\n</html>\n`;
|
|
240
|
+
return req.method === "HEAD" ? chunk == null && Number(res.getHeader("Content-Length")) === node_buffer.Buffer.byteLength(page) : chunk === page;
|
|
241
|
+
}
|
|
156
242
|
//#endregion
|
|
157
243
|
exports.integrateFederation = integrateFederation;
|
package/dist/index.js
CHANGED
|
@@ -5,9 +5,10 @@ function integrateFederation(federation, contextDataFactory) {
|
|
|
5
5
|
const body = req.method === "GET" || req.method === "HEAD" ? void 0 : createRequestBody(req);
|
|
6
6
|
const request = fromERequest(req, body?.stream);
|
|
7
7
|
const contextData = contextDataFactory(req);
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
const contextDataPromise = contextData instanceof Promise ? contextData : Promise.resolve(contextData);
|
|
9
|
+
let notFound = false;
|
|
10
|
+
let notAcceptable = false;
|
|
11
|
+
contextDataPromise.then(async (contextData) => {
|
|
11
12
|
const response = await federation.fetch(request, {
|
|
12
13
|
contextData,
|
|
13
14
|
onNotFound: () => {
|
|
@@ -18,6 +19,7 @@ function integrateFederation(federation, contextDataFactory) {
|
|
|
18
19
|
},
|
|
19
20
|
onNotAcceptable: () => {
|
|
20
21
|
notAcceptable = true;
|
|
22
|
+
installNotAcceptableFallback(req, res);
|
|
21
23
|
body?.restore();
|
|
22
24
|
next();
|
|
23
25
|
return new Response("Not acceptable", {
|
|
@@ -29,7 +31,7 @@ function integrateFederation(federation, contextDataFactory) {
|
|
|
29
31
|
});
|
|
30
32
|
}
|
|
31
33
|
});
|
|
32
|
-
if (notFound || notAcceptable
|
|
34
|
+
if (notFound || notAcceptable) return;
|
|
33
35
|
await setEResponse(res, response);
|
|
34
36
|
res.end();
|
|
35
37
|
res.status = () => res;
|
|
@@ -38,6 +40,9 @@ function integrateFederation(federation, contextDataFactory) {
|
|
|
38
40
|
res.json = () => res;
|
|
39
41
|
res.removeHeader = () => res;
|
|
40
42
|
res.setHeader = () => res;
|
|
43
|
+
}).catch((error) => {
|
|
44
|
+
if (notFound || notAcceptable) throw error;
|
|
45
|
+
next(error === "route" || error === "router" ? new Error(error) : error || /* @__PURE__ */ new Error("The federation middleware promise was rejected."));
|
|
41
46
|
});
|
|
42
47
|
};
|
|
43
48
|
}
|
|
@@ -152,5 +157,86 @@ function setEResponse(res, response) {
|
|
|
152
157
|
});
|
|
153
158
|
});
|
|
154
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* Waits for downstream handling to finish before replacing an unsent 404.
|
|
162
|
+
* A matched route can still fall through, and next() does not wait for an
|
|
163
|
+
* asynchronous handler. Already committed responses belong to the application.
|
|
164
|
+
*/
|
|
165
|
+
function installNotAcceptableFallback(req, res) {
|
|
166
|
+
const end = res.end;
|
|
167
|
+
const send = res.send;
|
|
168
|
+
let pending = true;
|
|
169
|
+
let applicationResponse = false;
|
|
170
|
+
const wrappedSend = function(body) {
|
|
171
|
+
applicationResponse = true;
|
|
172
|
+
return send.call(this, body);
|
|
173
|
+
};
|
|
174
|
+
res.send = wrappedSend;
|
|
175
|
+
const wrapEnd = (end) => function(...args) {
|
|
176
|
+
if (pending) {
|
|
177
|
+
Object.defineProperty(this, "end", {
|
|
178
|
+
configurable: true,
|
|
179
|
+
enumerable: true,
|
|
180
|
+
writable: true,
|
|
181
|
+
value: end
|
|
182
|
+
});
|
|
183
|
+
if (this.send === wrappedSend) this.send = send;
|
|
184
|
+
}
|
|
185
|
+
if (!pending || applicationResponse || this.headersSent || this.statusCode !== 404 || !isDefaultNotFound(req, this, args[0])) {
|
|
186
|
+
pending = false;
|
|
187
|
+
return end.apply(this, args);
|
|
188
|
+
}
|
|
189
|
+
pending = false;
|
|
190
|
+
const callback = args.find((arg) => typeof arg === "function");
|
|
191
|
+
const body = "Not acceptable";
|
|
192
|
+
this.statusCode = 406;
|
|
193
|
+
this.statusMessage = "Not Acceptable";
|
|
194
|
+
for (const header of [
|
|
195
|
+
"Content-Encoding",
|
|
196
|
+
"Content-Language",
|
|
197
|
+
"Content-Range",
|
|
198
|
+
"ETag",
|
|
199
|
+
"Last-Modified",
|
|
200
|
+
"Transfer-Encoding"
|
|
201
|
+
]) this.removeHeader(header);
|
|
202
|
+
this.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
203
|
+
this.setHeader("Content-Length", Buffer.byteLength(body));
|
|
204
|
+
this.vary("Accept");
|
|
205
|
+
return end.call(this, req.method === "HEAD" ? void 0 : body, "utf8", callback);
|
|
206
|
+
};
|
|
207
|
+
let wrappedEnd = wrapEnd(end);
|
|
208
|
+
Object.defineProperty(res, "end", {
|
|
209
|
+
configurable: true,
|
|
210
|
+
enumerable: true,
|
|
211
|
+
get: () => wrappedEnd,
|
|
212
|
+
set: (value) => {
|
|
213
|
+
wrappedEnd = wrapEnd(value);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Express offers no public router-exhaustion callback to middleware. Recognize
|
|
219
|
+
* finalhandler's default page, failing safe when its format changes. An app
|
|
220
|
+
* response identical to that default page cannot be distinguished from it.
|
|
221
|
+
*/
|
|
222
|
+
function isDefaultNotFound(req, res, chunk) {
|
|
223
|
+
if (res.getHeader("Content-Security-Policy") !== "default-src 'none'" || res.getHeader("X-Content-Type-Options") !== "nosniff" || res.getHeader("Content-Type") !== "text/html; charset=utf-8") return false;
|
|
224
|
+
const encodedPath = (req.originalUrl ?? req.url).split("?")[0].replace(/(^|[^\uD800-\uDBFF])[\uDC00-\uDFFF]|[\uD800-\uDBFF]([^\uDC00-\uDFFF]|$)/g, "$1�$2").replace(/(?:[^\x21\x23-\x3B\x3D\x3F-\x5F\x61-\x7A\x7C\x7E]|%(?:[^0-9A-Fa-f]|[0-9A-Fa-f][^0-9A-Fa-f]|$))+/g, encodeURI);
|
|
225
|
+
const page = `<!DOCTYPE html>
|
|
226
|
+
<html lang="en">
|
|
227
|
+
<head>
|
|
228
|
+
<meta charset="utf-8">
|
|
229
|
+
<title>Error</title>
|
|
230
|
+
</head>
|
|
231
|
+
<body>
|
|
232
|
+
<pre>${`Cannot ${req.method} ${encodedPath}`.replace(/[&<>"']/g, (character) => ({
|
|
233
|
+
"&": "&",
|
|
234
|
+
"<": "<",
|
|
235
|
+
">": ">",
|
|
236
|
+
"\"": """,
|
|
237
|
+
"'": "'"
|
|
238
|
+
})[character])}</pre>\n</body>\n</html>\n`;
|
|
239
|
+
return req.method === "HEAD" ? chunk == null && Number(res.getHeader("Content-Length")) === Buffer.byteLength(page) : chunk === page;
|
|
240
|
+
}
|
|
155
241
|
//#endregion
|
|
156
242
|
export { integrateFederation };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fedify/express",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.13",
|
|
4
4
|
"description": "Integrate Fedify with Express",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Fedify",
|
|
@@ -49,13 +49,14 @@
|
|
|
49
49
|
],
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"express": ">=4.0.0",
|
|
52
|
-
"@fedify/fedify": "^2.3.
|
|
52
|
+
"@fedify/fedify": "^2.3.13"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@types/express": "^4.0.0",
|
|
56
56
|
"@types/node": "^22.17.0",
|
|
57
57
|
"tsdown": "^0.22.0",
|
|
58
|
-
"typescript": "^6.0.0"
|
|
58
|
+
"typescript": "^6.0.0",
|
|
59
|
+
"@fedify/fixture": "2.0.0"
|
|
59
60
|
},
|
|
60
61
|
"scripts": {
|
|
61
62
|
"build:self": "tsdown",
|