@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.
@@ -0,0 +1,4 @@
1
+ import { Request } from 'express';
2
+ import { StaticHandlerContext } from '../../router/src';
3
+ export declare function applyRouteLoaders(req: Request, dataRoutes: any[]): Promise<StaticHandlerContext>;
4
+ //# sourceMappingURL=applyRouteLoaders.d.ts.map
@@ -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
@@ -1,4 +1,5 @@
1
1
  export * from './initServer';
2
2
  export * from './getDerivedKey';
3
3
  export * from './hashPassword';
4
+ export * from './ssrMiddleware';
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -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 initServer = async (app, serverEnv) => {
44
- await initApiClient({ app });
45
- app.disable("x-powered-by");
46
- app.set("trust proxy", true);
47
- app.use(requestIp.mw());
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
- const sessionSecret = getDerivedKey(serverEnv.MASTER_KEY, "express_session_key");
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: serverEnv.REDIS_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
- redisClient.connect();
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: new RedisStore({ client: redisClient }),
299
+ store,
90
300
  proxy: true,
91
301
  resave: false,
92
302
  saveUninitialized: false,
93
303
  secret: sessionSecret,
94
304
  cookie: {
95
- maxAge: 1e3 * 3600 * 24 * 60
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
  };
@@ -1,5 +1,7 @@
1
1
  import { Application } from 'express';
2
- export declare const initServer: (app: Application, serverEnv: {
2
+ type ServerEnv = {
3
3
  [key: string]: string | undefined;
4
- }) => Promise<void>;
4
+ };
5
+ export declare const initServer: (app: Application, serverEnv: ServerEnv) => Promise<void>;
6
+ export {};
5
7
  //# sourceMappingURL=initServer.d.ts.map
@@ -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,eAAO,MAAM,UAAU,GAAU,KAAK,WAAW,EAAE,WAAW;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;CAAE,kBA6ElG,CAAA"}
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.441.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.8.2",
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
  }