diff --git a/oaer/lib/api/webapi.ts b/oaer/lib/api/webapi.ts index 6a48e22..069d032 100644 --- a/oaer/lib/api/webapi.ts +++ b/oaer/lib/api/webapi.ts @@ -27,14 +27,31 @@ export const token = { update: () => { return new Promise((resolve) => { resolve(token.value) }) }, - set_updator: (fn: () => Promise<{ raw: () => string }>) => { + set_updator: (fn: () => Promise) => { + let locked = false token.update = () => { + if (locked) { + return new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, 1000) + }).then(() => { + return token.update() + }) + } + locked = true return new Promise((resolve, reject) => { + if (token.value) { + locked = false + resolve(token.value) + return + } fn().then((e) => { - resolve(e.raw()) + token.value = e + resolve(e) }).catch(() => { reject() - }) + }).finally(() => { locked = false }) }) } } @@ -78,12 +95,23 @@ const responseFailed = (error: AxiosError) => { needRetry = false } else if (response?.status == 401) { needRetry = false - if (data.code === 40103) { + // AuthNotFound = New(40100, "auth not found") + // AuthExpired = New(40102, "auth expired") + if (data.code === 40102 || data.code === 40100) { + token.value = '' + return token.update().then(() => { + return requestRetry(1000, response!) + }) } } if (!needRetry) { return Promise.reject(data || response) }; + return requestRetry(1000, response!) +} + +const requestRetry = (delay = 0, response: AxiosResponse) => { + const config = response?.config // @ts-ignore const { __retryCount = 0, retryDelay = 1000, retryTimes } = config; // 在请求对象上设置重试次数 @@ -93,18 +121,20 @@ const responseFailed = (error: AxiosError) => { if (__retryCount >= retryTimes) { return Promise.reject(response?.data || response?.headers.error) } + if (delay <= 0) { + return proxy.request(config as any) + } // 延时处理 - const delay = new Promise((resolve) => { + return new Promise((resolve) => { setTimeout(() => { resolve(); - }, retryDelay); - }); - // 重新发起请求 - return delay.then(function() { - return proxy.request(config as any); - }); + }, delay) + }).then(() => { + return proxy.request(config as any) + }) } + proxy.interceptors.response.use(responseSuccess, responseFailed) interface data { diff --git a/oaer/lib/components/index.ts b/oaer/lib/components/index.ts index 6bfc9fd..b37bfed 100644 --- a/oaer/lib/components/index.ts +++ b/oaer/lib/components/index.ts @@ -8,6 +8,7 @@ import slide from './slide' import v from './v2dom' import logic from '../logic' +import bus from '../bus' export default class { slide: slide frame: HTMLElement @@ -34,11 +35,10 @@ export default class { inner: '登录', onclick: () => { console.log('click login') - logic.login() + bus.emit('login') } }) return () => { - console.log(logic.ready) if (logic.ready) { return frame_user } else { diff --git a/oaer/lib/logic.ts b/oaer/lib/logic.ts index f742935..5b732cc 100644 --- a/oaer/lib/logic.ts +++ b/oaer/lib/logic.ts @@ -7,6 +7,7 @@ import bus from './bus' import proxy from './components/proxy' import api, { apitoken } from './api' +import * as typs from './typs' class Token { iat?: string @@ -17,7 +18,7 @@ class Token { icon?: string name?: string uid?: string - access?: any + access?: typs.Auths private _raw: string private _typ: 'refresh' | 'oa' | 'app' constructor(typ: 'refresh' | 'oa' | 'app') { @@ -42,6 +43,7 @@ class Token { // sign = parts[2] this._raw = t Object.assign(this, body) + this.access = typs.NewAuths(body.access || []) localStorage.setItem(this._typ, t) } catch (error) { console.warn('Error parsing JWT:', error); @@ -59,6 +61,9 @@ class Token { if (!logic.token.refresh.isVaild() || this._typ === 'refresh') { reject() } + if (this.isVaild()) { + return resolve(this) + } let aid = logic.oa_id if (this._typ === 'app') { aid = logic.app_id @@ -119,25 +124,29 @@ const logic = proxy.Watch({ if (logic.token.refresh.isVaild()) { logic.app_id = logic.token.refresh.aid! logic.token.oa.update().then((e) => { - apitoken.set_updator(logic.token.oa.update) + apitoken.value = e.raw() + apitoken.set_updator(() => new Promise((resolve, reject) => { + logic.token.oa.update().then(e => { resolve(e.raw()) }).catch(() => reject) + }) + ) if (logic.app_id !== logic.oa_id) { logic.token.app.update().then((e) => { logic.ready = true resolve(e); - }).catch(() => reject()) + }).catch((e) => reject(e)) } else { logic.ready = true resolve(e); } - }).catch(() => { - reject() + }).catch((e) => { + reject(e) }) } else { reject() } }).catch(e => { console.warn(`can not get info from ${logic.host}: ${e} `) - reject(); + reject(e); }) }) }, diff --git a/oaer/lib/main.ts b/oaer/lib/main.ts index 39b93ef..1836b00 100644 --- a/oaer/lib/main.ts +++ b/oaer/lib/main.ts @@ -9,6 +9,9 @@ import './assets/css/oaer.scss' import bus from './bus' import logic from './logic' import ui from './components' +import * as typs from './typs' +import api from './api' + export default new class { private ui?: ui @@ -21,7 +24,17 @@ export default new class { if (code) { logic.token.refresh.set(code) } - return logic.init() + return new Promise((resolve, reject) => { + logic.init().then((e) => { + api.user.Get(e.uid!).then((u) => { + resolve(Object.assign(u, { access: e.access! })) + }).catch((e) => { + reject(e) + }) + }).catch((e) => { + reject(e) + }) + }) } render_ui(domid = 'voa') { if (!this.ui) { @@ -46,6 +59,26 @@ export default new class { on(evt: string, fn: (d?: any) => void) { bus.on(evt, fn) } -}() + api() { + return { + user: { + Get: api.user.Get, + List: api.user.List, + }, + app: { + Get: api.app.Get, + List: api.app.List, + }, + } + } + Token() { + return logic.token.app.raw() + } + TokenRefresh() { + return new Promise((resolve, reject) => { + logic.token.app.update().then((e) => { resolve(e.raw()) }).catch(() => reject) + }) + } +} diff --git a/oaer/lib/typs.ts b/oaer/lib/typs.ts new file mode 100644 index 0000000..0947130 --- /dev/null +++ b/oaer/lib/typs.ts @@ -0,0 +1,106 @@ +/* + * typ.ts + * Copyright (C) 2024 veypi + * 2024-10-28 14:19 + * Distributed under terms of the GPL license. + */ + +export * from './api/models' +import * as models from './api/models' + +export interface UserData extends models.User { + access: Auths +} + +export interface modelsSimpleAuth { + level: number + name: string + tid: string +} + + +export const R = { + // 应用管理配置权限 + App: 'app', + // 用户管理和绑定应用权限 + User: 'user', + // 权限资源定义权限 + Resource: 'resource', + // 角色管理和绑定用户权限 + Role: 'role', + // 权限管理和绑定角色权限 + Auth: 'auth', +} + + +export enum AccessLevel { + None = 0, + Do = 1, + Read = 1, + Create = 2, + Update = 3, + Delete = 4, + All = 5 +} + +export const LevelOptions = [ + { key: 0, name: '无权限' }, + { key: 1, name: '读取数据权限' }, + { key: 2, name: '创建数据权限' }, + { key: 3, name: '更新数据权限' }, + { key: 4, name: '删除数据权限' }, + { key: 5, name: '管理员权限' }, +] + +class authLevel { + level = AccessLevel.None + constructor(level: number) { + this.level = level + } + CanDo(): boolean { + return this.level >= AccessLevel.Do + } + CanRead(): boolean { + return this.level >= AccessLevel.Read + } + CanCreate(): boolean { + return this.level >= AccessLevel.Create + } + CanUpdate(): boolean { + return this.level >= AccessLevel.Update + } + CanDelete(): boolean { + return this.level >= AccessLevel.Delete + } + CanDoAny(): boolean { + return this.level >= AccessLevel.All + } +} + +export class auths { + private readonly list: modelsSimpleAuth[] + + constructor(auths: modelsSimpleAuth[]) { + this.list = auths + } + + Get(name: string, rid: string): authLevel { + let l = AccessLevel.None + for (let i of this.list) { + if (i.name == name && (!i.tid || i.tid === rid) && i.level > l) { + l = i.level + } + } + return new authLevel(l) + } +} + +export interface Auths { + Get(name: string, rid: string): authLevel +} + + +export function NewAuths(a: modelsSimpleAuth[]): Auths { + return new auths(a) +} +