@antelopejs/dms-frontend 0.10.3 → 0.10.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config.js +1 -0
- package/dist/manifest.d.ts +1 -1
- package/dist/manifest.js +5 -5
- package/dist/output.d.ts +1 -1
- package/dist/output.js +6 -6
- package/dist/verify-source-runner.js +8 -1
- package/package.json +7 -1
- package/templates/eslint-globals.mjs +133 -0
- package/templates/eslint-globals.types.d.mts +6 -0
- package/templates/vue/auto-imports.mjs +54 -0
- package/templates/vue/server/auth/session.mjs +52 -21
- package/templates/vue/server/module-event.mjs +3 -3
- package/templates/vue/server/module-server.mjs +14 -6
- package/templates/vue/server/ssr-module.mjs +6 -5
- package/templates/vue/vite.config.ts +4 -50
package/dist/config.js
CHANGED
|
@@ -74,6 +74,7 @@ exports.TEMPLATE_FILES = [
|
|
|
74
74
|
["sfc-global-types.mjs", "sfc-global-types.mjs"],
|
|
75
75
|
["icon-api.mjs", "icon-api.mjs"],
|
|
76
76
|
["icon-scan.mjs", "icon-scan.mjs"],
|
|
77
|
+
["auto-imports.mjs", "auto-imports.mjs"],
|
|
77
78
|
["ui-kbd.mjs", "ui-kbd.mjs"],
|
|
78
79
|
["ui-lazy-icons.mjs", "ui-lazy-icons.mjs"],
|
|
79
80
|
["email-renderer.ts", "email-renderer.ts"],
|
package/dist/manifest.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export declare class ManifestUnauthorizedError extends CliError {
|
|
|
8
8
|
export declare class BackendUnreachableError extends CliError {
|
|
9
9
|
readonly url: string;
|
|
10
10
|
readonly code?: string;
|
|
11
|
-
constructor(url: string,
|
|
11
|
+
constructor(url: string, cause: unknown);
|
|
12
12
|
}
|
|
13
13
|
export declare function displayUrl(url: string | URL): string;
|
|
14
14
|
export declare function backendEndpoint(backendUrl: string, path: string): URL;
|
package/dist/manifest.js
CHANGED
|
@@ -47,10 +47,10 @@ const UNREACHABLE_REASONS = new Map([
|
|
|
47
47
|
]);
|
|
48
48
|
const UNKNOWN_HOST_CODES = ["ENOTFOUND", "EAI_AGAIN"];
|
|
49
49
|
class BackendUnreachableError extends cli_1.CliError {
|
|
50
|
-
constructor(url,
|
|
51
|
-
const
|
|
52
|
-
const code = hasProperties(
|
|
53
|
-
const message = () =>
|
|
50
|
+
constructor(url, cause) {
|
|
51
|
+
const reason = (hasProperties(cause) ? cause.cause : undefined) ?? cause;
|
|
52
|
+
const code = hasProperties(reason) ? errorCode(reason) : undefined;
|
|
53
|
+
const message = () => reason instanceof Error ? reason.message : String(reason);
|
|
54
54
|
const detail = code
|
|
55
55
|
? (UNREACHABLE_REASONS.get(code) ?? message())
|
|
56
56
|
: `The request failed: ${message()}`;
|
|
@@ -61,7 +61,7 @@ class BackendUnreachableError extends cli_1.CliError {
|
|
|
61
61
|
title: `Cannot reach the DMS backend at ${url}`,
|
|
62
62
|
reason: `${code ? `${detail} (${code})` : detail}.`,
|
|
63
63
|
fixes: [fix],
|
|
64
|
-
}, { cause
|
|
64
|
+
}, { cause });
|
|
65
65
|
this.url = url;
|
|
66
66
|
this.name = "BackendUnreachableError";
|
|
67
67
|
this.code = code;
|
package/dist/output.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ export type Failure = {
|
|
|
25
25
|
kind: "thrown";
|
|
26
26
|
text: string;
|
|
27
27
|
};
|
|
28
|
-
export declare function classifyFailure(
|
|
28
|
+
export declare function classifyFailure(cause: unknown): Failure;
|
|
29
29
|
export declare function failureDetails(failure: Failure, ui?: Ui): string[];
|
|
30
30
|
export declare function endInterruptedTask(task: TaskHandle, error: CancelledError | undefined, stopped: string): void;
|
|
31
31
|
export declare function reportStopped(stop: CancelledError, ui?: Ui): void;
|
package/dist/output.js
CHANGED
|
@@ -140,12 +140,12 @@ function formatAge(iso, now = Date.now()) {
|
|
|
140
140
|
function cachedAge(fetchedAt) {
|
|
141
141
|
return fetchedAt ? ` cached ${formatAge(fetchedAt)}` : "";
|
|
142
142
|
}
|
|
143
|
-
function classifyFailure(
|
|
144
|
-
if (
|
|
145
|
-
return { kind: "cli", error:
|
|
146
|
-
if (
|
|
147
|
-
return { kind: "error", error:
|
|
148
|
-
return { kind: "thrown", text: String(
|
|
143
|
+
function classifyFailure(cause) {
|
|
144
|
+
if (cause instanceof cli_1.CliError)
|
|
145
|
+
return { kind: "cli", error: cause };
|
|
146
|
+
if (cause instanceof Error)
|
|
147
|
+
return { kind: "error", error: cause };
|
|
148
|
+
return { kind: "thrown", text: String(cause) };
|
|
149
149
|
}
|
|
150
150
|
function failureDetails(failure, ui = (0, cli_1.getProcessUi)()) {
|
|
151
151
|
const { title, reason, fixes = [], details = [], } = failure.kind === "cli"
|
|
@@ -62,6 +62,9 @@ async function checkServerModulesBundle(workspace) {
|
|
|
62
62
|
(0, ensure_1.ensure)(Array.isArray(serverModules), "The server modules bundle exports none");
|
|
63
63
|
return `${(0, cli_1.pluralize)(serverModules.length, "server module")}, ${(0, output_1.formatSize)(Buffer.byteLength(javascript))} JS`;
|
|
64
64
|
}
|
|
65
|
+
function isFunction(value) {
|
|
66
|
+
return typeof value === "function";
|
|
67
|
+
}
|
|
65
68
|
const EMAIL_CASES = [
|
|
66
69
|
{
|
|
67
70
|
name: "EmailAdminInvite",
|
|
@@ -141,7 +144,11 @@ async function checkEmailContracts(workspace) {
|
|
|
141
144
|
const emailLocaleBytes = (0, node_fs_1.readdirSync)((0, node_path_1.join)(emailRoot, "locales")).reduce((total, file) => total + (0, node_fs_1.readFileSync)((0, node_path_1.join)(emailRoot, "locales", file)).byteLength, 0);
|
|
142
145
|
const limit = (0, output_1.formatSize)(EMAIL_BUILD_SIZE_CEILING_BYTES);
|
|
143
146
|
(0, ensure_1.ensure)(emailBuildSize <= EMAIL_BUILD_SIZE_CEILING_BYTES, `The e-mail bundle is ${(0, output_1.formatSize)(emailBuildSize)} of JavaScript`, `The limit is ${limit}.`);
|
|
144
|
-
const
|
|
147
|
+
const emailRenderer = await Promise.resolve(`${(0, node_path_1.join)(emailRoot, "email-renderer.js")}`).then(s => __importStar(require(s)));
|
|
148
|
+
(0, ensure_1.ensure)((0, guards_1.isObjectLike)(emailRenderer) &&
|
|
149
|
+
"renderEmail" in emailRenderer &&
|
|
150
|
+
isFunction(emailRenderer.renderEmail), "The e-mail bundle exports no renderEmail function");
|
|
151
|
+
const { renderEmail } = emailRenderer;
|
|
145
152
|
for (const emailCase of EMAIL_CASES) {
|
|
146
153
|
const html = await renderEmail(emailCase.name, emailCase.props);
|
|
147
154
|
(0, ensure_1.ensure)((0, guards_1.isText)(html) && html.startsWith("<!doctype html>"), `${emailCase.name} does not render a full HTML document`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antelopejs/dms-frontend",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.5",
|
|
4
4
|
"description": "Frontend-agnostic loader for AntelopeJS DMS, shipping the Vue 3 renderer (Vite, Inertia, SSR)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"antelope",
|
|
@@ -44,6 +44,10 @@
|
|
|
44
44
|
"types": "./dist/common.d.ts",
|
|
45
45
|
"default": "./dist/common.js"
|
|
46
46
|
},
|
|
47
|
+
"./eslint-globals": {
|
|
48
|
+
"types": "./templates/eslint-globals.types.d.mts",
|
|
49
|
+
"default": "./templates/eslint-globals.mjs"
|
|
50
|
+
},
|
|
47
51
|
"./dist/*.js": {
|
|
48
52
|
"types": "./dist/*.d.ts",
|
|
49
53
|
"default": "./dist/*.js"
|
|
@@ -71,6 +75,7 @@
|
|
|
71
75
|
"knip": "knip --dependencies",
|
|
72
76
|
"knip:all": "knip",
|
|
73
77
|
"docs:reference": "tsx scripts/docs-reference.ts",
|
|
78
|
+
"eslint-globals": "tsx scripts/eslint-globals.ts",
|
|
74
79
|
"docs:reference:check": "tsx scripts/docs-reference.ts --check",
|
|
75
80
|
"source-checks": "tsx scripts/source-checks.ts",
|
|
76
81
|
"prepare": "if [ -e .git ] && command -v git >/dev/null 2>&1 && command -v lefthook >/dev/null 2>&1 && GIT_DIR=.git GIT_WORK_TREE=. git rev-parse --git-dir >/dev/null 2>&1; then GIT_DIR=.git GIT_WORK_TREE=. lefthook install; fi",
|
|
@@ -109,6 +114,7 @@
|
|
|
109
114
|
"knip": "^6.34.0",
|
|
110
115
|
"lefthook": "^2.2.2",
|
|
111
116
|
"mongodb-memory-server": "^11.3.0",
|
|
117
|
+
"nuxt-ui-auto-imports": "npm:@nuxt/ui@4.10.0",
|
|
112
118
|
"ofetch": "^1.5.1",
|
|
113
119
|
"oxfmt": "^0.66.0",
|
|
114
120
|
"oxlint": "1.81.0",
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint flat-config globals for every name the DMS renderer auto-imports
|
|
3
|
+
* into a module's sources: Vue's preset, `frontend-module.ts` and Nuxt
|
|
4
|
+
* UI's composables. Written by `pnpm eslint-globals`.
|
|
5
|
+
*/
|
|
6
|
+
export default Object.freeze({
|
|
7
|
+
$fetch: "readonly",
|
|
8
|
+
Component: "readonly",
|
|
9
|
+
ComponentPublicInstance: "readonly",
|
|
10
|
+
ComputedRef: "readonly",
|
|
11
|
+
DirectiveBinding: "readonly",
|
|
12
|
+
DmsClientOnly: "readonly",
|
|
13
|
+
DmsLink: "readonly",
|
|
14
|
+
EffectScope: "readonly",
|
|
15
|
+
ExtractDefaultPropTypes: "readonly",
|
|
16
|
+
ExtractPropTypes: "readonly",
|
|
17
|
+
ExtractPublicPropTypes: "readonly",
|
|
18
|
+
InjectionKey: "readonly",
|
|
19
|
+
MaybeRef: "readonly",
|
|
20
|
+
MaybeRefOrGetter: "readonly",
|
|
21
|
+
PropType: "readonly",
|
|
22
|
+
Ref: "readonly",
|
|
23
|
+
ShallowRef: "readonly",
|
|
24
|
+
Slot: "readonly",
|
|
25
|
+
Slots: "readonly",
|
|
26
|
+
VNode: "readonly",
|
|
27
|
+
WritableComputedRef: "readonly",
|
|
28
|
+
abortDmsNavigation: "readonly",
|
|
29
|
+
clearDmsError: "readonly",
|
|
30
|
+
computed: "readonly",
|
|
31
|
+
createApp: "readonly",
|
|
32
|
+
createDmsError: "readonly",
|
|
33
|
+
customRef: "readonly",
|
|
34
|
+
defineAsyncComponent: "readonly",
|
|
35
|
+
defineComponent: "readonly",
|
|
36
|
+
defineDmsAppConfig: "readonly",
|
|
37
|
+
defineDmsMiddleware: "readonly",
|
|
38
|
+
defineDmsPageMeta: "readonly",
|
|
39
|
+
defineDmsPlugin: "readonly",
|
|
40
|
+
defineLocale: "readonly",
|
|
41
|
+
defineShortcuts: "readonly",
|
|
42
|
+
dmsRequestKey: "readonly",
|
|
43
|
+
effectScope: "readonly",
|
|
44
|
+
extendLocale: "readonly",
|
|
45
|
+
extractShortcuts: "readonly",
|
|
46
|
+
getCurrentInstance: "readonly",
|
|
47
|
+
getCurrentScope: "readonly",
|
|
48
|
+
getCurrentWatcher: "readonly",
|
|
49
|
+
h: "readonly",
|
|
50
|
+
inject: "readonly",
|
|
51
|
+
isDmsHydrating: "readonly",
|
|
52
|
+
isProxy: "readonly",
|
|
53
|
+
isReactive: "readonly",
|
|
54
|
+
isReadonly: "readonly",
|
|
55
|
+
isRef: "readonly",
|
|
56
|
+
isShallow: "readonly",
|
|
57
|
+
markRaw: "readonly",
|
|
58
|
+
navigateDms: "readonly",
|
|
59
|
+
nextTick: "readonly",
|
|
60
|
+
onActivated: "readonly",
|
|
61
|
+
onBeforeMount: "readonly",
|
|
62
|
+
onBeforeUnmount: "readonly",
|
|
63
|
+
onBeforeUpdate: "readonly",
|
|
64
|
+
onDeactivated: "readonly",
|
|
65
|
+
onErrorCaptured: "readonly",
|
|
66
|
+
onMounted: "readonly",
|
|
67
|
+
onRenderTracked: "readonly",
|
|
68
|
+
onRenderTriggered: "readonly",
|
|
69
|
+
onScopeDispose: "readonly",
|
|
70
|
+
onServerPrefetch: "readonly",
|
|
71
|
+
onUnmounted: "readonly",
|
|
72
|
+
onUpdated: "readonly",
|
|
73
|
+
onWatcherCleanup: "readonly",
|
|
74
|
+
preloadDmsComponents: "readonly",
|
|
75
|
+
provide: "readonly",
|
|
76
|
+
provideDmsKbdPlatform: "readonly",
|
|
77
|
+
reactive: "readonly",
|
|
78
|
+
readonly: "readonly",
|
|
79
|
+
ref: "readonly",
|
|
80
|
+
refreshDmsData: "readonly",
|
|
81
|
+
refreshDmsPageData: "readonly",
|
|
82
|
+
resolveComponent: "readonly",
|
|
83
|
+
setDmsAsyncDataScope: "readonly",
|
|
84
|
+
shallowReactive: "readonly",
|
|
85
|
+
shallowReadonly: "readonly",
|
|
86
|
+
shallowRef: "readonly",
|
|
87
|
+
showDmsError: "readonly",
|
|
88
|
+
toRaw: "readonly",
|
|
89
|
+
toRef: "readonly",
|
|
90
|
+
toRefs: "readonly",
|
|
91
|
+
toValue: "readonly",
|
|
92
|
+
triggerRef: "readonly",
|
|
93
|
+
unref: "readonly",
|
|
94
|
+
useAppConfig: "readonly",
|
|
95
|
+
useAttrs: "readonly",
|
|
96
|
+
useContentSearch: "readonly",
|
|
97
|
+
useCssModule: "readonly",
|
|
98
|
+
useCssVars: "readonly",
|
|
99
|
+
useDmsApp: "readonly",
|
|
100
|
+
useDmsAppConfig: "readonly",
|
|
101
|
+
useDmsAsyncData: "readonly",
|
|
102
|
+
useDmsCookie: "readonly",
|
|
103
|
+
useDmsData: "readonly",
|
|
104
|
+
useDmsError: "readonly",
|
|
105
|
+
useDmsFetch: "readonly",
|
|
106
|
+
useDmsPageLoading: "readonly",
|
|
107
|
+
useDmsPagedData: "readonly",
|
|
108
|
+
useDmsRoute: "readonly",
|
|
109
|
+
useDmsRouter: "readonly",
|
|
110
|
+
useDmsRuntimeConfig: "readonly",
|
|
111
|
+
useDmsState: "readonly",
|
|
112
|
+
useDmsUserSession: "readonly",
|
|
113
|
+
useFileUpload: "readonly",
|
|
114
|
+
useFormField: "readonly",
|
|
115
|
+
useHead: "readonly",
|
|
116
|
+
useI18n: "readonly",
|
|
117
|
+
useId: "readonly",
|
|
118
|
+
useKbd: "readonly",
|
|
119
|
+
useModel: "readonly",
|
|
120
|
+
useOverlay: "readonly",
|
|
121
|
+
useResizable: "readonly",
|
|
122
|
+
useScrollShadow: "readonly",
|
|
123
|
+
useScrollspy: "readonly",
|
|
124
|
+
useSeoMeta: "readonly",
|
|
125
|
+
useSlots: "readonly",
|
|
126
|
+
useTemplateRef: "readonly",
|
|
127
|
+
useToast: "readonly",
|
|
128
|
+
useTour: "readonly",
|
|
129
|
+
watch: "readonly",
|
|
130
|
+
watchEffect: "readonly",
|
|
131
|
+
watchPostEffect: "readonly",
|
|
132
|
+
watchSyncEffect: "readonly",
|
|
133
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/** The names `frontend-module.ts` gives every module's sources without an import. */
|
|
2
|
+
const FRONTEND_MODULE_IMPORTS = [
|
|
3
|
+
"$fetch",
|
|
4
|
+
"abortDmsNavigation",
|
|
5
|
+
"clearDmsError",
|
|
6
|
+
"createDmsError",
|
|
7
|
+
"dmsRequestKey",
|
|
8
|
+
"defineDmsAppConfig",
|
|
9
|
+
"defineDmsPlugin",
|
|
10
|
+
"defineDmsMiddleware",
|
|
11
|
+
"defineDmsPageMeta",
|
|
12
|
+
"navigateDms",
|
|
13
|
+
"preloadDmsComponents",
|
|
14
|
+
"refreshDmsData",
|
|
15
|
+
"refreshDmsPageData",
|
|
16
|
+
"setDmsAsyncDataScope",
|
|
17
|
+
"showDmsError",
|
|
18
|
+
"useDmsAsyncData",
|
|
19
|
+
"useDmsData",
|
|
20
|
+
"useDmsCookie",
|
|
21
|
+
"useDmsFetch",
|
|
22
|
+
"useHead",
|
|
23
|
+
"useI18n",
|
|
24
|
+
"useDmsError",
|
|
25
|
+
"useDmsPagedData",
|
|
26
|
+
"useDmsPageLoading",
|
|
27
|
+
"isDmsHydrating",
|
|
28
|
+
"useDmsApp",
|
|
29
|
+
"useDmsRoute",
|
|
30
|
+
"useDmsRouter",
|
|
31
|
+
"useDmsRuntimeConfig",
|
|
32
|
+
"useSeoMeta",
|
|
33
|
+
"useDmsState",
|
|
34
|
+
"useDmsAppConfig",
|
|
35
|
+
"DmsClientOnly",
|
|
36
|
+
"DmsLink",
|
|
37
|
+
"useDmsUserSession",
|
|
38
|
+
"provideDmsKbdPlatform",
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The `imports` of the workspace's auto-importer: Vue's preset and the names
|
|
43
|
+
* of `frontend-module.ts`. Nuxt UI adds its own composables to them.
|
|
44
|
+
*
|
|
45
|
+
* The auto-importer inlines `frontendModule` verbatim as the specifier of the
|
|
46
|
+
* import it prepends to each file, inside a single-quoted JavaScript string:
|
|
47
|
+
* pass a POSIX path, since a Windows backslash there reads as an escape.
|
|
48
|
+
*
|
|
49
|
+
* @param {string} frontendModule The import specifier of `frontend-module.ts`.
|
|
50
|
+
* @returns {["vue", Record<string, string[]>]}
|
|
51
|
+
*/
|
|
52
|
+
export function autoImportSources(frontendModule) {
|
|
53
|
+
return ["vue", { [frontendModule]: FRONTEND_MODULE_IMPORTS }];
|
|
54
|
+
}
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
createHash,
|
|
5
5
|
randomBytes,
|
|
6
6
|
} from "node:crypto";
|
|
7
|
+
import { isBoolean, isNumber, isRecord, isString } from "../type-guards.mjs";
|
|
7
8
|
|
|
8
9
|
export const SESSION_COOKIE = "dms_session";
|
|
9
10
|
const REQUEST_SESSION = Symbol("dms-request-session");
|
|
@@ -66,12 +67,17 @@ function key() {
|
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
/**
|
|
70
|
+
* Seals `value` for the cookie `name` alone: the name is the AES-GCM
|
|
71
|
+
* additional authenticated data, so the value opens under no other cookie.
|
|
72
|
+
*
|
|
73
|
+
* @param {string} name The cookie's name in the jar
|
|
69
74
|
* @param {unknown} value A session, or the value of a module's sealed cookie
|
|
70
75
|
* @returns {string}
|
|
71
76
|
*/
|
|
72
|
-
function encode(value) {
|
|
77
|
+
function encode(name, value) {
|
|
73
78
|
const iv = randomBytes(12);
|
|
74
79
|
const cipher = createCipheriv("aes-256-gcm", key(), iv);
|
|
80
|
+
cipher.setAAD(Buffer.from(name));
|
|
75
81
|
const encrypted = Buffer.concat([
|
|
76
82
|
cipher.update(JSON.stringify(value)),
|
|
77
83
|
cipher.final(),
|
|
@@ -82,11 +88,12 @@ function encode(value) {
|
|
|
82
88
|
}
|
|
83
89
|
|
|
84
90
|
/**
|
|
91
|
+
* @param {string} name The cookie's name in the jar
|
|
85
92
|
* @param {string} value The cookie's value
|
|
86
|
-
* @returns {
|
|
87
|
-
* when it does not open
|
|
93
|
+
* @returns {unknown} What was sealed for `name`, as the JSON it was written
|
|
94
|
+
* from, `undefined` when it does not open
|
|
88
95
|
*/
|
|
89
|
-
function decode(value) {
|
|
96
|
+
function decode(name, value) {
|
|
90
97
|
try {
|
|
91
98
|
const payload = Buffer.from(value, "base64url");
|
|
92
99
|
const decipher = createDecipheriv(
|
|
@@ -95,6 +102,7 @@ function decode(value) {
|
|
|
95
102
|
payload.subarray(0, 12),
|
|
96
103
|
);
|
|
97
104
|
decipher.setAuthTag(payload.subarray(12, 28));
|
|
105
|
+
decipher.setAAD(Buffer.from(name));
|
|
98
106
|
return JSON.parse(
|
|
99
107
|
Buffer.concat([
|
|
100
108
|
decipher.update(payload.subarray(28)),
|
|
@@ -107,16 +115,21 @@ function decode(value) {
|
|
|
107
115
|
}
|
|
108
116
|
|
|
109
117
|
/**
|
|
110
|
-
* @param {
|
|
111
|
-
* @
|
|
112
|
-
* @returns {string | undefined}
|
|
118
|
+
* @param {unknown} value
|
|
119
|
+
* @returns {value is Session} Whether `value` is what `writeSession` seals
|
|
113
120
|
*/
|
|
114
|
-
function
|
|
115
|
-
|
|
116
|
-
const
|
|
117
|
-
return
|
|
118
|
-
|
|
119
|
-
|
|
121
|
+
function isSession(value) {
|
|
122
|
+
if (!isRecord(value) || !isBoolean(value.persistent)) return false;
|
|
123
|
+
const { user } = value;
|
|
124
|
+
return (
|
|
125
|
+
["accessToken", "refreshToken", "accountId", "activeTenantId"].every(
|
|
126
|
+
(field) => !Object.hasOwn(value, field) || isString(value[field]),
|
|
127
|
+
) &&
|
|
128
|
+
(!Object.hasOwn(value, "updatedAt") || isNumber(value.updatedAt)) &&
|
|
129
|
+
(!Object.hasOwn(value, "user") ||
|
|
130
|
+
(isRecord(user) &&
|
|
131
|
+
(!Object.hasOwn(user, "language") || isString(user.language))))
|
|
132
|
+
);
|
|
120
133
|
}
|
|
121
134
|
|
|
122
135
|
/**
|
|
@@ -158,6 +171,21 @@ function appendCookie(response, name, value, cookieAttributes) {
|
|
|
158
171
|
);
|
|
159
172
|
}
|
|
160
173
|
|
|
174
|
+
/**
|
|
175
|
+
* @param {import("node:http").ServerResponse} response
|
|
176
|
+
* @param {string} name
|
|
177
|
+
* @param {unknown} value
|
|
178
|
+
* @param {string} cookieAttributes
|
|
179
|
+
*/
|
|
180
|
+
function appendSealedCookie(response, name, value, cookieAttributes) {
|
|
181
|
+
appendCookie(
|
|
182
|
+
response,
|
|
183
|
+
name,
|
|
184
|
+
encode(scopedCookieName(name), value),
|
|
185
|
+
cookieAttributes,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
161
189
|
/**
|
|
162
190
|
* @param {SessionRequest} request
|
|
163
191
|
* @returns {Session | undefined} The session set for the request, else the one
|
|
@@ -165,9 +193,8 @@ function appendCookie(response, name, value, cookieAttributes) {
|
|
|
165
193
|
*/
|
|
166
194
|
export function readSession(request) {
|
|
167
195
|
if (REQUEST_SESSION in request) return request[REQUEST_SESSION];
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
return value ? decode(value) : undefined;
|
|
196
|
+
const sealed = readSealedCookie(request, SESSION_COOKIE);
|
|
197
|
+
return isSession(sealed) ? sealed : undefined;
|
|
171
198
|
}
|
|
172
199
|
|
|
173
200
|
/**
|
|
@@ -183,10 +210,10 @@ export function setRequestSession(request, session) {
|
|
|
183
210
|
* @param {Session} session
|
|
184
211
|
*/
|
|
185
212
|
export function writeSession(response, session) {
|
|
186
|
-
|
|
213
|
+
appendSealedCookie(
|
|
187
214
|
response,
|
|
188
215
|
SESSION_COOKIE,
|
|
189
|
-
|
|
216
|
+
session,
|
|
190
217
|
attributes(lifetime(session)),
|
|
191
218
|
);
|
|
192
219
|
}
|
|
@@ -203,8 +230,12 @@ export function clearSession(response) {
|
|
|
203
230
|
* `undefined` when there is none or it does not open
|
|
204
231
|
*/
|
|
205
232
|
export function readSealedCookie(request, name) {
|
|
206
|
-
const
|
|
207
|
-
|
|
233
|
+
const scoped = scopedCookieName(name);
|
|
234
|
+
const cookies = request.headers.cookie?.split(";") ?? [];
|
|
235
|
+
const value = cookies
|
|
236
|
+
.map((part) => part.trim().split("="))
|
|
237
|
+
.find(([key]) => key === scoped)?.[1];
|
|
238
|
+
return value ? decode(scoped, value) : undefined;
|
|
208
239
|
}
|
|
209
240
|
|
|
210
241
|
/**
|
|
@@ -215,7 +246,7 @@ export function readSealedCookie(request, name) {
|
|
|
215
246
|
* @param {string} [path]
|
|
216
247
|
*/
|
|
217
248
|
export function writeSealedCookie(response, name, value, maxAge, path = "/") {
|
|
218
|
-
|
|
249
|
+
appendSealedCookie(response, name, value, attributes(maxAge, path));
|
|
219
250
|
}
|
|
220
251
|
|
|
221
252
|
/**
|
|
@@ -295,14 +295,14 @@ function isResponse(value) {
|
|
|
295
295
|
}
|
|
296
296
|
|
|
297
297
|
/**
|
|
298
|
-
* @param {
|
|
298
|
+
* @param {unknown} result What a handler answered: any value, of which only
|
|
299
299
|
* `isResponse` looks
|
|
300
300
|
* @returns {Response}
|
|
301
301
|
*/
|
|
302
302
|
function asResponse(result) {
|
|
303
303
|
if (isResponse(result))
|
|
304
|
-
//
|
|
305
|
-
return result;
|
|
304
|
+
// SAFETY: `isResponse` accepts a `Response` of this realm or of another one (another copy of undici) by its `status`, `headers.get` and `body`; an answer that only mimics those members is read as a `Response` too, as it was before this assertion.
|
|
305
|
+
return /** @type {Response} */ (result);
|
|
306
306
|
if (result === undefined || result === null)
|
|
307
307
|
return new Response(null, { status: 204 });
|
|
308
308
|
return Response.json(result);
|
|
@@ -222,16 +222,24 @@ function readPublicOptions() {
|
|
|
222
222
|
*/
|
|
223
223
|
async function loadDeclarations(devServer) {
|
|
224
224
|
if (devServer)
|
|
225
|
-
|
|
226
|
-
return (await devServer.ssrLoadModule(SOURCE_LOADER)).serverModules;
|
|
225
|
+
return declarationsOf(await devServer.ssrLoadModule(SOURCE_LOADER));
|
|
227
226
|
if (!existsSync(fileURLToPath(BUILT_LOADER))) return [];
|
|
228
|
-
builtDeclarations ??= import(BUILT_LOADER.href).then(
|
|
229
|
-
// oxlint-disable-next-line typescript/no-unsafe-return -- the build of that same generated module is imported by a URL computed at run time, so it is untyped.
|
|
230
|
-
(loaded) => loaded.serverModules,
|
|
231
|
-
);
|
|
227
|
+
builtDeclarations ??= import(BUILT_LOADER.href).then(declarationsOf);
|
|
232
228
|
return builtDeclarations;
|
|
233
229
|
}
|
|
234
230
|
|
|
231
|
+
/**
|
|
232
|
+
* @param {unknown} loader The generated `server-modules.generated.ts`, from
|
|
233
|
+
* its source or its build
|
|
234
|
+
* @returns {ServerModuleDeclaration[]}
|
|
235
|
+
*/
|
|
236
|
+
function declarationsOf(loader) {
|
|
237
|
+
// SAFETY: `ssrLoadModule` types every module `Record<string, any>` and the build is imported by a URL computed at run time; both are the generated `server-modules.generated.ts`, which exports `serverModules`, the declarations of the modules with a dms.server.ts.
|
|
238
|
+
return /** @type {{ serverModules: ServerModuleDeclaration[] }} */ (
|
|
239
|
+
loader
|
|
240
|
+
).serverModules;
|
|
241
|
+
}
|
|
242
|
+
|
|
235
243
|
/**
|
|
236
244
|
* The declarations to serve. A module entry that fails to load in development
|
|
237
245
|
* (a syntax error being typed) is reported once, and the last routes that
|
|
@@ -22,11 +22,12 @@ let productionSsrRenderer;
|
|
|
22
22
|
* @returns {Promise<typeof import("../ssr-renderer.ts")>}
|
|
23
23
|
*/
|
|
24
24
|
export async function ssrRenderer(devServer) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
// SAFETY: both load this template's own `ssr-renderer.ts`, the development server from its source and the production import from its build; `ssrLoadModule` types every module `Record<string, any>` and the built URL is computed at run time.
|
|
26
|
+
const renderer = /** @type {typeof import("../ssr-renderer.ts")} */ (
|
|
27
|
+
devServer
|
|
28
|
+
? await devServer.ssrLoadModule("/ssr-renderer.ts")
|
|
29
|
+
: await (productionSsrRenderer ??= import(BUILT_SSR_RENDERER_URL.href))
|
|
30
|
+
);
|
|
30
31
|
await icons.provideIconCollections(renderer);
|
|
31
32
|
return renderer;
|
|
32
33
|
}
|
|
@@ -18,6 +18,7 @@ import { uiKbd } from "./ui-kbd.mjs";
|
|
|
18
18
|
import { uiLazyIcons } from "./ui-lazy-icons.mjs";
|
|
19
19
|
import { uiRequestState } from "./ui-request-state.mjs";
|
|
20
20
|
import { autoImportDirectories, vitePlugins } from "./frontend-build-loader.ts";
|
|
21
|
+
import { autoImportSources } from "./auto-imports.mjs";
|
|
21
22
|
import { frontendBuilds } from "./frontend-builds.generated.ts";
|
|
22
23
|
import { orderHeadForFirstPaint } from "./head-order.mjs";
|
|
23
24
|
import { iconScanGlobs } from "./icon-scan.mjs";
|
|
@@ -218,56 +219,9 @@ export default defineConfig({
|
|
|
218
219
|
autoImport: {
|
|
219
220
|
dirs: importDirectories,
|
|
220
221
|
dirsScanOptions: { types: true },
|
|
221
|
-
imports:
|
|
222
|
-
"
|
|
223
|
-
|
|
224
|
-
// This key is inlined verbatim as the module specifier of the
|
|
225
|
-
// import the auto-importer prepends to each file. A native Windows
|
|
226
|
-
// path would land inside a single-quoted JavaScript string with its
|
|
227
|
-
// backslashes intact, where `\U`, `\M` and `\f` are read as escape
|
|
228
|
-
// sequences: the specifier the bundler then resolves is a mangled
|
|
229
|
-
// path that cannot exist. Keep it POSIX.
|
|
230
|
-
[normalizePath(resolve(import.meta.dirname, "frontend-module.ts"))]:
|
|
231
|
-
[
|
|
232
|
-
"$fetch",
|
|
233
|
-
"abortDmsNavigation",
|
|
234
|
-
"clearDmsError",
|
|
235
|
-
"createDmsError",
|
|
236
|
-
"dmsRequestKey",
|
|
237
|
-
"defineDmsAppConfig",
|
|
238
|
-
"defineDmsPlugin",
|
|
239
|
-
"defineDmsMiddleware",
|
|
240
|
-
"defineDmsPageMeta",
|
|
241
|
-
"navigateDms",
|
|
242
|
-
"preloadDmsComponents",
|
|
243
|
-
"refreshDmsData",
|
|
244
|
-
"refreshDmsPageData",
|
|
245
|
-
"setDmsAsyncDataScope",
|
|
246
|
-
"showDmsError",
|
|
247
|
-
"useDmsAsyncData",
|
|
248
|
-
"useDmsData",
|
|
249
|
-
"useDmsCookie",
|
|
250
|
-
"useDmsFetch",
|
|
251
|
-
"useHead",
|
|
252
|
-
"useI18n",
|
|
253
|
-
"useDmsError",
|
|
254
|
-
"useDmsPagedData",
|
|
255
|
-
"useDmsPageLoading",
|
|
256
|
-
"isDmsHydrating",
|
|
257
|
-
"useDmsApp",
|
|
258
|
-
"useDmsRoute",
|
|
259
|
-
"useDmsRouter",
|
|
260
|
-
"useDmsRuntimeConfig",
|
|
261
|
-
"useSeoMeta",
|
|
262
|
-
"useDmsState",
|
|
263
|
-
"useDmsAppConfig",
|
|
264
|
-
"DmsClientOnly",
|
|
265
|
-
"DmsLink",
|
|
266
|
-
"useDmsUserSession",
|
|
267
|
-
"provideDmsKbdPlatform",
|
|
268
|
-
],
|
|
269
|
-
},
|
|
270
|
-
],
|
|
222
|
+
imports: autoImportSources(
|
|
223
|
+
normalizePath(resolve(import.meta.dirname, "frontend-module.ts")),
|
|
224
|
+
),
|
|
271
225
|
dts: "auto-imports.d.ts",
|
|
272
226
|
vueTemplate: true,
|
|
273
227
|
},
|