@webority/web-core 0.51.2 → 0.52.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
@@ -111,6 +111,19 @@ server's reason from the `X-Sign-Out-Reason` header (`signOutReasonHeader` renam
111
111
  off). A 401 that names a sign-out reason is a revoked session, so no refresh is tried. Cross-origin,
112
112
  the API lists the header in `Access-Control-Expose-Headers`.
113
113
 
114
+ A request never runs in a workspace other than the one it was made in. `initUserAuth`'s `accountId`
115
+ provider is read once, when a request is made, and sent as `X-Account-Id`; a retry, a replay after a
116
+ refresh and a replay after a session replacement send that same id and never read the provider again.
117
+ A request refused (401) for a session that has since been replaced is sent again with the stored
118
+ token only when the provider still names the account it was made in (or neither names one). When the
119
+ session moved to another workspace, it is not sent again: it rejects with `WorkspaceChangedError`
120
+ (`code: 'workspace_changed'`, `status: 409`) and the session is kept. An API answer of 409 with
121
+ `{ code: 'workspace_changed' }` rejects the same way, is never retried and never logs out.
122
+ `extractApiError` maps both to `code: 'workspace_changed'`; branch with `isWorkspaceChangedError(error)`,
123
+ which accepts the error or the `ApiError` made of it. Mark a call that ends the session and returns a
124
+ new one (a workspace switch) with `replacesSession: true`, so requests refused while it is open wait
125
+ for its tokens.
126
+
114
127
  `legacyKeys` moves a session an app kept under its own keys before it adopted the library: the first
115
128
  token read that finds the library's keys empty moves it (bare token strings are fine; an access
116
129
  token's expiry comes from its JWT) and deletes the old keys, so the deploy signs nobody out. Each move
@@ -222,9 +235,14 @@ write returns `false`.
222
235
 
223
236
  ## Chunk reload
224
237
 
225
- `importWithReload` reloads once when a lazy chunk from an older deploy fails, then refuses for 60
226
- seconds. An app's own error boundary shares that window: reload only when
227
- `!hasRecentlyReloaded() && markReloaded()`.
238
+ `importWithReload` reloads once when a lazy chunk from an older deploy fails, and reloads the page
239
+ being opened (path, query and hash), not the page being left: the failed route renders once the
240
+ navigation commits and reloads from there. A form posted to that route reaches the route's error
241
+ element with the load error, and `useRouteErrorReport` reloads it once. `lazyWithReload` and
242
+ `useRouteErrorReport` reload the same way. The reload is claimed in sessionStorage for that page with a time stamp; if the page's
243
+ chunk still fails after its reload, inside 60 seconds, the route's error element shows it and
244
+ `useRouteErrorReport` warns once, with no further reload. An app's own error boundary shares the
245
+ claim: reload only when `!hasRecentlyReloaded() && markReloaded()`.
228
246
 
229
247
  ## Logger
230
248
 
