@solidjs/web 2.0.0-rc.1 → 2.0.0-rc.2
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/dev.cjs +31 -6
- package/dist/dev.js +30 -7
- package/dist/server.cjs +353 -93
- package/dist/server.js +346 -97
- package/dist/web.cjs +31 -6
- package/dist/web.js +30 -7
- package/frames/dist/client.cjs +93 -7
- package/frames/dist/client.dev.cjs +96 -7
- package/frames/dist/client.dev.js +97 -8
- package/frames/dist/client.js +94 -8
- package/frames/dist/server.cjs +259 -138
- package/frames/dist/server.js +260 -139
- package/package.json +3 -3
- package/serialization/dist/decode.cjs +19 -2
- package/serialization/dist/decode.js +20 -3
- package/serialization/dist/serialization.cjs +19 -2
- package/serialization/dist/serialization.js +20 -3
- package/server-functions/dist/client.cjs +48 -7
- package/server-functions/dist/client.js +48 -8
- package/server-functions/dist/server.cjs +76 -5
- package/server-functions/dist/server.dev.cjs +76 -5
- package/server-functions/dist/server.dev.js +76 -6
- package/server-functions/dist/server.js +76 -6
- package/types/core.d.ts +3 -0
- package/types/frames/frame-client.d.ts +18 -0
- package/types/server-functions/client.d.ts +33 -2
- package/types/server-functions/server.d.ts +67 -2
- package/types/server-mock.d.ts +11 -2
- package/types/server.d.ts +23 -2
- package/types-cjs/core.d.cts +3 -0
- package/types-cjs/frames/frame-client.d.cts +18 -0
- package/types-cjs/server-functions/client.d.cts +33 -2
- package/types-cjs/server-functions/server.d.cts +67 -2
- package/types-cjs/server-mock.d.cts +11 -2
- package/types-cjs/server.d.cts +23 -2
|
@@ -362,6 +362,7 @@ async function decodeResponsePayload(response, codecOptions) {
|
|
|
362
362
|
};
|
|
363
363
|
}
|
|
364
364
|
|
|
365
|
+
typeof setImmediate === "function" ? setImmediate : fn => setTimeout(fn, 0);
|
|
365
366
|
const RequestContext = Symbol.for("solid.RequestContext");
|
|
366
367
|
function getRequestEvent() {
|
|
367
368
|
return globalThis[RequestContext] ? globalThis[RequestContext].getStore() || solidJs.sharedConfig.context && solidJs.sharedConfig.context.event || console.warn("RequestEvent is missing. This is most likely due to accessing `getRequestEvent` non-managed async scope in a partially polyfilled environment. Try moving it above all `await` calls.") : undefined;
|
|
@@ -493,7 +494,8 @@ const config = {
|
|
|
493
494
|
transformFlightResult: undefined,
|
|
494
495
|
transformDirectResult: undefined,
|
|
495
496
|
handleNoJS: undefined,
|
|
496
|
-
endpoint: "/_server"
|
|
497
|
+
endpoint: "/_server",
|
|
498
|
+
csrf: true
|
|
497
499
|
};
|
|
498
500
|
function configureServerFunctionsServer({
|
|
499
501
|
provideEvent,
|
|
@@ -504,6 +506,7 @@ function configureServerFunctionsServer({
|
|
|
504
506
|
transformDirectResult,
|
|
505
507
|
handleNoJS,
|
|
506
508
|
endpoint,
|
|
509
|
+
csrf,
|
|
507
510
|
codec
|
|
508
511
|
} = {}) {
|
|
509
512
|
if (provideEvent !== undefined) config.provideEvent = provideEvent;
|
|
@@ -514,6 +517,7 @@ function configureServerFunctionsServer({
|
|
|
514
517
|
if (transformDirectResult !== undefined) config.transformDirectResult = transformDirectResult;
|
|
515
518
|
if (handleNoJS !== undefined) config.handleNoJS = handleNoJS;
|
|
516
519
|
if (endpoint !== undefined) config.endpoint = endpoint;
|
|
520
|
+
if (csrf !== undefined) config.csrf = csrf;
|
|
517
521
|
if (codec !== undefined) configureServerFunctionsCodec(codec);
|
|
518
522
|
}
|
|
519
523
|
function provideEvent(event, fn) {
|
|
@@ -950,31 +954,96 @@ function sanitizeServerError(value) {
|
|
|
950
954
|
if (isSafeError(value)) return value;
|
|
951
955
|
return new Error(GENERIC_SERVER_ERROR_MESSAGE);
|
|
952
956
|
}
|
|
957
|
+
function observeServerFunctionCalls() {
|
|
958
|
+
return () => {};
|
|
959
|
+
}
|
|
960
|
+
async function matchesOrigin(origin, request, matcher) {
|
|
961
|
+
if (matcher === undefined) return origin === new URL(request.url).origin;
|
|
962
|
+
if (typeof matcher === "function") return !!(await matcher(origin, request));
|
|
963
|
+
return Array.isArray(matcher) ? matcher.includes(origin) : origin === matcher;
|
|
964
|
+
}
|
|
965
|
+
async function allowsServerFunctionRequest(request, options) {
|
|
966
|
+
const fetchSite = request.headers.get("Sec-Fetch-Site");
|
|
967
|
+
if (fetchSite === "same-origin") return true;
|
|
968
|
+
if (fetchSite === "same-site" || fetchSite === "cross-site" || fetchSite === "none") {
|
|
969
|
+
return false;
|
|
970
|
+
}
|
|
971
|
+
const origin = request.headers.get("Origin");
|
|
972
|
+
if (origin !== null) return matchesOrigin(origin, request, options.origin);
|
|
973
|
+
const referer = request.headers.get("Referer");
|
|
974
|
+
if (referer !== null) {
|
|
975
|
+
try {
|
|
976
|
+
return matchesOrigin(new URL(referer).origin, request, options.origin);
|
|
977
|
+
} catch {
|
|
978
|
+
return false;
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
return options.allowRequestsWithoutOriginCheck === true;
|
|
982
|
+
}
|
|
983
|
+
const CSRF_VARY = ["Sec-Fetch-Site", "Origin", "Referer"];
|
|
984
|
+
function withCSRFVary(response) {
|
|
985
|
+
const current = response.headers.get("Vary");
|
|
986
|
+
if (current === "*") return response;
|
|
987
|
+
const values = current ? current.split(",").map(value => value.trim()) : [];
|
|
988
|
+
const names = new Set(values.map(value => value.toLowerCase()));
|
|
989
|
+
for (const value of CSRF_VARY) {
|
|
990
|
+
if (!names.has(value.toLowerCase())) values.push(value);
|
|
991
|
+
}
|
|
992
|
+
const vary = values.join(", ");
|
|
993
|
+
try {
|
|
994
|
+
response.headers.set("Vary", vary);
|
|
995
|
+
return response;
|
|
996
|
+
} catch {
|
|
997
|
+
const headers = new Headers(response.headers);
|
|
998
|
+
headers.set("Vary", vary);
|
|
999
|
+
return new Response(response.body, {
|
|
1000
|
+
status: response.status,
|
|
1001
|
+
statusText: response.statusText,
|
|
1002
|
+
headers
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
function forbiddenResponse() {
|
|
1007
|
+
return withCSRFVary(new Response(DEV ? "Forbidden" : null, {
|
|
1008
|
+
status: 403,
|
|
1009
|
+
headers: {
|
|
1010
|
+
"Cache-Control": "no-store"
|
|
1011
|
+
}
|
|
1012
|
+
}));
|
|
1013
|
+
}
|
|
953
1014
|
async function handleServerFunctionRequest(request, options = {}) {
|
|
954
1015
|
const codec = options.codec !== undefined ? options.codec : getServerFunctionsCodec();
|
|
955
1016
|
const url = new URL(request.url);
|
|
1017
|
+
const csrf = options.csrf !== undefined ? options.csrf : config.csrf;
|
|
1018
|
+
const protectsRequest = csrf !== false;
|
|
1019
|
+
if (protectsRequest && !(await allowsServerFunctionRequest(request, csrf === true ? {} : csrf))) {
|
|
1020
|
+
return forbiddenResponse();
|
|
1021
|
+
}
|
|
956
1022
|
const instance = request.headers.get(INSTANCE_HEADER);
|
|
957
1023
|
const functionId = resolveFunctionId(request, url);
|
|
958
1024
|
if (!functionId) {
|
|
959
|
-
|
|
1025
|
+
const response = new Response(DEV ? "Server function not found" : null, {
|
|
960
1026
|
status: 404
|
|
961
1027
|
});
|
|
1028
|
+
return protectsRequest ? withCSRFVary(response) : response;
|
|
962
1029
|
}
|
|
963
1030
|
let serverFunction;
|
|
964
1031
|
try {
|
|
965
1032
|
serverFunction = getServerFunction(functionId);
|
|
966
1033
|
} catch {
|
|
967
|
-
|
|
1034
|
+
const response = new Response(DEV ? `Unknown server function: ${functionId}` : null, {
|
|
968
1035
|
status: 404
|
|
969
1036
|
});
|
|
1037
|
+
return protectsRequest ? withCSRFVary(response) : response;
|
|
970
1038
|
}
|
|
971
1039
|
if (request.method === "GET" && METHODS.get(functionId) !== "GET") {
|
|
972
|
-
|
|
1040
|
+
const response = new Response(DEV ? `Method not allowed for server function: ${functionId}` : null, {
|
|
973
1041
|
status: 405,
|
|
974
1042
|
headers: {
|
|
975
1043
|
Allow: "POST"
|
|
976
1044
|
}
|
|
977
1045
|
});
|
|
1046
|
+
return protectsRequest ? withCSRFVary(response) : response;
|
|
978
1047
|
}
|
|
979
1048
|
const event = options.createEvent ? options.createEvent(request) : {
|
|
980
1049
|
request,
|
|
@@ -1133,7 +1202,8 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1133
1202
|
return encodeResult(safe, headers, 200, codec, request.signal);
|
|
1134
1203
|
}
|
|
1135
1204
|
};
|
|
1136
|
-
|
|
1205
|
+
const response = commitEventResponse(await dispatch(), event);
|
|
1206
|
+
return protectsRequest ? withCSRFVary(response) : response;
|
|
1137
1207
|
}
|
|
1138
1208
|
|
|
1139
1209
|
exports.ERROR_HEADER = ERROR_HEADER;
|
|
@@ -1162,6 +1232,7 @@ exports.handleServerFunctionRequest = handleServerFunctionRequest;
|
|
|
1162
1232
|
exports.hasFlashCookie = hasFlashCookie;
|
|
1163
1233
|
exports.isServerFunction = isServerFunction;
|
|
1164
1234
|
exports.live = live;
|
|
1235
|
+
exports.observeServerFunctionCalls = observeServerFunctionCalls;
|
|
1165
1236
|
exports.registerServerFunction = registerServerFunction;
|
|
1166
1237
|
exports.registerServerReference = registerServerReference;
|
|
1167
1238
|
exports.sanitizeServerError = sanitizeServerError;
|
|
@@ -360,6 +360,7 @@ async function decodeResponsePayload(response, codecOptions) {
|
|
|
360
360
|
};
|
|
361
361
|
}
|
|
362
362
|
|
|
363
|
+
typeof setImmediate === "function" ? setImmediate : fn => setTimeout(fn, 0);
|
|
363
364
|
const RequestContext = Symbol.for("solid.RequestContext");
|
|
364
365
|
function getRequestEvent() {
|
|
365
366
|
return globalThis[RequestContext] ? globalThis[RequestContext].getStore() || sharedConfig.context && sharedConfig.context.event || console.warn("RequestEvent is missing. This is most likely due to accessing `getRequestEvent` non-managed async scope in a partially polyfilled environment. Try moving it above all `await` calls.") : undefined;
|
|
@@ -491,7 +492,8 @@ const config = {
|
|
|
491
492
|
transformFlightResult: undefined,
|
|
492
493
|
transformDirectResult: undefined,
|
|
493
494
|
handleNoJS: undefined,
|
|
494
|
-
endpoint: "/_server"
|
|
495
|
+
endpoint: "/_server",
|
|
496
|
+
csrf: true
|
|
495
497
|
};
|
|
496
498
|
function configureServerFunctionsServer({
|
|
497
499
|
provideEvent,
|
|
@@ -502,6 +504,7 @@ function configureServerFunctionsServer({
|
|
|
502
504
|
transformDirectResult,
|
|
503
505
|
handleNoJS,
|
|
504
506
|
endpoint,
|
|
507
|
+
csrf,
|
|
505
508
|
codec
|
|
506
509
|
} = {}) {
|
|
507
510
|
if (provideEvent !== undefined) config.provideEvent = provideEvent;
|
|
@@ -512,6 +515,7 @@ function configureServerFunctionsServer({
|
|
|
512
515
|
if (transformDirectResult !== undefined) config.transformDirectResult = transformDirectResult;
|
|
513
516
|
if (handleNoJS !== undefined) config.handleNoJS = handleNoJS;
|
|
514
517
|
if (endpoint !== undefined) config.endpoint = endpoint;
|
|
518
|
+
if (csrf !== undefined) config.csrf = csrf;
|
|
515
519
|
if (codec !== undefined) configureServerFunctionsCodec(codec);
|
|
516
520
|
}
|
|
517
521
|
function provideEvent(event, fn) {
|
|
@@ -948,31 +952,96 @@ function sanitizeServerError(value) {
|
|
|
948
952
|
if (isSafeError(value)) return value;
|
|
949
953
|
return new Error(GENERIC_SERVER_ERROR_MESSAGE);
|
|
950
954
|
}
|
|
955
|
+
function observeServerFunctionCalls() {
|
|
956
|
+
return () => {};
|
|
957
|
+
}
|
|
958
|
+
async function matchesOrigin(origin, request, matcher) {
|
|
959
|
+
if (matcher === undefined) return origin === new URL(request.url).origin;
|
|
960
|
+
if (typeof matcher === "function") return !!(await matcher(origin, request));
|
|
961
|
+
return Array.isArray(matcher) ? matcher.includes(origin) : origin === matcher;
|
|
962
|
+
}
|
|
963
|
+
async function allowsServerFunctionRequest(request, options) {
|
|
964
|
+
const fetchSite = request.headers.get("Sec-Fetch-Site");
|
|
965
|
+
if (fetchSite === "same-origin") return true;
|
|
966
|
+
if (fetchSite === "same-site" || fetchSite === "cross-site" || fetchSite === "none") {
|
|
967
|
+
return false;
|
|
968
|
+
}
|
|
969
|
+
const origin = request.headers.get("Origin");
|
|
970
|
+
if (origin !== null) return matchesOrigin(origin, request, options.origin);
|
|
971
|
+
const referer = request.headers.get("Referer");
|
|
972
|
+
if (referer !== null) {
|
|
973
|
+
try {
|
|
974
|
+
return matchesOrigin(new URL(referer).origin, request, options.origin);
|
|
975
|
+
} catch {
|
|
976
|
+
return false;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
return options.allowRequestsWithoutOriginCheck === true;
|
|
980
|
+
}
|
|
981
|
+
const CSRF_VARY = ["Sec-Fetch-Site", "Origin", "Referer"];
|
|
982
|
+
function withCSRFVary(response) {
|
|
983
|
+
const current = response.headers.get("Vary");
|
|
984
|
+
if (current === "*") return response;
|
|
985
|
+
const values = current ? current.split(",").map(value => value.trim()) : [];
|
|
986
|
+
const names = new Set(values.map(value => value.toLowerCase()));
|
|
987
|
+
for (const value of CSRF_VARY) {
|
|
988
|
+
if (!names.has(value.toLowerCase())) values.push(value);
|
|
989
|
+
}
|
|
990
|
+
const vary = values.join(", ");
|
|
991
|
+
try {
|
|
992
|
+
response.headers.set("Vary", vary);
|
|
993
|
+
return response;
|
|
994
|
+
} catch {
|
|
995
|
+
const headers = new Headers(response.headers);
|
|
996
|
+
headers.set("Vary", vary);
|
|
997
|
+
return new Response(response.body, {
|
|
998
|
+
status: response.status,
|
|
999
|
+
statusText: response.statusText,
|
|
1000
|
+
headers
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
function forbiddenResponse() {
|
|
1005
|
+
return withCSRFVary(new Response(DEV ? "Forbidden" : null, {
|
|
1006
|
+
status: 403,
|
|
1007
|
+
headers: {
|
|
1008
|
+
"Cache-Control": "no-store"
|
|
1009
|
+
}
|
|
1010
|
+
}));
|
|
1011
|
+
}
|
|
951
1012
|
async function handleServerFunctionRequest(request, options = {}) {
|
|
952
1013
|
const codec = options.codec !== undefined ? options.codec : getServerFunctionsCodec();
|
|
953
1014
|
const url = new URL(request.url);
|
|
1015
|
+
const csrf = options.csrf !== undefined ? options.csrf : config.csrf;
|
|
1016
|
+
const protectsRequest = csrf !== false;
|
|
1017
|
+
if (protectsRequest && !(await allowsServerFunctionRequest(request, csrf === true ? {} : csrf))) {
|
|
1018
|
+
return forbiddenResponse();
|
|
1019
|
+
}
|
|
954
1020
|
const instance = request.headers.get(INSTANCE_HEADER);
|
|
955
1021
|
const functionId = resolveFunctionId(request, url);
|
|
956
1022
|
if (!functionId) {
|
|
957
|
-
|
|
1023
|
+
const response = new Response(DEV ? "Server function not found" : null, {
|
|
958
1024
|
status: 404
|
|
959
1025
|
});
|
|
1026
|
+
return protectsRequest ? withCSRFVary(response) : response;
|
|
960
1027
|
}
|
|
961
1028
|
let serverFunction;
|
|
962
1029
|
try {
|
|
963
1030
|
serverFunction = getServerFunction(functionId);
|
|
964
1031
|
} catch {
|
|
965
|
-
|
|
1032
|
+
const response = new Response(DEV ? `Unknown server function: ${functionId}` : null, {
|
|
966
1033
|
status: 404
|
|
967
1034
|
});
|
|
1035
|
+
return protectsRequest ? withCSRFVary(response) : response;
|
|
968
1036
|
}
|
|
969
1037
|
if (request.method === "GET" && METHODS.get(functionId) !== "GET") {
|
|
970
|
-
|
|
1038
|
+
const response = new Response(DEV ? `Method not allowed for server function: ${functionId}` : null, {
|
|
971
1039
|
status: 405,
|
|
972
1040
|
headers: {
|
|
973
1041
|
Allow: "POST"
|
|
974
1042
|
}
|
|
975
1043
|
});
|
|
1044
|
+
return protectsRequest ? withCSRFVary(response) : response;
|
|
976
1045
|
}
|
|
977
1046
|
const event = options.createEvent ? options.createEvent(request) : {
|
|
978
1047
|
request,
|
|
@@ -1131,7 +1200,8 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1131
1200
|
return encodeResult(safe, headers, 200, codec, request.signal);
|
|
1132
1201
|
}
|
|
1133
1202
|
};
|
|
1134
|
-
|
|
1203
|
+
const response = commitEventResponse(await dispatch(), event);
|
|
1204
|
+
return protectsRequest ? withCSRFVary(response) : response;
|
|
1135
1205
|
}
|
|
1136
1206
|
|
|
1137
|
-
export { ERROR_HEADER, FLASH_COOKIE, FUNCTION_HEADER, GENERIC_SERVER_ERROR_MESSAGE, GET, INSTANCE_HEADER, SINGLE_FLIGHT_HEADER, clearFlashCookie, configureServerFunctionsServer, createNoJSHandler, createServerReference, decodeErrorHeaderValue, decodeFlashCookie, decodeResponse, decodeResponsePayload, encodeErrorHeaderValue, encodeFlashCookie, foldSetCookies, getEventServerFunctionInvocation, getServerFunction, getServerFunctionInvocation, getServerFunctionMetadata, handleServerFunctionRequest, hasFlashCookie, isServerFunction, live, registerServerFunction, registerServerReference, sanitizeServerError, serializeResponseStream, setServerFunctionsDev, subscribeFlightData, withMeta };
|
|
1207
|
+
export { ERROR_HEADER, FLASH_COOKIE, FUNCTION_HEADER, GENERIC_SERVER_ERROR_MESSAGE, GET, INSTANCE_HEADER, SINGLE_FLIGHT_HEADER, clearFlashCookie, configureServerFunctionsServer, createNoJSHandler, createServerReference, decodeErrorHeaderValue, decodeFlashCookie, decodeResponse, decodeResponsePayload, encodeErrorHeaderValue, encodeFlashCookie, foldSetCookies, getEventServerFunctionInvocation, getServerFunction, getServerFunctionInvocation, getServerFunctionMetadata, handleServerFunctionRequest, hasFlashCookie, isServerFunction, live, observeServerFunctionCalls, registerServerFunction, registerServerReference, sanitizeServerError, serializeResponseStream, setServerFunctionsDev, subscribeFlightData, withMeta };
|
|
@@ -360,6 +360,7 @@ async function decodeResponsePayload(response, codecOptions) {
|
|
|
360
360
|
};
|
|
361
361
|
}
|
|
362
362
|
|
|
363
|
+
typeof setImmediate === "function" ? setImmediate : fn => setTimeout(fn, 0);
|
|
363
364
|
const RequestContext = Symbol.for("solid.RequestContext");
|
|
364
365
|
function getRequestEvent() {
|
|
365
366
|
return globalThis[RequestContext] ? globalThis[RequestContext].getStore() || sharedConfig.context && sharedConfig.context.event || console.warn("RequestEvent is missing. This is most likely due to accessing `getRequestEvent` non-managed async scope in a partially polyfilled environment. Try moving it above all `await` calls.") : undefined;
|
|
@@ -491,7 +492,8 @@ const config = {
|
|
|
491
492
|
transformFlightResult: undefined,
|
|
492
493
|
transformDirectResult: undefined,
|
|
493
494
|
handleNoJS: undefined,
|
|
494
|
-
endpoint: "/_server"
|
|
495
|
+
endpoint: "/_server",
|
|
496
|
+
csrf: true
|
|
495
497
|
};
|
|
496
498
|
function configureServerFunctionsServer({
|
|
497
499
|
provideEvent,
|
|
@@ -502,6 +504,7 @@ function configureServerFunctionsServer({
|
|
|
502
504
|
transformDirectResult,
|
|
503
505
|
handleNoJS,
|
|
504
506
|
endpoint,
|
|
507
|
+
csrf,
|
|
505
508
|
codec
|
|
506
509
|
} = {}) {
|
|
507
510
|
if (provideEvent !== undefined) config.provideEvent = provideEvent;
|
|
@@ -512,6 +515,7 @@ function configureServerFunctionsServer({
|
|
|
512
515
|
if (transformDirectResult !== undefined) config.transformDirectResult = transformDirectResult;
|
|
513
516
|
if (handleNoJS !== undefined) config.handleNoJS = handleNoJS;
|
|
514
517
|
if (endpoint !== undefined) config.endpoint = endpoint;
|
|
518
|
+
if (csrf !== undefined) config.csrf = csrf;
|
|
515
519
|
if (codec !== undefined) configureServerFunctionsCodec(codec);
|
|
516
520
|
}
|
|
517
521
|
function provideEvent(event, fn) {
|
|
@@ -948,31 +952,96 @@ function sanitizeServerError(value) {
|
|
|
948
952
|
if (isSafeError(value)) return value;
|
|
949
953
|
return new Error(GENERIC_SERVER_ERROR_MESSAGE);
|
|
950
954
|
}
|
|
955
|
+
function observeServerFunctionCalls() {
|
|
956
|
+
return () => {};
|
|
957
|
+
}
|
|
958
|
+
async function matchesOrigin(origin, request, matcher) {
|
|
959
|
+
if (matcher === undefined) return origin === new URL(request.url).origin;
|
|
960
|
+
if (typeof matcher === "function") return !!(await matcher(origin, request));
|
|
961
|
+
return Array.isArray(matcher) ? matcher.includes(origin) : origin === matcher;
|
|
962
|
+
}
|
|
963
|
+
async function allowsServerFunctionRequest(request, options) {
|
|
964
|
+
const fetchSite = request.headers.get("Sec-Fetch-Site");
|
|
965
|
+
if (fetchSite === "same-origin") return true;
|
|
966
|
+
if (fetchSite === "same-site" || fetchSite === "cross-site" || fetchSite === "none") {
|
|
967
|
+
return false;
|
|
968
|
+
}
|
|
969
|
+
const origin = request.headers.get("Origin");
|
|
970
|
+
if (origin !== null) return matchesOrigin(origin, request, options.origin);
|
|
971
|
+
const referer = request.headers.get("Referer");
|
|
972
|
+
if (referer !== null) {
|
|
973
|
+
try {
|
|
974
|
+
return matchesOrigin(new URL(referer).origin, request, options.origin);
|
|
975
|
+
} catch {
|
|
976
|
+
return false;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
return options.allowRequestsWithoutOriginCheck === true;
|
|
980
|
+
}
|
|
981
|
+
const CSRF_VARY = ["Sec-Fetch-Site", "Origin", "Referer"];
|
|
982
|
+
function withCSRFVary(response) {
|
|
983
|
+
const current = response.headers.get("Vary");
|
|
984
|
+
if (current === "*") return response;
|
|
985
|
+
const values = current ? current.split(",").map(value => value.trim()) : [];
|
|
986
|
+
const names = new Set(values.map(value => value.toLowerCase()));
|
|
987
|
+
for (const value of CSRF_VARY) {
|
|
988
|
+
if (!names.has(value.toLowerCase())) values.push(value);
|
|
989
|
+
}
|
|
990
|
+
const vary = values.join(", ");
|
|
991
|
+
try {
|
|
992
|
+
response.headers.set("Vary", vary);
|
|
993
|
+
return response;
|
|
994
|
+
} catch {
|
|
995
|
+
const headers = new Headers(response.headers);
|
|
996
|
+
headers.set("Vary", vary);
|
|
997
|
+
return new Response(response.body, {
|
|
998
|
+
status: response.status,
|
|
999
|
+
statusText: response.statusText,
|
|
1000
|
+
headers
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
function forbiddenResponse() {
|
|
1005
|
+
return withCSRFVary(new Response(DEV ? "Forbidden" : null, {
|
|
1006
|
+
status: 403,
|
|
1007
|
+
headers: {
|
|
1008
|
+
"Cache-Control": "no-store"
|
|
1009
|
+
}
|
|
1010
|
+
}));
|
|
1011
|
+
}
|
|
951
1012
|
async function handleServerFunctionRequest(request, options = {}) {
|
|
952
1013
|
const codec = options.codec !== undefined ? options.codec : getServerFunctionsCodec();
|
|
953
1014
|
const url = new URL(request.url);
|
|
1015
|
+
const csrf = options.csrf !== undefined ? options.csrf : config.csrf;
|
|
1016
|
+
const protectsRequest = csrf !== false;
|
|
1017
|
+
if (protectsRequest && !(await allowsServerFunctionRequest(request, csrf === true ? {} : csrf))) {
|
|
1018
|
+
return forbiddenResponse();
|
|
1019
|
+
}
|
|
954
1020
|
const instance = request.headers.get(INSTANCE_HEADER);
|
|
955
1021
|
const functionId = resolveFunctionId(request, url);
|
|
956
1022
|
if (!functionId) {
|
|
957
|
-
|
|
1023
|
+
const response = new Response(DEV ? "Server function not found" : null, {
|
|
958
1024
|
status: 404
|
|
959
1025
|
});
|
|
1026
|
+
return protectsRequest ? withCSRFVary(response) : response;
|
|
960
1027
|
}
|
|
961
1028
|
let serverFunction;
|
|
962
1029
|
try {
|
|
963
1030
|
serverFunction = getServerFunction(functionId);
|
|
964
1031
|
} catch {
|
|
965
|
-
|
|
1032
|
+
const response = new Response(DEV ? `Unknown server function: ${functionId}` : null, {
|
|
966
1033
|
status: 404
|
|
967
1034
|
});
|
|
1035
|
+
return protectsRequest ? withCSRFVary(response) : response;
|
|
968
1036
|
}
|
|
969
1037
|
if (request.method === "GET" && METHODS.get(functionId) !== "GET") {
|
|
970
|
-
|
|
1038
|
+
const response = new Response(DEV ? `Method not allowed for server function: ${functionId}` : null, {
|
|
971
1039
|
status: 405,
|
|
972
1040
|
headers: {
|
|
973
1041
|
Allow: "POST"
|
|
974
1042
|
}
|
|
975
1043
|
});
|
|
1044
|
+
return protectsRequest ? withCSRFVary(response) : response;
|
|
976
1045
|
}
|
|
977
1046
|
const event = options.createEvent ? options.createEvent(request) : {
|
|
978
1047
|
request,
|
|
@@ -1131,7 +1200,8 @@ async function handleServerFunctionRequest(request, options = {}) {
|
|
|
1131
1200
|
return encodeResult(safe, headers, 200, codec, request.signal);
|
|
1132
1201
|
}
|
|
1133
1202
|
};
|
|
1134
|
-
|
|
1203
|
+
const response = commitEventResponse(await dispatch(), event);
|
|
1204
|
+
return protectsRequest ? withCSRFVary(response) : response;
|
|
1135
1205
|
}
|
|
1136
1206
|
|
|
1137
|
-
export { ERROR_HEADER, FLASH_COOKIE, FUNCTION_HEADER, GENERIC_SERVER_ERROR_MESSAGE, GET, INSTANCE_HEADER, SINGLE_FLIGHT_HEADER, clearFlashCookie, configureServerFunctionsServer, createNoJSHandler, createServerReference, decodeErrorHeaderValue, decodeFlashCookie, decodeResponse, decodeResponsePayload, encodeErrorHeaderValue, encodeFlashCookie, foldSetCookies, getEventServerFunctionInvocation, getServerFunction, getServerFunctionInvocation, getServerFunctionMetadata, handleServerFunctionRequest, hasFlashCookie, isServerFunction, live, registerServerFunction, registerServerReference, sanitizeServerError, serializeResponseStream, setServerFunctionsDev, subscribeFlightData, withMeta };
|
|
1207
|
+
export { ERROR_HEADER, FLASH_COOKIE, FUNCTION_HEADER, GENERIC_SERVER_ERROR_MESSAGE, GET, INSTANCE_HEADER, SINGLE_FLIGHT_HEADER, clearFlashCookie, configureServerFunctionsServer, createNoJSHandler, createServerReference, decodeErrorHeaderValue, decodeFlashCookie, decodeResponse, decodeResponsePayload, encodeErrorHeaderValue, encodeFlashCookie, foldSetCookies, getEventServerFunctionInvocation, getServerFunction, getServerFunctionInvocation, getServerFunctionMetadata, handleServerFunctionRequest, hasFlashCookie, isServerFunction, live, observeServerFunctionCalls, registerServerFunction, registerServerReference, sanitizeServerError, serializeResponseStream, setServerFunctionsDev, subscribeFlightData, withMeta };
|
package/types/core.d.ts
CHANGED
|
@@ -4,3 +4,6 @@ export declare const memo: (fn: any) => import("solid-js").SourceAccessor<any>;
|
|
|
4
4
|
export declare const runWithHydrationScope: (id: any, fn: any) => unknown;
|
|
5
5
|
export declare const ssrAsyncValue: (value: any) => import("solid-js").SourceAccessor<any>;
|
|
6
6
|
export declare const waitAsset: (promise: any) => void;
|
|
7
|
+
export declare const driveList: undefined;
|
|
8
|
+
export declare const patchableRaw: undefined;
|
|
9
|
+
export declare const registerPatch: undefined;
|
|
@@ -245,6 +245,15 @@ export interface FrameHostOptions {
|
|
|
245
245
|
* identity only.
|
|
246
246
|
*/
|
|
247
247
|
isContainer?(value: unknown): boolean;
|
|
248
|
+
/**
|
|
249
|
+
* Arms event types for behavior claims: the `_bnd` sweep collects the
|
|
250
|
+
* event names it finds and hands them here so delegated dispatch can
|
|
251
|
+
* reach them. Platform glue passes its `delegateEvents` — the option
|
|
252
|
+
* exists (rather than client.js importing the event system) so
|
|
253
|
+
* tree-shaken subsets without events pay nothing. Frames registered
|
|
254
|
+
* with this host inherit it unless they pass their own `delegate`.
|
|
255
|
+
*/
|
|
256
|
+
delegate?(eventNames: Iterable<string>): void;
|
|
248
257
|
}
|
|
249
258
|
|
|
250
259
|
/** @experimental */
|
|
@@ -260,6 +269,13 @@ export interface FrameOptions {
|
|
|
260
269
|
id?: string;
|
|
261
270
|
/** Client content keyed by prop name (occurrences resolve by prop). */
|
|
262
271
|
slots?: Record<string, Slot>;
|
|
272
|
+
/**
|
|
273
|
+
* Raw client props for behavior-claim resolution: server elements carrying
|
|
274
|
+
* `_bnd="pos=prop"` markers (compiled under the `serverComponents` option)
|
|
275
|
+
* resolve ref/event positions by name through this object — read live at
|
|
276
|
+
* dispatch/materialize time, so compiled prop getters stay latest-value.
|
|
277
|
+
*/
|
|
278
|
+
props?: Record<string, unknown>;
|
|
263
279
|
/**
|
|
264
280
|
* Adopt existing server-rendered DOM: the first apply morphs against it,
|
|
265
281
|
* and slots sync immediately (hydration attach) — a document-SSR boot
|
|
@@ -277,6 +293,8 @@ export interface FrameOptions {
|
|
|
277
293
|
* streamed chunks).
|
|
278
294
|
*/
|
|
279
295
|
ownerScope?<T>(fn: () => T): T;
|
|
296
|
+
/** Per-frame override of the host's `delegate` (see FrameHostOptions). */
|
|
297
|
+
delegate?(eventNames: Iterable<string>): void;
|
|
280
298
|
/**
|
|
281
299
|
* Boundary-driven segment reveal. When present, `#revealSegment` hands the
|
|
282
300
|
* placeholder seam to this hook instead of swapping imperatively: the binding
|
|
@@ -127,6 +127,36 @@ export interface ServerFunctionsClientConfig {
|
|
|
127
127
|
*/
|
|
128
128
|
export function configureServerFunctionsClient(config?: ServerFunctionsClientConfig): void;
|
|
129
129
|
|
|
130
|
+
export interface ServerFunctionRequestCall {
|
|
131
|
+
type: "request";
|
|
132
|
+
id: string;
|
|
133
|
+
instance: string;
|
|
134
|
+
request: Request;
|
|
135
|
+
meta: ServerFunctionMetadata | undefined;
|
|
136
|
+
time: number;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export interface ServerFunctionResponseCall {
|
|
140
|
+
type: "response";
|
|
141
|
+
id: string;
|
|
142
|
+
instance: string;
|
|
143
|
+
response: Response;
|
|
144
|
+
meta: ServerFunctionMetadata | undefined;
|
|
145
|
+
time: number;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export type ServerFunctionCall = ServerFunctionRequestCall | ServerFunctionResponseCall;
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Observes cloned requests and responses without handling them. Subscribe
|
|
152
|
+
* from devtools; do not use this to replace `prepareRequest` /
|
|
153
|
+
* `responseHandler`. The server entry exports a no-op of the same name so
|
|
154
|
+
* isomorphic `@solidjs/web/server-functions` imports resolve.
|
|
155
|
+
*/
|
|
156
|
+
export function observeServerFunctionCalls(
|
|
157
|
+
observer: (call: ServerFunctionCall) => void
|
|
158
|
+
): () => void;
|
|
159
|
+
|
|
130
160
|
/**
|
|
131
161
|
* Declares a server function callable over HTTP GET: calls to the returned
|
|
132
162
|
* reference go out as GET requests with the arguments codec-encoded in the
|
|
@@ -163,8 +193,9 @@ export type LiveSourceStatus = "connected" | "reconnecting" | "closed";
|
|
|
163
193
|
* A live call's answer: the source's iterable, plus an optional `onstatus`
|
|
164
194
|
* side channel for the wire facts the reconnect loop erases from the value
|
|
165
195
|
* stream — `"connected"` on each successful (re)connect, `"reconnecting"`
|
|
166
|
-
* (with the error) on each post-connect death, `"closed"` when
|
|
167
|
-
* completes or the consumer ends it
|
|
196
|
+
* (with the error) on each transient post-connect death, `"closed"` when
|
|
197
|
+
* the source completes or the consumer ends it — with the error when the
|
|
198
|
+
* end was a definite rejection (4xx) failing fast instead of retrying.
|
|
168
199
|
*/
|
|
169
200
|
export type LiveSource<R> = R & {
|
|
170
201
|
onstatus?: (state: LiveSourceStatus, error?: unknown) => void;
|
|
@@ -28,7 +28,7 @@ export type {
|
|
|
28
28
|
} from "./shared.js";
|
|
29
29
|
export { decodeFlashCookie, encodeFlashCookie } from "./flash.js";
|
|
30
30
|
export type { FlashSubmission } from "./flash.js";
|
|
31
|
-
import { ServerFunction } from "./shared.js";
|
|
31
|
+
import { ServerFunction, ServerFunctionMetadata } from "./shared.js";
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
34
|
* The request event a server function call runs under: the base
|
|
@@ -197,6 +197,26 @@ export function createNoJSHandler(
|
|
|
197
197
|
options?: NoJSHandlerOptions
|
|
198
198
|
): (result: unknown, request: Request, args: unknown[], thrown?: boolean) => Response;
|
|
199
199
|
|
|
200
|
+
export type ServerFunctionOriginMatcher =
|
|
201
|
+
| string
|
|
202
|
+
| readonly string[]
|
|
203
|
+
| ((origin: string, request: Request) => boolean | Promise<boolean>);
|
|
204
|
+
|
|
205
|
+
/** Same-origin validation options for server function requests. */
|
|
206
|
+
export interface ServerFunctionCSRFOptions {
|
|
207
|
+
/**
|
|
208
|
+
* Expected public origin. Defaults to the incoming request URL's origin.
|
|
209
|
+
* A function can validate origins dynamically for multi-tenant hosts.
|
|
210
|
+
*/
|
|
211
|
+
origin?: ServerFunctionOriginMatcher;
|
|
212
|
+
/**
|
|
213
|
+
* Allows requests without `Sec-Fetch-Site`, `Origin`, or `Referer`.
|
|
214
|
+
* Cross-origin metadata is still rejected.
|
|
215
|
+
* @default false
|
|
216
|
+
*/
|
|
217
|
+
allowRequestsWithoutOriginCheck?: boolean;
|
|
218
|
+
}
|
|
219
|
+
|
|
200
220
|
/** Options for `configureServerFunctionsServer`. */
|
|
201
221
|
export interface ServerFunctionsServerConfig {
|
|
202
222
|
/**
|
|
@@ -287,6 +307,12 @@ export interface ServerFunctionsServerConfig {
|
|
|
287
307
|
* @default "/_server"
|
|
288
308
|
*/
|
|
289
309
|
endpoint?: string;
|
|
310
|
+
/**
|
|
311
|
+
* Same-origin protection for HTTP server function calls. Enabled by
|
|
312
|
+
* default. Set to `false` only when another trusted layer protects the
|
|
313
|
+
* endpoint.
|
|
314
|
+
*/
|
|
315
|
+
csrf?: boolean | ServerFunctionCSRFOptions;
|
|
290
316
|
/**
|
|
291
317
|
* Codec options (extra plugins etc.) for decoding arguments and encoding
|
|
292
318
|
* results — must match the client's. Stored in the shared layer, so
|
|
@@ -392,7 +418,9 @@ export function GET<A extends readonly any[], R>(
|
|
|
392
418
|
fn: (...args: A) => R
|
|
393
419
|
): ServerFunction<A, Awaited<R>>;
|
|
394
420
|
|
|
395
|
-
/** Wire-state transitions a live call's iterable can report (client side).
|
|
421
|
+
/** Wire-state transitions a live call's iterable can report (client side).
|
|
422
|
+
* `"closed"` carries the error when a definite rejection (4xx) ended the
|
|
423
|
+
* call instead of the retry loop. */
|
|
396
424
|
export type LiveSourceStatus = "connected" | "reconnecting" | "closed";
|
|
397
425
|
|
|
398
426
|
/**
|
|
@@ -529,6 +557,11 @@ export interface HandleServerFunctionOptions {
|
|
|
529
557
|
args: unknown[],
|
|
530
558
|
thrown?: boolean
|
|
531
559
|
): Response | Promise<Response>;
|
|
560
|
+
/**
|
|
561
|
+
* Overrides same-origin protection for this handler. Set to `false` only
|
|
562
|
+
* when another trusted layer protects the endpoint.
|
|
563
|
+
*/
|
|
564
|
+
csrf?: boolean | ServerFunctionCSRFOptions;
|
|
532
565
|
/** Overrides the configured codec options for this handler. */
|
|
533
566
|
codec?: JSONCodecOptions;
|
|
534
567
|
}
|
|
@@ -543,6 +576,10 @@ export interface HandleServerFunctionOptions {
|
|
|
543
576
|
* (default `/_server`); platform adapters (h3, express, ...) convert their
|
|
544
577
|
* request shape to a web `Request` around it.
|
|
545
578
|
*
|
|
579
|
+
* Requests are same-origin by default. The handler accepts browser requests
|
|
580
|
+
* proven by `Sec-Fetch-Site`, `Origin`, or `Referer`, and rejects requests
|
|
581
|
+
* without usable metadata unless explicitly configured otherwise.
|
|
582
|
+
*
|
|
546
583
|
* When the event carries a `response` head stub (`event.response`, see the
|
|
547
584
|
* server entry's `ResponseStub`), the handler folds it onto every outgoing
|
|
548
585
|
* response as the head freezes — its `Set-Cookie` values (cookies appended
|
|
@@ -606,6 +643,34 @@ export const GENERIC_SERVER_ERROR_MESSAGE: string;
|
|
|
606
643
|
*/
|
|
607
644
|
export function sanitizeServerError(value: unknown): unknown;
|
|
608
645
|
|
|
646
|
+
export interface ServerFunctionRequestCall {
|
|
647
|
+
type: "request";
|
|
648
|
+
id: string;
|
|
649
|
+
instance: string;
|
|
650
|
+
request: Request;
|
|
651
|
+
meta: ServerFunctionMetadata | undefined;
|
|
652
|
+
time: number;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
export interface ServerFunctionResponseCall {
|
|
656
|
+
type: "response";
|
|
657
|
+
id: string;
|
|
658
|
+
instance: string;
|
|
659
|
+
response: Response;
|
|
660
|
+
meta: ServerFunctionMetadata | undefined;
|
|
661
|
+
time: number;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
export type ServerFunctionCall = ServerFunctionRequestCall | ServerFunctionResponseCall;
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* Client-only inspection seam. A no-op on this entry so isomorphic
|
|
668
|
+
* `@solidjs/web/server-functions` imports resolve.
|
|
669
|
+
*/
|
|
670
|
+
export function observeServerFunctionCalls(
|
|
671
|
+
observer: (call: ServerFunctionCall) => void
|
|
672
|
+
): () => void;
|
|
673
|
+
|
|
609
674
|
/**
|
|
610
675
|
* Overrides the build-variant dev flag for this module instance — the seam
|
|
611
676
|
* for test harnesses and hand-rolled bundles whose packaging cannot replace
|