feat: Add browser documentation
This commit is contained in:
104
electron/api/docusaurus-static.ts
Normal file
104
electron/api/docusaurus-static.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { promises as fs } from 'fs';
|
||||
import * as path from 'path';
|
||||
import { HttpError } from './http-helpers';
|
||||
|
||||
const MIME_TYPES_BY_EXTENSION: Record<string, string> = {
|
||||
'.css': 'text/css; charset=utf-8',
|
||||
'.gif': 'image/gif',
|
||||
'.html': 'text/html; charset=utf-8',
|
||||
'.ico': 'image/x-icon',
|
||||
'.jpeg': 'image/jpeg',
|
||||
'.jpg': 'image/jpeg',
|
||||
'.js': 'application/javascript; charset=utf-8',
|
||||
'.json': 'application/json; charset=utf-8',
|
||||
'.map': 'application/json; charset=utf-8',
|
||||
'.png': 'image/png',
|
||||
'.svg': 'image/svg+xml; charset=utf-8',
|
||||
'.txt': 'text/plain; charset=utf-8',
|
||||
'.webp': 'image/webp',
|
||||
'.woff': 'font/woff',
|
||||
'.woff2': 'font/woff2'
|
||||
};
|
||||
|
||||
function getDocsRootCandidates(): string[] {
|
||||
const processWithResources = process as NodeJS.Process & { resourcesPath?: string };
|
||||
const candidates: string[] = [];
|
||||
|
||||
if (processWithResources.resourcesPath) {
|
||||
candidates.push(path.join(processWithResources.resourcesPath, 'docusaurus'));
|
||||
}
|
||||
|
||||
candidates.push(path.join(process.cwd(), 'docs-site', 'build'));
|
||||
|
||||
return candidates;
|
||||
}
|
||||
|
||||
async function getDocusaurusBuildRoot(): Promise<string | null> {
|
||||
for (const candidate of getDocsRootCandidates()) {
|
||||
try {
|
||||
const stat = await fs.stat(candidate);
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
return candidate;
|
||||
}
|
||||
} catch {
|
||||
// try next candidate
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function isPathInside(parent: string, child: string): boolean {
|
||||
const relative = path.relative(parent, child);
|
||||
|
||||
return relative === '' || (!!relative && !relative.startsWith('..') && !path.isAbsolute(relative));
|
||||
}
|
||||
|
||||
function resolveAssetPath(root: string, pathname: string): string {
|
||||
const withoutPrefix = pathname.replace(/^\/docusaurus\/?/u, '');
|
||||
const decoded = decodeURIComponent(withoutPrefix);
|
||||
const normalized = decoded.endsWith('/') || decoded === '' ? path.join(decoded, 'index.html') : decoded;
|
||||
const absolutePath = path.resolve(root, normalized);
|
||||
|
||||
if (!isPathInside(root, absolutePath)) {
|
||||
throw new HttpError(400, 'Invalid Docusaurus asset path', 'INVALID_DOCS_PATH');
|
||||
}
|
||||
|
||||
return absolutePath;
|
||||
}
|
||||
|
||||
export async function resolveDocusaurusRoute(pathname: string): Promise<{ filePath: string; contentType: string }> {
|
||||
const root = await getDocusaurusBuildRoot();
|
||||
|
||||
if (!root) {
|
||||
throw new HttpError(503, 'Docusaurus build is not available. Run npm run build:docs before opening the docs endpoint.', 'DOCUSAURUS_BUILD_MISSING');
|
||||
}
|
||||
|
||||
let filePath = resolveAssetPath(root, pathname);
|
||||
|
||||
try {
|
||||
const stat = await fs.stat(filePath);
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
filePath = path.join(filePath, 'index.html');
|
||||
}
|
||||
} catch {
|
||||
const directoryIndexPath = path.join(filePath, 'index.html');
|
||||
|
||||
try {
|
||||
await fs.access(directoryIndexPath);
|
||||
filePath = directoryIndexPath;
|
||||
} catch {
|
||||
filePath = path.join(root, '404.html');
|
||||
}
|
||||
}
|
||||
|
||||
if (!isPathInside(root, filePath)) {
|
||||
throw new HttpError(400, 'Invalid Docusaurus asset path', 'INVALID_DOCS_PATH');
|
||||
}
|
||||
|
||||
const contentType = MIME_TYPES_BY_EXTENSION[path.extname(filePath).toLowerCase()] ?? 'application/octet-stream';
|
||||
|
||||
return { filePath, contentType };
|
||||
}
|
||||
Reference in New Issue
Block a user