feat: add WeChat QR code login and AGP WebSocket channel plugin
- Auth module: WeChat OAuth2 scan-to-login flow with terminal QR code - Token persistence to ~/.openclaw/wechat-access-auth.json (chmod 600) - Token resolution: config > saved state > interactive login - Invite code verification (configurable bypass) - Production/test environment support - AGP WebSocket client with heartbeat, reconnect, wake detection - Message handler: Agent dispatch with streaming text and tool calls - Random device GUID generation (persisted, no real machine ID)
This commit is contained in:
48
auth/state-store.ts
Normal file
48
auth/state-store.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* @file state-store.ts
|
||||
* @description 登录态持久化存储
|
||||
*
|
||||
* 将 token 保存到本地文件,下次启动时自动加载,避免重复扫码。
|
||||
* 文件权限设为 0o600,仅当前用户可读写。
|
||||
*/
|
||||
|
||||
import { readFileSync, writeFileSync, unlinkSync, mkdirSync, chmodSync } from "node:fs";
|
||||
import { join, dirname } from "node:path";
|
||||
import { homedir } from "node:os";
|
||||
import type { PersistedAuthState } from "./types.js";
|
||||
|
||||
const DEFAULT_STATE_PATH = join(homedir(), ".openclaw", "wechat-access-auth.json");
|
||||
|
||||
export const getStatePath = (customPath?: string): string =>
|
||||
customPath || DEFAULT_STATE_PATH;
|
||||
|
||||
export const loadState = (customPath?: string): PersistedAuthState | null => {
|
||||
const filePath = getStatePath(customPath);
|
||||
try {
|
||||
const raw = readFileSync(filePath, "utf-8");
|
||||
return JSON.parse(raw) as PersistedAuthState;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const saveState = (state: PersistedAuthState, customPath?: string): void => {
|
||||
const filePath = getStatePath(customPath);
|
||||
mkdirSync(dirname(filePath), { recursive: true });
|
||||
writeFileSync(filePath, JSON.stringify(state, null, 2), { encoding: "utf-8", mode: 0o600 });
|
||||
// 确保已有文件也收紧权限
|
||||
try {
|
||||
chmodSync(filePath, 0o600);
|
||||
} catch {
|
||||
// Windows 等平台可能不支持 chmod,忽略
|
||||
}
|
||||
};
|
||||
|
||||
export const clearState = (customPath?: string): void => {
|
||||
const filePath = getStatePath(customPath);
|
||||
try {
|
||||
unlinkSync(filePath);
|
||||
} catch {
|
||||
// file not found — ignore
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user