@jetlinks-web/core 2.2.0 → 2.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jetlinks-web/core",
3
- "version": "2.2.0",
3
+ "version": "2.2.2",
4
4
  "main": "index.ts",
5
5
  "module": "index.ts",
6
6
  "keywords": [
@@ -22,8 +22,8 @@
22
22
  "axios": "^1.7.4",
23
23
  "rxjs": "^7.8.1",
24
24
  "@jetlinks-web/constants": "^1.0.9",
25
- "@jetlinks-web/utils": "^1.2.8",
26
- "@jetlinks-web/types": "^1.0.2"
25
+ "@jetlinks-web/types": "^1.0.2",
26
+ "@jetlinks-web/utils": "^1.2.8"
27
27
  },
28
28
  "publishConfig": {
29
29
  "registry": "https://registry.npmjs.org/",
package/src/axios.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { TOKEN_KEY, BASE_API } from '@jetlinks-web/constants'
2
- import { getToken } from '@jetlinks-web/utils'
2
+ import {getToken, randomString} from '@jetlinks-web/utils'
3
3
  import axios from 'axios'
4
4
  import type {
5
5
  AxiosInstance,
@@ -13,6 +13,7 @@ import {isFunction, isObject} from 'lodash-es'
13
13
  interface Options {
14
14
 
15
15
  tokenExpiration: (err: AxiosError<any>, response: AxiosResponse) => void
16
+ handleReconnect: (err: AxiosError<any>, response: AxiosResponse) => Promise<any>
16
17
  filter_url?: Array<string>
17
18
  code?: number
18
19
  codeKey?: string
@@ -35,7 +36,7 @@ interface Options {
35
36
  */
36
37
  handleError?: (msg: string, status: string | number, error: AxiosError<any>) => void
37
38
  requestOptions?: (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig | Record<string, any>
38
-
39
+ isCreateTokenRefresh?: boolean
39
40
  }
40
41
 
41
42
  interface RequestOptions {
@@ -58,13 +59,33 @@ let _options: Options = {
58
59
  langKey: 'lang',
59
60
  requestOptions: (config) => ({}),
60
61
  tokenExpiration: () => {},
62
+ handleReconnect: () => new Promise(),
63
+ isCreateTokenRefresh: false
61
64
  }
62
65
 
66
+ let failedQueue = [];
67
+ let isRefreshing = false;
68
+
63
69
  const isApp = (window as any).__MICRO_APP_ENVIRONMENT__
64
- const controller = new AbortController();
65
70
 
71
+ const pendingRequests = new Map<string, AbortController>();
72
+ const requestRecords = (config: InternalAxiosRequestConfig) => {
73
+ const key = randomString(32)
74
+
75
+ // 取消重复请求
76
+ if (pendingRequests.has(key)) {
77
+ pendingRequests.get(key)?.abort()
78
+ }
79
+
80
+ const controller = new AbortController()
81
+ config.signal = controller.signal;
82
+ config.__requestKey = key;
83
+
84
+ pendingRequests.set(key, controller)
85
+ }
66
86
  const handleRequest = (config: InternalAxiosRequestConfig) => {
67
- const token = getToken()
87
+ requestRecords(config)
88
+ const token = getToken();
68
89
  const lang = localStorage.getItem(_options.langKey)
69
90
  const localBaseApi = localStorage.getItem('')
70
91
 
@@ -97,16 +118,19 @@ const handleRequest = (config: InternalAxiosRequestConfig) => {
97
118
  }
98
119
  }
99
120
 
100
-
101
121
  return config
102
122
  }
103
123
 
104
124
  const handleResponse = (response: AxiosResponse) => {
105
-
106
125
  if (_options.handleResponse && isFunction(_options.handleResponse)) {
107
126
  return _options.handleResponse(response)
108
127
  }
109
128
 
129
+ const __key = response.config?.__requestKey
130
+ if(__key){
131
+ pendingRequests.delete(__key)
132
+ }
133
+
110
134
  if (response.data instanceof ArrayBuffer) {
111
135
  return response
112
136
  }
@@ -124,7 +148,35 @@ const handleResponse = (response: AxiosResponse) => {
124
148
  return response.data
125
149
  }
126
150
 
127
- const errorHandler = (err: AxiosError<any>) => {
151
+ const createTokenRefreshHandler = async (err) => {
152
+ const originalRequest = err.config;
153
+ if (isRefreshing) { // 记录之后失败的请求
154
+ return new Promise((resolve, reject) => {
155
+ failedQueue.push({ resolve, reject });
156
+ }).then((_token) => {
157
+ originalRequest.headers[TOKEN_KEY] = _token;
158
+ instance(originalRequest)
159
+ }).catch(err => Promise.reject(err))
160
+ }
161
+ originalRequest._retry = true;
162
+ isRefreshing = true;
163
+ try {
164
+ const loginResult = await _options.handleReconnect?.()
165
+ if(loginResult){
166
+ const token = getToken() // 更新请求头, 修改全部的token
167
+ originalRequest.headers[TOKEN_KEY] = token;
168
+ failedQueue.forEach(a => a.resolve(token));
169
+ return instance(originalRequest);
170
+ }
171
+ } catch (err) {
172
+ failedQueue.forEach(cb => cb.reject(err));
173
+ throw err;
174
+ } finally {
175
+ failedQueue = [];
176
+ isRefreshing = false;
177
+ }
178
+ }
179
+ const errorHandler = async (err: AxiosError<any>) => {
128
180
  let description = err.response?.message || 'Error'
129
181
  let _status: string | number = 0
130
182
  if (err.response) {
@@ -137,8 +189,11 @@ const errorHandler = (err: AxiosError<any>) => {
137
189
  description = (`${data?.message}`).substring(0, 90)
138
190
  break;
139
191
  case 401:
140
- description = err.response.data.result.text || '用户未登录'
141
- _options.tokenExpiration?.(err, err.response)
192
+ description = err.response.data.result.text || '用户未登录';
193
+ _options.tokenExpiration?.()
194
+ if(_options.isCreateTokenRefresh){
195
+ return createTokenRefreshHandler(err)
196
+ }
142
197
  break;
143
198
  case 404:
144
199
  description = err?.response?.data?.message || `${data?.error} ${data?.path}`
@@ -158,6 +213,11 @@ const errorHandler = (err: AxiosError<any>) => {
158
213
  return Promise.reject(err)
159
214
  }
160
215
 
216
+ export const abortAllRequests = () => {
217
+ pendingRequests.forEach(controller => controller.abort())
218
+ pendingRequests.clear()
219
+ }
220
+
161
221
  export const crateAxios = (options: Options) => {
162
222
  if (options) {
163
223
  _options = Object.assign(_options, options)
package/src/websocket.ts CHANGED
@@ -1,10 +1,7 @@
1
- import { wsClient } from '@/utils/websocket';
2
1
  import { webSocket, WebSocketSubject } from 'rxjs/webSocket';
3
2
  import { Observable, Subject, timer, Subscription, EMPTY } from 'rxjs';
4
3
  import { retry, catchError } from 'rxjs/operators';
5
4
  import { notification } from 'ant-design-vue';
6
- import app from '@micro-zoe/micro-app'
7
- import { log } from 'console';
8
5
 
9
6
  interface WebSocketMessage {
10
7
  type: string;
@@ -36,7 +33,7 @@ export class WebSocketClient {
36
33
  private wsClient: WebSocketClient | undefined
37
34
 
38
35
  constructor(options?: WS_Options) {
39
- this.options = options || {};
36
+ this.setOptions(options)
40
37
  this.setupConnectionMonitor();
41
38
  if (isApp) {
42
39
  (window as any).microApp.addGlobalDataListener((data) => {
@@ -45,6 +42,10 @@ export class WebSocketClient {
45
42
  }
46
43
  }
47
44
 
45
+ public setOptions(options: WS_Options) {
46
+ this.options = options || {}
47
+ }
48
+
48
49
  public initWebSocket(url: string) {
49
50
  this.url = url;
50
51
  }