@@ -1 +1 @@
1
- import{getConfigValue as e}from"../config/index.js";var t=`X-Client-Build`,n=`X-Client-Kind`,r=`X-Account-Id`,i=`Browser`,a=()=>{let t=e(`appVersion`);if(!t)return null;let n=e(`commit`);return n?`${t}+${n}`:t},o=(e,t,n)=>{n&&!e.has(t)&&e.set(t,n)},s=e=>{e.interceptors.request.use(e=>(o(e.headers,n,i),o(e.headers,t,a()),e))},c=async(e,t)=>{let n=t?await t():null;n&&e.set(r,n)};export{r as ACCOUNT_ID_HEADER,t as CLIENT_BUILD_HEADER,i as CLIENT_KIND,n as CLIENT_KIND_HEADER,c as applyAccountIdHeader,s as applyClientHeaders,a as getClientBuild};
1
+ import{getConfigValue as e}from"../config/index.js";var t=`X-Client-Build`,n=`X-Client-Kind`,r=`X-Account-Id`,i=`Browser`,a=()=>{let t=e(`appVersion`);if(!t)return null;let n=e(`commit`);return n?`${t}+${n}`:t},o=(e,t,n)=>{n&&!e.has(t)&&e.set(t,n)},s=e=>{e.interceptors.request.use(e=>(o(e.headers,n,i),o(e.headers,t,a()),e))},c=async e=>(e?await e():null)||null,l=async(e,t)=>{if(e._accountId!==void 0){e._accountId&&e.headers.set(r,e._accountId);return}let n=e.headers.get(r);if(typeof n==`string`&&n!==``){e._accountId=n;return}let i=await c(t);e._accountId=i,i&&e.headers.set(r,i)};export{r as ACCOUNT_ID_HEADER,t as CLIENT_BUILD_HEADER,i as CLIENT_KIND,n as CLIENT_KIND_HEADER,s as applyClientHeaders,a as getClientBuild,c as readAccountId,l as stampAccountId};
package/dist/api/index.js CHANGED
@@ -1 +1 @@
1
- import{ACCOUNT_ID_HEADER as e,CLIENT_BUILD_HEADER as t,CLIENT_KIND as n,CLIENT_KIND_HEADER as r,getClientBuild as i}from"./clientHeaders.js";import{MIN_APP_VERSION_HEADER as a,addMinAppVersionListener as o,getMinAppVersion as s}from"./minAppVersion.js";import{applyRequestDefaults as c,newCorrelationId as l}from"./requestDefaults.js";import{IDEMPOTENCY_KEY_HEADER as u,attachRetry as d,isNeverSentError as f,isRetryableFailure as p,isRetryableStatus as m,retryAfterMs as h}from"./retry.js";import{REFRESH_LOCK_NAME as g,withCrossTabRefreshLock as _}from"./crossTabRefreshLock.js";import v from"./publicClient.js";import{SIGN_OUT_REASON_HEADER as y,injectStore as b,injectUserAuth as x,refreshAccessToken as S,resetRefreshState as C}from"./tokenRefresh.js";import w,{RequestOfflineError as T,SESSION_TERMINATION_CODES as E}from"./userClient.js";import{initUserAuth as D}from"../initUserAuth.js";export{e as ACCOUNT_ID_HEADER,t as CLIENT_BUILD_HEADER,n as CLIENT_KIND,r as CLIENT_KIND_HEADER,u as IDEMPOTENCY_KEY_HEADER,a as MIN_APP_VERSION_HEADER,g as REFRESH_LOCK_NAME,T as RequestOfflineError,E as SESSION_TERMINATION_CODES,y as SIGN_OUT_REASON_HEADER,o as addMinAppVersionListener,c as applyRequestDefaults,d as attachRetry,i as getClientBuild,s as getMinAppVersion,D as initUserAuth,b as injectStore,x as injectUserAuth,f as isNeverSentError,p as isRetryableFailure,m as isRetryableStatus,l as newCorrelationId,v as publicClient,S as refreshAccessToken,C as resetRefreshState,h as retryAfterMs,w as userClient,_ as withCrossTabRefreshLock};
1
+ import{ACCOUNT_ID_HEADER as e,CLIENT_BUILD_HEADER as t,CLIENT_KIND as n,CLIENT_KIND_HEADER as r,getClientBuild as i}from"./clientHeaders.js";import{MIN_APP_VERSION_HEADER as a,addMinAppVersionListener as o,getMinAppVersion as s}from"./minAppVersion.js";import{applyRequestDefaults as c,newCorrelationId as l}from"./requestDefaults.js";import{WORKSPACE_CHANGED_CODE as u,WorkspaceChangedError as d,isWorkspaceChangedError as f}from"./workspaceChanged.js";import{IDEMPOTENCY_KEY_HEADER as p,attachRetry as m,isNeverSentError as h,isRetryableFailure as g,isRetryableStatus as _,retryAfterMs as v}from"./retry.js";import{REFRESH_LOCK_NAME as y,withCrossTabRefreshLock as b}from"./crossTabRefreshLock.js";import x from"./publicClient.js";import{SIGN_OUT_REASON_HEADER as S,injectStore as C,injectUserAuth as w,refreshAccessToken as T,resetRefreshState as E}from"./tokenRefresh.js";import D,{RequestOfflineError as O,SESSION_TERMINATION_CODES as k}from"./userClient.js";import{initUserAuth as A}from"../initUserAuth.js";export{e as ACCOUNT_ID_HEADER,t as CLIENT_BUILD_HEADER,n as CLIENT_KIND,r as CLIENT_KIND_HEADER,p as IDEMPOTENCY_KEY_HEADER,a as MIN_APP_VERSION_HEADER,y as REFRESH_LOCK_NAME,O as RequestOfflineError,k as SESSION_TERMINATION_CODES,S as SIGN_OUT_REASON_HEADER,u as WORKSPACE_CHANGED_CODE,d as WorkspaceChangedError,o as addMinAppVersionListener,c as applyRequestDefaults,m as attachRetry,i as getClientBuild,s as getMinAppVersion,A as initUserAuth,C as injectStore,w as injectUserAuth,h as isNeverSentError,g as isRetryableFailure,_ as isRetryableStatus,f as isWorkspaceChangedError,l as newCorrelationId,x as publicClient,T as refreshAccessToken,E as resetRefreshState,v as retryAfterMs,D as userClient,b as withCrossTabRefreshLock};
package/dist/api/retry.js CHANGED
@@ -1 +1 @@
1
- import{getConfigValue as e}from"../config/index.js";import t from"axios";var n=`Idempotency-Key`,r=e=>typeof e==`number`&&(e===429||e>=500&&e<=599),i=new Set([`get`,`head`,`options`,`put`,`delete`]),a=new Set([`ECONNREFUSED`,`ENOTFOUND`,`EAI_AGAIN`,`ENETUNREACH`,`EHOSTUNREACH`]),o=e=>!e.response&&typeof e.code==`string`&&a.has(e.code),s=3e4,c=e=>{let t=e.response;if(!t)return null;let n=t.data,r=t.headers,i=typeof r?.get==`function`?r.get(`retry-after`):r?.[`retry-after`];for(let e of[n?.retryAfterSeconds,i]){let t=Number(e);if(e!=null&&Number.isFinite(t)&&t>0)return t*1e3}return null},l=e=>{let t=e?.headers,r=typeof t?.get==`function`?t.get(n):t?.[n];return typeof r==`string`&&r!==``},u=e=>{if(t.isCancel(e)||e.code===`ERR_CANCELED`)return!1;if(o(e))return!0;let n=(e.config?.method??`get`).toLowerCase(),a=i.has(n),u=e.response?.status;if(u===void 0)return a;if(!r(u))return!1;let d=c(e);return d!==null&&d>s?!1:a||(u===429||u===503)&&l(e.config)},d={retryDelayMs:e=>e<=1?0:2**e*1e3,shouldRetry:u,onRetry:()=>void 0},f=(e,t)=>new Promise(n=>{let r=()=>{clearTimeout(i),n(!1)},i=setTimeout(()=>{t?.removeEventListener?.(`abort`,r),n(!0)},e);t?.addEventListener?.(`abort`,r,{once:!0})}),p=(t,n={})=>{t.interceptors.response.use(void 0,async r=>{let i=r,a=i?.config;if(!a||a.retry===!1)return Promise.reject(r);let o=typeof a.retry==`object`?a.retry:{},s={...d,retries:e(`retries`),...n,...o},l=(a.__retryAttempt??0)+1;return l>s.retries||!s.shouldRetry(i)||a.signal?.aborted||(a.__retryAttempt=l,a.transformRequest=[e=>e],s.onRetry(l,i),!await f(c(i)??s.retryDelayMs(l),a.signal))?Promise.reject(r):t.request(a)})};export{n as IDEMPOTENCY_KEY_HEADER,p as attachRetry,o as isNeverSentError,u as isRetryableFailure,r as isRetryableStatus,c as retryAfterMs};
1
+ import{getConfigValue as e}from"../config/index.js";import{isWorkspaceChangedResponse as t}from"./workspaceChanged.js";import n from"axios";var r=`Idempotency-Key`,i=e=>typeof e==`number`&&(e===429||e>=500&&e<=599),a=new Set([`get`,`head`,`options`,`put`,`delete`]),o=new Set([`ECONNREFUSED`,`ENOTFOUND`,`EAI_AGAIN`,`ENETUNREACH`,`EHOSTUNREACH`]),s=e=>!e.response&&typeof e.code==`string`&&o.has(e.code),c=3e4,l=e=>{let t=e.response;if(!t)return null;let n=t.data,r=t.headers,i=typeof r?.get==`function`?r.get(`retry-after`):r?.[`retry-after`];for(let e of[n?.retryAfterSeconds,i]){let t=Number(e);if(e!=null&&Number.isFinite(t)&&t>0)return t*1e3}return null},u=e=>{let t=e?.headers,n=typeof t?.get==`function`?t.get(r):t?.[r];return typeof n==`string`&&n!==``},d=e=>{if(n.isCancel(e)||e.code===`ERR_CANCELED`)return!1;if(s(e))return!0;let t=(e.config?.method??`get`).toLowerCase(),r=a.has(t),o=e.response?.status;if(o===void 0)return r;if(!i(o))return!1;let d=l(e);return d!==null&&d>c?!1:r||(o===429||o===503)&&u(e.config)},f={retryDelayMs:e=>e<=1?0:2**e*1e3,shouldRetry:d,onRetry:()=>void 0},p=(e,t)=>new Promise(n=>{let r=()=>{clearTimeout(i),n(!1)},i=setTimeout(()=>{t?.removeEventListener?.(`abort`,r),n(!0)},e);t?.addEventListener?.(`abort`,r,{once:!0})}),m=(n,r={})=>{n.interceptors.response.use(void 0,async i=>{let a=i,o=a?.config;if(!o||o.retry===!1)return Promise.reject(i);let s=typeof o.retry==`object`?o.retry:{},c={...f,retries:e(`retries`),...r,...s},u=(o.__retryAttempt??0)+1;return t(a)||u>c.retries||!c.shouldRetry(a)||o.signal?.aborted||(o.__retryAttempt=u,o.transformRequest=[e=>e],c.onRetry(u,a),!await p(l(a)??c.retryDelayMs(u),o.signal))?Promise.reject(i):n.request(o)})};export{r as IDEMPOTENCY_KEY_HEADER,m as attachRetry,s as isNeverSentError,d as isRetryableFailure,i as isRetryableStatus,l as retryAfterMs};
@@ -1 +1 @@
1
- import{Logger as e}from"../logger/logger.js";import{ACCESS_TOKEN_KEY as t,REFRESH_TOKEN_KEY as n,clearSession as r,getToken as i,storeToken as a}from"../auth/authStore.js";import{withCrossTabRefreshLock as o}from"./crossTabRefreshLock.js";import s from"./publicClient.js";import c from"axios";var l,u,d=e=>{l=e},f=e=>{u=e},p=null,m=[],h=(e,t=null)=>{m.forEach(({resolve:n,reject:r})=>{e?r(e):t!==null&&n(t)}),m=[]},g=()=>{p=null,h(Error(`[@webority/web-core] Session ended`))},_=e=>e instanceof Error?e.message:typeof e==`string`?e:`Unknown error`,v=()=>{if(!l||!u)throw Error(`User package store is not initialized. Call initUserAuth first.`);return{store:l,auth:u}},y=()=>u?.accountId,b=()=>u?.refresh.path,x=async()=>{let e=v();await e.store.dispatch(e.auth.logout())},S=`X-Sign-Out-Reason`,C=e=>{let t=u?.signOutReasonHeader===void 0?S:u.signOutReasonHeader;if(!t||!e||typeof e!=`object`)return null;let n=e.get,r=typeof n==`function`?n.call(e,t):e[t.toLowerCase()];return typeof r==`string`&&r.trim()!==``?r.trim():null},w=async(e,t)=>{await r(e,{signOutReason:t}),await x()},T=()=>p!==null,E=()=>new Promise((e,t)=>{m.push({resolve:e,reject:t})}),D=class e extends Error{constructor(t){super(t),this.name=`SessionEndedError`,Object.setPrototypeOf(this,e.prototype)}},O=e=>e instanceof D||c.isAxiosError(e)&&(e.response?.status===400||e.response?.status===401),k=(e,t)=>{if(typeof e!=`string`||e===``)throw Error(`The refresh response has no "${t}".`);return e},A=e=>{let t=e??{};return{accessToken:k(t.accessToken,`accessToken`),accessTokenExpiry:k(t.accessTokenExpiration,`accessTokenExpiration`),refreshToken:k(t.refreshToken,`refreshToken`),refreshTokenExpiry:k(t.refreshTokenExpiration,`refreshTokenExpiration`)}},j=async r=>{let o=await i(n);if(!o?.token)throw new D(`No refresh token available`);if(Date.parse(o.expiry)<=Date.now())throw new D(`Refresh token expired`);let c={accessToken:(await i(`accessToken`))?.token??null,refreshToken:o.token},l=r.body?await r.body(c):c,u=r.headers?await r.headers():void 0;e.info(`Refreshing the access token`);let{data:d}=await s.post(r.path,l,u?{headers:u}:void 0),f=(r.readTokens??A)(d);return await a(t,f.accessToken,f.accessTokenExpiry),await a(n,f.refreshToken,f.refreshTokenExpiry),e.info(`Access token refreshed`),f.accessToken},M=30*1e3,N=async(n,r)=>{let a=await i(t);return a?.token&&a.token!==r&&Date.parse(a.expiry)>Date.now()+M?(e.info(`Adopting the access token another tab refreshed`),a.token):j(n)},P=()=>{if(p)return p;let t=(async()=>{try{let{refresh:e}=v().auth,t=(await i(`accessToken`))?.token??null,n=await o(()=>N(e,t));return h(null,n),n}catch(t){let n=c.isAxiosError(t)?t.response?.data:void 0;throw e.error(`Token refresh failed:`,n??_(t)),h(t,null),O(t)&&await w(`rejected`,c.isAxiosError(t)?C(t.response?.headers):null),t}})().finally(()=>{p===t&&(p=null)});return p=t,t};export{S as SIGN_OUT_REASON_HEADER,x as dispatchLogout,w as endSessionAndLogout,y as getAccountIdProvider,_ as getErrorMessage,b as getRefreshPath,d as injectStore,f as injectUserAuth,T as isRefreshInFlight,E as queueBehindRefresh,C as readSignOutReason,P as refreshAccessToken,g as resetRefreshState};
1
+ import{Logger as e}from"../logger/logger.js";import{ACCESS_TOKEN_KEY as t,REFRESH_TOKEN_KEY as n,clearSession as r,getToken as i,storeToken as a}from"../auth/authStore.js";import{withCrossTabRefreshLock as o}from"./crossTabRefreshLock.js";import s from"./publicClient.js";import c from"axios";var l,u,d=e=>{l=e},f=e=>{u=e},p=null,m=[],h=(e,t=null)=>{m.forEach(({resolve:n,reject:r})=>{e?r(e):t!==null&&n(t)}),m=[]},g=()=>{p=null,h(Error(`[@webority/web-core] Session ended`))},_=e=>e instanceof Error?e.message:typeof e==`string`?e:`Unknown error`,v=()=>{if(!l||!u)throw Error(`User package store is not initialized. Call initUserAuth first.`);return{store:l,auth:u}},y=()=>u?.accountId,b=()=>u?.refresh.path,x=async()=>{let e=v();await e.store.dispatch(e.auth.logout())},S=`X-Sign-Out-Reason`,C=e=>{let t=u?.signOutReasonHeader===void 0?S:u.signOutReasonHeader;if(!t||!e||typeof e!=`object`)return null;let n=e.get,r=typeof n==`function`?n.call(e,t):e[t.toLowerCase()];return typeof r==`string`&&r.trim()!==``?r.trim():null},w=async(e,t)=>{await r(e,{signOutReason:t}),await x()},T=()=>p!==null,E=0,D=Promise.resolve(),O=null,k=()=>{E===0&&(D=new Promise(e=>{O=e})),E+=1;let e=!1;return()=>{e||(e=!0,--E,E===0&&(O?.(),O=null))}},A=()=>E===0?Promise.resolve():D,j=async r=>{let i;try{i=(u?.refresh.readTokens??L)(r)}catch(t){e.error(`A session-replacing response carried no tokens:`,_(t));return}await a(t,i.accessToken,i.accessTokenExpiry),await a(n,i.refreshToken,i.refreshTokenExpiry)},M=async e=>{let t=(await i(`accessToken`))?.token??null;return t!==null&&t!==e?t:null},N=()=>new Promise((e,t)=>{m.push({resolve:e,reject:t})}),P=class e extends Error{constructor(t){super(t),this.name=`SessionEndedError`,Object.setPrototypeOf(this,e.prototype)}},F=e=>e instanceof P||c.isAxiosError(e)&&(e.response?.status===400||e.response?.status===401),I=(e,t)=>{if(typeof e!=`string`||e===``)throw Error(`The refresh response has no "${t}".`);return e},L=e=>{let t=e??{};return{accessToken:I(t.accessToken,`accessToken`),accessTokenExpiry:I(t.accessTokenExpiration,`accessTokenExpiration`),refreshToken:I(t.refreshToken,`refreshToken`),refreshTokenExpiry:I(t.refreshTokenExpiration,`refreshTokenExpiration`)}},R=async r=>{let o=await i(n);if(!o?.token)throw new P(`No refresh token available`);if(Date.parse(o.expiry)<=Date.now())throw new P(`Refresh token expired`);let c={accessToken:(await i(`accessToken`))?.token??null,refreshToken:o.token},l=r.body?await r.body(c):c,u=r.headers?await r.headers():void 0;e.info(`Refreshing the access token`);let{data:d}=await s.post(r.path,l,u?{headers:u}:void 0),f=(r.readTokens??L)(d);await A();let p=(await i(`refreshToken`))?.token===o.token?null:await i(t);return p?.token?(e.info(`Keeping the session that replaced this one during the refresh`),p.token):(await a(t,f.accessToken,f.accessTokenExpiry),await a(n,f.refreshToken,f.refreshTokenExpiry),e.info(`Access token refreshed`),f.accessToken)},z=30*1e3,B=async(n,r)=>{let a=await i(t);return a?.token&&a.token!==r&&Date.parse(a.expiry)>Date.now()+z?(e.info(`Adopting the access token another tab refreshed`),a.token):R(n)},V=()=>{if(p)return p;let t=(async()=>{let t=null;try{let{refresh:e}=v().auth;t=(await i(`accessToken`))?.token??null;let n=await o(()=>B(e,t));return h(null,n),n}catch(n){if(F(n)){await A();let n=await M(t);if(n)return e.info(`The session was replaced during the refresh; continuing with the new one`),h(null,n),n}let r=c.isAxiosError(n)?n.response?.data:void 0;throw e.error(`Token refresh failed:`,r??_(n)),h(n,null),F(n)&&await w(`rejected`,c.isAxiosError(n)?C(n.response?.headers):null),n}})().finally(()=>{p===t&&(p=null)});return p=t,t};export{S as SIGN_OUT_REASON_HEADER,k as beginSessionReplacement,M as currentTokenIfReplaced,x as dispatchLogout,w as endSessionAndLogout,y as getAccountIdProvider,_ as getErrorMessage,b as getRefreshPath,d as injectStore,f as injectUserAuth,T as isRefreshInFlight,N as queueBehindRefresh,C as readSignOutReason,V as refreshAccessToken,g as resetRefreshState,j as storeReplacementSession,A as waitForSessionReplacement};
@@ -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=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
+ 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{applyClientHeaders as a,readAccountId as o,stampAccountId as s}from"./clientHeaders.js";import{readMinAppVersion as c}from"./minAppVersion.js";import{sendFormDataAsMultipart as l}from"./multipart.js";import{applyRequestDefaults as u}from"./requestDefaults.js";import{responseBodyText as d}from"./responseBodyText.js";import{WorkspaceChangedError as f,isWorkspaceChangedResponse as p,workspaceChangedFromResponse as m}from"./workspaceChanged.js";import{attachRetry as h}from"./retry.js";import{SIGN_OUT_REASON_HEADER as g,beginSessionReplacement as _,currentTokenIfReplaced as v,endSessionAndLogout as y,getAccountIdProvider as b,getErrorMessage as x,getRefreshPath as S,injectStore as C,injectUserAuth as w,isRefreshInFlight as T,queueBehindRefresh as E,readSignOutReason as D,refreshAccessToken as O,resetRefreshState as k,storeReplacementSession as A,waitForSessionReplacement as j}from"./tokenRefresh.js";import M,{AxiosHeaders as N}from"axios";var P=class e extends Error{constructor(t=`Request blocked: device is offline`){super(t),this.name=`RequestOfflineError`,Object.setPrototypeOf(this,e.prototype)}},F=class e extends Error{constructor(){super(`No access token available`),this.name=`NoAccessTokenError`,Object.setPrototypeOf(this,e.prototype)}},I={TERMINATED:`SESSION_TERMINATED`,EXPIRED:`SESSION_EXPIRED`},L=new Set(Object.values(I)),R=`x-security-stamp-invalid`,z=e=>{if(!e||typeof e!=`object`)return!1;let t=e.get,n=typeof t==`function`?t.call(e,R):e[R];return String(n).toLowerCase()===`true`},B=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`&&L.has(e))return e},V=e=>{e?._endReplacement?.(),e&&(e._endReplacement=void 0)},H=e=>{let t=e.headers instanceof N?e.headers.get(`Authorization`):void 0;return typeof t==`string`&&t.startsWith(`Bearer `)?t.slice(7):null},U=M.create({headers:{"Content-Type":`application/json`}});c(U),h(U);var W=(e,t)=>{e.headers instanceof N?e.headers.set(`Authorization`,`Bearer ${t}`):e.headers=N.from({...e.headers,Authorization:`Bearer ${t}`})},G=e=>{let t=S();return!!t&&!!e?.includes(t)};i(U),u(U),l(U),a(U),U.interceptors.request.use(async i=>{if(V(i),(await r()).isConnected===!1)return e.warn(`Request blocked: browser is offline. URL: ${i.url}`),Promise.reject(new P);try{if(G(i.url))return i;await s(i,b());let r=await n(t);if(!r?.token)throw new F;let a=Date.now();return Date.parse(r.expiry)<=a+30*1e3?(e.info(`Access token expired or about to expire, refreshing before request`),W(i,await O())):W(i,r.token),i.replacesSession&&(i._endReplacement=_()),i}catch(t){return t instanceof F?e.info(`Request not sent: signed out. URL: ${i.url}`):e.error(`Failed to attach token:`,x(t)),Promise.reject(t)}},t=>(e.error(`Request error:`,x(t)),Promise.reject(t))),U.interceptors.response.use(async e=>{let t=e.config;if(t._endReplacement)try{await A(e.data)}finally{V(t)}return e},async t=>{if(V(t?.config),M.isCancel(t)||t instanceof P||t instanceof F||t instanceof f)return Promise.reject(t);if(!M.isAxiosError(t))return e.error(`Non-axios error in response interceptor: ${x(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(p(n))return e.info(`Request refused: the workspace changed after it was made. URL: ${r?.url}`),Promise.reject(m(n));if(n.response.status===401&&r&&!r._replayedWithCurrentToken&&!r.replacesSession&&!G(r.url)&&(await j(),await v(H(r))))return(r._accountId??null)===await o(b())?(e.info(`Session changed while the request was in flight; sending it again. URL: ${r.url}`),r._replayedWithCurrentToken=!0,U(r)):(e.info(`Session moved to another workspace while the request was in flight; not sending it again. URL: ${r.url}`),Promise.reject(new f(void 0,{config:r,cause:t})));if(n.response.status===401&&r&&!r._retry){if(G(r.url))return Promise.reject(t);let i=D(n.response.headers),a=B(n.response.data)??(z(n.response.headers)||i!==null?I.TERMINATED:void 0);if(a)return e.warn(`Session ${a}, logging out without refresh. URL: ${r.url}`),k(),await y(`terminated`,i),Promise.reject(t);if(e.warn(`Unauthorized URL: ${r.url}`),r._retry=!0,T())return E().then(t=>(e.info(`Retrying request after token refresh:`,r.url),W(r,t),U(r))).catch(t=>(t instanceof f||e.error(`Failed to retry request after token refresh:`,x(t)),Promise.reject(t)));try{return W(r,await O()),U(r)}catch(e){return Promise.reject(e)}}return r?.skipErrorLog||e.error(`Response error: ${d(n.response?.data,n.message)}, URL: ${r?.url}, Status: ${n.response.status}`),Promise.reject(t)});export{P as RequestOfflineError,R as SESSION_INVALID_HEADER,I as SESSION_TERMINATION_CODES,g as SIGN_OUT_REASON_HEADER,U as default,C as injectStore,w as injectUserAuth,k as resetRefreshState};
@@ -0,0 +1 @@
1
+ var e=`workspace_changed`,t=e=>e&&typeof e==`object`&&!Array.isArray(e)?e:null,n=class t extends Error{code=e;response;config;cause;constructor(e=`This was started in another workspace. Switch back to it and try again.`,n={}){super(e),this.name=`WorkspaceChangedError`,this.response=n.response??{status:409,data:{code:`workspace_changed`,message:e},headers:{}},this.config=n.config,this.cause=n.cause,Object.setPrototypeOf(this,t.prototype)}},r=e=>e instanceof n||typeof e==`object`&&!!e&&(e.name===`WorkspaceChangedError`||e.code===`workspace_changed`),i=e=>e.response?.status===409&&t(e.response.data)?.code===`workspace_changed`,a=e=>{let r=t(e.response?.data)?.message;return new n(typeof r==`string`&&r.trim()!==``?r:void 0,{response:e.response,config:e.config,cause:e})};export{e as WORKSPACE_CHANGED_CODE,n as WorkspaceChangedError,r as isWorkspaceChangedError,i as isWorkspaceChangedResponse,a as workspaceChangedFromResponse};
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{DEFAULT_CORRELATION_ID_HEADER as e,DEFAULT_LOG_LEVEL as t,DEFAULT_RETRIES as n,DEFAULT_TIMEOUT_MS as r,getConfig as i,getConfigValue as a,resetConfig as o,setConfig as s}from"./config/index.js";import{Logger as c,configureLogger as l,getDeviceContext as u,setDeviceContext as d,setRemoteLogger as f}from"./logger/logger.js";import{decodeJWT as p,getJWTExpiry as m,isJWTExpired as h}from"./auth/jwt.js";import{ACCESS_TOKEN_KEY as g,LEGACY_REFRESH_TOKEN_LIFETIME_MS as _,REFRESH_TOKEN_KEY as v,SESSION_USER_KEY as y,addSessionEndedListener as b,clearSession as x,getKeepSignedIn as S,getSessionUser as C,getToken as w,hasSession as T,hasSessionEndedInTab as E,removeSessionUser as D,removeToken as O,setKeepSignedIn as k,setLegacyTokenKeys as A,storeSessionUser as j,storeToken as M}from"./auth/authStore.js";import{addNetworkStatusListener as N,getNetworkStatus as P,setNetworkStatusImplementation as F,useNetworkStatus as I}from"./network/networkStatus.js";import{ACCOUNT_ID_HEADER as L,CLIENT_BUILD_HEADER as R,CLIENT_KIND as z,CLIENT_KIND_HEADER as B,getClientBuild as V}from"./api/clientHeaders.js";import{MIN_APP_VERSION_HEADER as H,addMinAppVersionListener as U,getMinAppVersion as W}from"./api/minAppVersion.js";import{applyRequestDefaults as G,newCorrelationId as K}from"./api/requestDefaults.js";import{IDEMPOTENCY_KEY_HEADER as q,attachRetry as J,isNeverSentError as Y,isRetryableFailure as X,isRetryableStatus as Z,retryAfterMs as Q}from"./api/retry.js";import{REFRESH_LOCK_NAME as $,withCrossTabRefreshLock as ee}from"./api/crossTabRefreshLock.js";import te from"./api/publicClient.js";import{SIGN_OUT_REASON_HEADER as ne,injectStore as re,injectUserAuth as ie,refreshAccessToken as ae,resetRefreshState as oe}from"./api/tokenRefresh.js";import se,{RequestOfflineError as ce,SESSION_TERMINATION_CODES as le}from"./api/userClient.js";import{initUserAuth as ue}from"./initUserAuth.js";import{CONTRACT_ERROR_CODE as de,NETWORK_CODE as fe,OFFLINE_MESSAGE as pe,REQUEST_CANCELED_CODE as me,SERVER_FAILURE_MESSAGE as he,TIMEOUT_CODE as ge,TIMEOUT_MESSAGE as _e,UNEXPECTED_MESSAGE as ve,apiErrorMessage as ye,extractApiError as be,isApiError as xe,isNoResponse as Se,toFieldPath as Ce}from"./errors/extractApiError.js";import{configureFormatters as we,getFormatterConfig as Te}from"./formatters/formatConfig.js";import{formatCurrency as Ee}from"./formatters/currency.js";import{addDays as De}from"./formatters/dateMath.js";import{formatDate as Oe,formatDateTime as ke,formatMinor as Ae,formatNumber as je,formatPercent as Me,formatRelative as Ne,formatTime as Pe}from"./formatters/house.js";import{initWebority as Fe}from"./initSDK.js";import{createApiBaseQuery as Ie}from"./query/apiBaseQuery.js";import{APP_API_DEFAULTS as Le,createAppApi as Re}from"./query/createAppApi.js";import{setupApiListeners as ze}from"./query/listeners.js";import{buildListQuery as Be,getNextPageParam as Ve,getPreviousPageParam as He,hasNextPage as Ue,hasPreviousPage as We}from"./query/paging.js";import{LIST_TAG_ID as Ge,itemTag as Ke,listTag as qe,providesList as Je,providesPagedList as Ye}from"./query/tags.js";import{errorToastMiddleware as Xe,isSilentRejection as Ze}from"./redux/errorToastMiddleware.js";import{createRootReducer as Qe,resetState as $e}from"./redux/rootReducer.js";import{SERIALIZABLE_IGNORED_ACTION_PATHS as et}from"./redux/serializableCheck.js";import{hasRecentlyReloaded as tt,importWithReload as nt,isChunkLoadError as rt,lazyWithReload as it,markReloaded as at}from"./router/chunkReload.js";import{hasPermission as ot}from"./router/permissions.js";import{RETURN_TO_PARAM as st,buildLoginPath as ct,getReturnTo as lt,toSafeReturnTo as ut}from"./router/returnTo.js";import{PublicOnly as dt,RequireAuth as ft,RequirePermission as pt}from"./router/guards.js";import{publicOnly as mt,requireAuth as ht,requirePermission as gt,withGuards as _t}from"./router/loaders.js";import{classifyRouteError as vt,useRouteErrorReport as yt}from"./router/routeError.js";import{Storage as bt,isStorageAvailable as xt,setStorageImplementation as St,syncStorage as Ct}from"./storage/index.js";import{isBuildBelowMinimum as wt,parseBuildNumber as Tt}from"./versionCheck/buildNumber.js";import{installReloadOnNavigate as Et,isUpdateAvailable as Dt,useVersionCheck as Ot}from"./versionCheck/index.js";export{g as ACCESS_TOKEN_KEY,L as ACCOUNT_ID_HEADER,Le as APP_API_DEFAULTS,R as CLIENT_BUILD_HEADER,z as CLIENT_KIND,B as CLIENT_KIND_HEADER,de as CONTRACT_ERROR_CODE,e as DEFAULT_CORRELATION_ID_HEADER,t as DEFAULT_LOG_LEVEL,n as DEFAULT_RETRIES,r as DEFAULT_TIMEOUT_MS,q as IDEMPOTENCY_KEY_HEADER,_ as LEGACY_REFRESH_TOKEN_LIFETIME_MS,Ge as LIST_TAG_ID,c as Logger,H as MIN_APP_VERSION_HEADER,fe as NETWORK_CODE,pe as OFFLINE_MESSAGE,dt as PublicOnly,$ as REFRESH_LOCK_NAME,v as REFRESH_TOKEN_KEY,me as REQUEST_CANCELED_CODE,st as RETURN_TO_PARAM,ce as RequestOfflineError,ft as RequireAuth,pt as RequirePermission,et as SERIALIZABLE_IGNORED_ACTION_PATHS,he as SERVER_FAILURE_MESSAGE,le as SESSION_TERMINATION_CODES,y as SESSION_USER_KEY,ne as SIGN_OUT_REASON_HEADER,bt as Storage,ge as TIMEOUT_CODE,_e as TIMEOUT_MESSAGE,ve as UNEXPECTED_MESSAGE,De as addDays,U as addMinAppVersionListener,N as addNetworkStatusListener,b as addSessionEndedListener,ye as apiErrorMessage,G as applyRequestDefaults,J as attachRetry,Be as buildListQuery,ct as buildLoginPath,vt as classifyRouteError,x as clearSession,we as configureFormatters,l as configureLogger,Ie as createApiBaseQuery,Re as createAppApi,Qe as createRootReducer,p as decodeJWT,Xe as errorToastMiddleware,be as extractApiError,Ee as formatCurrency,Oe as formatDate,ke as formatDateTime,Ae as formatMinor,je as formatNumber,Me as formatPercent,Ne as formatRelative,Pe as formatTime,V as getClientBuild,i as getConfig,a as getConfigValue,u as getDeviceContext,Te as getFormatterConfig,m as getJWTExpiry,S as getKeepSignedIn,W as getMinAppVersion,P as getNetworkStatus,Ve as getNextPageParam,He as getPreviousPageParam,lt as getReturnTo,C as getSessionUser,w as getToken,Ue as hasNextPage,ot as hasPermission,We as hasPreviousPage,tt as hasRecentlyReloaded,T as hasSession,E as hasSessionEndedInTab,nt as importWithReload,ue as initUserAuth,Fe as initWebority,re as injectStore,ie as injectUserAuth,Et as installReloadOnNavigate,xe as isApiError,wt as isBuildBelowMinimum,rt as isChunkLoadError,h as isJWTExpired,Y as isNeverSentError,Se as isNoResponse,X as isRetryableFailure,Z as isRetryableStatus,Ze as isSilentRejection,xt as isStorageAvailable,Dt as isUpdateAvailable,Ke as itemTag,it as lazyWithReload,qe as listTag,at as markReloaded,K as newCorrelationId,Tt as parseBuildNumber,Je as providesList,Ye as providesPagedList,te as publicClient,mt as publicOnly,ae as refreshAccessToken,D as removeSessionUser,O as removeToken,ht as requireAuth,gt as requirePermission,o as resetConfig,oe as resetRefreshState,$e as resetState,Q as retryAfterMs,s as setConfig,d as setDeviceContext,k as setKeepSignedIn,A as setLegacyTokenKeys,F as setNetworkStatusImplementation,f as setRemoteLogger,St as setStorageImplementation,ze as setupApiListeners,j as storeSessionUser,M as storeToken,Ct as syncStorage,Ce as toFieldPath,ut as toSafeReturnTo,I as useNetworkStatus,yt as useRouteErrorReport,Ot as useVersionCheck,se as userClient,ee as withCrossTabRefreshLock,_t as withGuards};
1
+ import{DEFAULT_CORRELATION_ID_HEADER as e,DEFAULT_LOG_LEVEL as t,DEFAULT_RETRIES as n,DEFAULT_TIMEOUT_MS as r,getConfig as i,getConfigValue as a,resetConfig as o,setConfig as s}from"./config/index.js";import{Logger as c,configureLogger as l,getDeviceContext as u,setDeviceContext as d,setRemoteLogger as f}from"./logger/logger.js";import{decodeJWT as p,getJWTExpiry as m,isJWTExpired as h}from"./auth/jwt.js";import{ACCESS_TOKEN_KEY as g,LEGACY_REFRESH_TOKEN_LIFETIME_MS as _,REFRESH_TOKEN_KEY as v,SESSION_USER_KEY as y,addSessionEndedListener as b,clearSession as x,getKeepSignedIn as S,getSessionUser as C,getToken as w,hasSession as T,hasSessionEndedInTab as E,removeSessionUser as D,removeToken as O,setKeepSignedIn as k,setLegacyTokenKeys as A,storeSessionUser as j,storeToken as M}from"./auth/authStore.js";import{addNetworkStatusListener as N,getNetworkStatus as P,setNetworkStatusImplementation as F,useNetworkStatus as I}from"./network/networkStatus.js";import{ACCOUNT_ID_HEADER as L,CLIENT_BUILD_HEADER as R,CLIENT_KIND as z,CLIENT_KIND_HEADER as B,getClientBuild as V}from"./api/clientHeaders.js";import{MIN_APP_VERSION_HEADER as H,addMinAppVersionListener as U,getMinAppVersion as W}from"./api/minAppVersion.js";import{applyRequestDefaults as G,newCorrelationId as K}from"./api/requestDefaults.js";import{WORKSPACE_CHANGED_CODE as q,WorkspaceChangedError as J,isWorkspaceChangedError as Y}from"./api/workspaceChanged.js";import{IDEMPOTENCY_KEY_HEADER as X,attachRetry as Z,isNeverSentError as Q,isRetryableFailure as $,isRetryableStatus as ee,retryAfterMs as te}from"./api/retry.js";import{REFRESH_LOCK_NAME as ne,withCrossTabRefreshLock as re}from"./api/crossTabRefreshLock.js";import ie from"./api/publicClient.js";import{SIGN_OUT_REASON_HEADER as ae,injectStore as oe,injectUserAuth as se,refreshAccessToken as ce,resetRefreshState as le}from"./api/tokenRefresh.js";import ue,{RequestOfflineError as de,SESSION_TERMINATION_CODES as fe}from"./api/userClient.js";import{initUserAuth as pe}from"./initUserAuth.js";import{CONTRACT_ERROR_CODE as me,NETWORK_CODE as he,OFFLINE_MESSAGE as ge,REQUEST_CANCELED_CODE as _e,SERVER_FAILURE_MESSAGE as ve,TIMEOUT_CODE as ye,TIMEOUT_MESSAGE as be,UNEXPECTED_MESSAGE as xe,apiErrorMessage as Se,extractApiError as Ce,isApiError as we,isNoResponse as Te,toFieldPath as Ee}from"./errors/extractApiError.js";import{configureFormatters as De,getFormatterConfig as Oe}from"./formatters/formatConfig.js";import{formatCurrency as ke}from"./formatters/currency.js";import{addDays as Ae}from"./formatters/dateMath.js";import{formatDate as je,formatDateTime as Me,formatMinor as Ne,formatNumber as Pe,formatPercent as Fe,formatRelative as Ie,formatTime as Le}from"./formatters/house.js";import{initWebority as Re}from"./initSDK.js";import{createApiBaseQuery as ze}from"./query/apiBaseQuery.js";import{APP_API_DEFAULTS as Be,createAppApi as Ve}from"./query/createAppApi.js";import{setupApiListeners as He}from"./query/listeners.js";import{buildListQuery as Ue,getNextPageParam as We,getPreviousPageParam as Ge,hasNextPage as Ke,hasPreviousPage as qe}from"./query/paging.js";import{LIST_TAG_ID as Je,itemTag as Ye,listTag as Xe,providesList as Ze,providesPagedList as Qe}from"./query/tags.js";import{errorToastMiddleware as $e,isSilentRejection as et}from"./redux/errorToastMiddleware.js";import{createRootReducer as tt,resetState as nt}from"./redux/rootReducer.js";import{SERIALIZABLE_IGNORED_ACTION_PATHS as rt}from"./redux/serializableCheck.js";import{hasRecentlyReloaded as it,importWithReload as at,isChunkLoadError as ot,lazyWithReload as st,markReloaded as ct}from"./router/chunkReload.js";import{hasPermission as lt}from"./router/permissions.js";import{RETURN_TO_PARAM as ut,buildLoginPath as dt,getReturnTo as ft,toSafeReturnTo as pt}from"./router/returnTo.js";import{PublicOnly as mt,RequireAuth as ht,RequirePermission as gt}from"./router/guards.js";import{publicOnly as _t,requireAuth as vt,requirePermission as yt,withGuards as bt}from"./router/loaders.js";import{classifyRouteError as xt,useRouteErrorReport as St}from"./router/routeError.js";import{Storage as Ct,isStorageAvailable as wt,setStorageImplementation as Tt,syncStorage as Et}from"./storage/index.js";import{isBuildBelowMinimum as Dt,parseBuildNumber as Ot}from"./versionCheck/buildNumber.js";import{installReloadOnNavigate as kt,isUpdateAvailable as At,useVersionCheck as jt}from"./versionCheck/index.js";export{g as ACCESS_TOKEN_KEY,L as ACCOUNT_ID_HEADER,Be as APP_API_DEFAULTS,R as CLIENT_BUILD_HEADER,z as CLIENT_KIND,B as CLIENT_KIND_HEADER,me as CONTRACT_ERROR_CODE,e as DEFAULT_CORRELATION_ID_HEADER,t as DEFAULT_LOG_LEVEL,n as DEFAULT_RETRIES,r as DEFAULT_TIMEOUT_MS,X as IDEMPOTENCY_KEY_HEADER,_ as LEGACY_REFRESH_TOKEN_LIFETIME_MS,Je as LIST_TAG_ID,c as Logger,H as MIN_APP_VERSION_HEADER,he as NETWORK_CODE,ge as OFFLINE_MESSAGE,mt as PublicOnly,ne as REFRESH_LOCK_NAME,v as REFRESH_TOKEN_KEY,_e as REQUEST_CANCELED_CODE,ut as RETURN_TO_PARAM,de as RequestOfflineError,ht as RequireAuth,gt as RequirePermission,rt as SERIALIZABLE_IGNORED_ACTION_PATHS,ve as SERVER_FAILURE_MESSAGE,fe as SESSION_TERMINATION_CODES,y as SESSION_USER_KEY,ae as SIGN_OUT_REASON_HEADER,Ct as Storage,ye as TIMEOUT_CODE,be as TIMEOUT_MESSAGE,xe as UNEXPECTED_MESSAGE,q as WORKSPACE_CHANGED_CODE,J as WorkspaceChangedError,Ae as addDays,U as addMinAppVersionListener,N as addNetworkStatusListener,b as addSessionEndedListener,Se as apiErrorMessage,G as applyRequestDefaults,Z as attachRetry,Ue as buildListQuery,dt as buildLoginPath,xt as classifyRouteError,x as clearSession,De as configureFormatters,l as configureLogger,ze as createApiBaseQuery,Ve as createAppApi,tt as createRootReducer,p as decodeJWT,$e as errorToastMiddleware,Ce as extractApiError,ke as formatCurrency,je as formatDate,Me as formatDateTime,Ne as formatMinor,Pe as formatNumber,Fe as formatPercent,Ie as formatRelative,Le as formatTime,V as getClientBuild,i as getConfig,a as getConfigValue,u as getDeviceContext,Oe as getFormatterConfig,m as getJWTExpiry,S as getKeepSignedIn,W as getMinAppVersion,P as getNetworkStatus,We as getNextPageParam,Ge as getPreviousPageParam,ft as getReturnTo,C as getSessionUser,w as getToken,Ke as hasNextPage,lt as hasPermission,qe as hasPreviousPage,it as hasRecentlyReloaded,T as hasSession,E as hasSessionEndedInTab,at as importWithReload,pe as initUserAuth,Re as initWebority,oe as injectStore,se as injectUserAuth,kt as installReloadOnNavigate,we as isApiError,Dt as isBuildBelowMinimum,ot as isChunkLoadError,h as isJWTExpired,Q as isNeverSentError,Te as isNoResponse,$ as isRetryableFailure,ee as isRetryableStatus,et as isSilentRejection,wt as isStorageAvailable,At as isUpdateAvailable,Y as isWorkspaceChangedError,Ye as itemTag,st as lazyWithReload,Xe as listTag,ct as markReloaded,K as newCorrelationId,Ot as parseBuildNumber,Ze as providesList,Qe as providesPagedList,ie as publicClient,_t as publicOnly,ce as refreshAccessToken,D as removeSessionUser,O as removeToken,vt as requireAuth,yt as requirePermission,o as resetConfig,le as resetRefreshState,nt as resetState,te as retryAfterMs,s as setConfig,d as setDeviceContext,k as setKeepSignedIn,A as setLegacyTokenKeys,F as setNetworkStatusImplementation,f as setRemoteLogger,Tt as setStorageImplementation,He as setupApiListeners,j as storeSessionUser,M as storeToken,Et as syncStorage,Ee as toFieldPath,pt as toSafeReturnTo,I as useNetworkStatus,St as useRouteErrorReport,jt as useVersionCheck,ue as userClient,re as withCrossTabRefreshLock,bt as withGuards};
@@ -1 +1 @@
1
- import{extractApiError as e}from"../errors/extractApiError.js";var t=e=>typeof e==`object`&&!!e&&!Array.isArray(e),n=e=>t(e)&&e.silent===!0,r=e=>n(e)||n(e.arg)||t(e.arg)&&n(e.arg.originalArgs)||n(e.baseQueryMeta);function i(n){let{showToast:i,ignoreCodes:a=[],ignoreStatuses:o=[401],dedupeMs:s=4e3,onRejected:c}=n,l=new Map,u=e=>{if(s<=0)return!0;let t=Date.now();for(let[e,n]of l)t-n>=s&&l.delete(e);return l.has(e)?!1:(l.set(e,t),!0)};return n=>s=>l=>{let d=s(l);if(!t(l)||!t(l.meta))return d;let f=l.meta;if(f.requestStatus!==`rejected`||f.aborted===!0||f.condition===!0)return d;let p=e(f.rejectedWithValue===!0?l.payload:l.error);if(p.code===`request_canceled`)return d;let m=c?.(l,p,n);return m===`silent`||r(f)||m!==`toast`&&(p.code!==null&&a.includes(p.code)||o.includes(p.status))||u(p.message)&&i(p.message,p),d}}export{i as errorToastMiddleware,r as isSilentRejection};
1
+ import"../api/workspaceChanged.js";import{extractApiError as e}from"../errors/extractApiError.js";var t=e=>typeof e==`object`&&!!e&&!Array.isArray(e),n=e=>t(e)&&e.silent===!0,r=e=>n(e)||n(e.arg)||t(e.arg)&&n(e.arg.originalArgs)||n(e.baseQueryMeta);function i(n){let{showToast:i,ignoreCodes:a=[],ignoreStatuses:o=[401],dedupeMs:s=4e3,onRejected:c}=n,l=new Map,u=e=>{if(s<=0)return!0;let t=Date.now();for(let[e,n]of l)t-n>=s&&l.delete(e);return l.has(e)?!1:(l.set(e,t),!0)};return n=>s=>l=>{let d=s(l);if(!t(l)||!t(l.meta))return d;let f=l.meta;if(f.requestStatus!==`rejected`||f.aborted===!0||f.condition===!0)return d;let p=e(f.rejectedWithValue===!0?l.payload:l.error);if(p.code===`request_canceled`)return d;let m=c?.(l,p,n);return m===`silent`||r(f)||m!==`toast`&&(p.code!==null&&(p.code===`workspace_changed`||a.includes(p.code))||o.includes(p.status))||u(p.message)&&i(p.message,p),d}}export{i as errorToastMiddleware,r as isSilentRejection};
@@ -1 +1 @@
1
- import{Logger as e}from"../logger/logger.js";import{lazy as t}from"react";var n=`webority.chunkReloadAt`,r=6e4,i=[/failed to fetch dynamically imported module/i,/error loading dynamically imported module/i,/importing a module script failed/i,/unable to preload css/i,/loading (css )?chunk [\w-]+ failed/i],a=e=>{if(!e||typeof e!=`object`)return!1;let{name:t,message:n}=e;return t===`ChunkLoadError`||typeof n==`string`&&i.some(e=>e.test(n))},o=()=>{try{let e=Number(globalThis.sessionStorage.getItem(n));return e>0&&Date.now()-e<r}catch{return!0}},s=()=>{try{return globalThis.sessionStorage.setItem(n,String(Date.now())),!0}catch{return!1}},c=()=>!o()&&s(),l=t=>()=>t().catch(t=>{if(!a(t)||!c())throw t;return e.warn(`[router] a code chunk failed to load; reloading once for the new build`,t),globalThis.location.reload(),new Promise(()=>void 0)}),u=e=>()=>l(e)().catch(e=>({Component(){throw e}})),d=e=>t(l(e));export{o as hasRecentlyReloaded,u as importWithReload,a as isChunkLoadError,d as lazyWithReload,s as markReloaded};
1
+ import{Logger as e}from"../logger/logger.js";import{lazy as t,useEffect as n,useState as r}from"react";var i=`webority.chunkReload`,a=6e4,o=[/failed to fetch dynamically imported module/i,/error loading dynamically imported module/i,/importing a module script failed/i,/unable to preload css/i,/loading (css )?chunk [\w-]+ failed/i],s=e=>{if(!e||typeof e!=`object`)return!1;let{name:t,message:n}=e;return t===`ChunkLoadError`||typeof n==`string`&&o.some(e=>e.test(n))},c=()=>{let{pathname:e,search:t,hash:n}=globalThis.location;return`${e}${t}${n}`},l=e=>typeof e==`object`&&!!e&&typeof e.target==`string`&&typeof e.at==`number`,u=e=>{if(!e)return null;try{let t=JSON.parse(e);return l(t)?t:null}catch{return null}},d=()=>{try{return u(globalThis.sessionStorage.getItem(i))}catch{return`blocked`}},f=e=>{let t=d();return t===`blocked`?`blocked`:!t||t.target!==e||Date.now()-t.at>=a?`none`:t.at>globalThis.performance.timeOrigin?`pending`:`reloaded`},p=()=>f(c())!==`none`,m=()=>{try{let e={target:c(),at:Date.now()};return globalThis.sessionStorage.setItem(i,JSON.stringify(e)),!0}catch{return!1}},h=()=>{let e=d();if(!(!e||e===`blocked`||e.target===c()))try{globalThis.sessionStorage.removeItem(i)}catch{}},g=t=>{let n=f(c());return n===`pending`?`reloading`:n===`none`?m()?(e.warn(`[router] a code chunk failed to load; reloading once for the new build`,t),globalThis.location.reload(),`reloading`):`blocked`:n},_=e=>(h(),e),v=e=>({Component(){throw e}}),y=e=>({action(){throw e},Component(){let[t,i]=r(()=>f(c()));if(n(()=>{t===`none`&&g(e)===`blocked`&&i(`blocked`)},[t,e]),t===`reloaded`||t===`blocked`)throw e;return null}}),b=e=>()=>e().then(_,e=>s(e)?y(e):v(e)),x=e=>t(()=>e().then(_,e=>{if(s(e)&&g(e)===`reloading`)return new Promise(()=>void 0);throw e}));export{p as hasRecentlyReloaded,b as importWithReload,s as isChunkLoadError,x as lazyWithReload,m as markReloaded,g as reloadForChunkError};
@@ -1 +1 @@
1
- import{Logger as e}from"../logger/logger.js";import{isChunkLoadError as t}from"./chunkReload.js";import{useEffect as n}from"react";import{isRouteErrorResponse as r,useRouteError as i}from"react-router";var a=e=>r(e)&&e.status===404?`not-found`:t(e)?`stale-build`:`error`,o=()=>{let t=i(),r=a(t);return n(()=>{r===`not-found`?e.breadcrumb(`Route not found`):r===`stale-build`?e.warn(`[router] a code chunk failed to load after the reload`,t):e.recordError(t,{source:`RouteError`})},[t,r]),{error:t,kind:r}};export{a as classifyRouteError,o as useRouteErrorReport};
1
+ import{Logger as e}from"../logger/logger.js";import{isChunkLoadError as t,reloadForChunkError as n}from"./chunkReload.js";import{useEffect as r}from"react";import{isRouteErrorResponse as i,useRouteError as a}from"react-router";var o=e=>i(e)&&e.status===404?`not-found`:t(e)?`stale-build`:`error`,s=()=>{let t=a(),i=o(t);return r(()=>{if(i===`not-found`)e.breadcrumb(`Route not found`);else if(i===`stale-build`){let r=n(t);r===`reloaded`?e.warn(`[router] a code chunk failed to load after the reload`,t):r===`blocked`&&e.warn(`[router] a code chunk failed to load; sessionStorage is unavailable, so the page is not reloaded`,t)}else e.recordError(t,{source:`RouteError`})},[t,i]),{error:t,kind:i}};export{o as classifyRouteError,s as useRouteErrorReport};
@@ -14,11 +14,22 @@ export declare const getClientBuild: () => string | null;
14
14
  */
