@jetlinks-web/core 2.3.0 → 2.3.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/dist/index.d.ts CHANGED
@@ -28,7 +28,7 @@ interface Options {
28
28
  * @param status 错误code
29
29
  * @param error 错误实例
30
30
  */
31
- handleError?: (msg: string, status: string | number, error: AxiosError<any>) => void;
31
+ handleError?: (msg: string, status: string | number, error: AxiosError<any>) => void | Promise<any>;
32
32
  requestOptions?: (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig | Record<string, any>;
33
33
  isCreateTokenRefresh?: boolean;
34
34
  }
@@ -256,6 +256,7 @@ interface NdJsonOptions {
256
256
  /** 基础 API 地址,默认使用 BASE_API 常量 */
257
257
  baseURL?: string;
258
258
  }
259
+ type RequestData = BodyInit | Record<string, unknown>;
259
260
  declare class NdJson {
260
261
  private options;
261
262
  private activeRequests;
@@ -280,14 +281,18 @@ declare class NdJson {
280
281
  * 刷新剩余缓冲区
281
282
  */
282
283
  private flushBuffer;
284
+ private emitLine;
283
285
  /**
284
286
  * 创建请求的 Observable
285
287
  */
286
288
  private request;
287
289
  get<T = unknown>(url: string, _data?: string, extra?: RequestInit): Observable<T>;
288
- post<T = unknown>(url: string, data?: BodyInit | Record<string, unknown>, extra?: RequestInit): Observable<T>;
290
+ post<T = unknown>(url: string, data?: RequestData, extra?: RequestInit): Observable<T>;
289
291
  private handleRequest;
290
292
  handleResponse<T>(response: T): T;
293
+ private mergeRequestInit;
294
+ private mergeHeaders;
295
+ private isAbortError;
291
296
  /**
292
297
  * 取消所有活跃的请求
293
298
  */
package/dist/index.mjs CHANGED
@@ -1,2 +1,3 @@
1
- import{TOKEN_KEY as l,BASE_API as q,LOCAL_BASE_API as E}from"@jetlinks-web/constants";import{getToken as x,randomString as O}from"@jetlinks-web/utils";import b from"axios";import{isFunction as g,isObject as k}from"lodash-es";var h=class{instance=null;options;failedQueue=[];isRefreshing=!1;pendingRequests=new Map;isApp=window.__MICRO_APP_ENVIRONMENT__;constructor(e){this.options={filter_url:[],code:200,codeKey:"status",timeout:1e3*15,handleRequest:void 0,handleResponse:void 0,handleError:void 0,langKey:"lang",requestOptions:t=>({}),tokenExpiration:()=>{},handleReconnect:()=>Promise.resolve(),isCreateTokenRefresh:!1,cancelDuplicateRequests:!1,...e},window.JetlinksCore?.instance&&(this.instance=window.JetlinksCore.instance)}initialize(e){e&&(this.options={...this.options,...e}),this.instance=b.create({withCredentials:!1,timeout:this.options.timeout,baseURL:q}),this.instance.interceptors.request.use(t=>this.handleRequest(t),t=>this.errorHandler(t)),this.instance.interceptors.response.use(t=>this.handleResponse(t),t=>this.errorHandler(t))}getInstance(){return this.instance||this.initialize(),this.instance}generateRequestKey(e){let t=e.method?.toUpperCase()||"GET",s=e.url||"",n=e.params||{},i=e.data||{},r=t==="GET"?n:i,a="";try{a=JSON.stringify(r,Object.keys(r).sort())}catch{a=O(16)}return`${t}:${s}:${a}`}requestRecords(e){if(!this.options.cancelDuplicateRequests)return;let t=this.generateRequestKey(e);this.pendingRequests.has(t)&&(this.pendingRequests.get(t)?.abort(),this.pendingRequests.delete(t));let s=new AbortController;e.signal=s.signal,e.__requestKey=t,this.pendingRequests.set(t,s)}handleRequest(e){this.requestRecords(e);let t=x(),s=localStorage.getItem(this.options.langKey),n=localStorage.getItem(E);if(s&&(e.headers[this.options.langKey]=s),n&&!e.baseURL){let i=e.url.startsWith("/")?e.url:`/${e.url}`;e.url=n+i}if(!t&&!this.options.filter_url?.some(i=>e.url?.includes(i)))return this.options.tokenExpiration?.(),e;if(e.headers[l]||(e.headers[l]=t),this.options.requestOptions&&g(this.options.requestOptions)){let i=this.options.requestOptions(e);if(i&&k(i))for(let r in i)e[r]=i[r]}return e}handleResponse(e){let t=e.config?.__requestKey;if(t&&this.pendingRequests.delete(t),this.options.handleResponse&&g(this.options.handleResponse))return this.options.handleResponse(e);if(e.data instanceof ArrayBuffer)return e;let s=e.data[this.options.codeKey||"status"];return typeof e.data=="object"&&typeof e.data.success>"u"&&(e.data.success=s===this.options.code),e.data}async createTokenRefreshHandler(e){let t=e.config;if(this.isRefreshing)return new Promise((s,n)=>{this.failedQueue.push({resolve:s,reject:n})}).then(s=>t.signal?.aborted?Promise.reject(new b.Cancel("Request aborted")):(t.headers[l]=s,this.instance(t))).catch(s=>Promise.reject(s));t._retry=!0,this.isRefreshing=!0;try{if(await this.options.handleReconnect?.()){let n=x();return t.headers[l]=n,this.failedQueue.forEach(i=>i.resolve(n)),this.instance(t)}}catch(s){throw this.failedQueue.forEach(n=>n.reject(s)),s}finally{this.failedQueue=[],this.isRefreshing=!1}}async errorHandler(e){let t=e.config?.__requestKey;if(t&&this.pendingRequests.delete(t),b.isCancel(e))return Promise.reject(e);let s=e.response?.message||"Error",n=0,i=e.response;if(i){let{data:r,status:a}=i;switch(n=a,a){case 400:case 403:case 500:s=`${r?.message}`.substring(0,90);break;case 401:if(s=r?.result?.text||"\u7528\u6237\u672A\u767B\u5F55",this.options.tokenExpiration?.(e),this.options.isCreateTokenRefresh)return this.createTokenRefreshHandler(e);break;case 404:s=r?.message||`${r?.error} ${r?.path}`;break;default:break}}else{let r=e;r.message&&(s=r.message.includes("timeout")?"\u63A5\u53E3\u54CD\u5E94\u8D85\u65F6":r.message,n="timeout")}return this.options.handleError&&g(this.options.handleError)&&this.options.handleError(s,n,e),Promise.reject(e)}abortAllRequests(){this.pendingRequests.forEach(e=>e.abort()),this.pendingRequests.clear()}abortRequest(e){let t=this.pendingRequests.get(e);t&&(t.abort(),this.pendingRequests.delete(e))}getPendingRequestsCount(){return this.pendingRequests.size}post(e,t={},s){return this.getInstance()({method:"POST",url:e,data:t,...s})}get(e,t=void 0,s){return this.getInstance()({method:"GET",url:e,params:t,...s})}put(e,t={},s){return this.getInstance()({method:"PUT",url:e,data:t,...s})}patch(e,t={},s){return this.getInstance()({method:"PATCH",url:e,data:t,...s})}remove(e,t=void 0,s){return this.getInstance()({method:"DELETE",url:e,params:t,...s})}getStream(e,t,s){return this.get(e,t,{responseType:"arraybuffer",...s})}postStream(e,t,s){return this.post(e,t,{responseType:"arraybuffer",...s})}},o=new h,Y=p=>{let e=new h(p);return e.initialize(),e},w=class{constructor(e,t){this.basePath=e;this.basePath=e.startsWith("/")?e:`/${e}`,this._instance=t}_instance;get instance(){return this._instance||o.getInstance()}requestWrapper(e,t,s={},n={}){let{url:i=e,method:r=t,...a}=n;return this[r].call(this,i,s,a)}page(e={},t={url:void 0,method:void 0}){return this.requestWrapper("/_query","post",e,t)}noPage(e={},t={url:void 0,method:void 0}){return this.requestWrapper("/_query/no-paging","post",{paging:!1,...e},t)}detail(e,t,s={url:void 0,method:void 0}){return this.requestWrapper(`/${e}/detail`,"get",t,s)}save(e={},t={url:void 0,method:void 0}){return this.requestWrapper("","post",e,t)}update(e={},t={url:void 0,method:void 0}){return this.requestWrapper("","patch",e,t)}delete(e,t,s={url:void 0,method:void 0}){return this.requestWrapper(`/${e}`,"remove",t,s)}batch(e={},t,s){let n=`/_batch${t?"/"+t:""}`;return this.requestWrapper(n,"post",e,s)}post(e,t,s){return this.instance({method:"POST",url:`${this.basePath}${e}`,data:t,...s})}get(e,t,s){return this.instance({method:"GET",url:`${this.basePath}${e}`,params:t,...s})}put(e,t,s){return this.instance({method:"PUT",url:`${this.basePath}${e}`,data:t,...s})}patch(e,t,s){return this.instance({method:"PATCH",url:`${this.basePath}${e}`,data:t,...s})}remove(e,t,s){return this.instance({method:"DELETE",url:`${this.basePath}${e}`,params:t,...s})}getStream(e,t,s){return this.get(`${e}`,t,{responseType:"arraybuffer",...s})}postStream(e,t,s){return this.post(`${e}`,t,{responseType:"arraybuffer",...s})}},F={post:o.post.bind(o),get:o.get.bind(o),put:o.put.bind(o),patch:o.patch.bind(o),remove:o.remove.bind(o),getStream:o.getStream.bind(o),postStream:o.postStream.bind(o)},V=o.post.bind(o),X=o.get.bind(o),Z=o.put.bind(o),ee=o.patch.bind(o),te=o.remove.bind(o),se=o.getStream.bind(o),ne=o.postStream.bind(o),ie=()=>o.abortAllRequests(),re=()=>o.getInstance(),C,oe=p=>{o.initialize(p),C=o.getInstance()};import{getToken as P}from"@jetlinks-web/utils";import{BASE_API as _,TOKEN_KEY as W}from"@jetlinks-web/constants";import{isFunction as T,isObject as S}from"lodash-es";import{Observable as I}from"rxjs";var d=class{options={code:200,codeKey:"status"};activeRequests=new Set;constructor(e){e&&(this.options={...this.options,...e})}create(e){this.options={...this.options,...e}}getUrl(e){return(this.options.baseURL??_)+e}processStream(e,t,s){let n=new TextDecoder,i="",r=()=>{if(!s.isActive){e.cancel(),t.complete();return}e.read().then(({done:a,value:u})=>{if(a){this.flushBuffer(i,t),t.complete();return}i+=n.decode(u,{stream:!0}),i=this.parseLines(i,t),r()}).catch(a=>{a.name!=="AbortError"&&t.error(a)})};r()}parseLines(e,t){let s=e.split(`
2
- `);for(let n=0;n<s.length-1;n++){let i=s[n].trim();if(i.length>0)try{let r=i.startsWith("data:")?i.slice(5):i;t.next(JSON.parse(r))}catch(r){return t.error(r),""}}return s[s.length-1]}flushBuffer(e,t){let s=e.trim();if(s.length>0)try{t.next(JSON.parse(s))}catch(n){t.error(n)}}request(e,t,s,n={}){let i=this.getUrl(t);return new I(r=>{let a=new AbortController,u={controller:a,isActive:!0};this.activeRequests.add(u);let m={method:e,signal:a.signal,keepalive:!0,...n,...this.handleRequest(i,e)};return e==="POST"&&s!==void 0&&(m.body=S(s)?JSON.stringify(s):s),fetch(i,m).then(f=>{let y=f.body?.getReader();if(!y){r.error(new Error("No readable stream available"));return}u.isActive=!0,this.processStream(y,r,u)}).catch(f=>{r.error(f)}),()=>{u.isActive=!1,a.abort(),this.activeRequests.delete(u)}})}get(e,t="{}",s={}){return this.request("GET",e,void 0,s)}post(e,t={},s={}){return this.request("POST",e,t,s)}handleRequest(e,t){let s={};t==="POST"&&(s["Content-Type"]="application/x-ndjson");let n={headers:s},i=P();if(!i&&this.options.filter_url?.some(r=>e.includes(r)))return this.options.tokenExpiration?.(),n;if(i&&(s[W]=i),this.options.requestOptions&&T(this.options.requestOptions)){let r=this.options.requestOptions(n);r&&S(r)&&Object.assign(n,r)}return n}handleResponse(e){return this.options.handleResponse&&T(this.options.handleResponse)?this.options.handleResponse(e):e}cancelAll(){this.activeRequests.forEach(e=>{e.isActive=!1,e.controller.abort()}),this.activeRequests.clear()}},v=new d,de=p=>new d(p),fe=p=>{v.create(p)},be=v;import{webSocket as M}from"rxjs/webSocket";import{Observable as N,Subject as $,timer as A,EMPTY as j}from"rxjs";import{retry as H,catchError as L}from"rxjs/operators";import{notification as J}from"ant-design-vue";var c=window.__MICRO_APP_ENVIRONMENT__,R=class{ws=null;subscriptions=new Map;pendingSubscriptions=new Map;heartbeatSubscription=null;reconnectAttempts=0;maxReconnectAttempts=2;isConnected=!1;tempQueue=[];url="";options={};wsClient;constructor(e){this.setOptions(e),this.setupConnectionMonitor(),c&&window.microApp.addGlobalDataListener(t=>{this.wsClient=t.wsClient})}setOptions(e){this.options=e||{}}initWebSocket(e){this.url=e}setupConnectionMonitor(){c||(window.addEventListener("online",()=>{console.log("Network is online, attempting to reconnect..."),this.reconnect()}),window.addEventListener("offline",()=>{console.log("Network is offline, caching subscriptions..."),this.cacheSubscriptions()}),window.addEventListener("beforeunload",()=>{this.disconnect()}))}getReconnectDelay(){return this.reconnectAttempts<=10?5e3:this.reconnectAttempts<=20?15e3:6e4}setupWebSocket(){if(c&&this.wsClient){this.wsClient.setupWebSocket();return}this.ws||!this.url||(this.ws=M({url:this.url,openObserver:{next:()=>{console.log("WebSocket connected"),this.isConnected=!0,this.reconnectAttempts=0,this.startHeartbeat(),this.restoreSubscriptions(),this.processTempQueue()}},closeObserver:{next:()=>{console.log("WebSocket disconnected"),this.isConnected=!1;let e=this.getReconnectDelay();setTimeout(()=>{this.reconnectAttempts+=1,!(this.reconnectAttempts>this.maxReconnectAttempts)&&(this.cacheSubscriptions(),this.stopHeartbeat(),this.reconnect())},e)}}}),this.ws.pipe(L(e=>(console.error("WebSocket error:",e),j)),H({delay:(e,t)=>{if(this.reconnectAttempts=t,t>this.maxReconnectAttempts)throw new Error("Max reconnection attempts reached");return A(this.getReconnectDelay())}})).subscribe(e=>this.handleMessage(e),e=>console.error("WebSocket error:",e)))}startHeartbeat(){if(c&&this.wsClient){this.wsClient.startHeartbeat();return}this.stopHeartbeat(),this.heartbeatSubscription=A(0,2e3).subscribe(()=>{this.send({type:"ping"})})}stopHeartbeat(){if(c&&this.wsClient){this.wsClient.stopHeartbeat();return}this.heartbeatSubscription&&(this.heartbeatSubscription.unsubscribe(),this.heartbeatSubscription=null)}handleMessage(e){if(c&&this.wsClient){this.wsClient.handleMessage(e);return}if(e.type==="pong")return;if(e.type==="error"){this.options.onError?this.options.onError(e):J.error({key:"error",message:e.message});return}let t=this.subscriptions.get(e.requestId||"");t&&(e.type==="complete"?(t.complete(),this.subscriptions.delete(e.requestId||"")):e.type==="result"&&t.next(e))}processTempQueue(){if(c&&this.wsClient){this.wsClient.processTempQueue();return}for(;this.tempQueue.length>0;){let e=this.tempQueue.shift();e&&this.send(e)}}cacheSubscriptions(){if(c&&this.wsClient){this.wsClient.cacheSubscriptions();return}this.pendingSubscriptions=new Map(this.subscriptions),this.subscriptions.clear()}restoreSubscriptions(){if(c&&this.wsClient){this.wsClient.restoreSubscriptions();return}this.pendingSubscriptions.forEach((e,t)=>{this.subscriptions.set(t,e)}),this.pendingSubscriptions.clear()}reconnect(){if(c&&this.wsClient){this.wsClient.reconnect();return}!this.isConnected&&navigator.onLine&&(this.ws=null,this.setupWebSocket())}connect(){if(c&&this.wsClient){this.wsClient.connect();return}this.setupWebSocket()}disconnect(){if(c&&this.wsClient){this.wsClient.disconnect();return}this.ws&&(this.ws.complete(),this.ws=null),this.stopHeartbeat(),this.subscriptions.clear(),this.pendingSubscriptions.clear(),this.tempQueue=[]}send(e){if(c&&this.wsClient){this.wsClient.send(e);return}this.ws&&this.isConnected?this.ws.next(e):this.tempQueue.push(e)}getWebSocket(e,t,s={}){if(console.log("getWebSocket",this.wsClient,e),c&&this.wsClient)return this.wsClient.getWebSocket(e,t,s);let n=new $;this.subscriptions.set(e,n);let i={id:e,topic:t,parameter:s,type:"sub"};return this.send(i),new N(r=>{let a=n.subscribe(r);return()=>{a.unsubscribe(),this.send({id:e,type:"unsub"}),this.subscriptions.delete(e)}})}},Te=new R;var K,ve=p=>{K=p};var U={},qe=(p={})=>{U=p};var Q,Oe=p=>{Q=p};export{h as AxiosService,d as NdJson,w as Request,R as WebSocketClient,ie as abortAllRequests,oe as crateAxios,Y as createAxiosService,de as createNdJson,fe as createNdJsonService,X as get,re as getInstance,se as getStream,Oe as installLocales,ve as installRouter,qe as installStores,C as instance,Q as locales,be as ndJson,ee as patch,V as post,ne as postStream,Z as put,te as remove,F as request,K as router,U as stores,Te as wsClient};
1
+ import{TOKEN_KEY as d,BASE_API as A,LOCAL_BASE_API as E}from"@jetlinks-web/constants";import{getToken as w,randomString as O}from"@jetlinks-web/utils";import b from"axios";import{isFunction as R,isObject as k}from"lodash-es";var f=class{instance=null;options;failedQueue=[];isRefreshing=!1;pendingRequests=new Map;isApp=window.__MICRO_APP_ENVIRONMENT__;constructor(e){this.options={filter_url:[],code:200,codeKey:"status",timeout:1e3*15,handleRequest:void 0,handleResponse:void 0,handleError:void 0,langKey:"lang",requestOptions:t=>({}),tokenExpiration:()=>{},handleReconnect:()=>Promise.resolve(),isCreateTokenRefresh:!1,cancelDuplicateRequests:!1,...e},window.JetlinksCore?.instance&&(this.instance=window.JetlinksCore.instance)}initialize(e){e&&(this.options={...this.options,...e}),this.instance=b.create({withCredentials:!1,timeout:this.options.timeout,baseURL:A}),this.instance.interceptors.request.use(t=>this.handleRequest(t),t=>this.errorHandler(t)),this.instance.interceptors.response.use(t=>this.handleResponse(t),t=>this.errorHandler(t))}getInstance(){return this.instance||this.initialize(),this.instance}generateRequestKey(e){let t=e.method?.toUpperCase()||"GET",s=e.url||"",n=e.params||{},i=e.data||{},r=t==="GET"?n:i,p="";try{p=JSON.stringify(r,Object.keys(r).sort())}catch{p=O(16)}return`${t}:${s}:${p}`}requestRecords(e){if(!this.options.cancelDuplicateRequests)return;let t=this.generateRequestKey(e);this.pendingRequests.has(t)&&(this.pendingRequests.get(t)?.abort(),this.pendingRequests.delete(t));let s=new AbortController;e.signal=s.signal,e.__requestKey=t,this.pendingRequests.set(t,s)}handleRequest(e){this.requestRecords(e);let t=w(),s=localStorage.getItem(this.options.langKey),n=localStorage.getItem(E);if(s&&(e.headers[this.options.langKey]=s),n&&!e.baseURL){let i=e.url.startsWith("/")?e.url:`/${e.url}`;e.url=n+i}if(!t&&!this.options.filter_url?.some(i=>e.url?.includes(i)))return this.options.tokenExpiration?.(),e;if(e.headers[d]||(e.headers[d]=t),this.options.requestOptions&&R(this.options.requestOptions)){let i=this.options.requestOptions(e);if(i&&k(i))for(let r in i)e[r]=i[r]}return e}handleResponse(e){let t=e.config?.__requestKey;if(t&&this.pendingRequests.delete(t),this.options.handleResponse&&R(this.options.handleResponse))return this.options.handleResponse(e);if(e.data instanceof ArrayBuffer)return e;let s=e.data[this.options.codeKey||"status"];return typeof e.data=="object"&&typeof e.data.success>"u"&&(e.data.success=s===this.options.code),e.data}async createTokenRefreshHandler(e){let t=e.config;if(this.isRefreshing)return new Promise((s,n)=>{this.failedQueue.push({resolve:s,reject:n})}).then(s=>t.signal?.aborted?Promise.reject(new b.Cancel("Request aborted")):(t.headers[d]=s,this.instance(t))).catch(s=>Promise.reject(s));t._retry=!0,this.isRefreshing=!0;try{if(await this.options.handleReconnect?.()){let n=w();return t.headers[d]=n,this.failedQueue.forEach(i=>i.resolve(n)),this.instance(t)}}catch(s){throw this.failedQueue.forEach(n=>n.reject(s)),s}finally{this.failedQueue=[],this.isRefreshing=!1}}async errorHandler(e){let t=e.config?.__requestKey;if(t&&this.pendingRequests.delete(t),b.isCancel(e))return Promise.reject(e);let s=e.response?.message||"Error",n=0,i=e.response;if(i){let{data:r,status:p}=i;switch(n=p,p){case 400:case 403:case 500:s=`${r?.message}`.substring(0,90);break;case 401:if(s=r?.result?.text||"\u7528\u6237\u672A\u767B\u5F55",this.options.tokenExpiration?.(e),this.options.isCreateTokenRefresh)return this.createTokenRefreshHandler(e);break;case 404:s=r?.message||`${r?.error} ${r?.path}`;break;default:break}}else{let r=e;r.message&&(s=r.message.includes("timeout")?"\u63A5\u53E3\u54CD\u5E94\u8D85\u65F6":r.message,n="timeout")}if(this.options.handleError&&R(this.options.handleError)){let r=this.options.handleError(s,n,e);if(r&&typeof r.then=="function")return r}return Promise.reject(e)}abortAllRequests(){this.pendingRequests.forEach(e=>e.abort()),this.pendingRequests.clear()}abortRequest(e){let t=this.pendingRequests.get(e);t&&(t.abort(),this.pendingRequests.delete(e))}getPendingRequestsCount(){return this.pendingRequests.size}post(e,t={},s){return this.getInstance()({method:"POST",url:e,data:t,...s})}get(e,t=void 0,s){return this.getInstance()({method:"GET",url:e,params:t,...s})}put(e,t={},s){return this.getInstance()({method:"PUT",url:e,data:t,...s})}patch(e,t={},s){return this.getInstance()({method:"PATCH",url:e,data:t,...s})}remove(e,t=void 0,s){return this.getInstance()({method:"DELETE",url:e,params:t,...s})}getStream(e,t,s){return this.get(e,t,{responseType:"arraybuffer",...s})}postStream(e,t,s){return this.post(e,t,{responseType:"arraybuffer",...s})}},o=new f,V=a=>{let e=new f(a);return e.initialize(),e},x=class{constructor(e,t){this.basePath=e;this.basePath=e.startsWith("/")?e:`/${e}`,this._instance=t}_instance;get instance(){return this._instance||o.getInstance()}requestWrapper(e,t,s={},n={}){let{url:i=e,method:r=t,...p}=n;return this[r].call(this,i,s,p)}page(e={},t={url:void 0,method:void 0}){return this.requestWrapper("/_query","post",e,t)}noPage(e={},t={url:void 0,method:void 0}){return this.requestWrapper("/_query/no-paging","post",{paging:!1,...e},t)}detail(e,t,s={url:void 0,method:void 0}){return this.requestWrapper(`/${e}/detail`,"get",t,s)}save(e={},t={url:void 0,method:void 0}){return this.requestWrapper("","post",e,t)}update(e={},t={url:void 0,method:void 0}){return this.requestWrapper("","patch",e,t)}delete(e,t,s={url:void 0,method:void 0}){return this.requestWrapper(`/${e}`,"remove",t,s)}batch(e={},t,s){let n=`/_batch${t?"/"+t:""}`;return this.requestWrapper(n,"post",e,s)}post(e,t,s){return this.instance({method:"POST",url:`${this.basePath}${e}`,data:t,...s})}get(e,t,s){return this.instance({method:"GET",url:`${this.basePath}${e}`,params:t,...s})}put(e,t,s){return this.instance({method:"PUT",url:`${this.basePath}${e}`,data:t,...s})}patch(e,t,s){return this.instance({method:"PATCH",url:`${this.basePath}${e}`,data:t,...s})}remove(e,t,s){return this.instance({method:"DELETE",url:`${this.basePath}${e}`,params:t,...s})}getStream(e,t,s){return this.get(`${e}`,t,{responseType:"arraybuffer",...s})}postStream(e,t,s){return this.post(`${e}`,t,{responseType:"arraybuffer",...s})}},X={post:o.post.bind(o),get:o.get.bind(o),put:o.put.bind(o),patch:o.patch.bind(o),remove:o.remove.bind(o),getStream:o.getStream.bind(o),postStream:o.postStream.bind(o)},Z=o.post.bind(o),ee=o.get.bind(o),te=o.put.bind(o),se=o.patch.bind(o),ne=o.remove.bind(o),ie=o.getStream.bind(o),re=o.postStream.bind(o),oe=()=>o.abortAllRequests(),ae=()=>o.getInstance(),P,pe=a=>{o.initialize(a),P=o.getInstance()};import{getToken as C}from"@jetlinks-web/utils";import{BASE_API as _,TOKEN_KEY as I}from"@jetlinks-web/constants";import{Observable as W}from"rxjs";var M="application/x-ndjson",S=a=>typeof a=="object"&&a!==null,N=a=>{if(!S(a))return!1;let e=Object.getPrototypeOf(a);return e===Object.prototype||e===null},T=a=>typeof a=="function",g=class{options={code:200,codeKey:"status"};activeRequests=new Set;constructor(e){e&&(this.options={...this.options,...e})}create(e){this.options={...this.options,...e}}getUrl(e){return(this.options.baseURL??_)+e}processStream(e,t,s){let n=new TextDecoder,i="";(async()=>{try{for(;s.isActive;){let{done:p,value:u}=await e.read();if(p){let h=n.decode();h&&(i+=h),this.flushBuffer(i,t),t.closed||t.complete();return}if(i+=n.decode(u,{stream:!0}),i=this.parseLines(i,t),t.closed)return}}catch(p){!this.isAbortError(p)&&!t.closed&&t.error(p)}})()}parseLines(e,t){let s=0,n=e.indexOf(`
2
+ `);for(;n!==-1;){let i=e.slice(s,n).trim();if(i.length>0&&!this.emitLine(i,t))return"";s=n+1,n=e.indexOf(`
3
+ `,s)}return e.slice(s)}flushBuffer(e,t){let s=e.trim();s.length>0&&this.emitLine(s,t)}emitLine(e,t){let s=e.startsWith("data:")?e.slice(5).trimStart():e;try{return t.next(this.handleResponse(JSON.parse(s))),!0}catch(n){return t.error(n),!1}}request(e,t,s,n={}){let i=this.getUrl(t);return new W(r=>{let p=new AbortController,u={controller:p,isActive:!0};this.activeRequests.add(u);let h=this.mergeRequestInit({method:e,signal:p.signal,keepalive:!0},this.handleRequest(i,e),n,{method:e,signal:p.signal});return e==="POST"&&s!==void 0&&(h.body=N(s)?JSON.stringify(s):s),fetch(i,h).then(l=>{if(l.status!==this.options.code){this.isAbortError(l)||r.error(l);return}let y=l.body?.getReader();if(!y){r.error(new Error("No readable stream available"));return}u.isActive=!0,this.processStream(y,r,u)}).catch(l=>{this.isAbortError(l)||r.error(l)}),()=>{u.isActive=!1,p.abort(),this.activeRequests.delete(u)}})}get(e,t="{}",s={}){return this.request("GET",e,void 0,s)}post(e,t={},s={}){return this.request("POST",e,t,s)}handleRequest(e,t){let s={};t==="POST"&&(s["Content-Type"]=M);let n={headers:s},i=C();if(!i&&!this.options.filter_url?.some(r=>e.includes(r)))return this.options.tokenExpiration?.(),n;if(i&&(s[I]=i),T(this.options.requestOptions)){let r=this.options.requestOptions(n);if(S(r))return this.mergeRequestInit(n,r)}return n}handleResponse(e){return T(this.options.handleResponse)?this.options.handleResponse(e):e}mergeRequestInit(...e){let t={},s=new Headers,n=!1;return e.forEach(i=>{if(!i)return;let{headers:r,...p}=i;Object.assign(t,p),n=this.mergeHeaders(s,r)||n}),n&&(t.headers=s),t}mergeHeaders(e,t){if(!t)return!1;let s=!1;return new Headers(t).forEach((n,i)=>{e.set(i,n),s=!0}),s}isAbortError(e){return e instanceof Error&&e.name==="AbortError"}cancelAll(){this.activeRequests.forEach(e=>{e.isActive=!1,e.controller.abort()}),this.activeRequests.clear()}},q=new g,fe=a=>new g(a),ge=a=>{q.create(a)},be=q;import{webSocket as H}from"rxjs/webSocket";import{Observable as j,Subject as $,timer as v,EMPTY as L}from"rxjs";import{retry as J,catchError as D}from"rxjs/operators";import{notification as K}from"ant-design-vue";var c=window.__MICRO_APP_ENVIRONMENT__,m=class{ws=null;subscriptions=new Map;pendingSubscriptions=new Map;heartbeatSubscription=null;reconnectAttempts=0;maxReconnectAttempts=2;isConnected=!1;tempQueue=[];url="";options={};wsClient;constructor(e){this.setOptions(e),this.setupConnectionMonitor(),c&&window.microApp.addGlobalDataListener(t=>{this.wsClient=t.wsClient})}setOptions(e){this.options=e||{}}initWebSocket(e){this.url=e}setupConnectionMonitor(){c||(window.addEventListener("online",()=>{console.log("Network is online, attempting to reconnect..."),this.reconnect()}),window.addEventListener("offline",()=>{console.log("Network is offline, caching subscriptions..."),this.cacheSubscriptions()}),window.addEventListener("beforeunload",()=>{this.disconnect()}))}getReconnectDelay(){return this.reconnectAttempts<=10?5e3:this.reconnectAttempts<=20?15e3:6e4}setupWebSocket(){if(c&&this.wsClient){this.wsClient.setupWebSocket();return}this.ws||!this.url||(this.ws=H({url:this.url,openObserver:{next:()=>{console.log("WebSocket connected"),this.isConnected=!0,this.reconnectAttempts=0,this.startHeartbeat(),this.restoreSubscriptions(),this.processTempQueue()}},closeObserver:{next:()=>{console.log("WebSocket disconnected"),this.isConnected=!1;let e=this.getReconnectDelay();setTimeout(()=>{this.reconnectAttempts+=1,!(this.reconnectAttempts>this.maxReconnectAttempts)&&(this.cacheSubscriptions(),this.stopHeartbeat(),this.reconnect())},e)}}}),this.ws.pipe(D(e=>(console.error("WebSocket error:",e),L)),J({delay:(e,t)=>{if(this.reconnectAttempts=t,t>this.maxReconnectAttempts)throw new Error("Max reconnection attempts reached");return v(this.getReconnectDelay())}})).subscribe(e=>this.handleMessage(e),e=>console.error("WebSocket error:",e)))}startHeartbeat(){if(c&&this.wsClient){this.wsClient.startHeartbeat();return}this.stopHeartbeat(),this.heartbeatSubscription=v(0,2e3).subscribe(()=>{this.send({type:"ping"})})}stopHeartbeat(){if(c&&this.wsClient){this.wsClient.stopHeartbeat();return}this.heartbeatSubscription&&(this.heartbeatSubscription.unsubscribe(),this.heartbeatSubscription=null)}handleMessage(e){if(c&&this.wsClient){this.wsClient.handleMessage(e);return}if(e.type==="pong")return;if(e.type==="error"){this.options.onError?this.options.onError(e):K.error({key:"error",message:e.message});return}let t=this.subscriptions.get(e.requestId||"");t&&(e.type==="complete"?(t.complete(),this.subscriptions.delete(e.requestId||"")):e.type==="result"&&t.next(e))}processTempQueue(){if(c&&this.wsClient){this.wsClient.processTempQueue();return}for(;this.tempQueue.length>0;){let e=this.tempQueue.shift();e&&this.send(e)}}cacheSubscriptions(){if(c&&this.wsClient){this.wsClient.cacheSubscriptions();return}this.pendingSubscriptions=new Map(this.subscriptions),this.subscriptions.clear()}restoreSubscriptions(){if(c&&this.wsClient){this.wsClient.restoreSubscriptions();return}this.pendingSubscriptions.forEach((e,t)=>{this.subscriptions.set(t,e)}),this.pendingSubscriptions.clear()}reconnect(){if(c&&this.wsClient){this.wsClient.reconnect();return}!this.isConnected&&navigator.onLine&&(this.ws=null,this.setupWebSocket())}connect(){if(c&&this.wsClient){this.wsClient.connect();return}this.setupWebSocket()}disconnect(){if(c&&this.wsClient){this.wsClient.disconnect();return}this.ws&&(this.ws.complete(),this.ws=null),this.stopHeartbeat(),this.subscriptions.clear(),this.pendingSubscriptions.clear(),this.tempQueue=[]}send(e){if(c&&this.wsClient){this.wsClient.send(e);return}this.ws&&this.isConnected?this.ws.next(e):this.tempQueue.push(e)}getWebSocket(e,t,s={}){if(console.log("getWebSocket",this.wsClient,e),c&&this.wsClient)return this.wsClient.getWebSocket(e,t,s);let n=new $;this.subscriptions.set(e,n);let i={id:e,topic:t,parameter:s,type:"sub"};return this.send(i),new j(r=>{let p=n.subscribe(r);return()=>{p.unsubscribe(),this.send({id:e,type:"unsub"}),this.subscriptions.delete(e)}})}},Se=new m;var U,ve=a=>{U=a};var Q={},Ee=(a={})=>{Q=a};var B,ke=a=>{B=a};export{f as AxiosService,g as NdJson,x as Request,m as WebSocketClient,oe as abortAllRequests,pe as crateAxios,V as createAxiosService,fe as createNdJson,ge as createNdJsonService,ee as get,ae as getInstance,ie as getStream,ke as installLocales,ve as installRouter,Ee as installStores,P as instance,B as locales,be as ndJson,se as patch,Z as post,re as postStream,te as put,ne as remove,X as request,U as router,Q as stores,Se as wsClient};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jetlinks-web/core",
3
- "version": "2.3.0",
3
+ "version": "2.3.2",
4
4
  "main": "dist/index.mjs",
5
5
  "module": "dist/index.mjs",
6
6
  "types": "dist/index.d.ts",
@@ -29,8 +29,8 @@
29
29
  "axios": "^1.7.4",
30
30
  "rxjs": "^7.8.1",
31
31
  "@jetlinks-web/constants": "^1.0.9",
32
- "@jetlinks-web/utils": "^1.2.12",
33
- "@jetlinks-web/types": "^1.0.2"
32
+ "@jetlinks-web/types": "^1.0.2",
33
+ "@jetlinks-web/utils": "^1.3.0"
34
34
  },
35
35
  "publishConfig": {
36
36
  "registry": "https://registry.npmjs.org/",
package/src/axios.ts CHANGED
@@ -309,7 +309,11 @@ export class AxiosService {
309
309
  }
310
310
 
311
311
  if (this.options.handleError && isFunction(this.options.handleError)) {
312
- this.options.handleError(description, _status, err as any)
312
+ const result = this.options.handleError(description, _status, err as any)
313
+ // 如果 handleError 返回了 Promise,则返回它以替换原始错误
314
+ if (result && typeof result.then === 'function') {
315
+ return result
316
+ }
313
317
  }
314
318
 
315
319
  return Promise.reject(err)
package/src/fetch.ts CHANGED
@@ -1,271 +1,351 @@
1
- import { getToken } from "@jetlinks-web/utils";
2
- import { BASE_API, TOKEN_KEY } from "@jetlinks-web/constants";
3
- import { isFunction, isObject } from "lodash-es";
4
- import { Observable, Subscriber } from 'rxjs';
5
-
6
- /**
7
- * NdJson 配置选项
8
- */
9
- export interface NdJsonOptions {
10
- /** 成功状态码 */
11
- code?: number;
12
- /** 状态码字段名 */
13
- codeKey?: string;
14
- /** 不需要 token 的 URL 列表 */
15
- filter_url?: string[];
16
- /** token 过期回调 */
17
- tokenExpiration?: () => void;
18
- /** 自定义请求配置 */
19
- requestOptions?: (config: RequestInit) => Record<string, unknown>;
20
- /** 自定义响应处理 */
21
- handleResponse?: <T>(response: T) => T;
22
- /** 基础 API 地址,默认使用 BASE_API 常量 */
23
- baseURL?: string;
24
- }
25
-
26
- interface RequestContext {
27
- controller: AbortController;
28
- isActive: boolean;
29
- }
30
-
31
- type HttpMethod = 'GET' | 'POST';
32
-
33
- export class NdJson {
34
- private options: NdJsonOptions = {
35
- code: 200,
36
- codeKey: 'status'
37
- };
38
-
39
- private activeRequests = new Set<RequestContext>();
40
-
41
- constructor(options?: NdJsonOptions) {
42
- if (options) {
43
- this.options = { ...this.options, ...options };
44
- }
45
- }
46
-
47
- /**
48
- * 初始化/更新配置
49
- */
50
- create(options: NdJsonOptions): void {
51
- this.options = { ...this.options, ...options };
52
- }
53
-
54
- /**
55
- * 获取完整 URL
56
- */
57
- private getUrl(url: string): string {
58
- const baseURL = this.options.baseURL ?? BASE_API;
59
- return baseURL + url;
60
- }
61
-
62
- /**
63
- * 处理 NDJSON 流的核心逻辑
64
- */
65
- private processStream<T>(
66
- reader: ReadableStreamDefaultReader<Uint8Array>,
67
- observer: Subscriber<T>,
68
- context: RequestContext
69
- ): void {
70
- const decoder = new TextDecoder();
71
- let buffer = '';
72
-
73
- const read = (): void => {
74
- if (!context.isActive) {
75
- reader.cancel();
76
- observer.complete();
77
- return;
78
- }
79
-
80
- reader.read()
81
- .then(({ done, value }) => {
82
- if (done) {
83
- this.flushBuffer(buffer, observer);
84
- observer.complete();
85
- return;
86
- }
87
-
88
- buffer += decoder.decode(value, { stream: true });
89
- buffer = this.parseLines(buffer, observer);
90
- read();
91
- })
92
- .catch(err => {
93
- if (err.name !== 'AbortError') {
94
- observer.error(err);
95
- }
96
- });
97
- };
98
-
99
- read();
100
- }
101
-
102
- /**
103
- * 解析缓冲区中的完整行
104
- */
105
- private parseLines<T>(buffer: string, observer: Subscriber<T>): string {
106
- const lines = buffer.split('\n');
107
-
108
- for (let i = 0; i < lines.length - 1; i++) {
109
- const line = lines[i].trim();
110
- if (line.length > 0) {
111
- try {
112
- const data = line.startsWith('data:') ? line.slice(5) : line;
113
- observer.next(JSON.parse(data));
114
- } catch (e) {
115
- observer.error(e);
116
- return '';
117
- }
118
- }
119
- }
120
-
121
- return lines[lines.length - 1];
122
- }
123
-
124
- /**
125
- * 刷新剩余缓冲区
126
- */
127
- private flushBuffer<T>(buffer: string, observer: Subscriber<T>): void {
128
- const trimmed = buffer.trim();
129
- if (trimmed.length > 0) {
130
- try {
131
- observer.next(JSON.parse(trimmed));
132
- } catch (e) {
133
- observer.error(e);
134
- }
135
- }
136
- }
137
-
138
- /**
139
- * 创建请求的 Observable
140
- */
141
- private request<T>(
142
- method: HttpMethod,
143
- url: string,
144
- data?: BodyInit | Record<string, unknown>,
145
- extra: RequestInit = {}
146
- ): Observable<T> {
147
- const fullUrl = this.getUrl(url);
148
-
149
- return new Observable<T>(observer => {
150
- const controller = new AbortController();
151
- const context: RequestContext = {
152
- controller,
153
- isActive: true
154
- };
155
-
156
- this.activeRequests.add(context);
157
-
158
- const requestInit: RequestInit = {
159
- method,
160
- signal: controller.signal,
161
- keepalive: true,
162
- ...extra,
163
- ...this.handleRequest(fullUrl, method)
164
- };
165
-
166
- // POST 请求添加 body
167
- if (method === 'POST' && data !== undefined) {
168
- requestInit.body = isObject(data) ? JSON.stringify(data) : data as BodyInit;
169
- }
170
-
171
- fetch(fullUrl, requestInit)
172
- .then(resp => {
173
- const reader = resp.body?.getReader();
174
-
175
- if (!reader) {
176
- observer.error(new Error('No readable stream available'));
177
- return;
178
- }
179
-
180
- context.isActive = true;
181
- this.processStream(reader, observer, context);
182
- })
183
- .catch(e => {
184
- observer.error(e);
185
- });
186
-
187
- // 返回清理函数
188
- return () => {
189
- context.isActive = false;
190
- controller.abort();
191
- this.activeRequests.delete(context);
192
- };
193
- });
194
- }
195
-
196
- get<T = unknown>(url: string, _data = '{}', extra: RequestInit = {}): Observable<T> {
197
- return this.request<T>('GET', url, undefined, extra);
198
- }
199
-
200
- post<T = unknown>(url: string, data: BodyInit | Record<string, unknown> = {}, extra: RequestInit = {}): Observable<T> {
201
- return this.request<T>('POST', url, data, extra);
202
- }
203
-
204
- private handleRequest(url: string, method: HttpMethod): RequestInit {
205
- const headers: Record<string, string> = {};
206
-
207
- // 只有 POST 请求才设置 Content-Type
208
- if (method === 'POST') {
209
- headers['Content-Type'] = 'application/x-ndjson';
210
- }
211
-
212
- const config: RequestInit = { headers };
213
- const token = getToken();
214
-
215
- if (!token && this.options.filter_url?.some(_url => url.includes(_url))) {
216
- this.options.tokenExpiration?.();
217
- return config;
218
- }
219
-
220
- if (token) {
221
- headers[TOKEN_KEY] = token;
222
- }
223
-
224
- if (this.options.requestOptions && isFunction(this.options.requestOptions)) {
225
- const extraOptions = this.options.requestOptions(config);
226
- if (extraOptions && isObject(extraOptions)) {
227
- Object.assign(config, extraOptions);
228
- }
229
- }
230
-
231
- return config;
232
- }
233
-
234
- handleResponse<T>(response: T): T {
235
- if (this.options.handleResponse && isFunction(this.options.handleResponse)) {
236
- return this.options.handleResponse(response);
237
- }
238
- return response;
239
- }
240
-
241
- /**
242
- * 取消所有活跃的请求
243
- */
244
- cancelAll(): void {
245
- this.activeRequests.forEach(context => {
246
- context.isActive = false;
247
- context.controller.abort();
248
- });
249
- this.activeRequests.clear();
250
- }
251
- }
252
-
253
- // 默认实例
254
- const defaultNdJson = new NdJson();
255
-
256
- /**
257
- * 创建新的 NdJson 实例
258
- */
259
- export const createNdJson = (options?: NdJsonOptions): NdJson => {
260
- return new NdJson(options);
261
- };
262
-
263
- /**
264
- * 初始化默认实例
265
- */
266
- export const createNdJsonService = (options: NdJsonOptions): void => {
267
- defaultNdJson.create(options);
268
- };
269
-
270
- // 导出默认实例 (保持向后兼容)
271
- export const ndJson = defaultNdJson;
1
+ import { getToken } from "@jetlinks-web/utils";
2
+ import { BASE_API, TOKEN_KEY } from "@jetlinks-web/constants";
3
+ import { Observable, Subscriber } from "rxjs";
4
+
5
+ /**
6
+ * NdJson 配置选项
7
+ */
8
+ export interface NdJsonOptions {
9
+ /** 成功状态码 */
10
+ code?: number;
11
+ /** 状态码字段名 */
12
+ codeKey?: string;
13
+ /** 不需要 token 的 URL 列表 */
14
+ filter_url?: string[];
15
+ /** token 过期回调 */
16
+ tokenExpiration?: () => void;
17
+ /** 自定义请求配置 */
18
+ requestOptions?: (config: RequestInit) => Record<string, unknown>;
19
+ /** 自定义响应处理 */
20
+ handleResponse?: <T>(response: T) => T;
21
+ /** 基础 API 地址,默认使用 BASE_API 常量 */
22
+ baseURL?: string;
23
+ }
24
+
25
+ interface RequestContext {
26
+ controller: AbortController;
27
+ isActive: boolean;
28
+ }
29
+
30
+ type HttpMethod = "GET" | "POST";
31
+ type RequestData = BodyInit | Record<string, unknown>;
32
+
33
+ const NDJSON_CONTENT_TYPE = "application/x-ndjson";
34
+
35
+ const isObjectLike = (value: unknown): value is Record<string, unknown> =>
36
+ typeof value === "object" && value !== null;
37
+
38
+ const isPlainObject = (value: unknown): value is Record<string, unknown> => {
39
+ if (!isObjectLike(value)) {
40
+ return false;
41
+ }
42
+
43
+ const proto = Object.getPrototypeOf(value);
44
+ return proto === Object.prototype || proto === null;
45
+ };
46
+
47
+ const isFunction = (value: unknown): value is (...args: unknown[]) => unknown =>
48
+ typeof value === "function";
49
+
50
+ export class NdJson {
51
+ private options: NdJsonOptions = {
52
+ code: 200,
53
+ codeKey: "status"
54
+ };
55
+
56
+ private activeRequests = new Set<RequestContext>();
57
+
58
+ constructor(options?: NdJsonOptions) {
59
+ if (options) {
60
+ this.options = { ...this.options, ...options };
61
+ }
62
+ }
63
+
64
+ /**
65
+ * 初始化/更新配置
66
+ */
67
+ create(options: NdJsonOptions): void {
68
+ this.options = { ...this.options, ...options };
69
+ }
70
+
71
+ /**
72
+ * 获取完整 URL
73
+ */
74
+ private getUrl(url: string): string {
75
+ const baseURL = this.options.baseURL ?? BASE_API;
76
+ return baseURL + url;
77
+ }
78
+
79
+ /**
80
+ * 处理 NDJSON 流的核心逻辑
81
+ */
82
+ private processStream<T>(
83
+ reader: ReadableStreamDefaultReader<Uint8Array>,
84
+ observer: Subscriber<T>,
85
+ context: RequestContext
86
+ ): void {
87
+ const decoder = new TextDecoder();
88
+ let buffer = "";
89
+
90
+ const read = async (): Promise<void> => {
91
+ try {
92
+ while (context.isActive) {
93
+ const { done, value } = await reader.read();
94
+ if (done) {
95
+ const finalText = decoder.decode();
96
+ if (finalText) {
97
+ buffer += finalText;
98
+ }
99
+ this.flushBuffer(buffer, observer);
100
+ if (!observer.closed) {
101
+ observer.complete();
102
+ }
103
+ return;
104
+ }
105
+
106
+ buffer += decoder.decode(value, { stream: true });
107
+ buffer = this.parseLines(buffer, observer);
108
+ if (observer.closed) {
109
+ return;
110
+ }
111
+ }
112
+ } catch (error) {
113
+ if (!this.isAbortError(error) && !observer.closed) {
114
+ observer.error(error);
115
+ }
116
+ }
117
+ };
118
+
119
+ void read();
120
+ }
121
+
122
+ /**
123
+ * 解析缓冲区中的完整行
124
+ */
125
+ private parseLines<T>(buffer: string, observer: Subscriber<T>): string {
126
+ let start = 0;
127
+ let lineEnd = buffer.indexOf("\n");
128
+
129
+ while (lineEnd !== -1) {
130
+ const line = buffer.slice(start, lineEnd).trim();
131
+ if (line.length > 0 && !this.emitLine(line, observer)) {
132
+ return "";
133
+ }
134
+
135
+ start = lineEnd + 1;
136
+ lineEnd = buffer.indexOf("\n", start);
137
+ }
138
+
139
+ return buffer.slice(start);
140
+ }
141
+
142
+ /**
143
+ * 刷新剩余缓冲区
144
+ */
145
+ private flushBuffer<T>(buffer: string, observer: Subscriber<T>): void {
146
+ const trimmed = buffer.trim();
147
+ if (trimmed.length > 0) {
148
+ this.emitLine(trimmed, observer);
149
+ }
150
+ }
151
+
152
+ private emitLine<T>(line: string, observer: Subscriber<T>): boolean {
153
+ const data = line.startsWith("data:") ? line.slice(5).trimStart() : line;
154
+ try {
155
+ observer.next(this.handleResponse(JSON.parse(data)));
156
+ return true;
157
+ } catch (error) {
158
+ observer.error(error);
159
+ return false;
160
+ }
161
+ }
162
+
163
+ /**
164
+ * 创建请求的 Observable
165
+ */
166
+ private request<T>(
167
+ method: HttpMethod,
168
+ url: string,
169
+ data?: RequestData,
170
+ extra: RequestInit = {}
171
+ ): Observable<T> {
172
+ const fullUrl = this.getUrl(url);
173
+
174
+ return new Observable<T>(observer => {
175
+ const controller = new AbortController();
176
+ const context: RequestContext = {
177
+ controller,
178
+ isActive: true
179
+ };
180
+
181
+ this.activeRequests.add(context);
182
+
183
+ const requestInit = this.mergeRequestInit(
184
+ {
185
+ method,
186
+ signal: controller.signal,
187
+ keepalive: true
188
+ },
189
+ this.handleRequest(fullUrl, method),
190
+ extra,
191
+ {
192
+ method,
193
+ signal: controller.signal
194
+ }
195
+ );
196
+
197
+ // POST 请求添加 body
198
+ if (method === "POST" && data !== undefined) {
199
+ requestInit.body = isPlainObject(data) ? JSON.stringify(data) : (data as BodyInit);
200
+ }
201
+
202
+ fetch(fullUrl, requestInit)
203
+ .then(resp => {
204
+ if (resp.status !== this.options.code) {
205
+ if (!this.isAbortError(resp)) {
206
+ observer.error(resp);
207
+ }
208
+ return
209
+ }
210
+
211
+ const reader = resp.body?.getReader();
212
+
213
+ if (!reader) {
214
+ observer.error(new Error("No readable stream available"));
215
+ return;
216
+ }
217
+
218
+ context.isActive = true;
219
+ this.processStream(reader, observer, context);
220
+ })
221
+ .catch(e => {
222
+ if (!this.isAbortError(e)) {
223
+ observer.error(e);
224
+ }
225
+ });
226
+
227
+ // 返回清理函数
228
+ return () => {
229
+ context.isActive = false;
230
+ controller.abort();
231
+ this.activeRequests.delete(context);
232
+ };
233
+ });
234
+ }
235
+
236
+ get<T = unknown>(url: string, _data = "{}", extra: RequestInit = {}): Observable<T> {
237
+ return this.request<T>("GET", url, undefined, extra);
238
+ }
239
+
240
+ post<T = unknown>(url: string, data: RequestData = {}, extra: RequestInit = {}): Observable<T> {
241
+ return this.request<T>("POST", url, data, extra);
242
+ }
243
+
244
+ private handleRequest(url: string, method: HttpMethod): RequestInit {
245
+ const headers: Record<string, string> = {};
246
+
247
+ // 只有 POST 请求才设置 Content-Type
248
+ if (method === "POST") {
249
+ headers["Content-Type"] = NDJSON_CONTENT_TYPE;
250
+ }
251
+
252
+ const config: RequestInit = { headers };
253
+ const token = getToken();
254
+
255
+ if (!token && !this.options.filter_url?.some(_url => url.includes(_url))) {
256
+ this.options.tokenExpiration?.();
257
+ return config;
258
+ }
259
+
260
+ if (token) {
261
+ headers[TOKEN_KEY] = token;
262
+ }
263
+
264
+ if (isFunction(this.options.requestOptions)) {
265
+ const extraOptions = this.options.requestOptions(config);
266
+ if (isObjectLike(extraOptions)) {
267
+ return this.mergeRequestInit(config, extraOptions as RequestInit);
268
+ }
269
+ }
270
+
271
+ return config;
272
+ }
273
+
274
+ handleResponse<T>(response: T): T {
275
+ if (isFunction(this.options.handleResponse)) {
276
+ return this.options.handleResponse(response);
277
+ }
278
+ return response;
279
+ }
280
+
281
+ private mergeRequestInit(...configs: Array<RequestInit | undefined>): RequestInit {
282
+ const merged: RequestInit = {};
283
+ const mergedHeaders = new Headers();
284
+ let hasHeaders = false;
285
+
286
+ configs.forEach((config) => {
287
+ if (!config) {
288
+ return;
289
+ }
290
+
291
+ const { headers, ...rest } = config;
292
+ Object.assign(merged, rest);
293
+ hasHeaders = this.mergeHeaders(mergedHeaders, headers) || hasHeaders;
294
+ });
295
+
296
+ if (hasHeaders) {
297
+ merged.headers = mergedHeaders;
298
+ }
299
+
300
+ return merged;
301
+ }
302
+
303
+ private mergeHeaders(target: Headers, source?: HeadersInit): boolean {
304
+ if (!source) {
305
+ return false;
306
+ }
307
+
308
+ let merged = false;
309
+ new Headers(source).forEach((value, key) => {
310
+ target.set(key, value);
311
+ merged = true;
312
+ });
313
+
314
+ return merged;
315
+ }
316
+
317
+ private isAbortError(error: unknown): boolean {
318
+ return error instanceof Error && error.name === "AbortError";
319
+ }
320
+
321
+ /**
322
+ * 取消所有活跃的请求
323
+ */
324
+ cancelAll(): void {
325
+ this.activeRequests.forEach(context => {
326
+ context.isActive = false;
327
+ context.controller.abort();
328
+ });
329
+ this.activeRequests.clear();
330
+ }
331
+ }
332
+
333
+ // 默认实例
334
+ const defaultNdJson = new NdJson();
335
+
336
+ /**
337
+ * 创建新的 NdJson 实例
338
+ */
339
+ export const createNdJson = (options?: NdJsonOptions): NdJson => {
340
+ return new NdJson(options);
341
+ };
342
+
343
+ /**
344
+ * 初始化默认实例
345
+ */
346
+ export const createNdJsonService = (options: NdJsonOptions): void => {
347
+ defaultNdJson.create(options);
348
+ };
349
+
350
+ // 导出默认实例 (保持向后兼容)
351
+ export const ndJson = defaultNdJson;
package/src/type.ts CHANGED
@@ -30,7 +30,7 @@ export interface Options {
30
30
  * @param status 错误code
31
31
  * @param error 错误实例
32
32
  */
33
- handleError?: (msg: string, status: string | number, error: AxiosError<any>) => void
33
+ handleError?: (msg: string, status: string | number, error: AxiosError<any>) => void | Promise<any>
34
34
  requestOptions?: (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig | Record<string, any>
35
35
  isCreateTokenRefresh?: boolean
36
36
  }