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 +21 -6
- package/dist/index.cjs +211 -0
- package/dist/index.d.ts +22 -1
- package/dist/index.mjs +207 -1
- package/package.json +12 -10
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[](https://npmjs.com/package/h3)
|
|
2
2
|
[](https://npmjs.com/package/h3)
|
|
3
3
|
[](https://bundlephobia.com/result?p=h3)
|
|
4
|
-
[](https://github.com/unjs/h3/actions)
|
|
5
5
|
[](https://codecov.io/gh/unjs/h3)
|
|
6
6
|
[](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,
|
|
150
|
-
- `proxyRequest(event, { target,
|
|
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.
|
|
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.
|
|
26
|
+
"iron-webcrypto": "^0.5.0",
|
|
26
27
|
"radix3": "^1.0.0",
|
|
27
|
-
"ufo": "^1.0
|
|
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.
|
|
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.
|
|
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.
|
|
44
|
+
"jiti": "^1.17.0",
|
|
44
45
|
"listhen": "^1.0.2",
|
|
45
|
-
"node-fetch-native": "^1.0.
|
|
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.
|
|
51
|
+
"vitest": "^0.28.5"
|
|
51
52
|
},
|
|
52
|
-
"packageManager": "pnpm@7.
|
|
53
|
+
"packageManager": "pnpm@7.27.0",
|
|
53
54
|
"scripts": {
|
|
54
55
|
"build": "unbuild",
|
|
55
56
|
"dev": "vitest",
|
|
56
|
-
"lint": "eslint --ext ts
|
|
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",
|