15
15
  export declare const applyClientHeaders: (client: AxiosInstance) => void;
16
16
  export type AccountIdProvider = () => string | null | undefined | Promise<string | null | undefined>;
17
+ /** The provider's current account id; `null` when there is no provider or it names none. */
18
+ export declare const readAccountId: (provider: AccountIdProvider | undefined) => Promise<string | null>;
19
+ /** A request config carrying the account it was made in; axios copies it onto every resend. */
20
+ export interface AccountStampedConfig {
21
+ headers: {
22
+ get: (name: string) => unknown;
23
+ set: (name: string, value: string) => unknown;
24
+ };
25
+ /** `undefined` until the first send; `null` when it was made with no account. */
26
+ _accountId?: string | null;
27
+ }
17
28
  /**
18
- * Sets X-Account-Id from the app's provider on every send, overwriting, so a
19
- * request replayed after a refresh or an account switch carries the current
20
- * account rather than the one it first went out with. `null` sends none.
29
+ * Stamps a request with the account it is made in, once: the first send reads the provider and
30
+ * records the id on the config, and every resend (a retry, a replay after a refresh or a session
31
+ * replacement) sends that same id and never reads the provider again, so a request never runs in
32
+ * a workspace other than its own. `null` sends no header. A header the caller set itself (an
33
+ * outbox item naming its own workspace) wins over the provider and is recorded as the stamp.
21
34
  */
