fix: improve plugins functionality with server management

This commit is contained in:
2026-04-29 20:33:54 +02:00
parent b8f6d58d99
commit fa2cca6fa4
82 changed files with 1708 additions and 303 deletions

View File

@@ -11,7 +11,6 @@ export interface IssuedToken {
}
const TOKEN_TTL_MS = 24 * 60 * 60 * 1000;
const tokens = new Map<string, IssuedToken>();
export function issueToken(params: {

View File

@@ -59,6 +59,17 @@ export function getDocsHtml(specUrl: string): string {
disabled: true
}
};
const contentSecurityPolicy = [
"default-src 'none'",
"script-src 'self' 'nonce-metoyou-local-api-docs'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: blob:",
"font-src 'self' data:",
"connect-src 'self'",
"base-uri 'none'",
"form-action 'none'",
"frame-ancestors 'none'"
].join('; ');
return `<!doctype html>
<html lang="en">
@@ -67,7 +78,7 @@ export function getDocsHtml(specUrl: string): string {
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'none'; script-src 'self' 'nonce-metoyou-local-api-docs'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'"
content="${contentSecurityPolicy}"
/>
<title>MetoYou Local API</title>
<style>

View File

@@ -72,7 +72,11 @@ export async function resolveDocusaurusRoute(pathname: string): Promise<{ filePa
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');
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);

View File

@@ -37,6 +37,7 @@ export async function readJsonBody<T>(req: IncomingMessage): Promise<T> {
}
const chunks: Buffer[] = [];
let received = 0;
for await (const chunk of req) {

View File

@@ -196,9 +196,9 @@ export async function startLocalApiServer(settings: LocalApiSettings): Promise<S
currentError = null;
currentBindHost = pickBindHost(settings);
currentBindPort = settings.port;
const requestSettings = activeSettings;
const httpServer = createServer((req, res) => {
void handleRequest(req, res, activeSettings!).catch((error) => {
void handleRequest(req, res, requestSettings).catch((error) => {
console.error('[LocalApi] Unhandled request error:', error);
try {

View File

@@ -36,7 +36,11 @@ export function buildOpenApiDocument(options: OpenApiBuildOptions): unknown {
},
LoginRequest: {
type: 'object',
required: ['username', 'password', 'serverUrl'],
required: [
'username',
'password',
'serverUrl'
],
properties: {
username: { type: 'string' },
password: { type: 'string' },
@@ -49,7 +53,11 @@ export function buildOpenApiDocument(options: OpenApiBuildOptions): unknown {
},
LoginResponse: {
type: 'object',
required: ['token', 'expiresAt', 'user'],
required: [
'token',
'expiresAt',
'user'
],
properties: {
token: { type: 'string' },
expiresAt: { type: 'integer', format: 'int64' },
@@ -58,7 +66,11 @@ export function buildOpenApiDocument(options: OpenApiBuildOptions): unknown {
},
AuthUser: {
type: 'object',
required: ['id', 'username', 'displayName'],
required: [
'id',
'username',
'displayName'
],
properties: {
id: { type: 'string' },
username: { type: 'string' },

View File

@@ -1,10 +1,19 @@
import { app, net } from 'electron';
import { DataSource } from 'typeorm';
import { buildQueryHandlers } from '../cqrs/queries';
import { QueryType, QueryTypeKey, Query } from '../cqrs/types';
import { issueToken, consumeToken, revokeToken, IssuedToken } from './auth-store';
import {
QueryType,
QueryTypeKey,
Query
} from '../cqrs/types';
import {
issueToken,
consumeToken,
revokeToken,
IssuedToken
} from './auth-store';
import { buildOpenApiDocument } from './openapi';
import { HttpError, RequestContext, readJsonBody } from './http-helpers';
import { HttpError, RequestContext } from './http-helpers';
import { getDocsHtml, getScalarApiReferenceBundlePath } from './docs-html';
import { resolveDocusaurusRoute } from './docusaurus-static';
import { LocalApiSettings } from '../desktop-settings';
@@ -48,12 +57,14 @@ function compilePattern(template: string): { pattern: RegExp; paramKeys: string[
const escaped = template.replace(/[.*+?^${}()|[\]\\]/g, (match) => {
if (match === '*' || match === '+' || match === '?')
return `\\${match}`;
return `\\${match}`;
});
const source = template.replace(/\{([^}]+)\}/g, (_full, key: string) => {
paramKeys.push(key);
return '([^/]+)';
});
void escaped;
return { pattern: new RegExp(`^${source}$`), paramKeys };
@@ -273,7 +284,6 @@ const ROUTES: RouteDefinition[] = [
const limit = clampInt(ctx.request.url.searchParams.get('limit'), 1, 500, 100);
const offset = clampInt(ctx.request.url.searchParams.get('offset'), 0, Number.MAX_SAFE_INTEGER, 0);
const messages = await runQuery<unknown[]>(requireDataSource(ctx.dataSource), {
type: QueryType.GetMessages,
payload: { roomId: decodeURIComponent(roomId), limit, offset }