@webority/web-core 0.92.0 → 0.93.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 +31 -20
- package/dist/api/clientHeaders.js +1 -1
- package/dist/api/designSample.js +1 -1
- package/dist/api/index.js +1 -1
- package/dist/api/minAppVersion.js +1 -1
- package/dist/api/publicClient.js +1 -1
- package/dist/api/requestDefaults.js +1 -1
- package/dist/api/retry.js +1 -1
- package/dist/api/tokenRefresh.js +1 -1
- package/dist/api/userClient.js +1 -1
- package/dist/api/workspaceChanged.js +1 -1
- package/dist/auth/authStore.js +1 -1
- package/dist/config/index.js +1 -1
- package/dist/errors/extractApiError.js +1 -1
- package/dist/index.js +1 -1
- package/dist/initSDK.js +1 -1
- package/dist/logger/logger.js +2 -2
- package/dist/types/api/clientHeaders.d.ts +4 -4
- package/dist/types/api/designSample.d.ts +1 -1
- package/dist/types/api/index.d.ts +3 -3
- package/dist/types/api/minAppVersion.d.ts +1 -1
- package/dist/types/api/requestDefaults.d.ts +9 -7
- package/dist/types/api/retry.d.ts +3 -3
- package/dist/types/api/tokenRefresh.d.ts +8 -5
- package/dist/types/api/userClient.d.ts +1 -23
- package/dist/types/api/workspaceChanged.d.ts +1 -1
- package/dist/types/auth/authStore.d.ts +4 -9
- package/dist/types/config/index.d.ts +2 -9
- package/dist/types/errors/extractApiError.d.ts +7 -13
- package/dist/types/initSDK.d.ts +1 -3
- package/dist/types/initUserAuth.d.ts +1 -9
- package/dist/types/versionCheck/buildNumber.d.ts +1 -1
- package/dist/types/versionCheck/index.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,8 +15,8 @@ store.
|
|
|
15
15
|
| --- | --- |
|
|
16
16
|
| `api` | `publicClient`, `userClient`, `initUserAuth`, single-flight token refresh, a cross-tab refresh lock, retry of safe requests, the client headers and the minimum-version header reader |
|
|
17
17
|
| `auth` | the token store (`getToken`, `storeToken`, `removeToken`), the synchronous `hasSession`, the session user, `clearSession` and `addSessionEndedListener`, `hasSessionEndedInTab`, JWT helpers, "keep me signed in" |
|
|
18
|
-
| `config` | `setConfig`, `getConfig`, `getConfigValue`: base URL, version, commit, build number, log settings, request timeout, retry count
|
|
19
|
-
| `errors` | `extractApiError`: any thrown value to `{ code, message, fieldErrors, status, offline }`, with `code` `'network'`, `'timeout'` or `'offline'` when no answer arrived (`offline` is true for all three, and `'offline'` only when the browser reports no network); `apiErrorMessage(error, fallback)`; `isNoResponse` |
|
|
18
|
+
| `config` | `setConfig`, `getConfig`, `getConfigValue`: base URL, version, commit, build number, log settings, request timeout, retry count |
|
|
19
|
+
| `errors` | `extractApiError`: any thrown value to `{ code, message, fieldErrors, status, offline, traceId, diagnostic }`, with `code` `'network'`, `'timeout'` or `'offline'` when no answer arrived (`offline` is true for all three, and `'offline'` only when the browser reports no network); `apiErrorMessage(error, fallback)`; `isNoResponse` |
|
|
20
20
|
| `formatters` | house date, time, number, percent and money (`formatMinor`) formatters; India Standard Time by default, the viewer's zone on request |
|
|
21
21
|
| `logger` | levelled `Logger` with secret redaction, and `Logger.requestFailed` for a failed service call |
|
|
22
22
|
| `network` | `useNetworkStatus` and the online/offline listener |
|
|
@@ -55,14 +55,18 @@ initWebority({ baseUrl: settings.apiUrl, appVersion: settings.version, commit: s
|
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
`appBuild` must be an integer the deploy stamps at build time (CI's run number, for example). The
|
|
58
|
-
version check compares it with the API's `
|
|
58
|
+
version check compares it with the API's `Min-App-Version` header as a number; a string build id
|
|
59
59
|
(`<sha>-<time>`) cannot be compared, and without an integer the update prompt never fires.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
`
|
|
64
|
-
|
|
65
|
-
|
|
60
|
+
Cross-origin, the portal can read `Min-App-Version` and `Retry-After` only when the API lists them
|
|
61
|
+
in `Access-Control-Expose-Headers`.
|
|
62
|
+
|
|
63
|
+
Every request carries a W3C `traceparent` header (`00-<trace id>-<span id>-01`) with a new value per
|
|
64
|
+
call; a retry resends the value of its first attempt, and a request that sets its own `traceparent`
|
|
65
|
+
keeps it. The trace id is the support reference: `ApiError.traceId` is the body's `traceId` when the
|
|
66
|
+
server sends one, else the trace id of the request's `traceparent`. Every request times out after
|
|
67
|
+
`timeoutMs`. `initWebority` takes `timeoutMs` (default 30000) and `retries` (default 3; 0 turns
|
|
68
|
+
retrying off). A single long call sets its own axios `timeout`. A `timeoutMs` or `timeout` of 0
|
|
69
|
+
means no timeout. `baseUrl: ""` sends every request to the page's own
|
|
66
70
|
origin, for an API answered through a same-origin dev proxy.
|
|
67
71
|
|
|
68
72
|
```ts
|
|
@@ -99,22 +103,22 @@ export const store = configureStore({
|
|
|
99
103
|
|
|
100
104
|
initUserAuth(store, {
|
|
101
105
|
logout: resetState,
|
|
102
|
-
refresh: { path: "/api/account/refresh", headers: () => ({ "
|
|
106
|
+
refresh: { path: "/api/account/refresh", headers: () => ({ "Installation-Id": getInstallationId() }) },
|
|
103
107
|
legacyKeys: { accessToken: "app.accessToken", refreshToken: "app.refreshToken" },
|
|
104
108
|
});
|
|
105
109
|
setupApiListeners(store.dispatch);
|
|
106
110
|
```
|
|
107
111
|
|
|
108
|
-
A session ends in one place. When the refresh endpoint refuses the refresh token,
|
|
109
|
-
the
|
|
110
|
-
the library calls `clearSession('
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
112
|
+
A session ends in one place. When the refresh endpoint refuses the refresh token, the library calls
|
|
113
|
+
`clearSession('rejected')`. When the API answers 401 with a body `code` of `session_revoked` or
|
|
114
|
+
`session_expired`, the library calls `clearSession('terminated')` and sends no refresh request. Any
|
|
115
|
+
other 401 takes the refresh path. Either way the library then dispatches `logout`. Every
|
|
116
|
+
`addSessionEndedListener((reason, { code }) => ...)` listener hears the end once. `code` is the body
|
|
117
|
+
code of a terminated session; for a rejected refresh it is the same code when the refusal carried one,
|
|
118
|
+
else `null`. Another tab's sign-out and an app's own `clearSession()` give `null`.
|
|
115
119
|
|
|
116
120
|
A request never runs in a workspace other than the one it was made in. `initUserAuth`'s `accountId`
|
|
117
|
-
provider is read once, when a request is made, and sent as `
|
|
121
|
+
provider is read once, when a request is made, and sent as `Account-Id`; a retry, a replay after a
|
|
118
122
|
refresh and a replay after a session replacement send that same id and never read the provider again.
|
|
119
123
|
A request refused (401) for a session that has since been replaced is sent again with the stored
|
|
120
124
|
token only when the provider still names the account it was made in (or neither names one). When the
|
|
@@ -179,6 +183,8 @@ body has a code other than `internal_error` and a curated message shows that mes
|
|
|
179
183
|
server sent nothing curated, for a panel that shows only the server's own sentence. `isNoResponse(error)`
|
|
180
184
|
says whether an answer arrived at all; `code` tells a timeout (`TIMEOUT_CODE`) from a network failure
|
|
181
185
|
while online (`NETWORK_CODE`) and from a browser that reports no network (`OFFLINE_CODE`).
|
|
186
|
+
`diagnostic` holds the server's own text (the body `detail`, else `title`, else a plain-text body, else
|
|
187
|
+
the request's error message) for the logger, never for a screen.
|
|
182
188
|
|
|
183
189
|
Errors raise a toast by default; a thunk or endpoint opts out with `meta: { silent: true }`, the
|
|
184
190
|
same on web and mobile. `onRejected` gives the product its say over each rejection before the
|
|
@@ -199,6 +205,11 @@ errorToastMiddleware({
|
|
|
199
205
|
});
|
|
200
206
|
```
|
|
201
207
|
|
|
208
|
+
An app moves to this contract by running `webority upgrade 0.93.0`. It renames the old `X-` header
|
|
209
|
+
strings to the plain names, `ApiError.detail` to `diagnostic` and `detail.signOutReason` to
|
|
210
|
+
`detail.code`, and lists each removed option (`correlationIdHeader`, `signOutReasonHeader`) and
|
|
211
|
+
constant for a person to delete.
|
|
212
|
+
|
|
202
213
|
## Formatters
|
|
203
214
|
|
|
204
215
|
Called with no options, the house formatters print (zone IST, locale en-IN, empty `-`):
|
|
@@ -302,7 +313,7 @@ claim: reload only when `!hasRecentlyReloaded() && markReloaded()`.
|
|
|
302
313
|
`requestFailed`, `setUser`, `setAttribute`, `trackScreen`, with secrets redacted before anything is
|
|
303
314
|
printed or forwarded. `Logger.requestFailed(error, { label, expectedStatuses })` logs a failed service
|
|
304
315
|
call: a cancel as info, an expected status as info (never a console warning), anything else as a recorded error with its
|
|
305
|
-
status, method, path (no query string), the server's `code` and `traceId
|
|
316
|
+
status, method, path (no query string), the server's `code` and `traceId`.
|
|
306
317
|
`setConfig({ logLevel, loggingEnabled })` decides what reaches the console; `setRemoteLogger` forwards
|
|
307
318
|
info and above (never debug) to an error-reporting service, and errors are forwarded even with logging
|
|
308
319
|
off. The library strips nothing at runtime. To drop debug calls from a production bundle, mark them pure
|
|
@@ -321,7 +332,7 @@ key into `sessionStorage` (`webority.designSample`) and leaves the address as it
|
|
|
321
332
|
the stored ticket, so it survives in-app navigation and redirects and ends with the tab.
|
|
322
333
|
|
|
323
334
|
`applyDesignSample(client)` is wired into `userClient` and `publicClient`: every request carries the
|
|
324
|
-
`
|
|
335
|
+
`Design-Sample` header when a ticket exists and the request does not set one itself. A header the
|
|
325
336
|
request sets by hand wins.
|
|
326
337
|
|
|
327
338
|
The ticket is never read in `Production`: `readDesignSampleTicket()` returns `null` there, whatever the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getConfigValue as e}from"../config/index.js";var t=`
|
|
1
|
+
import{getConfigValue as e}from"../config/index.js";var t=`Client-Build`,n=`Client-Kind`,r=`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/designSample.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getConfigValue as e}from"../config/index.js";var t=`designSample`,n=`
|
|
1
|
+
import{getConfigValue as e}from"../config/index.js";var t=`designSample`,n=`Design-Sample`,r=`webority.designSample`,i=!1,a=()=>{if(e(`environment`)===`Production`)return null;if(!i){i=!0;let e=new URLSearchParams(window.location.search).get(t);e&&sessionStorage.setItem(r,e)}return sessionStorage.getItem(r)},o=e=>{e.interceptors.request.use(e=>{let t=a();return t&&!e.headers.has(`Design-Sample`)&&e.headers.set(n,t),e})};export{n as DESIGN_SAMPLE_HEADER,t as DESIGN_SAMPLE_QUERY_KEY,r as DESIGN_SAMPLE_STORAGE_KEY,o as applyDesignSample,a as readDesignSampleTicket};
|
package/dist/api/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{TRACEPARENT_HEADER as e,applyRequestDefaults as t,newTraceparent as n}from"./requestDefaults.js";import{ACCOUNT_ID_HEADER as r,CLIENT_BUILD_HEADER as i,CLIENT_KIND as a,CLIENT_KIND_HEADER as o,getClientBuild as s}from"./clientHeaders.js";import{DESIGN_SAMPLE_HEADER as c,DESIGN_SAMPLE_QUERY_KEY as l,DESIGN_SAMPLE_STORAGE_KEY as u,applyDesignSample as d,readDesignSampleTicket as f}from"./designSample.js";import{MIN_APP_VERSION_HEADER as p,addMinAppVersionListener as m,getMinAppVersion as h}from"./minAppVersion.js";import{WORKSPACE_CHANGED_CODE as g,WorkspaceChangedError as _,isWorkspaceChangedError as v}from"./workspaceChanged.js";import{IDEMPOTENCY_KEY_HEADER as y,attachRetry as b,isNeverSentError as x,isRetryableFailure as S,isRetryableStatus as C,retryAfterMs as w}from"./retry.js";import{REFRESH_LOCK_NAME as T,withCrossTabRefreshLock as E}from"./crossTabRefreshLock.js";import D from"./publicClient.js";import{SESSION_EXPIRED_CODE as O,SESSION_REVOKED_CODE as k,injectStore as A,injectUserAuth as j,refreshAccessToken as M,resetRefreshState as N}from"./tokenRefresh.js";import P,{RequestOfflineError as F}from"./userClient.js";import{initUserAuth as I}from"../initUserAuth.js";export{r as ACCOUNT_ID_HEADER,i as CLIENT_BUILD_HEADER,a as CLIENT_KIND,o as CLIENT_KIND_HEADER,c as DESIGN_SAMPLE_HEADER,l as DESIGN_SAMPLE_QUERY_KEY,u as DESIGN_SAMPLE_STORAGE_KEY,y as IDEMPOTENCY_KEY_HEADER,p as MIN_APP_VERSION_HEADER,T as REFRESH_LOCK_NAME,F as RequestOfflineError,O as SESSION_EXPIRED_CODE,k as SESSION_REVOKED_CODE,e as TRACEPARENT_HEADER,g as WORKSPACE_CHANGED_CODE,_ as WorkspaceChangedError,m as addMinAppVersionListener,d as applyDesignSample,t as applyRequestDefaults,b as attachRetry,s as getClientBuild,h as getMinAppVersion,I as initUserAuth,A as injectStore,j as injectUserAuth,x as isNeverSentError,S as isRetryableFailure,C as isRetryableStatus,v as isWorkspaceChangedError,n as newTraceparent,D as publicClient,f as readDesignSampleTicket,M as refreshAccessToken,N as resetRefreshState,w as retryAfterMs,P as userClient,E as withCrossTabRefreshLock};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Logger as e}from"../logger/logger.js";var t=`
|
|
1
|
+
import{Logger as e}from"../logger/logger.js";var t=`Min-App-Version`,n=null,r=new Set,i=()=>n,a=e=>(r.add(e),n!==null&&e(n),()=>{r.delete(e)}),o=e=>{if(!e||typeof e!=`object`)return;let n=e.get;return typeof n==`function`?n.call(e,t):e[t.toLowerCase()]},s=i=>{let a=o(i?.headers);if(a==null||a===``)return;let s=String(a).trim();if(!/^\d+$/.test(s)){e.warn(`[minAppVersion] ignored a non-integer ${t}: ${s}`);return}let c=Number.parseInt(s,10);if(c!==n){n=c;for(let t of[...r])try{t(c)}catch(t){e.error(`[minAppVersion] a listener threw`,t)}}},c=e=>{e.interceptors.response.use(e=>(s(e),e),e=>(s(e?.response),Promise.reject(e)))};export{t as MIN_APP_VERSION_HEADER,a as addMinAppVersionListener,i as getMinAppVersion,c as readMinAppVersion};
|
package/dist/api/publicClient.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{applyRequestDefaults as e}from"./requestDefaults.js";import{Logger as t}from"../logger/logger.js";import{applyConfiguredBaseUrl as n}from"./baseUrl.js";import{applyClientHeaders as r}from"./clientHeaders.js";import{applyDesignSample as i}from"./designSample.js";import{readMinAppVersion as a}from"./minAppVersion.js";import{sendFormDataAsMultipart as o}from"./multipart.js";import{responseBodyText as s}from"./responseBodyText.js";import{attachRetry as c}from"./retry.js";import l from"axios";var u=l.create({headers:{"Content-Type":`application/json`,Accept:`application/json`}});n(u),e(u),o(u),a(u),c(u),u.interceptors.request.use(e=>(t.info(`Request: ${e.method?.toUpperCase()} ${e.url}`),e),e=>{let n=e instanceof Error?e.message:String(e);return t.error(`Request error:`,n),Promise.reject(e)}),r(u),i(u),u.interceptors.response.use(e=>(t.info(`Response: ${e.status} from ${e.config.url}`),e),e=>{if(l.isCancel(e))return Promise.reject(e);if(l.isAxiosError(e)){let n=e.config;if(n?.skipErrorLog)return Promise.reject(e);e.response?t.error(`Response error: ${s(e.response.data,e.message)}, URL: ${n?.url}, Status: ${e.response.status}`):t.error(`Network error or no response from server: ${e.message}, URL: ${n?.url}`)}else{let n=e instanceof Error?e.message:String(e);t.error(`Unknown response error: ${n}`)}return Promise.reject(e)});export{u as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getConfigValue as e}from"../config/index.js";var t=
|
|
1
|
+
import{getConfigValue as e}from"../config/index.js";var t=`traceparent`,n=e=>{let t=globalThis.crypto,n=new Uint8Array(e);if(typeof t?.getRandomValues==`function`)t.getRandomValues(n);else for(let e=0;e<n.length;e+=1)n[e]=Math.floor(Math.random()*256);return n.every(e=>e===0)&&(n[n.length-1]=1),Array.from(n,e=>e.toString(16).padStart(2,`0`)).join(``)},r=()=>{let e=n(16);return{header:`00-${e}-${n(8)}-01`,traceId:e}},i=n=>{n.defaults.timeout=void 0,n.interceptors.request.use(n=>(n.timeout===void 0&&(n.timeout=e(`timeoutMs`)),n.headers.has(`traceparent`)||n.headers.set(t,r().header),n))};export{t as TRACEPARENT_HEADER,i as applyRequestDefaults,r as newTraceparent};
|
package/dist/api/retry.js
CHANGED
|
@@ -1 +1 @@
|
|
|
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.
|
|
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.headers,r=typeof n?.get==`function`?n.get(`retry-after`):n?.[`retry-after`],i=Number(r);return r!=null&&Number.isFinite(i)&&i>0?i*1e3: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:Math.random()*Math.min(3e4,1e3*2**e),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};
|
package/dist/api/tokenRefresh.js
CHANGED
|
@@ -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
|
|
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=`session_revoked`,u=`session_expired`,d,f,p=e=>{d=e},m=e=>{f=e},h=null,g=[],_=(e,t=null)=>{g.forEach(({resolve:n,reject:r})=>{e?r(e):t!==null&&n(t)}),g=[]},v=()=>{h=null,_(Error(`[@webority/web-core] Session ended`))},y=e=>e instanceof Error?e.message:typeof e==`string`?e:`Unknown error`,b=()=>{if(!d||!f)throw Error(`User package store is not initialized. Call initUserAuth first.`);return{store:d,auth:f}},x=()=>f?.accountId,S=()=>f?.refresh.path,C=async()=>{let e=b();await e.store.dispatch(e.auth.logout())},w=async(e,t)=>{await r(e,{code:t}),await C()},T=e=>{let t=e&&typeof e==`object`?e.code:void 0;return t===`session_revoked`||t===`session_expired`?t:null},E=()=>h!==null,D=0,O=Promise.resolve(),k=null,A=()=>{D===0&&(O=new Promise(e=>{k=e})),D+=1;let e=!1;return()=>{e||(e=!0,--D,D===0&&(k?.(),k=null))}},j=()=>D===0?Promise.resolve():O,M=async r=>{let i;try{i=(f?.refresh.readTokens??z)(r)}catch(t){e.error(`A session-replacing response carried no tokens:`,y(t));return}await a(t,i.accessToken,i.accessTokenExpiry),await a(n,i.refreshToken,i.refreshTokenExpiry)},N=async e=>{let t=(await i(`accessToken`))?.token??null;return t!==null&&t!==e?t:null},P=()=>new Promise((e,t)=>{g.push({resolve:e,reject:t})}),F=class e extends Error{constructor(t){super(t),this.name=`SessionEndedError`,Object.setPrototypeOf(this,e.prototype)}},I=e=>e instanceof F||c.isAxiosError(e)&&(e.response?.status===400||e.response?.status===401),L=e=>!c.isAxiosError(e)||e.response?.status!==401?null:T(e.response.data),R=(e,t)=>{if(typeof e!=`string`||e===``)throw Error(`The refresh response has no "${t}".`);return e},z=e=>{let t=e??{};return{accessToken:R(t.accessToken,`accessToken`),accessTokenExpiry:R(t.accessTokenExpiration,`accessTokenExpiration`),refreshToken:R(t.refreshToken,`refreshToken`),refreshTokenExpiry:R(t.refreshTokenExpiration,`refreshTokenExpiration`)}},B=async r=>{let o=await i(n);if(!o?.token)throw new F(`No refresh token available`);if(Date.parse(o.expiry)<=Date.now())throw new F(`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??z)(d);await j();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)},V=30*1e3,H=async(n,r)=>{let a=await i(t);return a?.token&&a.token!==r&&Date.parse(a.expiry)>Date.now()+V?(e.info(`Adopting the access token another tab refreshed`),a.token):B(n)},U=()=>{if(h)return h;let t=(async()=>{let t=null;try{let{refresh:e}=b().auth;t=(await i(`accessToken`))?.token??null;let n=await o(()=>H(e,t));return _(null,n),n}catch(n){if(I(n)){await j();let n=await N(t);if(n)return e.info(`The session was replaced during the refresh; continuing with the new one`),_(null,n),n}let r=c.isAxiosError(n)?n.response?.data:void 0;throw e.error(`Token refresh failed:`,r??y(n)),_(n,null),I(n)&&await w(`rejected`,L(n)),n}})().finally(()=>{h===t&&(h=null)});return h=t,t};export{u as SESSION_EXPIRED_CODE,l as SESSION_REVOKED_CODE,A as beginSessionReplacement,N as currentTokenIfReplaced,C as dispatchLogout,w as endSessionAndLogout,x as getAccountIdProvider,y as getErrorMessage,S as getRefreshPath,p as injectStore,m as injectUserAuth,E as isRefreshInFlight,P as queueBehindRefresh,U as refreshAccessToken,v as resetRefreshState,T as sessionCodeOf,M as storeReplacementSession,j as waitForSessionReplacement};
|
package/dist/api/userClient.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{applyRequestDefaults as e}from"./requestDefaults.js";import{Logger as t}from"../logger/logger.js";import{ACCESS_TOKEN_KEY as n,getToken as r}from"../auth/authStore.js";import{getLastKnownNetworkStatus as i}from"../network/networkStatus.js";import{applyConfiguredBaseUrl as a}from"./baseUrl.js";import{applyClientHeaders as o,readAccountId as s,stampAccountId as c}from"./clientHeaders.js";import{applyDesignSample as l}from"./designSample.js";import{readMinAppVersion as u}from"./minAppVersion.js";import{sendFormDataAsMultipart as d}from"./multipart.js";import{responseBodyText as f}from"./responseBodyText.js";import{WorkspaceChangedError as p,isWorkspaceChangedResponse as m,workspaceChangedFromResponse as h}from"./workspaceChanged.js";import{attachRetry as g}from"./retry.js";import{SESSION_EXPIRED_CODE as _,SESSION_REVOKED_CODE as v,beginSessionReplacement as y,currentTokenIfReplaced as b,endSessionAndLogout as x,getAccountIdProvider as S,getErrorMessage as C,getRefreshPath as w,injectStore as T,injectUserAuth as E,isRefreshInFlight as D,queueBehindRefresh as O,refreshAccessToken as k,resetRefreshState as A,sessionCodeOf as j,storeReplacementSession as M,waitForSessionReplacement as N}from"./tokenRefresh.js";import P,{AxiosHeaders as F}from"axios";var I=class e extends Error{constructor(t=`Request blocked: device is offline`){super(t),this.name=`RequestOfflineError`,Object.setPrototypeOf(this,e.prototype)}},L=class e extends Error{constructor(){super(`No access token available`),this.name=`NoAccessTokenError`,Object.setPrototypeOf(this,e.prototype)}},R=e=>{e?._endReplacement?.(),e&&(e._endReplacement=void 0)},z=e=>{let t=e.headers instanceof F?e.headers.get(`Authorization`):void 0;return typeof t==`string`&&t.startsWith(`Bearer `)?t.slice(7):null},B=P.create({headers:{"Content-Type":`application/json`}});u(B),g(B);var V=(e,t)=>{e.headers instanceof F?e.headers.set(`Authorization`,`Bearer ${t}`):e.headers=F.from({...e.headers,Authorization:`Bearer ${t}`})},H=e=>{let t=w();return!!t&&!!e?.includes(t)};a(B),e(B),d(B),o(B),l(B),B.interceptors.request.use(async e=>{if(R(e),(await i()).isConnected===!1)return t.warn(`Request blocked: browser is offline. URL: ${e.url}`),Promise.reject(new I);try{if(H(e.url))return e;await c(e,S());let i=await r(n);if(!i?.token)throw new L;let a=Date.now();return Date.parse(i.expiry)<=a+30*1e3?(t.info(`Access token expired or about to expire, refreshing before request`),V(e,await k())):V(e,i.token),e.replacesSession&&(e._endReplacement=y()),e}catch(n){return n instanceof L?t.info(`Request not sent: signed out. URL: ${e.url}`):t.error(`Failed to attach token:`,C(n)),Promise.reject(n)}},e=>(t.error(`Request error:`,C(e)),Promise.reject(e))),B.interceptors.response.use(async e=>{let t=e.config;if(t._endReplacement)try{await M(e.data)}finally{R(t)}return e},async e=>{if(R(e?.config),P.isCancel(e)||e instanceof I||e instanceof L||e instanceof p)return Promise.reject(e);if(!P.isAxiosError(e))return t.error(`Non-axios error in response interceptor: ${C(e)}`),Promise.reject(e);let n=e,r=n.config;if(!n.response)return r?.skipErrorLog||t.error(`Network error or no response from server: ${n.message}, URL: ${r?.url}`),Promise.reject(e);if(m(n))return t.info(`Request refused: the workspace changed after it was made. URL: ${r?.url}`),Promise.reject(h(n));if(n.response.status===401&&r&&!r._replayedWithCurrentToken&&!r.replacesSession&&!H(r.url)&&(await N(),await b(z(r))))return(r._accountId??null)===await s(S())?(t.info(`Session changed while the request was in flight; sending it again. URL: ${r.url}`),r._replayedWithCurrentToken=!0,B(r)):(t.info(`Session moved to another workspace while the request was in flight; not sending it again. URL: ${r.url}`),Promise.reject(new p(void 0,{config:r,cause:e})));if(n.response.status===401&&r&&!r._retry){if(H(r.url))return Promise.reject(e);let i=j(n.response.data);if(i!==null)return t.warn(`Session ${i}, logging out without refresh. URL: ${r.url}`),A(),await x(`terminated`,i),Promise.reject(e);if(t.warn(`Unauthorized URL: ${r.url}`),r._retry=!0,D())return O().then(e=>(t.info(`Retrying request after token refresh:`,r.url),V(r,e),B(r))).catch(e=>(e instanceof p||t.error(`Failed to retry request after token refresh:`,C(e)),Promise.reject(e)));try{return V(r,await k()),B(r)}catch(e){return Promise.reject(e)}}return r?.skipErrorLog||t.error(`Response error: ${f(n.response?.data,n.message)}, URL: ${r?.url}, Status: ${n.response.status}`),Promise.reject(e)});export{I as RequestOfflineError,_ as SESSION_EXPIRED_CODE,v as SESSION_REVOKED_CODE,B as default,T as injectStore,E as injectUserAuth,A as resetRefreshState};
|
|
@@ -1 +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`,
|
|
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`,detail: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)?.detail;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/auth/authStore.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Logger as e}from"../logger/logger.js";import{getJWTExpiry as t}from"./jwt.js";var n=`accessToken`,r=`refreshToken`,i=`sessionUser`,a=720*60*60*1e3,o=new Set([n,r,i]),s=null,c=()=>globalThis.localStorage,l=()=>globalThis.sessionStorage,u=(e,t)=>{try{return e().getItem(t)}catch{return null}},d=(e,t)=>{try{e().removeItem(t)}catch{}},f=()=>s===null?u(c,`refreshToken`)!==null||u(c,`accessToken`)!==null:s,p=()=>f()?c:l,m=()=>f()?l:c,h=t=>{s=t;let n=p(),r=m();for(let t of o){let i=u(r,t);if(i!==null)try{n().setItem(t,i),d(r,t)}catch(n){e.error(`[authStore] could not move "${t}" to the chosen store`,n)}}},g=new Set,_=`sessionEnded`,v=()=>u(l,_)!==null,y=(t,n)=>{try{l().setItem(_,t)}catch{}for(let r of[...g])try{r(t,n)}catch(t){e.error(`[authStore] a session-ended listener threw`,t)}},b=e=>{if(e.storageArea!==c())return;let t=e.key===`refreshToken`&&e.oldValue!==null&&e.newValue===null;!(e.key===null||t)||u(l,`refreshToken`)!==null||y(`otherTab`,{
|
|
1
|
+
import{Logger as e}from"../logger/logger.js";import{getJWTExpiry as t}from"./jwt.js";var n=`accessToken`,r=`refreshToken`,i=`sessionUser`,a=720*60*60*1e3,o=new Set([n,r,i]),s=null,c=()=>globalThis.localStorage,l=()=>globalThis.sessionStorage,u=(e,t)=>{try{return e().getItem(t)}catch{return null}},d=(e,t)=>{try{e().removeItem(t)}catch{}},f=()=>s===null?u(c,`refreshToken`)!==null||u(c,`accessToken`)!==null:s,p=()=>f()?c:l,m=()=>f()?l:c,h=t=>{s=t;let n=p(),r=m();for(let t of o){let i=u(r,t);if(i!==null)try{n().setItem(t,i),d(r,t)}catch(n){e.error(`[authStore] could not move "${t}" to the chosen store`,n)}}},g=new Set,_=`sessionEnded`,v=()=>u(l,_)!==null,y=(t,n)=>{try{l().setItem(_,t)}catch{}for(let r of[...g])try{r(t,n)}catch(t){e.error(`[authStore] a session-ended listener threw`,t)}},b=e=>{if(e.storageArea!==c())return;let t=e.key===`refreshToken`&&e.oldValue!==null&&e.newValue===null;!(e.key===null||t)||u(l,`refreshToken`)!==null||y(`otherTab`,{code:null})},x=e=>(g.size===0&&typeof window<`u`&&window.addEventListener(`storage`,b),g.add(e),()=>{g.delete(e),g.size===0&&typeof window<`u`&&window.removeEventListener(`storage`,b)}),S=async(t,n,r)=>{try{p()().setItem(t,JSON.stringify({token:n,expiry:r})),d(m(),t),d(l,_)}catch(n){throw e.error(`Error storing token for key: ${t}`,n),n}},C=e=>{try{let t=JSON.parse(e);return t&&typeof t==`object`&&typeof t.token==`string`&&typeof t.expiry==`string`?t:null}catch{return null}},w=e=>u(l,e)??u(c,e),T=async t=>{let n=w(t);return n===null&&j(t)&&L()&&(n=w(t)),n===null?null:C(n)||(e.warn(`Stored credentials for key "${t}" are not a valid token; clearing them.`),j(t)?await R(`corrupt`,!0):await D(t),null)},E=()=>{w(`refreshToken`)===null&&w(`accessToken`)===null&&L();let e=w(r);if(e!==null&&C(e)!==null)return!0;let t=w(n),i=t===null?null:C(t);if(i===null)return!1;let a=Date.parse(i.expiry);return Number.isNaN(a)||a>Date.now()},D=async e=>{d(c,e),d(l,e)},O=async t=>{try{p()().setItem(i,JSON.stringify(t)),d(m(),i)}catch(t){throw e.error(`Error storing the session user`,t),t}},k=async()=>{let t=w(i);if(t===null)return null;try{return JSON.parse(t)}catch{return e.warn(`The stored session user is not valid JSON; clearing it.`),await A(),null}},A=async()=>{d(c,i),d(l,i)},j=e=>e===`accessToken`||e===`refreshToken`,M=null,N=!1,P=e=>{M=e,N=!1},F=e=>{let t=C(e);if(t)return t;let n=e;try{let t=JSON.parse(e);typeof t==`string`&&(n=t)}catch{}return n.trim()===``?null:{token:n,expiry:null}},I=e=>e.split(`.`).length===3?t(e)?.toISOString()??null:null,L=()=>{if(!M||N)return!1;N=!0;let t=M,a=[c,l].every(e=>u(e,`accessToken`)===null&&u(e,`refreshToken`)===null),o=!1;for(let s of[c,l]){let l=u(s,t.accessToken),f=u(s,t.refreshToken),p=t.sessionUser?u(s,t.sessionUser):null;if(a&&!o&&(l!==null||f!==null))try{let t=l===null?null:F(l),a=f===null?null:F(f);if(t){let e=t.expiry??I(t.token)??new Date().toISOString();s().setItem(n,JSON.stringify({token:t.token,expiry:e}))}if(a){let e=a.expiry??I(a.token)??new Date(Date.now()+2592e6).toISOString();s().setItem(r,JSON.stringify({token:a.token,expiry:e}))}p!==null&&s().setItem(i,p),o=t!==null||a!==null,o&&e.breadcrumb(`[authStore] moved a session from the legacy token keys`,{store:s===c?`localStorage`:`sessionStorage`})}catch(t){e.error(`[authStore] could not move the session from the legacy token keys`,t)}for(let e of[t.accessToken,t.refreshToken,t.sessionUser])e&&d(s,e)}return o},R=async(e,t,i=null)=>{let a=t||await T(`accessToken`)!==null||await T(`refreshToken`)!==null;await D(n),await D(r),await A(),s=null,(a||t)&&y(e,{code:i})},z=async(e=`signedOut`,t={})=>R(e,!1,t.code??null);export{n as ACCESS_TOKEN_KEY,a as LEGACY_REFRESH_TOKEN_LIFETIME_MS,r as REFRESH_TOKEN_KEY,i as SESSION_USER_KEY,x as addSessionEndedListener,z as clearSession,f as getKeepSignedIn,k as getSessionUser,T as getToken,E as hasSession,v as hasSessionEndedInTab,A as removeSessionUser,D as removeToken,h as setKeepSignedIn,P as setLegacyTokenKeys,O as storeSessionUser,S as storeToken};
|
package/dist/config/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var e=`warn`,t=3e4,n=3,r
|
|
1
|
+
var e=`warn`,t=3e4,n=3,r=`/version.json`,i=3e5,a={baseUrl:null,loggingEnabled:!0,logLevel:e,environment:`Development`,appVersion:null,commit:null,appBuild:null,buildId:null,versionUrl:r,buildCheckIntervalMs:i,timeoutMs:t,retries:3,staticAssets:null},o={...a},s=e=>{o={...o,...e}},c=()=>o;function l(e,t){let n=o[e];return n===null?t:n}var u=()=>{o={...a}};export{i as DEFAULT_BUILD_CHECK_INTERVAL_MS,e as DEFAULT_LOG_LEVEL,n as DEFAULT_RETRIES,t as DEFAULT_TIMEOUT_MS,r as DEFAULT_VERSION_URL,c as getConfig,l as getConfigValue,u as resetConfig,s as setConfig};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
var e=`Something went wrong on our side. Please try again in a moment.`,t=`You appear to be offline. Check your connection and try again.`,n=`We couldn't reach the server. Check your connection and try again.`,r=`That took too long to respond. Check your connection and try again.`,i=`Something went wrong. Please try again.`,a=`request_canceled`,o=`contract_error`,s=`timeout`,c=`network`,l=`offline`,u=e=>`Request failed (${e})`,d=e=>typeof e==`object`&&!!e&&!Array.isArray(e),f=e=>typeof e==`string`&&e.trim().length>0?e:null,p=e=>e.length>300||/^\s*</.test(e)||e.includes(`
|
|
2
|
-
at `)||/\b(Exception|StackTrace):/.test(e)||/\b(System|Microsoft)\.[A-Za-z]+\.[A-Za-z]/.test(e),m=e=>{let t=f(e);return t!==null&&!p(t)?t:null},h=e=>e.replace(/^\$\.?/,``).split(`.`).map(e=>e.charAt(0).toLowerCase()+e.slice(1)).join(`.`),g=e=>{if(Array.isArray(e)){for(let t of e){let e=f(t);if(e!==null)return e}return null}return f(e)},_=(e,t,n)=>{let r=h(t);r===``?e.form??=n:r in e.fields||(e.fields[r]=n)},v=e=>{let t={fields:{},form:null};if(Array.isArray(e)){for(let n of e){if(!d(n))continue;let e=f(n.message);e!==null&&_(t,typeof n.field==`string`?n.field:``,e)}return t}if(!d(e))return t;for(let[n,r]of Object.entries(e)){let e=g(r);e!==null&&_(t,n,e)}return t},y=e=>
|
|
2
|
+
at `)||/\b(Exception|StackTrace):/.test(e)||/\b(System|Microsoft)\.[A-Za-z]+\.[A-Za-z]/.test(e),m=e=>{let t=f(e);return t!==null&&!p(t)?t:null},h=e=>e.replace(/^\$\.?/,``).split(`.`).map(e=>e.charAt(0).toLowerCase()+e.slice(1)).join(`.`),g=e=>{if(Array.isArray(e)){for(let t of e){let e=f(t);if(e!==null)return e}return null}return f(e)},_=(e,t,n)=>{let r=h(t);r===``?e.form??=n:r in e.fields||(e.fields[r]=n)},v=e=>{let t={fields:{},form:null};if(Array.isArray(e)){for(let n of e){if(!d(n))continue;let e=f(n.message);e!==null&&_(t,typeof n.field==`string`?n.field:``,e)}return t}if(!d(e))return t;for(let[n,r]of Object.entries(e)){let e=g(r);e!==null&&_(t,n,e)}return t},y=e=>d(e)&&typeof e.message==`string`&&typeof e.status==`number`&&typeof e.offline==`boolean`&&d(e.fieldErrors),b=e=>({code:null,fieldErrors:{},status:0,offline:!1,traceId:null,diagnostic:null,...e}),x=()=>typeof navigator<`u`&&navigator.onLine===!1,S=e=>{let t=d(e.config)?e.config.headers:void 0;if(!d(t))return null;let n=typeof t.get==`function`?t.get(`traceparent`):t.traceparent??t.Traceparent,r=typeof n==`string`?/^00-([0-9a-f]{32})-[0-9a-f]{16}-[0-9a-f]{2}$/.exec(n):null;return r?r[1]:null},C=e=>{let o=f(e.code),u=f(e.name),d=f(e.message),p=S(e);return u===`CanceledError`||o===`ERR_CANCELED`?b({code:a,message:i,traceId:p,diagnostic:d}):o===`ECONNABORTED`||o===`ETIMEDOUT`?b({code:s,message:r,offline:!0,traceId:p,diagnostic:d}):u===`RequestOfflineError`||o===`ERR_NETWORK`||d===`Network Error`?x()?b({code:l,message:t,offline:!0,traceId:p,diagnostic:d}):b({code:c,message:n,offline:!0,traceId:p,diagnostic:d}):b({message:i,traceId:p,diagnostic:d})};function w(e){if(y(e))return e;if(typeof e==`string`)return b({message:m(e)??`Something went wrong. Please try again.`,diagnostic:e||null});if(!d(e))return b({message:i,diagnostic:e==null?null:String(e)});if(e.isContractError===!0)return b({code:o,message:m(e.message)??`Something went wrong. Please try again.`,diagnostic:f(e.message)});let t=d(e.response)?e.response:null,n=typeof t?.status==`number`?t.status:typeof e.status==`number`?e.status:0,r=t?t.data:e.body;if(n===0&&r==null)return C(e);let a=d(r)?r:null,{fields:s,form:c}=v(a?.errors),l=f(a?.code),p=f(a?.traceId)??S(e),h=f(a?.detail)??f(a?.title)??f(r)??f(e.message),g;return g=n>=500?(l!==null&&l!==`internal_error`?m(a?.detail):null)??`Something went wrong on our side. Please try again in a moment.`:m(Object.values(s)[0])??m(c)??m(a?.detail)??m(a?.title)??m(r)??(n?u(n):`Something went wrong. Please try again.`),b({code:l,message:g,fieldErrors:s,status:n,traceId:p,diagnostic:h})}var T=e=>w(e).offline;function E(e,t){let n=w(e);return n.offline||t!==null&&!t||n.status>=500&&n.message!==`Something went wrong on our side. Please try again in a moment.`?n.message:n.status>=500||n.code===`request_canceled`||n.message===`Something went wrong. Please try again.`||n.message===u(n.status)?t??null:n.message}export{o as CONTRACT_ERROR_CODE,c as NETWORK_CODE,l as OFFLINE_CODE,t as OFFLINE_MESSAGE,a as REQUEST_CANCELED_CODE,e as SERVER_FAILURE_MESSAGE,s as TIMEOUT_CODE,r as TIMEOUT_MESSAGE,i as UNEXPECTED_MESSAGE,n as UNREACHABLE_MESSAGE,E as apiErrorMessage,w as extractApiError,y as isApiError,T as isNoResponse,h as toFieldPath};
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{DEFAULT_BUILD_CHECK_INTERVAL_MS as e,
|
|
1
|
+
import{DEFAULT_BUILD_CHECK_INTERVAL_MS as e,DEFAULT_LOG_LEVEL as t,DEFAULT_RETRIES as n,DEFAULT_TIMEOUT_MS as r,DEFAULT_VERSION_URL as i,getConfig as a,getConfigValue as o,resetConfig as s,setConfig as c}from"./config/index.js";import{TRACEPARENT_HEADER as l,applyRequestDefaults as u,newTraceparent as d}from"./api/requestDefaults.js";import{Logger as f,configureLogger as p,getDeviceContext as m,setDeviceContext as h,setRemoteLogger as g}from"./logger/logger.js";import{decodeJWT as _,getJWTExpiry as v,isJWTExpired as y}from"./auth/jwt.js";import{ACCESS_TOKEN_KEY as b,LEGACY_REFRESH_TOKEN_LIFETIME_MS as x,REFRESH_TOKEN_KEY as S,SESSION_USER_KEY as C,addSessionEndedListener as w,clearSession as T,getKeepSignedIn as E,getSessionUser as D,getToken as O,hasSession as k,hasSessionEndedInTab as A,removeSessionUser as j,removeToken as M,setKeepSignedIn as N,setLegacyTokenKeys as P,storeSessionUser as F,storeToken as I}from"./auth/authStore.js";import{addNetworkStatusListener as L,getNetworkStatus as R,setNetworkStatusImplementation as z,useNetworkStatus as B}from"./network/networkStatus.js";import{ACCOUNT_ID_HEADER as V,CLIENT_BUILD_HEADER as H,CLIENT_KIND as U,CLIENT_KIND_HEADER as W,getClientBuild as G}from"./api/clientHeaders.js";import{DESIGN_SAMPLE_HEADER as K,DESIGN_SAMPLE_QUERY_KEY as q,DESIGN_SAMPLE_STORAGE_KEY as J,applyDesignSample as Y,readDesignSampleTicket as X}from"./api/designSample.js";import{MIN_APP_VERSION_HEADER as Z,addMinAppVersionListener as Q,getMinAppVersion as $}from"./api/minAppVersion.js";import{WORKSPACE_CHANGED_CODE as ee,WorkspaceChangedError as te,isWorkspaceChangedError as ne}from"./api/workspaceChanged.js";import{IDEMPOTENCY_KEY_HEADER as re,attachRetry as ie,isNeverSentError as ae,isRetryableFailure as oe,isRetryableStatus as se,retryAfterMs as ce}from"./api/retry.js";import{REFRESH_LOCK_NAME as le,withCrossTabRefreshLock as ue}from"./api/crossTabRefreshLock.js";import de from"./api/publicClient.js";import{SESSION_EXPIRED_CODE as fe,SESSION_REVOKED_CODE as pe,injectStore as me,injectUserAuth as he,refreshAccessToken as ge,resetRefreshState as _e}from"./api/tokenRefresh.js";import ve,{RequestOfflineError as ye}from"./api/userClient.js";import{initUserAuth as be}from"./initUserAuth.js";import{CONTRACT_ERROR_CODE as xe,NETWORK_CODE as Se,OFFLINE_CODE as Ce,OFFLINE_MESSAGE as we,REQUEST_CANCELED_CODE as Te,SERVER_FAILURE_MESSAGE as Ee,TIMEOUT_CODE as De,TIMEOUT_MESSAGE as Oe,UNEXPECTED_MESSAGE as ke,UNREACHABLE_MESSAGE as Ae,apiErrorMessage as je,extractApiError as Me,isApiError as Ne,isNoResponse as Pe,toFieldPath as Fe}from"./errors/extractApiError.js";import{configureFormatters as Ie,getFormatterConfig as Le}from"./formatters/formatConfig.js";import{formatCurrency as Re}from"./formatters/currency.js";import{addDays as ze}from"./formatters/dateMath.js";import{formatDate as Be,formatDateTime as Ve,formatMinor as He,formatNumber as Ue,formatPercent as We,formatRelative as Ge,formatTime as Ke}from"./formatters/house.js";import{initWebority as qe}from"./initSDK.js";import{createApiBaseQuery as Je}from"./query/apiBaseQuery.js";import{APP_API_DEFAULTS as Ye,createAppApi as Xe}from"./query/createAppApi.js";import{setupApiListeners as Ze}from"./query/listeners.js";import{buildListQuery as Qe,getNextPageParam as $e,getPreviousPageParam as et,hasNextPage as tt,hasPreviousPage as nt}from"./query/paging.js";import{LIST_TAG_ID as rt,itemTag as it,listTag as at,providesList as ot,providesPagedList as st}from"./query/tags.js";import{errorToastMiddleware as ct,isSilentRejection as lt}from"./redux/errorToastMiddleware.js";import{createRootReducer as ut,resetState as dt}from"./redux/rootReducer.js";import{SERIALIZABLE_IGNORED_ACTION_PATHS as ft}from"./redux/serializableCheck.js";import{hasRecentlyReloaded as pt,importWithReload as mt,isChunkLoadError as ht,lazyWithReload as gt,markReloaded as _t}from"./router/chunkReload.js";import{hasPermission as vt}from"./router/permissions.js";import{RETURN_TO_PARAM as yt,buildLoginPath as bt,getReturnTo as xt,toSafeReturnTo as St}from"./router/returnTo.js";import{PublicOnly as Ct,RequireAuth as wt,RequirePermission as Tt}from"./router/guards.js";import{publicOnly as Et,requireAuth as Dt,requirePermission as Ot,withGuards as kt}from"./router/loaders.js";import{classifyRouteError as At,useRouteErrorReport as jt}from"./router/routeError.js";import{staticUrl as Mt}from"./static/index.js";import{Storage as Nt,isStorageAvailable as Pt,setStorageImplementation as Ft,syncStorage as It}from"./storage/index.js";import{isBuildBelowMinimum as Lt,parseBuildNumber as Rt}from"./versionCheck/buildNumber.js";import{addDeployedBuildListener as zt,getNewerDeployedBuild as Bt,startDeployedBuildCheck as Vt}from"./versionCheck/deployedBuild.js";import{installReloadOnNavigate as Ht,isUpdateAvailable as Ut,useVersionCheck as Wt}from"./versionCheck/index.js";export{b as ACCESS_TOKEN_KEY,V as ACCOUNT_ID_HEADER,Ye as APP_API_DEFAULTS,H as CLIENT_BUILD_HEADER,U as CLIENT_KIND,W as CLIENT_KIND_HEADER,xe as CONTRACT_ERROR_CODE,e as DEFAULT_BUILD_CHECK_INTERVAL_MS,t as DEFAULT_LOG_LEVEL,n as DEFAULT_RETRIES,r as DEFAULT_TIMEOUT_MS,i as DEFAULT_VERSION_URL,K as DESIGN_SAMPLE_HEADER,q as DESIGN_SAMPLE_QUERY_KEY,J as DESIGN_SAMPLE_STORAGE_KEY,re as IDEMPOTENCY_KEY_HEADER,x as LEGACY_REFRESH_TOKEN_LIFETIME_MS,rt as LIST_TAG_ID,f as Logger,Z as MIN_APP_VERSION_HEADER,Se as NETWORK_CODE,Ce as OFFLINE_CODE,we as OFFLINE_MESSAGE,Ct as PublicOnly,le as REFRESH_LOCK_NAME,S as REFRESH_TOKEN_KEY,Te as REQUEST_CANCELED_CODE,yt as RETURN_TO_PARAM,ye as RequestOfflineError,wt as RequireAuth,Tt as RequirePermission,ft as SERIALIZABLE_IGNORED_ACTION_PATHS,Ee as SERVER_FAILURE_MESSAGE,fe as SESSION_EXPIRED_CODE,pe as SESSION_REVOKED_CODE,C as SESSION_USER_KEY,Nt as Storage,De as TIMEOUT_CODE,Oe as TIMEOUT_MESSAGE,l as TRACEPARENT_HEADER,ke as UNEXPECTED_MESSAGE,Ae as UNREACHABLE_MESSAGE,ee as WORKSPACE_CHANGED_CODE,te as WorkspaceChangedError,ze as addDays,zt as addDeployedBuildListener,Q as addMinAppVersionListener,L as addNetworkStatusListener,w as addSessionEndedListener,je as apiErrorMessage,Y as applyDesignSample,u as applyRequestDefaults,ie as attachRetry,Qe as buildListQuery,bt as buildLoginPath,At as classifyRouteError,T as clearSession,Ie as configureFormatters,p as configureLogger,Je as createApiBaseQuery,Xe as createAppApi,ut as createRootReducer,_ as decodeJWT,ct as errorToastMiddleware,Me as extractApiError,Re as formatCurrency,Be as formatDate,Ve as formatDateTime,He as formatMinor,Ue as formatNumber,We as formatPercent,Ge as formatRelative,Ke as formatTime,G as getClientBuild,a as getConfig,o as getConfigValue,m as getDeviceContext,Le as getFormatterConfig,v as getJWTExpiry,E as getKeepSignedIn,$ as getMinAppVersion,R as getNetworkStatus,Bt as getNewerDeployedBuild,$e as getNextPageParam,et as getPreviousPageParam,xt as getReturnTo,D as getSessionUser,O as getToken,tt as hasNextPage,vt as hasPermission,nt as hasPreviousPage,pt as hasRecentlyReloaded,k as hasSession,A as hasSessionEndedInTab,mt as importWithReload,be as initUserAuth,qe as initWebority,me as injectStore,he as injectUserAuth,Ht as installReloadOnNavigate,Ne as isApiError,Lt as isBuildBelowMinimum,ht as isChunkLoadError,y as isJWTExpired,ae as isNeverSentError,Pe as isNoResponse,oe as isRetryableFailure,se as isRetryableStatus,lt as isSilentRejection,Pt as isStorageAvailable,Ut as isUpdateAvailable,ne as isWorkspaceChangedError,it as itemTag,gt as lazyWithReload,at as listTag,_t as markReloaded,d as newTraceparent,Rt as parseBuildNumber,ot as providesList,st as providesPagedList,de as publicClient,Et as publicOnly,X as readDesignSampleTicket,ge as refreshAccessToken,j as removeSessionUser,M as removeToken,Dt as requireAuth,Ot as requirePermission,s as resetConfig,_e as resetRefreshState,dt as resetState,ce as retryAfterMs,c as setConfig,h as setDeviceContext,N as setKeepSignedIn,P as setLegacyTokenKeys,z as setNetworkStatusImplementation,g as setRemoteLogger,Ft as setStorageImplementation,Ze as setupApiListeners,Vt as startDeployedBuildCheck,Mt as staticUrl,F as storeSessionUser,I as storeToken,It as syncStorage,Fe as toFieldPath,St as toSafeReturnTo,B as useNetworkStatus,jt as useRouteErrorReport,Wt as useVersionCheck,ve as userClient,ue as withCrossTabRefreshLock,kt as withGuards};
|
package/dist/initSDK.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{setConfig as e}from"./config/index.js";import{Logger as t}from"./logger/logger.js";var n=n=>{if(typeof n.baseUrl!=`string`)throw Error(`baseUrl is required for Webority packages initialization`);e({baseUrl:n.baseUrl,loggingEnabled:n.loggingEnabled??!0,logLevel:n.logLevel??`warn`,environment:n.environment??`Development`,appVersion:n.appVersion??null,commit:n.commit??null,appBuild:n.appBuild??null,buildId:n.buildId??null,versionUrl:n.versionUrl??`/version.json`,buildCheckIntervalMs:n.buildCheckIntervalMs??3e5,timeoutMs:n.timeoutMs??3e4,retries:n.retries??3,staticAssets:n.staticAssets??null}),t.info(`[@webority/web-core] initialized`)};export{n as initWebority};
|
package/dist/logger/logger.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{getConfigValue as e}from"../config/index.js";import{redact as t,redactError as n,redactString as r}from"./redact.js";var i=[`debug`,`info`,`warn`,`error`],a=t=>{let n=e(`logLevel`,`info`);return i.indexOf(t)>=i.indexOf(n)},o=()=>e(`loggingEnabled`,!0),s=null,c=e=>{s=e},l=e=>{if(e)try{e()}catch{}},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))l(()=>s?.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&&l(()=>s?.recordError?.(e,C(t,r)))},T=(e,...n)=>{!o()||!a(`debug`)||console.debug(`[DEBUG]: ${r(e)}`,...t(n))},E=(e,...n)=>{let i=r(e),c=t(n);!o()||!a(`info`)||(console.info(`[INFO]: ${i}`,...c),l(()=>s?.log?.(`info`,i,c)))},D=(e,...n)=>{let i=r(e),c=t(n);!o()||!a(`warn`)||(console.warn(`[WARNING]: ${i}`,...c),l(()=>s?.log?.(`warn`,i,c)))},O=(e,...i)=>{let c=r(e),u=t(i);o()&&a(`error`)&&console.error(`[ERROR]: ${c}`,...u),l(()=>s?.log?.(`error`,c,u));let d=i[0];d!==void 0&&w(n(d),{source:`Logger.error`,message:c})},k=(e,n)=>{let i=r(e),c=t(n);o()&&a(`info`)&&console.info(`[BREADCRUMB]: ${i}`,c??``),l(()=>s?.breadcrumb?.(i,c))},A=(e,r)=>{let i=n(e),s=t(r);o()&&a(`error`)&&console.error(`[RECORDED]: ${i.message}`,s??``),w(i,s)},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:(n
|
|
1
|
+
import{getConfigValue as e}from"../config/index.js";import"../api/requestDefaults.js";import{redact as t,redactError as n,redactString as r}from"./redact.js";var i=[`debug`,`info`,`warn`,`error`],a=t=>{let n=e(`logLevel`,`info`);return i.indexOf(t)>=i.indexOf(n)},o=()=>e(`loggingEnabled`,!0),s=null,c=e=>{s=e},l=e=>{if(e)try{e()}catch{}},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))l(()=>s?.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&&l(()=>s?.recordError?.(e,C(t,r)))},T=(e,...n)=>{!o()||!a(`debug`)||console.debug(`[DEBUG]: ${r(e)}`,...t(n))},E=(e,...n)=>{let i=r(e),c=t(n);!o()||!a(`info`)||(console.info(`[INFO]: ${i}`,...c),l(()=>s?.log?.(`info`,i,c)))},D=(e,...n)=>{let i=r(e),c=t(n);!o()||!a(`warn`)||(console.warn(`[WARNING]: ${i}`,...c),l(()=>s?.log?.(`warn`,i,c)))},O=(e,...i)=>{let c=r(e),u=t(i);o()&&a(`error`)&&console.error(`[ERROR]: ${c}`,...u),l(()=>s?.log?.(`error`,c,u));let d=i[0];d!==void 0&&w(n(d),{source:`Logger.error`,message:c})},k=(e,n)=>{let i=r(e),c=t(n);o()&&a(`info`)&&console.info(`[BREADCRUMB]: ${i}`,c??``),l(()=>s?.breadcrumb?.(i,c))},A=(e,r)=>{let i=n(e),s=t(r);o()&&a(`error`)&&console.error(`[RECORDED]: ${i.message}`,s??``),w(i,s)},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=e=>M(e,`traceparent`)?.split(`-`)[1]??null,P={debug:T,info:E,warn:D,error:O,breadcrumb:k,recordError:A,requestFailed:(e,n={})=>{let{label:i,expectedStatuses:c=[],...u}=n,d=j(e),f=j(d?.config),p=j(d?.response),m=j(p?.data),h=typeof p?.status==`number`?p.status:null,g=typeof f?.method==`string`?f.method.toUpperCase():null,_=typeof f?.url==`string`?f.url.split(`?`)[0]??null:null,v=i??([g,_].filter(Boolean).join(` `)||`Request`);if(d?.name===`CanceledError`||d?.code===`ERR_CANCELED`||d?.__CANCEL__===!0){E(`${v} canceled`);return}if(h!==null&&c.includes(h)){E(`${v} refused (${h})`);return}let y=typeof d?.message==`string`?d.message:e instanceof Error?e.message:null,b=r(h===null?`${v} failed`:`${v} failed (${h})`);o()&&a(`error`)&&console.error(`[ERROR]: ${b}`,r(y??``)),l(()=>s?.log?.(`error`,b,[]));let x=t({source:`Logger.requestFailed`,label:v,status:h,method:g,path:_,code:typeof m?.code==`string`?m.code:null,traceId:(typeof m?.traceId==`string`?m.traceId:null)??N(f?.headers),detail:y,...u});w(Error(b),x)},setUser:(e,t)=>{l(()=>s?.setUser?.(e,t))},setAttribute:(e,t)=>{l(()=>s?.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{P as Logger,_ as configureLogger,p as getDeviceContext,f as setDeviceContext,c as setRemoteLogger};
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import type { AxiosInstance } from "axios";
|
|
2
|
-
export declare const CLIENT_BUILD_HEADER = "
|
|
3
|
-
export declare const CLIENT_KIND_HEADER = "
|
|
4
|
-
export declare const ACCOUNT_ID_HEADER = "
|
|
2
|
+
export declare const CLIENT_BUILD_HEADER = "Client-Build";
|
|
3
|
+
export declare const CLIENT_KIND_HEADER = "Client-Kind";
|
|
4
|
+
export declare const ACCOUNT_ID_HEADER = "Account-Id";
|
|
5
5
|
/** The server picks session rules and the minimum version per kind; the names match its enum. */
|
|
6
6
|
export type ClientKind = "Browser" | "MobileApp";
|
|
7
7
|
export declare const CLIENT_KIND: ClientKind;
|
|
8
8
|
/** `<appVersion>+<commit>`, `<appVersion>` when no commit was stamped, `null` before init. */
|
|
9
9
|
export declare const getClientBuild: () => string | null;
|
|
10
10
|
/**
|
|
11
|
-
* Stamps
|
|
11
|
+
* Stamps Client-Kind and Client-Build on every request. A value the app
|
|
12
12
|
* already set (a default header, or a per-request one) wins, so an app that
|
|
13
13
|
* wired them by hand keeps working unchanged.
|
|
14
14
|
*/
|
|
@@ -2,7 +2,7 @@ import type { AxiosInstance } from "axios";
|
|
|
2
2
|
/** The page address key a compare or a person opens a staging page with. */
|
|
3
3
|
export declare const DESIGN_SAMPLE_QUERY_KEY = "designSample";
|
|
4
4
|
/** The request header that carries the ticket to the product's API; the API's Webority.Delivery reads it. */
|
|
5
|
-
export declare const DESIGN_SAMPLE_HEADER = "
|
|
5
|
+
export declare const DESIGN_SAMPLE_HEADER = "Design-Sample";
|
|
6
6
|
/** sessionStorage key: the ticket outlives in-app navigation and redirects, and ends with the tab. */
|
|
7
7
|
export declare const DESIGN_SAMPLE_STORAGE_KEY = "webority.designSample";
|
|
8
8
|
/**
|
|
@@ -7,10 +7,10 @@ export { applyDesignSample, DESIGN_SAMPLE_HEADER, DESIGN_SAMPLE_QUERY_KEY, DESIG
|
|
|
7
7
|
export type { MinAppVersionListener } from "./minAppVersion";
|
|
8
8
|
export { addMinAppVersionListener, getMinAppVersion, MIN_APP_VERSION_HEADER } from "./minAppVersion";
|
|
9
9
|
export { default as publicClient } from "./publicClient";
|
|
10
|
-
export { applyRequestDefaults,
|
|
10
|
+
export { applyRequestDefaults, newTraceparent, TRACEPARENT_HEADER } from "./requestDefaults";
|
|
11
11
|
export type { RetryPolicy } from "./retry";
|
|
12
12
|
export { attachRetry, IDEMPOTENCY_KEY_HEADER, isNeverSentError, isRetryableFailure, isRetryableStatus, retryAfterMs, } from "./retry";
|
|
13
13
|
export { refreshAccessToken } from "./tokenRefresh";
|
|
14
|
-
export type { RefreshableStore
|
|
15
|
-
export { default as userClient, injectStore, injectUserAuth, RequestOfflineError, resetRefreshState,
|
|
14
|
+
export type { RefreshableStore } from "./userClient";
|
|
15
|
+
export { default as userClient, injectStore, injectUserAuth, RequestOfflineError, resetRefreshState, SESSION_EXPIRED_CODE, SESSION_REVOKED_CODE, } from "./userClient";
|
|
16
16
|
export { isWorkspaceChangedError, WORKSPACE_CHANGED_CODE, WorkspaceChangedError } from "./workspaceChanged";
|
|
@@ -5,7 +5,7 @@ import type { AxiosInstance } from "axios";
|
|
|
5
5
|
* errors included; cross-origin it is readable only when the API lists it in
|
|
6
6
|
* `Access-Control-Expose-Headers`.
|
|
7
7
|
*/
|
|
8
|
-
export declare const MIN_APP_VERSION_HEADER = "
|
|
8
|
+
export declare const MIN_APP_VERSION_HEADER = "Min-App-Version";
|
|
9
9
|
export type MinAppVersionListener = (minAppVersion: number) => void;
|
|
10
10
|
/** The floor the API last named, or `null` before any response carried one. */
|
|
11
11
|
export declare const getMinAppVersion: () => number | null;
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import type { AxiosInstance } from "axios";
|
|
2
|
+
export declare const TRACEPARENT_HEADER = "traceparent";
|
|
2
3
|
/**
|
|
3
|
-
* A fresh
|
|
4
|
-
* not at all on Hermes, so the id falls back to `getRandomValues`, then to `Math.random`. A
|
|
5
|
-
* correlation id only has to be unique enough to find one request in the logs; it is never a secret.
|
|
4
|
+
* A fresh W3C traceparent, version 00, sampled: the header value and its 32-hex trace id. The ids only have to be unique enough to find one request in the logs; they are never secrets.
|
|
6
5
|
*/
|
|
7
|
-
export declare const
|
|
6
|
+
export declare const newTraceparent: () => {
|
|
7
|
+
header: string;
|
|
8
|
+
traceId: string;
|
|
9
|
+
};
|
|
8
10
|
/**
|
|
9
|
-
* Applies the configured timeout and the
|
|
11
|
+
* Applies the configured timeout and the traceparent per request, read on every send so a
|
|
10
12
|
* value set after import still applies (the same reason `applyConfiguredBaseUrl` exists).
|
|
11
13
|
* - `timeoutMs`: used when the request carries no timeout of its own; a request's own axios
|
|
12
14
|
* `timeout` wins. 0, either way, means no timeout (axios's own meaning).
|
|
13
|
-
* - `
|
|
14
|
-
* resends the same config, so it keeps the
|
|
15
|
+
* - `traceparent`: a fresh W3C traceparent unless the request already carries one. A retry
|
|
16
|
+
* resends the same config, so it keeps the value the first attempt went out with.
|
|
15
17
|
*
|
|
16
18
|
* axios fills a missing `timeout` with its own default of 0, which would make "this request
|
|
17
19
|
* set none" and "this request asked for none" look alike. The client's default is therefore
|
|
@@ -20,9 +20,9 @@ export declare const IDEMPOTENCY_KEY_HEADER = "Idempotency-Key";
|
|
|
20
20
|
export declare const isRetryableStatus: (status: number | undefined) => boolean;
|
|
21
21
|
export declare const isNeverSentError: (error: AxiosError) => boolean;
|
|
22
22
|
/**
|
|
23
|
-
* The wait a refusal named, in milliseconds: the `Retry-After` header (seconds)
|
|
24
|
-
*
|
|
25
|
-
*
|
|
23
|
+
* The wait a refusal named, in milliseconds: the `Retry-After` header (seconds).
|
|
24
|
+
* `null` when it named none. Cross-origin, the header is readable only when the API
|
|
25
|
+
* lists it in `Access-Control-Expose-Headers`.
|
|
26
26
|
*/
|
|
27
27
|
export declare const retryAfterMs: (error: AxiosError) => number | null;
|
|
28
28
|
/**
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { type SessionEndReason } from "../auth/authStore";
|
|
2
2
|
import type { UserAuthOptions } from "../initUserAuth";
|
|
3
|
+
/** The problem-body `code` of a 401 that means the server revoked the session (a revoked bearer token or a rejected cookie stamp). */
|
|
4
|
+
export declare const SESSION_REVOKED_CODE = "session_revoked";
|
|
5
|
+
/** The problem-body `code` of a 401 that means the session's refresh token has expired. */
|
|
6
|
+
export declare const SESSION_EXPIRED_CODE = "session_expired";
|
|
3
7
|
/**
|
|
4
8
|
* The one token-refresh flow in the library, run by userClient's interceptors.
|
|
5
9
|
* The library calls the refresh endpoint itself, so it can tell a rejected
|
|
@@ -34,14 +38,13 @@ export declare const getAccountIdProvider: () => UserAuthOptions["accountId"];
|
|
|
34
38
|
/** The refresh endpoint's path, once initUserAuth has run. */
|
|
35
39
|
export declare const getRefreshPath: () => string | undefined;
|
|
36
40
|
export declare const dispatchLogout: () => Promise<void>;
|
|
37
|
-
export declare const SIGN_OUT_REASON_HEADER = "X-Sign-Out-Reason";
|
|
38
|
-
/** The server's sign-out reason from response headers (AxiosHeaders or a plain object), or null. */
|
|
39
|
-
export declare const readSignOutReason: (headers: unknown) => string | null;
|
|
40
41
|
/**
|
|
41
42
|
* The session is over: clear it (tokens and session user removed, listeners told why, with
|
|
42
|
-
* the server's
|
|
43
|
+
* the server's code), then dispatch the app's logout.
|
|
43
44
|
*/
|
|
44
|
-
export declare const endSessionAndLogout: (reason: SessionEndReason,
|
|
45
|
+
export declare const endSessionAndLogout: (reason: SessionEndReason, code: string | null) => Promise<void>;
|
|
46
|
+
/** The server's session code in a 401 body (`session_revoked`, `session_expired`), or null. */
|
|
47
|
+
export declare const sessionCodeOf: (data: unknown) => string | null;
|
|
45
48
|
export declare const isRefreshInFlight: () => boolean;
|
|
46
49
|
/** Opens the replacement window for one flagged request; call the returned function once it settles. */
|
|
47
50
|
export declare const beginSessionReplacement: () => (() => void);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type AxiosInstance } from "axios";
|
|
2
2
|
import "../types/axios";
|
|
3
3
|
export type { RefreshableStore } from "./tokenRefresh";
|
|
4
|
-
export { injectStore, injectUserAuth, resetRefreshState,
|
|
4
|
+
export { injectStore, injectUserAuth, resetRefreshState, SESSION_EXPIRED_CODE, SESSION_REVOKED_CODE } from "./tokenRefresh";
|
|
5
5
|
/**
|
|
6
6
|
* Thrown by the request interceptor when the device is offline, before the
|
|
7
7
|
* request ever reaches the adapter (and therefore the network). Distinct from
|
|
@@ -11,27 +11,5 @@ export { injectStore, injectUserAuth, resetRefreshState, SIGN_OUT_REASON_HEADER
|
|
|
11
11
|
export declare class RequestOfflineError extends Error {
|
|
12
12
|
constructor(message?: string);
|
|
13
13
|
}
|
|
14
|
-
/**
|
|
15
|
-
* Sub-codes a 401 response body carries when the session itself is dead
|
|
16
|
-
* (terminated by an admin, or expired server-side) rather than merely
|
|
17
|
-
* out-of-date. These never warrant a refresh attempt — only logout.
|
|
18
|
-
*/
|
|
19
|
-
export declare const SESSION_TERMINATION_CODES: {
|
|
20
|
-
readonly TERMINATED: "SESSION_TERMINATED";
|
|
21
|
-
readonly EXPIRED: "SESSION_EXPIRED";
|
|
22
|
-
};
|
|
23
|
-
export type SessionTerminationCode = (typeof SESSION_TERMINATION_CODES)[keyof typeof SESSION_TERMINATION_CODES];
|
|
24
|
-
/**
|
|
25
|
-
* The header is the AUTHORITATIVE signal, verified against the backend:
|
|
26
|
-
* `WeborityJwtMiddleware.ReturnUnauthorized` answers an invalidated session with
|
|
27
|
-
* `401` + `X-Security-Stamp-Invalid: true` and a body of
|
|
28
|
-
* `{ error: 'Unauthorized', message: '...' }` — no code field at all, and
|
|
29
|
-
* `error` is a string rather than an object. An earlier version of this checked
|
|
30
|
-
* only body fields and would therefore never have fired against a real server.
|
|
31
|
-
*
|
|
32
|
-
* The body codes below are kept as a secondary path for any head that does send
|
|
33
|
-
* an explicit sub-code; they match on exact strings, so looking is free.
|
|
34
|
-
*/
|
|
35
|
-
export declare const SESSION_INVALID_HEADER = "x-security-stamp-invalid";
|
|
36
14
|
declare const userClient: AxiosInstance;
|
|
37
15
|
export default userClient;
|
|
@@ -32,6 +32,6 @@ export declare class WorkspaceChangedError extends Error {
|
|
|
32
32
|
export declare const isWorkspaceChangedError: (error: unknown) => boolean;
|
|
33
33
|
/** The API refused the request because the account it names is no longer the session's. */
|
|
34
34
|
export declare const isWorkspaceChangedResponse: (error: AxiosError) => boolean;
|
|
35
|
-
/** The rejection for a 409 `workspace_changed` answer: the server's
|
|
35
|
+
/** The rejection for a 409 `workspace_changed` answer: the server's `detail` when it sent one. */
|
|
36
36
|
export declare const workspaceChangedFromResponse: (error: AxiosError) => WorkspaceChangedError;
|
|
37
37
|
export {};
|
|
@@ -16,20 +16,15 @@ export interface StoredToken {
|
|
|
16
16
|
* Why a session ended, handed to every `addSessionEndedListener` listener.
|
|
17
17
|
* - `signedOut`: the app ended it (the default for `clearSession`).
|
|
18
18
|
* - `rejected`: the refresh endpoint refused the refresh token.
|
|
19
|
-
* - `terminated`: the API reported the session dead (
|
|
19
|
+
* - `terminated`: the API reported the session dead (revoked or expired).
|
|
20
20
|
* - `corrupt`: a stored token could not be read back as a token.
|
|
21
21
|
* - `otherTab`: web only. Another tab signed out of the shared session.
|
|
22
22
|
*/
|
|
23
23
|
export type SessionEndReason = "signedOut" | "rejected" | "terminated" | "corrupt" | "otherTab";
|
|
24
24
|
/** What else is known about a session end, beside its reason. */
|
|
25
25
|
export interface SessionEndDetail {
|
|
26
|
-
/**
|
|
27
|
-
|
|
28
|
-
* header (`X-Sign-Out-Reason` by default, `initUserAuth({ signOutReasonHeader })`), on a
|
|
29
|
-
* terminated session or a refused refresh. `null` when the server named none, and always
|
|
30
|
-
* `null` for `signedOut`, `corrupt` and `otherTab` unless the app passes one.
|
|
31
|
-
*/
|
|
32
|
-
signOutReason: string | null;
|
|
26
|
+
/** The server's session code (`session_revoked`, `session_expired`) when the server ended the session; null when the app or another tab did. */
|
|
27
|
+
code: string | null;
|
|
33
28
|
}
|
|
34
29
|
export type SessionEndedListener = (reason: SessionEndReason, detail: SessionEndDetail) => void;
|
|
35
30
|
/**
|
|
@@ -117,6 +112,6 @@ export declare const setLegacyTokenKeys: (keys: LegacyTokenKeys | null) => void;
|
|
|
117
112
|
* flow and the app's own sign-out can both call it without a double event.
|
|
118
113
|
* The library calls it itself with `rejected` when the refresh endpoint refuses
|
|
119
114
|
* the refresh token and `terminated` when the API reports the session dead,
|
|
120
|
-
* with the server's
|
|
115
|
+
* with the server's code in `detail`.
|
|
121
116
|
*/
|
|
122
117
|
export declare const clearSession: (reason?: SessionEndReason, detail?: Partial<SessionEndDetail>) => Promise<void>;
|
|
@@ -17,13 +17,13 @@ export interface Config {
|
|
|
17
17
|
logLevel: LogLevel;
|
|
18
18
|
/** Current runtime environment. */
|
|
19
19
|
environment: Environment;
|
|
20
|
-
/** The portal's user-visible version, e.g. `'1.4.2'`; with `commit` it makes the
|
|
20
|
+
/** The portal's user-visible version, e.g. `'1.4.2'`; with `commit` it makes the Client-Build header. */
|
|
21
21
|
appVersion: string | null;
|
|
22
22
|
/** The short git sha stamped into the build. */
|
|
23
23
|
commit: string | null;
|
|
24
24
|
/**
|
|
25
25
|
* The portal's integer build number, stamped by the deploy. The version check
|
|
26
|
-
* compares it with the API's `
|
|
26
|
+
* compares it with the API's `Min-App-Version`.
|
|
27
27
|
*/
|
|
28
28
|
appBuild: number | null;
|
|
29
29
|
/**
|
|
@@ -42,12 +42,6 @@ export interface Config {
|
|
|
42
42
|
timeoutMs: number;
|
|
43
43
|
/** How many times a failed request is resent under the retry rule. 0 turns retrying off. Default 3. */
|
|
44
44
|
retries: number;
|
|
45
|
-
/**
|
|
46
|
-
* Header that carries a fresh id (a uuid) on every request, for tracing one request across
|
|
47
|
-
* the portal, the API and its logs. A retry keeps the id it first went out with. `null`
|
|
48
|
-
* sends none. Default `'X-Correlation-Id'`.
|
|
49
|
-
*/
|
|
50
|
-
correlationIdHeader: string | null;
|
|
51
45
|
/**
|
|
52
46
|
* The authored static assets the portal links by key, compiled from the static deployable in `.webority/infra/deployables.json` by the
|
|
53
47
|
* `weborityStatic` Vite plugin. `staticUrl` reads it. `null` when the portal has no list. Web only.
|
|
@@ -61,7 +55,6 @@ export interface Config {
|
|
|
61
55
|
export declare const DEFAULT_LOG_LEVEL: LogLevel;
|
|
62
56
|
export declare const DEFAULT_TIMEOUT_MS = 30000;
|
|
63
57
|
export declare const DEFAULT_RETRIES = 3;
|
|
64
|
-
export declare const DEFAULT_CORRELATION_ID_HEADER = "X-Correlation-Id";
|
|
65
58
|
export declare const DEFAULT_VERSION_URL = "/version.json";
|
|
66
59
|
export declare const DEFAULT_BUILD_CHECK_INTERVAL_MS = 300000;
|
|
67
60
|
/** Set global configuration for all Webority packages. */
|
|
@@ -2,19 +2,13 @@
|
|
|
2
2
|
* Normalizes any failure a thunk, a service or the RTK Query base query can see into one
|
|
3
3
|
* `ApiError`. Identical source in @webority/web-core and @webority/mobile-core.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* where `errors` is a ModelState dictionary (`{ Field: ["msg"] }`) or a list of
|
|
7
|
-
* `{ field, message, reference }` (a refusal that names its fields),
|
|
8
|
-
* ProblemDetails `{ title, detail, errors }` from APIs not yet moved, legacy `{ error }` and
|
|
9
|
-
* `{ message }`, and a plain-text body.
|
|
5
|
+
* The body read is the RFC 9457 problem `{ type, title, status, detail, code, traceId, errors }`,
|
|
6
|
+
* where `errors` is a ModelState dictionary (`{ Field: ["msg"] }`) or a list of `{ field, message }`.
|
|
10
7
|
*
|
|
11
|
-
* Message precedence
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* Two things never become `message`: a 5xx body unless it carries a code other than
|
|
16
|
-
* `internal_error` and a curated message, and anything that reads like an exception dump.
|
|
17
|
-
* The server's text goes on `detail` for the logger instead.
|
|
8
|
+
* Message precedence (react.md): first field error, form error, `detail`, `title`, a plain-text
|
|
9
|
+
* body, then `Request failed (<status>)`. A 5xx shows `detail` only when it carries a code other
|
|
10
|
+
* than `internal_error`. The server's text goes on `diagnostic` for the logger, never the screen.
|
|
11
|
+
* The trace id falls back to the request's own `traceparent`, so a lost request keeps a reference.
|
|
18
12
|
*/
|
|
19
13
|
export interface ApiError {
|
|
20
14
|
/** Stable snake_case branch key from the server (`not_found`, `validation_failed`). Branch on it, never render it. */
|
|
@@ -30,7 +24,7 @@ export interface ApiError {
|
|
|
30
24
|
/** The server's trace id, for a support reference. */
|
|
31
25
|
traceId: string | null;
|
|
32
26
|
/** What the server or runtime actually said. For the logger, never for the screen. */
|
|
33
|
-
|
|
27
|
+
diagnostic: string | null;
|
|
34
28
|
}
|
|
35
29
|
export declare const SERVER_FAILURE_MESSAGE = "Something went wrong on our side. Please try again in a moment.";
|
|
36
30
|
export declare const OFFLINE_MESSAGE = "You appear to be offline. Check your connection and try again.";
|
package/dist/types/initSDK.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export interface InitConfig {
|
|
|
13
13
|
logLevel?: LogLevel;
|
|
14
14
|
/** Environment. Defaults to `'Development'`. */
|
|
15
15
|
environment?: Environment;
|
|
16
|
-
/** The user-visible version and the short git sha; together they make
|
|
16
|
+
/** The user-visible version and the short git sha; together they make Client-Build. */
|
|
17
17
|
appVersion?: string;
|
|
18
18
|
commit?: string;
|
|
19
19
|
/** The integer build number the deploy stamps; the version check compares it with the API's minimum. */
|
|
@@ -28,8 +28,6 @@ export interface InitConfig {
|
|
|
28
28
|
timeoutMs?: number;
|
|
29
29
|
/** Retries under the retry rule. Defaults to 3; 0 turns retrying off. */
|
|
30
30
|
retries?: number;
|
|
31
|
-
/** Header for the per-request correlation id. Defaults to `'X-Correlation-Id'`; `null` sends none. */
|
|
32
|
-
correlationIdHeader?: string | null;
|
|
33
31
|
/** The default export of `virtual:webority-static-assets` (the `weborityStatic` Vite plugin); `staticUrl` reads it. Defaults to `null`. Web only. */
|
|
34
32
|
staticAssets?: StaticAssetsConfig | null;
|
|
35
33
|
}
|
|
@@ -36,7 +36,7 @@ export interface UserAuthOptions {
|
|
|
36
36
|
logout: () => unknown;
|
|
37
37
|
refresh: TokenRefreshConfig;
|
|
38
38
|
/**
|
|
39
|
-
* The account the signed-in user is working in, sent as `
|
|
39
|
+
* The account the signed-in user is working in, sent as `Account-Id` on
|
|
40
40
|
* every userClient request. Read once, when the request is made; a retry or
|
|
41
41
|
* replay sends the same id, so a request never runs in another workspace. A
|
|
42
42
|
* request whose session moved to another workspace while it was in flight is
|
|
@@ -45,14 +45,6 @@ export interface UserAuthOptions {
|
|
|
45
45
|
* token alone.
|
|
46
46
|
*/
|
|
47
47
|
accountId?: AccountIdProvider;
|
|
48
|
-
/**
|
|
49
|
-
* The response header in which the API names why it ended a session. A 401 carrying it is a
|
|
50
|
-
* revoked session: no refresh is tried, the session is cleared as `terminated`, and the
|
|
51
|
-
* value reaches every `addSessionEndedListener` listener as `detail.signOutReason`. It is
|
|
52
|
-
* read from a refused refresh too. Default `'X-Sign-Out-Reason'`; `null` turns it off.
|
|
53
|
-
* Cross-origin, the API must list it in `Access-Control-Expose-Headers`.
|
|
54
|
-
*/
|
|
55
|
-
signOutReasonHeader?: string | null;
|
|
56
48
|
/**
|
|
57
49
|
* The keys the app kept its session under before this library. The first token read that
|
|
58
50
|
* finds the library's keys empty moves a session found there and deletes the old keys, so
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Build-number arithmetic for the server-driven update check, shared word for
|
|
3
3
|
* word by web-core and mobile-core.
|
|
4
4
|
*
|
|
5
|
-
* The API's `
|
|
5
|
+
* The API's `Min-App-Version` is the lowest internal build number it still
|
|
6
6
|
* supports for the calling client kind, and the app's own build number is an
|
|
7
7
|
* integer too (`VERSION_INTERNAL` on mobile, `appBuild` on web). The comparison
|
|
8
8
|
* is integer against integer, never semver against semver.
|
|
@@ -7,7 +7,7 @@ export interface VersionCheckState {
|
|
|
7
7
|
updateAvailable: boolean;
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
|
-
* Whether a newer build must be loaded: the minimum the API sends in `
|
|
10
|
+
* Whether a newer build must be loaded: the minimum the API sends in `Min-App-Version`, or a
|
|
11
11
|
* newer deployed build found by `startDeployedBuildCheck`. Re-renders the moment either changes.
|
|
12
12
|
* The "new version" bar that reads this lives in `@webority/ui-react`.
|
|
13
13
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webority/web-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.93.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",
|