22
- export declare const applyAccountIdHeader: (headers: {
23
- set: (name: string, value: string) => unknown;
24
- }, provider: AccountIdProvider | undefined) => Promise<void>;
35
+ export declare const stampAccountId: (config: AccountStampedConfig, provider: AccountIdProvider | undefined) => Promise<void>;
@@ -12,3 +12,4 @@ export { attachRetry, IDEMPOTENCY_KEY_HEADER, isNeverSentError, isRetryableFailu
12
12
  export { refreshAccessToken } from "./tokenRefresh";
13
13
  export type { RefreshableStore, SessionTerminationCode } from "./userClient";
14
14
  export { default as userClient, injectStore, injectUserAuth, RequestOfflineError, resetRefreshState, SESSION_TERMINATION_CODES, SIGN_OUT_REASON_HEADER, } from "./userClient";
15
+ export { isWorkspaceChangedError, WORKSPACE_CHANGED_CODE, WorkspaceChangedError } from "./workspaceChanged";
@@ -43,6 +43,22 @@ export declare const readSignOutReason: (headers: unknown) => string | null;
43
43
  */
44
44
  export declare const endSessionAndLogout: (reason: SessionEndReason, signOutReason: string | null) => Promise<void>;
45
45
  export declare const isRefreshInFlight: () => boolean;
46
+ /** Opens the replacement window for one flagged request; call the returned function once it settles. */
47
+ export declare const beginSessionReplacement: () => (() => void);
48
+ /** Resolves at once when no session replacement is open, else once every open one has settled. */
49
+ export declare const waitForSessionReplacement: () => Promise<void>;
50
+ /**
51
+ * Stores the session a flagged request's response carries, read the way refresh responses are, before
52
+ * the replacement window closes, so a request waiting on the window retries with the new token.
53
+ * A body without tokens is logged and left to the caller.
54
+ */
55
+ export declare const storeReplacementSession: (data: unknown) => Promise<void>;
56
+ /**
57
+ * The stored access token when it is no longer `sentToken`: the session was refreshed or replaced
58
+ * while a request carrying `sentToken` was in flight, so that request's refusal says nothing about
59
+ * the session now held. Null when the token is unchanged or nothing is stored.
60
+ */
61
+ export declare const currentTokenIfReplaced: (sentToken: string | null) => Promise<string | null>;
46
62
  /** Wait for the in-flight refresh without joining it: resolved or rejected by processQueue. */
47
63
  export declare const queueBehindRefresh: () => Promise<string>;
48
64
  /**
@@ -0,0 +1,37 @@
1
+ import type { AxiosError, InternalAxiosRequestConfig } from "axios";
2
+ /** `code` of a request refused because the workspace changed after it was made. */
3
+ export declare const WORKSPACE_CHANGED_CODE = "workspace_changed";
4
+ interface WorkspaceChangedResponse {
5
+ status: number;
6
+ data: unknown;
7
+ headers: unknown;
8
+ }
9
+ /**
10
+ * A request was refused because it belongs to a workspace other than the one now held: the
11
+ * server answered 409 `workspace_changed`, or the user switched workspace while it was in flight
12
+ * so sending it again would run it in the new one. Never retried and never a logout.
13
+ *
14
+ * It carries the server's 409 as `response`, or one in the same shape when the client held the
15
+ * request back, so `extractApiError` reads `code: 'workspace_changed'` either way.
16
+ */
17
+ export declare class WorkspaceChangedError extends Error {
18
+ readonly code = "workspace_changed";
19
+ readonly response: WorkspaceChangedResponse;
20
+ readonly config?: InternalAxiosRequestConfig;
21
+ readonly cause?: unknown;
22
+ constructor(message?: string, options?: {
23
+ response?: WorkspaceChangedResponse;
24
+ config?: InternalAxiosRequestConfig;
25
+ cause?: unknown;
26
+ });
27
+ }
28
+ /**
29
+ * By instanceof, by name (a second copy of the package fails instanceof), and by code: the
30
+ * `ApiError` `extractApiError` or the RTK Query base query makes of it carries the code too.
31
+ */
32
+ export declare const isWorkspaceChangedError: (error: unknown) => boolean;
33
+ /** The API refused the request because the account it names is no longer the session's. */
34
+ export declare const isWorkspaceChangedResponse: (error: AxiosError) => boolean;
35
+ /** The rejection for a 409 `workspace_changed` answer: the server's message when it sent one. */
36
+ export declare const workspaceChangedFromResponse: (error: AxiosError) => WorkspaceChangedError;
37
+ export {};
@@ -37,8 +37,12 @@ export interface UserAuthOptions {
37
37
  refresh: TokenRefreshConfig;
38
38
  /**
39
39
  * The account the signed-in user is working in, sent as `X-Account-Id` on
40
- * every userClient request. Read on every send, so a switch applies at once.
41
- * Omit it in a product whose API takes the account from the token alone.
40
+ * every userClient request. Read once, when the request is made; a retry or
41
+ * replay sends the same id, so a request never runs in another workspace. A
42
+ * request whose session moved to another workspace while it was in flight is
43
+ * rejected with `WorkspaceChangedError` instead of being sent again, and the
44
+ * session is kept. Omit it in a product whose API takes the account from the
45
+ * token alone.
42
46
  */
43
47
  accountId?: AccountIdProvider;
44
48
  /**
@@ -14,7 +14,8 @@ import { type ApiError } from "../errors/extractApiError";
14
14
  *
15
15
  * Never toasted: an aborted thunk or a cancelled request (a screen unmounting), an RTK Query
16
16
  * condition skip (the data is already cached), a 401 by default (the auth layer owns a dead
17
- * session). Identical messages are de-duplicated for `dedupeMs`, because a screen that fires
17
+ * session), a `workspace_changed` rejection (the request belonged to a workspace the user left).
18
+ * Identical messages are de-duplicated for `dedupeMs`, because a screen that fires
18
19
  * four fetches and loses the network would otherwise stack four copies of one sentence.
19
20
  *
20
21
  * `onRejected` is the product's say over one rejection, run before the default rules:
@@ -2,29 +2,41 @@ import { type ComponentType, type LazyExoticComponent } from "react";
2
2
  /** Whether `error` is a lazy chunk that failed to load, the signature of a tab older than the deploy. */
3
3
  export declare const isChunkLoadError: (error: unknown) => boolean;
4
4
  /**
5
- * Whether a recovery reload happened inside the window (60 s). An app's own error boundary
6
- * asks this before reloading for a chunk error, so the router and the boundary share one
7
- * window and the page reloads at most once between them. Without sessionStorage there is no
8
- * way to stop a loop, so the answer is then `true`: never reload.
5
+ * Whether a recovery reload of the current page (path, query and hash) was claimed inside the
6
+ * window (60 s). An app's own error boundary asks this before reloading for a chunk error, so the
7
+ * router and the boundary share one claim and the page reloads at most once between them. Without
8
+ * sessionStorage there is no way to stop a loop, so the answer is then `true`: never reload.
9
9
  */
10
10
  export declare const hasRecentlyReloaded: () => boolean;
11
11
  /**
12
- * Record that a recovery reload is about to happen; call it right before `location.reload()`.
12
+ * Claim a recovery reload of the current page; call it right before `location.reload()`.
13
13
  * Returns false when it could not be recorded, in which case the caller must not reload.
14
14
  */
15
15
  export declare const markReloaded: () => boolean;
16
+ /**
17
+ * What a chunk failure on the current page leads to: `reloading` (the one reload has started, or
18
+ * this document already started it), `reloaded` (the page already had its reload) or `blocked`
19
+ * (sessionStorage is unavailable, so the page is not reloaded).
20
+ */
21
+ export type ChunkErrorOutcome = "reloading" | "reloaded" | "blocked";
22
+ /** Starts the one reload of the current page for a chunk error, unless it already had it. */
23
+ export declare const reloadForChunkError: (error: unknown) => ChunkErrorOutcome;
16
24
  /**
17
25
  * A route object's `lazy` (`lazy: importWithReload(() => import('./routes/projects'))`) that
18
- * reloads the page once on a stale-deploy chunk failure.
26
+ * reloads the page being opened once on a stale-deploy chunk failure.
19
27
  *
20
- * Any failure it cannot recover resolves to a route whose Component throws that error, so the
21
- * route's errorElement shows it. Rejecting instead leaves react-router holding the route as still
22
- * lazy, and it renders nothing below the nearest HydrateFallback: a blank page.
28
+ * Every failure resolves to a route rather than rejecting: a rejected `lazy` leaves react-router
29
+ * holding the route as still lazy, and it renders nothing below the nearest HydrateFallback, a
30
+ * blank page. A chunk failure's route reloads when it renders; a failure it cannot recover, and
31
+ * any other error, is thrown by the route's Component, so the route's errorElement shows it.
23
32
  */
24
33
  export declare const importWithReload: <T extends object>(importFn: () => Promise<T>) => (() => Promise<T | {
25
34
  Component: ComponentType;
26
35
  }>);
27
- /** `React.lazy` with the same one reload: a lazy screen that recovers a tab opened before a deploy. */
36
+ /**
37
+ * `React.lazy` with the same one reload: a lazy screen that recovers a tab opened before a deploy.
38
+ * It renders after the navigation has committed, so the reload is of the page it is on.
39
+ */
28
40
  export declare const lazyWithReload: <C extends ComponentType<any>>(importFn: () => Promise<{
29
41
  default: C;
30
42
  }>) => LazyExoticComponent<C>;
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * What went wrong, in the terms the error screen speaks:
3
3
  * - `not-found`: no route matched, or a loader threw a 404.
4
- * - `stale-build`: a code chunk would not load even after the one reload; a
5
- * newer build is live and a reload by the person is the way out.
4
+ * - `stale-build`: a code chunk would not load; a newer build is live. The
5
+ * page reloads once by itself, and if the chunk still will not load after
6
+ * that, a reload by the person is the way out.
6
7
  * - `error`: anything else.
7
8
  */
8
9
  export type RouteErrorKind = "not-found" | "stale-build" | "error";
@@ -13,8 +14,11 @@ export interface RouteErrorReport {
13
14
  }
14
15
  /**
15
16
  * For a route's `errorElement`: reads the error React Router caught, reports it
16
- * once (a missing page as a breadcrumb, a stale build as a warning, anything
17
- * else through `Logger.recordError`), and returns its kind for the error screen.
18
- * The error itself is for the logs; the screen shows no error text.
17
+ * once (a missing page as a breadcrumb, anything else but a stale build through
18
+ * `Logger.recordError`), and returns its kind for the error screen. A stale build
19
+ * reloads the page once, the page the navigation was going to, since the error
20
+ * element renders after the navigation has committed; only a failure after that
21
+ * reload is warned as one. The error itself is for the logs; the screen shows no
22
+ * error text.
19
23
  */
20
24
  export declare const useRouteErrorReport: () => RouteErrorReport;
@@ -3,5 +3,13 @@ declare module "axios" {
3
3
  interface AxiosRequestConfig {
4
4
  /** When true, the library's response-error interceptors do not log this request's failure. */
5
5
  skipErrorLog?: boolean;
6
+ /**
7
+ * The request ends the current session and answers with a new one in the refresh
8
+ * response's shape (a workspace switch, a reissue after a security change). userClient
9
+ * stores those tokens before the response resolves, and a request refused meanwhile for
10
+ * the old session waits for them and is sent again instead of ending the session, unless the
11
+ * new session is in another workspace: then it rejects with `WorkspaceChangedError`.
12
+ */
13
+ replacesSession?: boolean;
6
14
  }
7
15
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webority/web-core",
3
- "version": "0.51.2",
3
+ "version": "0.52.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",