@luckystack/server 0.14.0 → 0.15.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/CHANGELOG.md +21 -0
- package/dist/hookPayloads.d.ts +1 -0
- package/dist/httpHandler.d.ts +1 -0
- package/dist/httpRoutes/clientSession.d.ts +1 -0
- package/dist/httpRoutes/sessionCookie.d.ts +8 -0
- package/dist/index.js +177 -103
- package/dist/index.js.map +1 -1
- package/package.json +11 -11
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
|
|
10
10
|
// src/createServer.ts
|
|
11
11
|
import http from "http";
|
|
12
|
-
import { registerBindAddress, registerBoundAddress, writeBootUuid, startBootUuidHeartbeat, getLogger as getLogger14, getProjectConfig as
|
|
12
|
+
import { registerBindAddress, registerBoundAddress, writeBootUuid, startBootUuidHeartbeat, getLogger as getLogger14, getProjectConfig as getProjectConfig16, tryCatch as tryCatch12, tryCatchSync as tryCatchSync3, resolveRuntimeMode, dispatchHook as dispatchHook8 } from "@luckystack/core";
|
|
13
13
|
|
|
14
14
|
// src/httpHandler.ts
|
|
15
15
|
import { randomUUID } from "crypto";
|
|
@@ -17,9 +17,10 @@ import {
|
|
|
17
17
|
allowedOrigin,
|
|
18
18
|
dispatchHook as dispatchHook5,
|
|
19
19
|
extractTokenFromRequest as extractTokenFromRequest3,
|
|
20
|
+
getCsrfConfig as getCsrfConfig3,
|
|
20
21
|
getLogger as getLogger7,
|
|
21
22
|
getParams,
|
|
22
|
-
getProjectConfig as
|
|
23
|
+
getProjectConfig as getProjectConfig13,
|
|
23
24
|
hasCookie,
|
|
24
25
|
normalizeOrigin,
|
|
25
26
|
readSession as readSession3,
|
|
@@ -494,18 +495,42 @@ import {
|
|
|
494
495
|
checkRateLimit,
|
|
495
496
|
dispatchHook as dispatchHook2,
|
|
496
497
|
getLogger,
|
|
497
|
-
getProjectConfig as
|
|
498
|
+
getProjectConfig as getProjectConfig6,
|
|
498
499
|
resolveClientIp,
|
|
499
500
|
resolveDevCallbackUrl
|
|
500
501
|
} from "@luckystack/core";
|
|
501
502
|
|
|
503
|
+
// src/httpRoutes/clientSession.ts
|
|
504
|
+
var toClientSession = (session) => {
|
|
505
|
+
if (typeof session !== "object" || session === null) return session;
|
|
506
|
+
const { token: _token, csrfToken: _csrfToken, ...clientSession } = session;
|
|
507
|
+
return clientSession;
|
|
508
|
+
};
|
|
509
|
+
|
|
502
510
|
// src/httpRoutes/sessionCookie.ts
|
|
503
|
-
import { isProductionRuntime } from "@luckystack/core";
|
|
511
|
+
import { applyCookiePrefixConstraints, getProjectConfig as getProjectConfig5, isProductionRuntime } from "@luckystack/core";
|
|
504
512
|
var resolveCookieSecure = (sessionCookieSecure, secureEnv) => {
|
|
505
513
|
if (sessionCookieSecure !== void 0) return sessionCookieSecure;
|
|
506
514
|
if (secureEnv === "true") return true;
|
|
507
515
|
return isProductionRuntime();
|
|
508
516
|
};
|
|
517
|
+
var resolveSessionCookieName = () => {
|
|
518
|
+
const { http: http2 } = getProjectConfig5();
|
|
519
|
+
return applyCookiePrefixConstraints(http2.sessionCookieName, http2.sessionCookiePrefix).name;
|
|
520
|
+
};
|
|
521
|
+
var buildSessionCookieOptions = (maxAgeSeconds) => {
|
|
522
|
+
const { http: http2 } = getProjectConfig5();
|
|
523
|
+
const constraints = applyCookiePrefixConstraints(
|
|
524
|
+
http2.sessionCookieName,
|
|
525
|
+
http2.sessionCookiePrefix,
|
|
526
|
+
resolveCookieSecure(http2.sessionCookieSecure, process.env.SECURE)
|
|
527
|
+
);
|
|
528
|
+
const path4 = constraints.forcePathRoot ? "/" : http2.sessionCookiePath;
|
|
529
|
+
const domain = http2.sessionCookieDomain && !constraints.forbidDomain ? `Domain=${http2.sessionCookieDomain}; ` : "";
|
|
530
|
+
return `HttpOnly; SameSite=${http2.sessionCookieSameSite}; Path=${path4}; Max-Age=${maxAgeSeconds}; ${domain}${constraints.secure ? "Secure;" : ""}`;
|
|
531
|
+
};
|
|
532
|
+
var buildSessionCookie = (token, cookieOptions) => `${resolveSessionCookieName()}=${token}; ${cookieOptions}`;
|
|
533
|
+
var buildClearedSessionCookie = () => buildSessionCookie("", buildSessionCookieOptions(0));
|
|
509
534
|
|
|
510
535
|
// src/httpRoutes/authApiRoute.ts
|
|
511
536
|
var parseSessionBasedTokenHeader = (headerValue) => {
|
|
@@ -528,6 +553,7 @@ var handleAuthApiRoute = async ({
|
|
|
528
553
|
req,
|
|
529
554
|
res,
|
|
530
555
|
routePath,
|
|
556
|
+
method,
|
|
531
557
|
token,
|
|
532
558
|
params,
|
|
533
559
|
sessionCookieOptions
|
|
@@ -539,8 +565,7 @@ var handleAuthApiRoute = async ({
|
|
|
539
565
|
res.end(JSON.stringify({ status: false, reason: "auth.disabled" }));
|
|
540
566
|
return true;
|
|
541
567
|
}
|
|
542
|
-
const config =
|
|
543
|
-
const sessionCookieName = config.http.sessionCookieName;
|
|
568
|
+
const config = getProjectConfig6();
|
|
544
569
|
const shouldLogDev = config.logging.devLogs;
|
|
545
570
|
const providerName = routePath.split("/")[3];
|
|
546
571
|
const provider = login.getOAuthProviders().find((p) => p.name === providerName);
|
|
@@ -624,6 +649,12 @@ var handleAuthApiRoute = async ({
|
|
|
624
649
|
res.end();
|
|
625
650
|
return true;
|
|
626
651
|
}
|
|
652
|
+
if (method !== "POST") {
|
|
653
|
+
res.statusCode = 405;
|
|
654
|
+
res.setHeader("Allow", "POST");
|
|
655
|
+
res.end();
|
|
656
|
+
return true;
|
|
657
|
+
}
|
|
627
658
|
const rateLimiting = config.rateLimiting;
|
|
628
659
|
const requesterIp = resolveClientIp({
|
|
629
660
|
rawAddress: req.socket.remoteAddress,
|
|
@@ -685,21 +716,21 @@ var handleAuthApiRoute = async ({
|
|
|
685
716
|
if (useSessionBasedToken) {
|
|
686
717
|
res.setHeader("X-Session-Token", result.newToken);
|
|
687
718
|
} else {
|
|
688
|
-
res.setHeader("Set-Cookie",
|
|
719
|
+
res.setHeader("Set-Cookie", buildSessionCookie(result.newToken, sessionCookieOptions));
|
|
689
720
|
}
|
|
690
721
|
}
|
|
691
722
|
res.setHeader("content-type", "application/json; charset=utf-8");
|
|
692
723
|
res.end(JSON.stringify({
|
|
693
724
|
status: result.status,
|
|
694
725
|
reason: result.reason,
|
|
695
|
-
session: result.session,
|
|
726
|
+
session: toClientSession(result.session),
|
|
696
727
|
authenticated: Boolean(result.newToken)
|
|
697
728
|
}));
|
|
698
729
|
return true;
|
|
699
730
|
};
|
|
700
731
|
|
|
701
732
|
// src/httpRoutes/authSecondFactorRoutes.ts
|
|
702
|
-
import { checkRateLimit as checkRateLimit2, getProjectConfig as
|
|
733
|
+
import { checkRateLimit as checkRateLimit2, getProjectConfig as getProjectConfig7, resolveClientIp as resolveClientIp2 } from "@luckystack/core";
|
|
703
734
|
var json = (ctx, statusCode, body) => {
|
|
704
735
|
ctx.res.statusCode = statusCode;
|
|
705
736
|
ctx.res.setHeader("content-type", "application/json; charset=utf-8");
|
|
@@ -713,7 +744,7 @@ var methodNotAllowed = (ctx) => {
|
|
|
713
744
|
return true;
|
|
714
745
|
};
|
|
715
746
|
var requesterIpOf = (ctx) => {
|
|
716
|
-
const config =
|
|
747
|
+
const config = getProjectConfig7();
|
|
717
748
|
return resolveClientIp2({
|
|
718
749
|
rawAddress: ctx.req.socket.remoteAddress,
|
|
719
750
|
headers: ctx.req.headers,
|
|
@@ -744,7 +775,7 @@ var sendLoginResult = async (ctx, login, result) => {
|
|
|
744
775
|
}
|
|
745
776
|
if (result.newToken) {
|
|
746
777
|
if (ctx.token) await login.deleteSession(ctx.token, { skipSocketLogout: true });
|
|
747
|
-
const config =
|
|
778
|
+
const config = getProjectConfig7();
|
|
748
779
|
const headerValue = ctx.req.headers["x-session-based-token"];
|
|
749
780
|
const raw = Array.isArray(headerValue) ? headerValue[0] : headerValue;
|
|
750
781
|
const requestedSessionMode = raw === "true" ? true : raw === "false" ? false : null;
|
|
@@ -752,13 +783,13 @@ var sendLoginResult = async (ctx, login, result) => {
|
|
|
752
783
|
if (useSessionBasedToken) {
|
|
753
784
|
ctx.res.setHeader("X-Session-Token", result.newToken);
|
|
754
785
|
} else {
|
|
755
|
-
ctx.res.setHeader("Set-Cookie",
|
|
786
|
+
ctx.res.setHeader("Set-Cookie", buildSessionCookie(result.newToken, ctx.sessionCookieOptions));
|
|
756
787
|
}
|
|
757
788
|
}
|
|
758
789
|
return json(ctx, 200, {
|
|
759
790
|
status: true,
|
|
760
791
|
reason: result.reason,
|
|
761
|
-
session: result.session,
|
|
792
|
+
session: toClientSession(result.session),
|
|
762
793
|
authenticated: Boolean(result.newToken)
|
|
763
794
|
});
|
|
764
795
|
};
|
|
@@ -841,7 +872,7 @@ var handleAuthTwoFactorRoute = async (ctx) => {
|
|
|
841
872
|
};
|
|
842
873
|
|
|
843
874
|
// src/httpRoutes/authLogoutRoute.ts
|
|
844
|
-
import { getLogger as getLogger2, getProjectConfig as
|
|
875
|
+
import { getLogger as getLogger2, getProjectConfig as getProjectConfig8, tryCatch as tryCatch3 } from "@luckystack/core";
|
|
845
876
|
var warnedNonStrictSameSite = false;
|
|
846
877
|
var handleAuthLogoutRoute = async ({ res, routePath, method, token }) => {
|
|
847
878
|
if (routePath !== "/auth/logout") return false;
|
|
@@ -861,18 +892,14 @@ var handleAuthLogoutRoute = async ({ res, routePath, method, token }) => {
|
|
|
861
892
|
}
|
|
862
893
|
}
|
|
863
894
|
}
|
|
864
|
-
const http2 =
|
|
895
|
+
const http2 = getProjectConfig8().http;
|
|
865
896
|
if (http2.sessionCookieSameSite !== "Strict" && !warnedNonStrictSameSite) {
|
|
866
897
|
warnedNonStrictSameSite = true;
|
|
867
898
|
getLogger2().warn(
|
|
868
899
|
`/auth/logout is exempt from CSRF middleware and relies on SameSite=Strict. Current sessionCookieSameSite="${http2.sessionCookieSameSite}" weakens this protection.`
|
|
869
900
|
);
|
|
870
901
|
}
|
|
871
|
-
|
|
872
|
-
res.setHeader(
|
|
873
|
-
"Set-Cookie",
|
|
874
|
-
`${http2.sessionCookieName}=; HttpOnly; SameSite=${http2.sessionCookieSameSite}; Path=${http2.sessionCookiePath}; Max-Age=0; ${secure ? "Secure;" : ""}`
|
|
875
|
-
);
|
|
902
|
+
res.setHeader("Set-Cookie", buildClearedSessionCookie());
|
|
876
903
|
res.statusCode = 200;
|
|
877
904
|
res.setHeader("Content-Type", "application/json");
|
|
878
905
|
res.end(JSON.stringify({ status: "success", result: true }));
|
|
@@ -880,19 +907,19 @@ var handleAuthLogoutRoute = async ({ res, routePath, method, token }) => {
|
|
|
880
907
|
};
|
|
881
908
|
|
|
882
909
|
// src/httpRoutes/authProvidersRoute.ts
|
|
883
|
-
import { getProjectConfig as
|
|
910
|
+
import { getProjectConfig as getProjectConfig9 } from "@luckystack/core";
|
|
884
911
|
var handleAuthProvidersRoute = async ({ req, res, routePath }) => {
|
|
885
912
|
if (routePath !== "/auth/providers" || req.method !== "GET") return false;
|
|
886
913
|
const login = await getLogin();
|
|
887
914
|
const providers = login ? login.getOAuthProviders().map((provider) => provider.name) : [];
|
|
888
|
-
const emailCodeLogin = Boolean(login) &&
|
|
915
|
+
const emailCodeLogin = Boolean(login) && getProjectConfig9().auth.emailCodeLogin;
|
|
889
916
|
res.setHeader("Content-Type", "application/json");
|
|
890
917
|
res.end(JSON.stringify({ providers, emailCodeLogin }));
|
|
891
918
|
return true;
|
|
892
919
|
};
|
|
893
920
|
|
|
894
921
|
// src/httpRoutes/authCallbackRoute.ts
|
|
895
|
-
import { getLogger as getLogger3, getProjectConfig as
|
|
922
|
+
import { getLogger as getLogger3, getProjectConfig as getProjectConfig10 } from "@luckystack/core";
|
|
896
923
|
var handleAuthCallbackRoute = async ({
|
|
897
924
|
req,
|
|
898
925
|
res,
|
|
@@ -907,8 +934,7 @@ var handleAuthCallbackRoute = async ({
|
|
|
907
934
|
res.end("Auth is not enabled");
|
|
908
935
|
return true;
|
|
909
936
|
}
|
|
910
|
-
const config =
|
|
911
|
-
const sessionCookieName = config.http.sessionCookieName;
|
|
937
|
+
const config = getProjectConfig10();
|
|
912
938
|
const shouldLogDev = config.logging.devLogs;
|
|
913
939
|
const publicOrigin = (config.app.publicUrl || "").replace(/\/+$/, "");
|
|
914
940
|
const loginRedirect = config.loginRedirectUrl || "/";
|
|
@@ -933,7 +959,7 @@ var handleAuthCallbackRoute = async ({
|
|
|
933
959
|
res.setHeader("Set-Cookie", clearStateCookie);
|
|
934
960
|
res.writeHead(302, { Location: `${baseUrl}#token=${newToken}` });
|
|
935
961
|
} else {
|
|
936
|
-
res.setHeader("Set-Cookie", [clearStateCookie,
|
|
962
|
+
res.setHeader("Set-Cookie", [clearStateCookie, buildSessionCookie(newToken, sessionCookieOptions)]);
|
|
937
963
|
res.writeHead(302, { Location: redirectUrl });
|
|
938
964
|
}
|
|
939
965
|
res.end();
|
|
@@ -952,7 +978,7 @@ import {
|
|
|
952
978
|
import { handleHttpApiRequest } from "@luckystack/api";
|
|
953
979
|
|
|
954
980
|
// src/sse.ts
|
|
955
|
-
import { getProjectConfig as
|
|
981
|
+
import { getProjectConfig as getProjectConfig11 } from "@luckystack/core";
|
|
956
982
|
var isExpectingEventStream = (acceptHeader) => {
|
|
957
983
|
if (!acceptHeader) return false;
|
|
958
984
|
const value = Array.isArray(acceptHeader) ? acceptHeader.join(",") : acceptHeader;
|
|
@@ -960,7 +986,7 @@ var isExpectingEventStream = (acceptHeader) => {
|
|
|
960
986
|
};
|
|
961
987
|
var queryRequestsStream = (queryString) => {
|
|
962
988
|
if (!queryString) return false;
|
|
963
|
-
const { queryParam, enabledValue } =
|
|
989
|
+
const { queryParam, enabledValue } = getProjectConfig11().http.stream;
|
|
964
990
|
const params = new URLSearchParams(queryString);
|
|
965
991
|
const value = params.get(queryParam);
|
|
966
992
|
return value === enabledValue || value === "1";
|
|
@@ -976,7 +1002,7 @@ var initSseResponse = (res) => {
|
|
|
976
1002
|
Connection: "keep-alive",
|
|
977
1003
|
"X-Accel-Buffering": "no"
|
|
978
1004
|
});
|
|
979
|
-
const connectedComment =
|
|
1005
|
+
const connectedComment = getProjectConfig11().http.stream.connectedComment;
|
|
980
1006
|
if (connectedComment) {
|
|
981
1007
|
res.write(`${connectedComment}
|
|
982
1008
|
|
|
@@ -999,10 +1025,10 @@ var sendSseEvent = ({
|
|
|
999
1025
|
};
|
|
1000
1026
|
|
|
1001
1027
|
// src/httpRoutes/resolveRequesterIp.ts
|
|
1002
|
-
import { getProjectConfig as
|
|
1028
|
+
import { getProjectConfig as getProjectConfig12, resolveClientIp as resolveClientIp3 } from "@luckystack/core";
|
|
1003
1029
|
var resolveRequesterIp = (req) => {
|
|
1004
|
-
const trustProxy =
|
|
1005
|
-
const trustedProxyHopCount =
|
|
1030
|
+
const trustProxy = getProjectConfig12().http.trustProxy;
|
|
1031
|
+
const trustedProxyHopCount = getProjectConfig12().http.trustedProxyHopCount;
|
|
1006
1032
|
const rawRemoteAddress = req.socket.remoteAddress;
|
|
1007
1033
|
return rawRemoteAddress || trustProxy && (req.headers["x-forwarded-for"] || req.headers["x-real-ip"]) ? resolveClientIp3({ rawAddress: rawRemoteAddress, headers: req.headers, trustProxy, trustedProxyHopCount }) : void 0;
|
|
1008
1034
|
};
|
|
@@ -1065,7 +1091,7 @@ var handleApiRoute = async ({
|
|
|
1065
1091
|
return true;
|
|
1066
1092
|
}
|
|
1067
1093
|
let apiData = typeof params === "object" ? { ...params } : {};
|
|
1068
|
-
delete apiData.stream;
|
|
1094
|
+
if (method === "GET") delete apiData.stream;
|
|
1069
1095
|
const routedData = apiData.__luckystack_data;
|
|
1070
1096
|
if (typeof routedData === "string") {
|
|
1071
1097
|
const [parseError, parsed] = tryCatchSync(() => JSON.parse(routedData));
|
|
@@ -1399,7 +1425,7 @@ var handleStaticAndSpaFallback = async ({
|
|
|
1399
1425
|
res.end("Not Found");
|
|
1400
1426
|
return true;
|
|
1401
1427
|
}
|
|
1402
|
-
await options.serveFile
|
|
1428
|
+
await serveWithRewrittenUrl(options.serveFile, req, res, routePath);
|
|
1403
1429
|
return true;
|
|
1404
1430
|
}
|
|
1405
1431
|
if (path.extname(routePath)) {
|
|
@@ -1417,9 +1443,17 @@ var handleStaticAndSpaFallback = async ({
|
|
|
1417
1443
|
};
|
|
1418
1444
|
|
|
1419
1445
|
// src/httpHandler.ts
|
|
1420
|
-
var
|
|
1446
|
+
var buildHookSafeHeaders = (headers) => {
|
|
1447
|
+
const csrfHeaderName = getCsrfConfig3().headerName.toLowerCase();
|
|
1448
|
+
const safeHeaders = {};
|
|
1449
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
1450
|
+
if (k === "authorization" || k === "proxy-authorization" || k === "cookie" || k === "set-cookie" || k === "x-csrf-token" || k === csrfHeaderName || k === "x-test-reset-token" || k === "x-session-based-token") continue;
|
|
1451
|
+
safeHeaders[k] = Array.isArray(v) ? v.join(", ") : v ?? "";
|
|
1452
|
+
}
|
|
1453
|
+
return safeHeaders;
|
|
1454
|
+
};
|
|
1421
1455
|
var setSecurityHeaders = (req, res, origin) => {
|
|
1422
|
-
const { cors, securityHeaders } =
|
|
1456
|
+
const { cors, securityHeaders } = getProjectConfig13().http;
|
|
1423
1457
|
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
1424
1458
|
res.setHeader("Access-Control-Allow-Methods", cors.allowedMethods);
|
|
1425
1459
|
res.setHeader("Access-Control-Allow-Headers", cors.allowedHeaders);
|
|
@@ -1503,14 +1537,13 @@ var refreshSessionCookieIfPresent = async ({
|
|
|
1503
1537
|
req,
|
|
1504
1538
|
res,
|
|
1505
1539
|
token,
|
|
1506
|
-
sessionCookieName,
|
|
1507
1540
|
sessionCookieOptions
|
|
1508
1541
|
}) => {
|
|
1509
|
-
const hasTokenCookie = hasCookie(req.headers.cookie,
|
|
1542
|
+
const hasTokenCookie = hasCookie(req.headers.cookie, resolveSessionCookieName());
|
|
1510
1543
|
if (!hasTokenCookie || !token) return;
|
|
1511
1544
|
const currentSession = await readSession3(token);
|
|
1512
1545
|
if (currentSession?.id) {
|
|
1513
|
-
res.setHeader("Set-Cookie",
|
|
1546
|
+
res.setHeader("Set-Cookie", buildSessionCookie(token, sessionCookieOptions));
|
|
1514
1547
|
}
|
|
1515
1548
|
};
|
|
1516
1549
|
var parseRequestParams = async ({
|
|
@@ -1539,17 +1572,9 @@ var parseRequestParams = async ({
|
|
|
1539
1572
|
return params;
|
|
1540
1573
|
};
|
|
1541
1574
|
var handleHttpRequestInner = async (req, res, options) => {
|
|
1542
|
-
const config =
|
|
1575
|
+
const config = getProjectConfig13();
|
|
1543
1576
|
const shouldLogDev = config.logging.devLogs;
|
|
1544
|
-
const
|
|
1545
|
-
const sessionCookieOptions = buildSessionCookieOptions(
|
|
1546
|
-
config.session.expiryDays,
|
|
1547
|
-
//? Honor the explicit `http.sessionCookieSecure` override (CORE-39), else the
|
|
1548
|
-
//? `SECURE` env flag — shared with the OAuth state cookie via
|
|
1549
|
-
//? `resolveCookieSecure` so the two can never drift (WAVE4).
|
|
1550
|
-
resolveCookieSecure(config.http.sessionCookieSecure, process.env.SECURE),
|
|
1551
|
-
config.http
|
|
1552
|
-
);
|
|
1577
|
+
const sessionCookieOptions = buildSessionCookieOptions(60 * 60 * 24 * config.session.expiryDays);
|
|
1553
1578
|
const url = req.url ?? "/";
|
|
1554
1579
|
const [routePathRaw, queryStringRaw] = url.split("?");
|
|
1555
1580
|
const [decodeError, decodedPath] = tryCatchSync2(() => decodeURIComponent(routePathRaw ?? "/"));
|
|
@@ -1568,11 +1593,7 @@ var handleHttpRequestInner = async (req, res, options) => {
|
|
|
1568
1593
|
const rawRequestId = Array.isArray(incomingRequestId) ? incomingRequestId[0] : incomingRequestId;
|
|
1569
1594
|
const requestId = rawRequestId && /^[a-zA-Z0-9-]{1,128}$/.test(rawRequestId) ? rawRequestId : randomUUID();
|
|
1570
1595
|
res.setHeader("X-Request-Id", requestId);
|
|
1571
|
-
const safeHeaders =
|
|
1572
|
-
for (const [k, v] of Object.entries(req.headers)) {
|
|
1573
|
-
if (k === "authorization" || k === "cookie" || k === "set-cookie" || k === "x-csrf-token" || k === "x-test-reset-token" || k === "x-session-based-token") continue;
|
|
1574
|
-
safeHeaders[k] = Array.isArray(v) ? v.join(", ") : v ?? "";
|
|
1575
|
-
}
|
|
1596
|
+
const safeHeaders = buildHookSafeHeaders(req.headers);
|
|
1576
1597
|
const preHttpResult = await dispatchHook5("preHttpRequest", {
|
|
1577
1598
|
method: req.method?.toUpperCase() ?? "GET",
|
|
1578
1599
|
url: req.url ?? "/",
|
|
@@ -1599,7 +1620,7 @@ var handleHttpRequestInner = async (req, res, options) => {
|
|
|
1599
1620
|
return;
|
|
1600
1621
|
}
|
|
1601
1622
|
const token = extractTokenFromRequest3(req);
|
|
1602
|
-
await refreshSessionCookieIfPresent({ req, res, token,
|
|
1623
|
+
await refreshSessionCookieIfPresent({ req, res, token, sessionCookieOptions });
|
|
1603
1624
|
const csrfRejected = await enforceCsrfOnStateChangingRequest({ req, res, routePath, token, requestId });
|
|
1604
1625
|
if (csrfRejected) return;
|
|
1605
1626
|
const baseCtx = {
|
|
@@ -1655,10 +1676,11 @@ import {
|
|
|
1655
1676
|
extractLanguageFromHeader,
|
|
1656
1677
|
extractTokenFromSocket,
|
|
1657
1678
|
getLogger as getLogger8,
|
|
1658
|
-
getProjectConfig as
|
|
1679
|
+
getProjectConfig as getProjectConfig14,
|
|
1659
1680
|
dispatchHook as dispatchHook6,
|
|
1660
1681
|
normalizeErrorResponse,
|
|
1661
1682
|
readSession as readSession4,
|
|
1683
|
+
resolveClientIp as resolveClientIp4,
|
|
1662
1684
|
writeSession,
|
|
1663
1685
|
tryCatch as tryCatch8
|
|
1664
1686
|
} from "@luckystack/core";
|
|
@@ -1671,6 +1693,7 @@ var withSessionLock = async (token, fn) => {
|
|
|
1671
1693
|
await tryCatch8(() => next);
|
|
1672
1694
|
if (sessionLocks.get(token) === next) sessionLocks.delete(token);
|
|
1673
1695
|
};
|
|
1696
|
+
var SYNC_ALL_RECEIVER = "all";
|
|
1674
1697
|
var getVisibleSocketRooms = (socket, token) => {
|
|
1675
1698
|
return [...socket.rooms].filter((room) => typeof room === "string").filter((room) => room !== socket.id).filter((room) => !token || room !== token);
|
|
1676
1699
|
};
|
|
@@ -1680,6 +1703,7 @@ var getSessionRoomCodes = (session) => {
|
|
|
1680
1703
|
) : [];
|
|
1681
1704
|
return [...new Set(roomCodes)];
|
|
1682
1705
|
};
|
|
1706
|
+
var asPayload = (data) => typeof data === "object" && data !== null ? data : {};
|
|
1683
1707
|
var sanitizeSessionRoomKeys = (session) => {
|
|
1684
1708
|
const { code: _legacyCode, codes: _legacyCodes, ...sanitizedSession } = session;
|
|
1685
1709
|
return sanitizedSession;
|
|
@@ -1741,7 +1765,16 @@ var executeRoomMutation = async (opts) => {
|
|
|
1741
1765
|
const physicalRoom = formatRoomName(group, { purpose: "broadcast", userId: session.id });
|
|
1742
1766
|
const nextRoomCodes = await mutate(socket, physicalRoom, group, existingRoomCodes, session.id);
|
|
1743
1767
|
const sanitizedSession = sanitizeSessionRoomKeys(session);
|
|
1744
|
-
await writeSession(token, { ...sanitizedSession, roomCodes: nextRoomCodes });
|
|
1768
|
+
const saved = await writeSession(token, { ...sanitizedSession, roomCodes: nextRoomCodes }, void 0, { onlyIfExists: true });
|
|
1769
|
+
if (!saved.ok) {
|
|
1770
|
+
if (preHook === "preRoomJoin") await socket.leave(physicalRoom);
|
|
1771
|
+
socket.emit(buildResponseEventName(responseIndex), normalizeErrorResponse({
|
|
1772
|
+
response: { status: "error", errorCode: saved.errorCode ?? "session.notFound" },
|
|
1773
|
+
preferredLocale,
|
|
1774
|
+
userLanguage: session.language
|
|
1775
|
+
}));
|
|
1776
|
+
return;
|
|
1777
|
+
}
|
|
1745
1778
|
const visibleRooms = getVisibleSocketRooms(socket, token);
|
|
1746
1779
|
socket.emit(buildResponseEventName(responseIndex), { rooms: visibleRooms });
|
|
1747
1780
|
if (shouldLogDev) {
|
|
@@ -1766,12 +1799,12 @@ var registerApiAndSyncEvents = (ctx) => {
|
|
|
1766
1799
|
var registerCancellationEvents = (ctx) => {
|
|
1767
1800
|
const { socket } = ctx;
|
|
1768
1801
|
socket.on(socketEventNames.syncCancel, (data) => {
|
|
1769
|
-
const
|
|
1770
|
-
if (!cb) return;
|
|
1802
|
+
const { cb } = asPayload(data);
|
|
1803
|
+
if (typeof cb !== "string" || !cb) return;
|
|
1771
1804
|
abortSyncByCb(socket.id, cb);
|
|
1772
1805
|
});
|
|
1773
1806
|
socket.on(socketEventNames.apiCancel, (data) => {
|
|
1774
|
-
const responseIndex = data
|
|
1807
|
+
const { responseIndex } = asPayload(data);
|
|
1775
1808
|
if (typeof responseIndex !== "number" && typeof responseIndex !== "string") return;
|
|
1776
1809
|
abortApiByResponseIndex(socket.id, responseIndex);
|
|
1777
1810
|
});
|
|
@@ -1779,10 +1812,18 @@ var registerCancellationEvents = (ctx) => {
|
|
|
1779
1812
|
var registerRoomEvents = (ctx) => {
|
|
1780
1813
|
const { socket, token, preferredLocale, shouldLogDev } = ctx;
|
|
1781
1814
|
socket.on(socketEventNames.joinRoom, (data) => {
|
|
1782
|
-
const
|
|
1783
|
-
const
|
|
1815
|
+
const payload = asPayload(data);
|
|
1816
|
+
const group = typeof payload.group === "string" ? payload.group.trim() : "";
|
|
1817
|
+
const responseIndex = payload.responseIndex;
|
|
1784
1818
|
if (!validateRoomRequest(socket, responseIndex, token, group, preferredLocale, buildJoinRoomResponseEventName)) return;
|
|
1785
1819
|
if (!token) return;
|
|
1820
|
+
if (group === SYNC_ALL_RECEIVER) {
|
|
1821
|
+
socket.emit(buildJoinRoomResponseEventName(responseIndex), normalizeErrorResponse({
|
|
1822
|
+
response: { status: "error", errorCode: "room.invalid" },
|
|
1823
|
+
preferredLocale
|
|
1824
|
+
}));
|
|
1825
|
+
return;
|
|
1826
|
+
}
|
|
1786
1827
|
void withSessionLock(token, async () => {
|
|
1787
1828
|
await executeRoomMutation({
|
|
1788
1829
|
socket,
|
|
@@ -1797,7 +1838,7 @@ var registerRoomEvents = (ctx) => {
|
|
|
1797
1838
|
blockedErrorCode: "room.joinBlocked",
|
|
1798
1839
|
logVerb: "joined",
|
|
1799
1840
|
mutate: async (sock, physicalRoom, rawGroup, existingCodes, userId) => {
|
|
1800
|
-
const maxRooms =
|
|
1841
|
+
const maxRooms = getProjectConfig14().socket.maxRoomsPerSession;
|
|
1801
1842
|
let kept = existingCodes;
|
|
1802
1843
|
if (maxRooms !== false && maxRooms > 0 && !existingCodes.includes(rawGroup)) {
|
|
1803
1844
|
while (kept.length >= maxRooms) {
|
|
@@ -1814,8 +1855,9 @@ var registerRoomEvents = (ctx) => {
|
|
|
1814
1855
|
});
|
|
1815
1856
|
});
|
|
1816
1857
|
socket.on(socketEventNames.leaveRoom, (data) => {
|
|
1817
|
-
const
|
|
1818
|
-
const
|
|
1858
|
+
const payload = asPayload(data);
|
|
1859
|
+
const group = typeof payload.group === "string" ? payload.group.trim() : "";
|
|
1860
|
+
const responseIndex = payload.responseIndex;
|
|
1819
1861
|
if (!validateRoomRequest(socket, responseIndex, token, group, preferredLocale, buildLeaveRoomResponseEventName)) return;
|
|
1820
1862
|
if (!token) return;
|
|
1821
1863
|
void withSessionLock(token, async () => {
|
|
@@ -1840,7 +1882,7 @@ var registerRoomEvents = (ctx) => {
|
|
|
1840
1882
|
});
|
|
1841
1883
|
});
|
|
1842
1884
|
socket.on(socketEventNames.getJoinedRooms, (data) => {
|
|
1843
|
-
const responseIndex = data
|
|
1885
|
+
const { responseIndex } = asPayload(data);
|
|
1844
1886
|
if (typeof responseIndex !== "number") return;
|
|
1845
1887
|
if (!token) {
|
|
1846
1888
|
socket.emit(buildGetJoinedRoomsResponseEventName(responseIndex), {
|
|
@@ -1884,17 +1926,22 @@ var registerUpdateLocationEvent = (ctx) => {
|
|
|
1884
1926
|
const { socket, token, activityBroadcasterEnabled, locationProviderEnabled, shouldLogDev } = ctx;
|
|
1885
1927
|
socket.on(
|
|
1886
1928
|
socketEventNames.updateLocation,
|
|
1887
|
-
(
|
|
1929
|
+
(data) => {
|
|
1888
1930
|
if (!token) return;
|
|
1889
1931
|
if (!locationProviderEnabled) return;
|
|
1890
|
-
|
|
1891
|
-
if (
|
|
1892
|
-
|
|
1893
|
-
|
|
1932
|
+
const { pathName, searchParams } = asPayload(data);
|
|
1933
|
+
if (typeof pathName !== "string" || !pathName.startsWith("/") || pathName.length > 2048 || pathName.includes("\0")) return;
|
|
1934
|
+
const newLocation = { pathName };
|
|
1935
|
+
if (searchParams !== void 0) {
|
|
1936
|
+
if (typeof searchParams !== "object" || searchParams === null || Array.isArray(searchParams)) return;
|
|
1937
|
+
const entries = Object.entries(searchParams);
|
|
1894
1938
|
if (entries.length > 50) return;
|
|
1939
|
+
const validated = {};
|
|
1895
1940
|
for (const [k, v] of entries) {
|
|
1896
|
-
if (
|
|
1941
|
+
if (k.length > 256 || typeof v !== "string" || v.length > 1024) return;
|
|
1942
|
+
validated[k] = v;
|
|
1897
1943
|
}
|
|
1944
|
+
newLocation.searchParams = validated;
|
|
1898
1945
|
}
|
|
1899
1946
|
if (shouldLogDev) {
|
|
1900
1947
|
getLogger8().debug("updating location", { pathName: newLocation.pathName });
|
|
@@ -1912,7 +1959,8 @@ var registerUpdateLocationEvent = (ctx) => {
|
|
|
1912
1959
|
const extendedUser = user;
|
|
1913
1960
|
const oldLocation = extendedUser.location;
|
|
1914
1961
|
const sanitizedUser = sanitizeSessionRoomKeys({ ...extendedUser, location: newLocation });
|
|
1915
|
-
await writeSession(token, sanitizedUser);
|
|
1962
|
+
const saved = await writeSession(token, sanitizedUser, void 0, { onlyIfExists: true });
|
|
1963
|
+
if (!saved.ok) return;
|
|
1916
1964
|
void dispatchHook6("onLocationUpdate", { token, oldLocation, newLocation });
|
|
1917
1965
|
});
|
|
1918
1966
|
}
|
|
@@ -1955,6 +2003,7 @@ var rejoinPersistedRooms = (ctx) => {
|
|
|
1955
2003
|
const roomCodes = session ? getSessionRoomCodes(session) : [];
|
|
1956
2004
|
const userId = session?.id ?? null;
|
|
1957
2005
|
for (const roomCode of roomCodes) {
|
|
2006
|
+
if (roomCode === SYNC_ALL_RECEIVER) continue;
|
|
1958
2007
|
await socket.join(formatRoomName(roomCode, { purpose: "broadcast", userId }));
|
|
1959
2008
|
}
|
|
1960
2009
|
return roomCodes;
|
|
@@ -1969,7 +2018,7 @@ var rejoinPersistedRooms = (ctx) => {
|
|
|
1969
2018
|
})();
|
|
1970
2019
|
};
|
|
1971
2020
|
var loadSocket = (httpServer, options = {}) => {
|
|
1972
|
-
const config =
|
|
2021
|
+
const config = getProjectConfig14();
|
|
1973
2022
|
const shouldLogDev = config.logging.devLogs;
|
|
1974
2023
|
const shouldLogSocketStartup = config.logging.socketStartup;
|
|
1975
2024
|
const io = new SocketIOServer(httpServer, {
|
|
@@ -2014,7 +2063,14 @@ var loadSocket = (httpServer, options = {}) => {
|
|
|
2014
2063
|
void dispatchHook6("onSocketConnect", {
|
|
2015
2064
|
socketId: socket.id,
|
|
2016
2065
|
token,
|
|
2017
|
-
|
|
2066
|
+
//? Same resolution as the api/sync socket paths, so behind a trusted proxy
|
|
2067
|
+
//? the hook sees the client and not the proxy address.
|
|
2068
|
+
ip: resolveClientIp4({
|
|
2069
|
+
rawAddress: socket.handshake.address,
|
|
2070
|
+
headers: socket.handshake.headers,
|
|
2071
|
+
trustProxy: config.http.trustProxy,
|
|
2072
|
+
trustedProxyHopCount: config.http.trustedProxyHopCount
|
|
2073
|
+
})
|
|
2018
2074
|
});
|
|
2019
2075
|
const ctx = {
|
|
2020
2076
|
socket,
|
|
@@ -2040,7 +2096,7 @@ var loadSocket = (httpServer, options = {}) => {
|
|
|
2040
2096
|
import {
|
|
2041
2097
|
collectSynchronizedEnvKeys,
|
|
2042
2098
|
getLogger as getLogger9,
|
|
2043
|
-
getProjectConfig as
|
|
2099
|
+
getProjectConfig as getProjectConfig15,
|
|
2044
2100
|
isDeployConfigRegistered,
|
|
2045
2101
|
isLocalizedNormalizerRegistered,
|
|
2046
2102
|
isProjectConfigRegistered,
|
|
@@ -2107,7 +2163,7 @@ var verifyBootstrap = async (requirements = {}) => {
|
|
|
2107
2163
|
}
|
|
2108
2164
|
if (isProjectConfigRegistered()) {
|
|
2109
2165
|
const synchronizedKeyCount = collectSynchronizedEnvKeys().length;
|
|
2110
|
-
const healthHashMode =
|
|
2166
|
+
const healthHashMode = getProjectConfig15().http.healthHash.mode;
|
|
2111
2167
|
if (synchronizedKeyCount > 0 && healthHashMode === "plain") {
|
|
2112
2168
|
getLogger9().warn(
|
|
2113
2169
|
`[LuckyStack] SECURITY: /_health exposes UNSALTED sha256 fingerprints of ${String(synchronizedKeyCount)} synchronized env secret(s) by default. Set \`http.healthHash.mode\` to 'hmac' (or 'salted' with salt '@bootUuid') to stop publishing brute-forceable secret fingerprints to unauthenticated callers.`
|
|
@@ -2115,7 +2171,7 @@ var verifyBootstrap = async (requirements = {}) => {
|
|
|
2115
2171
|
}
|
|
2116
2172
|
}
|
|
2117
2173
|
if (isProjectConfigRegistered()) {
|
|
2118
|
-
const cfg =
|
|
2174
|
+
const cfg = getProjectConfig15();
|
|
2119
2175
|
if (!cfg.session.basedToken && cfg.http.sessionCookieSameSite !== "Strict") {
|
|
2120
2176
|
getLogger9().warn(
|
|
2121
2177
|
`[LuckyStack] SECURITY: http.sessionCookieSameSite is '${cfg.http.sessionCookieSameSite}', not 'Strict', but the CSRF-exempt auth-bootstrap endpoints (/auth/api/credentials, /auth/callback/*) rely on SameSite=Strict to block cross-site login-CSRF. Use 'Strict', or add your own CSRF/origin check on those routes.`
|
|
@@ -2138,7 +2194,8 @@ var verifyBootstrap = async (requirements = {}) => {
|
|
|
2138
2194
|
import {
|
|
2139
2195
|
getLogger as getLogger10,
|
|
2140
2196
|
registerRuntimeMapsProvider,
|
|
2141
|
-
isProductionRuntime as isProductionRuntime3
|
|
2197
|
+
isProductionRuntime as isProductionRuntime3,
|
|
2198
|
+
tryCatch as tryCatch9
|
|
2142
2199
|
} from "@luckystack/core";
|
|
2143
2200
|
var emptyRuntimeMaps = {
|
|
2144
2201
|
apisObject: {},
|
|
@@ -2201,6 +2258,7 @@ var mergeInto = (target, source, kind, fromPreset, keyOrigin) => {
|
|
|
2201
2258
|
var isProduction = () => isProductionRuntime3();
|
|
2202
2259
|
var createProdRuntimeMapsProvider = (options) => {
|
|
2203
2260
|
let prodMapsPromise = null;
|
|
2261
|
+
const reportedFailures = /* @__PURE__ */ new Set();
|
|
2204
2262
|
let devkitModulePromise = null;
|
|
2205
2263
|
const getDevkit = async () => {
|
|
2206
2264
|
devkitModulePromise ??= import("@luckystack/devkit");
|
|
@@ -2208,6 +2266,7 @@ var createProdRuntimeMapsProvider = (options) => {
|
|
|
2208
2266
|
};
|
|
2209
2267
|
const loadProdRuntimeMaps = async () => {
|
|
2210
2268
|
if (prodMapsPromise) return await prodMapsPromise;
|
|
2269
|
+
const loadState = { anyPresetFailed: false };
|
|
2211
2270
|
prodMapsPromise = (async () => {
|
|
2212
2271
|
const presets = resolvePresets(options);
|
|
2213
2272
|
const merged = {
|
|
@@ -2219,19 +2278,29 @@ var createProdRuntimeMapsProvider = (options) => {
|
|
|
2219
2278
|
const syncOrigin = /* @__PURE__ */ new Map();
|
|
2220
2279
|
const functionOrigin = /* @__PURE__ */ new Map();
|
|
2221
2280
|
const loadedModules = await Promise.all(
|
|
2222
|
-
presets.map(async (preset) =>
|
|
2223
|
-
preset
|
|
2224
|
-
|
|
2225
|
-
})
|
|
2281
|
+
presets.map(async (preset) => {
|
|
2282
|
+
const [loadError, mod] = await tryCatch9(() => options.loadGenerated(preset));
|
|
2283
|
+
return { preset, mod, loadError };
|
|
2284
|
+
})
|
|
2226
2285
|
);
|
|
2227
2286
|
let loadedAny = false;
|
|
2228
|
-
|
|
2287
|
+
let newFailure = false;
|
|
2288
|
+
for (const { preset, mod, loadError } of loadedModules) {
|
|
2229
2289
|
if (!mod) {
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2290
|
+
loadState.anyPresetFailed = true;
|
|
2291
|
+
if (!reportedFailures.has(preset)) {
|
|
2292
|
+
reportedFailures.add(preset);
|
|
2293
|
+
newFailure = true;
|
|
2294
|
+
getLogger10().error(
|
|
2295
|
+
`[luckystack:runtimeMaps] preset "${preset}" failed to load \u2014 skipping. Calls owned by that preset will return notFound until the generated module resolves (retried on the next request).`,
|
|
2296
|
+
loadError ?? new Error("loadGenerated returned no module")
|
|
2297
|
+
);
|
|
2298
|
+
}
|
|
2233
2299
|
continue;
|
|
2234
2300
|
}
|
|
2301
|
+
if (reportedFailures.delete(preset)) {
|
|
2302
|
+
getLogger10().info(`[luckystack:runtimeMaps] preset "${preset}" loaded after an earlier failure.`);
|
|
2303
|
+
}
|
|
2235
2304
|
loadedAny = true;
|
|
2236
2305
|
const normalized = normalizeGeneratedModule(mod, preset);
|
|
2237
2306
|
mergeInto(merged.apisObject, normalized.apisObject, "api", preset, apiOrigin);
|
|
@@ -2239,14 +2308,16 @@ var createProdRuntimeMapsProvider = (options) => {
|
|
|
2239
2308
|
mergeInto(merged.functionsObject, normalized.functionsObject, "function", preset, functionOrigin);
|
|
2240
2309
|
}
|
|
2241
2310
|
if (!loadedAny) {
|
|
2242
|
-
getLogger10().warn(
|
|
2311
|
+
if (newFailure || presets.length === 0) getLogger10().warn(
|
|
2243
2312
|
`[luckystack:runtimeMaps] no presets resolved (tried: ${presets.join(", ")}). Every api/sync request will return notFound until at least one generated module loads.`
|
|
2244
2313
|
);
|
|
2245
2314
|
return emptyRuntimeMaps;
|
|
2246
2315
|
}
|
|
2247
2316
|
return merged;
|
|
2248
2317
|
})();
|
|
2249
|
-
|
|
2318
|
+
const result = await prodMapsPromise;
|
|
2319
|
+
if (loadState.anyPresetFailed) prodMapsPromise = null;
|
|
2320
|
+
return result;
|
|
2250
2321
|
};
|
|
2251
2322
|
const getRuntimeApiMaps = async () => {
|
|
2252
2323
|
if (!isProduction()) {
|
|
@@ -2283,7 +2354,7 @@ import {
|
|
|
2283
2354
|
dispatchHook as dispatchHook7,
|
|
2284
2355
|
flushErrorTrackers,
|
|
2285
2356
|
getLogger as getLogger11,
|
|
2286
|
-
tryCatch as
|
|
2357
|
+
tryCatch as tryCatch10
|
|
2287
2358
|
} from "@luckystack/core";
|
|
2288
2359
|
var DEFAULT_SHUTDOWN_TIMEOUT_MS = 1e4;
|
|
2289
2360
|
var withTimeout = async (label, timeoutMs, fn) => {
|
|
@@ -2296,7 +2367,7 @@ var withTimeout = async (label, timeoutMs, fn) => {
|
|
|
2296
2367
|
timer.unref();
|
|
2297
2368
|
});
|
|
2298
2369
|
const run = (async () => {
|
|
2299
|
-
const [error] = await
|
|
2370
|
+
const [error] = await tryCatch10(fn);
|
|
2300
2371
|
if (error) getLogger11().warn(`[shutdown] step "${label}" failed: ${error.message}`);
|
|
2301
2372
|
return true;
|
|
2302
2373
|
})();
|
|
@@ -2312,7 +2383,7 @@ var closeHttpServer = (httpServer) => new Promise((resolve, reject) => {
|
|
|
2312
2383
|
});
|
|
2313
2384
|
var closeIoServer = (ioServer) => new Promise((resolve, reject) => {
|
|
2314
2385
|
void ioServer.close((error) => {
|
|
2315
|
-
if (error) reject(error);
|
|
2386
|
+
if (error && error.code !== "ERR_SERVER_NOT_RUNNING") reject(error);
|
|
2316
2387
|
else resolve();
|
|
2317
2388
|
});
|
|
2318
2389
|
});
|
|
@@ -2366,9 +2437,9 @@ var clearDevServerInfo = () => {
|
|
|
2366
2437
|
};
|
|
2367
2438
|
|
|
2368
2439
|
// src/afterListen.ts
|
|
2369
|
-
import { getLogger as getLogger13, tryCatch as
|
|
2440
|
+
import { getLogger as getLogger13, tryCatch as tryCatch11 } from "@luckystack/core";
|
|
2370
2441
|
var runAfterListenTask = async (task, { fatal = false, label = "post-listen task" } = {}) => {
|
|
2371
|
-
const [error] = await
|
|
2442
|
+
const [error] = await tryCatch11(async () => task());
|
|
2372
2443
|
if (!error) return;
|
|
2373
2444
|
if (fatal) throw error;
|
|
2374
2445
|
getLogger13().error(
|
|
@@ -2403,7 +2474,7 @@ var initDevTools = async () => {
|
|
|
2403
2474
|
);
|
|
2404
2475
|
return;
|
|
2405
2476
|
}
|
|
2406
|
-
const [devkitError] = await
|
|
2477
|
+
const [devkitError] = await tryCatch12(async () => {
|
|
2407
2478
|
const devkit = await import(devkitModuleId);
|
|
2408
2479
|
await devkit.initializeAll();
|
|
2409
2480
|
devkit.setupWatchers();
|
|
@@ -2428,6 +2499,7 @@ var listenLuckyStackServer = (httpServer, ip, port, callback) => new Promise((re
|
|
|
2428
2499
|
else autoIncrement = !production;
|
|
2429
2500
|
const tryListen = (attemptPort) => {
|
|
2430
2501
|
const onError = (err) => {
|
|
2502
|
+
httpServer.off("listening", onListening);
|
|
2431
2503
|
if (err.code !== "EADDRINUSE") {
|
|
2432
2504
|
reject(err);
|
|
2433
2505
|
return;
|
|
@@ -2452,8 +2524,7 @@ var listenLuckyStackServer = (httpServer, ip, port, callback) => new Promise((re
|
|
|
2452
2524
|
);
|
|
2453
2525
|
reject(err);
|
|
2454
2526
|
};
|
|
2455
|
-
|
|
2456
|
-
httpServer.listen(attemptPort, ip, () => {
|
|
2527
|
+
function onListening() {
|
|
2457
2528
|
httpServer.off("error", onError);
|
|
2458
2529
|
const address = httpServer.address();
|
|
2459
2530
|
const boundPort = typeof address === "object" && address !== null ? address.port : attemptPort;
|
|
@@ -2463,7 +2534,7 @@ var listenLuckyStackServer = (httpServer, ip, port, callback) => new Promise((re
|
|
|
2463
2534
|
process.once("exit", clearDevServerInfo);
|
|
2464
2535
|
}
|
|
2465
2536
|
if (attemptPort !== startPort && !production) {
|
|
2466
|
-
const configuredCallbackBase = tryCatchSync3(() =>
|
|
2537
|
+
const configuredCallbackBase = tryCatchSync3(() => getProjectConfig16().oauthCallbackBase)[1] ?? "";
|
|
2467
2538
|
const callbackPort = tryCatchSync3(() => {
|
|
2468
2539
|
const callbackUrl = new URL(configuredCallbackBase);
|
|
2469
2540
|
return callbackUrl.port || (callbackUrl.protocol === "https:" ? "443" : "80");
|
|
@@ -2474,13 +2545,16 @@ var listenLuckyStackServer = (httpServer, ip, port, callback) => new Promise((re
|
|
|
2474
2545
|
);
|
|
2475
2546
|
}
|
|
2476
2547
|
}
|
|
2477
|
-
const config =
|
|
2548
|
+
const config = getProjectConfig16();
|
|
2478
2549
|
if (config.logging.socketStartup || config.logging.devLogs) {
|
|
2479
2550
|
getLogger14().info(`Server is running on http://${ip}:${String(boundPort)}/`);
|
|
2480
2551
|
}
|
|
2481
2552
|
callback?.();
|
|
2482
2553
|
resolve(httpServer);
|
|
2483
|
-
}
|
|
2554
|
+
}
|
|
2555
|
+
httpServer.once("error", onError);
|
|
2556
|
+
httpServer.once("listening", onListening);
|
|
2557
|
+
httpServer.listen(attemptPort, ip);
|
|
2484
2558
|
};
|
|
2485
2559
|
tryListen(startPort);
|
|
2486
2560
|
});
|
|
@@ -2507,7 +2581,7 @@ var createLuckyStackServer = async (options = {}) => {
|
|
|
2507
2581
|
if (enableDevTools) {
|
|
2508
2582
|
await initDevTools();
|
|
2509
2583
|
}
|
|
2510
|
-
const [bootUuidError] = await
|
|
2584
|
+
const [bootUuidError] = await tryCatch12(() => writeBootUuid());
|
|
2511
2585
|
if (bootUuidError) {
|
|
2512
2586
|
throw new Error(
|
|
2513
2587
|
"Failed to write the boot UUID to Redis. Check REDIS_HOST / REDIS_PORT / REDIS_USER / REDIS_PASSWORD and that Redis is reachable.",
|
|
@@ -2557,7 +2631,7 @@ var createLuckyStackServer = async (options = {}) => {
|
|
|
2557
2631
|
import path3 from "path";
|
|
2558
2632
|
import fs2 from "fs";
|
|
2559
2633
|
import { pathToFileURL } from "url";
|
|
2560
|
-
import { ROOT_DIR, tryCatch as
|
|
2634
|
+
import { ROOT_DIR, tryCatch as tryCatch13, getLogger as getLogger15, isTestFile, isTestDirectory } from "@luckystack/core";
|
|
2561
2635
|
var OVERLAY_ORDER = [
|
|
2562
2636
|
// Core registries first — clients, paths, routing rules. Anything below
|
|
2563
2637
|
// depends on these being in place.
|
|
@@ -2582,7 +2656,7 @@ var OVERLAY_ORDER = [
|
|
|
2582
2656
|
];
|
|
2583
2657
|
var importIfExists = async (filePath) => {
|
|
2584
2658
|
if (!fs2.existsSync(filePath)) return;
|
|
2585
|
-
const [error] = await
|
|
2659
|
+
const [error] = await tryCatch13(async () => {
|
|
2586
2660
|
await import(pathToFileURL(filePath).href);
|
|
2587
2661
|
});
|
|
2588
2662
|
if (error) {
|