@rpcbase/server 0.441.0 → 0.443.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/dist/applyRouteLoaders.d.ts +4 -0
- package/dist/applyRouteLoaders.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +557 -29
- package/dist/initServer.d.ts +4 -2
- package/dist/initServer.d.ts.map +1 -1
- package/dist/renderSSR.d.ts +8 -0
- package/dist/renderSSR.d.ts.map +1 -0
- package/dist/ssrMiddleware.d.ts +13 -0
- package/dist/ssrMiddleware.d.ts.map +1 -0
- package/package.json +6 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"applyRouteLoaders.d.ts","sourceRoot":"","sources":["../src/applyRouteLoaders.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,OAAO,EAAC,MAAM,SAAS,CAAA;AAC/B,OAAO,EACL,oBAAoB,EAMrB,MAAM,iBAAiB,CAAA;AAsDxB,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,OAAO,EACZ,UAAU,EAAE,GAAG,EAAE,GAChB,OAAO,CAAC,oBAAoB,CAAC,CAkG/B"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,gBAAgB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,iBAAiB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,191 @@
|
|
|
1
1
|
import session from "express-session";
|
|
2
2
|
import { RedisStore } from "connect-redis";
|
|
3
|
+
import MongoStore from "connect-mongo";
|
|
3
4
|
import { createClient } from "redis";
|
|
4
|
-
import requestIp from "request-ip";
|
|
5
5
|
import env from "@rpcbase/env";
|
|
6
|
-
import { initApiClient } from "@rpcbase/client";
|
|
6
|
+
import { initApiClient, SsrErrorFallback, SSR_ERROR_STATE_GLOBAL_KEY, serializeSsrErrorState } from "@rpcbase/client";
|
|
7
7
|
import assert from "assert";
|
|
8
8
|
import { hkdfSync, scrypt } from "crypto";
|
|
9
9
|
import { createProxyMiddleware } from "http-proxy-middleware";
|
|
10
|
+
import fs from "node:fs/promises";
|
|
11
|
+
import { readFileSync } from "node:fs";
|
|
12
|
+
import { Transform } from "node:stream";
|
|
13
|
+
import { StrictMode, createElement } from "react";
|
|
14
|
+
import { renderToPipeableStream, renderToStaticMarkup } from "react-dom/server";
|
|
15
|
+
import { jsx } from "react/jsx-runtime";
|
|
16
|
+
import { createPath, matchRoutes, parsePath, createStaticRouter, StaticRouterProvider } from "@rpcbase/router";
|
|
17
|
+
function getDefaultExportFromCjs(x) {
|
|
18
|
+
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
|
|
19
|
+
}
|
|
20
|
+
var is_1;
|
|
21
|
+
var hasRequiredIs;
|
|
22
|
+
function requireIs() {
|
|
23
|
+
if (hasRequiredIs) return is_1;
|
|
24
|
+
hasRequiredIs = 1;
|
|
25
|
+
var regexes = {
|
|
26
|
+
ipv4: /^(?:(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.){3}(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])$/,
|
|
27
|
+
ipv6: /^((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\3)::|:\b|$))|(?!\2\3)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i
|
|
28
|
+
};
|
|
29
|
+
function not(func) {
|
|
30
|
+
return function() {
|
|
31
|
+
return !func.apply(null, Array.prototype.slice.call(arguments));
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function existy(value) {
|
|
35
|
+
return value != null;
|
|
36
|
+
}
|
|
37
|
+
function ip(value) {
|
|
38
|
+
return existy(value) && regexes.ipv4.test(value) || regexes.ipv6.test(value);
|
|
39
|
+
}
|
|
40
|
+
function object(value) {
|
|
41
|
+
return Object(value) === value;
|
|
42
|
+
}
|
|
43
|
+
function string(value) {
|
|
44
|
+
return Object.prototype.toString.call(value) === "[object String]";
|
|
45
|
+
}
|
|
46
|
+
var is = {
|
|
47
|
+
existy,
|
|
48
|
+
ip,
|
|
49
|
+
object,
|
|
50
|
+
string,
|
|
51
|
+
not: {
|
|
52
|
+
existy: not(existy),
|
|
53
|
+
ip: not(ip),
|
|
54
|
+
object: not(object),
|
|
55
|
+
string: not(string)
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
is_1 = is;
|
|
59
|
+
return is_1;
|
|
60
|
+
}
|
|
61
|
+
var lib;
|
|
62
|
+
var hasRequiredLib;
|
|
63
|
+
function requireLib() {
|
|
64
|
+
if (hasRequiredLib) return lib;
|
|
65
|
+
hasRequiredLib = 1;
|
|
66
|
+
function _typeof(obj) {
|
|
67
|
+
"@babel/helpers - typeof";
|
|
68
|
+
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(obj2) {
|
|
69
|
+
return typeof obj2;
|
|
70
|
+
} : function(obj2) {
|
|
71
|
+
return obj2 && "function" == typeof Symbol && obj2.constructor === Symbol && obj2 !== Symbol.prototype ? "symbol" : typeof obj2;
|
|
72
|
+
}, _typeof(obj);
|
|
73
|
+
}
|
|
74
|
+
var is = requireIs();
|
|
75
|
+
function getClientIpFromXForwardedFor(value) {
|
|
76
|
+
if (!is.existy(value)) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
if (is.not.string(value)) {
|
|
80
|
+
throw new TypeError('Expected a string, got "'.concat(_typeof(value), '"'));
|
|
81
|
+
}
|
|
82
|
+
var forwardedIps = value.split(",").map(function(e) {
|
|
83
|
+
var ip = e.trim();
|
|
84
|
+
if (ip.includes(":")) {
|
|
85
|
+
var splitted = ip.split(":");
|
|
86
|
+
if (splitted.length === 2) {
|
|
87
|
+
return splitted[0];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return ip;
|
|
91
|
+
});
|
|
92
|
+
for (var i = 0; i < forwardedIps.length; i++) {
|
|
93
|
+
if (is.ip(forwardedIps[i])) {
|
|
94
|
+
return forwardedIps[i];
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
function getClientIp(req) {
|
|
100
|
+
if (req.headers) {
|
|
101
|
+
if (is.ip(req.headers["x-client-ip"])) {
|
|
102
|
+
return req.headers["x-client-ip"];
|
|
103
|
+
}
|
|
104
|
+
var xForwardedFor = getClientIpFromXForwardedFor(req.headers["x-forwarded-for"]);
|
|
105
|
+
if (is.ip(xForwardedFor)) {
|
|
106
|
+
return xForwardedFor;
|
|
107
|
+
}
|
|
108
|
+
if (is.ip(req.headers["cf-connecting-ip"])) {
|
|
109
|
+
return req.headers["cf-connecting-ip"];
|
|
110
|
+
}
|
|
111
|
+
if (is.ip(req.headers["fastly-client-ip"])) {
|
|
112
|
+
return req.headers["fastly-client-ip"];
|
|
113
|
+
}
|
|
114
|
+
if (is.ip(req.headers["true-client-ip"])) {
|
|
115
|
+
return req.headers["true-client-ip"];
|
|
116
|
+
}
|
|
117
|
+
if (is.ip(req.headers["x-real-ip"])) {
|
|
118
|
+
return req.headers["x-real-ip"];
|
|
119
|
+
}
|
|
120
|
+
if (is.ip(req.headers["x-cluster-client-ip"])) {
|
|
121
|
+
return req.headers["x-cluster-client-ip"];
|
|
122
|
+
}
|
|
123
|
+
if (is.ip(req.headers["x-forwarded"])) {
|
|
124
|
+
return req.headers["x-forwarded"];
|
|
125
|
+
}
|
|
126
|
+
if (is.ip(req.headers["forwarded-for"])) {
|
|
127
|
+
return req.headers["forwarded-for"];
|
|
128
|
+
}
|
|
129
|
+
if (is.ip(req.headers.forwarded)) {
|
|
130
|
+
return req.headers.forwarded;
|
|
131
|
+
}
|
|
132
|
+
if (is.ip(req.headers["x-appengine-user-ip"])) {
|
|
133
|
+
return req.headers["x-appengine-user-ip"];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (is.existy(req.connection)) {
|
|
137
|
+
if (is.ip(req.connection.remoteAddress)) {
|
|
138
|
+
return req.connection.remoteAddress;
|
|
139
|
+
}
|
|
140
|
+
if (is.existy(req.connection.socket) && is.ip(req.connection.socket.remoteAddress)) {
|
|
141
|
+
return req.connection.socket.remoteAddress;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (is.existy(req.socket) && is.ip(req.socket.remoteAddress)) {
|
|
145
|
+
return req.socket.remoteAddress;
|
|
146
|
+
}
|
|
147
|
+
if (is.existy(req.info) && is.ip(req.info.remoteAddress)) {
|
|
148
|
+
return req.info.remoteAddress;
|
|
149
|
+
}
|
|
150
|
+
if (is.existy(req.requestContext) && is.existy(req.requestContext.identity) && is.ip(req.requestContext.identity.sourceIp)) {
|
|
151
|
+
return req.requestContext.identity.sourceIp;
|
|
152
|
+
}
|
|
153
|
+
if (req.headers) {
|
|
154
|
+
if (is.ip(req.headers["Cf-Pseudo-IPv4"])) {
|
|
155
|
+
return req.headers["Cf-Pseudo-IPv4"];
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (is.existy(req.raw)) {
|
|
159
|
+
return getClientIp(req.raw);
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
function mw(options) {
|
|
164
|
+
var configuration = is.not.existy(options) ? {} : options;
|
|
165
|
+
if (is.not.object(configuration)) {
|
|
166
|
+
throw new TypeError("Options must be an object!");
|
|
167
|
+
}
|
|
168
|
+
var attributeName = configuration.attributeName || "clientIp";
|
|
169
|
+
return function(req, res, next) {
|
|
170
|
+
var ip = getClientIp(req);
|
|
171
|
+
Object.defineProperty(req, attributeName, {
|
|
172
|
+
get: function get() {
|
|
173
|
+
return ip;
|
|
174
|
+
},
|
|
175
|
+
configurable: true
|
|
176
|
+
});
|
|
177
|
+
next();
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
lib = {
|
|
181
|
+
getClientIpFromXForwardedFor,
|
|
182
|
+
getClientIp,
|
|
183
|
+
mw
|
|
184
|
+
};
|
|
185
|
+
return lib;
|
|
186
|
+
}
|
|
187
|
+
var libExports = requireLib();
|
|
188
|
+
const requestIp = /* @__PURE__ */ getDefaultExportFromCjs(libExports);
|
|
10
189
|
const getDerivedKey = (masterKey, info, length = 32, salt = "") => {
|
|
11
190
|
assert(masterKey?.length >= 32, "MASTER_KEY must be 32 chars or longer.");
|
|
12
191
|
return Buffer.from(hkdfSync(
|
|
@@ -39,27 +218,33 @@ process.env = {
|
|
|
39
218
|
...__rb_env__,
|
|
40
219
|
...process.env
|
|
41
220
|
};
|
|
42
|
-
const isProduction = process.env.NODE_ENV === "production";
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
app.use((req, res, next) => {
|
|
49
|
-
if (req.headers.host?.startsWith("www.")) {
|
|
50
|
-
const newHost = req.headers.host.replace("www.", "");
|
|
51
|
-
return res.redirect(301, `${req.protocol}://${newHost}${req.originalUrl}`);
|
|
52
|
-
}
|
|
53
|
-
next();
|
|
54
|
-
});
|
|
55
|
-
metricsIngestProxyMiddleware(app);
|
|
56
|
-
if (!serverEnv.REDIS_URL) {
|
|
57
|
-
console.log("WARNING", "missing REDIS_URL, will skip session storage middleware");
|
|
58
|
-
return;
|
|
59
|
-
} else {
|
|
60
|
-
console.log("REDIS_URL:", serverEnv.REDIS_URL);
|
|
221
|
+
const isProduction$1 = process.env.NODE_ENV === "production";
|
|
222
|
+
const SESSION_MAX_AGE_S = 3600 * 24 * 60;
|
|
223
|
+
const getMongoUrl = (serverEnv) => {
|
|
224
|
+
const explicitUrl = serverEnv.MONGODB_URL || serverEnv.MONGO_URL || serverEnv.MONGODB_URI || serverEnv.DB_URL;
|
|
225
|
+
if (explicitUrl) {
|
|
226
|
+
return explicitUrl;
|
|
61
227
|
}
|
|
62
|
-
|
|
228
|
+
if (serverEnv.DB_PORT) {
|
|
229
|
+
const host = serverEnv.DB_HOST ?? "localhost";
|
|
230
|
+
const dbName = serverEnv.APP_NAME ?? "rb";
|
|
231
|
+
return `mongodb://${host}:${serverEnv.DB_PORT}/${dbName}-sessions`;
|
|
232
|
+
}
|
|
233
|
+
return void 0;
|
|
234
|
+
};
|
|
235
|
+
const createMongoSessionStore = (serverEnv) => {
|
|
236
|
+
const mongoUrl = getMongoUrl(serverEnv);
|
|
237
|
+
if (!mongoUrl) {
|
|
238
|
+
throw new Error("Missing REDIS_URL and Mongo connection details (MONGODB_URL/MONGO_URL/MONGODB_URI/DB_PORT)");
|
|
239
|
+
}
|
|
240
|
+
console.log("Using MongoDB session store");
|
|
241
|
+
return MongoStore.create({
|
|
242
|
+
mongoUrl,
|
|
243
|
+
collectionName: "sessions",
|
|
244
|
+
ttl: SESSION_MAX_AGE_S
|
|
245
|
+
});
|
|
246
|
+
};
|
|
247
|
+
const createRedisSessionStore = async (redisUrl) => {
|
|
63
248
|
const reconnectStrategy = (retries) => {
|
|
64
249
|
console.log("redis_client::rb/server reconnectStrategy::retrying with arg", retries);
|
|
65
250
|
if (retries < 5) {
|
|
@@ -70,7 +255,7 @@ const initServer = async (app, serverEnv) => {
|
|
|
70
255
|
}
|
|
71
256
|
};
|
|
72
257
|
const redisClient = createClient({
|
|
73
|
-
url:
|
|
258
|
+
url: redisUrl,
|
|
74
259
|
socket: {
|
|
75
260
|
reconnectStrategy,
|
|
76
261
|
connectTimeout: 1e4,
|
|
@@ -83,20 +268,44 @@ const initServer = async (app, serverEnv) => {
|
|
|
83
268
|
redisClient.on("error", (error) => {
|
|
84
269
|
console.log("session-storage::redis_client ERROR", error);
|
|
85
270
|
});
|
|
86
|
-
|
|
271
|
+
console.log("Using Redis session store");
|
|
272
|
+
await redisClient.connect();
|
|
273
|
+
return new RedisStore({
|
|
274
|
+
client: redisClient,
|
|
275
|
+
ttl: SESSION_MAX_AGE_S
|
|
276
|
+
});
|
|
277
|
+
};
|
|
278
|
+
const initServer = async (app, serverEnv) => {
|
|
279
|
+
await initApiClient({ app });
|
|
280
|
+
app.disable("x-powered-by");
|
|
281
|
+
app.set("trust proxy", true);
|
|
282
|
+
app.use(requestIp.mw());
|
|
283
|
+
app.use((req, res, next) => {
|
|
284
|
+
if (req.headers.host?.startsWith("www.")) {
|
|
285
|
+
const newHost = req.headers.host.replace("www.", "");
|
|
286
|
+
return res.redirect(301, `${req.protocol}://${newHost}${req.originalUrl}`);
|
|
287
|
+
}
|
|
288
|
+
next();
|
|
289
|
+
});
|
|
290
|
+
metricsIngestProxyMiddleware(app);
|
|
291
|
+
if (!serverEnv.MASTER_KEY) {
|
|
292
|
+
throw new Error("MASTER_KEY must be defined to derive the session secret");
|
|
293
|
+
}
|
|
294
|
+
const sessionSecret = getDerivedKey(serverEnv.MASTER_KEY, "express_session_key");
|
|
295
|
+
const redisUrl = serverEnv.REDIS_URL?.trim();
|
|
296
|
+
const store = redisUrl ? await createRedisSessionStore(redisUrl) : createMongoSessionStore(serverEnv);
|
|
87
297
|
const sessionConfig = {
|
|
88
298
|
name: "session",
|
|
89
|
-
store
|
|
299
|
+
store,
|
|
90
300
|
proxy: true,
|
|
91
301
|
resave: false,
|
|
92
302
|
saveUninitialized: false,
|
|
93
303
|
secret: sessionSecret,
|
|
94
304
|
cookie: {
|
|
95
|
-
maxAge:
|
|
96
|
-
// 60 days
|
|
305
|
+
maxAge: SESSION_MAX_AGE_S * 1e3
|
|
97
306
|
}
|
|
98
307
|
};
|
|
99
|
-
if (isProduction) {
|
|
308
|
+
if (isProduction$1) {
|
|
100
309
|
sessionConfig.cookie.secure = true;
|
|
101
310
|
}
|
|
102
311
|
app.use(session(sessionConfig));
|
|
@@ -122,8 +331,327 @@ async function hashPassword(password, salt) {
|
|
|
122
331
|
});
|
|
123
332
|
return derivedKey;
|
|
124
333
|
}
|
|
334
|
+
function createLocation(current, to, state = null, key) {
|
|
335
|
+
const location = {
|
|
336
|
+
pathname: current,
|
|
337
|
+
search: "",
|
|
338
|
+
hash: "",
|
|
339
|
+
...typeof to === "string" ? parsePath(to) : to,
|
|
340
|
+
state,
|
|
341
|
+
// TODO: This could be cleaned up. push/replace should probably just take
|
|
342
|
+
// full Locations now and avoid the need to run through this flow at all
|
|
343
|
+
// But that's a pretty big refactor to the current test suite so going to
|
|
344
|
+
// keep as is for the time being and just let any incoming keys take precedence
|
|
345
|
+
key: to && to.key || key
|
|
346
|
+
};
|
|
347
|
+
return location;
|
|
348
|
+
}
|
|
349
|
+
function getShortCircuitMatches(routes) {
|
|
350
|
+
const route = routes.length === 1 ? routes[0] : routes.find((r) => r.index || !r.path || r.path === "/") || {
|
|
351
|
+
id: "__shim-error-route__"
|
|
352
|
+
};
|
|
353
|
+
return {
|
|
354
|
+
matches: [
|
|
355
|
+
{
|
|
356
|
+
params: {},
|
|
357
|
+
pathname: "",
|
|
358
|
+
pathnameBase: "",
|
|
359
|
+
route
|
|
360
|
+
}
|
|
361
|
+
],
|
|
362
|
+
route
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
async function applyRouteLoaders(req, dataRoutes) {
|
|
366
|
+
const baseUrl = `${req.protocol}://${req.get("host")}`;
|
|
367
|
+
const url = new URL(req.originalUrl, baseUrl);
|
|
368
|
+
const method = req.method;
|
|
369
|
+
const location = createLocation("", createPath(url), null, "default");
|
|
370
|
+
const baseContext = {
|
|
371
|
+
basename: "",
|
|
372
|
+
location,
|
|
373
|
+
loaderHeaders: {},
|
|
374
|
+
actionHeaders: {}
|
|
375
|
+
};
|
|
376
|
+
const matches = matchRoutes(dataRoutes, location) || [];
|
|
377
|
+
if (!matches) {
|
|
378
|
+
const error = {
|
|
379
|
+
status: 404,
|
|
380
|
+
message: `No route matches URL: ${req.originalUrl}`
|
|
381
|
+
};
|
|
382
|
+
const { matches: notFoundMatches, route } = getShortCircuitMatches(dataRoutes);
|
|
383
|
+
return {
|
|
384
|
+
...baseContext,
|
|
385
|
+
matches: notFoundMatches,
|
|
386
|
+
loaderData: {},
|
|
387
|
+
actionData: null,
|
|
388
|
+
errors: { [route.id]: error },
|
|
389
|
+
statusCode: 404
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
if (method !== "GET") {
|
|
393
|
+
return {
|
|
394
|
+
...baseContext,
|
|
395
|
+
matches,
|
|
396
|
+
loaderData: {},
|
|
397
|
+
actionData: null,
|
|
398
|
+
errors: null,
|
|
399
|
+
statusCode: 200
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
const loaderPromisesResults = await Promise.allSettled(
|
|
403
|
+
matches.map(async (match) => {
|
|
404
|
+
const { route, params } = match;
|
|
405
|
+
if (!route.loader) return null;
|
|
406
|
+
try {
|
|
407
|
+
return {
|
|
408
|
+
id: route.id,
|
|
409
|
+
data: await route.loader({
|
|
410
|
+
params,
|
|
411
|
+
ctx: { req }
|
|
412
|
+
})
|
|
413
|
+
};
|
|
414
|
+
} catch (error) {
|
|
415
|
+
throw { id: route.id, reason: error };
|
|
416
|
+
}
|
|
417
|
+
})
|
|
418
|
+
);
|
|
419
|
+
const loaderData = {};
|
|
420
|
+
let errors = null;
|
|
421
|
+
for (const result of loaderPromisesResults) {
|
|
422
|
+
if (result.status === "fulfilled") {
|
|
423
|
+
if (result.value) {
|
|
424
|
+
loaderData[result.value.id] = result.value.data;
|
|
425
|
+
}
|
|
426
|
+
} else if (result.status === "rejected") {
|
|
427
|
+
const id = result.reason?.id;
|
|
428
|
+
if (!id) {
|
|
429
|
+
throw new Error(`missing route ID in error: ${result.reason}`);
|
|
430
|
+
}
|
|
431
|
+
if (!errors) {
|
|
432
|
+
errors = {};
|
|
433
|
+
}
|
|
434
|
+
errors[id] = result.reason;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return {
|
|
438
|
+
...baseContext,
|
|
439
|
+
matches,
|
|
440
|
+
loaderData,
|
|
441
|
+
actionData: null,
|
|
442
|
+
errors,
|
|
443
|
+
statusCode: Object.keys(errors || {}).length > 0 ? 500 : 200
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
async function renderSSR(req, dataRoutes) {
|
|
447
|
+
let routerContext;
|
|
448
|
+
try {
|
|
449
|
+
routerContext = await applyRouteLoaders(req, dataRoutes);
|
|
450
|
+
} catch (err) {
|
|
451
|
+
console.log(err);
|
|
452
|
+
throw err;
|
|
453
|
+
}
|
|
454
|
+
if (routerContext.errors) {
|
|
455
|
+
console.log("SERVER ERRORS", routerContext.errors);
|
|
456
|
+
}
|
|
457
|
+
const router = createStaticRouter(dataRoutes, routerContext);
|
|
458
|
+
const element = /* @__PURE__ */ jsx(StrictMode, { children: /* @__PURE__ */ jsx(
|
|
459
|
+
StaticRouterProvider,
|
|
460
|
+
{
|
|
461
|
+
router,
|
|
462
|
+
context: routerContext
|
|
463
|
+
}
|
|
464
|
+
) });
|
|
465
|
+
const isMatched = routerContext.matches.length > 0;
|
|
466
|
+
return { element, isMatched };
|
|
467
|
+
}
|
|
468
|
+
const ABORT_DELAY_MS = 1e4;
|
|
469
|
+
const APP_HTML_PLACEHOLDER = "<!--app-html-->";
|
|
470
|
+
const DEFAULT_SERVER_ERROR_MESSAGE = "We couldn't render this page on the server. Please refresh and try again.";
|
|
471
|
+
const isProduction = env.NODE_ENV === "production";
|
|
472
|
+
const templateHtml = isProduction ? readFileSync("./build/dist/client/src/client/index.html", "utf-8") : "";
|
|
473
|
+
const formatErrorDetails = (error) => {
|
|
474
|
+
if (isProduction) return void 0;
|
|
475
|
+
if (!error) return void 0;
|
|
476
|
+
if (error instanceof Error) {
|
|
477
|
+
return error.stack || error.message;
|
|
478
|
+
}
|
|
479
|
+
if (typeof error === "string") {
|
|
480
|
+
return error;
|
|
481
|
+
}
|
|
482
|
+
try {
|
|
483
|
+
return JSON.stringify(error, null, 2);
|
|
484
|
+
} catch {
|
|
485
|
+
return void 0;
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
const formatPlainErrorBody = (error) => {
|
|
489
|
+
if (isProduction) {
|
|
490
|
+
return "Server error";
|
|
491
|
+
}
|
|
492
|
+
if (error instanceof Error) {
|
|
493
|
+
return error.stack || error.message;
|
|
494
|
+
}
|
|
495
|
+
if (typeof error === "string") {
|
|
496
|
+
return error;
|
|
497
|
+
}
|
|
498
|
+
try {
|
|
499
|
+
return JSON.stringify(error, null, 2);
|
|
500
|
+
} catch {
|
|
501
|
+
return "Unknown error";
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
const sendErrorResponse = ({
|
|
505
|
+
res,
|
|
506
|
+
htmlStart,
|
|
507
|
+
htmlEnd,
|
|
508
|
+
error,
|
|
509
|
+
status = 500,
|
|
510
|
+
title = "Server rendering error",
|
|
511
|
+
message = DEFAULT_SERVER_ERROR_MESSAGE,
|
|
512
|
+
errorExtraComponent
|
|
513
|
+
}) => {
|
|
514
|
+
if (res.headersSent) return;
|
|
515
|
+
if (!htmlStart || !htmlEnd) {
|
|
516
|
+
res.status(status).end(formatPlainErrorBody(error));
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
res.status(status);
|
|
520
|
+
res.set({ "Content-Type": "text/html" });
|
|
521
|
+
const details = formatErrorDetails(error);
|
|
522
|
+
const state = {
|
|
523
|
+
statusCode: status,
|
|
524
|
+
title,
|
|
525
|
+
message,
|
|
526
|
+
details
|
|
527
|
+
};
|
|
528
|
+
const markup = renderToStaticMarkup(
|
|
529
|
+
createElement(
|
|
530
|
+
StrictMode,
|
|
531
|
+
null,
|
|
532
|
+
createElement(SsrErrorFallback, {
|
|
533
|
+
state,
|
|
534
|
+
renderErrorExtra: (payload) => createElement(errorExtraComponent, { state: payload })
|
|
535
|
+
})
|
|
536
|
+
)
|
|
537
|
+
);
|
|
538
|
+
const serializedState = `<script>window.${SSR_ERROR_STATE_GLOBAL_KEY}=${serializeSsrErrorState({
|
|
539
|
+
...state,
|
|
540
|
+
details: isProduction ? void 0 : details
|
|
541
|
+
})};<\/script>`;
|
|
542
|
+
res.end(`${htmlStart}${markup}${serializedState}${htmlEnd}`);
|
|
543
|
+
};
|
|
544
|
+
const ssrMiddleware = ({
|
|
545
|
+
viteInstance,
|
|
546
|
+
dataRoutes,
|
|
547
|
+
errorExtraComponent,
|
|
548
|
+
renderTemplateStart
|
|
549
|
+
}) => async (req, res, next) => {
|
|
550
|
+
let template;
|
|
551
|
+
let htmlStart = null;
|
|
552
|
+
let htmlEnd = null;
|
|
553
|
+
let responseCommitted = false;
|
|
554
|
+
const finalizeWithErrorPage = (error, status = 500, message = DEFAULT_SERVER_ERROR_MESSAGE) => {
|
|
555
|
+
if (responseCommitted) return;
|
|
556
|
+
sendErrorResponse({
|
|
557
|
+
res,
|
|
558
|
+
htmlStart,
|
|
559
|
+
htmlEnd,
|
|
560
|
+
error,
|
|
561
|
+
status,
|
|
562
|
+
message,
|
|
563
|
+
errorExtraComponent
|
|
564
|
+
});
|
|
565
|
+
responseCommitted = true;
|
|
566
|
+
};
|
|
567
|
+
try {
|
|
568
|
+
const base = "/";
|
|
569
|
+
const url = req.originalUrl.replace(base, "");
|
|
570
|
+
if (isProduction) {
|
|
571
|
+
template = templateHtml;
|
|
572
|
+
} else {
|
|
573
|
+
template = await fs.readFile("./src/client/index.html", "utf-8");
|
|
574
|
+
template = await viteInstance.transformIndexHtml(url, template);
|
|
575
|
+
}
|
|
576
|
+
const placeholderIndex = template.indexOf(APP_HTML_PLACEHOLDER);
|
|
577
|
+
if (placeholderIndex === -1) {
|
|
578
|
+
throw new Error(`Template missing ${APP_HTML_PLACEHOLDER} placeholder`);
|
|
579
|
+
}
|
|
580
|
+
const templateStart = template.slice(0, placeholderIndex);
|
|
581
|
+
if (renderTemplateStart) {
|
|
582
|
+
htmlStart = await renderTemplateStart(req, templateStart);
|
|
583
|
+
} else {
|
|
584
|
+
htmlStart = templateStart;
|
|
585
|
+
}
|
|
586
|
+
htmlEnd = template.slice(placeholderIndex + APP_HTML_PLACEHOLDER.length);
|
|
587
|
+
const { element, isMatched } = await renderSSR(req, dataRoutes);
|
|
588
|
+
if (!isMatched) {
|
|
589
|
+
next();
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
let didError = false;
|
|
593
|
+
const { pipe, abort } = renderToPipeableStream(
|
|
594
|
+
element,
|
|
595
|
+
{
|
|
596
|
+
onShellError(error) {
|
|
597
|
+
if (error instanceof Error) {
|
|
598
|
+
viteInstance?.ssrFixStacktrace(error);
|
|
599
|
+
}
|
|
600
|
+
console.error("SSR shell error", error);
|
|
601
|
+
finalizeWithErrorPage(error);
|
|
602
|
+
abort();
|
|
603
|
+
},
|
|
604
|
+
onError(error) {
|
|
605
|
+
didError = true;
|
|
606
|
+
if (error instanceof Error) {
|
|
607
|
+
viteInstance?.ssrFixStacktrace(error);
|
|
608
|
+
}
|
|
609
|
+
console.error("SSR rendering error", error);
|
|
610
|
+
},
|
|
611
|
+
onShellReady() {
|
|
612
|
+
if (responseCommitted) {
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
if (!htmlStart || !htmlEnd) {
|
|
616
|
+
finalizeWithErrorPage();
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
responseCommitted = true;
|
|
620
|
+
res.status(didError ? 500 : 200);
|
|
621
|
+
res.set({ "Content-Type": "text/html" });
|
|
622
|
+
const transformStream = new Transform({
|
|
623
|
+
transform(chunk, encoding, callback) {
|
|
624
|
+
res.write(chunk, encoding);
|
|
625
|
+
callback();
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
const start = htmlStart;
|
|
629
|
+
const end = htmlEnd;
|
|
630
|
+
res.write(start);
|
|
631
|
+
transformStream.on("finish", () => {
|
|
632
|
+
res.end(end);
|
|
633
|
+
});
|
|
634
|
+
pipe(transformStream);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
);
|
|
638
|
+
setTimeout(() => {
|
|
639
|
+
abort();
|
|
640
|
+
}, ABORT_DELAY_MS);
|
|
641
|
+
} catch (err) {
|
|
642
|
+
if (err instanceof Error) {
|
|
643
|
+
viteInstance?.ssrFixStacktrace(err);
|
|
644
|
+
console.error("SSR middleware error:", err.message);
|
|
645
|
+
console.error(err.stack);
|
|
646
|
+
} else {
|
|
647
|
+
console.error("Unknown SSR middleware error", err);
|
|
648
|
+
}
|
|
649
|
+
finalizeWithErrorPage(err);
|
|
650
|
+
}
|
|
651
|
+
};
|
|
125
652
|
export {
|
|
126
653
|
getDerivedKey,
|
|
127
654
|
hashPassword,
|
|
128
|
-
initServer
|
|
655
|
+
initServer,
|
|
656
|
+
ssrMiddleware
|
|
129
657
|
};
|
package/dist/initServer.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Application } from 'express';
|
|
2
|
-
|
|
2
|
+
type ServerEnv = {
|
|
3
3
|
[key: string]: string | undefined;
|
|
4
|
-
}
|
|
4
|
+
};
|
|
5
|
+
export declare const initServer: (app: Application, serverEnv: ServerEnv) => Promise<void>;
|
|
6
|
+
export {};
|
|
5
7
|
//# sourceMappingURL=initServer.d.ts.map
|
package/dist/initServer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"initServer.d.ts","sourceRoot":"","sources":["../src/initServer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAsBrC,
|
|
1
|
+
{"version":3,"file":"initServer.d.ts","sourceRoot":"","sources":["../src/initServer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAsBrC,KAAK,SAAS,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;CAAE,CAAA;AA8EtD,eAAO,MAAM,UAAU,GAAU,KAAK,WAAW,EAAE,WAAW,SAAS,kBAmDtE,CAAA"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { StaticHandler } from '../../router/src';
|
|
3
|
+
import * as express from "express";
|
|
4
|
+
export declare function renderSSR(req: express.Request, dataRoutes: StaticHandler["dataRoutes"]): Promise<{
|
|
5
|
+
element: ReactNode;
|
|
6
|
+
isMatched: boolean;
|
|
7
|
+
}>;
|
|
8
|
+
//# sourceMappingURL=renderSSR.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"renderSSR.d.ts","sourceRoot":"","sources":["../src/renderSSR.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,SAAS,CAAA;AAClC,OAAO,EAAE,SAAS,EAAc,MAAM,OAAO,CAAA;AAC7C,OAAO,EAGL,aAAa,EACd,MAAM,iBAAiB,CAAA;AAOxB,wBAAsB,SAAS,CAC7B,GAAG,EAAE,OAAO,CAAC,OAAO,EACpB,UAAU,EAAE,aAAa,CAAC,YAAY,CAAC,GACtC,OAAO,CAAC;IAAC,OAAO,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAC,CAAC,CA+BnD"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { StaticHandler } from '../../router/src';
|
|
2
|
+
import { createServer } from 'vite';
|
|
3
|
+
import { ReactNode } from 'react';
|
|
4
|
+
import { NextFunction, Request, Response } from 'express';
|
|
5
|
+
type ViteDevServer = Awaited<ReturnType<typeof createServer>>;
|
|
6
|
+
export declare const ssrMiddleware: ({ viteInstance, dataRoutes, errorExtraComponent, renderTemplateStart, }: {
|
|
7
|
+
viteInstance: ViteDevServer;
|
|
8
|
+
dataRoutes: StaticHandler["dataRoutes"];
|
|
9
|
+
errorExtraComponent: ReactNode;
|
|
10
|
+
renderTemplateStart?: (req: Request, templateStart: string) => Promise<string>;
|
|
11
|
+
}) => (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=ssrMiddleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssrMiddleware.d.ts","sourceRoot":"","sources":["../src/ssrMiddleware.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAEjE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAKzD,KAAK,aAAa,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAC;AAuG9D,eAAO,MAAM,aAAa,GAAI,yEAK3B;IACD,YAAY,EAAE,aAAa,CAAC;IAC5B,UAAU,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;IACxC,mBAAmB,EAAE,SAAS,CAAC;IAC/B,mBAAmB,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CAChF,MAAW,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,kBAyH1D,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rpcbase/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.443.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist"
|
|
@@ -44,11 +44,13 @@
|
|
|
44
44
|
}
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
+
"connect-mongo": "6.0.0",
|
|
47
48
|
"connect-redis": "9.0.0",
|
|
48
49
|
"express-session": "1.18.2",
|
|
49
50
|
"http-proxy-middleware": "3.0.5",
|
|
50
|
-
"redis": "5.
|
|
51
|
-
"request-ip": "3.3.0"
|
|
51
|
+
"redis": "5.10.0"
|
|
52
52
|
},
|
|
53
|
-
"devDependencies": {
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"request-ip": "3.3.0"
|
|
55
|
+
}
|
|
54
56
|
}
|