@rpcbase/server 0.440.0 → 0.442.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 +502 -5
- 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 +5 -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,190 @@
|
|
|
1
1
|
import session from "express-session";
|
|
2
2
|
import { RedisStore } from "connect-redis";
|
|
3
3
|
import { createClient } from "redis";
|
|
4
|
-
import requestIp from "request-ip";
|
|
5
4
|
import env from "@rpcbase/env";
|
|
6
|
-
import { initApiClient } from "@rpcbase/client";
|
|
5
|
+
import { initApiClient, SsrErrorFallback, SSR_ERROR_STATE_GLOBAL_KEY, serializeSsrErrorState } from "@rpcbase/client";
|
|
7
6
|
import assert from "assert";
|
|
8
7
|
import { hkdfSync, scrypt } from "crypto";
|
|
9
8
|
import { createProxyMiddleware } from "http-proxy-middleware";
|
|
9
|
+
import fs from "node:fs/promises";
|
|
10
|
+
import { readFileSync } from "node:fs";
|
|
11
|
+
import { Transform } from "node:stream";
|
|
12
|
+
import { StrictMode, createElement } from "react";
|
|
13
|
+
import { renderToPipeableStream, renderToStaticMarkup } from "react-dom/server";
|
|
14
|
+
import { jsx } from "react/jsx-runtime";
|
|
15
|
+
import { createPath, matchRoutes, parsePath, createStaticRouter, StaticRouterProvider } from "@rpcbase/router";
|
|
16
|
+
function getDefaultExportFromCjs(x) {
|
|
17
|
+
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
|
|
18
|
+
}
|
|
19
|
+
var is_1;
|
|
20
|
+
var hasRequiredIs;
|
|
21
|
+
function requireIs() {
|
|
22
|
+
if (hasRequiredIs) return is_1;
|
|
23
|
+
hasRequiredIs = 1;
|
|
24
|
+
var regexes = {
|
|
25
|
+
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])$/,
|
|
26
|
+
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
|
|
27
|
+
};
|
|
28
|
+
function not(func) {
|
|
29
|
+
return function() {
|
|
30
|
+
return !func.apply(null, Array.prototype.slice.call(arguments));
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function existy(value) {
|
|
34
|
+
return value != null;
|
|
35
|
+
}
|
|
36
|
+
function ip(value) {
|
|
37
|
+
return existy(value) && regexes.ipv4.test(value) || regexes.ipv6.test(value);
|
|
38
|
+
}
|
|
39
|
+
function object(value) {
|
|
40
|
+
return Object(value) === value;
|
|
41
|
+
}
|
|
42
|
+
function string(value) {
|
|
43
|
+
return Object.prototype.toString.call(value) === "[object String]";
|
|
44
|
+
}
|
|
45
|
+
var is = {
|
|
46
|
+
existy,
|
|
47
|
+
ip,
|
|
48
|
+
object,
|
|
49
|
+
string,
|
|
50
|
+
not: {
|
|
51
|
+
existy: not(existy),
|
|
52
|
+
ip: not(ip),
|
|
53
|
+
object: not(object),
|
|
54
|
+
string: not(string)
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
is_1 = is;
|
|
58
|
+
return is_1;
|
|
59
|
+
}
|
|
60
|
+
var lib;
|
|
61
|
+
var hasRequiredLib;
|
|
62
|
+
function requireLib() {
|
|
63
|
+
if (hasRequiredLib) return lib;
|
|
64
|
+
hasRequiredLib = 1;
|
|
65
|
+
function _typeof(obj) {
|
|
66
|
+
"@babel/helpers - typeof";
|
|
67
|
+
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(obj2) {
|
|
68
|
+
return typeof obj2;
|
|
69
|
+
} : function(obj2) {
|
|
70
|
+
return obj2 && "function" == typeof Symbol && obj2.constructor === Symbol && obj2 !== Symbol.prototype ? "symbol" : typeof obj2;
|
|
71
|
+
}, _typeof(obj);
|
|
72
|
+
}
|
|
73
|
+
var is = requireIs();
|
|
74
|
+
function getClientIpFromXForwardedFor(value) {
|
|
75
|
+
if (!is.existy(value)) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
if (is.not.string(value)) {
|
|
79
|
+
throw new TypeError('Expected a string, got "'.concat(_typeof(value), '"'));
|
|
80
|
+
}
|
|
81
|
+
var forwardedIps = value.split(",").map(function(e) {
|
|
82
|
+
var ip = e.trim();
|
|
83
|
+
if (ip.includes(":")) {
|
|
84
|
+
var splitted = ip.split(":");
|
|
85
|
+
if (splitted.length === 2) {
|
|
86
|
+
return splitted[0];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return ip;
|
|
90
|
+
});
|
|
91
|
+
for (var i = 0; i < forwardedIps.length; i++) {
|
|
92
|
+
if (is.ip(forwardedIps[i])) {
|
|
93
|
+
return forwardedIps[i];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
function getClientIp(req) {
|
|
99
|
+
if (req.headers) {
|
|
100
|
+
if (is.ip(req.headers["x-client-ip"])) {
|
|
101
|
+
return req.headers["x-client-ip"];
|
|
102
|
+
}
|
|
103
|
+
var xForwardedFor = getClientIpFromXForwardedFor(req.headers["x-forwarded-for"]);
|
|
104
|
+
if (is.ip(xForwardedFor)) {
|
|
105
|
+
return xForwardedFor;
|
|
106
|
+
}
|
|
107
|
+
if (is.ip(req.headers["cf-connecting-ip"])) {
|
|
108
|
+
return req.headers["cf-connecting-ip"];
|
|
109
|
+
}
|
|
110
|
+
if (is.ip(req.headers["fastly-client-ip"])) {
|
|
111
|
+
return req.headers["fastly-client-ip"];
|
|
112
|
+
}
|
|
113
|
+
if (is.ip(req.headers["true-client-ip"])) {
|
|
114
|
+
return req.headers["true-client-ip"];
|
|
115
|
+
}
|
|
116
|
+
if (is.ip(req.headers["x-real-ip"])) {
|
|
117
|
+
return req.headers["x-real-ip"];
|
|
118
|
+
}
|
|
119
|
+
if (is.ip(req.headers["x-cluster-client-ip"])) {
|
|
120
|
+
return req.headers["x-cluster-client-ip"];
|
|
121
|
+
}
|
|
122
|
+
if (is.ip(req.headers["x-forwarded"])) {
|
|
123
|
+
return req.headers["x-forwarded"];
|
|
124
|
+
}
|
|
125
|
+
if (is.ip(req.headers["forwarded-for"])) {
|
|
126
|
+
return req.headers["forwarded-for"];
|
|
127
|
+
}
|
|
128
|
+
if (is.ip(req.headers.forwarded)) {
|
|
129
|
+
return req.headers.forwarded;
|
|
130
|
+
}
|
|
131
|
+
if (is.ip(req.headers["x-appengine-user-ip"])) {
|
|
132
|
+
return req.headers["x-appengine-user-ip"];
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (is.existy(req.connection)) {
|
|
136
|
+
if (is.ip(req.connection.remoteAddress)) {
|
|
137
|
+
return req.connection.remoteAddress;
|
|
138
|
+
}
|
|
139
|
+
if (is.existy(req.connection.socket) && is.ip(req.connection.socket.remoteAddress)) {
|
|
140
|
+
return req.connection.socket.remoteAddress;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (is.existy(req.socket) && is.ip(req.socket.remoteAddress)) {
|
|
144
|
+
return req.socket.remoteAddress;
|
|
145
|
+
}
|
|
146
|
+
if (is.existy(req.info) && is.ip(req.info.remoteAddress)) {
|
|
147
|
+
return req.info.remoteAddress;
|
|
148
|
+
}
|
|
149
|
+
if (is.existy(req.requestContext) && is.existy(req.requestContext.identity) && is.ip(req.requestContext.identity.sourceIp)) {
|
|
150
|
+
return req.requestContext.identity.sourceIp;
|
|
151
|
+
}
|
|
152
|
+
if (req.headers) {
|
|
153
|
+
if (is.ip(req.headers["Cf-Pseudo-IPv4"])) {
|
|
154
|
+
return req.headers["Cf-Pseudo-IPv4"];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (is.existy(req.raw)) {
|
|
158
|
+
return getClientIp(req.raw);
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
function mw(options) {
|
|
163
|
+
var configuration = is.not.existy(options) ? {} : options;
|
|
164
|
+
if (is.not.object(configuration)) {
|
|
165
|
+
throw new TypeError("Options must be an object!");
|
|
166
|
+
}
|
|
167
|
+
var attributeName = configuration.attributeName || "clientIp";
|
|
168
|
+
return function(req, res, next) {
|
|
169
|
+
var ip = getClientIp(req);
|
|
170
|
+
Object.defineProperty(req, attributeName, {
|
|
171
|
+
get: function get() {
|
|
172
|
+
return ip;
|
|
173
|
+
},
|
|
174
|
+
configurable: true
|
|
175
|
+
});
|
|
176
|
+
next();
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
lib = {
|
|
180
|
+
getClientIpFromXForwardedFor,
|
|
181
|
+
getClientIp,
|
|
182
|
+
mw
|
|
183
|
+
};
|
|
184
|
+
return lib;
|
|
185
|
+
}
|
|
186
|
+
var libExports = requireLib();
|
|
187
|
+
const requestIp = /* @__PURE__ */ getDefaultExportFromCjs(libExports);
|
|
10
188
|
const getDerivedKey = (masterKey, info, length = 32, salt = "") => {
|
|
11
189
|
assert(masterKey?.length >= 32, "MASTER_KEY must be 32 chars or longer.");
|
|
12
190
|
return Buffer.from(hkdfSync(
|
|
@@ -39,7 +217,7 @@ process.env = {
|
|
|
39
217
|
...__rb_env__,
|
|
40
218
|
...process.env
|
|
41
219
|
};
|
|
42
|
-
const isProduction = process.env.NODE_ENV === "production";
|
|
220
|
+
const isProduction$1 = process.env.NODE_ENV === "production";
|
|
43
221
|
const initServer = async (app, serverEnv) => {
|
|
44
222
|
await initApiClient({ app });
|
|
45
223
|
app.disable("x-powered-by");
|
|
@@ -96,7 +274,7 @@ const initServer = async (app, serverEnv) => {
|
|
|
96
274
|
// 60 days
|
|
97
275
|
}
|
|
98
276
|
};
|
|
99
|
-
if (isProduction) {
|
|
277
|
+
if (isProduction$1) {
|
|
100
278
|
sessionConfig.cookie.secure = true;
|
|
101
279
|
}
|
|
102
280
|
app.use(session(sessionConfig));
|
|
@@ -122,8 +300,327 @@ async function hashPassword(password, salt) {
|
|
|
122
300
|
});
|
|
123
301
|
return derivedKey;
|
|
124
302
|
}
|
|
303
|
+
function createLocation(current, to, state = null, key) {
|
|
304
|
+
const location = {
|
|
305
|
+
pathname: current,
|
|
306
|
+
search: "",
|
|
307
|
+
hash: "",
|
|
308
|
+
...typeof to === "string" ? parsePath(to) : to,
|
|
309
|
+
state,
|
|
310
|
+
// TODO: This could be cleaned up. push/replace should probably just take
|
|
311
|
+
// full Locations now and avoid the need to run through this flow at all
|
|
312
|
+
// But that's a pretty big refactor to the current test suite so going to
|
|
313
|
+
// keep as is for the time being and just let any incoming keys take precedence
|
|
314
|
+
key: to && to.key || key
|
|
315
|
+
};
|
|
316
|
+
return location;
|
|
317
|
+
}
|
|
318
|
+
function getShortCircuitMatches(routes) {
|
|
319
|
+
const route = routes.length === 1 ? routes[0] : routes.find((r) => r.index || !r.path || r.path === "/") || {
|
|
320
|
+
id: "__shim-error-route__"
|
|
321
|
+
};
|
|
322
|
+
return {
|
|
323
|
+
matches: [
|
|
324
|
+
{
|
|
325
|
+
params: {},
|
|
326
|
+
pathname: "",
|
|
327
|
+
pathnameBase: "",
|
|
328
|
+
route
|
|
329
|
+
}
|
|
330
|
+
],
|
|
331
|
+
route
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
async function applyRouteLoaders(req, dataRoutes) {
|
|
335
|
+
const baseUrl = `${req.protocol}://${req.get("host")}`;
|
|
336
|
+
const url = new URL(req.originalUrl, baseUrl);
|
|
337
|
+
const method = req.method;
|
|
338
|
+
const location = createLocation("", createPath(url), null, "default");
|
|
339
|
+
const baseContext = {
|
|
340
|
+
basename: "",
|
|
341
|
+
location,
|
|
342
|
+
loaderHeaders: {},
|
|
343
|
+
actionHeaders: {}
|
|
344
|
+
};
|
|
345
|
+
const matches = matchRoutes(dataRoutes, location) || [];
|
|
346
|
+
if (!matches) {
|
|
347
|
+
const error = {
|
|
348
|
+
status: 404,
|
|
349
|
+
message: `No route matches URL: ${req.originalUrl}`
|
|
350
|
+
};
|
|
351
|
+
const { matches: notFoundMatches, route } = getShortCircuitMatches(dataRoutes);
|
|
352
|
+
return {
|
|
353
|
+
...baseContext,
|
|
354
|
+
matches: notFoundMatches,
|
|
355
|
+
loaderData: {},
|
|
356
|
+
actionData: null,
|
|
357
|
+
errors: { [route.id]: error },
|
|
358
|
+
statusCode: 404
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
if (method !== "GET") {
|
|
362
|
+
return {
|
|
363
|
+
...baseContext,
|
|
364
|
+
matches,
|
|
365
|
+
loaderData: {},
|
|
366
|
+
actionData: null,
|
|
367
|
+
errors: null,
|
|
368
|
+
statusCode: 200
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
const loaderPromisesResults = await Promise.allSettled(
|
|
372
|
+
matches.map(async (match) => {
|
|
373
|
+
const { route, params } = match;
|
|
374
|
+
if (!route.loader) return null;
|
|
375
|
+
try {
|
|
376
|
+
return {
|
|
377
|
+
id: route.id,
|
|
378
|
+
data: await route.loader({
|
|
379
|
+
params,
|
|
380
|
+
ctx: { req }
|
|
381
|
+
})
|
|
382
|
+
};
|
|
383
|
+
} catch (error) {
|
|
384
|
+
throw { id: route.id, reason: error };
|
|
385
|
+
}
|
|
386
|
+
})
|
|
387
|
+
);
|
|
388
|
+
const loaderData = {};
|
|
389
|
+
let errors = null;
|
|
390
|
+
for (const result of loaderPromisesResults) {
|
|
391
|
+
if (result.status === "fulfilled") {
|
|
392
|
+
if (result.value) {
|
|
393
|
+
loaderData[result.value.id] = result.value.data;
|
|
394
|
+
}
|
|
395
|
+
} else if (result.status === "rejected") {
|
|
396
|
+
const id = result.reason?.id;
|
|
397
|
+
if (!id) {
|
|
398
|
+
throw new Error(`missing route ID in error: ${result.reason}`);
|
|
399
|
+
}
|
|
400
|
+
if (!errors) {
|
|
401
|
+
errors = {};
|
|
402
|
+
}
|
|
403
|
+
errors[id] = result.reason;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return {
|
|
407
|
+
...baseContext,
|
|
408
|
+
matches,
|
|
409
|
+
loaderData,
|
|
410
|
+
actionData: null,
|
|
411
|
+
errors,
|
|
412
|
+
statusCode: Object.keys(errors || {}).length > 0 ? 500 : 200
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
async function renderSSR(req, dataRoutes) {
|
|
416
|
+
let routerContext;
|
|
417
|
+
try {
|
|
418
|
+
routerContext = await applyRouteLoaders(req, dataRoutes);
|
|
419
|
+
} catch (err) {
|
|
420
|
+
console.log(err);
|
|
421
|
+
throw err;
|
|
422
|
+
}
|
|
423
|
+
if (routerContext.errors) {
|
|
424
|
+
console.log("SERVER ERRORS", routerContext.errors);
|
|
425
|
+
}
|
|
426
|
+
const router = createStaticRouter(dataRoutes, routerContext);
|
|
427
|
+
const element = /* @__PURE__ */ jsx(StrictMode, { children: /* @__PURE__ */ jsx(
|
|
428
|
+
StaticRouterProvider,
|
|
429
|
+
{
|
|
430
|
+
router,
|
|
431
|
+
context: routerContext
|
|
432
|
+
}
|
|
433
|
+
) });
|
|
434
|
+
const isMatched = routerContext.matches.length > 0;
|
|
435
|
+
return { element, isMatched };
|
|
436
|
+
}
|
|
437
|
+
const ABORT_DELAY_MS = 1e4;
|
|
438
|
+
const APP_HTML_PLACEHOLDER = "<!--app-html-->";
|
|
439
|
+
const DEFAULT_SERVER_ERROR_MESSAGE = "We couldn't render this page on the server. Please refresh and try again.";
|
|
440
|
+
const isProduction = env.NODE_ENV === "production";
|
|
441
|
+
const templateHtml = isProduction ? readFileSync("./build/dist/client/src/client/index.html", "utf-8") : "";
|
|
442
|
+
const formatErrorDetails = (error) => {
|
|
443
|
+
if (isProduction) return void 0;
|
|
444
|
+
if (!error) return void 0;
|
|
445
|
+
if (error instanceof Error) {
|
|
446
|
+
return error.stack || error.message;
|
|
447
|
+
}
|
|
448
|
+
if (typeof error === "string") {
|
|
449
|
+
return error;
|
|
450
|
+
}
|
|
451
|
+
try {
|
|
452
|
+
return JSON.stringify(error, null, 2);
|
|
453
|
+
} catch {
|
|
454
|
+
return void 0;
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
const formatPlainErrorBody = (error) => {
|
|
458
|
+
if (isProduction) {
|
|
459
|
+
return "Server error";
|
|
460
|
+
}
|
|
461
|
+
if (error instanceof Error) {
|
|
462
|
+
return error.stack || error.message;
|
|
463
|
+
}
|
|
464
|
+
if (typeof error === "string") {
|
|
465
|
+
return error;
|
|
466
|
+
}
|
|
467
|
+
try {
|
|
468
|
+
return JSON.stringify(error, null, 2);
|
|
469
|
+
} catch {
|
|
470
|
+
return "Unknown error";
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
const sendErrorResponse = ({
|
|
474
|
+
res,
|
|
475
|
+
htmlStart,
|
|
476
|
+
htmlEnd,
|
|
477
|
+
error,
|
|
478
|
+
status = 500,
|
|
479
|
+
title = "Server rendering error",
|
|
480
|
+
message = DEFAULT_SERVER_ERROR_MESSAGE,
|
|
481
|
+
errorExtraComponent
|
|
482
|
+
}) => {
|
|
483
|
+
if (res.headersSent) return;
|
|
484
|
+
if (!htmlStart || !htmlEnd) {
|
|
485
|
+
res.status(status).end(formatPlainErrorBody(error));
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
res.status(status);
|
|
489
|
+
res.set({ "Content-Type": "text/html" });
|
|
490
|
+
const details = formatErrorDetails(error);
|
|
491
|
+
const state = {
|
|
492
|
+
statusCode: status,
|
|
493
|
+
title,
|
|
494
|
+
message,
|
|
495
|
+
details
|
|
496
|
+
};
|
|
497
|
+
const markup = renderToStaticMarkup(
|
|
498
|
+
createElement(
|
|
499
|
+
StrictMode,
|
|
500
|
+
null,
|
|
501
|
+
createElement(SsrErrorFallback, {
|
|
502
|
+
state,
|
|
503
|
+
renderErrorExtra: (payload) => createElement(errorExtraComponent, { state: payload })
|
|
504
|
+
})
|
|
505
|
+
)
|
|
506
|
+
);
|
|
507
|
+
const serializedState = `<script>window.${SSR_ERROR_STATE_GLOBAL_KEY}=${serializeSsrErrorState({
|
|
508
|
+
...state,
|
|
509
|
+
details: isProduction ? void 0 : details
|
|
510
|
+
})};<\/script>`;
|
|
511
|
+
res.end(`${htmlStart}${markup}${serializedState}${htmlEnd}`);
|
|
512
|
+
};
|
|
513
|
+
const ssrMiddleware = ({
|
|
514
|
+
viteInstance,
|
|
515
|
+
dataRoutes,
|
|
516
|
+
errorExtraComponent,
|
|
517
|
+
renderTemplateStart
|
|
518
|
+
}) => async (req, res, next) => {
|
|
519
|
+
let template;
|
|
520
|
+
let htmlStart = null;
|
|
521
|
+
let htmlEnd = null;
|
|
522
|
+
let responseCommitted = false;
|
|
523
|
+
const finalizeWithErrorPage = (error, status = 500, message = DEFAULT_SERVER_ERROR_MESSAGE) => {
|
|
524
|
+
if (responseCommitted) return;
|
|
525
|
+
sendErrorResponse({
|
|
526
|
+
res,
|
|
527
|
+
htmlStart,
|
|
528
|
+
htmlEnd,
|
|
529
|
+
error,
|
|
530
|
+
status,
|
|
531
|
+
message,
|
|
532
|
+
errorExtraComponent
|
|
533
|
+
});
|
|
534
|
+
responseCommitted = true;
|
|
535
|
+
};
|
|
536
|
+
try {
|
|
537
|
+
const base = "/";
|
|
538
|
+
const url = req.originalUrl.replace(base, "");
|
|
539
|
+
if (isProduction) {
|
|
540
|
+
template = templateHtml;
|
|
541
|
+
} else {
|
|
542
|
+
template = await fs.readFile("./src/client/index.html", "utf-8");
|
|
543
|
+
template = await viteInstance.transformIndexHtml(url, template);
|
|
544
|
+
}
|
|
545
|
+
const placeholderIndex = template.indexOf(APP_HTML_PLACEHOLDER);
|
|
546
|
+
if (placeholderIndex === -1) {
|
|
547
|
+
throw new Error(`Template missing ${APP_HTML_PLACEHOLDER} placeholder`);
|
|
548
|
+
}
|
|
549
|
+
const templateStart = template.slice(0, placeholderIndex);
|
|
550
|
+
if (renderTemplateStart) {
|
|
551
|
+
htmlStart = await renderTemplateStart(req, templateStart);
|
|
552
|
+
} else {
|
|
553
|
+
htmlStart = templateStart;
|
|
554
|
+
}
|
|
555
|
+
htmlEnd = template.slice(placeholderIndex + APP_HTML_PLACEHOLDER.length);
|
|
556
|
+
const { element, isMatched } = await renderSSR(req, dataRoutes);
|
|
557
|
+
if (!isMatched) {
|
|
558
|
+
next();
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
let didError = false;
|
|
562
|
+
const { pipe, abort } = renderToPipeableStream(
|
|
563
|
+
element,
|
|
564
|
+
{
|
|
565
|
+
onShellError(error) {
|
|
566
|
+
if (error instanceof Error) {
|
|
567
|
+
viteInstance?.ssrFixStacktrace(error);
|
|
568
|
+
}
|
|
569
|
+
console.error("SSR shell error", error);
|
|
570
|
+
finalizeWithErrorPage(error);
|
|
571
|
+
abort();
|
|
572
|
+
},
|
|
573
|
+
onError(error) {
|
|
574
|
+
didError = true;
|
|
575
|
+
if (error instanceof Error) {
|
|
576
|
+
viteInstance?.ssrFixStacktrace(error);
|
|
577
|
+
}
|
|
578
|
+
console.error("SSR rendering error", error);
|
|
579
|
+
},
|
|
580
|
+
onShellReady() {
|
|
581
|
+
if (responseCommitted) {
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
if (!htmlStart || !htmlEnd) {
|
|
585
|
+
finalizeWithErrorPage();
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
responseCommitted = true;
|
|
589
|
+
res.status(didError ? 500 : 200);
|
|
590
|
+
res.set({ "Content-Type": "text/html" });
|
|
591
|
+
const transformStream = new Transform({
|
|
592
|
+
transform(chunk, encoding, callback) {
|
|
593
|
+
res.write(chunk, encoding);
|
|
594
|
+
callback();
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
const start = htmlStart;
|
|
598
|
+
const end = htmlEnd;
|
|
599
|
+
res.write(start);
|
|
600
|
+
transformStream.on("finish", () => {
|
|
601
|
+
res.end(end);
|
|
602
|
+
});
|
|
603
|
+
pipe(transformStream);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
);
|
|
607
|
+
setTimeout(() => {
|
|
608
|
+
abort();
|
|
609
|
+
}, ABORT_DELAY_MS);
|
|
610
|
+
} catch (err) {
|
|
611
|
+
if (err instanceof Error) {
|
|
612
|
+
viteInstance?.ssrFixStacktrace(err);
|
|
613
|
+
console.error("SSR middleware error:", err.message);
|
|
614
|
+
console.error(err.stack);
|
|
615
|
+
} else {
|
|
616
|
+
console.error("Unknown SSR middleware error", err);
|
|
617
|
+
}
|
|
618
|
+
finalizeWithErrorPage(err);
|
|
619
|
+
}
|
|
620
|
+
};
|
|
125
621
|
export {
|
|
126
622
|
getDerivedKey,
|
|
127
623
|
hashPassword,
|
|
128
|
-
initServer
|
|
624
|
+
initServer,
|
|
625
|
+
ssrMiddleware
|
|
129
626
|
};
|
|
@@ -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.442.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist"
|
|
@@ -47,8 +47,9 @@
|
|
|
47
47
|
"connect-redis": "9.0.0",
|
|
48
48
|
"express-session": "1.18.2",
|
|
49
49
|
"http-proxy-middleware": "3.0.5",
|
|
50
|
-
"redis": "5.
|
|
51
|
-
"request-ip": "3.3.0"
|
|
50
|
+
"redis": "5.10.0"
|
|
52
51
|
},
|
|
53
|
-
"devDependencies": {
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"request-ip": "3.3.0"
|
|
54
|
+
}
|
|
54
55
|
}
|