h3 1.4.0 → 1.5.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/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  [![npm downloads](https://img.shields.io/npm/dm/h3.svg?style=flat-square)](https://npmjs.com/package/h3)
2
2
  [![version](https://img.shields.io/npm/v/h3/latest.svg?style=flat-square)](https://npmjs.com/package/h3)
3
3
  [![bundlephobia](https://img.shields.io/bundlephobia/min/h3/latest.svg?style=flat-square)](https://bundlephobia.com/result?p=h3)
4
- [![build status](https://img.shields.io/github/workflow/status/unjs/h3/ci/main?style=flat-square)](https://github.com/unjs/h3/actions)
4
+ [![build status](https://img.shields.io/github/actions/workflow/status/unjs/h3/ci.yml?branch=main&style=flat-square)](https://github.com/unjs/h3/actions)
5
5
  [![coverage](https://img.shields.io/codecov/c/gh/unjs/h3/main?style=flat-square)](https://codecov.io/gh/unjs/h3)
6
6
  [![jsDocs.io](https://img.shields.io/badge/jsDocs.io-reference-blue?style=flat-square)](https://www.jsdocs.io/package/h3)
7
7
 
@@ -110,6 +110,19 @@ app.use(eventHandler(() => '<h1>Hello world!</h1>'))
110
110
  app.use('/1', eventHandler(() => '<h1>Hello world!</h1>'))
111
111
  .use('/2', eventHandler(() => '<h1>Goodbye!</h1>'))
112
112
 
113
+ // We can proxy requests and rewrite cookie's domain and path
114
+ app.use('/api', eventHandler((event) => proxyRequest('https://example.com', {
115
+ // f.e. keep one domain unchanged, rewrite one domain and remove other domains
116
+ cookieDomainRewrite: {
117
+ "example.com": "example.com",
118
+ "example.com": "somecompany.co.uk",
119
+ "*": "",
120
+ },
121
+ cookiePathRewrite: {
122
+ "/": "/api"
123
+ },
124
+ }))
125
+
113
126
  // Legacy middleware with 3rd argument are automatically promisified
114
127
  app.use(fromNodeMiddleware((req, res, next) => { req.setHeader('x-foo', 'bar'); next() }))
115
128
 
@@ -146,8 +159,8 @@ H3 has a concept of composable utilities that accept `event` (from `eventHandler
146
159
  - `isMethod(event, expected, allowHead?)`
147
160
  - `assertMethod(event, expected, allowHead?)`
148
161
  - `createError({ statusCode, statusMessage, data? })`
149
- - `sendProxy(event, { target, headers?, fetchOptions?, fetch?, sendStream? })`
150
- - `proxyRequest(event, { target, headers?, fetchOptions?, fetch?, sendStream? })`
162
+ - `sendProxy(event, { target, ...options })`
163
+ - `proxyRequest(event, { target, ...options })`
151
164
  - `fetchWithEvent(event, req, init, { fetch? }?)`
152
165
  - `getProxyRequestHeaders(event)`
153
166
  - `sendNoContent(event, code = 204)`
@@ -161,6 +174,11 @@ H3 has a concept of composable utilities that accept `event` (from `eventHandler
161
174
  - `clearSession(event, config)`
162
175
  - `sealSession(event, config)`
163
176
  - `unsealSession(event, config, sealed)`
177
+ - `handleCors(options)` (see [h3-cors](https://github.com/NozomuIkuta/h3-cors) for more detail about options)
178
+ - `isPreflightRequest(event)`
179
+ - `isCorsOriginAllowed(event)`
180
+ - `appendCorsHeaders(event, options)` (see [h3-cors](https://github.com/NozomuIkuta/h3-cors) for more detail about options)
181
+ - `appendCorsPreflightHeaders(event, options)` (see [h3-cors](https://github.com/NozomuIkuta/h3-cors) for more detail about options)
164
182
 
165
183
  👉 You can learn more about usage in [JSDocs Documentation](https://www.jsdocs.io/package/h3#package-functions).
166
184
 
@@ -172,9 +190,6 @@ Please check their READMEs for more details.
172
190
 
173
191
  PRs are welcome to add your packages.
174
192
 
175
- - [h3-cors](https://github.com/NozomuIkuta/h3-cors)
176
- - `defineCorsEventHandler(options)`
177
- - `isPreflight(event)`
178
193
  - [h3-typebox](https://github.com/kevinmarrec/h3-typebox)
179
194
  - `validateBody(event, schema)`
180
195
  - `validateQuery(event, schema)`
package/dist/index.cjs CHANGED
@@ -6,6 +6,7 @@ const destr = require('destr');
6
6
  const cookieEs = require('cookie-es');
7
7
  const crypto = require('uncrypto');
8
8
  const ironWebcrypto = require('iron-webcrypto');
9
+ const defu = require('defu');
9
10
 
10
11
  function useBase(base, handler) {
11
12
  base = ufo.withoutTrailingSlash(base);
@@ -380,6 +381,59 @@ function deleteCookie(event, name, serializeOptions) {
380
381
  });
381
382
  }
382
383
 
384
+ function splitCookiesString(cookiesString) {
385
+ if (typeof cookiesString !== "string") {
386
+ return [];
387
+ }
388
+ const cookiesStrings = [];
389
+ let pos = 0;
390
+ let start;
391
+ let ch;
392
+ let lastComma;
393
+ let nextStart;
394
+ let cookiesSeparatorFound;
395
+ function skipWhitespace() {
396
+ while (pos < cookiesString.length && /\s/.test(cookiesString.charAt(pos))) {
397
+ pos += 1;
398
+ }
399
+ return pos < cookiesString.length;
400
+ }
401
+ function notSpecialChar() {
402
+ ch = cookiesString.charAt(pos);
403
+ return ch !== "=" && ch !== ";" && ch !== ",";
404
+ }
405
+ while (pos < cookiesString.length) {
406
+ start = pos;
407
+ cookiesSeparatorFound = false;
408
+ while (skipWhitespace()) {
409
+ ch = cookiesString.charAt(pos);
410
+ if (ch === ",") {
411
+ lastComma = pos;
412
+ pos += 1;
413
+ skipWhitespace();
414
+ nextStart = pos;
415
+ while (pos < cookiesString.length && notSpecialChar()) {
416
+ pos += 1;
417
+ }
418
+ if (pos < cookiesString.length && cookiesString.charAt(pos) === "=") {
419
+ cookiesSeparatorFound = true;
420
+ pos = nextStart;
421
+ cookiesStrings.push(cookiesString.slice(start, lastComma));
422
+ start = pos;
423
+ } else {
424
+ pos = lastComma + 1;
425
+ }
426
+ } else {
427
+ pos += 1;
428
+ }
429
+ }
430
+ if (!cookiesSeparatorFound || pos >= cookiesString.length) {
431
+ cookiesStrings.push(cookiesString.slice(start, cookiesString.length));
432
+ }
433
+ }
434
+ return cookiesStrings;
435
+ }
436
+
383
437
  const PayloadMethods = /* @__PURE__ */ new Set(["PATCH", "POST", "PUT", "DELETE"]);
384
438
  const ignoredHeaders = /* @__PURE__ */ new Set([
385
439
  "transfer-encoding",
@@ -426,6 +480,27 @@ async function sendProxy(event, target, opts = {}) {
426
480
  if (key === "content-length") {
427
481
  continue;
428
482
  }
483
+ if (key === "set-cookie") {
484
+ const cookies = splitCookiesString(value).map((cookie) => {
485
+ if (opts.cookieDomainRewrite) {
486
+ cookie = rewriteCookieProperty(
487
+ cookie,
488
+ opts.cookieDomainRewrite,
489
+ "domain"
490
+ );
491
+ }
492
+ if (opts.cookiePathRewrite) {
493
+ cookie = rewriteCookieProperty(
494
+ cookie,
495
+ opts.cookiePathRewrite,
496
+ "path"
497
+ );
498
+ }
499
+ return cookie;
500
+ });
501
+ event.node.res.setHeader("set-cookie", cookies);
502
+ continue;
503
+ }
429
504
  event.node.res.setHeader(key, value);
430
505
  }
431
506
  if (response._data !== void 0) {
@@ -472,6 +547,23 @@ function _getFetch(_fetch) {
472
547
  "fetch is not available. Try importing `node-fetch-native/polyfill` for Node.js."
473
548
  );
474
549
  }
550
+ function rewriteCookieProperty(header, map, property) {
551
+ const _map = typeof map === "string" ? { "*": map } : map;
552
+ return header.replace(
553
+ new RegExp(`(;\\s*${property}=)([^;]+)`, "gi"),
554
+ (match, prefix, previousValue) => {
555
+ let newValue;
556
+ if (previousValue in _map) {
557
+ newValue = _map[previousValue];
558
+ } else if ("*" in _map) {
559
+ newValue = _map["*"];
560
+ } else {
561
+ return match;
562
+ }
563
+ return newValue ? prefix + newValue : "";
564
+ }
565
+ );
566
+ }
475
567
 
476
568
  const defer = typeof setImmediate !== "undefined" ? setImmediate : (fn) => fn();
477
569
  function send(event, data, type) {
@@ -730,6 +822,120 @@ async function clearSession(event, config) {
730
822
  });
731
823
  }
732
824
 
825
+ function resolveCorsOptions(options = {}) {
826
+ const defaultOptions = {
827
+ origin: "*",
828
+ methods: "*",
829
+ allowHeaders: "*",
830
+ exposeHeaders: "*",
831
+ credentials: false,
832
+ maxAge: false,
833
+ preflight: {
834
+ statusCode: 204
835
+ }
836
+ };
837
+ return defu.defu(options, defaultOptions);
838
+ }
839
+ function isPreflightRequest(event) {
840
+ const method = getMethod(event);
841
+ const origin = getRequestHeader(event, "origin");
842
+ const accessControlRequestMethod = getRequestHeader(
843
+ event,
844
+ "access-control-request-method"
845
+ );
846
+ return method === "OPTIONS" && !!origin && !!accessControlRequestMethod;
847
+ }
848
+ function isCorsOriginAllowed(origin, options) {
849
+ const { origin: originOption } = options;
850
+ if (!origin || !originOption || originOption === "*" || originOption === "null") {
851
+ return true;
852
+ }
853
+ if (Array.isArray(originOption)) {
854
+ return originOption.some((_origin) => {
855
+ if (_origin instanceof RegExp) {
856
+ return _origin.test(origin);
857
+ }
858
+ return origin === _origin;
859
+ });
860
+ }
861
+ return originOption(origin);
862
+ }
863
+ function createOriginHeaders(event, options) {
864
+ const { origin: originOption } = options;
865
+ const origin = getRequestHeader(event, "origin");
866
+ if (!origin || !originOption || originOption === "*") {
867
+ return { "access-control-allow-origin": "*" };
868
+ }
869
+ if (typeof originOption === "string") {
870
+ return { "access-control-allow-origin": originOption, vary: "origin" };
871
+ }
872
+ return isCorsOriginAllowed(origin, options) ? { "access-control-allow-origin": origin, vary: "origin" } : {};
873
+ }
874
+ function createMethodsHeaders(options) {
875
+ const { methods } = options;
876
+ if (!methods) {
877
+ return {};
878
+ }
879
+ if (methods === "*") {
880
+ return { "access-control-allow-methods": "*" };
881
+ }
882
+ return methods.length > 0 ? { "access-control-allow-methods": methods.join(",") } : {};
883
+ }
884
+ function createCredentialsHeaders(options) {
885
+ const { credentials } = options;
886
+ if (credentials) {
887
+ return { "access-control-allow-credentials": "true" };
888
+ }
889
+ return {};
890
+ }
891
+ function createAllowHeaderHeaders(event, options) {
892
+ const { allowHeaders } = options;
893
+ if (!allowHeaders || allowHeaders === "*" || allowHeaders.length === 0) {
894
+ const header = getRequestHeader(event, "access-control-request-headers");
895
+ return header ? {
896
+ "access-control-allow-headers": header,
897
+ vary: "access-control-request-headers"
898
+ } : {};
899
+ }
900
+ return {
901
+ "access-control-allow-headers": allowHeaders.join(","),
902
+ vary: "access-control-request-headers"
903
+ };
904
+ }
905
+ function createExposeHeaders(options) {
906
+ const { exposeHeaders } = options;
907
+ if (!exposeHeaders) {
908
+ return {};
909
+ }
910
+ if (exposeHeaders === "*") {
911
+ return { "access-control-expose-headers": exposeHeaders };
912
+ }
913
+ return { "access-control-expose-headers": exposeHeaders.join(",") };
914
+ }
915
+ function appendCorsPreflightHeaders(event, options) {
916
+ appendHeaders(event, createOriginHeaders(event, options));
917
+ appendHeaders(event, createCredentialsHeaders(options));
918
+ appendHeaders(event, createExposeHeaders(options));
919
+ appendHeaders(event, createMethodsHeaders(options));
920
+ appendHeaders(event, createAllowHeaderHeaders(event, options));
921
+ }
922
+ function appendCorsHeaders(event, options) {
923
+ appendHeaders(event, createOriginHeaders(event, options));
924
+ appendHeaders(event, createCredentialsHeaders(options));
925
+ appendHeaders(event, createExposeHeaders(options));
926
+ }
927
+
928
+ function handleCors(event, options) {
929
+ const _options = resolveCorsOptions(options);
930
+ if (isPreflightRequest(event)) {
931
+ appendCorsPreflightHeaders(event, options);
932
+ sendNoContent(event, _options.preflight.statusCode);
933
+ return true;
934
+ }
935
+ appendCorsHeaders(event, options);
936
+ return false;
937
+ }
938
+
733
939
  class H3Headers {
734
940
  constructor(init) {
735
941
  if (!init) {
@@ -1184,6 +1390,8 @@ exports.H3Event = H3Event;
1184
1390
  exports.H3Headers = H3Headers;
1185
1391
  exports.H3Response = H3Response;
1186
1392
  exports.MIMES = MIMES;
1393
+ exports.appendCorsHeaders = appendCorsHeaders;
1394
+ exports.appendCorsPreflightHeaders = appendCorsPreflightHeaders;
1187
1395
  exports.appendHeader = appendHeader;
1188
1396
  exports.appendHeaders = appendHeaders;
1189
1397
  exports.appendResponseHeader = appendResponseHeader;
@@ -1222,10 +1430,13 @@ exports.getRouterParam = getRouterParam;
1222
1430
  exports.getRouterParams = getRouterParams;
1223
1431
  exports.getSession = getSession;
1224
1432
  exports.handleCacheHeaders = handleCacheHeaders;
1433
+ exports.handleCors = handleCors;
1434
+ exports.isCorsOriginAllowed = isCorsOriginAllowed;
1225
1435
  exports.isError = isError;
1226
1436
  exports.isEvent = isEvent;
1227
1437
  exports.isEventHandler = isEventHandler;
1228
1438
  exports.isMethod = isMethod;
1439
+ exports.isPreflightRequest = isPreflightRequest;
1229
1440
  exports.isStream = isStream;
1230
1441
  exports.lazyEventHandler = lazyEventHandler;
1231
1442
  exports.parseCookies = parseCookies;
package/dist/index.d.ts CHANGED
@@ -301,6 +301,8 @@ interface ProxyOptions {
301
301
  fetchOptions?: RequestInit;
302
302
  fetch?: typeof fetch;
303
303
  sendStream?: boolean;
304
+ cookieDomainRewrite?: string | Record<string, string>;
305
+ cookiePathRewrite?: string | Record<string, string>;
304
306
  }
305
307
  declare function proxyRequest(event: H3Event, target: string, opts?: ProxyOptions): Promise<any>;
306
308
  declare function sendProxy(event: H3Event, target: string, opts?: ProxyOptions): Promise<any>;
@@ -348,6 +350,25 @@ declare function isStream(data: any): any;
348
350
  declare function sendStream(event: H3Event, data: any): Promise<void>;
349
351
  declare function writeEarlyHints(event: H3Event, hints: string | string[] | Record<string, string | string[]>, cb?: () => void): void;
350
352
 
353
+ interface H3CorsOptions {
354
+ origin?: "*" | "null" | (string | RegExp)[] | ((origin: string) => boolean);
355
+ methods?: "*" | HTTPMethod[];
356
+ allowHeaders?: "*" | string[];
357
+ exposeHeaders?: "*" | string[];
358
+ credentials?: boolean;
359
+ maxAge?: string | false;
360
+ preflight?: {
361
+ statusCode?: number;
362
+ };
363
+ }
364
+
365
+ declare function handleCors(event: H3Event, options: H3CorsOptions): boolean;
366
+
367
+ declare function isPreflightRequest(event: H3Event): boolean;
368
+ declare function isCorsOriginAllowed(origin: ReturnType<typeof getRequestHeaders>["origin"], options: H3CorsOptions): boolean;
369
+ declare function appendCorsPreflightHeaders(event: H3Event, options: H3CorsOptions): void;
370
+ declare function appendCorsHeaders(event: H3Event, options: H3CorsOptions): void;
371
+
351
372
  type RouterMethod = Lowercase<HTTPMethod>;
352
373
  type RouterUse = (path: string, handler: EventHandler, method?: RouterMethod | RouterMethod[]) => Router;
353
374
  type AddRouteShortcuts = Record<RouterMethod, RouterUse>;
@@ -363,4 +384,4 @@ interface CreateRouterOptions {
363
384
  }
364
385
  declare function createRouter(opts?: CreateRouterOptions): Router;
365
386
 
366
- export { AddRouteShortcuts, App, AppOptions, AppUse, CacheConditions, CreateRouterOptions, DynamicEventHandler, Encoding, EventHandler, EventHandlerResponse, H3Error, H3Event, H3EventContext, H3Headers, H3Response, HTTPMethod, InputLayer, InputStack, Layer, LazyEventHandler, MIMES, Matcher, NodeEventContext, NodeListener, NodeMiddleware, NodePromisifiedHandler, ProxyOptions, RequestHeaders, Router, RouterMethod, RouterUse, Session, SessionConfig, SessionData, Stack, appendHeader, appendHeaders, appendResponseHeader, appendResponseHeaders, assertMethod, callNodeListener, clearSession, createApp, createAppEventHandler, createError, createEvent, createRouter, defaultContentType, defineEventHandler, defineLazyEventHandler, defineNodeListener, defineNodeMiddleware, deleteCookie, dynamicEventHandler, eventHandler, fetchWithEvent, fromNodeMiddleware, getCookie, getHeader, getHeaders, getMethod, getProxyRequestHeaders, getQuery, getRequestHeader, getRequestHeaders, getResponseHeader, getResponseHeaders, getResponseStatus, getResponseStatusText, getRouterParam, getRouterParams, getSession, handleCacheHeaders, isError, isEvent, isEventHandler, isMethod, isStream, lazyEventHandler, parseCookies, promisifyNodeListener, proxyRequest, readBody, readMultipartFormData, readRawBody, sealSession, send, sendError, sendNoContent, sendProxy, sendRedirect, sendStream, setCookie, setHeader, setHeaders, setResponseHeader, setResponseHeaders, setResponseStatus, toEventHandler, toNodeListener, unsealSession, updateSession, use, useBase, useSession, writeEarlyHints };
387
+ export { AddRouteShortcuts, App, AppOptions, AppUse, CacheConditions, CreateRouterOptions, DynamicEventHandler, Encoding, EventHandler, EventHandlerResponse, H3CorsOptions, H3Error, H3Event, H3EventContext, H3Headers, H3Response, HTTPMethod, InputLayer, InputStack, Layer, LazyEventHandler, MIMES, Matcher, MultiPartData, NodeEventContext, NodeListener, NodeMiddleware, NodePromisifiedHandler, ProxyOptions, RequestHeaders, Router, RouterMethod, RouterUse, Session, SessionConfig, SessionData, Stack, appendCorsHeaders, appendCorsPreflightHeaders, appendHeader, appendHeaders, appendResponseHeader, appendResponseHeaders, assertMethod, callNodeListener, clearSession, createApp, createAppEventHandler, createError, createEvent, createRouter, defaultContentType, defineEventHandler, defineLazyEventHandler, defineNodeListener, defineNodeMiddleware, deleteCookie, dynamicEventHandler, eventHandler, fetchWithEvent, fromNodeMiddleware, getCookie, getHeader, getHeaders, getMethod, getProxyRequestHeaders, getQuery, getRequestHeader, getRequestHeaders, getResponseHeader, getResponseHeaders, getResponseStatus, getResponseStatusText, getRouterParam, getRouterParams, getSession, handleCacheHeaders, handleCors, isCorsOriginAllowed, isError, isEvent, isEventHandler, isMethod, isPreflightRequest, isStream, lazyEventHandler, parseCookies, promisifyNodeListener, proxyRequest, readBody, readMultipartFormData, readRawBody, sealSession, send, sendError, sendNoContent, sendProxy, sendRedirect, sendStream, setCookie, setHeader, setHeaders, setResponseHeader, setResponseHeaders, setResponseStatus, toEventHandler, toNodeListener, unsealSession, updateSession, use, useBase, useSession, writeEarlyHints };
package/dist/index.mjs CHANGED
@@ -4,6 +4,7 @@ import destr from 'destr';
4
4
  import { parse as parse$1, serialize } from 'cookie-es';
5
5
  import crypto from 'uncrypto';
6
6
  import { seal, defaults, unseal } from 'iron-webcrypto';
7
+ import { defu } from 'defu';
7
8
 
8
9
  function useBase(base, handler) {
9
10
  base = withoutTrailingSlash(base);
@@ -378,6 +379,59 @@ function deleteCookie(event, name, serializeOptions) {
378
379
  });
379
380
  }
380
381
 
382
+ function splitCookiesString(cookiesString) {
383
+ if (typeof cookiesString !== "string") {
384
+ return [];
385
+ }
386
+ const cookiesStrings = [];
387
+ let pos = 0;
388
+ let start;
389
+ let ch;
390
+ let lastComma;
391
+ let nextStart;
392
+ let cookiesSeparatorFound;
393
+ function skipWhitespace() {
394
+ while (pos < cookiesString.length && /\s/.test(cookiesString.charAt(pos))) {
395
+ pos += 1;
396
+ }
397
+ return pos < cookiesString.length;
398
+ }
399
+ function notSpecialChar() {
400
+ ch = cookiesString.charAt(pos);
401
+ return ch !== "=" && ch !== ";" && ch !== ",";
402
+ }
403
+ while (pos < cookiesString.length) {
404
+ start = pos;
405
+ cookiesSeparatorFound = false;
406
+ while (skipWhitespace()) {
407
+ ch = cookiesString.charAt(pos);
408
+ if (ch === ",") {
409
+ lastComma = pos;
410
+ pos += 1;
411
+ skipWhitespace();
412
+ nextStart = pos;
413
+ while (pos < cookiesString.length && notSpecialChar()) {
414
+ pos += 1;
415
+ }
416
+ if (pos < cookiesString.length && cookiesString.charAt(pos) === "=") {
417
+ cookiesSeparatorFound = true;
418
+ pos = nextStart;
419
+ cookiesStrings.push(cookiesString.slice(start, lastComma));
420
+ start = pos;
421
+ } else {
422
+ pos = lastComma + 1;
423
+ }
424
+ } else {
425
+ pos += 1;
426
+ }
427
+ }
428
+ if (!cookiesSeparatorFound || pos >= cookiesString.length) {
429
+ cookiesStrings.push(cookiesString.slice(start, cookiesString.length));
430
+ }
431
+ }
432
+ return cookiesStrings;
433
+ }
434
+
381
435
  const PayloadMethods = /* @__PURE__ */ new Set(["PATCH", "POST", "PUT", "DELETE"]);
382
436
  const ignoredHeaders = /* @__PURE__ */ new Set([
383
437
  "transfer-encoding",
@@ -424,6 +478,27 @@ async function sendProxy(event, target, opts = {}) {
424
478
  if (key === "content-length") {
425
479
  continue;
426
480
  }
481
+ if (key === "set-cookie") {
482
+ const cookies = splitCookiesString(value).map((cookie) => {
483
+ if (opts.cookieDomainRewrite) {
484
+ cookie = rewriteCookieProperty(
485
+ cookie,
486
+ opts.cookieDomainRewrite,
487
+ "domain"
488
+ );
489
+ }
490
+ if (opts.cookiePathRewrite) {
491
+ cookie = rewriteCookieProperty(
492
+ cookie,
493
+ opts.cookiePathRewrite,
494
+ "path"
495
+ );
496
+ }
497
+ return cookie;
498
+ });
499
+ event.node.res.setHeader("set-cookie", cookies);
500
+ continue;
501
+ }
427
502
  event.node.res.setHeader(key, value);
428
503
  }
429
504
  if (response._data !== void 0) {
@@ -470,6 +545,23 @@ function _getFetch(_fetch) {
470
545
  "fetch is not available. Try importing `node-fetch-native/polyfill` for Node.js."
471
546
  );
472
547
  }
548
+ function rewriteCookieProperty(header, map, property) {
549
+ const _map = typeof map === "string" ? { "*": map } : map;
550
+ return header.replace(
551
+ new RegExp(`(;\\s*${property}=)([^;]+)`, "gi"),
552
+ (match, prefix, previousValue) => {
553
+ let newValue;
554
+ if (previousValue in _map) {
555
+ newValue = _map[previousValue];
556
+ } else if ("*" in _map) {
557
+ newValue = _map["*"];
558
+ } else {
559
+ return match;
560
+ }
561
+ return newValue ? prefix + newValue : "";
562
+ }
563
+ );
564
+ }
473
565
 
474
566
  const defer = typeof setImmediate !== "undefined" ? setImmediate : (fn) => fn();
475
567
  function send(event, data, type) {
@@ -728,6 +820,120 @@ async function clearSession(event, config) {
728
820
  });
729
821
  }
730
822
 
823
+ function resolveCorsOptions(options = {}) {
824
+ const defaultOptions = {
825
+ origin: "*",
826
+ methods: "*",
827
+ allowHeaders: "*",
828
+ exposeHeaders: "*",
829
+ credentials: false,
830
+ maxAge: false,
831
+ preflight: {
832
+ statusCode: 204
833
+ }
834
+ };
835
+ return defu(options, defaultOptions);
836
+ }
837
+ function isPreflightRequest(event) {
838
+ const method = getMethod(event);
839
+ const origin = getRequestHeader(event, "origin");
840
+ const accessControlRequestMethod = getRequestHeader(
841
+ event,
842
+ "access-control-request-method"
843
+ );
844
+ return method === "OPTIONS" && !!origin && !!accessControlRequestMethod;
845
+ }
846
+ function isCorsOriginAllowed(origin, options) {
847
+ const { origin: originOption } = options;
848
+ if (!origin || !originOption || originOption === "*" || originOption === "null") {
849
+ return true;
850
+ }
851
+ if (Array.isArray(originOption)) {
852
+ return originOption.some((_origin) => {
853
+ if (_origin instanceof RegExp) {
854
+ return _origin.test(origin);
855
+ }
856
+ return origin === _origin;
857
+ });
858
+ }
859
+ return originOption(origin);
860
+ }
861
+ function createOriginHeaders(event, options) {
862
+ const { origin: originOption } = options;
863
+ const origin = getRequestHeader(event, "origin");
864
+ if (!origin || !originOption || originOption === "*") {
865
+ return { "access-control-allow-origin": "*" };
866
+ }
867
+ if (typeof originOption === "string") {
868
+ return { "access-control-allow-origin": originOption, vary: "origin" };
869
+ }
870
+ return isCorsOriginAllowed(origin, options) ? { "access-control-allow-origin": origin, vary: "origin" } : {};
871
+ }
872
+ function createMethodsHeaders(options) {
873
+ const { methods } = options;
874
+ if (!methods) {
875
+ return {};
876
+ }
877
+ if (methods === "*") {
878
+ return { "access-control-allow-methods": "*" };
879
+ }
880
+ return methods.length > 0 ? { "access-control-allow-methods": methods.join(",") } : {};
881
+ }
882
+ function createCredentialsHeaders(options) {
883
+ const { credentials } = options;
884
+ if (credentials) {
885
+ return { "access-control-allow-credentials": "true" };
886
+ }
887
+ return {};
888
+ }
889
+ function createAllowHeaderHeaders(event, options) {
890
+ const { allowHeaders } = options;
891
+ if (!allowHeaders || allowHeaders === "*" || allowHeaders.length === 0) {
892
+ const header = getRequestHeader(event, "access-control-request-headers");
893
+ return header ? {
894
+ "access-control-allow-headers": header,
895
+ vary: "access-control-request-headers"
896
+ } : {};
897
+ }
898
+ return {
899
+ "access-control-allow-headers": allowHeaders.join(","),
900
+ vary: "access-control-request-headers"
901
+ };
902
+ }
903
+ function createExposeHeaders(options) {
904
+ const { exposeHeaders } = options;
905
+ if (!exposeHeaders) {
906
+ return {};
907
+ }
908
+ if (exposeHeaders === "*") {
909
+ return { "access-control-expose-headers": exposeHeaders };
910
+ }
911
+ return { "access-control-expose-headers": exposeHeaders.join(",") };
912
+ }
913
+ function appendCorsPreflightHeaders(event, options) {
914
+ appendHeaders(event, createOriginHeaders(event, options));
915
+ appendHeaders(event, createCredentialsHeaders(options));
916
+ appendHeaders(event, createExposeHeaders(options));
917
+ appendHeaders(event, createMethodsHeaders(options));
918
+ appendHeaders(event, createAllowHeaderHeaders(event, options));
919
+ }
920
+ function appendCorsHeaders(event, options) {
921
+ appendHeaders(event, createOriginHeaders(event, options));
922
+ appendHeaders(event, createCredentialsHeaders(options));
923
+ appendHeaders(event, createExposeHeaders(options));
924
+ }
925
+
926
+ function handleCors(event, options) {
927
+ const _options = resolveCorsOptions(options);
928
+ if (isPreflightRequest(event)) {
929
+ appendCorsPreflightHeaders(event, options);
930
+ sendNoContent(event, _options.preflight.statusCode);
931
+ return true;
932
+ }
933
+ appendCorsHeaders(event, options);
934
+ return false;
935
+ }
936
+
731
937
  class H3Headers {
732
938
  constructor(init) {
733
939
  if (!init) {
@@ -1177,4 +1383,4 @@ function createRouter(opts = {}) {
1177
1383
  return router;
1178
1384
  }
1179
1385
 
1180
- export { H3Error, H3Event, H3Headers, H3Response, MIMES, appendHeader, appendHeaders, appendResponseHeader, appendResponseHeaders, assertMethod, callNodeListener, clearSession, createApp, createAppEventHandler, createError, createEvent, createRouter, defaultContentType, defineEventHandler, defineLazyEventHandler, defineNodeListener, defineNodeMiddleware, deleteCookie, dynamicEventHandler, eventHandler, fetchWithEvent, fromNodeMiddleware, getCookie, getHeader, getHeaders, getMethod, getProxyRequestHeaders, getQuery, getRequestHeader, getRequestHeaders, getResponseHeader, getResponseHeaders, getResponseStatus, getResponseStatusText, getRouterParam, getRouterParams, getSession, handleCacheHeaders, isError, isEvent, isEventHandler, isMethod, isStream, lazyEventHandler, parseCookies, promisifyNodeListener, proxyRequest, readBody, readMultipartFormData, readRawBody, sealSession, send, sendError, sendNoContent, sendProxy, sendRedirect, sendStream, setCookie, setHeader, setHeaders, setResponseHeader, setResponseHeaders, setResponseStatus, toEventHandler, toNodeListener, unsealSession, updateSession, use, useBase, useSession, writeEarlyHints };
1386
+ export { H3Error, H3Event, H3Headers, H3Response, MIMES, appendCorsHeaders, appendCorsPreflightHeaders, appendHeader, appendHeaders, appendResponseHeader, appendResponseHeaders, assertMethod, callNodeListener, clearSession, createApp, createAppEventHandler, createError, createEvent, createRouter, defaultContentType, defineEventHandler, defineLazyEventHandler, defineNodeListener, defineNodeMiddleware, deleteCookie, dynamicEventHandler, eventHandler, fetchWithEvent, fromNodeMiddleware, getCookie, getHeader, getHeaders, getMethod, getProxyRequestHeaders, getQuery, getRequestHeader, getRequestHeaders, getResponseHeader, getResponseHeaders, getResponseStatus, getResponseStatusText, getRouterParam, getRouterParams, getSession, handleCacheHeaders, handleCors, isCorsOriginAllowed, isError, isEvent, isEventHandler, isMethod, isPreflightRequest, isStream, lazyEventHandler, parseCookies, promisifyNodeListener, proxyRequest, readBody, readMultipartFormData, readRawBody, sealSession, send, sendError, sendNoContent, sendProxy, sendRedirect, sendStream, setCookie, setHeader, setHeaders, setResponseHeader, setResponseHeaders, setResponseStatus, toEventHandler, toNodeListener, unsealSession, updateSession, use, useBase, useSession, writeEarlyHints };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "h3",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Tiny JavaScript Server",
5
5
  "repository": "unjs/h3",
6
6
  "license": "MIT",
@@ -21,10 +21,11 @@
21
21
  ],
22
22
  "dependencies": {
23
23
  "cookie-es": "^0.5.0",
24
+ "defu": "^6.1.2",
24
25
  "destr": "^1.2.2",
25
- "iron-webcrypto": "^0.4.0",
26
+ "iron-webcrypto": "^0.5.0",
26
27
  "radix3": "^1.0.0",
27
- "ufo": "^1.0.1",
28
+ "ufo": "^1.1.0",
28
29
  "uncrypto": "^0.1.2"
29
30
  },
30
31
  "devDependencies": {
@@ -32,28 +33,29 @@
32
33
  "@types/express": "^4.17.17",
33
34
  "@types/node": "^18.13.0",
34
35
  "@types/supertest": "^2.0.12",
35
- "@vitest/coverage-c8": "^0.28.4",
36
+ "@vitest/coverage-c8": "^0.28.5",
36
37
  "autocannon": "^7.10.0",
37
38
  "changelogen": "^0.4.1",
38
39
  "connect": "^3.7.0",
39
- "eslint": "^8.33.0",
40
+ "eslint": "^8.34.0",
40
41
  "eslint-config-unjs": "^0.1.0",
41
42
  "express": "^4.18.2",
42
43
  "get-port": "^6.1.2",
43
- "jiti": "^1.16.2",
44
+ "jiti": "^1.17.0",
44
45
  "listhen": "^1.0.2",
45
- "node-fetch-native": "^1.0.1",
46
+ "node-fetch-native": "^1.0.2",
46
47
  "prettier": "^2.8.4",
47
48
  "supertest": "^6.3.3",
48
49
  "typescript": "^4.9.5",
49
50
  "unbuild": "^1.1.1",
50
- "vitest": "^0.28.4"
51
+ "vitest": "^0.28.5"
51
52
  },
52
- "packageManager": "pnpm@7.26.3",
53
+ "packageManager": "pnpm@7.27.0",
53
54
  "scripts": {
54
55
  "build": "unbuild",
55
56
  "dev": "vitest",
56
- "lint": "eslint --ext ts,mjs,cjs . && prettier -c src test playground",
57
+ "lint": "eslint --cache --ext .ts,.js,.mjs,.cjs . && prettier -c src test playground",
58
+ "lint:fix": "eslint --cache --ext .ts,.js,.mjs,.cjs . --fix && prettier -c src test playground -w",
57
59
  "play": "jiti ./playground/index.ts",
58
60
  "profile": "0x -o -D .profile -P 'autocannon -c 100 -p 10 -d 40 http://localhost:$PORT' ./playground/server.cjs",
59
61
  "release": "pnpm test && pnpm build && changelogen --release && pnpm publish && git push --follow-tags",