fix live analysis multi-device lock

这个提交包含在:
cryptocommuniums-afk
2026-03-16 18:05:58 +08:00
父节点 13e59b8e8a
当前提交 f9db6ef590
修改 7 个文件,包含 221 行新增28 行删除

查看文件

@@ -13,6 +13,26 @@ import { createBackgroundTask, getAdminUserId, hasRecentBackgroundTaskOfType, se
import { nanoid } from "nanoid";
import { syncTutorialImages } from "../tutorialImages";
async function warmupApplicationData() {
const tasks: Array<{ label: string; run: () => Promise<unknown> }> = [
{ label: "seedTutorials", run: () => seedTutorials() },
{ label: "syncTutorialImages", run: () => syncTutorialImages() },
{ label: "seedVisionReferenceImages", run: () => seedVisionReferenceImages() },
{ label: "seedAchievementDefinitions", run: () => seedAchievementDefinitions() },
{ label: "seedAppSettings", run: () => seedAppSettings() },
];
for (const task of tasks) {
const startedAt = Date.now();
try {
await task.run();
console.log(`[startup] ${task.label} finished in ${Date.now() - startedAt}ms`);
} catch (error) {
console.error(`[startup] ${task.label} failed`, error);
}
}
}
async function scheduleDailyNtrpRefresh() {
const now = new Date();
if (now.getHours() !== 0 || now.getMinutes() > 5) {
@@ -64,12 +84,6 @@ async function findAvailablePort(startPort: number = 3000): Promise<number> {
}
async function startServer() {
await seedTutorials();
await syncTutorialImages();
await seedVisionReferenceImages();
await seedAchievementDefinitions();
await seedAppSettings();
const app = express();
const server = createServer(app);
registerMediaProxy(app);
@@ -108,6 +122,7 @@ async function startServer() {
server.listen(port, () => {
console.log(`Server running on http://localhost:${port}/`);
void warmupApplicationData();
});
setInterval(() => {

57
server/_core/sdk.test.ts 普通文件
查看文件

@@ -0,0 +1,57 @@
import { SignJWT } from "jose";
import { describe, expect, it, vi } from "vitest";
async function loadSdkForTest() {
process.env.JWT_SECRET = "test-cookie-secret";
process.env.VITE_APP_ID = "test-app";
vi.resetModules();
const [{ sdk }, { ENV }] = await Promise.all([
import("./sdk"),
import("./env"),
]);
return { sdk, ENV };
}
async function signLegacyToken(openId: string, appId: string, name: string) {
const secret = new TextEncoder().encode(process.env.JWT_SECRET || "");
return new SignJWT({
openId,
appId,
name,
})
.setProtectedHeader({ alg: "HS256", typ: "JWT" })
.setExpirationTime(Math.floor((Date.now() + 60_000) / 1000))
.sign(secret);
}
describe("sdk.verifySession", () => {
it("derives a stable legacy sid when the token payload does not include sid", async () => {
const { sdk, ENV } = await loadSdkForTest();
const legacyToken = await signLegacyToken("username_H1_legacy", ENV.appId, "H1");
const session = await sdk.verifySession(legacyToken);
expect(session).not.toBeNull();
expect(session?.sid).toMatch(/^legacy-token:/);
expect(session?.sid).toHaveLength("legacy-token:".length + 32);
});
it("derives different legacy sid values for different legacy login tokens", async () => {
const firstLoad = await loadSdkForTest();
const tokenA = await signLegacyToken("username_H1_legacy", firstLoad.ENV.appId, "H1");
await new Promise((resolve) => setTimeout(resolve, 5));
const secondLoad = await loadSdkForTest();
const tokenB = await signLegacyToken("username_H1_legacy", secondLoad.ENV.appId, "H1-second");
const sessionA = await firstLoad.sdk.verifySession(tokenA);
const sessionB = await secondLoad.sdk.verifySession(tokenB);
expect(sessionA?.sid).toMatch(/^legacy-token:/);
expect(sessionB?.sid).toMatch(/^legacy-token:/);
expect(sessionA?.sid).not.toBe(sessionB?.sid);
});
});

查看文件

@@ -4,6 +4,7 @@ import axios, { type AxiosInstance } from "axios";
import { parse as parseCookieHeader } from "cookie";
import type { Request } from "express";
import { SignJWT, jwtVerify } from "jose";
import { createHash } from "node:crypto";
import type { User } from "../../drizzle/schema";
import * as db from "../db";
import { ENV } from "./env";
@@ -223,11 +224,15 @@ class SDKServer {
return null;
}
const derivedSid = typeof sid === "string" && sid.length > 0
? sid
: `legacy-token:${createHash("sha256").update(cookieValue).digest("hex").slice(0, 32)}`;
return {
openId,
appId,
name: typeof name === "string" ? name : undefined,
sid: typeof sid === "string" ? sid : undefined,
sid: derivedSid,
};
} catch (error) {
console.warn("[Auth] Session verification failed", String(error));