Compare commits

...

16 Commits

Author SHA1 Message Date
bae8275b11 feat: add set login token 2025-05-20 16:06:32 +08:00
0a0ffbdb23 update git 2025-05-17 15:54:30 +08:00
557cd99b20 temp 2025-04-24 12:41:31 +08:00
8b4312782d temp 2025-04-10 02:14:32 +08:00
f8af24506b fix: update query-login getme if has token not check401 2025-04-03 01:30:30 +08:00
f1024941ed fix:clear console 2025-04-03 01:08:20 +08:00
98c8a2ad86 Merge branch 'main' of git.xiongxiao.me:kevisual/kevisual-query-login 2025-03-30 20:23:08 +08:00
8ac11bbd28 fix: query login for node 2025-03-30 20:22:37 +08:00
05f0373834 feat: add query by login web wechat 2025-03-27 10:58:59 +08:00
e0bf83f062 fix: type currrentUser type and list is not same 2025-03-23 21:43:02 +08:00
fdf6d3ac0a update 2025-03-23 12:27:17 +08:00
54672a5574 fix: fix login-node-cache error 2025-03-23 12:16:43 +08:00
9ba45c37c2 add logout 2025-03-23 02:59:25 +08:00
24f091ac79 add run in actions 2025-03-22 14:36:16 +08:00
a457dbabe9 temp 2025-03-22 13:33:16 +08:00
c546ad2459 add lib:turbo 2025-03-22 13:31:51 +08:00
7 changed files with 337 additions and 79 deletions

65
.gitignore vendored
View File

@@ -1,5 +1,66 @@
dist
node_modules
# mac
.DS_Store
.env
.env*
!.env*example
dist
build
logs
.turbo
pack-dist
# astro
.astro
# next
.next
# nuxt
.nuxt
# vercel
.vercel
# vuepress
.vuepress/dist
# coverage
coverage/
# typescript
*.tsbuildinfo
# debug logs
*.log
*.tmp
# vscode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# idea
.idea
# system
Thumbs.db
ehthumbs.db
Desktop.ini
# temp files
*.tmp
*.temp
# local development
*.local
public/r
.pnpm-store

24
.turbo/turbo-build.log Normal file
View File

@@ -0,0 +1,24 @@
WARN Issue while reading "/home/ubuntu/kevisual/center/submodules/query-login/.npmrc". Failed to replace env in config: ${ME_NPM_TOKEN}
WARN Issue while reading "/home/ubuntu/kevisual/center/.npmrc". Failed to replace env in config: ${ME_NPM_TOKEN}
> @kevisual/query-login@0.0.2 build /home/ubuntu/kevisual/center/submodules/query-login
> tsup
CLI Building entry: src/query-login-browser.ts, src/query-login-node.ts, src/query-login.ts
CLI Using tsconfig: tsconfig.json
CLI tsup v8.4.0
CLI Using tsup config: /home/ubuntu/kevisual/center/submodules/query-login/tsup.config.ts
CLI Target: esnext
CLI Cleaning output folder
ESM Build start
ESM You have emitDecoratorMetadata enabled but @swc/core was not installed, skipping swc plugin
ESM dist/query-login-node.js 15.31 KB
ESM dist/query-login-browser.js 11.80 KB
ESM dist/query-login.js 11.58 KB
ESM ⚡️ Build success in 10ms
DTS Build start
DTS ⚡️ Build success in 873ms
DTS dist/query-login-browser.d.ts 332.00 B
DTS dist/query-login-node.d.ts 701.00 B
DTS dist/query-login.d.ts 4.80 KB

View File

@@ -1,12 +1,14 @@
{
"name": "@kevisual/query-login",
"version": "0.0.2",
"version": "0.0.6",
"description": "",
"main": "dist/query-login.js",
"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,10 +18,10 @@
"access": "public"
},
"peerDependencies": {
"@kevisual/query": "^0.0.12"
"@kevisual/query": "^0.0.17"
},
"devDependencies": {
"@types/node": "^22.13.11",
"@types/node": "^22.14.1",
"tsup": "^8.4.0"
},
"exports": {
@@ -29,7 +31,7 @@
"./browser": "./dist/query-login-browser.js"
},
"dependencies": {
"@kevisual/cache": "^0.0.1",
"dotenv": "^16.4.7"
"@kevisual/cache": "^0.0.2",
"dotenv": "^16.5.0"
}
}

