@antelopejs/dms-frontend 0.5.6 → 0.6.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/README.md +376 -18
- package/dist/app-configs.d.ts +9 -0
- package/dist/app-configs.js +52 -0
- package/dist/asset-roots.d.ts +7 -0
- package/dist/asset-roots.js +34 -0
- package/dist/commands/build-action.js +9 -0
- package/dist/commands/dev-action.js +88 -19
- package/dist/commands/start-action.js +2 -0
- package/dist/common.d.ts +1 -0
- package/dist/common.js +1 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.js +8 -3
- package/dist/derived-outputs.d.ts +2 -0
- package/dist/derived-outputs.js +38 -31
- package/dist/help.js +10 -3
- package/dist/layer-watch.js +123 -131
- package/dist/layers.js +2 -0
- package/dist/manifest-secrets.js +5 -5
- package/dist/manifest.js +7 -1
- package/dist/materialize.js +21 -1
- package/dist/module-set.d.ts +16 -0
- package/dist/module-set.js +80 -0
- package/dist/server-process.d.ts +1 -0
- package/dist/server-process.js +28 -2
- package/dist/verify-source-runner.js +12 -0
- package/dist/workspace.d.ts +9 -0
- package/package.json +2 -1
- package/templates/vue/app-runtime.ts +58 -16
- package/templates/vue/client-server-guard.mjs +73 -0
- package/templates/vue/email-renderer.ts +17 -4
- package/templates/vue/email-runtime.ts +1 -1
- package/templates/vue/frontend-module.ts +1817 -259
- package/templates/vue/globals.d.ts +1 -1
- package/templates/vue/icon-hydration.ts +106 -1
- package/templates/vue/index.html +0 -1
- package/templates/vue/layer-app-config.ts +92 -0
- package/templates/vue/main.ts +10 -2
- package/templates/vue/package.json +4 -1
- package/templates/vue/server/auth/backend.mjs +13 -19
- package/templates/vue/server/auth/client-ip.mjs +27 -0
- package/templates/vue/server/auth/establish-endpoints.mjs +70 -0
- package/templates/vue/server/auth/session.mjs +6 -97
- package/templates/vue/server/auth-hooks.mjs +84 -0
- package/templates/vue/server/backend-proxies.mjs +110 -0
- package/templates/vue/server/dev-private-files.mjs +99 -0
- package/templates/vue/server/email.mjs +11 -33
- package/templates/vue/server/favicon.mjs +20 -0
- package/templates/vue/server/inertia.mjs +48 -3
- package/templates/vue/server/listen-host.mjs +19 -0
- package/templates/vue/server/module-event.mjs +265 -0
- package/templates/vue/server/module-routes.mjs +513 -0
- package/templates/vue/server/module-server.mjs +225 -0
- package/templates/vue/server/module-testing.mjs +65 -0
- package/templates/vue/server/page-props.mjs +151 -0
- package/templates/vue/server/path-patterns.mjs +38 -0
- package/templates/vue/server/public-endpoints.mjs +69 -11
- package/templates/vue/server/request-routing.mjs +47 -0
- package/templates/vue/server/route-rules.mjs +129 -0
- package/templates/vue/server-module.ts +294 -0
- package/templates/vue/server.mjs +177 -161
- package/templates/vue/ssr-renderer.ts +60 -19
- package/templates/vue/tsconfig.json +2 -1
- package/templates/vue/ui-app-config.ts +18 -6
- package/templates/vue/ui-inertia-page.mjs +37 -0
- package/templates/vue/vite.config.ts +31 -10
- package/templates/vue/vite.email.config.ts +1 -1
- package/templates/vue/vite.server.config.ts +57 -0
- package/templates/vue/server/auth/oauth.mjs +0 -233
- package/templates/vue/server/auth/routes.mjs +0 -444
- package/templates/vue/server/render-token.mjs +0 -81
- package/templates/vue/server/tester.mjs +0 -227
package/templates/vue/server.mjs
CHANGED
|
@@ -10,12 +10,8 @@ import {
|
|
|
10
10
|
RequestBodyError,
|
|
11
11
|
UpstreamError,
|
|
12
12
|
} from "./server/auth/backend.mjs";
|
|
13
|
+
import { forwardedHeaders } from "./server/auth/client-ip.mjs";
|
|
13
14
|
import { proxyAccess, refuseCrossOrigin } from "./server/public-endpoints.mjs";
|
|
14
|
-
import {
|
|
15
|
-
handleAuth,
|
|
16
|
-
publicSession,
|
|
17
|
-
refreshSession,
|
|
18
|
-
} from "./server/auth/routes.mjs";
|
|
19
15
|
import { readSession } from "./server/auth/session.mjs";
|
|
20
16
|
import {
|
|
21
17
|
BackendResponseError,
|
|
@@ -37,14 +33,24 @@ import {
|
|
|
37
33
|
writeViteError,
|
|
38
34
|
} from "./server/vite-errors.mjs";
|
|
39
35
|
import { documentStyleTags } from "./server/dev-styles.mjs";
|
|
40
|
-
import {
|
|
36
|
+
import { watchEmailBundle } from "./server/email.mjs";
|
|
37
|
+
import { withFaviconPlaceholder } from "./server/favicon.mjs";
|
|
38
|
+
import { DEV_FS_DENY, denyPrivateFiles } from "./server/dev-private-files.mjs";
|
|
39
|
+
import { ownerLabel, requestOwner } from "./server/request-routing.mjs";
|
|
40
|
+
import { routingReport } from "./server/backend-proxies.mjs";
|
|
41
|
+
import { routeRuleFor, ruleHeaders } from "./server/route-rules.mjs";
|
|
42
|
+
import {
|
|
43
|
+
handleModuleRoute,
|
|
44
|
+
loadServerModules,
|
|
45
|
+
} from "./server/module-server.mjs";
|
|
41
46
|
import { HOMEPAGE } from "./server/homepage.mjs";
|
|
47
|
+
import { loadPageProps } from "./server/page-props.mjs";
|
|
42
48
|
import { htmlTag } from "./server/html-tag.mjs";
|
|
49
|
+
import { listenHost } from "./server/listen-host.mjs";
|
|
43
50
|
import * as icons from "./server/icons.mjs";
|
|
44
51
|
import * as requestFailure from "./server/request-failure.mjs";
|
|
45
52
|
import { writeProxyHeaders } from "./server/proxy-headers.mjs";
|
|
46
53
|
import { backendUrl, refuseForeignTarget } from "./server/request-target.mjs";
|
|
47
|
-
import { handleTester } from "./server/tester.mjs";
|
|
48
54
|
import {
|
|
49
55
|
createInertiaPage,
|
|
50
56
|
htmlHeaders,
|
|
@@ -64,27 +70,8 @@ const MIME_TYPES = {
|
|
|
64
70
|
".js": "text/javascript",
|
|
65
71
|
".svg": "image/svg+xml",
|
|
66
72
|
};
|
|
67
|
-
const PROXY_PREFIXES = ["/api/", "/dms/"];
|
|
68
73
|
const SAFE_METHODS = new Set(["GET", "HEAD", "OPTIONS"]);
|
|
69
|
-
const
|
|
70
|
-
["GET", /^\/api\/_auth\/session\/?$/],
|
|
71
|
-
["POST", /^\/api\/_auth\/session\/?$/],
|
|
72
|
-
["DELETE", /^\/api\/_auth\/session\/?$/],
|
|
73
|
-
[
|
|
74
|
-
"POST",
|
|
75
|
-
/^\/auth\/(?:login|signup|verify-2fa|establish|request-2fa-email|switch-account|switch-tenant|validate-account|remove-account)\/?$/,
|
|
76
|
-
],
|
|
77
|
-
["POST", /^\/auth\/oauth\/handoff\/?$/],
|
|
78
|
-
["GET", /^\/auth\/oauth\/[^/]+\/(?:start|callback)\/?$/],
|
|
79
|
-
];
|
|
80
|
-
const HTML_RENDER_ROUTE = "/api/html/render";
|
|
81
|
-
const TESTER_ROUTE = /^\/api\/_dms\/tester\/?$/;
|
|
82
|
-
const LOCAL_ROUTES = [
|
|
83
|
-
[(pathname) => TESTER_ROUTE.test(pathname), handleTester],
|
|
84
|
-
[icons.isIconRequest, icons.handleIcons],
|
|
85
|
-
];
|
|
86
|
-
const AUTH_PAGE = "/auth";
|
|
87
|
-
const ONBOARDING_PAGE = "/onboarding";
|
|
74
|
+
const LOCAL_ROUTES = [[icons.isIconRequest, icons.handleIcons]];
|
|
88
75
|
const SOURCE_TEMPLATE_PATH = join(PROJECT_ROOT, "index.html");
|
|
89
76
|
const BUILT_SSR_RENDERER_PATH = join(PROJECT_ROOT, "dist/ssr/ssr-renderer.js");
|
|
90
77
|
// The SSR bundle keeps vue-i18n external, so Node loads its esm-bundler build,
|
|
@@ -102,13 +89,17 @@ async function developmentServer() {
|
|
|
102
89
|
if (vite) return vite;
|
|
103
90
|
vitePromise ??= import("vite")
|
|
104
91
|
.then(({ createServer: createViteServer }) => {
|
|
105
|
-
const serverOptions = {
|
|
92
|
+
const serverOptions = {
|
|
93
|
+
middlewareMode: true,
|
|
94
|
+
// The generated secrets and the modules' server code, never served.
|
|
95
|
+
fs: { deny: DEV_FS_DENY },
|
|
96
|
+
};
|
|
106
97
|
if (frontendHttpServer)
|
|
107
98
|
serverOptions.hmr = { server: frontendHttpServer };
|
|
108
99
|
return createViteServer({
|
|
109
100
|
server: serverOptions,
|
|
110
101
|
appType: "custom",
|
|
111
|
-
plugins: [captureViteErrors()],
|
|
102
|
+
plugins: [denyPrivateFiles(), captureViteErrors()],
|
|
112
103
|
});
|
|
113
104
|
})
|
|
114
105
|
.then((server) => {
|
|
@@ -122,17 +113,28 @@ async function developmentServer() {
|
|
|
122
113
|
return vitePromise;
|
|
123
114
|
}
|
|
124
115
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
...(
|
|
134
|
-
|
|
135
|
-
|
|
116
|
+
/**
|
|
117
|
+
* The headers of a backend request: the relayed client headers, the client's
|
|
118
|
+
* Origin and Referer as received, the `X-Forwarded-*` headers (set last, so no
|
|
119
|
+
* caller writes them), and the session's access token unless the request goes
|
|
120
|
+
* anonymously.
|
|
121
|
+
*/
|
|
122
|
+
function backendHeaders(request, access = { anonymous: false, headers: [] }) {
|
|
123
|
+
const headers = {
|
|
124
|
+
...relayedHeaders(request, [
|
|
125
|
+
"accept",
|
|
126
|
+
"content-type",
|
|
127
|
+
"if-match",
|
|
128
|
+
"origin",
|
|
129
|
+
"referer",
|
|
130
|
+
"user-agent",
|
|
131
|
+
"x-content-language",
|
|
132
|
+
"x-realtime-session",
|
|
133
|
+
...access.headers,
|
|
134
|
+
]),
|
|
135
|
+
...forwardedHeaders(request),
|
|
136
|
+
};
|
|
137
|
+
const session = access.anonymous ? undefined : readSession(request);
|
|
136
138
|
if (session?.accessToken)
|
|
137
139
|
headers.authorization = `Bearer ${session.accessToken}`;
|
|
138
140
|
return headers;
|
|
@@ -162,37 +164,23 @@ async function backendJson(path, request) {
|
|
|
162
164
|
return response.json();
|
|
163
165
|
}
|
|
164
166
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
return
|
|
167
|
+
/** The session logic of the module that owns sign-in. */
|
|
168
|
+
async function currentAuth() {
|
|
169
|
+
return (await loadServerModules(await developmentServer())).serverAuth;
|
|
168
170
|
}
|
|
169
171
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
path: pathname,
|
|
183
|
-
...session,
|
|
184
|
-
page: {
|
|
185
|
-
componentName: pathname,
|
|
186
|
-
isFrontendOnly: true,
|
|
187
|
-
route: {
|
|
188
|
-
fullId: pathname.slice(1).replaceAll("/", "."),
|
|
189
|
-
fullSlug: pathname,
|
|
190
|
-
hasAccess: pathname.startsWith("/auth/"),
|
|
191
|
-
},
|
|
192
|
-
layout: { layout: { componentName: "DefaultLayout" } },
|
|
193
|
-
},
|
|
194
|
-
};
|
|
195
|
-
}
|
|
172
|
+
function pageProps(request, auth) {
|
|
173
|
+
return loadPageProps(
|
|
174
|
+
request,
|
|
175
|
+
{
|
|
176
|
+
fetchJson: (path) => backendJson(path, request),
|
|
177
|
+
isFrontendPage: async (pathname) =>
|
|
178
|
+
(await ssrRenderer(await developmentServer())).isDmsFrontendPage(
|
|
179
|
+
pathname,
|
|
180
|
+
),
|
|
181
|
+
},
|
|
182
|
+
auth,
|
|
183
|
+
);
|
|
196
184
|
}
|
|
197
185
|
|
|
198
186
|
function redirectToHomepage(request, response, pathname) {
|
|
@@ -201,7 +189,7 @@ function redirectToHomepage(request, response, pathname) {
|
|
|
201
189
|
return true;
|
|
202
190
|
}
|
|
203
191
|
|
|
204
|
-
function redirectSignedOutPrivatePage(request, response, props) {
|
|
192
|
+
function redirectSignedOutPrivatePage(request, response, props, auth) {
|
|
205
193
|
const route = props.page?.route;
|
|
206
194
|
if (
|
|
207
195
|
route?.publicAccess === true ||
|
|
@@ -209,27 +197,14 @@ function redirectSignedOutPrivatePage(request, response, props) {
|
|
|
209
197
|
readSession(request)
|
|
210
198
|
)
|
|
211
199
|
return false;
|
|
212
|
-
|
|
213
|
-
redirectFrontendVisit(request, response, location);
|
|
200
|
+
redirectFrontendVisit(request, response, auth.signInLocation(request.url));
|
|
214
201
|
return true;
|
|
215
202
|
}
|
|
216
203
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
if (
|
|
221
|
-
AUTH_SERVER_ROUTES.some(
|
|
222
|
-
([routeMethod, pattern]) =>
|
|
223
|
-
routeMethod === method && pattern.test(pathname),
|
|
224
|
-
)
|
|
225
|
-
)
|
|
226
|
-
return "server";
|
|
227
|
-
return method === "GET" ? "frontend" : "server";
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
async function proxy(request, response, fallbackOnNotFound = false) {
|
|
231
|
-
const { publicEndpoint, isRefused } = proxyAccess(request, SAFE_METHODS);
|
|
232
|
-
if (isRefused) {
|
|
204
|
+
/** Sends a request to the backend through the proxy it reached. */
|
|
205
|
+
async function proxy(request, response, backendProxy) {
|
|
206
|
+
const access = proxyAccess(request, SAFE_METHODS, backendProxy);
|
|
207
|
+
if (access.isRefused) {
|
|
233
208
|
refuseCrossOrigin(response);
|
|
234
209
|
return true;
|
|
235
210
|
}
|
|
@@ -240,18 +215,13 @@ async function proxy(request, response, fallbackOnNotFound = false) {
|
|
|
240
215
|
const upstream = await fetch(backendUrl(request.url), {
|
|
241
216
|
signal: abandoned.signal,
|
|
242
217
|
method: request.method,
|
|
243
|
-
headers: backendHeaders(request,
|
|
218
|
+
headers: backendHeaders(request, access),
|
|
244
219
|
body: ["GET", "HEAD"].includes(request.method) ? undefined : request,
|
|
245
220
|
duplex: "half",
|
|
246
221
|
});
|
|
247
|
-
if (fallbackOnNotFound && upstream.status === 404) {
|
|
248
|
-
await upstream.body?.cancel();
|
|
249
|
-
return false;
|
|
250
|
-
}
|
|
251
222
|
writeProxyHeaders(response, upstream);
|
|
252
223
|
if (!upstream.body || request.method === "HEAD") response.end();
|
|
253
224
|
else await pipeline(Readable.fromWeb(upstream.body), response);
|
|
254
|
-
return true;
|
|
255
225
|
}
|
|
256
226
|
|
|
257
227
|
function acceptedAsset(pathname, request) {
|
|
@@ -298,11 +268,37 @@ async function ssrRenderer(devServer) {
|
|
|
298
268
|
return renderer;
|
|
299
269
|
}
|
|
300
270
|
|
|
271
|
+
function clientRenderedShell(page) {
|
|
272
|
+
return {
|
|
273
|
+
body: inertiaAppHtml(page),
|
|
274
|
+
head: { headTags: "", htmlAttrs: "", bodyAttrs: "" },
|
|
275
|
+
overlays: "",
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async function serverRendered(page, serverFetch, requestCookies, devServer) {
|
|
280
|
+
try {
|
|
281
|
+
const renderer = await ssrRenderer(devServer);
|
|
282
|
+
return await renderer.renderDmsPage(page, serverFetch, requestCookies);
|
|
283
|
+
} catch (error) {
|
|
284
|
+
console.error(
|
|
285
|
+
"DMS SSR render failed; falling back to client rendering",
|
|
286
|
+
error,
|
|
287
|
+
);
|
|
288
|
+
return { ...clientRenderedShell(page), error: { statusCode: 500 } };
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Renders a page document. With `{ ssr: false }` (a route rule), the client
|
|
294
|
+
* renders the page from the shell instead.
|
|
295
|
+
*/
|
|
301
296
|
export async function renderHtml(
|
|
302
297
|
page,
|
|
303
298
|
requestUrl,
|
|
304
299
|
serverFetch,
|
|
305
300
|
requestCookies,
|
|
301
|
+
options = {},
|
|
306
302
|
) {
|
|
307
303
|
const devServer = await developmentServer();
|
|
308
304
|
const rawTemplate = devServer
|
|
@@ -311,27 +307,17 @@ export async function renderHtml(
|
|
|
311
307
|
const template = devServer
|
|
312
308
|
? await devServer.transformIndexHtml(requestUrl, rawTemplate)
|
|
313
309
|
: rawTemplate;
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
} catch (error) {
|
|
319
|
-
console.error(
|
|
320
|
-
"DMS SSR render failed; falling back to client rendering",
|
|
321
|
-
error,
|
|
322
|
-
);
|
|
323
|
-
rendered = {
|
|
324
|
-
body: inertiaAppHtml(page),
|
|
325
|
-
head: { headTags: "", htmlAttrs: "", bodyAttrs: "" },
|
|
326
|
-
overlays: "",
|
|
327
|
-
error: { statusCode: 500 },
|
|
328
|
-
};
|
|
329
|
-
}
|
|
310
|
+
const rendered =
|
|
311
|
+
options.ssr === false
|
|
312
|
+
? clientRenderedShell(page)
|
|
313
|
+
: await serverRendered(page, serverFetch, requestCookies, devServer);
|
|
330
314
|
if (rendered.redirect)
|
|
331
315
|
return { html: "", status: 200, redirect: rendered.redirect };
|
|
332
316
|
const preloads = await documentStyleTags(devServer, page, template);
|
|
333
317
|
const html = template
|
|
334
|
-
.replace("<title>Antelope DMS</title>",
|
|
318
|
+
.replace("<title>Antelope DMS</title>", () =>
|
|
319
|
+
withFaviconPlaceholder(rendered.head.headTags),
|
|
320
|
+
)
|
|
335
321
|
.replace(/<html([^>]*)>/, (_, attributes) =>
|
|
336
322
|
htmlTag(attributes, rendered.head.htmlAttrs),
|
|
337
323
|
)
|
|
@@ -345,23 +331,24 @@ export async function renderHtml(
|
|
|
345
331
|
return { html, status: rendered.error?.statusCode ?? 200 };
|
|
346
332
|
}
|
|
347
333
|
|
|
348
|
-
function renderRequestHtml(page, request) {
|
|
334
|
+
function renderRequestHtml(page, request, rule = {}) {
|
|
349
335
|
const fetch = serverComponentFetch(request);
|
|
350
|
-
return renderHtml(page, request.url, fetch, request.headers.cookie
|
|
336
|
+
return renderHtml(page, request.url, fetch, request.headers.cookie, {
|
|
337
|
+
ssr: rule.ssr,
|
|
338
|
+
});
|
|
351
339
|
}
|
|
352
340
|
|
|
353
341
|
async function writeBackendError(error, request, response) {
|
|
342
|
+
const auth = await currentAuth();
|
|
354
343
|
const status = Number.isInteger(error?.status) ? error.status : 502;
|
|
355
344
|
const pathname = new URL(request.url, "http://frontend.local").pathname;
|
|
356
345
|
if (
|
|
357
346
|
error instanceof BackendResponseError &&
|
|
358
347
|
[401, 403].includes(status) &&
|
|
359
348
|
!readSession(request) &&
|
|
360
|
-
!
|
|
361
|
-
pathname !== ONBOARDING_PAGE
|
|
349
|
+
!auth.isSignInExempt(pathname)
|
|
362
350
|
) {
|
|
363
|
-
|
|
364
|
-
redirectFrontendVisit(request, response, location);
|
|
351
|
+
redirectFrontendVisit(request, response, auth.signInLocation(request.url));
|
|
365
352
|
return;
|
|
366
353
|
}
|
|
367
354
|
const loadRenderer = async () => ssrRenderer(await developmentServer());
|
|
@@ -372,7 +359,7 @@ async function writeBackendError(error, request, response) {
|
|
|
372
359
|
const props = {
|
|
373
360
|
path: pathname,
|
|
374
361
|
page: {},
|
|
375
|
-
...
|
|
362
|
+
...auth.view(readSession(request)),
|
|
376
363
|
error: { statusCode: status },
|
|
377
364
|
};
|
|
378
365
|
const page = createInertiaPage(request.url, props);
|
|
@@ -403,19 +390,45 @@ async function answeredByVite(devServer, request, response) {
|
|
|
403
390
|
}
|
|
404
391
|
|
|
405
392
|
/**
|
|
406
|
-
*
|
|
407
|
-
*
|
|
408
|
-
*
|
|
409
|
-
* pages they serve data for, which have none (`/<page>/pagelayout`).
|
|
393
|
+
* Whether the renderer has a file at `pathname`: a development source Vite
|
|
394
|
+
* serves (under the workspace or its `public/`), or a built asset. Only a
|
|
395
|
+
* path with an extension names one.
|
|
410
396
|
*/
|
|
411
|
-
function
|
|
412
|
-
|
|
397
|
+
function hasRendererFile(devServer, requested) {
|
|
398
|
+
let pathname;
|
|
399
|
+
try {
|
|
400
|
+
// As Vite and the asset server read it: `%2E` is a dot.
|
|
401
|
+
pathname = decodeURIComponent(requested);
|
|
402
|
+
} catch {
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
if (extname(pathname) === "") return false;
|
|
406
|
+
const roots = devServer
|
|
407
|
+
? [devServer.config.root, devServer.config.publicDir].filter(Boolean)
|
|
408
|
+
: [join(PROJECT_ROOT, "dist/client")];
|
|
409
|
+
return roots.some((root) => {
|
|
410
|
+
const file = resolve(root, `.${pathname}`);
|
|
411
|
+
return (
|
|
412
|
+
file.startsWith(`${resolve(root)}${sep}`) &&
|
|
413
|
+
existsSync(file) &&
|
|
414
|
+
statSync(file).isFile()
|
|
415
|
+
);
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/** Vite's files in development, the build's in production; else a 404. */
|
|
420
|
+
async function serveRendererFile(devServer, request, response, pathname) {
|
|
421
|
+
const answered = devServer
|
|
422
|
+
? await answeredByVite(devServer, request, response)
|
|
423
|
+
: serveAsset(pathname, request, response);
|
|
424
|
+
if (answered) return;
|
|
425
|
+
response.writeHead(404, { "content-type": "text/plain" });
|
|
426
|
+
response.end("Not found");
|
|
413
427
|
}
|
|
414
428
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
return pathname !== "/" && answeredByVite(devServer, request, response);
|
|
429
|
+
function writeNotFound(response) {
|
|
430
|
+
response.writeHead(404, { "content-type": JSON_TYPE });
|
|
431
|
+
response.end(JSON.stringify({ message: "Not found" }));
|
|
419
432
|
}
|
|
420
433
|
|
|
421
434
|
export async function handleRequest(request, response) {
|
|
@@ -423,48 +436,39 @@ export async function handleRequest(request, response) {
|
|
|
423
436
|
const pathname = url.pathname;
|
|
424
437
|
const localRoute = LOCAL_ROUTES.find(([matches]) => matches(pathname));
|
|
425
438
|
if (localRoute) return localRoute[1](request, response);
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
if (
|
|
438
|
-
return proxy(request, response);
|
|
439
|
+
const devServer = await developmentServer();
|
|
440
|
+
const modules = await loadServerModules(devServer);
|
|
441
|
+
const owner = requestOwner(request.method, pathname, modules, (path) =>
|
|
442
|
+
hasRendererFile(devServer, path),
|
|
443
|
+
);
|
|
444
|
+
if (devServer) response.setHeader("x-dms-route-owner", ownerLabel(owner));
|
|
445
|
+
if (owner.kind === "renderer")
|
|
446
|
+
return serveRendererFile(devServer, request, response, pathname);
|
|
447
|
+
if (owner.kind === "module")
|
|
448
|
+
return handleModuleRoute(request, response, pathname, modules);
|
|
449
|
+
if (owner.kind === "backend") return proxy(request, response, owner.proxy);
|
|
450
|
+
if (owner.kind === "not-found") return writeNotFound(response);
|
|
439
451
|
if (requestFailure.hasMalformedPath(request.url))
|
|
440
452
|
return requestFailure.writeBadRequest(response);
|
|
441
|
-
const devServer = await developmentServer();
|
|
442
|
-
const visit = isFrontendVisit(request);
|
|
443
|
-
// A file is looked for among the frontend's own before the backend is asked
|
|
444
|
-
// for it: asking first cost a backend round trip for each of the hundreds of
|
|
445
|
-
// modules a development page loads, and for each built asset. A file the
|
|
446
|
-
// frontend does not have, a media file, is still the backend's.
|
|
447
|
-
const fileFirst = !visit && isFilePath(pathname);
|
|
448
|
-
const serveFile = () =>
|
|
449
|
-
answeredWithFile(devServer, request, response, pathname);
|
|
450
|
-
if (fileFirst && (await serveFile())) return;
|
|
451
|
-
if (!visit && (await proxy(request, response, true))) return;
|
|
452
|
-
if (!fileFirst && (await serveFile())) return;
|
|
453
453
|
if (handleAssetVersionMismatch(request, response)) return;
|
|
454
454
|
if (redirectToHomepage(request, response, pathname)) return;
|
|
455
|
-
const
|
|
456
|
-
|
|
457
|
-
|
|
455
|
+
const { serverAuth: auth, rules } = modules;
|
|
456
|
+
const { props, onceProps } = await pageProps(request, auth);
|
|
457
|
+
if (redirectSignedOutPrivatePage(request, response, props, auth)) return;
|
|
458
|
+
const page = createInertiaPage(request.url, props, undefined, onceProps);
|
|
459
|
+
const rule = routeRuleFor(rules, pathname);
|
|
460
|
+
const headersFor = (status) =>
|
|
461
|
+
ruleHeaders(rule, request, props.page?.route, status);
|
|
458
462
|
if (request.headers[INERTIA_HEADER]) {
|
|
459
463
|
return writeContent(
|
|
460
464
|
request,
|
|
461
465
|
response,
|
|
462
466
|
200,
|
|
463
|
-
inertiaHeaders(page.version),
|
|
467
|
+
{ ...inertiaHeaders(page.version), ...headersFor(200) },
|
|
464
468
|
JSON.stringify(page),
|
|
465
469
|
);
|
|
466
470
|
}
|
|
467
|
-
const rendered = await renderRequestHtml(page, request);
|
|
471
|
+
const rendered = await renderRequestHtml(page, request, rule);
|
|
468
472
|
if (rendered.redirect) {
|
|
469
473
|
redirectFrontendVisit(request, response, rendered.redirect);
|
|
470
474
|
return;
|
|
@@ -473,7 +477,7 @@ export async function handleRequest(request, response) {
|
|
|
473
477
|
request,
|
|
474
478
|
response,
|
|
475
479
|
rendered.status,
|
|
476
|
-
htmlHeaders(),
|
|
480
|
+
{ ...htmlHeaders(), ...headersFor(rendered.status) },
|
|
477
481
|
rendered.html,
|
|
478
482
|
);
|
|
479
483
|
}
|
|
@@ -488,7 +492,7 @@ async function writeRequestFailure(error, request, response) {
|
|
|
488
492
|
!request.dmsSessionRetry
|
|
489
493
|
) {
|
|
490
494
|
request.dmsSessionRetry = true;
|
|
491
|
-
if (await
|
|
495
|
+
if (await (await currentAuth()).refresh(request, response)) {
|
|
492
496
|
await handleRequestSafely(request, response);
|
|
493
497
|
return;
|
|
494
498
|
}
|
|
@@ -517,7 +521,7 @@ if (
|
|
|
517
521
|
handleClientError(error, socket, maxHeaderSize),
|
|
518
522
|
);
|
|
519
523
|
const port = Number(process.env.PORT ?? 3001);
|
|
520
|
-
const host =
|
|
524
|
+
const host = listenHost();
|
|
521
525
|
const onListenError = (error) => void reportListenError(error, host, port);
|
|
522
526
|
frontendHttpServer.once("error", onListenError);
|
|
523
527
|
frontendHttpServer.listen(port, host, () => {
|
|
@@ -526,6 +530,17 @@ if (
|
|
|
526
530
|
});
|
|
527
531
|
}
|
|
528
532
|
|
|
533
|
+
/** Prints who owns which paths, once the server is up. */
|
|
534
|
+
async function reportRouting() {
|
|
535
|
+
try {
|
|
536
|
+
const modules = await loadServerModules(await developmentServer());
|
|
537
|
+
for (const line of routingReport(modules.proxies, modules.routes.length))
|
|
538
|
+
console.log(line);
|
|
539
|
+
} catch (error) {
|
|
540
|
+
console.error("DMS server modules failed to load", error);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
529
544
|
/**
|
|
530
545
|
* Report the server ready. In development Vite would otherwise start with the
|
|
531
546
|
* first request, so it is started here: "ready" then means the first page is
|
|
@@ -540,6 +555,7 @@ async function announceReady() {
|
|
|
540
555
|
}
|
|
541
556
|
const { address, port } = frontendHttpServer.address();
|
|
542
557
|
await reportReady(address, port);
|
|
558
|
+
await reportRouting();
|
|
543
559
|
// Only the production build produces the email bundle otherwise. Started
|
|
544
560
|
// once the server answers, so it never delays the first page.
|
|
545
561
|
if (process.env.DMS_DEV === "true") watchEmailBundle();
|