@jaypie/express 1.2.5 → 1.2.7
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/cjs/cors.helper.d.ts +10 -0
- package/dist/cjs/index.cjs +106 -4
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/cors.helper.d.ts +10 -0
- package/dist/esm/index.js +106 -4
- package/dist/esm/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -5,5 +5,15 @@ export interface CorsConfig {
|
|
|
5
5
|
}
|
|
6
6
|
type CorsCallback = (err: Error | null, allow?: boolean) => void;
|
|
7
7
|
export declare const dynamicOriginCallbackHandler: (origin?: string | string[]) => ((requestOrigin: string | undefined, callback: CorsCallback) => void);
|
|
8
|
+
/**
|
|
9
|
+
* CORS middleware with Lambda streaming support.
|
|
10
|
+
*
|
|
11
|
+
* For OPTIONS preflight requests, this middleware handles them early and
|
|
12
|
+
* terminates the response immediately. This is critical for Lambda streaming
|
|
13
|
+
* handlers where the response stream would otherwise stay open waiting for
|
|
14
|
+
* streaming data that never comes.
|
|
15
|
+
*
|
|
16
|
+
* For regular requests, delegates to the standard cors package behavior.
|
|
17
|
+
*/
|
|
8
18
|
declare const _default: (config?: CorsConfig) => ((req: Request, res: Response, next: NextFunction) => void);
|
|
9
19
|
export default _default;
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -151,7 +151,8 @@ function buildQueryFromMultiValue(multiValueParams) {
|
|
|
151
151
|
const existingValues = result[key];
|
|
152
152
|
if (existingValues === undefined) {
|
|
153
153
|
// First occurrence - use array if multiple values or bracket notation
|
|
154
|
-
result[key] =
|
|
154
|
+
result[key] =
|
|
155
|
+
values.length === 1 && !rawKey.endsWith("[]") ? values[0] : values;
|
|
155
156
|
}
|
|
156
157
|
else if (Array.isArray(existingValues)) {
|
|
157
158
|
existingValues.push(...values);
|
|
@@ -1049,7 +1050,9 @@ function createLambdaHandler(app, _options) {
|
|
|
1049
1050
|
{
|
|
1050
1051
|
status: 500,
|
|
1051
1052
|
title: "Internal Server Error",
|
|
1052
|
-
detail: error instanceof Error
|
|
1053
|
+
detail: error instanceof Error
|
|
1054
|
+
? error.message
|
|
1055
|
+
: "Unknown error occurred",
|
|
1053
1056
|
},
|
|
1054
1057
|
],
|
|
1055
1058
|
}),
|
|
@@ -1136,6 +1139,33 @@ const ensureProtocol = (url) => {
|
|
|
1136
1139
|
return url;
|
|
1137
1140
|
return HTTPS_PROTOCOL + url;
|
|
1138
1141
|
};
|
|
1142
|
+
const extractHostname = (origin) => {
|
|
1143
|
+
try {
|
|
1144
|
+
const url = new URL(origin);
|
|
1145
|
+
return url.hostname;
|
|
1146
|
+
}
|
|
1147
|
+
catch {
|
|
1148
|
+
return undefined;
|
|
1149
|
+
}
|
|
1150
|
+
};
|
|
1151
|
+
const isOriginAllowed = (requestOrigin, allowed) => {
|
|
1152
|
+
const normalizedAllowed = ensureProtocol(allowed);
|
|
1153
|
+
const normalizedRequest = ensureProtocol(requestOrigin);
|
|
1154
|
+
const allowedHostname = extractHostname(normalizedAllowed);
|
|
1155
|
+
const requestHostname = extractHostname(normalizedRequest);
|
|
1156
|
+
if (!allowedHostname || !requestHostname) {
|
|
1157
|
+
return false;
|
|
1158
|
+
}
|
|
1159
|
+
// Exact match
|
|
1160
|
+
if (requestHostname === allowedHostname) {
|
|
1161
|
+
return true;
|
|
1162
|
+
}
|
|
1163
|
+
// Subdomain match
|
|
1164
|
+
if (requestHostname.endsWith(`.${allowedHostname}`)) {
|
|
1165
|
+
return true;
|
|
1166
|
+
}
|
|
1167
|
+
return false;
|
|
1168
|
+
};
|
|
1139
1169
|
const dynamicOriginCallbackHandler = (origin) => {
|
|
1140
1170
|
return (requestOrigin, callback) => {
|
|
1141
1171
|
// Handle wildcard origin
|
|
@@ -1169,7 +1199,7 @@ const dynamicOriginCallbackHandler = (origin) => {
|
|
|
1169
1199
|
if (allowed instanceof RegExp) {
|
|
1170
1200
|
return allowed.test(requestOrigin);
|
|
1171
1201
|
}
|
|
1172
|
-
return requestOrigin
|
|
1202
|
+
return isOriginAllowed(requestOrigin, allowed);
|
|
1173
1203
|
});
|
|
1174
1204
|
if (isAllowed) {
|
|
1175
1205
|
callback(null, true);
|
|
@@ -1192,9 +1222,81 @@ const corsHelper = (config = {}) => {
|
|
|
1192
1222
|
};
|
|
1193
1223
|
return expressCors(options);
|
|
1194
1224
|
};
|
|
1225
|
+
//
|
|
1226
|
+
//
|
|
1227
|
+
// Constants
|
|
1228
|
+
//
|
|
1229
|
+
const HTTP_CODE_NO_CONTENT = 204;
|
|
1230
|
+
const HTTP_METHOD_OPTIONS = "OPTIONS";
|
|
1231
|
+
/**
|
|
1232
|
+
* CORS middleware with Lambda streaming support.
|
|
1233
|
+
*
|
|
1234
|
+
* For OPTIONS preflight requests, this middleware handles them early and
|
|
1235
|
+
* terminates the response immediately. This is critical for Lambda streaming
|
|
1236
|
+
* handlers where the response stream would otherwise stay open waiting for
|
|
1237
|
+
* streaming data that never comes.
|
|
1238
|
+
*
|
|
1239
|
+
* For regular requests, delegates to the standard cors package behavior.
|
|
1240
|
+
*/
|
|
1195
1241
|
var cors_helper = (config) => {
|
|
1196
1242
|
const cors = corsHelper(config);
|
|
1243
|
+
const { origin, overrides = {} } = config || {};
|
|
1244
|
+
const originHandler = dynamicOriginCallbackHandler(origin);
|
|
1197
1245
|
return (req, res, next) => {
|
|
1246
|
+
// Handle OPTIONS preflight requests early for Lambda streaming compatibility.
|
|
1247
|
+
// The standard cors package would eventually call res.end(), but with Lambda
|
|
1248
|
+
// streaming, we need to ensure the response is terminated immediately without
|
|
1249
|
+
// going through any async middleware chains that might keep the stream open.
|
|
1250
|
+
if (req.method === HTTP_METHOD_OPTIONS) {
|
|
1251
|
+
const requestOrigin = req.headers.origin;
|
|
1252
|
+
originHandler(requestOrigin, (error, isAllowed) => {
|
|
1253
|
+
if (error || !isAllowed) {
|
|
1254
|
+
// Origin not allowed - send CORS error
|
|
1255
|
+
const corsError = error;
|
|
1256
|
+
if (corsError?.status && corsError?.body) {
|
|
1257
|
+
res.status(corsError.status);
|
|
1258
|
+
res.setHeader("Content-Type", "application/json");
|
|
1259
|
+
res.json(corsError.body());
|
|
1260
|
+
}
|
|
1261
|
+
else {
|
|
1262
|
+
// Fallback for non-CorsError errors
|
|
1263
|
+
res.status(HTTP_CODE_NO_CONTENT);
|
|
1264
|
+
res.end();
|
|
1265
|
+
}
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
// Origin is allowed - send preflight response
|
|
1269
|
+
// Set CORS headers
|
|
1270
|
+
if (requestOrigin) {
|
|
1271
|
+
res.setHeader("Access-Control-Allow-Origin", requestOrigin);
|
|
1272
|
+
}
|
|
1273
|
+
res.setHeader("Vary", "Origin");
|
|
1274
|
+
// Allow all methods by default (or use overrides if specified)
|
|
1275
|
+
const methods = overrides.methods || "GET,HEAD,PUT,PATCH,POST,DELETE";
|
|
1276
|
+
res.setHeader("Access-Control-Allow-Methods", methods);
|
|
1277
|
+
// Reflect requested headers (standard cors behavior)
|
|
1278
|
+
const requestedHeaders = req.headers["access-control-request-headers"];
|
|
1279
|
+
if (requestedHeaders) {
|
|
1280
|
+
res.setHeader("Access-Control-Allow-Headers", requestedHeaders);
|
|
1281
|
+
}
|
|
1282
|
+
// Handle credentials if configured
|
|
1283
|
+
if (overrides.credentials === true) {
|
|
1284
|
+
res.setHeader("Access-Control-Allow-Credentials", "true");
|
|
1285
|
+
}
|
|
1286
|
+
// Handle max age if configured
|
|
1287
|
+
if (overrides.maxAge !== undefined) {
|
|
1288
|
+
res.setHeader("Access-Control-Max-Age", String(overrides.maxAge));
|
|
1289
|
+
}
|
|
1290
|
+
// Send 204 No Content response and terminate immediately
|
|
1291
|
+
// This is critical for Lambda streaming - we must end the response
|
|
1292
|
+
// synchronously to prevent the stream from hanging.
|
|
1293
|
+
res.statusCode = HTTP_CODE_NO_CONTENT;
|
|
1294
|
+
res.setHeader("Content-Length", "0");
|
|
1295
|
+
res.end();
|
|
1296
|
+
});
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
// For non-OPTIONS requests, use the standard cors middleware
|
|
1198
1300
|
cors(req, res, (error) => {
|
|
1199
1301
|
if (error) {
|
|
1200
1302
|
const corsError = error;
|
|
@@ -1440,7 +1542,7 @@ const logger$1 = logger$2.log;
|
|
|
1440
1542
|
* Uses Symbol marker to survive prototype chain modifications from Express and dd-trace.
|
|
1441
1543
|
*/
|
|
1442
1544
|
function isLambdaMockResponse(res) {
|
|
1443
|
-
return res[JAYPIE_LAMBDA_MOCK] === true;
|
|
1545
|
+
return (res[JAYPIE_LAMBDA_MOCK] === true);
|
|
1444
1546
|
}
|
|
1445
1547
|
/**
|
|
1446
1548
|
* Safely send a JSON response, avoiding dd-trace interception.
|