feat(desktop): implement phase 1 tauri client

This commit is contained in:
Cheng Zhou
2026-06-30 17:25:03 +08:00
parent 4654a812a0
commit d1a6c957f7
45 changed files with 5473 additions and 27 deletions
@@ -0,0 +1,68 @@
import { isTauriRuntime } from "./platform";
export const DESKTOP_SERVER_URL_KEY = "ctms_desktop_server_url";
export const DESKTOP_SERVER_URL_CHANGED_EVENT = "ctms:desktop-server-url-changed";
export type DesktopServerUrlValidationResult =
| { ok: true; url: string }
| { ok: false; reason: string };
const LOCAL_HTTP_HOSTS = new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
export const normalizeDesktopServerUrl = (value: string): DesktopServerUrlValidationResult => {
const raw = value.trim();
if (!raw) return { ok: false, reason: "请输入服务器地址" };
let parsed: URL;
try {
parsed = new URL(raw);
} catch {
return { ok: false, reason: "服务器地址格式不正确" };
}
if (parsed.username || parsed.password) {
return { ok: false, reason: "服务器地址不能包含用户名或密码" };
}
const isHttps = parsed.protocol === "https:";
const isLocalHttp = parsed.protocol === "http:" && LOCAL_HTTP_HOSTS.has(parsed.hostname);
if (!isHttps && !isLocalHttp) {
return { ok: false, reason: "非本地服务必须使用 HTTPS" };
}
return { ok: true, url: `${parsed.origin}/` };
};
export const getDesktopServerUrl = (): string | null => {
const stored = window.localStorage.getItem(DESKTOP_SERVER_URL_KEY);
if (!stored) return null;
const result = normalizeDesktopServerUrl(stored);
return result.ok ? result.url : null;
};
export const hasDesktopServerUrl = (): boolean => Boolean(getDesktopServerUrl());
export const setDesktopServerUrl = (value: string): DesktopServerUrlValidationResult => {
const result = normalizeDesktopServerUrl(value);
if (!result.ok) return result;
const previous = getDesktopServerUrl();
window.localStorage.setItem(DESKTOP_SERVER_URL_KEY, result.url);
window.dispatchEvent(
new CustomEvent(DESKTOP_SERVER_URL_CHANGED_EVENT, {
detail: { previous, current: result.url },
}),
);
return result;
};
export const clearDesktopServerUrl = (): void => {
const previous = getDesktopServerUrl();
window.localStorage.removeItem(DESKTOP_SERVER_URL_KEY);
window.dispatchEvent(
new CustomEvent(DESKTOP_SERVER_URL_CHANGED_EVENT, {
detail: { previous, current: null },
}),
);
};
export const shouldRequireDesktopServerUrl = (): boolean => isTauriRuntime() && !hasDesktopServerUrl();