Files
IC-Coder-Plugin/src/utils/jwtUtils.ts
Roe-xin 208c24682b feat: 实现试用用户欢迎引导和过期检测功能
- 新增试用用户首次登录欢迎弹窗,展示使用教程
- 新增试用期过期检测服务和过期提醒弹窗
- 从 JWT token 中提取 ispluginTrial 标识判断用户类型
- 试用用户跳过邀请码验证流程
- 在消息发送前检查试用期是否过期
- 新增 ExpiredPanel 和 WelcomePanel 面板组件
- 新增 expiredModal 和 welcomeModal 视图组件
- 优化用户登录流程,根据用户类型显示不同引导
2026-02-26 15:42:18 +08:00

127 lines
3.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* JWT 工具函数
*/
/**
* JWT Payload 接口
*/
export interface JwtPayload {
sub?: string; // subject (通常是 userId)
userId?: number; // 用户ID (驼峰命名)
user_id?: number; // 用户ID (下划线命名)
exp?: number; // 过期时间
iat?: number; // 签发时间
ispluginTrial?: boolean; // 是否是插件试用用户
[key: string]: unknown;
}
/**
* 解析 JWT token 的 payload
* @param token JWT token
* @returns 解析后的 payload解析失败返回 null
*/
export function parseJwtPayload(token: string): JwtPayload | null {
try {
const parts = token.split(".");
if (parts.length !== 3) {
console.warn("[JWT] token 格式不正确期望3部分实际:", parts.length);
return null;
}
// payload 是第二部分base64url 编码
const payload = parts[1];
// base64url 转 base64
const base64 = payload.replace(/-/g, "+").replace(/_/g, "/");
// 解码
const jsonStr = Buffer.from(base64, "base64").toString("utf-8");
const parsed = JSON.parse(jsonStr);
console.log("[JWT] 解析成功, payload 字段:", Object.keys(parsed));
console.log("[JWT] payload 内容:", JSON.stringify(parsed));
return parsed;
} catch (error) {
console.error("[JWT] 解析失败:", error);
return null;
}
}
/**
* 从 JWT token 中获取用户ID
* @param token JWT token
* @returns 用户ID字符串获取失败返回 null
*/
export function getUserIdFromToken(token: string): string | null {
const payload = parseJwtPayload(token);
if (!payload) {
return null;
}
// 支持多种字段名user_id, userId, sub
if (payload.user_id !== undefined) {
return String(payload.user_id);
}
if (payload.userId !== undefined) {
return String(payload.userId);
}
if (payload.sub !== undefined) {
return String(payload.sub);
}
console.warn("[JWT] payload 中没有 user_id, userId 或 sub 字段");
return null;
}
/**
* 检测 JWT token 是否已过期
* @param token JWT token
* @param bufferSeconds 提前多少秒判定为过期默认60秒
* @returns true 表示已过期false 表示未过期null 表示无法判断
*/
export function isTokenExpired(
token: string,
bufferSeconds: number = 60,
): boolean | null {
const payload = parseJwtPayload(token);
if (!payload) {
return null;
}
if (payload.exp === undefined) {
console.warn("[JWT] payload 中没有 exp 字段,无法判断过期");
return null;
}
const now = Math.floor(Date.now() / 1000);
const expTime = payload.exp - bufferSeconds;
const isExpired = now >= expTime;
if (isExpired) {
console.warn("[JWT] token 已过期exp:", payload.exp, "当前:", now);
}
return isExpired;
}
/**
* 从 JWT token 中获取 ispluginTrial 标识
* @param token JWT token
* @returns true=插件试用用户false=正式用户null=无法判断
*/
export function getIsPluginTrialFromToken(token: string): boolean | null {
const payload = parseJwtPayload(token);
if (!payload) {
return null;
}
// 检查 ispluginTrial 字段
if (payload.ispluginTrial !== undefined) {
console.log("[JWT] 从 token 中获取到 ispluginTrial:", payload.ispluginTrial);
return payload.ispluginTrial === true;
}
console.log("[JWT] token 中没有 ispluginTrial 字段,判定为正式用户");
return false;
}