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