mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 00:12:39 +00:00
feat: resolve deep links to the local instance
This commit is contained in:
@@ -155,6 +155,41 @@ export function buildLiteConfig(env = {}, { target = "static" } = {}) {
|
||||
}
|
||||
|
||||
/** policy.json: only the gates Lite must pin; the app merges the rest over its defaults. */
|
||||
/**
|
||||
* What `/api/connector/capabilities` answers on a server build, as a file.
|
||||
*
|
||||
* Lite is a static export with no route handlers, so a connector probing this
|
||||
* instance gets this instead - same document, same meaning. The connector
|
||||
* tries the API path first and falls back to `/connector.json`.
|
||||
*
|
||||
* The target names are read out of lib/connector/registry.ts rather than
|
||||
* duplicated here: a list that drifts would have this build telling connectors
|
||||
* it can resolve links it cannot.
|
||||
*/
|
||||
export function buildConnectorCapabilities({ appName, version, basePath = "" }) {
|
||||
const source = readFileSync(
|
||||
join(resolveRepoRoot(import.meta.url), "lib", "connector", "registry.ts"),
|
||||
"utf8",
|
||||
);
|
||||
const targets = [...source.matchAll(/^\s{4}name: '([a-z0-9_]+)',$/gm)].map((m) => m[1]);
|
||||
if (targets.length === 0) {
|
||||
throw new Error("no connector targets found in lib/connector/registry.ts");
|
||||
}
|
||||
return (
|
||||
JSON.stringify(
|
||||
{
|
||||
product: "bulwark-webmail",
|
||||
appName: appName || "Bulwark",
|
||||
version: version || null,
|
||||
connectorPath: `${basePath || ""}/connector`,
|
||||
targets,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
) + "\n"
|
||||
);
|
||||
}
|
||||
|
||||
export function buildLitePolicy() {
|
||||
return {
|
||||
_comment: "Optional admin policy for Bulwark Lite (same shape as the admin dashboard's policy). Plugins and sidebar apps stay off in Lite.",
|
||||
@@ -331,6 +366,9 @@ export function buildHeaders({ basePath = "", connectSrc = "*" }) {
|
||||
Content-Security-Policy: ${csp}
|
||||
${basePath || ""}/_next/static/*
|
||||
Cache-Control: public, max-age=31536000, immutable
|
||||
${basePath || ""}/connector.json
|
||||
Access-Control-Allow-Origin: *
|
||||
Cache-Control: public, max-age=300
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node
|
||||
import { join } from "node:path";
|
||||
import {
|
||||
STALWART_EXCLUDED_DIRS, STALWART_EXCLUDED_FILES, STALWART_ZIP_NAME,
|
||||
buildCaddyExample, buildHeaders, buildLiteConfig, buildLitePolicy, buildManifest, buildNginxExample, buildNotFoundShim, buildReadme, buildRedirects, buildRootRedirect,
|
||||
buildCaddyExample, buildConnectorCapabilities, buildHeaders, buildLiteConfig, buildLitePolicy, buildManifest, buildNginxExample, buildNotFoundShim, buildReadme, buildRedirects, buildRootRedirect,
|
||||
buildStalwartEntry, buildStalwartManifest, buildStalwartReadme, buildZip, collectZipEntries, discoverBuiltLocales, discoverShellDirs, findSegmentPrefetchDirs, isMainModule, makeBuildId, normalizeBasePath, parseLiteTarget, resolveRepoRoot,
|
||||
} from "./lib.mjs";
|
||||
|
||||
@@ -64,6 +64,9 @@ function staticFiles({ config, basePath, locales, defaultLocale, version, commit
|
||||
return {
|
||||
"config.json": JSON.stringify(config, null, 2) + "\n",
|
||||
"policy.json": JSON.stringify(buildLitePolicy(), null, 2) + "\n",
|
||||
// The file form of /api/connector/capabilities - a static export has no
|
||||
// route handlers, and a connector probing this instance needs an answer.
|
||||
"connector.json": buildConnectorCapabilities({ appName: config.appName, version, basePath }),
|
||||
"manifest.webmanifest": JSON.stringify(buildManifest({ appName: config.appName, basePath }), null, 2) + "\n",
|
||||
"index.html": buildRootRedirect({ basePath, locales, defaultLocale }),
|
||||
// Replaces Next's default not-found page: only this shim replays deep links
|
||||
|
||||
@@ -45,7 +45,10 @@ export function verifyExport({ root = repoRoot, target, safeBundleBytes = STALWA
|
||||
|
||||
const required = resolvedTarget === "stalwart"
|
||||
? ["index.html", "config.json", "policy.json", "manifest.json", "LITE-README.md", "lite-build.json"]
|
||||
: ["index.html", "404.html", "config.json", "policy.json", "manifest.webmanifest", "_redirects", "_headers", "LITE-README.md", "lite-build.json"];
|
||||
// connector.json is the static target only: the Stalwart bundle is served
|
||||
// without custom headers, so a connector's cross-origin probe could not
|
||||
// read it anyway (it falls back to adding the instance unverified).
|
||||
: ["index.html", "404.html", "config.json", "policy.json", "connector.json", "manifest.webmanifest", "_redirects", "_headers", "LITE-README.md", "lite-build.json"];
|
||||
for (const file of required) {
|
||||
if (!existsSync(join(outDir, file))) problems.push(`missing out/${file}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user