@webority/web-core 0.40.0 → 0.41.0

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/README.md CHANGED
@@ -222,7 +222,7 @@ seconds. An app's own error boundary shares that window: reload only when
222
222
  `Logger` is mobile-core's Logger: `debug`, `info`, `warn`, `error`, `breadcrumb`, `recordError`,
223
223
  `requestFailed`, `setUser`, `setAttribute`, `trackScreen`, with secrets redacted before anything is
224
224
  printed or forwarded. `Logger.requestFailed(error, { label, expectedStatuses })` logs a failed service
225
- call: a cancel as info, an expected status as a warning, anything else as a recorded error with its
225
+ call: a cancel as info, an expected status as info (never a console warning), anything else as a recorded error with its
226
226
  status, method, path (no query string), the server's `code` and `traceId`, and the correlation id.
227
227
  `setConfig({ logLevel, loggingEnabled })` decides what reaches the console; `setRemoteLogger` forwards
228
228
  info and above (never debug) to an error-reporting service, and errors are forwarded even with logging
@@ -1 +1 @@
1
- import{Logger as e}from"../logger/logger.js";import{applyConfiguredBaseUrl as t}from"./baseUrl.js";import{applyClientHeaders as n}from"./clientHeaders.js";import{readMinAppVersion as r}from"./minAppVersion.js";import{sendFormDataAsMultipart as i}from"./multipart.js";import{applyRequestDefaults as a}from"./requestDefaults.js";import{responseBodyText as o}from"./responseBodyText.js";import{attachRetry as s}from"./retry.js";import c from"axios";var l=c.create({headers:{"Content-Type":`application/json`,Accept:`application/json`}});t(l),a(l),i(l),r(l),s(l),l.interceptors.request.use(t=>(e.info(`Request: ${t.method?.toUpperCase()} ${t.url}`),t),t=>{let n=t instanceof Error?t.message:String(t);return e.error(`Request error:`,n),Promise.reject(t)}),n(l),l.interceptors.response.use(t=>(e.info(`Response: ${t.status} from ${t.config.url}`),t),t=>{if(c.isAxiosError(t)){let n=t.config;if(n?.skipErrorLog)return Promise.reject(t);t.response?e.error(`Response error: ${o(t.response.data,t.message)}, URL: ${n?.url}, Status: ${t.response.status}`):e.error(`Network error or no response from server: ${t.message}, URL: ${n?.url}`)}else{let n=t instanceof Error?t.message:String(t);e.error(`Unknown response error: ${n}`)}return Promise.reject(t)});export{l as default};
1
+ import{Logger as e}from"../logger/logger.js";import{applyConfiguredBaseUrl as t}from"./baseUrl.js";import{applyClientHeaders as n}from"./clientHeaders.js";import{readMinAppVersion as r}from"./minAppVersion.js";import{sendFormDataAsMultipart as i}from"./multipart.js";import{applyRequestDefaults as a}from"./requestDefaults.js";import{responseBodyText as o}from"./responseBodyText.js";import{attachRetry as s}from"./retry.js";import c from"axios";var l=c.create({headers:{"Content-Type":`application/json`,Accept:`application/json`}});t(l),a(l),i(l),r(l),s(l),l.interceptors.request.use(t=>(e.info(`Request: ${t.method?.toUpperCase()} ${t.url}`),t),t=>{let n=t instanceof Error?t.message:String(t);return e.error(`Request error:`,n),Promise.reject(t)}),n(l),l.interceptors.response.use(t=>(e.info(`Response: ${t.status} from ${t.config.url}`),t),t=>{if(c.isCancel(t))return Promise.reject(t);if(c.isAxiosError(t)){let n=t.config;if(n?.skipErrorLog)return Promise.reject(t);t.response?e.error(`Response error: ${o(t.response.data,t.message)}, URL: ${n?.url}, Status: ${t.response.status}`):e.error(`Network error or no response from server: ${t.message}, URL: ${n?.url}`)}else{let n=t instanceof Error?t.message:String(t);e.error(`Unknown response error: ${n}`)}return Promise.reject(t)});export{l as default};
@@ -1 +1 @@
1
- import{Logger as e}from"../logger/logger.js";import{ACCESS_TOKEN_KEY as t,getToken as n}from"../auth/authStore.js";import{getLastKnownNetworkStatus as r}from"../network/networkStatus.js";import{applyConfiguredBaseUrl as i}from"./baseUrl.js";import{applyAccountIdHeader as a,applyClientHeaders as o}from"./clientHeaders.js";import{readMinAppVersion as s}from"./minAppVersion.js";import{sendFormDataAsMultipart as c}from"./multipart.js";import{applyRequestDefaults as l}from"./requestDefaults.js";import{responseBodyText as u}from"./responseBodyText.js";import{attachRetry as d}from"./retry.js";import{SIGN_OUT_REASON_HEADER as f,endSessionAndLogout as p,getAccountIdProvider as m,getErrorMessage as h,getRefreshPath as g,injectStore as _,injectUserAuth as v,isRefreshInFlight as y,queueBehindRefresh as b,readSignOutReason as x,refreshAccessToken as S,resetRefreshState as C}from"./tokenRefresh.js";import w,{AxiosHeaders as T}from"axios";var E=class e extends Error{constructor(t=`Request blocked: device is offline`){super(t),this.name=`RequestOfflineError`,Object.setPrototypeOf(this,e.prototype)}},D={TERMINATED:`SESSION_TERMINATED`,EXPIRED:`SESSION_EXPIRED`},O=new Set(Object.values(D)),k=`x-security-stamp-invalid`,A=e=>{if(!e||typeof e!=`object`)return!1;let t=e.get,n=typeof t==`function`?t.call(e,k):e[k];return String(n).toLowerCase()===`true`},j=e=>{if(!e||typeof e!=`object`)return;let t=e,n=t.error,r=[t.code,t.errorCode,n&&typeof n==`object`?n.code:void 0];for(let e of r)if(typeof e==`string`&&O.has(e))return e},M=w.create({headers:{"Content-Type":`application/json`}});s(M),d(M);var N=(e,t)=>{e.headers instanceof T?e.headers.set(`Authorization`,`Bearer ${t}`):e.headers=T.from({...e.headers,Authorization:`Bearer ${t}`})},P=e=>{let t=g();return!!t&&!!e?.includes(t)};i(M),l(M),c(M),o(M),M.interceptors.request.use(async i=>{if((await r()).isConnected===!1)return e.warn(`Request blocked: browser is offline. URL: ${i.url}`),Promise.reject(new E);try{if(P(i.url))return i;let r=await n(t);if(!r?.token)throw Error(`No access token available`);let o=Date.now();return Date.parse(r.expiry)<=o+30*1e3?(e.info(`Access token expired or about to expire, refreshing before request`),N(i,await S()),await a(i.headers,m()),i):(N(i,r.token),await a(i.headers,m()),i)}catch(t){return e.error(`Failed to attach token:`,h(t)),Promise.reject(t)}},t=>(e.error(`Request error:`,h(t)),Promise.reject(t))),M.interceptors.response.use(e=>e,async t=>{if(!w.isAxiosError(t))return e.error(`Non-axios error in response interceptor: ${h(t)}`),Promise.reject(t);let n=t,r=n.config;if(!n.response)return r?.skipErrorLog||e.error(`Network error or no response from server: ${n.message}, URL: ${r?.url}`),Promise.reject(t);if(n.response.status===401&&r&&!r._retry){if(P(r.url))return Promise.reject(t);let i=x(n.response.headers),a=j(n.response.data)??(A(n.response.headers)||i!==null?D.TERMINATED:void 0);if(a)return e.warn(`Session ${a}, logging out without refresh. URL: ${r.url}`),C(),await p(`terminated`,i),Promise.reject(t);if(e.warn(`Unauthorized URL: ${r.url}`),r._retry=!0,y())return b().then(t=>(e.info(`Retrying request after token refresh:`,r.url),N(r,t),M(r))).catch(t=>(e.error(`Failed to retry request after token refresh:`,h(t)),Promise.reject(t)));try{return N(r,await S()),M(r)}catch(e){return Promise.reject(e)}}return r?.skipErrorLog||e.error(`Response error: ${u(n.response?.data,n.message)}, URL: ${r?.url}, Status: ${n.response.status}`),Promise.reject(t)});export{E as RequestOfflineError,k as SESSION_INVALID_HEADER,D as SESSION_TERMINATION_CODES,f as SIGN_OUT_REASON_HEADER,M as default,_ as injectStore,v as injectUserAuth,C as resetRefreshState};
1
+ import{Logger as e}from"../logger/logger.js";import{ACCESS_TOKEN_KEY as t,getToken as n}from"../auth/authStore.js";import{getLastKnownNetworkStatus as r}from"../network/networkStatus.js";import{applyConfiguredBaseUrl as i}from"./baseUrl.js";import{applyAccountIdHeader as a,applyClientHeaders as o}from"./clientHeaders.js";import{readMinAppVersion as s}from"./minAppVersion.js";import{sendFormDataAsMultipart as c}from"./multipart.js";import{applyRequestDefaults as l}from"./requestDefaults.js";import{responseBodyText as u}from"./responseBodyText.js";import{attachRetry as d}from"./retry.js";import{SIGN_OUT_REASON_HEADER as f,endSessionAndLogout as p,getAccountIdProvider as m,getErrorMessage as h,getRefreshPath as g,injectStore as _,injectUserAuth as v,isRefreshInFlight as y,queueBehindRefresh as b,readSignOutReason as x,refreshAccessToken as S,resetRefreshState as C}from"./tokenRefresh.js";import w,{AxiosHeaders as T}from"axios";var E=class e extends Error{constructor(t=`Request blocked: device is offline`){super(t),this.name=`RequestOfflineError`,Object.setPrototypeOf(this,e.prototype)}},D=class e extends Error{constructor(){super(`No access token available`),this.name=`NoAccessTokenError`,Object.setPrototypeOf(this,e.prototype)}},O={TERMINATED:`SESSION_TERMINATED`,EXPIRED:`SESSION_EXPIRED`},k=new Set(Object.values(O)),A=`x-security-stamp-invalid`,j=e=>{if(!e||typeof e!=`object`)return!1;let t=e.get,n=typeof t==`function`?t.call(e,A):e[A];return String(n).toLowerCase()===`true`},M=e=>{if(!e||typeof e!=`object`)return;let t=e,n=t.error,r=[t.code,t.errorCode,n&&typeof n==`object`?n.code:void 0];for(let e of r)if(typeof e==`string`&&k.has(e))return e},N=w.create({headers:{"Content-Type":`application/json`}});s(N),d(N);var P=(e,t)=>{e.headers instanceof T?e.headers.set(`Authorization`,`Bearer ${t}`):e.headers=T.from({...e.headers,Authorization:`Bearer ${t}`})},F=e=>{let t=g();return!!t&&!!e?.includes(t)};i(N),l(N),c(N),o(N),N.interceptors.request.use(async i=>{if((await r()).isConnected===!1)return e.warn(`Request blocked: browser is offline. URL: ${i.url}`),Promise.reject(new E);try{if(F(i.url))return i;let r=await n(t);if(!r?.token)throw new D;let o=Date.now();return Date.parse(r.expiry)<=o+30*1e3?(e.info(`Access token expired or about to expire, refreshing before request`),P(i,await S()),await a(i.headers,m()),i):(P(i,r.token),await a(i.headers,m()),i)}catch(t){return t instanceof D?e.info(`Request not sent: signed out. URL: ${i.url}`):e.error(`Failed to attach token:`,h(t)),Promise.reject(t)}},t=>(e.error(`Request error:`,h(t)),Promise.reject(t))),N.interceptors.response.use(e=>e,async t=>{if(w.isCancel(t)||t instanceof E||t instanceof D)return Promise.reject(t);if(!w.isAxiosError(t))return e.error(`Non-axios error in response interceptor: ${h(t)}`),Promise.reject(t);let n=t,r=n.config;if(!n.response)return r?.skipErrorLog||e.error(`Network error or no response from server: ${n.message}, URL: ${r?.url}`),Promise.reject(t);if(n.response.status===401&&r&&!r._retry){if(F(r.url))return Promise.reject(t);let i=x(n.response.headers),a=M(n.response.data)??(j(n.response.headers)||i!==null?O.TERMINATED:void 0);if(a)return e.warn(`Session ${a}, logging out without refresh. URL: ${r.url}`),C(),await p(`terminated`,i),Promise.reject(t);if(e.warn(`Unauthorized URL: ${r.url}`),r._retry=!0,y())return b().then(t=>(e.info(`Retrying request after token refresh:`,r.url),P(r,t),N(r))).catch(t=>(e.error(`Failed to retry request after token refresh:`,h(t)),Promise.reject(t)));try{return P(r,await S()),N(r)}catch(e){return Promise.reject(e)}}return r?.skipErrorLog||e.error(`Response error: ${u(n.response?.data,n.message)}, URL: ${r?.url}, Status: ${n.response.status}`),Promise.reject(t)});export{E as RequestOfflineError,A as SESSION_INVALID_HEADER,O as SESSION_TERMINATION_CODES,f as SIGN_OUT_REASON_HEADER,N as default,_ as injectStore,v as injectUserAuth,C as resetRefreshState};
@@ -1,2 +1,2 @@
1
1
  import{getConfigValue as e}from"../config/index.js";import{redact as t,redactString as n}from"./redact.js";var r=[`debug`,`info`,`warn`,`error`],i=t=>{let n=e(`logLevel`,`info`);return r.indexOf(t)>=r.indexOf(n)},a=()=>e(`loggingEnabled`,!0),o=null,s=e=>{o=e},c=e=>{if(e)try{e()}catch{}},l=e=>{if(e instanceof Error)return e;if(typeof e==`string`)return Error(e);try{return Error(JSON.stringify(e))}catch{return Error(String(e))}},u=null,d=e=>e==null?null:typeof e==`string`||typeof e==`number`||typeof e==`boolean`?e:String(e),f=e=>{if(u=e,e)for(let[t,n]of Object.entries(e))c(()=>o?.setAttribute?.(t,d(n)))},p=()=>u,m={dedupeWindowMs:5e3,dedupeMaxPerWindow:1,screenHistoryLimit:20},h=null,g=[],_=e=>{m={...m,...e},g.length>m.screenHistoryLimit&&(g=g.slice(g.length-m.screenHistoryLimit))},v=new Map,y=200,b=e=>{let t=e.stack?.split(`
2
- `)[1]?.trim();return t?`${e.message}::${t}`:e.message},x=()=>{for(;v.size>y;){let e=v.keys().next().value;if(e===void 0)return;v.delete(e)}},S=e=>{if(m.dedupeWindowMs<=0)return{forward:!0};let t=Date.now(),n=v.get(e);if(!n||t-n.windowStart>=m.dedupeWindowMs){let r=n?.suppressedCount??0;return v.set(e,{windowStart:t,count:1,suppressedCount:0}),x(),r>0?{forward:!0,suppressedCount:r}:{forward:!0}}return n.count+=1,n.count>m.dedupeMaxPerWindow?(n.suppressedCount+=1,{forward:!1}):{forward:!0}},C=(e,t)=>{let n={...u};return h!==null&&(n.screen=h),t&&(n.suppressedCount=t),{...n,...e}},w=(e,t)=>{let{forward:n,suppressedCount:r}=S(b(e));n&&c(()=>o?.recordError?.(e,C(t,r)))},T=(e,...r)=>{!a()||!i(`debug`)||console.debug(`[DEBUG]: ${n(e)}`,...t(r))},E=(e,...r)=>{let s=n(e),l=t(r);!a()||!i(`info`)||(console.info(`[INFO]: ${s}`,...l),c(()=>o?.log?.(`info`,s,l)))},D=(e,...r)=>{let s=n(e),l=t(r);!a()||!i(`warn`)||(console.warn(`[WARNING]: ${s}`,...l),c(()=>o?.log?.(`warn`,s,l)))},O=(e,...r)=>{let s=n(e),u=t(r);a()&&i(`error`)&&console.error(`[ERROR]: ${s}`,...u),c(()=>o?.log?.(`error`,s,u));let d=u[0];d!==void 0&&w(l(d),{source:`Logger.error`,message:s})},k=(e,r)=>{let s=n(e),l=t(r);a()&&i(`info`)&&console.info(`[BREADCRUMB]: ${s}`,l??``),c(()=>o?.breadcrumb?.(s,l))},A=(e,n)=>{let r=t(l(e)),o=t(n);a()&&i(`error`)&&console.error(`[RECORDED]: ${r.message}`,o??``),w(r,o)},j=e=>typeof e==`object`&&e?e:null,M=(e,t)=>{let n=j(e);if(!n)return null;let r=typeof n.get==`function`?n.get.call(e,t):n[t]??n[t.toLowerCase()];return typeof r==`string`&&r!==``?r:null},N={debug:T,info:E,warn:D,error:O,breadcrumb:k,recordError:A,requestFailed:(r,s={})=>{let{label:l,expectedStatuses:u=[],...d}=s,f=j(r),p=j(f?.config),m=j(f?.response),h=j(m?.data),g=typeof m?.status==`number`?m.status:null,_=typeof p?.method==`string`?p.method.toUpperCase():null,v=typeof p?.url==`string`?p.url.split(`?`)[0]??null:null,y=l??([_,v].filter(Boolean).join(` `)||`Request`);if(f?.name===`CanceledError`||f?.code===`ERR_CANCELED`||f?.__CANCEL__===!0){E(`${y} canceled`);return}if(g!==null&&u.includes(g)){D(`${y} refused (${g})`);return}let b=typeof f?.message==`string`?f.message:r instanceof Error?r.message:null,x=n(g===null?`${y} failed`:`${y} failed (${g})`);a()&&i(`error`)&&console.error(`[ERROR]: ${x}`,n(b??``)),c(()=>o?.log?.(`error`,x,[]));let S=e(`correlationIdHeader`,null),C=t({source:`Logger.requestFailed`,label:y,status:g,method:_,path:v,code:typeof h?.code==`string`?h.code:null,traceId:typeof h?.traceId==`string`?h.traceId:null,correlationId:S?M(p?.headers,S):null,detail:b,...d});w(t(Error(x)),C)},setUser:(e,t)=>{c(()=>o?.setUser?.(e,t))},setAttribute:(e,t)=>{c(()=>o?.setAttribute?.(e,t))},trackScreen:(e,t)=>{e!==h&&(h=e,g.push(e),g.length>m.screenHistoryLimit&&(g=g.slice(g.length-m.screenHistoryLimit)),k(`Screen: ${e}`,t))},getCurrentScreen:()=>h,getScreenHistory:()=>[...g]};export{N as Logger,_ as configureLogger,p as getDeviceContext,f as setDeviceContext,s as setRemoteLogger};
2
+ `)[1]?.trim();return t?`${e.message}::${t}`:e.message},x=()=>{for(;v.size>y;){let e=v.keys().next().value;if(e===void 0)return;v.delete(e)}},S=e=>{if(m.dedupeWindowMs<=0)return{forward:!0};let t=Date.now(),n=v.get(e);if(!n||t-n.windowStart>=m.dedupeWindowMs){let r=n?.suppressedCount??0;return v.set(e,{windowStart:t,count:1,suppressedCount:0}),x(),r>0?{forward:!0,suppressedCount:r}:{forward:!0}}return n.count+=1,n.count>m.dedupeMaxPerWindow?(n.suppressedCount+=1,{forward:!1}):{forward:!0}},C=(e,t)=>{let n={...u};return h!==null&&(n.screen=h),t&&(n.suppressedCount=t),{...n,...e}},w=(e,t)=>{let{forward:n,suppressedCount:r}=S(b(e));n&&c(()=>o?.recordError?.(e,C(t,r)))},T=(e,...r)=>{!a()||!i(`debug`)||console.debug(`[DEBUG]: ${n(e)}`,...t(r))},E=(e,...r)=>{let s=n(e),l=t(r);!a()||!i(`info`)||(console.info(`[INFO]: ${s}`,...l),c(()=>o?.log?.(`info`,s,l)))},D=(e,...r)=>{let s=n(e),l=t(r);!a()||!i(`warn`)||(console.warn(`[WARNING]: ${s}`,...l),c(()=>o?.log?.(`warn`,s,l)))},O=(e,...r)=>{let s=n(e),u=t(r);a()&&i(`error`)&&console.error(`[ERROR]: ${s}`,...u),c(()=>o?.log?.(`error`,s,u));let d=u[0];d!==void 0&&w(l(d),{source:`Logger.error`,message:s})},k=(e,r)=>{let s=n(e),l=t(r);a()&&i(`info`)&&console.info(`[BREADCRUMB]: ${s}`,l??``),c(()=>o?.breadcrumb?.(s,l))},A=(e,n)=>{let r=t(l(e)),o=t(n);a()&&i(`error`)&&console.error(`[RECORDED]: ${r.message}`,o??``),w(r,o)},j=e=>typeof e==`object`&&e?e:null,M=(e,t)=>{let n=j(e);if(!n)return null;let r=typeof n.get==`function`?n.get.call(e,t):n[t]??n[t.toLowerCase()];return typeof r==`string`&&r!==``?r:null},N={debug:T,info:E,warn:D,error:O,breadcrumb:k,recordError:A,requestFailed:(r,s={})=>{let{label:l,expectedStatuses:u=[],...d}=s,f=j(r),p=j(f?.config),m=j(f?.response),h=j(m?.data),g=typeof m?.status==`number`?m.status:null,_=typeof p?.method==`string`?p.method.toUpperCase():null,v=typeof p?.url==`string`?p.url.split(`?`)[0]??null:null,y=l??([_,v].filter(Boolean).join(` `)||`Request`);if(f?.name===`CanceledError`||f?.code===`ERR_CANCELED`||f?.__CANCEL__===!0){E(`${y} canceled`);return}if(g!==null&&u.includes(g)){E(`${y} refused (${g})`);return}let b=typeof f?.message==`string`?f.message:r instanceof Error?r.message:null,x=n(g===null?`${y} failed`:`${y} failed (${g})`);a()&&i(`error`)&&console.error(`[ERROR]: ${x}`,n(b??``)),c(()=>o?.log?.(`error`,x,[]));let S=e(`correlationIdHeader`,null),C=t({source:`Logger.requestFailed`,label:y,status:g,method:_,path:v,code:typeof h?.code==`string`?h.code:null,traceId:typeof h?.traceId==`string`?h.traceId:null,correlationId:S?M(p?.headers,S):null,detail:b,...d});w(t(Error(x)),C)},setUser:(e,t)=>{c(()=>o?.setUser?.(e,t))},setAttribute:(e,t)=>{c(()=>o?.setAttribute?.(e,t))},trackScreen:(e,t)=>{e!==h&&(h=e,g.push(e),g.length>m.screenHistoryLimit&&(g=g.slice(g.length-m.screenHistoryLimit)),k(`Screen: ${e}`,t))},getCurrentScreen:()=>h,getScreenHistory:()=>[...g]};export{N as Logger,_ as configureLogger,p as getDeviceContext,f as setDeviceContext,s as setRemoteLogger};
@@ -60,7 +60,7 @@ export interface RequestFailedContext {
60
60
  label?: string;
61
61
  /**
62
62
  * Statuses this call answers as part of its work (a 409 the screen explains, a 404 that
63
- * means "none yet"): logged as a warning, never recorded as an error.
63
+ * means "none yet"): logged as info, never a console warning and never recorded as an error.
64
64
  */
65
65
  expectedStatuses?: readonly number[];
66
66
  /** Anything else worth carrying to the remote sink. Never a token or a request body. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webority/web-core",
3
- "version": "0.40.0",
3
+ "version": "0.41.0",
4
4
  "description": "Platform layer for Webority React web portals: HTTP clients with single-flight token refresh, auth store, API error extraction, RTK Query base query, logger, formatters, storage, version check and route guards. No UI.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",