@antelopejs/dms-frontend 0.5.5 → 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 +377 -19
- 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 +9 -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-build.d.ts +11 -0
- package/dist/verify-source-build.js +113 -0
- package/dist/verify-source-runner.js +14 -57
- 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 +5 -1
- package/templates/vue/prepare-types.mjs +34 -0
- 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/proxy-headers.mjs +25 -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 +178 -169
- 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,13 +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";
|
|
52
|
+
import { writeProxyHeaders } from "./server/proxy-headers.mjs";
|
|
45
53
|
import { backendUrl, refuseForeignTarget } from "./server/request-target.mjs";
|
|
46
|
-
import { handleTester } from "./server/tester.mjs";
|
|
47
54
|
import {
|
|
48
55
|
createInertiaPage,
|
|
49
56
|
htmlHeaders,
|
|
@@ -63,27 +70,8 @@ const MIME_TYPES = {
|
|
|
63
70
|
".js": "text/javascript",
|
|
64
71
|
".svg": "image/svg+xml",
|
|
65
72
|
};
|
|
66
|
-
const PROXY_PREFIXES = ["/api/", "/dms/"];
|
|
67
73
|
const SAFE_METHODS = new Set(["GET", "HEAD", "OPTIONS"]);
|
|
68
|
-
const
|
|
69
|
-
["GET", /^\/api\/_auth\/session\/?$/],
|
|
70
|
-
["POST", /^\/api\/_auth\/session\/?$/],
|
|
71
|
-
["DELETE", /^\/api\/_auth\/session\/?$/],
|
|
72
|
-
[
|
|
73
|
-
"POST",
|
|
74
|
-
/^\/auth\/(?:login|signup|verify-2fa|establish|request-2fa-email|switch-account|switch-tenant|validate-account|remove-account)\/?$/,
|
|
75
|
-
],
|
|
76
|
-
["POST", /^\/auth\/oauth\/handoff\/?$/],
|
|
77
|
-
["GET", /^\/auth\/oauth\/[^/]+\/(?:start|callback)\/?$/],
|
|
78
|
-
];
|
|
79
|
-
const HTML_RENDER_ROUTE = "/api/html/render";
|
|
80
|
-
const TESTER_ROUTE = /^\/api\/_dms\/tester\/?$/;
|
|
81
|
-
const LOCAL_ROUTES = [
|
|
82
|
-
[(pathname) => TESTER_ROUTE.test(pathname), handleTester],
|
|
83
|
-
[icons.isIconRequest, icons.handleIcons],
|
|
84
|
-
];
|
|
85
|
-
const AUTH_PAGE = "/auth";
|
|
86
|
-
const ONBOARDING_PAGE = "/onboarding";
|
|
74
|
+
const LOCAL_ROUTES = [[icons.isIconRequest, icons.handleIcons]];
|
|
87
75
|
const SOURCE_TEMPLATE_PATH = join(PROJECT_ROOT, "index.html");
|
|
88
76
|
const BUILT_SSR_RENDERER_PATH = join(PROJECT_ROOT, "dist/ssr/ssr-renderer.js");
|
|
89
77
|
// The SSR bundle keeps vue-i18n external, so Node loads its esm-bundler build,
|
|
@@ -101,13 +89,17 @@ async function developmentServer() {
|
|
|
101
89
|
if (vite) return vite;
|
|
102
90
|
vitePromise ??= import("vite")
|
|
103
91
|
.then(({ createServer: createViteServer }) => {
|
|
104
|
-
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
|
+
};
|
|
105
97
|
if (frontendHttpServer)
|
|
106
98
|
serverOptions.hmr = { server: frontendHttpServer };
|
|
107
99
|
return createViteServer({
|
|
108
100
|
server: serverOptions,
|
|
109
101
|
appType: "custom",
|
|
110
|
-
plugins: [captureViteErrors()],
|
|
102
|
+
plugins: [denyPrivateFiles(), captureViteErrors()],
|
|
111
103
|
});
|
|
112
104
|
})
|
|
113
105
|
.then((server) => {
|
|
@@ -121,17 +113,28 @@ async function developmentServer() {
|
|
|
121
113
|
return vitePromise;
|
|
122
114
|
}
|
|
123
115
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
...(
|
|
133
|
-
|
|
134
|
-
|
|
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);
|
|
135
138
|
if (session?.accessToken)
|
|
136
139
|
headers.authorization = `Bearer ${session.accessToken}`;
|
|
137
140
|
return headers;
|
|
@@ -161,45 +164,23 @@ async function backendJson(path, request) {
|
|
|
161
164
|
return response.json();
|
|
162
165
|
}
|
|
163
166
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
return
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
async function pageProps(request) {
|
|
170
|
-
const pathname = new URL(request.url, "http://frontend.local").pathname;
|
|
171
|
-
const session = publicSession(readSession(request));
|
|
172
|
-
try {
|
|
173
|
-
const page = await fetchPage(request, pathname);
|
|
174
|
-
return { path: pathname, page, ...session };
|
|
175
|
-
} catch (error) {
|
|
176
|
-
if (!(error instanceof BackendResponseError) || error.status !== 404)
|
|
177
|
-
throw error;
|
|
178
|
-
const renderer = await ssrRenderer(await developmentServer());
|
|
179
|
-
if (!renderer.isDmsFrontendPage(pathname)) throw error;
|
|
180
|
-
return {
|
|
181
|
-
path: pathname,
|
|
182
|
-
...session,
|
|
183
|
-
page: {
|
|
184
|
-
componentName: pathname,
|
|
185
|
-
isFrontendOnly: true,
|
|
186
|
-
route: {
|
|
187
|
-
fullId: pathname.slice(1).replaceAll("/", "."),
|
|
188
|
-
fullSlug: pathname,
|
|
189
|
-
hasAccess: pathname.startsWith("/auth/"),
|
|
190
|
-
},
|
|
191
|
-
layout: { layout: { componentName: "DefaultLayout" } },
|
|
192
|
-
},
|
|
193
|
-
};
|
|
194
|
-
}
|
|
167
|
+
/** The session logic of the module that owns sign-in. */
|
|
168
|
+
async function currentAuth() {
|
|
169
|
+
return (await loadServerModules(await developmentServer())).serverAuth;
|
|
195
170
|
}
|
|
196
171
|
|
|
197
|
-
function
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
+
);
|
|
203
184
|
}
|
|
204
185
|
|
|
205
186
|
function redirectToHomepage(request, response, pathname) {
|
|
@@ -208,7 +189,7 @@ function redirectToHomepage(request, response, pathname) {
|
|
|
208
189
|
return true;
|
|
209
190
|
}
|
|
210
191
|
|
|
211
|
-
function redirectSignedOutPrivatePage(request, response, props) {
|
|
192
|
+
function redirectSignedOutPrivatePage(request, response, props, auth) {
|
|
212
193
|
const route = props.page?.route;
|
|
213
194
|
if (
|
|
214
195
|
route?.publicAccess === true ||
|
|
@@ -216,27 +197,14 @@ function redirectSignedOutPrivatePage(request, response, props) {
|
|
|
216
197
|
readSession(request)
|
|
217
198
|
)
|
|
218
199
|
return false;
|
|
219
|
-
|
|
220
|
-
redirectFrontendVisit(request, response, location);
|
|
200
|
+
redirectFrontendVisit(request, response, auth.signInLocation(request.url));
|
|
221
201
|
return true;
|
|
222
202
|
}
|
|
223
203
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
if (
|
|
228
|
-
AUTH_SERVER_ROUTES.some(
|
|
229
|
-
([routeMethod, pattern]) =>
|
|
230
|
-
routeMethod === method && pattern.test(pathname),
|
|
231
|
-
)
|
|
232
|
-
)
|
|
233
|
-
return "server";
|
|
234
|
-
return method === "GET" ? "frontend" : "server";
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
async function proxy(request, response, fallbackOnNotFound = false) {
|
|
238
|
-
const { publicEndpoint, isRefused } = proxyAccess(request, SAFE_METHODS);
|
|
239
|
-
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) {
|
|
240
208
|
refuseCrossOrigin(response);
|
|
241
209
|
return true;
|
|
242
210
|
}
|
|
@@ -247,18 +215,13 @@ async function proxy(request, response, fallbackOnNotFound = false) {
|
|
|
247
215
|
const upstream = await fetch(backendUrl(request.url), {
|
|
248
216
|
signal: abandoned.signal,
|
|
249
217
|
method: request.method,
|
|
250
|
-
headers: backendHeaders(request,
|
|
218
|
+
headers: backendHeaders(request, access),
|
|
251
219
|
body: ["GET", "HEAD"].includes(request.method) ? undefined : request,
|
|
252
220
|
duplex: "half",
|
|
253
221
|
});
|
|
254
|
-
if (fallbackOnNotFound && upstream.status === 404) {
|
|
255
|
-
await upstream.body?.cancel();
|
|
256
|
-
return false;
|
|
257
|
-
}
|
|
258
222
|
writeProxyHeaders(response, upstream);
|
|
259
223
|
if (!upstream.body || request.method === "HEAD") response.end();
|
|
260
224
|
else await pipeline(Readable.fromWeb(upstream.body), response);
|
|
261
|
-
return true;
|
|
262
225
|
}
|
|
263
226
|
|
|
264
227
|
function acceptedAsset(pathname, request) {
|
|
@@ -305,11 +268,37 @@ async function ssrRenderer(devServer) {
|
|
|
305
268
|
return renderer;
|
|
306
269
|
}
|
|
307
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
|
+
*/
|
|
308
296
|
export async function renderHtml(
|
|
309
297
|
page,
|
|
310
298
|
requestUrl,
|
|
311
299
|
serverFetch,
|
|
312
300
|
requestCookies,
|
|
301
|
+
options = {},
|
|
313
302
|
) {
|
|
314
303
|
const devServer = await developmentServer();
|
|
315
304
|
const rawTemplate = devServer
|
|
@@ -318,27 +307,17 @@ export async function renderHtml(
|
|
|
318
307
|
const template = devServer
|
|
319
308
|
? await devServer.transformIndexHtml(requestUrl, rawTemplate)
|
|
320
309
|
: rawTemplate;
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
} catch (error) {
|
|
326
|
-
console.error(
|
|
327
|
-
"DMS SSR render failed; falling back to client rendering",
|
|
328
|
-
error,
|
|
329
|
-
);
|
|
330
|
-
rendered = {
|
|
331
|
-
body: inertiaAppHtml(page),
|
|
332
|
-
head: { headTags: "", htmlAttrs: "", bodyAttrs: "" },
|
|
333
|
-
overlays: "",
|
|
334
|
-
error: { statusCode: 500 },
|
|
335
|
-
};
|
|
336
|
-
}
|
|
310
|
+
const rendered =
|
|
311
|
+
options.ssr === false
|
|
312
|
+
? clientRenderedShell(page)
|
|
313
|
+
: await serverRendered(page, serverFetch, requestCookies, devServer);
|
|
337
314
|
if (rendered.redirect)
|
|
338
315
|
return { html: "", status: 200, redirect: rendered.redirect };
|
|
339
316
|
const preloads = await documentStyleTags(devServer, page, template);
|
|
340
317
|
const html = template
|
|
341
|
-
.replace("<title>Antelope DMS</title>",
|
|
318
|
+
.replace("<title>Antelope DMS</title>", () =>
|
|
319
|
+
withFaviconPlaceholder(rendered.head.headTags),
|
|
320
|
+
)
|
|
342
321
|
.replace(/<html([^>]*)>/, (_, attributes) =>
|
|
343
322
|
htmlTag(attributes, rendered.head.htmlAttrs),
|
|
344
323
|
)
|
|
@@ -352,23 +331,24 @@ export async function renderHtml(
|
|
|
352
331
|
return { html, status: rendered.error?.statusCode ?? 200 };
|
|
353
332
|
}
|
|
354
333
|
|
|
355
|
-
function renderRequestHtml(page, request) {
|
|
334
|
+
function renderRequestHtml(page, request, rule = {}) {
|
|
356
335
|
const fetch = serverComponentFetch(request);
|
|
357
|
-
return renderHtml(page, request.url, fetch, request.headers.cookie
|
|
336
|
+
return renderHtml(page, request.url, fetch, request.headers.cookie, {
|
|
337
|
+
ssr: rule.ssr,
|
|
338
|
+
});
|
|
358
339
|
}
|
|
359
340
|
|
|
360
341
|
async function writeBackendError(error, request, response) {
|
|
342
|
+
const auth = await currentAuth();
|
|
361
343
|
const status = Number.isInteger(error?.status) ? error.status : 502;
|
|
362
344
|
const pathname = new URL(request.url, "http://frontend.local").pathname;
|
|
363
345
|
if (
|
|
364
346
|
error instanceof BackendResponseError &&
|
|
365
347
|
[401, 403].includes(status) &&
|
|
366
348
|
!readSession(request) &&
|
|
367
|
-
!
|
|
368
|
-
pathname !== ONBOARDING_PAGE
|
|
349
|
+
!auth.isSignInExempt(pathname)
|
|
369
350
|
) {
|
|
370
|
-
|
|
371
|
-
redirectFrontendVisit(request, response, location);
|
|
351
|
+
redirectFrontendVisit(request, response, auth.signInLocation(request.url));
|
|
372
352
|
return;
|
|
373
353
|
}
|
|
374
354
|
const loadRenderer = async () => ssrRenderer(await developmentServer());
|
|
@@ -379,7 +359,7 @@ async function writeBackendError(error, request, response) {
|
|
|
379
359
|
const props = {
|
|
380
360
|
path: pathname,
|
|
381
361
|
page: {},
|
|
382
|
-
...
|
|
362
|
+
...auth.view(readSession(request)),
|
|
383
363
|
error: { statusCode: status },
|
|
384
364
|
};
|
|
385
365
|
const page = createInertiaPage(request.url, props);
|
|
@@ -410,19 +390,45 @@ async function answeredByVite(devServer, request, response) {
|
|
|
410
390
|
}
|
|
411
391
|
|
|
412
392
|
/**
|
|
413
|
-
*
|
|
414
|
-
*
|
|
415
|
-
*
|
|
416
|
-
* 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.
|
|
417
396
|
*/
|
|
418
|
-
function
|
|
419
|
-
|
|
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
|
+
});
|
|
420
417
|
}
|
|
421
418
|
|
|
422
|
-
/** Vite's files in development, the build's in production. */
|
|
423
|
-
async function
|
|
424
|
-
|
|
425
|
-
|
|
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");
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function writeNotFound(response) {
|
|
430
|
+
response.writeHead(404, { "content-type": JSON_TYPE });
|
|
431
|
+
response.end(JSON.stringify({ message: "Not found" }));
|
|
426
432
|
}
|
|
427
433
|
|
|
428
434
|
export async function handleRequest(request, response) {
|
|
@@ -430,48 +436,39 @@ export async function handleRequest(request, response) {
|
|
|
430
436
|
const pathname = url.pathname;
|
|
431
437
|
const localRoute = LOCAL_ROUTES.find(([matches]) => matches(pathname));
|
|
432
438
|
if (localRoute) return localRoute[1](request, response);
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
if (
|
|
445
|
-
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);
|
|
446
451
|
if (requestFailure.hasMalformedPath(request.url))
|
|
447
452
|
return requestFailure.writeBadRequest(response);
|
|
448
|
-
const devServer = await developmentServer();
|
|
449
|
-
const visit = isFrontendVisit(request);
|
|
450
|
-
// A file is looked for among the frontend's own before the backend is asked
|
|
451
|
-
// for it: asking first cost a backend round trip for each of the hundreds of
|
|
452
|
-
// modules a development page loads, and for each built asset. A file the
|
|
453
|
-
// frontend does not have, a media file, is still the backend's.
|
|
454
|
-
const fileFirst = !visit && isFilePath(pathname);
|
|
455
|
-
const serveFile = () =>
|
|
456
|
-
answeredWithFile(devServer, request, response, pathname);
|
|
457
|
-
if (fileFirst && (await serveFile())) return;
|
|
458
|
-
if (!visit && (await proxy(request, response, true))) return;
|
|
459
|
-
if (!fileFirst && (await serveFile())) return;
|
|
460
453
|
if (handleAssetVersionMismatch(request, response)) return;
|
|
461
454
|
if (redirectToHomepage(request, response, pathname)) return;
|
|
462
|
-
const
|
|
463
|
-
|
|
464
|
-
|
|
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);
|
|
465
462
|
if (request.headers[INERTIA_HEADER]) {
|
|
466
463
|
return writeContent(
|
|
467
464
|
request,
|
|
468
465
|
response,
|
|
469
466
|
200,
|
|
470
|
-
inertiaHeaders(page.version),
|
|
467
|
+
{ ...inertiaHeaders(page.version), ...headersFor(200) },
|
|
471
468
|
JSON.stringify(page),
|
|
472
469
|
);
|
|
473
470
|
}
|
|
474
|
-
const rendered = await renderRequestHtml(page, request);
|
|
471
|
+
const rendered = await renderRequestHtml(page, request, rule);
|
|
475
472
|
if (rendered.redirect) {
|
|
476
473
|
redirectFrontendVisit(request, response, rendered.redirect);
|
|
477
474
|
return;
|
|
@@ -480,7 +477,7 @@ export async function handleRequest(request, response) {
|
|
|
480
477
|
request,
|
|
481
478
|
response,
|
|
482
479
|
rendered.status,
|
|
483
|
-
htmlHeaders(),
|
|
480
|
+
{ ...htmlHeaders(), ...headersFor(rendered.status) },
|
|
484
481
|
rendered.html,
|
|
485
482
|
);
|
|
486
483
|
}
|
|
@@ -495,7 +492,7 @@ async function writeRequestFailure(error, request, response) {
|
|
|
495
492
|
!request.dmsSessionRetry
|
|
496
493
|
) {
|
|
497
494
|
request.dmsSessionRetry = true;
|
|
498
|
-
if (await
|
|
495
|
+
if (await (await currentAuth()).refresh(request, response)) {
|
|
499
496
|
await handleRequestSafely(request, response);
|
|
500
497
|
return;
|
|
501
498
|
}
|
|
@@ -524,7 +521,7 @@ if (
|
|
|
524
521
|
handleClientError(error, socket, maxHeaderSize),
|
|
525
522
|
);
|
|
526
523
|
const port = Number(process.env.PORT ?? 3001);
|
|
527
|
-
const host =
|
|
524
|
+
const host = listenHost();
|
|
528
525
|
const onListenError = (error) => void reportListenError(error, host, port);
|
|
529
526
|
frontendHttpServer.once("error", onListenError);
|
|
530
527
|
frontendHttpServer.listen(port, host, () => {
|
|
@@ -533,6 +530,17 @@ if (
|
|
|
533
530
|
});
|
|
534
531
|
}
|
|
535
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
|
+
|
|
536
544
|
/**
|
|
537
545
|
* Report the server ready. In development Vite would otherwise start with the
|
|
538
546
|
* first request, so it is started here: "ready" then means the first page is
|
|
@@ -547,6 +555,7 @@ async function announceReady() {
|
|
|
547
555
|
}
|
|
548
556
|
const { address, port } = frontendHttpServer.address();
|
|
549
557
|
await reportReady(address, port);
|
|
558
|
+
await reportRouting();
|
|
550
559
|
// Only the production build produces the email bundle otherwise. Started
|
|
551
560
|
// once the server answers, so it never delays the first page.
|
|
552
561
|
if (process.env.DMS_DEV === "true") watchEmailBundle();
|