mirror of
https://gitee.com/spark-store-project/spark-store
synced 2026-06-22 22:23:49 +08:00
fix(account): route forum login through ipc
This commit is contained in:
@@ -55,6 +55,7 @@ import "./backend/install-manager.js";
|
||||
import "./handle-url-scheme.js";
|
||||
|
||||
const logger = pino({ name: "index.ts" });
|
||||
const FLARUM_TOKEN_URL = "https://bbs.spark-app.store/api/token";
|
||||
|
||||
// The built directory structure
|
||||
//
|
||||
@@ -118,6 +119,52 @@ ipcMain.handle("get-store-filter", (): "spark" | "apm" | "both" =>
|
||||
|
||||
ipcMain.handle("get-app-version", (): string => getAppVersion());
|
||||
|
||||
ipcMain.handle("request-flarum-token", async (_event, payload: unknown) => {
|
||||
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
||||
throw new Error("登录信息格式不正确,请重新输入。");
|
||||
}
|
||||
|
||||
const credentials = payload as Record<string, unknown>;
|
||||
if (
|
||||
typeof credentials.identification !== "string" ||
|
||||
typeof credentials.password !== "string"
|
||||
) {
|
||||
throw new Error("登录信息格式不正确,请重新输入。");
|
||||
}
|
||||
|
||||
const response = await fetch(FLARUM_TOKEN_URL, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
"User-Agent": getUserAgent(),
|
||||
},
|
||||
body: JSON.stringify({
|
||||
identification: credentials.identification,
|
||||
password: credentials.password,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("论坛登录失败,请检查账号和密码。");
|
||||
}
|
||||
|
||||
const data = (await response.json()) as Record<string, unknown>;
|
||||
const userId = data.userId ?? data.user_id;
|
||||
if (
|
||||
typeof data.token !== "string" ||
|
||||
userId === undefined ||
|
||||
userId === null
|
||||
) {
|
||||
throw new Error("论坛登录响应异常,请稍后重试。");
|
||||
}
|
||||
|
||||
return {
|
||||
token: data.token,
|
||||
userId: String(userId),
|
||||
};
|
||||
});
|
||||
|
||||
const getMainWindowCloseGuardState = (): MainWindowCloseGuardState => ({
|
||||
installTaskCount: tasks.size,
|
||||
hasRunningUpdateCenterTasks:
|
||||
|
||||
+36
-1
@@ -62,7 +62,29 @@
|
||||
@select-category="selectSubCategory"
|
||||
/>
|
||||
<div class="px-4 py-6 lg:px-10">
|
||||
<template v-if="activeTab === 'home'">
|
||||
<section
|
||||
v-if="currentView === 'account'"
|
||||
class="rounded-3xl border border-slate-200 bg-white p-6 shadow-sm dark:border-slate-800 dark:bg-slate-900"
|
||||
>
|
||||
<h1 class="text-2xl font-semibold text-slate-900 dark:text-white">
|
||||
用户管理
|
||||
</h1>
|
||||
<p class="mt-3 text-sm text-slate-500 dark:text-slate-400">
|
||||
账号资料与安全设置功能即将开放。
|
||||
</p>
|
||||
</section>
|
||||
<section
|
||||
v-else-if="currentView === 'favorites'"
|
||||
class="rounded-3xl border border-slate-200 bg-white p-6 shadow-sm dark:border-slate-800 dark:bg-slate-900"
|
||||
>
|
||||
<h1 class="text-2xl font-semibold text-slate-900 dark:text-white">
|
||||
我的收藏
|
||||
</h1>
|
||||
<p class="mt-3 text-sm text-slate-500 dark:text-slate-400">
|
||||
收藏应用列表功能即将开放。
|
||||
</p>
|
||||
</section>
|
||||
<template v-else-if="activeTab === 'home'">
|
||||
<HomeView
|
||||
:links="homeLinks"
|
||||
:lists="homeLists"
|
||||
@@ -307,6 +329,7 @@ const isDarkTheme = computed(() => {
|
||||
const categories: Ref<Record<string, CategoryInfo>> = ref({});
|
||||
const apps: Ref<App[]> = ref([]);
|
||||
const activeTab = ref("home");
|
||||
const currentView = ref<"default" | "account" | "favorites">("default");
|
||||
const selectedCategory = ref("all");
|
||||
const searchQuery = ref("");
|
||||
const isSidebarOpen = ref(false);
|
||||
@@ -456,6 +479,7 @@ const toggleTheme = () => {
|
||||
};
|
||||
|
||||
const selectTab = (tab: string) => {
|
||||
currentView.value = "default";
|
||||
activeTab.value = tab;
|
||||
selectedCategory.value = "all";
|
||||
isSidebarOpen.value = false;
|
||||
@@ -470,6 +494,7 @@ const selectTab = (tab: string) => {
|
||||
};
|
||||
|
||||
const selectSubCategory = (category: string) => {
|
||||
currentView.value = "default";
|
||||
selectedCategory.value = category;
|
||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||
};
|
||||
@@ -532,6 +557,7 @@ const fetchAppFromStore = async (
|
||||
};
|
||||
|
||||
const openDetail = async (app: App | Record<string, unknown>) => {
|
||||
currentView.value = "default";
|
||||
// 提取 pkgname 和 category(必须存在)
|
||||
const pkgname = (app as Record<string, unknown>).pkgname as string;
|
||||
const category =
|
||||
@@ -1142,11 +1168,17 @@ const handleFlarumLogin = async (payload: FlarumLoginPayload) => {
|
||||
|
||||
const openUserManagement = () => {
|
||||
if (!requireLogin("请登录后查看和管理账号信息。")) return;
|
||||
currentView.value = "account";
|
||||
activeTab.value = "account";
|
||||
isSidebarOpen.value = false;
|
||||
showLoginPrompt.value = false;
|
||||
};
|
||||
|
||||
const openFavoriteManagement = () => {
|
||||
if (!requireLogin("请登录后查看我的收藏。")) return;
|
||||
currentView.value = "favorites";
|
||||
activeTab.value = "favorites";
|
||||
isSidebarOpen.value = false;
|
||||
showLoginPrompt.value = false;
|
||||
};
|
||||
|
||||
@@ -1390,10 +1422,12 @@ const loadApps = async (onFirstBatch?: () => void) => {
|
||||
};
|
||||
|
||||
const handleSearchInput = (value: string) => {
|
||||
currentView.value = "default";
|
||||
searchQuery.value = value;
|
||||
};
|
||||
|
||||
const handleSearchFocus = () => {
|
||||
currentView.value = "default";
|
||||
if (activeTab.value === "home") activeTab.value = "all";
|
||||
};
|
||||
|
||||
@@ -1517,6 +1551,7 @@ onMounted(async () => {
|
||||
// 根据包名直接打开应用详情
|
||||
const tryOpen = () => {
|
||||
// 先切换到"全部应用"分类
|
||||
currentView.value = "default";
|
||||
activeTab.value = "all";
|
||||
// 使用类似 HomeView 的方式打开应用,从两个仓库获取完整信息
|
||||
const target = apps.value.find((a) => a.pkgname === data.pkgname);
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
import { fireEvent, render, screen } from "@testing-library/vue";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import App from "@/App.vue";
|
||||
import { setAuthSession } from "@/global/authState";
|
||||
|
||||
const invoke = vi.fn();
|
||||
|
||||
vi.mock("axios", () => {
|
||||
const get = vi.fn(async (url: string) => {
|
||||
if (url.includes("categories.json")) return { data: {} };
|
||||
return { data: [] };
|
||||
});
|
||||
|
||||
return {
|
||||
default: {
|
||||
create: () => ({ get }),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@/modules/updateCenter", () => ({
|
||||
createUpdateCenterStore: () => ({
|
||||
isOpen: { value: false },
|
||||
showCloseConfirm: { value: false },
|
||||
showMigrationConfirm: { value: false },
|
||||
searchQuery: { value: "" },
|
||||
selectedTaskKeys: { value: new Set<string>() },
|
||||
snapshot: {
|
||||
value: { items: [], tasks: [], warnings: [], hasRunningTasks: false },
|
||||
},
|
||||
filteredItems: { value: [] },
|
||||
allSelected: { value: false },
|
||||
someSelected: { value: false },
|
||||
bind: vi.fn(),
|
||||
unbind: vi.fn(),
|
||||
open: vi.fn(),
|
||||
refresh: vi.fn(),
|
||||
ignoreItem: vi.fn(),
|
||||
unignoreItem: vi.fn(),
|
||||
toggleSelection: vi.fn(),
|
||||
toggleSelectAll: vi.fn(),
|
||||
getSelectedItems: vi.fn(() => []),
|
||||
closeNow: vi.fn(),
|
||||
startSelected: vi.fn(),
|
||||
requestClose: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
describe("App account placeholders", () => {
|
||||
beforeEach(() => {
|
||||
invoke.mockReset();
|
||||
invoke.mockImplementation(async (channel: string) => {
|
||||
if (channel === "get-store-filter") return "both";
|
||||
if (channel === "check-spark-available") return true;
|
||||
if (channel === "check-apm-available") return true;
|
||||
if (channel === "get-app-version") return "5.0.0";
|
||||
return [];
|
||||
});
|
||||
|
||||
Object.assign(window.ipcRenderer, {
|
||||
invoke,
|
||||
on: vi.fn(),
|
||||
off: vi.fn(),
|
||||
send: vi.fn(),
|
||||
});
|
||||
|
||||
window.apm_store.arch = "amd64";
|
||||
localStorage.clear();
|
||||
setAuthSession({
|
||||
accessToken: "backend-token",
|
||||
tokenType: "bearer",
|
||||
user: {
|
||||
id: 1,
|
||||
flarumUserId: "42",
|
||||
username: "momen",
|
||||
displayName: "Momen",
|
||||
avatarUrl: "https://bbs.spark-app.store/avatar.png",
|
||||
forumLevel: "管理员",
|
||||
forumGroups: ["管理员"],
|
||||
},
|
||||
});
|
||||
|
||||
vi.stubGlobal(
|
||||
"matchMedia",
|
||||
vi.fn(() => ({
|
||||
matches: false,
|
||||
addEventListener: vi.fn(),
|
||||
removeEventListener: vi.fn(),
|
||||
})),
|
||||
);
|
||||
});
|
||||
|
||||
it("shows the user management placeholder from the logged-in quick menu", async () => {
|
||||
render(App);
|
||||
|
||||
await fireEvent.click(await screen.findByRole("button", { name: /Momen/ }));
|
||||
await fireEvent.click(screen.getByText("用户管理"));
|
||||
|
||||
expect(
|
||||
await screen.findByRole("heading", { name: "用户管理" }),
|
||||
).toBeTruthy();
|
||||
expect(screen.queryByText("请登录后查看和管理账号信息。")).toBeNull();
|
||||
});
|
||||
|
||||
it("shows the favorites placeholder from the logged-in quick menu", async () => {
|
||||
render(App);
|
||||
|
||||
await fireEvent.click(await screen.findByRole("button", { name: /Momen/ }));
|
||||
await fireEvent.click(screen.getByText("我的收藏"));
|
||||
|
||||
expect(
|
||||
await screen.findByRole("heading", { name: "我的收藏" }),
|
||||
).toBeTruthy();
|
||||
expect(screen.queryByText("请登录后查看我的收藏。")).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import axios from "axios";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { requestFlarumToken } from "@/modules/flarumAuth";
|
||||
|
||||
vi.mock("axios", () => ({
|
||||
default: {
|
||||
post: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe("requestFlarumToken", () => {
|
||||
beforeEach(() => {
|
||||
vi.mocked(window.ipcRenderer.invoke).mockReset();
|
||||
vi.mocked(axios.post).mockReset();
|
||||
});
|
||||
|
||||
it("requests the Flarum token through main-process IPC", async () => {
|
||||
vi.mocked(window.ipcRenderer.invoke).mockResolvedValue({
|
||||
token: "forum-token",
|
||||
user_id: 42,
|
||||
});
|
||||
|
||||
const payload = { identification: "user@example.com", password: "secret" };
|
||||
|
||||
const token = await requestFlarumToken(payload);
|
||||
|
||||
expect(window.ipcRenderer.invoke).toHaveBeenCalledWith(
|
||||
"request-flarum-token",
|
||||
payload,
|
||||
);
|
||||
expect(axios.post).not.toHaveBeenCalled();
|
||||
expect(token).toEqual({ token: "forum-token", userId: "42" });
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,3 @@
|
||||
import axios from "axios";
|
||||
|
||||
import { FLARUM_BASE_URL } from "@/global/storeConfig";
|
||||
import type { FlarumLoginPayload } from "@/global/typedefinition";
|
||||
|
||||
type FlarumTokenResponse = {
|
||||
@@ -18,8 +15,9 @@ const asRecord = (value: unknown): Record<string, unknown> => {
|
||||
export const requestFlarumToken = async (
|
||||
payload: FlarumLoginPayload,
|
||||
): Promise<FlarumTokenResponse> => {
|
||||
const response = await axios.post(`${FLARUM_BASE_URL}/api/token`, payload);
|
||||
const data = asRecord(response.data);
|
||||
const data = asRecord(
|
||||
await window.ipcRenderer.invoke("request-flarum-token", payload),
|
||||
);
|
||||
|
||||
return {
|
||||
token: String(data.token || ""),
|
||||
|
||||
Vendored
+4
@@ -34,6 +34,10 @@ interface IpcRendererFacade {
|
||||
// IPC channel type definitions
|
||||
declare interface IpcChannels {
|
||||
"get-app-version": () => string;
|
||||
"request-flarum-token": (payload: {
|
||||
identification: string;
|
||||
password: string;
|
||||
}) => Promise<{ token: string; userId: string }>;
|
||||
}
|
||||
|
||||
declare const __APP_VERSION__: string;
|
||||
|
||||
Reference in New Issue
Block a user