@antelopejs/dms-frontend 0.9.3 → 0.10.1
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 +46 -29
- package/dist/commands/build-action.d.ts +3 -0
- package/dist/commands/build-action.js +77 -8
- package/dist/commands/build.js +7 -0
- package/dist/commands/clean-action.js +1 -1
- package/dist/commands/clean.js +1 -1
- package/dist/commands/dev-action.js +4 -3
- package/dist/commands/dev.js +1 -1
- package/dist/commands/prepare-action.js +1 -1
- package/dist/commands/start-action.js +3 -24
- package/dist/commands/workspace-task.js +2 -1
- package/dist/config.js +1 -0
- package/dist/derived-outputs.d.ts +1 -0
- package/dist/derived-outputs.js +6 -4
- package/dist/discovery.js +17 -6
- package/dist/entry-weight.js +15 -0
- package/dist/fs-sync.js +7 -4
- package/dist/help.js +1 -1
- package/dist/index.js +2 -1
- package/dist/internal/built-workspace.d.ts +4 -0
- package/dist/internal/built-workspace.js +43 -0
- package/dist/internal/ensure.d.ts +1 -0
- package/dist/internal/ensure.js +8 -0
- package/dist/internal/locale-catalogs.d.ts +8 -3
- package/dist/internal/locale-catalogs.js +16 -6
- package/dist/internal/manifest-decoder.d.ts +7 -0
- package/dist/internal/manifest-decoder.js +81 -0
- package/dist/internal/own-release.d.ts +5 -0
- package/dist/internal/own-release.js +20 -0
- package/dist/internal/plain-object.d.ts +1 -0
- package/dist/internal/plain-object.js +6 -0
- package/dist/internal/prepared-workspace.d.ts +12 -0
- package/dist/internal/prepared-workspace.js +74 -0
- package/dist/internal/text-list.d.ts +1 -0
- package/dist/internal/text-list.js +10 -0
- package/dist/internal/vite-manifest.d.ts +6 -0
- package/dist/internal/vite-manifest.js +24 -0
- package/dist/layer-watch.js +25 -24
- package/dist/layers.d.ts +1 -1
- package/dist/layers.js +3 -1
- package/dist/locale-files.d.ts +6 -3
- package/dist/locale-files.js +15 -9
- package/dist/manifest.d.ts +1 -1
- package/dist/manifest.js +46 -32
- package/dist/materialize.js +6 -2
- package/dist/module-set.js +2 -1
- package/dist/node-options.d.ts +1 -0
- package/dist/node-options.js +8 -0
- package/dist/output.d.ts +15 -9
- package/dist/output.js +30 -18
- package/dist/ports.d.ts +3 -1
- package/dist/ports.js +1 -1
- package/dist/private-values.d.ts +1 -1
- package/dist/private-values.js +5 -3
- package/dist/project-modules.d.ts +5 -1
- package/dist/project-modules.js +23 -15
- package/dist/renderer-range.js +5 -3
- package/dist/server-process.js +1 -1
- package/dist/verify-source-build.js +10 -2
- package/dist/verify-source-client-bundle.d.ts +1 -0
- package/dist/verify-source-client-bundle.js +30 -0
- package/dist/verify-source-runner.js +36 -94
- package/dist/verify-source-sources.js +49 -11
- package/dist/verify-source-workspace.d.ts +2 -0
- package/dist/verify-source-workspace.js +111 -0
- package/dist/workspace-setup.d.ts +1 -0
- package/dist/workspace-setup.js +4 -3
- package/package.json +11 -7
- package/skills/dms-frontend/SKILL.md +1 -1
- package/templates/vue/app-runtime.ts +2 -9
- package/templates/vue/backend-boundary.mjs +132 -0
- package/templates/vue/client-chunking.mjs +2 -0
- package/templates/vue/client-server-guard.mjs +16 -0
- package/templates/vue/compress-assets.mjs +9 -1
- package/templates/vue/entry-weight.mjs +29 -3
- package/templates/vue/frontend-build.ts +4 -3
- package/templates/vue/frontend-module.ts +1 -0
- package/templates/vue/frontend-runtime/module-registration.ts +2 -2
- package/templates/vue/frontend-runtime/state.ts +2 -2
- package/templates/vue/frontend-runtime/types.ts +8 -2
- package/templates/vue/head-order.mjs +10 -2
- package/templates/vue/icon-api.mjs +2 -0
- package/templates/vue/icon-scan.mjs +4 -0
- package/templates/vue/server/auth/backend.mjs +42 -5
- package/templates/vue/server/auth/client-ip.mjs +52 -4
- package/templates/vue/server/auth/establish-endpoints.mjs +11 -7
- package/templates/vue/server/auth/session.mjs +86 -4
- package/templates/vue/server/auth-declarations.mjs +46 -0
- package/templates/vue/server/auth-hooks.mjs +55 -7
- package/templates/vue/server/backend-proxies.mjs +78 -18
- package/templates/vue/server/backend-request.mjs +61 -0
- package/templates/vue/server/backend-response.mjs +23 -6
- package/templates/vue/server/built-files.mjs +7 -0
- package/templates/vue/server/cli-report.mjs +75 -12
- package/templates/vue/server/client-error.mjs +5 -0
- package/templates/vue/server/client-manifest.mjs +23 -1
- package/templates/vue/server/content.mjs +40 -10
- package/templates/vue/server/dev-private-files.mjs +17 -1
- package/templates/vue/server/dev-styles.mjs +38 -0
- package/templates/vue/server/email-bundle-watch.mjs +9 -3
- package/templates/vue/server/email.mjs +14 -6
- package/templates/vue/server/favicon.mjs +3 -0
- package/templates/vue/server/generated-files.mjs +221 -0
- package/templates/vue/server/homepage.mjs +21 -9
- package/templates/vue/server/html-tag.mjs +4 -0
- package/templates/vue/server/icons.mjs +93 -16
- package/templates/vue/server/in-place-render.mjs +35 -0
- package/templates/vue/server/inertia.mjs +76 -3
- package/templates/vue/server/module-event.mjs +118 -25
- package/templates/vue/server/module-route-types.mjs +78 -0
- package/templates/vue/server/module-routes.mjs +151 -96
- package/templates/vue/server/module-server.mjs +121 -40
- package/templates/vue/server/module-testing.mjs +36 -12
- package/templates/vue/server/page-props.mjs +94 -3
- package/templates/vue/server/page-redirects.mjs +37 -0
- package/templates/vue/server/path-patterns.mjs +35 -12
- package/templates/vue/server/proxy-compression.mjs +22 -0
- package/templates/vue/server/proxy-headers.mjs +12 -2
- package/templates/vue/server/proxy-redirects.mjs +21 -5
- package/templates/vue/server/proxy-request.mjs +58 -0
- package/templates/vue/server/proxy-transfers.mjs +44 -10
- package/templates/vue/server/public-endpoints.mjs +139 -28
- package/templates/vue/server/public-options.mjs +20 -6
- package/templates/vue/server/request-failure.mjs +24 -1
- package/templates/vue/server/request-routing.mjs +32 -10
- package/templates/vue/server/request-target.mjs +30 -11
- package/templates/vue/server/route-rules.mjs +48 -18
- package/templates/vue/server/ssr-module.mjs +32 -0
- package/templates/vue/server/static-files.mjs +35 -7
- package/templates/vue/server/type-guards.mjs +71 -0
- package/templates/vue/server/vite-errors.mjs +45 -7
- package/templates/vue/server/web-body.mjs +5 -1
- package/templates/vue/server-module.ts +12 -2
- package/templates/vue/server.mjs +162 -157
- package/templates/vue/sfc-global-types.mjs +112 -13
- package/templates/vue/ssr-renderer.ts +2 -2
- package/templates/vue/ui-inertia-page.mjs +16 -1
- package/templates/vue/ui-kbd.mjs +2 -1
- package/templates/vue/ui-request-state.mjs +2 -1
- package/templates/vue/vite.config.ts +9 -0
|
@@ -101,7 +101,16 @@ export interface DmsServerEvent {
|
|
|
101
101
|
* overwrites another's, and naming a `dms_*` cookie throws.
|
|
102
102
|
*/
|
|
103
103
|
cookies: {
|
|
104
|
-
|
|
104
|
+
/**
|
|
105
|
+
* The value a sealed cookie holds, when `accepts` takes it. A cookie that
|
|
106
|
+
* is absent, does not open, or holds a value `accepts` refuses reads as
|
|
107
|
+
* `undefined`. `accepts` receives the JSON the cookie was written from and
|
|
108
|
+
* is never called for an absent or unreadable cookie.
|
|
109
|
+
*/
|
|
110
|
+
readSealed<T>(
|
|
111
|
+
name: string,
|
|
112
|
+
accepts: (value: unknown) => value is T,
|
|
113
|
+
): T | undefined;
|
|
105
114
|
writeSealed(
|
|
106
115
|
name: string,
|
|
107
116
|
value: unknown,
|
|
@@ -177,7 +186,8 @@ export interface DmsServerRouteOptions {
|
|
|
177
186
|
* The session logic the frontend server runs outside any route, which the
|
|
178
187
|
* module owning sign-in provides. Without `publicSession`, pages carry no
|
|
179
188
|
* `user` and `session`; without `refreshSession`, a session the backend
|
|
180
|
-
* refuses is not renewed; `signInPath` is `/auth` by default.
|
|
189
|
+
* refuses is not renewed; `signInPath` is `/auth` by default. The server
|
|
190
|
+
* calls each hook with the registered object as `this`.
|
|
181
191
|
*/
|
|
182
192
|
export interface DmsServerAuth {
|
|
183
193
|
/** Where a signed-out visit to a private page goes, with `?redirect=`. */
|
package/templates/vue/server.mjs
CHANGED
|
@@ -2,18 +2,14 @@ import { existsSync, readFileSync, statSync } from "node:fs";
|
|
|
2
2
|
import { createServer, maxHeaderSize } from "node:http";
|
|
3
3
|
import { extname, join, resolve, sep } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { RequestBodyError, UpstreamError } from "./server/auth/backend.mjs";
|
|
5
6
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
UpstreamError,
|
|
10
|
-
} from "./server/auth/backend.mjs";
|
|
11
|
-
import { forwardedHeaders } from "./server/auth/client-ip.mjs";
|
|
12
|
-
import { proxyAccess, refuseCrossOrigin } from "./server/public-endpoints.mjs";
|
|
7
|
+
backendJson,
|
|
8
|
+
serverComponentFetch,
|
|
9
|
+
} from "./server/backend-request.mjs";
|
|
13
10
|
import { readSession } from "./server/auth/session.mjs";
|
|
14
11
|
import {
|
|
15
12
|
BackendResponseError,
|
|
16
|
-
backendResponseError,
|
|
17
13
|
redirectAccessRefusal,
|
|
18
14
|
UNEXPECTED_ERROR_BODY,
|
|
19
15
|
} from "./server/backend-response.mjs";
|
|
@@ -21,6 +17,7 @@ import { handleClientError } from "./server/client-error.mjs";
|
|
|
21
17
|
import {
|
|
22
18
|
reportListenError,
|
|
23
19
|
reportReady,
|
|
20
|
+
reportRouting,
|
|
24
21
|
reportStartError,
|
|
25
22
|
} from "./server/cli-report.mjs";
|
|
26
23
|
import { publicBuildFile } from "./server/built-files.mjs";
|
|
@@ -50,20 +47,29 @@ import {
|
|
|
50
47
|
handleModuleRoute,
|
|
51
48
|
loadServerModules,
|
|
52
49
|
} from "./server/module-server.mjs";
|
|
53
|
-
import {
|
|
50
|
+
import {
|
|
51
|
+
redirectSignedOutPrivatePage,
|
|
52
|
+
redirectToHomepage,
|
|
53
|
+
} from "./server/page-redirects.mjs";
|
|
54
54
|
import { publicOptionsScript } from "./server/public-options.mjs";
|
|
55
|
-
import { renderInPlace
|
|
56
|
-
import {
|
|
55
|
+
import { renderInPlace } from "./server/in-place-render.mjs";
|
|
56
|
+
import {
|
|
57
|
+
loadPageProps,
|
|
58
|
+
pageRoute,
|
|
59
|
+
propsForQuery,
|
|
60
|
+
} from "./server/page-props.mjs";
|
|
57
61
|
import { htmlTag } from "./server/html-tag.mjs";
|
|
62
|
+
import { isString } from "./server/type-guards.mjs";
|
|
58
63
|
import { listenHost } from "./server/listen-host.mjs";
|
|
59
64
|
import * as icons from "./server/icons.mjs";
|
|
65
|
+
import { ssrRenderer } from "./server/ssr-module.mjs";
|
|
60
66
|
import * as requestFailure from "./server/request-failure.mjs";
|
|
67
|
+
import { proxy } from "./server/proxy-request.mjs";
|
|
61
68
|
import {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
} from "./server/
|
|
65
|
-
import {
|
|
66
|
-
import { sendWithinLimit, SERVER_OPTIONS } from "./server/proxy-transfers.mjs";
|
|
69
|
+
hasOriginFormTarget,
|
|
70
|
+
isServerRequest,
|
|
71
|
+
} from "./server/request-target.mjs";
|
|
72
|
+
import { SERVER_OPTIONS } from "./server/proxy-transfers.mjs";
|
|
67
73
|
import {
|
|
68
74
|
createInertiaPage,
|
|
69
75
|
htmlHeaders,
|
|
@@ -72,24 +78,29 @@ import {
|
|
|
72
78
|
isFrontendVisit,
|
|
73
79
|
redirectFrontendVisit,
|
|
74
80
|
handleAssetVersionMismatch,
|
|
81
|
+
isSsrPage,
|
|
75
82
|
} from "./server/inertia.mjs";
|
|
76
83
|
export * from "./server/inertia.mjs";
|
|
77
84
|
|
|
85
|
+
/** @typedef {import("node:http").IncomingMessage} IncomingMessage */
|
|
86
|
+
/** @typedef {import("./server/request-target.mjs").ServerRequest} ServerRequest */
|
|
87
|
+
/** @typedef {import("node:http").ServerResponse} ServerResponse */
|
|
88
|
+
/** @typedef {import("vite").ViteDevServer} ViteDevServer */
|
|
89
|
+
/** @typedef {import("./server/inertia.mjs").InertiaPage} InertiaPage */
|
|
90
|
+
/** @typedef {import("./server/in-place-render.mjs").ServerReads} ServerReads */
|
|
91
|
+
|
|
78
92
|
const INERTIA_HEADER = "x-inertia";
|
|
79
93
|
const JSON_TYPE = "application/json";
|
|
80
94
|
const PROJECT_ROOT = fileURLToPath(new URL(".", import.meta.url));
|
|
81
|
-
|
|
95
|
+
/**
|
|
96
|
+
* @type {[
|
|
97
|
+
* (pathname: string) => boolean,
|
|
98
|
+
* (request: ServerRequest, response: ServerResponse) => Promise<void>
|
|
99
|
+
* ][]}
|
|
100
|
+
*/
|
|
82
101
|
const LOCAL_ROUTES = [[icons.isIconRequest, icons.handleIcons]];
|
|
83
102
|
const CLIENT_ROOT = join(PROJECT_ROOT, "dist/client");
|
|
84
103
|
const SOURCE_TEMPLATE_PATH = join(PROJECT_ROOT, "index.html");
|
|
85
|
-
/**
|
|
86
|
-
* A URL, not a path: on Windows, `import()` reads `C:\...` as a URL whose
|
|
87
|
-
* scheme is `c:` and refuses it.
|
|
88
|
-
*/
|
|
89
|
-
const BUILT_SSR_RENDERER_URL = new URL(
|
|
90
|
-
"dist/ssr/ssr-renderer.js",
|
|
91
|
-
import.meta.url,
|
|
92
|
-
);
|
|
93
104
|
|
|
94
105
|
/**
|
|
95
106
|
* The SSR bundle keeps vue-i18n external, so Node loads its esm-bundler build,
|
|
@@ -98,20 +109,26 @@ const BUILT_SSR_RENDERER_URL = new URL(
|
|
|
98
109
|
* NODE_ENV=production throws a ReferenceError on the first render.
|
|
99
110
|
*/
|
|
100
111
|
function defineVueCompileFlags() {
|
|
101
|
-
globalThis
|
|
112
|
+
/** @type {typeof globalThis & { __VUE_PROD_DEVTOOLS__?: boolean }} */
|
|
113
|
+
const vueGlobals = globalThis;
|
|
114
|
+
vueGlobals.__VUE_PROD_DEVTOOLS__ ??= false;
|
|
102
115
|
}
|
|
103
116
|
|
|
104
117
|
defineVueCompileFlags();
|
|
105
|
-
|
|
118
|
+
/** @type {ViteDevServer | undefined} */
|
|
106
119
|
let vite;
|
|
120
|
+
/** @type {Promise<ViteDevServer> | undefined} */
|
|
107
121
|
let vitePromise;
|
|
122
|
+
/** @type {import("node:http").Server | undefined} */
|
|
108
123
|
let frontendHttpServer;
|
|
109
124
|
|
|
125
|
+
/** @returns {Promise<ViteDevServer | undefined>} */
|
|
110
126
|
async function developmentServer() {
|
|
111
127
|
if (process.env.DMS_DEV !== "true") return undefined;
|
|
112
128
|
if (vite) return vite;
|
|
113
129
|
vitePromise ??= import("vite")
|
|
114
130
|
.then(({ createServer: createViteServer }) => {
|
|
131
|
+
/** @type {import("vite").ServerOptions} */
|
|
115
132
|
const serverOptions = {
|
|
116
133
|
middlewareMode: true,
|
|
117
134
|
fs: { deny: DEV_FS_DENY },
|
|
@@ -127,57 +144,24 @@ async function developmentServer() {
|
|
|
127
144
|
vite = server;
|
|
128
145
|
return server;
|
|
129
146
|
})
|
|
130
|
-
.catch(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
147
|
+
.catch(
|
|
148
|
+
/** @param {unknown} error */ (error) => {
|
|
149
|
+
vitePromise = undefined;
|
|
150
|
+
throw error;
|
|
151
|
+
},
|
|
152
|
+
);
|
|
134
153
|
return vitePromise;
|
|
135
154
|
}
|
|
136
155
|
|
|
137
|
-
/**
|
|
138
|
-
* The headers of a backend request: the relayed client headers, the client's
|
|
139
|
-
* Origin and Referer as received, the `X-Forwarded-*` headers (set last, so no
|
|
140
|
-
* caller writes them), and the session's access token unless the request goes
|
|
141
|
-
* anonymously.
|
|
142
|
-
*/
|
|
143
|
-
function backendHeaders(request, access = { anonymous: false, headers: [] }) {
|
|
144
|
-
const headers = {
|
|
145
|
-
...relayedHeaders(request, [
|
|
146
|
-
"accept",
|
|
147
|
-
"content-type",
|
|
148
|
-
"if-match",
|
|
149
|
-
"origin",
|
|
150
|
-
"referer",
|
|
151
|
-
...CLIENT_HEADERS,
|
|
152
|
-
...access.headers,
|
|
153
|
-
]),
|
|
154
|
-
...forwardedHeaders(request),
|
|
155
|
-
};
|
|
156
|
-
const session = access.anonymous ? undefined : readSession(request);
|
|
157
|
-
if (session?.accessToken)
|
|
158
|
-
headers.authorization = `Bearer ${session.accessToken}`;
|
|
159
|
-
return headers;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function serverComponentFetch(request) {
|
|
163
|
-
return serverReads(() => backendHeaders(request));
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
async function backendJson(path, request) {
|
|
167
|
-
const response = await fetch(new URL(path, process.env.DMS_API_BASE_URL), {
|
|
168
|
-
headers: backendHeaders(request),
|
|
169
|
-
});
|
|
170
|
-
if (!response.ok) {
|
|
171
|
-
throw await backendResponseError(response);
|
|
172
|
-
}
|
|
173
|
-
return response.json();
|
|
174
|
-
}
|
|
175
|
-
|
|
176
156
|
/** The session logic of the module that owns sign-in. */
|
|
177
157
|
async function currentAuth() {
|
|
178
158
|
return (await loadServerModules(await developmentServer())).serverAuth;
|
|
179
159
|
}
|
|
180
160
|
|
|
161
|
+
/**
|
|
162
|
+
* @param {ServerRequest} request
|
|
163
|
+
* @returns {import("./server/page-props.mjs").PageBackend}
|
|
164
|
+
*/
|
|
181
165
|
function pageBackend(request) {
|
|
182
166
|
return {
|
|
183
167
|
fetchJson: (path) => backendJson(path, request),
|
|
@@ -188,76 +172,23 @@ function pageBackend(request) {
|
|
|
188
172
|
};
|
|
189
173
|
}
|
|
190
174
|
|
|
191
|
-
function pageProps(request, auth) {
|
|
192
|
-
return loadPageProps(request, pageBackend(request), auth);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function redirectToHomepage(request, response, pathname) {
|
|
196
|
-
if (pathname !== "/" || HOMEPAGE === "/") return false;
|
|
197
|
-
redirectFrontendVisit(request, response, HOMEPAGE);
|
|
198
|
-
return true;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function redirectSignedOutPrivatePage(request, response, props, auth) {
|
|
202
|
-
const route = props.page?.route;
|
|
203
|
-
if (
|
|
204
|
-
route?.publicAccess === true ||
|
|
205
|
-
route?.hasAccess !== false ||
|
|
206
|
-
readSession(request)
|
|
207
|
-
)
|
|
208
|
-
return false;
|
|
209
|
-
redirectFrontendVisit(request, response, auth.signInLocation(request.url));
|
|
210
|
-
return true;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
175
|
/**
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
* the frontend's origin.
|
|
217
|
-
*
|
|
218
|
-
* A client that left while the request was routed closed the response already,
|
|
219
|
-
* and would never abort the request below.
|
|
220
|
-
*
|
|
221
|
-
* Cancelling the body alone misses a client that leaves before the backend
|
|
222
|
-
* answers: its stream, an SSE feed above all, then stayed open for good.
|
|
223
|
-
*
|
|
224
|
-
* A backend redirect is the browser's to follow: Media sends a file's link to
|
|
225
|
-
* the storage's signed URL, on the public origin the server itself may not
|
|
226
|
-
* reach, which turned every download into a 502.
|
|
176
|
+
* @param {ServerRequest} request
|
|
177
|
+
* @param {import("./server/page-props.mjs").PageAuth} auth
|
|
227
178
|
*/
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
if (access.isRefused) {
|
|
231
|
-
refuseCrossOrigin(response);
|
|
232
|
-
return true;
|
|
233
|
-
}
|
|
234
|
-
if (response.closed) return;
|
|
235
|
-
const abandoned = new AbortController();
|
|
236
|
-
response.once("close", () => abandoned.abort());
|
|
237
|
-
const init = {
|
|
238
|
-
signal: abandoned.signal,
|
|
239
|
-
method: request.method,
|
|
240
|
-
headers: { ...backendHeaders(request, access), ...PLAIN_ANSWER_HEADERS },
|
|
241
|
-
duplex: "half",
|
|
242
|
-
redirect: "manual",
|
|
243
|
-
};
|
|
244
|
-
const upstream = await sendWithinLimit(
|
|
245
|
-
request,
|
|
246
|
-
response,
|
|
247
|
-
backendProxy.maxBodyBytes,
|
|
248
|
-
(body) => fetch(backendUrl(request.url), { ...init, body }),
|
|
249
|
-
);
|
|
250
|
-
if (!upstream) return true;
|
|
251
|
-
await relayAnswer(request, response, upstream, {
|
|
252
|
-
fetched: true,
|
|
253
|
-
isProxiedPath,
|
|
254
|
-
});
|
|
179
|
+
function pageProps(request, auth) {
|
|
180
|
+
return loadPageProps(request, pageBackend(request), auth);
|
|
255
181
|
}
|
|
256
182
|
|
|
257
183
|
/**
|
|
258
184
|
* Send the built file `pathname` names, or `false` when the build has none
|
|
259
185
|
* there. `publicBuildFile` decides which file that is; `serveStaticFile` how
|
|
260
186
|
* it goes out.
|
|
187
|
+
*
|
|
188
|
+
* @param {string} pathname
|
|
189
|
+
* @param {IncomingMessage} request
|
|
190
|
+
* @param {ServerResponse} response
|
|
191
|
+
* @returns {boolean}
|
|
261
192
|
*/
|
|
262
193
|
function serveAsset(pathname, request, response) {
|
|
263
194
|
const file = publicBuildFile(CLIENT_ROOT, pathname);
|
|
@@ -268,14 +199,10 @@ function serveAsset(pathname, request, response) {
|
|
|
268
199
|
return true;
|
|
269
200
|
}
|
|
270
201
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
await icons.provideIconCollections(renderer);
|
|
276
|
-
return renderer;
|
|
277
|
-
}
|
|
278
|
-
|
|
202
|
+
/**
|
|
203
|
+
* @param {InertiaPage} page
|
|
204
|
+
* @returns {import("./ssr-renderer.ts").DmsSsrResult}
|
|
205
|
+
*/
|
|
279
206
|
function clientRenderedShell(page) {
|
|
280
207
|
return {
|
|
281
208
|
body: inertiaAppHtml(page),
|
|
@@ -285,9 +212,17 @@ function clientRenderedShell(page) {
|
|
|
285
212
|
};
|
|
286
213
|
}
|
|
287
214
|
|
|
215
|
+
/**
|
|
216
|
+
* @param {InertiaPage} page
|
|
217
|
+
* @param {ServerReads} serverFetch
|
|
218
|
+
* @param {string | undefined} requestCookies
|
|
219
|
+
* @param {ViteDevServer | undefined} devServer
|
|
220
|
+
*/
|
|
288
221
|
async function serverRendered(page, serverFetch, requestCookies, devServer) {
|
|
289
222
|
try {
|
|
290
223
|
const renderer = await ssrRenderer(devServer);
|
|
224
|
+
if (!isSsrPage(page))
|
|
225
|
+
throw new TypeError("The page has no path and no page props to render");
|
|
291
226
|
return await renderer.renderDmsPage(page, serverFetch, requestCookies);
|
|
292
227
|
} catch (error) {
|
|
293
228
|
console.error(
|
|
@@ -301,6 +236,13 @@ async function serverRendered(page, serverFetch, requestCookies, devServer) {
|
|
|
301
236
|
/**
|
|
302
237
|
* Renders a page document. With `{ ssr: false }` (a route rule), the client
|
|
303
238
|
* renders the page from the shell instead.
|
|
239
|
+
*
|
|
240
|
+
* @param {InertiaPage} page
|
|
241
|
+
* @param {string} requestUrl
|
|
242
|
+
* @param {ServerReads} serverFetch
|
|
243
|
+
* @param {string | undefined} requestCookies
|
|
244
|
+
* @param {{ ssr?: false }} [options]
|
|
245
|
+
* @returns {Promise<{ html: string, status: number, redirect?: string }>}
|
|
304
246
|
*/
|
|
305
247
|
export async function renderHtml(
|
|
306
248
|
page,
|
|
@@ -330,7 +272,7 @@ export async function renderHtml(
|
|
|
330
272
|
.replace("<title>Antelope DMS</title>", () =>
|
|
331
273
|
withFaviconPlaceholder(rendered.head.headTags),
|
|
332
274
|
)
|
|
333
|
-
.replace(/<html([^>]*)>/, (_, attributes) =>
|
|
275
|
+
.replace(/<html([^>]*)>/, (_, /** @type {string} */ attributes) =>
|
|
334
276
|
htmlTag(attributes, rendered.head.htmlAttrs),
|
|
335
277
|
)
|
|
336
278
|
.replace("<body", `<body ${rendered.head.bodyAttrs}`)
|
|
@@ -343,6 +285,12 @@ export async function renderHtml(
|
|
|
343
285
|
return { html, status: rendered.error?.statusCode ?? 200 };
|
|
344
286
|
}
|
|
345
287
|
|
|
288
|
+
/**
|
|
289
|
+
* @param {InertiaPage} page
|
|
290
|
+
* @param {ServerRequest} request
|
|
291
|
+
* @param {Pick<import("./server/route-rules.mjs").RouteRule, "ssr">} [rule]
|
|
292
|
+
* @param {ServerReads} [reads]
|
|
293
|
+
*/
|
|
346
294
|
function renderRequestHtml(page, request, rule = {}, reads) {
|
|
347
295
|
const fetch = reads ?? serverComponentFetch(request);
|
|
348
296
|
return renderHtml(page, request.url, fetch, request.headers.cookie, {
|
|
@@ -353,6 +301,11 @@ function renderRequestHtml(page, request, rule = {}, reads) {
|
|
|
353
301
|
/**
|
|
354
302
|
* Renders a page document, at the query the page completes as it renders:
|
|
355
303
|
* see `renderInPlace`.
|
|
304
|
+
*
|
|
305
|
+
* @param {InertiaPage} page
|
|
306
|
+
* @param {ServerRequest} request
|
|
307
|
+
* @param {import("./server/route-rules.mjs").MergedRouteRule} rule
|
|
308
|
+
* @param {import("./server/page-props.mjs").PageAuth} auth
|
|
356
309
|
*/
|
|
357
310
|
function renderDocument(page, request, rule, auth) {
|
|
358
311
|
const fetch = serverComponentFetch(request);
|
|
@@ -365,9 +318,14 @@ function renderDocument(page, request, rule, auth) {
|
|
|
365
318
|
);
|
|
366
319
|
}
|
|
367
320
|
|
|
321
|
+
/**
|
|
322
|
+
* @param {unknown} error
|
|
323
|
+
* @param {ServerRequest} request
|
|
324
|
+
* @param {ServerResponse} response
|
|
325
|
+
*/
|
|
368
326
|
async function writeBackendError(error, request, response) {
|
|
369
327
|
const auth = await currentAuth();
|
|
370
|
-
const status =
|
|
328
|
+
const status = requestFailure.failureStatus(error);
|
|
371
329
|
const pathname = new URL(request.url, "http://frontend.local").pathname;
|
|
372
330
|
if (
|
|
373
331
|
error instanceof BackendResponseError &&
|
|
@@ -410,6 +368,12 @@ async function writeBackendError(error, request, response) {
|
|
|
410
368
|
response.end(JSON.stringify(payload));
|
|
411
369
|
}
|
|
412
370
|
|
|
371
|
+
/**
|
|
372
|
+
* @param {ViteDevServer} devServer
|
|
373
|
+
* @param {IncomingMessage} request
|
|
374
|
+
* @param {ServerResponse} response
|
|
375
|
+
* @returns {Promise<boolean>}
|
|
376
|
+
*/
|
|
413
377
|
async function answeredByVite(devServer, request, response) {
|
|
414
378
|
const result = await runViteMiddlewares(devServer, request, response);
|
|
415
379
|
if (result.status === "failed") writeViteError(response, result.error);
|
|
@@ -422,6 +386,10 @@ async function answeredByVite(devServer, request, response) {
|
|
|
422
386
|
* production server serves. Only a path with an extension names one.
|
|
423
387
|
*
|
|
424
388
|
* The path is decoded as Vite and the asset server read it: `%2E` is a dot.
|
|
389
|
+
*
|
|
390
|
+
* @param {ViteDevServer | undefined} devServer
|
|
391
|
+
* @param {string} requested
|
|
392
|
+
* @returns {boolean}
|
|
425
393
|
*/
|
|
426
394
|
function hasRendererFile(devServer, requested) {
|
|
427
395
|
let pathname;
|
|
@@ -445,7 +413,15 @@ function hasRendererFile(devServer, requested) {
|
|
|
445
413
|
});
|
|
446
414
|
}
|
|
447
415
|
|
|
448
|
-
/**
|
|
416
|
+
/**
|
|
417
|
+
* Vite's files in development, the build's in production; else a 404.
|
|
418
|
+
*
|
|
419
|
+
* @param {ViteDevServer | undefined} devServer
|
|
420
|
+
* @param {IncomingMessage} request
|
|
421
|
+
* @param {ServerResponse} response
|
|
422
|
+
* @param {string} pathname
|
|
423
|
+
* @returns {Promise<void>}
|
|
424
|
+
*/
|
|
449
425
|
async function serveRendererFile(devServer, request, response, pathname) {
|
|
450
426
|
const answered = devServer
|
|
451
427
|
? await answeredByVite(devServer, request, response)
|
|
@@ -455,11 +431,16 @@ async function serveRendererFile(devServer, request, response, pathname) {
|
|
|
455
431
|
response.end("Not found");
|
|
456
432
|
}
|
|
457
433
|
|
|
434
|
+
/** @param {ServerResponse} response */
|
|
458
435
|
function writeNotFound(response) {
|
|
459
436
|
response.writeHead(404, { "content-type": JSON_TYPE });
|
|
460
437
|
response.end(JSON.stringify({ message: "Not found" }));
|
|
461
438
|
}
|
|
462
439
|
|
|
440
|
+
/**
|
|
441
|
+
* @param {ServerRequest} request
|
|
442
|
+
* @param {ServerResponse} response
|
|
443
|
+
*/
|
|
463
444
|
export async function handleRequest(request, response) {
|
|
464
445
|
const url = new URL(request.url, "http://frontend.local");
|
|
465
446
|
const pathname = url.pathname;
|
|
@@ -467,6 +448,7 @@ export async function handleRequest(request, response) {
|
|
|
467
448
|
if (localRoute) return localRoute[1](request, response);
|
|
468
449
|
const devServer = await developmentServer();
|
|
469
450
|
const modules = await loadServerModules(devServer);
|
|
451
|
+
/** @param {string} path */
|
|
470
452
|
const hasFile = (path) => hasRendererFile(devServer, path);
|
|
471
453
|
const owner = requestOwner(request.method, pathname, modules, hasFile);
|
|
472
454
|
if (devServer) response.setHeader("x-dms-route-owner", ownerLabel(owner));
|
|
@@ -474,6 +456,7 @@ export async function handleRequest(request, response) {
|
|
|
474
456
|
return serveRendererFile(devServer, request, response, pathname);
|
|
475
457
|
if (owner.kind === "module")
|
|
476
458
|
return handleModuleRoute(request, response, pathname, modules);
|
|
459
|
+
/** @param {string} path */
|
|
477
460
|
const isProxiedPath = (path) => reachesBackend(path, modules, hasFile);
|
|
478
461
|
if (owner.kind === "backend")
|
|
479
462
|
return proxy(request, response, owner.proxy, isProxiedPath);
|
|
@@ -487,8 +470,9 @@ export async function handleRequest(request, response) {
|
|
|
487
470
|
if (redirectSignedOutPrivatePage(request, response, props, auth)) return;
|
|
488
471
|
const page = createInertiaPage(request.url, props, undefined, onceProps);
|
|
489
472
|
const rule = routeRuleFor(rules, pathname);
|
|
473
|
+
/** @param {number} status */
|
|
490
474
|
const headersFor = (status) =>
|
|
491
|
-
ruleHeaders(rule, request, props
|
|
475
|
+
ruleHeaders(rule, request, pageRoute(props), status);
|
|
492
476
|
if (request.headers[INERTIA_HEADER]) {
|
|
493
477
|
return writeContent(
|
|
494
478
|
request,
|
|
@@ -513,8 +497,15 @@ export async function handleRequest(request, response) {
|
|
|
513
497
|
);
|
|
514
498
|
}
|
|
515
499
|
|
|
500
|
+
/**
|
|
501
|
+
* @param {unknown} error
|
|
502
|
+
* @param {IncomingMessage & { dmsSessionRetry?: boolean }} request
|
|
503
|
+
* @param {ServerResponse} response
|
|
504
|
+
*/
|
|
516
505
|
async function writeRequestFailure(error, request, response) {
|
|
517
506
|
if (requestFailure.isResponseGone(error, response)) return;
|
|
507
|
+
if (!isServerRequest(request))
|
|
508
|
+
return requestFailure.abandonResponse(error, response);
|
|
518
509
|
if (
|
|
519
510
|
error instanceof BackendResponseError &&
|
|
520
511
|
error.status === 401 &&
|
|
@@ -531,14 +522,24 @@ async function writeRequestFailure(error, request, response) {
|
|
|
531
522
|
await writeBackendError(error, request, response);
|
|
532
523
|
}
|
|
533
524
|
|
|
534
|
-
/**
|
|
525
|
+
/**
|
|
526
|
+
* An error page failing too must not reject: that would end the process.
|
|
527
|
+
*
|
|
528
|
+
* @param {IncomingMessage} request
|
|
529
|
+
* @param {ServerResponse} response
|
|
530
|
+
* @returns {Promise<void>}
|
|
531
|
+
*/
|
|
535
532
|
export async function handleRequestSafely(request, response) {
|
|
536
533
|
try {
|
|
537
|
-
if (
|
|
534
|
+
if (!hasOriginFormTarget(request)) {
|
|
535
|
+
requestFailure.writeBadRequest(response);
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
538
|
await handleRequest(request, response);
|
|
539
539
|
} catch (error) {
|
|
540
|
-
await writeRequestFailure(error, request, response).catch(
|
|
541
|
-
|
|
540
|
+
await writeRequestFailure(error, request, response).catch(
|
|
541
|
+
/** @param {unknown} failure */ (failure) =>
|
|
542
|
+
requestFailure.abandonResponse(failure, response),
|
|
542
543
|
);
|
|
543
544
|
}
|
|
544
545
|
}
|
|
@@ -548,25 +549,26 @@ if (
|
|
|
548
549
|
import.meta.url === new URL(`file://${process.argv[1]}`).href
|
|
549
550
|
) {
|
|
550
551
|
frontendHttpServer = createServer(SERVER_OPTIONS, handleRequestSafely);
|
|
551
|
-
|
|
552
|
+
const server = frontendHttpServer;
|
|
553
|
+
server.on("clientError", (error, socket) =>
|
|
552
554
|
handleClientError(error, socket, maxHeaderSize),
|
|
553
555
|
);
|
|
554
556
|
const port = Number(process.env.PORT ?? 3001);
|
|
555
557
|
const host = listenHost();
|
|
558
|
+
/** @param {NodeJS.ErrnoException} error */
|
|
556
559
|
const onListenError = (error) => void reportListenError(error, host, port);
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
+
server.once("error", onListenError);
|
|
561
|
+
server.listen(port, host, () => {
|
|
562
|
+
server.off("error", onListenError);
|
|
560
563
|
void announceReady();
|
|
561
564
|
});
|
|
562
565
|
}
|
|
563
566
|
|
|
564
567
|
/** Prints who owns which paths, once the server is up. */
|
|
565
|
-
async function
|
|
568
|
+
async function reportServerModules() {
|
|
566
569
|
try {
|
|
567
570
|
const modules = await loadServerModules(await developmentServer());
|
|
568
|
-
|
|
569
|
-
console.log(line);
|
|
571
|
+
reportRouting(routingReport(modules.proxies, modules.routes.length));
|
|
570
572
|
} catch (error) {
|
|
571
573
|
console.error("DMS server modules failed to load", error);
|
|
572
574
|
}
|
|
@@ -587,8 +589,11 @@ async function announceReady() {
|
|
|
587
589
|
await reportStartError(error);
|
|
588
590
|
return;
|
|
589
591
|
}
|
|
590
|
-
const
|
|
592
|
+
const listening = frontendHttpServer?.address();
|
|
593
|
+
if (!listening || isString(listening))
|
|
594
|
+
throw new TypeError("The DMS server is not listening on a port");
|
|
595
|
+
const { address, port } = listening;
|
|
591
596
|
await reportReady(address, port);
|
|
592
|
-
await
|
|
597
|
+
await reportServerModules();
|
|
593
598
|
if (process.env.DMS_DEV === "true") watchEmailBundle();
|
|
594
599
|
}
|