View File

@@ -11,10 +11,23 @@ export interface Cache {
* @update 删除缓存
*/
del(): Promise<void>;
/**
* 初始化
*/
init?: () => Promise<any>;
}
type User = {
avatar?: string;
description?: string;
id?: string;
needChangePassword?: boolean;
orgs?: string[];
type?: string;
username?: string;
};
export type CacheLoginUser = {
user?: any;
user?: User;
id?: string;
accessToken?: string;
refreshToken?: string;
@@ -23,11 +36,15 @@ type CacheLogin = {
loginUsers: CacheLoginUser[];
} & CacheLoginUser;
export type CacheStore<T = any> = {
export type CacheStore<T = Cache> = {
name: string;
/**
* 缓存数据
* @important 需要先调用init
*/
cacheData: CacheLogin;
/**
* 实际操作的cache
* 实际操作的cache, 需要先调用init
*/
cache: T;
@@ -38,7 +55,7 @@ export type CacheStore<T = any> = {
/**
* 获取当前用户
*/
getCurrentUser(): Promise<CacheLoginUser>;
getCurrentUser(): Promise<User>;
/**
* 获取当前用户列表
*/
@@ -51,10 +68,6 @@ export type CacheStore<T = any> = {
* 获取缓存的accessToken
*/
getAccessToken(): Promise<string>;
/**
* 初始化
*/
init(): Promise<void>;
/**
* 清除当前用户
*/
@@ -63,9 +76,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 +112,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 +162,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 +191,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 +199,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);
}
}

View File

@@ -1,14 +1,21 @@
import { Cache } 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';
export const fileExists = async (filePath: string, createIfNotExists = false) => {
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 = true, isFile = true, isDir = false }: { createIfNotExists?: boolean; isFile?: boolean; isDir?: boolean } = {},
) => {
try {
await stat(filePath);
accessSync(filePath, fs.constants.F_OK);
return true;
} catch (error) {
if (createIfNotExists) {
if (createIfNotExists && isDir) {
await mkdir(filePath, { recursive: true });
return true;
} else if (createIfNotExists && isFile) {
await mkdir(dirname(filePath), { recursive: true });
return false;
}
@@ -24,6 +31,9 @@ export const readConfigFile = (filePath: string) => {
return {};
}
};
export const writeConfigFile = (filePath: string, data: any) => {
writeFileSync(filePath, JSON.stringify(data, null, 2));
};
export const getHostName = () => {
const configDir = join(homedir(), '.config', 'envision');
const configFile = join(configDir, 'config.json');
@@ -40,14 +50,13 @@ export class StorageNode implements Storage {
const configDir = join(homedir(), '.config', 'envision');
const hostname = getHostName();
this.filePath = join(configDir, 'config', `${hostname}-storage.json`);
fileExists(this.filePath, true);
fileExists(this.filePath, { isFile: true });
}
async loadCache() {
const filePath = this.filePath;
try {
const data = await readFile(filePath, 'utf-8');
const jsonData = JSON.parse(data);
this.cacheData = jsonData;
const data = await readConfigFile(filePath);
this.cacheData = data;
} catch (error) {
this.cacheData = {};
await writeFile(filePath, JSON.stringify(this.cacheData, null, 2));
@@ -76,37 +85,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, { isFile: true });
}
async get(_key: string) {
try {
const filePath = this.filepath;
const data = readConfigFile(filePath);
return data;
} catch (error) {
console.log('get error', error);
return {};
}
}
async set(_key: string, value: any) {
try {
const data = readConfigFile(this.filepath);
const newData = { ...data, ...value };
writeConfigFile(this.filepath, newData);
} 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);
console.log('create new cache file:', filePath);
const defaultData = { loginUsers: [] };
this.cacheData = defaultData;
await writeFile(filePath, JSON.stringify(defaultData, null, 2));
writeConfigFile(filePath, defaultData);
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);
}
}

View File

@@ -8,7 +8,7 @@ export class QueryLoginNode extends QueryLogin {
super({
...opts,
storage,
cache: new LoginNodeCache('login'),
cache: new LoginNodeCache(),
});
}
}

