Compare commits
	
		
			5 Commits
		
	
	
		
			af8ed90ab3
			...
			54672a5574
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 54672a5574 | |||
| 9ba45c37c2 | |||
| 24f091ac79 | |||
| a457dbabe9 | |||
| c546ad2459 | 
| @@ -6,7 +6,9 @@ | ||||
|   "types": "dist/query-login.d.ts", | ||||
|   "scripts": { | ||||
|     "build": "tsup", | ||||
|     "watch": "tsup --watch" | ||||
|     "watch": "tsup --watch", | ||||
|     "dev": "tsup --watch", | ||||
|     "dev:lib": "pnpm run dev" | ||||
|   }, | ||||
|   "keywords": [], | ||||
|   "author": "abearxiong <xiongxiao@xiongxiao.me>", | ||||
| @@ -16,7 +18,7 @@ | ||||
|     "access": "public" | ||||
|   }, | ||||
|   "peerDependencies": { | ||||
|     "@kevisual/query": "^0.0.12" | ||||
|     "@kevisual/query": "^0.0.15" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@types/node": "^22.13.11", | ||||
|   | ||||
| @@ -11,6 +11,10 @@ export interface Cache { | ||||
|    * @update 删除缓存 | ||||
|    */ | ||||
|   del(): Promise<void>; | ||||
|   /** | ||||
|    * 初始化 | ||||
|    */ | ||||
|   init?: () => Promise<any>; | ||||
| } | ||||
|  | ||||
| export type CacheLoginUser = { | ||||
| @@ -23,7 +27,7 @@ type CacheLogin = { | ||||
|   loginUsers: CacheLoginUser[]; | ||||
| } & CacheLoginUser; | ||||
|  | ||||
| export type CacheStore<T = any> = { | ||||
| export type CacheStore<T = Cache> = { | ||||
|   name: string; | ||||
|   cacheData: CacheLogin; | ||||
|   /** | ||||
| @@ -51,10 +55,6 @@ export type CacheStore<T = any> = { | ||||
|    * 获取缓存的accessToken | ||||
|    */ | ||||
|   getAccessToken(): Promise<string>; | ||||
|   /** | ||||
|    * 初始化 | ||||
|    */ | ||||
|   init(): Promise<void>; | ||||
|   /** | ||||
|    * 清除当前用户 | ||||
|    */ | ||||
| @@ -63,9 +63,14 @@ export type CacheStore<T = any> = { | ||||
|    * 清除所有用户 | ||||
|    */ | ||||
|   clearAll(): Promise<void>; | ||||
| } & Cache; | ||||
|  | ||||
| type LoginCacheStoreOpts = { | ||||
|   getValue(): Promise<CacheLogin>; | ||||
|   setValue(value: CacheLogin): Promise<CacheLogin>; | ||||
|   delValue(): Promise<void>; | ||||
|   init(): Promise<any>; | ||||
| }; | ||||
|  | ||||
| export type LoginCacheStoreOpts = { | ||||
|   name: string; | ||||
|   cache: Cache; | ||||
| }; | ||||
| @@ -94,28 +99,41 @@ export class LoginCacheStore implements CacheStore<any> { | ||||
|    * @param value | ||||
|    * @returns | ||||
|    */ | ||||
|   async set(key: string, value: CacheLogin) { | ||||
|     await this.cache.set(key, value); | ||||
|   async setValue(value: CacheLogin) { | ||||
|     await this.cache.set(this.name, value); | ||||
|     this.cacheData = value; | ||||
|     return value; | ||||
|   } | ||||
|   /** | ||||
|    * 删除缓存 | ||||
|    */ | ||||
|   async del() { | ||||
|   async delValue() { | ||||
|     await this.cache.del(); | ||||
|   } | ||||
|   get(key: string): Promise<CacheLogin> { | ||||
|     return this.cache.get(key); | ||||
|   getValue(): Promise<CacheLogin> { | ||||
|     return this.cache.get(this.name); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * 初始化,设置默认值 | ||||
|    */ | ||||
|   async init() { | ||||
|     this.cacheData = (await this.get(this.name)) || { | ||||
|     const defaultData = { | ||||
|       loginUsers: [], | ||||
|       user: null, | ||||
|       id: null, | ||||
|       accessToken: null, | ||||
|       refreshToken: null, | ||||
|     }; | ||||
|     if (this.cache.init) { | ||||
|       try { | ||||
|         const cacheData = await this.cache.init(); | ||||
|         this.cacheData = cacheData || defaultData; | ||||
|       } catch (error) { | ||||
|         console.log('cacheInit error', error); | ||||
|       } | ||||
|     } else { | ||||
|       this.cacheData = (await this.getValue()) || defaultData; | ||||
|     } | ||||
|   } | ||||
|   /** | ||||
|    * 设置当前用户 | ||||
| @@ -131,7 +149,7 @@ export class LoginCacheStore implements CacheStore<any> { | ||||
|     this.cacheData.id = user.id; | ||||
|     this.cacheData.accessToken = user.accessToken; | ||||
|     this.cacheData.refreshToken = user.refreshToken; | ||||
|     await this.set(this.name, this.cacheData); | ||||
|     await this.setValue(this.cacheData); | ||||
|   } | ||||
|  | ||||
|   getCurrentUser(): Promise<CacheLoginUser> { | ||||
| @@ -160,7 +178,7 @@ export class LoginCacheStore implements CacheStore<any> { | ||||
|     this.cacheData.id = undefined; | ||||
|     this.cacheData.accessToken = undefined; | ||||
|     this.cacheData.refreshToken = undefined; | ||||
|     await this.set(this.name, this.cacheData); | ||||
|     await this.setValue(this.cacheData); | ||||
|   } | ||||
|   async clearAll() { | ||||
|     this.cacheData.loginUsers = []; | ||||
| @@ -168,6 +186,6 @@ export class LoginCacheStore implements CacheStore<any> { | ||||
|     this.cacheData.id = undefined; | ||||
|     this.cacheData.accessToken = undefined; | ||||
|     this.cacheData.refreshToken = undefined; | ||||
|     await this.set(this.name, this.cacheData); | ||||
|     await this.setValue(this.cacheData); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,11 +1,12 @@ | ||||
| import { Cache } from './login-cache.ts'; | ||||
| import { Cache, LoginCacheStore, LoginCacheStoreOpts } from './login-cache.ts'; | ||||
| import { homedir } from 'node:os'; | ||||
| import { join, dirname } from 'node:path'; | ||||
| import { readFileSync } from 'node:fs'; | ||||
| import { readFile, writeFile, unlink, stat, mkdir } from 'node:fs/promises'; | ||||
| import fs from 'node:fs'; | ||||
| import { readFileSync, writeFileSync, accessSync } from 'node:fs'; | ||||
| import { readFile, writeFile, unlink, mkdir } from 'node:fs/promises'; | ||||
| export const fileExists = async (filePath: string, createIfNotExists = false) => { | ||||
|   try { | ||||
|     await stat(filePath); | ||||
|     accessSync(filePath, fs.constants.F_OK); | ||||
|     return true; | ||||
|   } catch (error) { | ||||
|     if (createIfNotExists) { | ||||
| @@ -76,37 +77,48 @@ export class StorageNode implements Storage { | ||||
|   } | ||||
| } | ||||
| export class LoginNodeCache implements Cache { | ||||
|   name: string; | ||||
|   cacheData: any; | ||||
|   filePath: string; | ||||
|   constructor(name: string) { | ||||
|     this.name = name; | ||||
|     const hostname = getHostName(); | ||||
|     this.filePath = join(homedir(), '.config', 'envision', 'config', `${hostname}-${name}.json`); | ||||
|     fileExists(this.filePath, true); | ||||
|     this.loadCache(this.filePath); | ||||
|   } | ||||
|   filepath: string; | ||||
|  | ||||
|   constructor(filepath?: string) { | ||||
|     this.filepath = filepath || join(homedir(), '.config', 'envision', 'config', `${getHostName()}-login.json`); | ||||
|     fileExists(this.filepath, true); | ||||
|   } | ||||
|   async get(_key: string) { | ||||
|     const data = readFileSync(this.filepath, 'utf-8'); | ||||
|     try { | ||||
|       const jsonData = JSON.parse(data); | ||||
|       return jsonData; | ||||
|     } catch (error) { | ||||
|       console.log('get error', error); | ||||
|       return null; | ||||
|     } | ||||
|   } | ||||
|   async set(_key: string, value: any) { | ||||
|     const data = readFileSync(this.filepath, 'utf-8'); | ||||
|     try { | ||||
|       const jsonData = JSON.parse(data); | ||||
|       const newData = { ...jsonData, ...value }; | ||||
|       writeFileSync(this.filepath, JSON.stringify(newData, null, 2)); | ||||
|     } catch (error) { | ||||
|       console.log('set error', error); | ||||
|     } | ||||
|   } | ||||
|   async del() { | ||||
|     await unlink(this.filepath); | ||||
|   } | ||||
|   async loadCache(filePath: string) { | ||||
|     try { | ||||
|       const data = await readFile(filePath, 'utf-8'); | ||||
|       const jsonData = JSON.parse(data); | ||||
|       this.cacheData = jsonData; | ||||
|       return jsonData; | ||||
|     } catch (error) { | ||||
|       console.log('loadCache error', error); | ||||
|       const defaultData = { loginUsers: [] }; | ||||
|       this.cacheData = defaultData; | ||||
|       await writeFile(filePath, JSON.stringify(defaultData, null, 2)); | ||||
|       return defaultData; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   async get() { | ||||
|     return this.cacheData; | ||||
|   } | ||||
|   async set(key: string, value: any) { | ||||
|     this.cacheData = value; | ||||
|     await writeFile(this.filePath, JSON.stringify(this.cacheData, null, 2)); | ||||
|   } | ||||
|   async del() { | ||||
|     await unlink(this.filePath); | ||||
|   async init() { | ||||
|     return await this.loadCache(this.filepath); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -8,7 +8,7 @@ export class QueryLoginNode extends QueryLogin { | ||||
|     super({ | ||||
|       ...opts, | ||||
|       storage, | ||||
|       cache: new LoginNodeCache('login'), | ||||
|       cache: new LoginNodeCache(), | ||||
|     }); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -26,7 +26,7 @@ export class QueryLogin { | ||||
|   /** | ||||
|    * query login cache, 非实际操作, 一个cache的包裹模块 | ||||
|    */ | ||||
|   cache: CacheStore; | ||||
|   cacheStore: CacheStore; | ||||
|   isBrowser: boolean; | ||||
|   load?: boolean; | ||||
|   storage: Storage; | ||||
| @@ -34,7 +34,7 @@ export class QueryLogin { | ||||
|  | ||||
|   constructor(opts?: QueryLoginOpts) { | ||||
|     this.query = opts?.query || new Query(); | ||||
|     this.cache = new LoginCacheStore({ name: 'login', cache: opts.cache }); | ||||
|     this.cacheStore = new LoginCacheStore({ name: 'login', cache: opts.cache }); | ||||
|     this.isBrowser = opts?.isBrowser ?? true; | ||||
|     this.init(); | ||||
|     this.onLoad = opts?.onLoad; | ||||
| @@ -44,12 +44,19 @@ export class QueryLogin { | ||||
|     this.query = query; | ||||
|   } | ||||
|   private async init() { | ||||
|     await this.cache.init(); | ||||
|     await this.cacheStore.init() | ||||
|     this.load = true; | ||||
|     this.onLoad?.(); | ||||
|   } | ||||
|   async post<T = any>(data: any, opts?: DataOpts) { | ||||
|     return this.query.post<T>({ path: 'user', ...data }, opts); | ||||
|     try { | ||||
|       return this.query.post<T>({ path: 'user', ...data }, opts); | ||||
|     } catch (error) { | ||||
|       console.log('error', error); | ||||
|       return { | ||||
|         code: 400, | ||||
|       } as any; | ||||
|     } | ||||
|   } | ||||
|   /** | ||||
|    * 登录, | ||||
| @@ -89,7 +96,7 @@ export class QueryLogin { | ||||
|       if (resUser.code === 200) { | ||||
|         const user = resUser.data; | ||||
|         if (user) { | ||||
|           this.cache.setLoginUser({ | ||||
|           this.cacheStore.setLoginUser({ | ||||
|             user, | ||||
|             id: user.id, | ||||
|             accessToken, | ||||
| @@ -107,10 +114,10 @@ export class QueryLogin { | ||||
|    * @returns | ||||
|    */ | ||||
|   async queryRefreshToken(refreshToken?: string) { | ||||
|     const _refreshToken = refreshToken || this.cache.getRefreshToken(); | ||||
|     const _refreshToken = refreshToken || this.cacheStore.getRefreshToken(); | ||||
|     let data = { refreshToken: _refreshToken }; | ||||
|     if (!_refreshToken) { | ||||
|       await this.cache.clearCurrentUser(); | ||||
|       await this.cacheStore.clearCurrentUser(); | ||||
|       return { | ||||
|         code: 401, | ||||
|         message: '请先登录', | ||||
| @@ -137,7 +144,7 @@ export class QueryLogin { | ||||
|   async afterCheck401ToRefreshToken(response: Result, ctx?: { req?: any; res?: any; fetch?: any }, refetch?: boolean) { | ||||
|     const that = this; | ||||
|     if (response?.code === 401) { | ||||
|       const hasRefreshToken = await that.cache.getRefreshToken(); | ||||
|       const hasRefreshToken = await that.cacheStore.getRefreshToken(); | ||||
|       if (hasRefreshToken) { | ||||
|         const res = await that.queryRefreshToken(hasRefreshToken); | ||||
|         if (res.code === 200) { | ||||
| @@ -159,13 +166,57 @@ export class QueryLogin { | ||||
|           } | ||||
|         } else { | ||||
|           that.storage.removeItem('token'); | ||||
|           await that.cache.clearCurrentUser(); | ||||
|           await that.cacheStore.clearCurrentUser(); | ||||
|         } | ||||
|         return res; | ||||
|       } | ||||
|     } | ||||
|     return response as any; | ||||
|   } | ||||
|   /** | ||||
|    * 一个简单的401处理, 如果401,则刷新token, 如果refreshToken不存在,则返回401 | ||||
|    * refetch 是否重新请求, 会有bug,无限循环,按需要使用 | ||||
|    * @param response | ||||
|    * @param ctx | ||||
|    * @param opts | ||||
|    * @returns | ||||
|    */ | ||||
|   async run401Action( | ||||
|     response: Result, | ||||
|     ctx?: { req?: any; res?: any; fetch?: any }, | ||||
|     opts?: { | ||||
|       /** | ||||
|        * 是否重新请求, 会有bug,无限循环,按需要使用 | ||||
|        */ | ||||
|       refetch?: boolean; | ||||
|       /** | ||||
|        * check之后的回调 | ||||
|        */ | ||||
|       afterCheck?: (res: Result) => any; | ||||
|       /** | ||||
|        * 401处理后, 还是401, 则回调 | ||||
|        */ | ||||
|       afterAlso401?: (res: Result) => any; | ||||
|     }, | ||||
|   ) { | ||||
|     const that = this; | ||||
|     const refetch = opts?.refetch ?? false; | ||||
|     if (response?.code === 401) { | ||||
|       if (that.query.stop === true) { | ||||
|         return { code: 500, success: false, message: 'refresh token loading...' }; | ||||
|       } | ||||
|       that.query.stop = true; | ||||
|       const res = await that.afterCheck401ToRefreshToken(response, ctx, refetch); | ||||
|       that.query.stop = false; | ||||
|       opts?.afterCheck?.(res); | ||||
|       if (res.code === 401) { | ||||
|         opts?.afterAlso401?.(res); | ||||
|       } | ||||
|       return res; | ||||
|     } else { | ||||
|       return response as any; | ||||
|     } | ||||
|   } | ||||
|   /** | ||||
|    * 获取用户信息 | ||||
|    * @param token | ||||
| @@ -206,7 +257,7 @@ export class QueryLogin { | ||||
|    * @returns | ||||
|    */ | ||||
|   async switchUser(username: string) { | ||||
|     const localUserList = await this.cache.getCurrentUserList(); | ||||
|     const localUserList = await this.cacheStore.getCurrentUserList(); | ||||
|     const user = localUserList.find((userItem) => userItem.user.username === username); | ||||
|     if (user) { | ||||
|       this.storage.setItem('token', user.accessToken || ''); | ||||
| @@ -230,11 +281,20 @@ export class QueryLogin { | ||||
|     } | ||||
|     return res; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * 退出登陆,去掉token, 并删除缓存 | ||||
|    * @returns | ||||
|    */ | ||||
|   async logout() { | ||||
|     this.storage.removeItem('token'); | ||||
|     this.cache.del(); | ||||
|     return this.post<Result>({ key: 'logout' }); | ||||
|     const users = await this.cacheStore.getCurrentUserList(); | ||||
|     const tokens = users | ||||
|       .map((user) => { | ||||
|         return user?.accessToken; | ||||
|       }) | ||||
|       .filter(Boolean); | ||||
|     this.cacheStore.delValue(); | ||||
|     return this.post<Result>({ key: 'logout', data: { tokens } }); | ||||
|   } | ||||
|   /** | ||||
|    * 检查用户名的组,这个用户是否存在 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user