chore: auto-commit (17 archivos)

- app.md
- applog.go
- frontend/package.json
- frontend/package.json.md5
- frontend/vite.config.ts
- go.mod
- main.go
- matrix_service.go
- sqlite_driver.go
- .wails_dev.log
- ...

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-26 19:38:16 +02:00
parent 23c933bfa2
commit 41bafa57cc
21 changed files with 1995 additions and 44 deletions
+67
View File
@@ -0,0 +1,67 @@
import { test, expect } from "@playwright/test";
const E2E_API = process.env.MATRIX_CLIENT_PC_E2E_API || "http://127.0.0.1:8767";
async function api(path: string, init: RequestInit = {}): Promise<any> {
const res = await fetch(E2E_API + path, init);
if (!res.ok) {
const body = await res.text();
throw new Error(`E2E API ${path} -> ${res.status}: ${body}`);
}
return res.json();
}
test.describe("Entry flow — LoginScreen → click Sign in → HomeScreen with rooms", () => {
test.beforeEach(async () => {
// Reset backend state: wipe last_user so frontend lands on LoginScreen.
await api("/wipe_session", { method: "POST" });
});
test("user can sign in and see their rooms", async ({ page }) => {
await page.goto("/", { waitUntil: "domcontentloaded" });
// LoginScreen visible
await expect(page.getByRole("heading", { name: "matrix_client_pc" })).toBeVisible({
timeout: 10_000,
});
const signInBtn = page.getByRole("button", { name: /Sign in with Matrix/i });
await expect(signInBtn).toBeVisible();
// Click Sign in → shim calls /signin_admin → returns user_id
await signInBtn.click();
// HomeScreen header buttons appear once authenticated.
await expect(page.getByRole("button", { name: /Health/i })).toBeVisible({
timeout: 15_000,
});
await expect(page.getByRole("button", { name: /Logs/i })).toBeVisible();
await expect(page.getByRole("button", { name: /Logout/i })).toBeVisible();
// Sidebar has at least one room (rooms are fetched after Start triggers sync).
const firstRoom = page.locator('nav a, [role="navigation"] a').first();
await expect(firstRoom).toBeVisible({ timeout: 30_000 });
// Backend sanity: diagnostics says we're synced with rooms.
const diag = await api("/diagnostics");
expect(diag.started).toBe(true);
expect(diag.rooms_count).toBeGreaterThan(0);
});
test("Logout returns to LoginScreen", async ({ page }) => {
await page.goto("/", { waitUntil: "domcontentloaded" });
// Sign in first
await page.getByRole("button", { name: /Sign in with Matrix/i }).click();
await expect(page.getByRole("button", { name: /Logout/i })).toBeVisible({
timeout: 15_000,
});
// Logout
await page.getByRole("button", { name: /Logout/i }).click();
// Back to LoginScreen
await expect(page.getByRole("button", { name: /Sign in with Matrix/i })).toBeVisible({
timeout: 10_000,
});
});
});
+1
View File
@@ -12,6 +12,7 @@
"test:ui": "vitest --ui",
"e2e": "playwright test",
"e2e:ui": "playwright test --ui",
"e2e:wails": "playwright test --config playwright.cdp.config.ts",
"e2e:report": "playwright show-report"
},
"dependencies": {
+1 -1
View File
@@ -1 +1 @@
9a66d5a5186912b91bb20602c66c7f8e
68223a3dca6c9351bad4d13d8f189cf0
+32
View File
@@ -0,0 +1,32 @@
import { defineConfig } from "@playwright/test";
// Drives the Vite dev server (frontend with HTTP-shim bindings) against a real
// Windows-side matrix_client_pc.exe running with MATRIX_CLIENT_PC_E2E=1 +
// BIND_ALL=1 (E2E HTTP API reachable on 127.0.0.1:8767 cross-WSL).
//
// Prerequisites:
// 1. bash scripts/launch_e2e.sh (Windows .exe up, :8767 listening)
// 2. VITE_E2E_API=http://localhost:8767 pnpm dev --host 0.0.0.0
// 3. pnpm e2e:wails
//
// Default URL targets the Vite dev port. Set BASE_URL env if vite picked a
// different port (5173 if free, otherwise 5174 etc.).
const BASE_URL = process.env.BASE_URL || "http://localhost:5174";
export default defineConfig({
testDir: "./e2e_cdp",
timeout: 60_000,
fullyParallel: false,
workers: 1,
retries: 0,
reporter: "list",
use: {
baseURL: BASE_URL,
headless: process.env.HEADED ? false : true,
trace: "retain-on-failure",
screenshot: "only-on-failure",
viewport: { width: 1280, height: 800 },
actionTimeout: 10_000,
},
});
+130
View File
@@ -0,0 +1,130 @@
// HTTP shim of the Wails MatrixService bindings — used in dev/E2E mode when
// running the frontend via `pnpm dev` against a real Wails app's E2E HTTP
// server (default http://127.0.0.1:8767). Vite resolve.alias swaps the
// `wailsjs/go/main/MatrixService` import to this file when VITE_E2E_API is set.
const API: string =
(import.meta as any).env?.VITE_E2E_API || "http://127.0.0.1:8767";
async function callJSON<T>(
path: string,
init: RequestInit = {},
): Promise<T> {
const res = await fetch(API + path, {
...init,
headers: {
"Content-Type": "application/json",
...(init.headers || {}),
},
});
if (!res.ok) {
const body = await res.text();
throw new Error(`E2E API ${path} -> ${res.status}: ${body}`);
}
return res.json() as Promise<T>;
}
// --- read endpoints ---
export async function GetLastUserID(): Promise<string> {
// /last_user reads last_user.txt directly so /wipe_session causes the
// frontend to fall back to LoginScreen even if sync is still active.
const r: any = await callJSON("/last_user");
return r.user_id || "";
}
export async function GetSession(user_id: string): Promise<any> {
// E2E server does not expose per-user session. Approximate with diagnostics.
const d: any = await callJSON("/diagnostics");
return {
user_id,
has_token: !!d.user_id,
homeserver_url: d.homeserver_url || "",
};
}
export async function GetDiagnostics(): Promise<any> {
return callJSON("/diagnostics");
}
export async function GetLogPath(): Promise<string> {
const r: any = await callJSON("/logs?n=1");
return r.path || "";
}
export async function GetLogTail(n: number): Promise<string[]> {
const r: any = await callJSON(`/logs?n=${encodeURIComponent(n)}`);
return r.lines || [];
}
export async function ListRooms(): Promise<any[]> {
const r: any = await callJSON("/rooms");
return r.rooms || [];
}
export async function LoadTimeline(room_id: string, limit: number): Promise<any[]> {
const r: any = await callJSON(
`/timeline?room_id=${encodeURIComponent(room_id)}&limit=${limit}`,
);
return r.events || [];
}
// --- write endpoints ---
// Dev mode forces skip_crypto: MatrixCryptoInit hangs indefinitely when MAS is
// active (the in-flight UIA roundtrip does not respect ctx cancellation). The
// frontend still sees rooms + can send plaintext to non-E2EE rooms; encrypted
// timelines render the wrapper events with "Encrypted" placeholders.
export async function Start(user_id: string): Promise<void> {
await callJSON("/start?skip_crypto=true", {
method: "POST",
body: JSON.stringify({ user_id }),
});
}
export async function StartNoCrypto(user_id: string): Promise<void> {
await callJSON("/start?skip_crypto=true", {
method: "POST",
body: JSON.stringify({ user_id }),
});
}
export async function Stop(): Promise<void> {
await fetch(API + "/stop", { method: "POST" });
}
export async function SendText(room_id: string, body: string): Promise<string> {
const r: any = await callJSON("/send", {
method: "POST",
body: JSON.stringify({ room_id, body }),
});
return r.event_id || "";
}
export async function SendMarkdown(room_id: string, body: string): Promise<string> {
const r: any = await callJSON("/send", {
method: "POST",
body: JSON.stringify({ room_id, body, markdown: true }),
});
return r.event_id || "";
}
// --- login flow ---
// In real Wails the Login() call opens the OIDC loopback flow. In dev/E2E we
// short-circuit via /signin_admin: the backend uses its env-stored
// MATRIX_SYNAPSE_ADMIN_TOKEN to whoami and persist the token. user_id is
// resolved server-side — no frontend prompt needed.
export async function Login(): Promise<string> {
const r: any = await callJSON("/signin_admin", {
method: "POST",
body: JSON.stringify({}),
});
if (!r.user_id) throw new Error("signin_admin returned no user_id");
return r.user_id;
}
export async function Logout(_user_id: string): Promise<void> {
await fetch(API + "/wipe_session", { method: "POST" });
}
export async function SetContext(_arg: any): Promise<void> {
// no-op in dev
}
+71
View File
@@ -0,0 +1,71 @@
// HTTP shim of `wailsjs/runtime/runtime` — only the event APIs the frontend
// uses are exposed. `EventsOn` polls /diagnostics every 1.5s and emits stub
// events with the new room/timeline counts so consumers see updates without
// needing the real Wails event bus.
type Handler = (...args: any[]) => void;
const handlers = new Map<string, Set<Handler>>();
let pollTimer: ReturnType<typeof setInterval> | null = null;
let lastDiag: any = {};
const API: string =
(import.meta as any).env?.VITE_E2E_API || "http://127.0.0.1:8767";
async function pollOnce() {
try {
const r = await fetch(API + "/diagnostics");
if (!r.ok) return;
const d = await r.json();
if (d.rooms_count !== lastDiag.rooms_count) {
emit("matrix:rooms_changed", d);
}
if (d.sync_active !== lastDiag.sync_active) {
emit("matrix:sync_changed", d);
}
lastDiag = d;
} catch {
/* ignore */
}
}
function emit(event: string, ...args: any[]) {
const set = handlers.get(event);
if (!set) return;
for (const h of set) {
try {
h(...args);
} catch {
/* ignore */
}
}
}
function ensurePolling() {
if (pollTimer) return;
pollTimer = setInterval(pollOnce, 1500);
}
export function EventsOn(event: string, handler: Handler): () => void {
if (!handlers.has(event)) handlers.set(event, new Set());
handlers.get(event)!.add(handler);
ensurePolling();
return () => {
handlers.get(event)?.delete(handler);
};
}
export function EventsOnce(event: string, handler: Handler): () => void {
const off = EventsOn(event, (...args) => {
off();
handler(...args);
});
return off;
}
export function EventsEmit(_event: string, ..._args: any[]): void {
// no-op in dev
}
export function EventsOff(event: string, ..._handlers: Handler[]): void {
handlers.delete(event);
}
+41 -6
View File
@@ -1,7 +1,42 @@
import {defineConfig} from 'vite'
import react from '@vitejs/plugin-react'
import { defineConfig, loadEnv } from "vite";
import react from "@vitejs/plugin-react";
import path from "node:path";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()]
})
// E2E/dev mode: when VITE_E2E_API is set, swap Wails bindings for HTTP shims
// that hit the matrix_client_pc E2E HTTP server. The production `wails build`
// run does NOT set VITE_E2E_API -> bindings resolve to the real generated files.
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), "");
const useShim = !!env.VITE_E2E_API;
const aliases = useShim
? {
"../wailsjs/go/main/MatrixService": path.resolve(
__dirname,
"src/shims/MatrixServiceShim.ts",
),
"../../wailsjs/go/main/MatrixService": path.resolve(
__dirname,
"src/shims/MatrixServiceShim.ts",
),
"../wailsjs/runtime/runtime": path.resolve(
__dirname,
"src/shims/RuntimeShim.ts",
),
"../../wailsjs/runtime/runtime": path.resolve(
__dirname,
"src/shims/RuntimeShim.ts",
),
}
: {};
return {
plugins: [react()],
resolve: { alias: aliases },
server: {
host: "0.0.0.0",
port: 5173,
strictPort: false,
},
};
});