View File

@@ -1,4 +1,4 @@
import { Query } from '@kevisual/query';
import { Query, BaseQuery } from '@kevisual/query';
import type { Result, DataOpts } from '@kevisual/query/query';
import { setBaseResponse } from '@kevisual/query/query';
import { LoginCacheStore, CacheStore } from './login-cache.ts';
@@ -21,20 +21,21 @@ export type QueryLoginResult = {
refreshToken: string;
};
export class QueryLogin {
query: Query;
export class QueryLogin extends BaseQuery {
/**
* query login cache 非实际操作, 一个cache的包裹模块
*/
cache: CacheStore;
cacheStore: CacheStore;
isBrowser: boolean;
load?: boolean;
storage: Storage;
onLoad?: () => void;
constructor(opts?: QueryLoginOpts) {
this.query = opts?.query || new Query();
this.cache = new LoginCacheStore({ name: 'login', cache: opts.cache });
super({
query: opts?.query || new Query(),
});
this.cacheStore = new LoginCacheStore({ name: 'login', cache: opts.cache });
this.isBrowser = opts?.isBrowser ?? true;
this.init();
this.onLoad = opts?.onLoad;
@@ -44,12 +45,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;
}
}
/**
* 登录,
@@ -79,6 +87,41 @@ export class QueryLogin {
}
return res;
}
/**
* 设置token
* @param token
*/
async setLoginToken(token: { accessToken: string; refreshToken: string }) {
const { accessToken, refreshToken } = token;
this.storage.setItem('token', accessToken || '');
await this.beforeSetLoginUser({ accessToken, refreshToken });
}
async loginByWechat(data: { code: string }) {
const res = await this.post<QueryLoginResult>({ path: 'wx', key: 'open-login', code: data.code });
if (res.code === 200) {
const { accessToken, refreshToken } = res?.data || {};
this.storage.setItem('token', accessToken || '');
await this.beforeSetLoginUser({ accessToken, refreshToken });
}
return res;
}
/**
* 检测微信登录登陆成功后调用onSuccess否则调用onError
* @param param0
*/
async checkWechat({ onSuccess, onError }: { onSuccess?: (res: QueryLoginResult) => void; onError?: (res: any) => void }) {
const url = new URL(window.location.href);
const code = url.searchParams.get('code');
const state = url.searchParams.get('state');
if (code && state) {
const res = await this.loginByWechat({ code });
if (res.code === 200) {
onSuccess?.(res.data);
} else {
onError?.(res);
}
}
}
/**
* 登陆成功,需要获取用户信息进行缓存
* @param param0
@@ -89,7 +132,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 +150,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: '请先登录',
@@ -129,6 +172,7 @@ export class QueryLogin {
}
/**
* 检查401错误并刷新token, 如果refreshToken存在则刷新token, 否则返回401
* 拦截请求请使用run401Action 不要直接使用 afterCheck401ToRefreshToken
* @param response
* @param ctx
* @param refetch
@@ -137,7 +181,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 +203,58 @@ 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无限循环按需要使用
* TODO:
* @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
@@ -181,10 +270,13 @@ export class QueryLogin {
if (config.headers) {
config.headers['Authorization'] = `Bearer ${_token}`;
}
if (!_token) {
// TODO: 取消请求,因为没有登陆
}
return config;
},
afterResponse: async (response, ctx) => {
if (response?.code === 401 && check401) {
if (response?.code === 401 && check401 && !token) {
return await that.afterCheck401ToRefreshToken(response, ctx);
}
return response as any;
@@ -192,6 +284,25 @@ export class QueryLogin {
},
);
}
/**
* 检查本地用户如果本地用户存在则返回本地用户否则返回null
* @returns
*/
async checkLocalUser() {
const user = await this.cacheStore.getCurrentUser();
if (user) {
return user;
}
return null;
}
/**
* 检查本地token是否存在简单的判断是否已经属于登陆状态
* @returns
*/
async checkLocalToken() {
const token = this.storage.getItem('token');
return !!token;
}
/**
* 请求更新,切换用户, 使用switchUser
* @param username
@@ -206,7 +317,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 +341,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 } });
}
/**
* 检查用户名的组,这个用户是否存在