@webority/mobile-core 0.100.0 → 0.101.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.
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.waitForSessionReplacement=exports.storeReplacementSession=exports.sessionCodeOf=exports.resetRefreshState=exports.refreshAccessToken=exports.queueBehindRefresh=exports.isRefreshInFlight=exports.injectUserAuth=exports.injectStore=exports.getRefreshPath=exports.getErrorMessage=exports.getAccountIdProvider=exports.endSessionAndLogout=exports.dispatchLogout=exports.currentTokenIfReplaced=exports.beginSessionReplacement=exports.SESSION_REVOKED_CODE=exports.SESSION_EXPIRED_CODE=void 0;var _axios=_interopRequireDefault(require("axios")),_authStore=require("../auth/authStore.js"),_index=require("../logger/index.js"),_publicClient=_interopRequireDefault(require("./publicClient.js"));function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}const SESSION_REVOKED_CODE=exports.SESSION_REVOKED_CODE="session_revoked",SESSION_EXPIRED_CODE=exports.SESSION_EXPIRED_CODE="session_expired";let store,auth;const injectStore=e=>{store=e};exports.injectStore=injectStore;const injectUserAuth=e=>{auth=e};exports.injectUserAuth=injectUserAuth;let refreshTokenPromise=null,failedQueue=[];const processQueue=(e,t=null)=>{failedQueue.forEach(({resolve:r,reject:s})=>{e?s(e):null!==t&&r(t)}),failedQueue=[]},resetRefreshState=()=>{refreshTokenPromise=null,processQueue(new Error("[@webority/mobile-core] Session ended"))};exports.resetRefreshState=resetRefreshState;const getErrorMessage=e=>e instanceof Error?e.message:"string"==typeof e?e:"Unknown error";exports.getErrorMessage=getErrorMessage;const requireAuth=()=>{if(!store||!auth)throw new Error("User package store is not initialized. Call initUserAuth first.");return{store:store,auth:auth}},getAccountIdProvider=()=>auth?.accountId;exports.getAccountIdProvider=getAccountIdProvider;const getRefreshPath=()=>auth?.refresh.path;exports.getRefreshPath=getRefreshPath;const dispatchLogout=async()=>{const e=requireAuth();await e.store.dispatch(e.auth.logout())};exports.dispatchLogout=dispatchLogout;const endSessionAndLogout=async(e,t)=>{await(0,_authStore.clearSession)(e,{code:t}),await dispatchLogout()};exports.endSessionAndLogout=endSessionAndLogout;const sessionCodeOf=e=>{const t=e&&"object"==typeof e?e.code:void 0;return t===SESSION_REVOKED_CODE||t===SESSION_EXPIRED_CODE?t:null};exports.sessionCodeOf=sessionCodeOf;const isRefreshInFlight=()=>null!==refreshTokenPromise;exports.isRefreshInFlight=isRefreshInFlight;let replacementCount=0,replacementSettled=Promise.resolve(),settleReplacement=null;const beginSessionReplacement=()=>{0===replacementCount&&(replacementSettled=new Promise(e=>{settleReplacement=e})),replacementCount+=1;let e=!1;return()=>{e||(e=!0,replacementCount-=1,0===replacementCount&&(settleReplacement?.(),settleReplacement=null))}};exports.beginSessionReplacement=beginSessionReplacement;const waitForSessionReplacement=()=>0===replacementCount?Promise.resolve():replacementSettled;exports.waitForSessionReplacement=waitForSessionReplacement;const currentTokenIfReplaced=async e=>{const t=(await(0,_authStore.getToken)(_authStore.ACCESS_TOKEN_KEY))?.token??null;return null!==t&&t!==e?t:null};exports.currentTokenIfReplaced=currentTokenIfReplaced;const queueBehindRefresh=()=>new Promise((e,t)=>{failedQueue.push({resolve:e,reject:t})});exports.queueBehindRefresh=queueBehindRefresh;class SessionEndedError extends Error{constructor(e){super(e),this.name="SessionEndedError",Object.setPrototypeOf(this,SessionEndedError.prototype)}}const endsSession=e=>e instanceof SessionEndedError||_axios.default.isAxiosError(e)&&(400===e.response?.status||401===e.response?.status),asString=(e,t)=>{if("string"!=typeof e||""===e)throw new Error(`The refresh response has no "${t}".`);return e},readWeborityTokens=e=>{const t=e??{};return{accessToken:asString(t.accessToken,"accessToken"),accessTokenExpiry:asString(t.accessTokenExpiration,"accessTokenExpiration"),refreshToken:asString(t.refreshToken,"refreshToken"),refreshTokenExpiry:asString(t.refreshTokenExpiration,"refreshTokenExpiration")}},storeReplacementSession=async e=>{let t;try{t=(auth?.refresh.readTokens??readWeborityTokens)(e)}catch(e){return void _index.Logger.error("A session-replacing response carried no tokens:",getErrorMessage(e))}await(0,_authStore.storeToken)(_authStore.ACCESS_TOKEN_KEY,t.accessToken,t.accessTokenExpiry),await(0,_authStore.storeToken)(_authStore.REFRESH_TOKEN_KEY,t.refreshToken,t.refreshTokenExpiry)};exports.storeReplacementSession=storeReplacementSession;const callRefreshEndpoint=async e=>{const t=await(0,_authStore.getToken)(_authStore.REFRESH_TOKEN_KEY);if(!t?.token)throw new SessionEndedError("No refresh token available");if(Date.parse(t.expiry)<=Date.now())throw new SessionEndedError("Refresh token expired");const r=await(0,_authStore.getToken)(_authStore.ACCESS_TOKEN_KEY),s={accessToken:r?.token??null,refreshToken:t.token},o=e.body?await e.body(s):s,n=e.headers?await e.headers():void 0;_index.Logger.info("Refreshing the access token");const{data:i}=await _publicClient.default.post(e.path,o,n?{headers:n}:void 0),a=(e.readTokens??readWeborityTokens)(i);if(await waitForSessionReplacement(),(await(0,_authStore.getToken)(_authStore.REFRESH_TOKEN_KEY))?.token!==t.token){const e=(await(0,_authStore.getToken)(_authStore.ACCESS_TOKEN_KEY))?.token;if(e)return _index.Logger.info("Keeping the session that replaced this one during the refresh"),e}return await(0,_authStore.storeToken)(_authStore.ACCESS_TOKEN_KEY,a.accessToken,a.accessTokenExpiry),await(0,_authStore.storeToken)(_authStore.REFRESH_TOKEN_KEY,a.refreshToken,a.refreshTokenExpiry),_index.Logger.info("Access token refreshed"),a.accessToken},refreshAccessToken=()=>{if(refreshTokenPromise)return refreshTokenPromise;const e=(async()=>{let e=null;try{const{refresh:t}=requireAuth().auth;e=(await(0,_authStore.getToken)(_authStore.ACCESS_TOKEN_KEY))?.token??null;const r=await callRefreshEndpoint(t);return processQueue(null,r),r}catch(t){if(endsSession(t)){await waitForSessionReplacement();const t=await currentTokenIfReplaced(e);if(t)return _index.Logger.info("The session was replaced during the refresh; continuing with the new one"),processQueue(null,t),t}const r=_axios.default.isAxiosError(t)?t.response?.data:void 0;if(_index.Logger.error("Token refresh failed:",r??getErrorMessage(t)),processQueue(t,null),endsSession(t)){const e=_axios.default.isAxiosError(t)&&401===t.response?.status?sessionCodeOf(t.response.data):null;await endSessionAndLogout("rejected",e)}throw t}})().finally(()=>{refreshTokenPromise===e&&(refreshTokenPromise=null)});return refreshTokenPromise=e,e};exports.refreshAccessToken=refreshAccessToken;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.waitForSessionReplacement=exports.storeReplacementSession=exports.sessionDetailOf=exports.sessionCodeOf=exports.resetRefreshState=exports.refreshAccessToken=exports.queueBehindRefresh=exports.isRefreshInFlight=exports.injectUserAuth=exports.injectStore=exports.getRefreshPath=exports.getErrorMessage=exports.getAccountIdProvider=exports.endSessionAndLogout=exports.dispatchLogout=exports.currentTokenIfReplaced=exports.beginSessionReplacement=exports.SESSION_REVOKED_CODE=exports.SESSION_EXPIRED_CODE=void 0;var _axios=_interopRequireDefault(require("axios")),_authStore=require("../auth/authStore.js"),_index=require("../logger/index.js"),_publicClient=_interopRequireDefault(require("./publicClient.js"));function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}const SESSION_REVOKED_CODE=exports.SESSION_REVOKED_CODE="session_revoked",SESSION_EXPIRED_CODE=exports.SESSION_EXPIRED_CODE="session_expired";let store,auth;const injectStore=e=>{store=e};exports.injectStore=injectStore;const injectUserAuth=e=>{auth=e};exports.injectUserAuth=injectUserAuth;let refreshTokenPromise=null,failedQueue=[];const processQueue=(e,t=null)=>{failedQueue.forEach(({resolve:s,reject:r})=>{e?r(e):null!==t&&s(t)}),failedQueue=[]},resetRefreshState=()=>{refreshTokenPromise=null,processQueue(new Error("[@webority/mobile-core] Session ended"))};exports.resetRefreshState=resetRefreshState;const getErrorMessage=e=>e instanceof Error?e.message:"string"==typeof e?e:"Unknown error";exports.getErrorMessage=getErrorMessage;const requireAuth=()=>{if(!store||!auth)throw new Error("User package store is not initialized. Call initUserAuth first.");return{store:store,auth:auth}},getAccountIdProvider=()=>auth?.accountId;exports.getAccountIdProvider=getAccountIdProvider;const getRefreshPath=()=>auth?.refresh.path;exports.getRefreshPath=getRefreshPath;const dispatchLogout=async()=>{const e=requireAuth();await e.store.dispatch(e.auth.logout())};exports.dispatchLogout=dispatchLogout;const endSessionAndLogout=async(e,t,s=null)=>{await(0,_authStore.clearSession)(e,{code:t,detail:s}),await dispatchLogout()};exports.endSessionAndLogout=endSessionAndLogout;const sessionCodeOf=e=>{const t=e&&"object"==typeof e?e.code:void 0;return t===SESSION_REVOKED_CODE||t===SESSION_EXPIRED_CODE?t:null};exports.sessionCodeOf=sessionCodeOf;const sessionDetailOf=e=>{if(null===sessionCodeOf(e))return null;const t=e.detail;return"string"==typeof t&&""!==t?t:null};exports.sessionDetailOf=sessionDetailOf;const isRefreshInFlight=()=>null!==refreshTokenPromise;exports.isRefreshInFlight=isRefreshInFlight;let replacementCount=0,replacementSettled=Promise.resolve(),settleReplacement=null;const beginSessionReplacement=()=>{0===replacementCount&&(replacementSettled=new Promise(e=>{settleReplacement=e})),replacementCount+=1;let e=!1;return()=>{e||(e=!0,replacementCount-=1,0===replacementCount&&(settleReplacement?.(),settleReplacement=null))}};exports.beginSessionReplacement=beginSessionReplacement;const waitForSessionReplacement=()=>0===replacementCount?Promise.resolve():replacementSettled;exports.waitForSessionReplacement=waitForSessionReplacement;const currentTokenIfReplaced=async e=>{const t=(await(0,_authStore.getToken)(_authStore.ACCESS_TOKEN_KEY))?.token??null;return null!==t&&t!==e?t:null};exports.currentTokenIfReplaced=currentTokenIfReplaced;const queueBehindRefresh=()=>new Promise((e,t)=>{failedQueue.push({resolve:e,reject:t})});exports.queueBehindRefresh=queueBehindRefresh;class SessionEndedError extends Error{constructor(e){super(e),this.name="SessionEndedError",Object.setPrototypeOf(this,SessionEndedError.prototype)}}const endsSession=e=>e instanceof SessionEndedError||_axios.default.isAxiosError(e)&&(400===e.response?.status||401===e.response?.status),asString=(e,t)=>{if("string"!=typeof e||""===e)throw new Error(`The refresh response has no "${t}".`);return e},readWeborityTokens=e=>{const t=e??{};return{accessToken:asString(t.accessToken,"accessToken"),accessTokenExpiry:asString(t.accessTokenExpiration,"accessTokenExpiration"),refreshToken:asString(t.refreshToken,"refreshToken"),refreshTokenExpiry:asString(t.refreshTokenExpiration,"refreshTokenExpiration")}},storeReplacementSession=async e=>{let t;try{t=(auth?.refresh.readTokens??readWeborityTokens)(e)}catch(e){return void _index.Logger.error("A session-replacing response carried no tokens:",getErrorMessage(e))}await(0,_authStore.storeToken)(_authStore.ACCESS_TOKEN_KEY,t.accessToken,t.accessTokenExpiry),await(0,_authStore.storeToken)(_authStore.REFRESH_TOKEN_KEY,t.refreshToken,t.refreshTokenExpiry)};exports.storeReplacementSession=storeReplacementSession;const callRefreshEndpoint=async e=>{const t=await(0,_authStore.getToken)(_authStore.REFRESH_TOKEN_KEY);if(!t?.token)throw new SessionEndedError("No refresh token available");if(Date.parse(t.expiry)<=Date.now())throw new SessionEndedError("Refresh token expired");const s=await(0,_authStore.getToken)(_authStore.ACCESS_TOKEN_KEY),r={accessToken:s?.token??null,refreshToken:t.token},o=e.body?await e.body(r):r,n=e.headers?await e.headers():void 0;_index.Logger.info("Refreshing the access token");const{data:i}=await _publicClient.default.post(e.path,o,n?{headers:n}:void 0),a=(e.readTokens??readWeborityTokens)(i);if(await waitForSessionReplacement(),(await(0,_authStore.getToken)(_authStore.REFRESH_TOKEN_KEY))?.token!==t.token){const e=(await(0,_authStore.getToken)(_authStore.ACCESS_TOKEN_KEY))?.token;if(e)return _index.Logger.info("Keeping the session that replaced this one during the refresh"),e}return await(0,_authStore.storeToken)(_authStore.ACCESS_TOKEN_KEY,a.accessToken,a.accessTokenExpiry),await(0,_authStore.storeToken)(_authStore.REFRESH_TOKEN_KEY,a.refreshToken,a.refreshTokenExpiry),_index.Logger.info("Access token refreshed"),a.accessToken},refreshAccessToken=()=>{if(refreshTokenPromise)return refreshTokenPromise;const e=(async()=>{let e=null;try{const{refresh:t}=requireAuth().auth;e=(await(0,_authStore.getToken)(_authStore.ACCESS_TOKEN_KEY))?.token??null;const s=await callRefreshEndpoint(t);return processQueue(null,s),s}catch(t){if(endsSession(t)){await waitForSessionReplacement();const t=await currentTokenIfReplaced(e);if(t)return _index.Logger.info("The session was replaced during the refresh; continuing with the new one"),processQueue(null,t),t}const s=_axios.default.isAxiosError(t)?t.response?.data:void 0;if(_index.Logger.error("Token refresh failed:",s??getErrorMessage(t)),processQueue(t,null),endsSession(t)){const e=_axios.default.isAxiosError(t)&&401===t.response?.status,s=e?sessionCodeOf(t.response?.data):null,r=e?sessionDetailOf(t.response?.data):null;await endSessionAndLogout("rejected",s,r)}throw t}})().finally(()=>{refreshTokenPromise===e&&(refreshTokenPromise=null)});return refreshTokenPromise=e,e};exports.refreshAccessToken=refreshAccessToken;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.RequestOfflineError=void 0,Object.defineProperty(exports,"SESSION_EXPIRED_CODE",{enumerable:!0,get:function(){return _tokenRefresh.SESSION_EXPIRED_CODE}}),Object.defineProperty(exports,"SESSION_REVOKED_CODE",{enumerable:!0,get:function(){return _tokenRefresh.SESSION_REVOKED_CODE}}),exports.default=void 0,Object.defineProperty(exports,"injectStore",{enumerable:!0,get:function(){return _tokenRefresh.injectStore}}),Object.defineProperty(exports,"injectUserAuth",{enumerable:!0,get:function(){return _tokenRefresh.injectUserAuth}}),Object.defineProperty(exports,"resetRefreshState",{enumerable:!0,get:function(){return _tokenRefresh.resetRefreshState}});var _axios=_interopRequireWildcard(require("axios")),_authStore=require("../auth/authStore.js"),_index=require("../config/index.js"),_index2=require("../logger/index.js"),_networkStatus=require("../network/networkStatus.js");require("../types/axios.js");var cache,_baseUrl=require("./baseUrl.js"),_clientHeaders=require("./clientHeaders.js"),_curl=require("./curl.js"),_minAppVersion=require("./minAppVersion.js"),_multipart=require("./multipart.js"),_requestDefaults=require("./requestDefaults.js"),_responseBodyText=require("./responseBodyText.js"),_retry=require("./retry.js"),_tokenRefresh=require("./tokenRefresh.js"),_workspace=require("./workspace.js");function _interopRequireWildcard(e,r){if(!r&&e&&e.__esModule)return e;var t,s,n=Object.defineProperty,o={__proto__:null,default:e};if(Object(e)!==e)return o;if(cache||"function"!=typeof WeakMap||(cache=new WeakMap),cache){if(cache.has(e))return cache.get(e);cache.set(e,o)}for(s in e)"default"!==s&&{}.hasOwnProperty.call(e,s)&&((t=n&&Object.getOwnPropertyDescriptor(e,s))&&(t.get||t.set)?n(o,s,t):o[s]=e[s]);return o}class RequestOfflineError extends Error{constructor(e="Request blocked: device is offline"){super(e),this.name="RequestOfflineError",Object.setPrototypeOf(this,RequestOfflineError.prototype)}}exports.RequestOfflineError=RequestOfflineError;class NoAccessTokenError extends Error{constructor(){super("No access token available"),this.name="NoAccessTokenError",Object.setPrototypeOf(this,NoAccessTokenError.prototype)}}const endReplacement=e=>{e?._endReplacement?.(),e&&(e._endReplacement=void 0)},closeWindowIfTransformThrows=e=>{if(e._transformGuarded)return;e._transformGuarded=!0;const r=[].concat(e.transformRequest??[]);e.transformRequest=[function guarded(e,t){try{return r.reduce((e,r)=>r.call(this,e,t),e)}catch(e){throw endReplacement(this),e}}]},readSentToken=e=>{const r=e.headers instanceof _axios.AxiosHeaders?e.headers.get("Authorization"):void 0;return"string"==typeof r&&r.startsWith("Bearer ")?r.slice(7):null},userClient=_axios.default.create({headers:{"Content-Type":"application/json"}});(0,_minAppVersion.readMinAppVersion)(userClient),(0,_retry.attachRetry)(userClient);const setAuthHeader=(e,r)=>{e.headers instanceof _axios.AxiosHeaders?e.headers.set("Authorization",`Bearer ${r}`):e.headers=_axios.AxiosHeaders.from({...e.headers,Authorization:`Bearer ${r}`})},isRefreshTokenRequest=e=>{const r=(0,_tokenRefresh.getRefreshPath)();return!!r&&!!e?.includes(r)};userClient.interceptors.request.use(e=>{if(e.replacesSession&&!isRefreshTokenRequest(e.url)){const r=e;r._endReplacement=(0,_tokenRefresh.beginSessionReplacement)(),closeWindowIfTransformThrows(r)}return e}),(0,_baseUrl.applyConfiguredBaseUrl)(userClient),(0,_requestDefaults.applyRequestDefaults)(userClient),(0,_multipart.sendFormDataAsMultipart)(userClient),userClient.interceptors.request.use(e=>((0,_index.getConfigValue)("logCurl")&&(0,_curl.logCurl)(e),e)),(0,_clientHeaders.applyClientHeaders)(userClient),userClient.interceptors.request.use(async e=>{endReplacement(e);if(!1===(await(0,_networkStatus.getLastKnownNetworkStatus)()).isConnected)return _index2.Logger.warn(`Request blocked: device is offline. URL: ${e.url}`),Promise.reject(new RequestOfflineError);try{if(isRefreshTokenRequest(e.url))return e;await(0,_clientHeaders.stampAccountId)(e,(0,_tokenRefresh.getAccountIdProvider)());const r=await(0,_authStore.getToken)(_authStore.ACCESS_TOKEN_KEY);if(!r?.token)throw new NoAccessTokenError;const t=Date.now(),s=Date.parse(r.expiry);if(s<=t+3e4){_index2.Logger.info("Access token expired or about to expire, refreshing before request");const r=await(0,_tokenRefresh.refreshAccessToken)();setAuthHeader(e,r)}else setAuthHeader(e,r.token);return e}catch(r){return r instanceof NoAccessTokenError?_index2.Logger.info(`Request not sent: signed out. URL: ${e.url}`):_index2.Logger.error("Failed to attach token:",(0,_tokenRefresh.getErrorMessage)(r)),Promise.reject(r)}},e=>(_index2.Logger.error("Request error:",(0,_tokenRefresh.getErrorMessage)(e)),Promise.reject(e))),userClient.interceptors.response.use(async e=>{const r=e.config;if(r._endReplacement)try{await(0,_tokenRefresh.storeReplacementSession)(e.data)}finally{endReplacement(r)}return e},async e=>{if(endReplacement(e?.config),_axios.default.isCancel(e))return Promise.reject(e);if(e instanceof RequestOfflineError||e instanceof NoAccessTokenError)return Promise.reject(e);if(!_axios.default.isAxiosError(e))return _index2.Logger.error(`Non-axios error in response interceptor: ${(0,_tokenRefresh.getErrorMessage)(e)}`),Promise.reject(e);const r=e,t=r.config;if(!r.response)return t?.skipErrorLog||_index2.Logger.info(`Network error or no response from server: ${r.message}, URL: ${t?.url}`),Promise.reject(e);if((0,_workspace.isWorkspaceChangedResponse)(r))return _index2.Logger.info(`Refused for another workspace. URL: ${t?.url}`),Promise.reject(new _workspace.WorkspaceChangedError("Refused for another workspace",{response:r.response,config:t,cause:e}));if(401===r.response.status&&t&&!t._replayedWithCurrentToken&&!t.replacesSession&&!isRefreshTokenRequest(t.url)&&(await(0,_tokenRefresh.waitForSessionReplacement)(),await(0,_tokenRefresh.currentTokenIfReplaced)(readSentToken(t)))){const r=await(0,_clientHeaders.readAccountId)((0,_tokenRefresh.getAccountIdProvider)());return(t._accountId??null)!==r?(_index2.Logger.info(`Workspace changed in flight; not sent again. URL: ${t.url}`),Promise.reject(new _workspace.WorkspaceChangedError("Workspace changed in flight",{config:t,cause:e}))):(_index2.Logger.info(`Session changed while the request was in flight; sending it again. URL: ${t.url}`),t._replayedWithCurrentToken=!0,userClient(t))}if(401===r.response.status&&t&&!t._retry){if(isRefreshTokenRequest(t.url))return Promise.reject(e);const s=(0,_tokenRefresh.sessionCodeOf)(r.response.data);if(null!==s)return _index2.Logger.warn(`Session ${s}, logging out without refresh. URL: ${t.url}`),(0,_tokenRefresh.resetRefreshState)(),await(0,_tokenRefresh.endSessionAndLogout)("terminated",s),Promise.reject(e);if(_index2.Logger.warn(`Unauthorized URL: ${t.url}`),t._retry=!0,(0,_tokenRefresh.isRefreshInFlight)())return(0,_tokenRefresh.queueBehindRefresh)().then(e=>(_index2.Logger.info("Retrying request after token refresh:",t.url),setAuthHeader(t,e),userClient(t))).catch(e=>(_index2.Logger.error("Failed to retry request after token refresh:",(0,_tokenRefresh.getErrorMessage)(e)),Promise.reject(e)));try{const e=await(0,_tokenRefresh.refreshAccessToken)();return setAuthHeader(t,e),userClient(t)}catch(e){return Promise.reject(e)}}return t?.skipErrorLog||_index2.Logger.error(`Response error: ${(0,_responseBodyText.responseBodyText)(r.response?.data,r.message)}, URL: ${t?.url}, Status: ${r.response.status}`),Promise.reject(e)});var _default=exports.default=userClient;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.RequestOfflineError=void 0,Object.defineProperty(exports,"SESSION_EXPIRED_CODE",{enumerable:!0,get:function(){return _tokenRefresh.SESSION_EXPIRED_CODE}}),Object.defineProperty(exports,"SESSION_REVOKED_CODE",{enumerable:!0,get:function(){return _tokenRefresh.SESSION_REVOKED_CODE}}),exports.default=void 0,Object.defineProperty(exports,"injectStore",{enumerable:!0,get:function(){return _tokenRefresh.injectStore}}),Object.defineProperty(exports,"injectUserAuth",{enumerable:!0,get:function(){return _tokenRefresh.injectUserAuth}}),Object.defineProperty(exports,"resetRefreshState",{enumerable:!0,get:function(){return _tokenRefresh.resetRefreshState}});var _axios=_interopRequireWildcard(require("axios")),_authStore=require("../auth/authStore.js"),_index=require("../config/index.js"),_index2=require("../logger/index.js"),_networkStatus=require("../network/networkStatus.js");require("../types/axios.js");var cache,_baseUrl=require("./baseUrl.js"),_clientHeaders=require("./clientHeaders.js"),_curl=require("./curl.js"),_minAppVersion=require("./minAppVersion.js"),_multipart=require("./multipart.js"),_requestDefaults=require("./requestDefaults.js"),_responseBodyText=require("./responseBodyText.js"),_retry=require("./retry.js"),_tokenRefresh=require("./tokenRefresh.js"),_workspace=require("./workspace.js");function _interopRequireWildcard(e,r){if(!r&&e&&e.__esModule)return e;var t,s,n=Object.defineProperty,o={__proto__:null,default:e};if(Object(e)!==e)return o;if(cache||"function"!=typeof WeakMap||(cache=new WeakMap),cache){if(cache.has(e))return cache.get(e);cache.set(e,o)}for(s in e)"default"!==s&&{}.hasOwnProperty.call(e,s)&&((t=n&&Object.getOwnPropertyDescriptor(e,s))&&(t.get||t.set)?n(o,s,t):o[s]=e[s]);return o}class RequestOfflineError extends Error{constructor(e="Request blocked: device is offline"){super(e),this.name="RequestOfflineError",Object.setPrototypeOf(this,RequestOfflineError.prototype)}}exports.RequestOfflineError=RequestOfflineError;class NoAccessTokenError extends Error{constructor(){super("No access token available"),this.name="NoAccessTokenError",Object.setPrototypeOf(this,NoAccessTokenError.prototype)}}const endReplacement=e=>{e?._endReplacement?.(),e&&(e._endReplacement=void 0)},closeWindowIfTransformThrows=e=>{if(e._transformGuarded)return;e._transformGuarded=!0;const r=[].concat(e.transformRequest??[]);e.transformRequest=[function guarded(e,t){try{return r.reduce((e,r)=>r.call(this,e,t),e)}catch(e){throw endReplacement(this),e}}]},readSentToken=e=>{const r=e.headers instanceof _axios.AxiosHeaders?e.headers.get("Authorization"):void 0;return"string"==typeof r&&r.startsWith("Bearer ")?r.slice(7):null},userClient=_axios.default.create({headers:{"Content-Type":"application/json"}});(0,_minAppVersion.readMinAppVersion)(userClient),(0,_retry.attachRetry)(userClient);const setAuthHeader=(e,r)=>{e.headers instanceof _axios.AxiosHeaders?e.headers.set("Authorization",`Bearer ${r}`):e.headers=_axios.AxiosHeaders.from({...e.headers,Authorization:`Bearer ${r}`})},isRefreshTokenRequest=e=>{const r=(0,_tokenRefresh.getRefreshPath)();return!!r&&!!e?.includes(r)};userClient.interceptors.request.use(e=>{if(e.replacesSession&&!isRefreshTokenRequest(e.url)){const r=e;r._endReplacement=(0,_tokenRefresh.beginSessionReplacement)(),closeWindowIfTransformThrows(r)}return e}),(0,_baseUrl.applyConfiguredBaseUrl)(userClient),(0,_requestDefaults.applyRequestDefaults)(userClient),(0,_multipart.sendFormDataAsMultipart)(userClient),userClient.interceptors.request.use(e=>((0,_index.getConfigValue)("logCurl")&&(0,_curl.logCurl)(e),e)),(0,_clientHeaders.applyClientHeaders)(userClient),userClient.interceptors.request.use(async e=>{endReplacement(e);if(!1===(await(0,_networkStatus.getLastKnownNetworkStatus)()).isConnected)return _index2.Logger.warn(`Request blocked: device is offline. URL: ${e.url}`),Promise.reject(new RequestOfflineError);try{if(isRefreshTokenRequest(e.url))return e;await(0,_clientHeaders.stampAccountId)(e,(0,_tokenRefresh.getAccountIdProvider)());const r=await(0,_authStore.getToken)(_authStore.ACCESS_TOKEN_KEY);if(!r?.token)throw new NoAccessTokenError;const t=Date.now(),s=Date.parse(r.expiry);if(s<=t+3e4){_index2.Logger.info("Access token expired or about to expire, refreshing before request");const r=await(0,_tokenRefresh.refreshAccessToken)();setAuthHeader(e,r)}else setAuthHeader(e,r.token);return e}catch(r){return r instanceof NoAccessTokenError?_index2.Logger.info(`Request not sent: signed out. URL: ${e.url}`):_index2.Logger.error("Failed to attach token:",(0,_tokenRefresh.getErrorMessage)(r)),Promise.reject(r)}},e=>(_index2.Logger.error("Request error:",(0,_tokenRefresh.getErrorMessage)(e)),Promise.reject(e))),userClient.interceptors.response.use(async e=>{const r=e.config;if(r._endReplacement)try{await(0,_tokenRefresh.storeReplacementSession)(e.data)}finally{endReplacement(r)}return e},async e=>{if(endReplacement(e?.config),_axios.default.isCancel(e))return Promise.reject(e);if(e instanceof RequestOfflineError||e instanceof NoAccessTokenError)return Promise.reject(e);if(!_axios.default.isAxiosError(e))return _index2.Logger.error(`Non-axios error in response interceptor: ${(0,_tokenRefresh.getErrorMessage)(e)}`),Promise.reject(e);const r=e,t=r.config;if(!r.response)return t?.skipErrorLog||_index2.Logger.info(`Network error or no response from server: ${r.message}, URL: ${t?.url}`),Promise.reject(e);if((0,_workspace.isWorkspaceChangedResponse)(r))return _index2.Logger.info(`Refused for another workspace. URL: ${t?.url}`),Promise.reject(new _workspace.WorkspaceChangedError("Refused for another workspace",{response:r.response,config:t,cause:e}));if(401===r.response.status&&t&&!t._replayedWithCurrentToken&&!t.replacesSession&&!isRefreshTokenRequest(t.url)&&(await(0,_tokenRefresh.waitForSessionReplacement)(),await(0,_tokenRefresh.currentTokenIfReplaced)(readSentToken(t)))){const r=await(0,_clientHeaders.readAccountId)((0,_tokenRefresh.getAccountIdProvider)());return(t._accountId??null)!==r?(_index2.Logger.info(`Workspace changed in flight; not sent again. URL: ${t.url}`),Promise.reject(new _workspace.WorkspaceChangedError("Workspace changed in flight",{config:t,cause:e}))):(_index2.Logger.info(`Session changed while the request was in flight; sending it again. URL: ${t.url}`),t._replayedWithCurrentToken=!0,userClient(t))}if(401===r.response.status&&t&&!t._retry){if(isRefreshTokenRequest(t.url))return Promise.reject(e);const s=(0,_tokenRefresh.sessionCodeOf)(r.response.data);if(null!==s)return _index2.Logger.warn(`Session ${s}, logging out without refresh. URL: ${t.url}`),(0,_tokenRefresh.resetRefreshState)(),await(0,_tokenRefresh.endSessionAndLogout)("terminated",s,(0,_tokenRefresh.sessionDetailOf)(r.response.data)),Promise.reject(e);if(_index2.Logger.warn(`Unauthorized URL: ${t.url}`),t._retry=!0,(0,_tokenRefresh.isRefreshInFlight)())return(0,_tokenRefresh.queueBehindRefresh)().then(e=>(_index2.Logger.info("Retrying request after token refresh:",t.url),setAuthHeader(t,e),userClient(t))).catch(e=>(_index2.Logger.error("Failed to retry request after token refresh:",(0,_tokenRefresh.getErrorMessage)(e)),Promise.reject(e)));try{const e=await(0,_tokenRefresh.refreshAccessToken)();return setAuthHeader(t,e),userClient(t)}catch(e){return Promise.reject(e)}}return t?.skipErrorLog||_index2.Logger.error(`Response error: ${(0,_responseBodyText.responseBodyText)(r.response?.data,r.message)}, URL: ${t?.url}, Status: ${r.response.status}`),Promise.reject(e)});var _default=exports.default=userClient;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.storeToken=exports.storeSessionUser=exports.setLegacyTokenKeys=exports.setKeychainImplementation=exports.removeToken=exports.removeSessionUser=exports.getToken=exports.getSessionUser=exports.clearSession=exports.addSessionEndedListener=exports.SESSION_USER_KEY=exports.REFRESH_TOKEN_KEY=exports.LEGACY_REFRESH_TOKEN_LIFETIME_MS=exports.ACCESS_TOKEN_KEY=void 0;var cache,SecureStore=_interopRequireWildcard(require("expo-secure-store")),_index=require("../logger/index.js"),_index2=require("../storage/index.js"),_jwt=require("./jwt.js");function _interopRequireWildcard(e,r){if(!r&&e&&e.__esModule)return e;var t,n,o=Object.defineProperty,s={__proto__:null,default:e};if(Object(e)!==e)return s;if(cache||"function"!=typeof WeakMap||(cache=new WeakMap),cache){if(cache.has(e))return cache.get(e);cache.set(e,s)}for(n in e)"default"!==n&&{}.hasOwnProperty.call(e,n)&&((t=o&&Object.getOwnPropertyDescriptor(e,n))&&(t.get||t.set)?o(s,n,t):s[n]=e[n]);return s}const ACCESS_TOKEN_KEY=exports.ACCESS_TOKEN_KEY="accessToken",REFRESH_TOKEN_KEY=exports.REFRESH_TOKEN_KEY="refreshToken",SESSION_USER_KEY=exports.SESSION_USER_KEY="sessionUser",LEGACY_REFRESH_TOKEN_LIFETIME_MS=exports.LEGACY_REFRESH_TOKEN_LIFETIME_MS=2592e6;let backend=null;const keyFor=e=>(e??"default").replace(/[^A-Za-z0-9._-]/g,"_"),expoSecureStoreBackend={setGenericPassword:async(e,r,t)=>(await SecureStore.setItemAsync(keyFor(t?.service),JSON.stringify({username:e,password:r})),!0),getGenericPassword:async e=>{const r=e?.service,t=await SecureStore.getItemAsync(keyFor(r));if(null===t)return!1;const n=JSON.parse(t);return{username:n.username,password:n.password,service:r??"default"}},resetGenericPassword:async e=>(await SecureStore.deleteItemAsync(keyFor(e?.service)),!0)},resolveBackend=()=>backend??expoSecureStoreBackend,memo=new Map;let generation=0;const forget=e=>{generation+=1,void 0===e?memo.clear():memo.delete(e)},requireBackend=e=>{const r=resolveBackend();if(!r){const r=new Error(`[authStore] no secure-store backend available; cannot ${e}.`);throw _index.Logger.error(r.message),r}return r},setKeychainImplementation=e=>{backend=e,forget()};exports.setKeychainImplementation=setKeychainImplementation;const storeToken=async(e,r,t)=>{_index.Logger.info(`Storing token for key: ${e}`),forget(e);try{const n=JSON.stringify({token:r,expiry:t});await requireBackend("store a token").setGenericPassword("token",n,{service:e}),forget(e),memo.set(e,{token:r,expiry:t}),_index.Logger.info(`Token stored successfully for key: ${e}`)}catch(r){throw forget(e),_index.Logger.error(`Error storing token for key: ${e}`,r),r}};exports.storeToken=storeToken;const parseTokenData=e=>{if(!e)return null;const{password:r}=e;try{const e=JSON.parse(r);return e&&"object"==typeof e&&"token"in e&&"expiry"in e&&"string"==typeof e.token&&"string"==typeof e.expiry?e:null}catch{return null}},getToken=async e=>{if(isSessionKey(e)&&legacyKeys&&await migrateLegacySession(),memo.has(e))return memo.get(e)??null;const r=generation;try{const t=await requireBackend("read a token").getGenericPassword({service:e});let n=null;if(t){const r=parseTokenData(t);if(r)n={token:r.token,expiry:r.expiry};else if(_index.Logger.warn(`Stored credentials for key "${e}" are not a valid token; ignoring them.`),isSessionKey(e))return await endSession("corrupt",!0),null}return r===generation&&memo.set(e,n),n}catch(r){const t=r instanceof Error?r.message:String(r);return t.includes("Key permanently invalidated")||t.includes("Secret invalidated")?(_index.Logger.error(`Stored token for key "${e}" can no longer be decrypted; clearing it.`),isSessionKey(e)?await endSession("corrupt",!0):await removeToken(e),null):(_index.Logger.error(`Error retrieving token for key: ${e}`,r),null)}};exports.getToken=getToken;const removeToken=async e=>{forget(e);try{await requireBackend("remove a token").resetGenericPassword({service:e}),_index.Logger.info(`Token removed successfully for key: ${e}`)}catch(r){if(r instanceof Error&&r.message.includes("Item not found"))return void _index.Logger.info(`No token found to remove for key: ${e}`);_index.Logger.error(`Failed to remove token for key: ${e}`,r)}finally{forget(e)}};exports.removeToken=removeToken;const isSessionKey=e=>e===ACCESS_TOKEN_KEY||e===REFRESH_TOKEN_KEY,storeSessionUser=async e=>{if(!await _index2.Storage.setItem(SESSION_USER_KEY,JSON.stringify(e)))throw new Error("[authStore] could not store the session user.")};exports.storeSessionUser=storeSessionUser;const getSessionUser=async()=>{const e=await _index2.Storage.getItem(SESSION_USER_KEY);if(null===e)return null;try{return JSON.parse(e)}catch{return _index.Logger.warn("The stored session user is not valid JSON; clearing it."),await removeSessionUser(),null}};exports.getSessionUser=getSessionUser;const removeSessionUser=async()=>{await _index2.Storage.removeItem(SESSION_USER_KEY)};exports.removeSessionUser=removeSessionUser;let legacyKeys=null,legacyCheck=null;const setLegacyTokenKeys=e=>{legacyKeys=e,legacyCheck=null};exports.setLegacyTokenKeys=setLegacyTokenKeys;const readLegacyToken=e=>{try{const r=JSON.parse(e);if("string"==typeof r)return""===r.trim()?null:{token:r,expiry:null};if(r&&"object"==typeof r&&"string"==typeof r.token&&"string"==typeof r.expiry)return r}catch{}return""===e.trim()?null:{token:e,expiry:null}},expiryOf=e=>3===e.split(".").length?(0,_jwt.getJWTExpiry)(e)?.toISOString()??null:null,readLegacyRaw=async e=>{const r=await resolveBackend().getGenericPassword({service:e});return r?r.password:null},migrateLegacySession=()=>{if(!legacyKeys)return Promise.resolve();if(legacyCheck)return legacyCheck;const e=legacyKeys;return legacyCheck=(async()=>{try{const r=resolveBackend(),t=!await r.getGenericPassword({service:ACCESS_TOKEN_KEY})&&!await r.getGenericPassword({service:REFRESH_TOKEN_KEY}),n=await readLegacyRaw(e.accessToken),o=await readLegacyRaw(e.refreshToken);if(t&&(null!==n||null!==o)){const t=null===n?null:readLegacyToken(n),s=null===o?null:readLegacyToken(o);if(t){const e=t.expiry??expiryOf(t.token)??(new Date).toISOString();await r.setGenericPassword("token",JSON.stringify({token:t.token,expiry:e}),{service:ACCESS_TOKEN_KEY})}if(s){const e=s.expiry??expiryOf(s.token)??new Date(Date.now()+LEGACY_REFRESH_TOKEN_LIFETIME_MS).toISOString();await r.setGenericPassword("token",JSON.stringify({token:s.token,expiry:e}),{service:REFRESH_TOKEN_KEY})}if(e.sessionUser){const r=await _index2.Storage.getItem(e.sessionUser);null!==r&&await _index2.Storage.setItem(SESSION_USER_KEY,r)}null===t&&null===s||(forget(),_index.Logger.breadcrumb("[authStore] moved a session from the legacy token keys"))}for(const t of[e.accessToken,e.refreshToken])await r.resetGenericPassword({service:t}).catch(()=>{});e.sessionUser&&await _index2.Storage.removeItem(e.sessionUser)}catch(e){_index.Logger.error("[authStore] could not move the session from the legacy token keys",e)}})(),legacyCheck},listeners=new Set,emitSessionEnded=(e,r)=>{for(const t of[...listeners])try{t(e,r)}catch(e){_index.Logger.error("[authStore] a session-ended listener threw",e)}},addSessionEndedListener=e=>(listeners.add(e),()=>{listeners.delete(e)});exports.addSessionEndedListener=addSessionEndedListener;const endSession=async(e,r,t=null)=>{const n=r||null!==await getToken(ACCESS_TOKEN_KEY)||null!==await getToken(REFRESH_TOKEN_KEY);await removeToken(ACCESS_TOKEN_KEY),await removeToken(REFRESH_TOKEN_KEY),await removeSessionUser(),n&&emitSessionEnded(e,{code:t})},clearSession=async(e="signedOut",r={})=>endSession(e,!1,r.code??null);exports.clearSession=clearSession;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.storeToken=exports.storeSessionUser=exports.setLegacyTokenKeys=exports.setKeychainImplementation=exports.removeToken=exports.removeSessionUser=exports.getToken=exports.getSessionUser=exports.clearSession=exports.addSessionEndedListener=exports.SESSION_USER_KEY=exports.REFRESH_TOKEN_KEY=exports.LEGACY_REFRESH_TOKEN_LIFETIME_MS=exports.ACCESS_TOKEN_KEY=void 0;var cache,SecureStore=_interopRequireWildcard(require("expo-secure-store")),_index=require("../logger/index.js"),_index2=require("../storage/index.js"),_jwt=require("./jwt.js");function _interopRequireWildcard(e,r){if(!r&&e&&e.__esModule)return e;var t,n,o=Object.defineProperty,s={__proto__:null,default:e};if(Object(e)!==e)return s;if(cache||"function"!=typeof WeakMap||(cache=new WeakMap),cache){if(cache.has(e))return cache.get(e);cache.set(e,s)}for(n in e)"default"!==n&&{}.hasOwnProperty.call(e,n)&&((t=o&&Object.getOwnPropertyDescriptor(e,n))&&(t.get||t.set)?o(s,n,t):s[n]=e[n]);return s}const ACCESS_TOKEN_KEY=exports.ACCESS_TOKEN_KEY="accessToken",REFRESH_TOKEN_KEY=exports.REFRESH_TOKEN_KEY="refreshToken",SESSION_USER_KEY=exports.SESSION_USER_KEY="sessionUser",LEGACY_REFRESH_TOKEN_LIFETIME_MS=exports.LEGACY_REFRESH_TOKEN_LIFETIME_MS=2592e6;let backend=null;const keyFor=e=>(e??"default").replace(/[^A-Za-z0-9._-]/g,"_"),expoSecureStoreBackend={setGenericPassword:async(e,r,t)=>(await SecureStore.setItemAsync(keyFor(t?.service),JSON.stringify({username:e,password:r})),!0),getGenericPassword:async e=>{const r=e?.service,t=await SecureStore.getItemAsync(keyFor(r));if(null===t)return!1;const n=JSON.parse(t);return{username:n.username,password:n.password,service:r??"default"}},resetGenericPassword:async e=>(await SecureStore.deleteItemAsync(keyFor(e?.service)),!0)},resolveBackend=()=>backend??expoSecureStoreBackend,memo=new Map;let generation=0;const forget=e=>{generation+=1,void 0===e?memo.clear():memo.delete(e)},requireBackend=e=>{const r=resolveBackend();if(!r){const r=new Error(`[authStore] no secure-store backend available; cannot ${e}.`);throw _index.Logger.error(r.message),r}return r},setKeychainImplementation=e=>{backend=e,forget()};exports.setKeychainImplementation=setKeychainImplementation;const storeToken=async(e,r,t)=>{_index.Logger.info(`Storing token for key: ${e}`),forget(e);try{const n=JSON.stringify({token:r,expiry:t});await requireBackend("store a token").setGenericPassword("token",n,{service:e}),forget(e),memo.set(e,{token:r,expiry:t}),_index.Logger.info(`Token stored successfully for key: ${e}`)}catch(r){throw forget(e),_index.Logger.error(`Error storing token for key: ${e}`,r),r}};exports.storeToken=storeToken;const parseTokenData=e=>{if(!e)return null;const{password:r}=e;try{const e=JSON.parse(r);return e&&"object"==typeof e&&"token"in e&&"expiry"in e&&"string"==typeof e.token&&"string"==typeof e.expiry?e:null}catch{return null}},getToken=async e=>{if(isSessionKey(e)&&legacyKeys&&await migrateLegacySession(),memo.has(e))return memo.get(e)??null;const r=generation;try{const t=await requireBackend("read a token").getGenericPassword({service:e});let n=null;if(t){const r=parseTokenData(t);if(r)n={token:r.token,expiry:r.expiry};else if(_index.Logger.warn(`Stored credentials for key "${e}" are not a valid token; ignoring them.`),isSessionKey(e))return await endSession("corrupt",!0),null}return r===generation&&memo.set(e,n),n}catch(r){const t=r instanceof Error?r.message:String(r);return t.includes("Key permanently invalidated")||t.includes("Secret invalidated")?(_index.Logger.error(`Stored token for key "${e}" can no longer be decrypted; clearing it.`),isSessionKey(e)?await endSession("corrupt",!0):await removeToken(e),null):(_index.Logger.error(`Error retrieving token for key: ${e}`,r),null)}};exports.getToken=getToken;const removeToken=async e=>{forget(e);try{await requireBackend("remove a token").resetGenericPassword({service:e}),_index.Logger.info(`Token removed successfully for key: ${e}`)}catch(r){if(r instanceof Error&&r.message.includes("Item not found"))return void _index.Logger.info(`No token found to remove for key: ${e}`);_index.Logger.error(`Failed to remove token for key: ${e}`,r)}finally{forget(e)}};exports.removeToken=removeToken;const isSessionKey=e=>e===ACCESS_TOKEN_KEY||e===REFRESH_TOKEN_KEY,storeSessionUser=async e=>{if(!await _index2.Storage.setItem(SESSION_USER_KEY,JSON.stringify(e)))throw new Error("[authStore] could not store the session user.")};exports.storeSessionUser=storeSessionUser;const getSessionUser=async()=>{const e=await _index2.Storage.getItem(SESSION_USER_KEY);if(null===e)return null;try{return JSON.parse(e)}catch{return _index.Logger.warn("The stored session user is not valid JSON; clearing it."),await removeSessionUser(),null}};exports.getSessionUser=getSessionUser;const removeSessionUser=async()=>{await _index2.Storage.removeItem(SESSION_USER_KEY)};exports.removeSessionUser=removeSessionUser;let legacyKeys=null,legacyCheck=null;const setLegacyTokenKeys=e=>{legacyKeys=e,legacyCheck=null};exports.setLegacyTokenKeys=setLegacyTokenKeys;const readLegacyToken=e=>{try{const r=JSON.parse(e);if("string"==typeof r)return""===r.trim()?null:{token:r,expiry:null};if(r&&"object"==typeof r&&"string"==typeof r.token&&"string"==typeof r.expiry)return r}catch{}return""===e.trim()?null:{token:e,expiry:null}},expiryOf=e=>3===e.split(".").length?(0,_jwt.getJWTExpiry)(e)?.toISOString()??null:null,readLegacyRaw=async e=>{const r=await resolveBackend().getGenericPassword({service:e});return r?r.password:null},migrateLegacySession=()=>{if(!legacyKeys)return Promise.resolve();if(legacyCheck)return legacyCheck;const e=legacyKeys;return legacyCheck=(async()=>{try{const r=resolveBackend(),t=!await r.getGenericPassword({service:ACCESS_TOKEN_KEY})&&!await r.getGenericPassword({service:REFRESH_TOKEN_KEY}),n=await readLegacyRaw(e.accessToken),o=await readLegacyRaw(e.refreshToken);if(t&&(null!==n||null!==o)){const t=null===n?null:readLegacyToken(n),s=null===o?null:readLegacyToken(o);if(t){const e=t.expiry??expiryOf(t.token)??(new Date).toISOString();await r.setGenericPassword("token",JSON.stringify({token:t.token,expiry:e}),{service:ACCESS_TOKEN_KEY})}if(s){const e=s.expiry??expiryOf(s.token)??new Date(Date.now()+LEGACY_REFRESH_TOKEN_LIFETIME_MS).toISOString();await r.setGenericPassword("token",JSON.stringify({token:s.token,expiry:e}),{service:REFRESH_TOKEN_KEY})}if(e.sessionUser){const r=await _index2.Storage.getItem(e.sessionUser);null!==r&&await _index2.Storage.setItem(SESSION_USER_KEY,r)}null===t&&null===s||(forget(),_index.Logger.breadcrumb("[authStore] moved a session from the legacy token keys"))}for(const t of[e.accessToken,e.refreshToken])await r.resetGenericPassword({service:t}).catch(()=>{});e.sessionUser&&await _index2.Storage.removeItem(e.sessionUser)}catch(e){_index.Logger.error("[authStore] could not move the session from the legacy token keys",e)}})(),legacyCheck},listeners=new Set,emitSessionEnded=(e,r)=>{for(const t of[...listeners])try{t(e,r)}catch(e){_index.Logger.error("[authStore] a session-ended listener threw",e)}},addSessionEndedListener=e=>(listeners.add(e),()=>{listeners.delete(e)});exports.addSessionEndedListener=addSessionEndedListener;const endSession=async(e,r,t=null,n=null)=>{const o=r||null!==await getToken(ACCESS_TOKEN_KEY)||null!==await getToken(REFRESH_TOKEN_KEY);await removeToken(ACCESS_TOKEN_KEY),await removeToken(REFRESH_TOKEN_KEY),await removeSessionUser(),o&&emitSessionEnded(e,{code:t,detail:n})},clearSession=async(e="signedOut",r={})=>endSession(e,!1,r.code??null,r.detail??null);exports.clearSession=clearSession;
@@ -1 +1 @@
1
- "use strict";import axios from"axios";import{ACCESS_TOKEN_KEY,clearSession,getToken,REFRESH_TOKEN_KEY,storeToken}from"../auth/authStore.js";import{Logger}from"../logger/index.js";import publicClient from"./publicClient.js";export const SESSION_REVOKED_CODE="session_revoked";export const SESSION_EXPIRED_CODE="session_expired";let store,auth;export const injectStore=e=>{store=e};export const injectUserAuth=e=>{auth=e};let refreshTokenPromise=null,failedQueue=[];const processQueue=(e,r=null)=>{failedQueue.forEach(({resolve:o,reject:s})=>{e?s(e):null!==r&&o(r)}),failedQueue=[]};export const resetRefreshState=()=>{refreshTokenPromise=null,processQueue(new Error("[@webority/mobile-core] Session ended"))};export const getErrorMessage=e=>e instanceof Error?e.message:"string"==typeof e?e:"Unknown error";const requireAuth=()=>{if(!store||!auth)throw new Error("User package store is not initialized. Call initUserAuth first.");return{store:store,auth:auth}};export const getAccountIdProvider=()=>auth?.accountId;export const getRefreshPath=()=>auth?.refresh.path;export const dispatchLogout=async()=>{const e=requireAuth();await e.store.dispatch(e.auth.logout())};export const endSessionAndLogout=async(e,r)=>{await clearSession(e,{code:r}),await dispatchLogout()};export const sessionCodeOf=e=>{const r=e&&"object"==typeof e?e.code:void 0;return"session_revoked"===r||"session_expired"===r?r:null};export const isRefreshInFlight=()=>null!==refreshTokenPromise;let replacementCount=0,replacementSettled=Promise.resolve(),settleReplacement=null;export const beginSessionReplacement=()=>{0===replacementCount&&(replacementSettled=new Promise(e=>{settleReplacement=e})),replacementCount+=1;let e=!1;return()=>{e||(e=!0,replacementCount-=1,0===replacementCount&&(settleReplacement?.(),settleReplacement=null))}};export const waitForSessionReplacement=()=>0===replacementCount?Promise.resolve():replacementSettled;export const currentTokenIfReplaced=async e=>{const r=(await getToken(ACCESS_TOKEN_KEY))?.token??null;return null!==r&&r!==e?r:null};export const queueBehindRefresh=()=>new Promise((e,r)=>{failedQueue.push({resolve:e,reject:r})});class SessionEndedError extends Error{constructor(e){super(e),this.name="SessionEndedError",Object.setPrototypeOf(this,SessionEndedError.prototype)}}const endsSession=e=>e instanceof SessionEndedError||axios.isAxiosError(e)&&(400===e.response?.status||401===e.response?.status),asString=(e,r)=>{if("string"!=typeof e||""===e)throw new Error(`The refresh response has no "${r}".`);return e},readWeborityTokens=e=>{const r=e??{};return{accessToken:asString(r.accessToken,"accessToken"),accessTokenExpiry:asString(r.accessTokenExpiration,"accessTokenExpiration"),refreshToken:asString(r.refreshToken,"refreshToken"),refreshTokenExpiry:asString(r.refreshTokenExpiration,"refreshTokenExpiration")}};export const storeReplacementSession=async e=>{let r;try{r=(auth?.refresh.readTokens??readWeborityTokens)(e)}catch(e){return void Logger.error("A session-replacing response carried no tokens:",getErrorMessage(e))}await storeToken(ACCESS_TOKEN_KEY,r.accessToken,r.accessTokenExpiry),await storeToken(REFRESH_TOKEN_KEY,r.refreshToken,r.refreshTokenExpiry)};const callRefreshEndpoint=async e=>{const r=await getToken(REFRESH_TOKEN_KEY);if(!r?.token)throw new SessionEndedError("No refresh token available");if(Date.parse(r.expiry)<=Date.now())throw new SessionEndedError("Refresh token expired");const o=await getToken(ACCESS_TOKEN_KEY),s={accessToken:o?.token??null,refreshToken:r.token},t=e.body?await e.body(s):s,n=e.headers?await e.headers():void 0;Logger.info("Refreshing the access token");const{data:i}=await publicClient.post(e.path,t,n?{headers:n}:void 0),a=(e.readTokens??readWeborityTokens)(i);if(await waitForSessionReplacement(),(await getToken(REFRESH_TOKEN_KEY))?.token!==r.token){const e=(await getToken(ACCESS_TOKEN_KEY))?.token;if(e)return Logger.info("Keeping the session that replaced this one during the refresh"),e}return await storeToken(ACCESS_TOKEN_KEY,a.accessToken,a.accessTokenExpiry),await storeToken(REFRESH_TOKEN_KEY,a.refreshToken,a.refreshTokenExpiry),Logger.info("Access token refreshed"),a.accessToken};export const refreshAccessToken=()=>{if(refreshTokenPromise)return refreshTokenPromise;const e=(async()=>{let e=null;try{const{refresh:r}=requireAuth().auth;e=(await getToken(ACCESS_TOKEN_KEY))?.token??null;const o=await callRefreshEndpoint(r);return processQueue(null,o),o}catch(r){if(endsSession(r)){await waitForSessionReplacement();const r=await currentTokenIfReplaced(e);if(r)return Logger.info("The session was replaced during the refresh; continuing with the new one"),processQueue(null,r),r}const o=axios.isAxiosError(r)?r.response?.data:void 0;if(Logger.error("Token refresh failed:",o??getErrorMessage(r)),processQueue(r,null),endsSession(r)){const e=axios.isAxiosError(r)&&401===r.response?.status?sessionCodeOf(r.response.data):null;await endSessionAndLogout("rejected",e)}throw r}})().finally(()=>{refreshTokenPromise===e&&(refreshTokenPromise=null)});return refreshTokenPromise=e,e};
1
+ "use strict";import axios from"axios";import{ACCESS_TOKEN_KEY,clearSession,getToken,REFRESH_TOKEN_KEY,storeToken}from"../auth/authStore.js";import{Logger}from"../logger/index.js";import publicClient from"./publicClient.js";export const SESSION_REVOKED_CODE="session_revoked";export const SESSION_EXPIRED_CODE="session_expired";let store,auth;export const injectStore=e=>{store=e};export const injectUserAuth=e=>{auth=e};let refreshTokenPromise=null,failedQueue=[];const processQueue=(e,r=null)=>{failedQueue.forEach(({resolve:o,reject:s})=>{e?s(e):null!==r&&o(r)}),failedQueue=[]};export const resetRefreshState=()=>{refreshTokenPromise=null,processQueue(new Error("[@webority/mobile-core] Session ended"))};export const getErrorMessage=e=>e instanceof Error?e.message:"string"==typeof e?e:"Unknown error";const requireAuth=()=>{if(!store||!auth)throw new Error("User package store is not initialized. Call initUserAuth first.");return{store:store,auth:auth}};export const getAccountIdProvider=()=>auth?.accountId;export const getRefreshPath=()=>auth?.refresh.path;export const dispatchLogout=async()=>{const e=requireAuth();await e.store.dispatch(e.auth.logout())};export const endSessionAndLogout=async(e,r,o=null)=>{await clearSession(e,{code:r,detail:o}),await dispatchLogout()};export const sessionCodeOf=e=>{const r=e&&"object"==typeof e?e.code:void 0;return"session_revoked"===r||"session_expired"===r?r:null};export const sessionDetailOf=e=>{if(null===sessionCodeOf(e))return null;const r=e.detail;return"string"==typeof r&&""!==r?r:null};export const isRefreshInFlight=()=>null!==refreshTokenPromise;let replacementCount=0,replacementSettled=Promise.resolve(),settleReplacement=null;export const beginSessionReplacement=()=>{0===replacementCount&&(replacementSettled=new Promise(e=>{settleReplacement=e})),replacementCount+=1;let e=!1;return()=>{e||(e=!0,replacementCount-=1,0===replacementCount&&(settleReplacement?.(),settleReplacement=null))}};export const waitForSessionReplacement=()=>0===replacementCount?Promise.resolve():replacementSettled;export const currentTokenIfReplaced=async e=>{const r=(await getToken(ACCESS_TOKEN_KEY))?.token??null;return null!==r&&r!==e?r:null};export const queueBehindRefresh=()=>new Promise((e,r)=>{failedQueue.push({resolve:e,reject:r})});class SessionEndedError extends Error{constructor(e){super(e),this.name="SessionEndedError",Object.setPrototypeOf(this,SessionEndedError.prototype)}}const endsSession=e=>e instanceof SessionEndedError||axios.isAxiosError(e)&&(400===e.response?.status||401===e.response?.status),asString=(e,r)=>{if("string"!=typeof e||""===e)throw new Error(`The refresh response has no "${r}".`);return e},readWeborityTokens=e=>{const r=e??{};return{accessToken:asString(r.accessToken,"accessToken"),accessTokenExpiry:asString(r.accessTokenExpiration,"accessTokenExpiration"),refreshToken:asString(r.refreshToken,"refreshToken"),refreshTokenExpiry:asString(r.refreshTokenExpiration,"refreshTokenExpiration")}};export const storeReplacementSession=async e=>{let r;try{r=(auth?.refresh.readTokens??readWeborityTokens)(e)}catch(e){return void Logger.error("A session-replacing response carried no tokens:",getErrorMessage(e))}await storeToken(ACCESS_TOKEN_KEY,r.accessToken,r.accessTokenExpiry),await storeToken(REFRESH_TOKEN_KEY,r.refreshToken,r.refreshTokenExpiry)};const callRefreshEndpoint=async e=>{const r=await getToken(REFRESH_TOKEN_KEY);if(!r?.token)throw new SessionEndedError("No refresh token available");if(Date.parse(r.expiry)<=Date.now())throw new SessionEndedError("Refresh token expired");const o=await getToken(ACCESS_TOKEN_KEY),s={accessToken:o?.token??null,refreshToken:r.token},t=e.body?await e.body(s):s,n=e.headers?await e.headers():void 0;Logger.info("Refreshing the access token");const{data:i}=await publicClient.post(e.path,t,n?{headers:n}:void 0),a=(e.readTokens??readWeborityTokens)(i);if(await waitForSessionReplacement(),(await getToken(REFRESH_TOKEN_KEY))?.token!==r.token){const e=(await getToken(ACCESS_TOKEN_KEY))?.token;if(e)return Logger.info("Keeping the session that replaced this one during the refresh"),e}return await storeToken(ACCESS_TOKEN_KEY,a.accessToken,a.accessTokenExpiry),await storeToken(REFRESH_TOKEN_KEY,a.refreshToken,a.refreshTokenExpiry),Logger.info("Access token refreshed"),a.accessToken};export const refreshAccessToken=()=>{if(refreshTokenPromise)return refreshTokenPromise;const e=(async()=>{let e=null;try{const{refresh:r}=requireAuth().auth;e=(await getToken(ACCESS_TOKEN_KEY))?.token??null;const o=await callRefreshEndpoint(r);return processQueue(null,o),o}catch(r){if(endsSession(r)){await waitForSessionReplacement();const r=await currentTokenIfReplaced(e);if(r)return Logger.info("The session was replaced during the refresh; continuing with the new one"),processQueue(null,r),r}const o=axios.isAxiosError(r)?r.response?.data:void 0;if(Logger.error("Token refresh failed:",o??getErrorMessage(r)),processQueue(r,null),endsSession(r)){const e=axios.isAxiosError(r)&&401===r.response?.status,o=e?sessionCodeOf(r.response?.data):null,s=e?sessionDetailOf(r.response?.data):null;await endSessionAndLogout("rejected",o,s)}throw r}})().finally(()=>{refreshTokenPromise===e&&(refreshTokenPromise=null)});return refreshTokenPromise=e,e};
@@ -1 +1 @@
1
- "use strict";import axios,{AxiosHeaders}from"axios";import{ACCESS_TOKEN_KEY,getToken}from"../auth/authStore.js";import{getConfigValue}from"../config/index.js";import{Logger}from"../logger/index.js";import{getLastKnownNetworkStatus}from"../network/networkStatus.js";import"../types/axios.js";import{applyConfiguredBaseUrl}from"./baseUrl.js";import{applyClientHeaders,readAccountId,stampAccountId}from"./clientHeaders.js";import{logCurl}from"./curl.js";import{readMinAppVersion}from"./minAppVersion.js";import{sendFormDataAsMultipart}from"./multipart.js";import{applyRequestDefaults}from"./requestDefaults.js";import{responseBodyText}from"./responseBodyText.js";import{attachRetry}from"./retry.js";import{beginSessionReplacement,currentTokenIfReplaced,endSessionAndLogout,getAccountIdProvider,getErrorMessage,getRefreshPath,isRefreshInFlight,queueBehindRefresh,refreshAccessToken,resetRefreshState,sessionCodeOf,storeReplacementSession,waitForSessionReplacement}from"./tokenRefresh.js";import{isWorkspaceChangedResponse,WorkspaceChangedError}from"./workspace.js";export{injectStore,injectUserAuth,resetRefreshState,SESSION_EXPIRED_CODE,SESSION_REVOKED_CODE}from"./tokenRefresh.js";export class RequestOfflineError extends Error{constructor(e="Request blocked: device is offline"){super(e),this.name="RequestOfflineError",Object.setPrototypeOf(this,RequestOfflineError.prototype)}}class NoAccessTokenError extends Error{constructor(){super("No access token available"),this.name="NoAccessTokenError",Object.setPrototypeOf(this,NoAccessTokenError.prototype)}}const endReplacement=e=>{e?._endReplacement?.(),e&&(e._endReplacement=void 0)},closeWindowIfTransformThrows=e=>{if(e._transformGuarded)return;e._transformGuarded=!0;const r=[].concat(e.transformRequest??[]);e.transformRequest=[function guarded(e,t){try{return r.reduce((e,r)=>r.call(this,e,t),e)}catch(e){throw endReplacement(this),e}}]},readSentToken=e=>{const r=e.headers instanceof AxiosHeaders?e.headers.get("Authorization"):void 0;return"string"==typeof r&&r.startsWith("Bearer ")?r.slice(7):null},userClient=axios.create({headers:{"Content-Type":"application/json"}});readMinAppVersion(userClient),attachRetry(userClient);const setAuthHeader=(e,r)=>{e.headers instanceof AxiosHeaders?e.headers.set("Authorization",`Bearer ${r}`):e.headers=AxiosHeaders.from({...e.headers,Authorization:`Bearer ${r}`})},isRefreshTokenRequest=e=>{const r=getRefreshPath();return!!r&&!!e?.includes(r)};userClient.interceptors.request.use(e=>{if(e.replacesSession&&!isRefreshTokenRequest(e.url)){const r=e;r._endReplacement=beginSessionReplacement(),closeWindowIfTransformThrows(r)}return e}),applyConfiguredBaseUrl(userClient),applyRequestDefaults(userClient),sendFormDataAsMultipart(userClient),userClient.interceptors.request.use(e=>(getConfigValue("logCurl")&&logCurl(e),e)),applyClientHeaders(userClient),userClient.interceptors.request.use(async e=>{endReplacement(e);if(!1===(await getLastKnownNetworkStatus()).isConnected)return Logger.warn(`Request blocked: device is offline. URL: ${e.url}`),Promise.reject(new RequestOfflineError);try{if(isRefreshTokenRequest(e.url))return e;await stampAccountId(e,getAccountIdProvider());const r=await getToken(ACCESS_TOKEN_KEY);if(!r?.token)throw new NoAccessTokenError;const t=Date.now(),s=Date.parse(r.expiry);if(s<=t+3e4){Logger.info("Access token expired or about to expire, refreshing before request");const r=await refreshAccessToken();setAuthHeader(e,r)}else setAuthHeader(e,r.token);return e}catch(r){return r instanceof NoAccessTokenError?Logger.info(`Request not sent: signed out. URL: ${e.url}`):Logger.error("Failed to attach token:",getErrorMessage(r)),Promise.reject(r)}},e=>(Logger.error("Request error:",getErrorMessage(e)),Promise.reject(e))),userClient.interceptors.response.use(async e=>{const r=e.config;if(r._endReplacement)try{await storeReplacementSession(e.data)}finally{endReplacement(r)}return e},async e=>{if(endReplacement(e?.config),axios.isCancel(e))return Promise.reject(e);if(e instanceof RequestOfflineError||e instanceof NoAccessTokenError)return Promise.reject(e);if(!axios.isAxiosError(e))return Logger.error(`Non-axios error in response interceptor: ${getErrorMessage(e)}`),Promise.reject(e);const r=e,t=r.config;if(!r.response)return t?.skipErrorLog||Logger.info(`Network error or no response from server: ${r.message}, URL: ${t?.url}`),Promise.reject(e);if(isWorkspaceChangedResponse(r))return Logger.info(`Refused for another workspace. URL: ${t?.url}`),Promise.reject(new WorkspaceChangedError("Refused for another workspace",{response:r.response,config:t,cause:e}));if(401===r.response.status&&t&&!t._replayedWithCurrentToken&&!t.replacesSession&&!isRefreshTokenRequest(t.url)&&(await waitForSessionReplacement(),await currentTokenIfReplaced(readSentToken(t)))){const r=await readAccountId(getAccountIdProvider());return(t._accountId??null)!==r?(Logger.info(`Workspace changed in flight; not sent again. URL: ${t.url}`),Promise.reject(new WorkspaceChangedError("Workspace changed in flight",{config:t,cause:e}))):(Logger.info(`Session changed while the request was in flight; sending it again. URL: ${t.url}`),t._replayedWithCurrentToken=!0,userClient(t))}if(401===r.response.status&&t&&!t._retry){if(isRefreshTokenRequest(t.url))return Promise.reject(e);const s=sessionCodeOf(r.response.data);if(null!==s)return Logger.warn(`Session ${s}, logging out without refresh. URL: ${t.url}`),resetRefreshState(),await endSessionAndLogout("terminated",s),Promise.reject(e);if(Logger.warn(`Unauthorized URL: ${t.url}`),t._retry=!0,isRefreshInFlight())return queueBehindRefresh().then(e=>(Logger.info("Retrying request after token refresh:",t.url),setAuthHeader(t,e),userClient(t))).catch(e=>(Logger.error("Failed to retry request after token refresh:",getErrorMessage(e)),Promise.reject(e)));try{const e=await refreshAccessToken();return setAuthHeader(t,e),userClient(t)}catch(e){return Promise.reject(e)}}return t?.skipErrorLog||Logger.error(`Response error: ${responseBodyText(r.response?.data,r.message)}, URL: ${t?.url}, Status: ${r.response.status}`),Promise.reject(e)});export default userClient;
1
+ "use strict";import axios,{AxiosHeaders}from"axios";import{ACCESS_TOKEN_KEY,getToken}from"../auth/authStore.js";import{getConfigValue}from"../config/index.js";import{Logger}from"../logger/index.js";import{getLastKnownNetworkStatus}from"../network/networkStatus.js";import"../types/axios.js";import{applyConfiguredBaseUrl}from"./baseUrl.js";import{applyClientHeaders,readAccountId,stampAccountId}from"./clientHeaders.js";import{logCurl}from"./curl.js";import{readMinAppVersion}from"./minAppVersion.js";import{sendFormDataAsMultipart}from"./multipart.js";import{applyRequestDefaults}from"./requestDefaults.js";import{responseBodyText}from"./responseBodyText.js";import{attachRetry}from"./retry.js";import{beginSessionReplacement,currentTokenIfReplaced,endSessionAndLogout,getAccountIdProvider,getErrorMessage,getRefreshPath,isRefreshInFlight,queueBehindRefresh,refreshAccessToken,resetRefreshState,sessionCodeOf,sessionDetailOf,storeReplacementSession,waitForSessionReplacement}from"./tokenRefresh.js";import{isWorkspaceChangedResponse,WorkspaceChangedError}from"./workspace.js";export{injectStore,injectUserAuth,resetRefreshState,SESSION_EXPIRED_CODE,SESSION_REVOKED_CODE}from"./tokenRefresh.js";export class RequestOfflineError extends Error{constructor(e="Request blocked: device is offline"){super(e),this.name="RequestOfflineError",Object.setPrototypeOf(this,RequestOfflineError.prototype)}}class NoAccessTokenError extends Error{constructor(){super("No access token available"),this.name="NoAccessTokenError",Object.setPrototypeOf(this,NoAccessTokenError.prototype)}}const endReplacement=e=>{e?._endReplacement?.(),e&&(e._endReplacement=void 0)},closeWindowIfTransformThrows=e=>{if(e._transformGuarded)return;e._transformGuarded=!0;const r=[].concat(e.transformRequest??[]);e.transformRequest=[function guarded(e,t){try{return r.reduce((e,r)=>r.call(this,e,t),e)}catch(e){throw endReplacement(this),e}}]},readSentToken=e=>{const r=e.headers instanceof AxiosHeaders?e.headers.get("Authorization"):void 0;return"string"==typeof r&&r.startsWith("Bearer ")?r.slice(7):null},userClient=axios.create({headers:{"Content-Type":"application/json"}});readMinAppVersion(userClient),attachRetry(userClient);const setAuthHeader=(e,r)=>{e.headers instanceof AxiosHeaders?e.headers.set("Authorization",`Bearer ${r}`):e.headers=AxiosHeaders.from({...e.headers,Authorization:`Bearer ${r}`})},isRefreshTokenRequest=e=>{const r=getRefreshPath();return!!r&&!!e?.includes(r)};userClient.interceptors.request.use(e=>{if(e.replacesSession&&!isRefreshTokenRequest(e.url)){const r=e;r._endReplacement=beginSessionReplacement(),closeWindowIfTransformThrows(r)}return e}),applyConfiguredBaseUrl(userClient),applyRequestDefaults(userClient),sendFormDataAsMultipart(userClient),userClient.interceptors.request.use(e=>(getConfigValue("logCurl")&&logCurl(e),e)),applyClientHeaders(userClient),userClient.interceptors.request.use(async e=>{endReplacement(e);if(!1===(await getLastKnownNetworkStatus()).isConnected)return Logger.warn(`Request blocked: device is offline. URL: ${e.url}`),Promise.reject(new RequestOfflineError);try{if(isRefreshTokenRequest(e.url))return e;await stampAccountId(e,getAccountIdProvider());const r=await getToken(ACCESS_TOKEN_KEY);if(!r?.token)throw new NoAccessTokenError;const t=Date.now(),s=Date.parse(r.expiry);if(s<=t+3e4){Logger.info("Access token expired or about to expire, refreshing before request");const r=await refreshAccessToken();setAuthHeader(e,r)}else setAuthHeader(e,r.token);return e}catch(r){return r instanceof NoAccessTokenError?Logger.info(`Request not sent: signed out. URL: ${e.url}`):Logger.error("Failed to attach token:",getErrorMessage(r)),Promise.reject(r)}},e=>(Logger.error("Request error:",getErrorMessage(e)),Promise.reject(e))),userClient.interceptors.response.use(async e=>{const r=e.config;if(r._endReplacement)try{await storeReplacementSession(e.data)}finally{endReplacement(r)}return e},async e=>{if(endReplacement(e?.config),axios.isCancel(e))return Promise.reject(e);if(e instanceof RequestOfflineError||e instanceof NoAccessTokenError)return Promise.reject(e);if(!axios.isAxiosError(e))return Logger.error(`Non-axios error in response interceptor: ${getErrorMessage(e)}`),Promise.reject(e);const r=e,t=r.config;if(!r.response)return t?.skipErrorLog||Logger.info(`Network error or no response from server: ${r.message}, URL: ${t?.url}`),Promise.reject(e);if(isWorkspaceChangedResponse(r))return Logger.info(`Refused for another workspace. URL: ${t?.url}`),Promise.reject(new WorkspaceChangedError("Refused for another workspace",{response:r.response,config:t,cause:e}));if(401===r.response.status&&t&&!t._replayedWithCurrentToken&&!t.replacesSession&&!isRefreshTokenRequest(t.url)&&(await waitForSessionReplacement(),await currentTokenIfReplaced(readSentToken(t)))){const r=await readAccountId(getAccountIdProvider());return(t._accountId??null)!==r?(Logger.info(`Workspace changed in flight; not sent again. URL: ${t.url}`),Promise.reject(new WorkspaceChangedError("Workspace changed in flight",{config:t,cause:e}))):(Logger.info(`Session changed while the request was in flight; sending it again. URL: ${t.url}`),t._replayedWithCurrentToken=!0,userClient(t))}if(401===r.response.status&&t&&!t._retry){if(isRefreshTokenRequest(t.url))return Promise.reject(e);const s=sessionCodeOf(r.response.data);if(null!==s)return Logger.warn(`Session ${s}, logging out without refresh. URL: ${t.url}`),resetRefreshState(),await endSessionAndLogout("terminated",s,sessionDetailOf(r.response.data)),Promise.reject(e);if(Logger.warn(`Unauthorized URL: ${t.url}`),t._retry=!0,isRefreshInFlight())return queueBehindRefresh().then(e=>(Logger.info("Retrying request after token refresh:",t.url),setAuthHeader(t,e),userClient(t))).catch(e=>(Logger.error("Failed to retry request after token refresh:",getErrorMessage(e)),Promise.reject(e)));try{const e=await refreshAccessToken();return setAuthHeader(t,e),userClient(t)}catch(e){return Promise.reject(e)}}return t?.skipErrorLog||Logger.error(`Response error: ${responseBodyText(r.response?.data,r.message)}, URL: ${t?.url}, Status: ${r.response.status}`),Promise.reject(e)});export default userClient;
@@ -1 +1 @@
1
- "use strict";import*as SecureStore from"expo-secure-store";import{Logger}from"../logger/index.js";import{Storage}from"../storage/index.js";import{getJWTExpiry}from"./jwt.js";export const ACCESS_TOKEN_KEY="accessToken";export const REFRESH_TOKEN_KEY="refreshToken";export const SESSION_USER_KEY="sessionUser";export const LEGACY_REFRESH_TOKEN_LIFETIME_MS=2592e6;let backend=null;const keyFor=e=>(e??"default").replace(/[^A-Za-z0-9._-]/g,"_"),expoSecureStoreBackend={setGenericPassword:async(e,r,o)=>(await SecureStore.setItemAsync(keyFor(o?.service),JSON.stringify({username:e,password:r})),!0),getGenericPassword:async e=>{const r=e?.service,o=await SecureStore.getItemAsync(keyFor(r));if(null===o)return!1;const t=JSON.parse(o);return{username:t.username,password:t.password,service:r??"default"}},resetGenericPassword:async e=>(await SecureStore.deleteItemAsync(keyFor(e?.service)),!0)},resolveBackend=()=>backend??expoSecureStoreBackend,memo=new Map;let generation=0;const forget=e=>{generation+=1,void 0===e?memo.clear():memo.delete(e)},requireBackend=e=>{const r=resolveBackend();if(!r){const r=new Error(`[authStore] no secure-store backend available; cannot ${e}.`);throw Logger.error(r.message),r}return r};export const setKeychainImplementation=e=>{backend=e,forget()};export const storeToken=async(e,r,o)=>{Logger.info(`Storing token for key: ${e}`),forget(e);try{const t=JSON.stringify({token:r,expiry:o});await requireBackend("store a token").setGenericPassword("token",t,{service:e}),forget(e),memo.set(e,{token:r,expiry:o}),Logger.info(`Token stored successfully for key: ${e}`)}catch(r){throw forget(e),Logger.error(`Error storing token for key: ${e}`,r),r}};const parseTokenData=e=>{if(!e)return null;const{password:r}=e;try{const e=JSON.parse(r);return e&&"object"==typeof e&&"token"in e&&"expiry"in e&&"string"==typeof e.token&&"string"==typeof e.expiry?e:null}catch{return null}};export const getToken=async e=>{if(isSessionKey(e)&&legacyKeys&&await migrateLegacySession(),memo.has(e))return memo.get(e)??null;const r=generation;try{const o=await requireBackend("read a token").getGenericPassword({service:e});let t=null;if(o){const r=parseTokenData(o);if(r)t={token:r.token,expiry:r.expiry};else if(Logger.warn(`Stored credentials for key "${e}" are not a valid token; ignoring them.`),isSessionKey(e))return await endSession("corrupt",!0),null}return r===generation&&memo.set(e,t),t}catch(r){const o=r instanceof Error?r.message:String(r);return o.includes("Key permanently invalidated")||o.includes("Secret invalidated")?(Logger.error(`Stored token for key "${e}" can no longer be decrypted; clearing it.`),isSessionKey(e)?await endSession("corrupt",!0):await removeToken(e),null):(Logger.error(`Error retrieving token for key: ${e}`,r),null)}};export const removeToken=async e=>{forget(e);try{await requireBackend("remove a token").resetGenericPassword({service:e}),Logger.info(`Token removed successfully for key: ${e}`)}catch(r){if(r instanceof Error&&r.message.includes("Item not found"))return void Logger.info(`No token found to remove for key: ${e}`);Logger.error(`Failed to remove token for key: ${e}`,r)}finally{forget(e)}};const isSessionKey=e=>"accessToken"===e||"refreshToken"===e;export const storeSessionUser=async e=>{if(!await Storage.setItem("sessionUser",JSON.stringify(e)))throw new Error("[authStore] could not store the session user.")};export const getSessionUser=async()=>{const e=await Storage.getItem("sessionUser");if(null===e)return null;try{return JSON.parse(e)}catch{return Logger.warn("The stored session user is not valid JSON; clearing it."),await removeSessionUser(),null}};export const removeSessionUser=async()=>{await Storage.removeItem("sessionUser")};let legacyKeys=null,legacyCheck=null;export const setLegacyTokenKeys=e=>{legacyKeys=e,legacyCheck=null};const readLegacyToken=e=>{try{const r=JSON.parse(e);if("string"==typeof r)return""===r.trim()?null:{token:r,expiry:null};if(r&&"object"==typeof r&&"string"==typeof r.token&&"string"==typeof r.expiry)return r}catch{}return""===e.trim()?null:{token:e,expiry:null}},expiryOf=e=>3===e.split(".").length?getJWTExpiry(e)?.toISOString()??null:null,readLegacyRaw=async e=>{const r=await resolveBackend().getGenericPassword({service:e});return r?r.password:null},migrateLegacySession=()=>{if(!legacyKeys)return Promise.resolve();if(legacyCheck)return legacyCheck;const e=legacyKeys;return legacyCheck=(async()=>{try{const r=resolveBackend(),o=!await r.getGenericPassword({service:"accessToken"})&&!await r.getGenericPassword({service:"refreshToken"}),t=await readLegacyRaw(e.accessToken),n=await readLegacyRaw(e.refreshToken);if(o&&(null!==t||null!==n)){const o=null===t?null:readLegacyToken(t),s=null===n?null:readLegacyToken(n);if(o){const e=o.expiry??expiryOf(o.token)??(new Date).toISOString();await r.setGenericPassword("token",JSON.stringify({token:o.token,expiry:e}),{service:"accessToken"})}if(s){const e=s.expiry??expiryOf(s.token)??new Date(Date.now()+2592e6).toISOString();await r.setGenericPassword("token",JSON.stringify({token:s.token,expiry:e}),{service:"refreshToken"})}if(e.sessionUser){const r=await Storage.getItem(e.sessionUser);null!==r&&await Storage.setItem("sessionUser",r)}null===o&&null===s||(forget(),Logger.breadcrumb("[authStore] moved a session from the legacy token keys"))}for(const o of[e.accessToken,e.refreshToken])await r.resetGenericPassword({service:o}).catch(()=>{});e.sessionUser&&await Storage.removeItem(e.sessionUser)}catch(e){Logger.error("[authStore] could not move the session from the legacy token keys",e)}})(),legacyCheck},listeners=new Set,emitSessionEnded=(e,r)=>{for(const o of[...listeners])try{o(e,r)}catch(e){Logger.error("[authStore] a session-ended listener threw",e)}};export const addSessionEndedListener=e=>(listeners.add(e),()=>{listeners.delete(e)});const endSession=async(e,r,o=null)=>{const t=r||null!==await getToken("accessToken")||null!==await getToken("refreshToken");await removeToken("accessToken"),await removeToken("refreshToken"),await removeSessionUser(),t&&emitSessionEnded(e,{code:o})};export const clearSession=async(e="signedOut",r={})=>endSession(e,!1,r.code??null);
1
+ "use strict";import*as SecureStore from"expo-secure-store";import{Logger}from"../logger/index.js";import{Storage}from"../storage/index.js";import{getJWTExpiry}from"./jwt.js";export const ACCESS_TOKEN_KEY="accessToken";export const REFRESH_TOKEN_KEY="refreshToken";export const SESSION_USER_KEY="sessionUser";export const LEGACY_REFRESH_TOKEN_LIFETIME_MS=2592e6;let backend=null;const keyFor=e=>(e??"default").replace(/[^A-Za-z0-9._-]/g,"_"),expoSecureStoreBackend={setGenericPassword:async(e,r,o)=>(await SecureStore.setItemAsync(keyFor(o?.service),JSON.stringify({username:e,password:r})),!0),getGenericPassword:async e=>{const r=e?.service,o=await SecureStore.getItemAsync(keyFor(r));if(null===o)return!1;const t=JSON.parse(o);return{username:t.username,password:t.password,service:r??"default"}},resetGenericPassword:async e=>(await SecureStore.deleteItemAsync(keyFor(e?.service)),!0)},resolveBackend=()=>backend??expoSecureStoreBackend,memo=new Map;let generation=0;const forget=e=>{generation+=1,void 0===e?memo.clear():memo.delete(e)},requireBackend=e=>{const r=resolveBackend();if(!r){const r=new Error(`[authStore] no secure-store backend available; cannot ${e}.`);throw Logger.error(r.message),r}return r};export const setKeychainImplementation=e=>{backend=e,forget()};export const storeToken=async(e,r,o)=>{Logger.info(`Storing token for key: ${e}`),forget(e);try{const t=JSON.stringify({token:r,expiry:o});await requireBackend("store a token").setGenericPassword("token",t,{service:e}),forget(e),memo.set(e,{token:r,expiry:o}),Logger.info(`Token stored successfully for key: ${e}`)}catch(r){throw forget(e),Logger.error(`Error storing token for key: ${e}`,r),r}};const parseTokenData=e=>{if(!e)return null;const{password:r}=e;try{const e=JSON.parse(r);return e&&"object"==typeof e&&"token"in e&&"expiry"in e&&"string"==typeof e.token&&"string"==typeof e.expiry?e:null}catch{return null}};export const getToken=async e=>{if(isSessionKey(e)&&legacyKeys&&await migrateLegacySession(),memo.has(e))return memo.get(e)??null;const r=generation;try{const o=await requireBackend("read a token").getGenericPassword({service:e});let t=null;if(o){const r=parseTokenData(o);if(r)t={token:r.token,expiry:r.expiry};else if(Logger.warn(`Stored credentials for key "${e}" are not a valid token; ignoring them.`),isSessionKey(e))return await endSession("corrupt",!0),null}return r===generation&&memo.set(e,t),t}catch(r){const o=r instanceof Error?r.message:String(r);return o.includes("Key permanently invalidated")||o.includes("Secret invalidated")?(Logger.error(`Stored token for key "${e}" can no longer be decrypted; clearing it.`),isSessionKey(e)?await endSession("corrupt",!0):await removeToken(e),null):(Logger.error(`Error retrieving token for key: ${e}`,r),null)}};export const removeToken=async e=>{forget(e);try{await requireBackend("remove a token").resetGenericPassword({service:e}),Logger.info(`Token removed successfully for key: ${e}`)}catch(r){if(r instanceof Error&&r.message.includes("Item not found"))return void Logger.info(`No token found to remove for key: ${e}`);Logger.error(`Failed to remove token for key: ${e}`,r)}finally{forget(e)}};const isSessionKey=e=>"accessToken"===e||"refreshToken"===e;export const storeSessionUser=async e=>{if(!await Storage.setItem("sessionUser",JSON.stringify(e)))throw new Error("[authStore] could not store the session user.")};export const getSessionUser=async()=>{const e=await Storage.getItem("sessionUser");if(null===e)return null;try{return JSON.parse(e)}catch{return Logger.warn("The stored session user is not valid JSON; clearing it."),await removeSessionUser(),null}};export const removeSessionUser=async()=>{await Storage.removeItem("sessionUser")};let legacyKeys=null,legacyCheck=null;export const setLegacyTokenKeys=e=>{legacyKeys=e,legacyCheck=null};const readLegacyToken=e=>{try{const r=JSON.parse(e);if("string"==typeof r)return""===r.trim()?null:{token:r,expiry:null};if(r&&"object"==typeof r&&"string"==typeof r.token&&"string"==typeof r.expiry)return r}catch{}return""===e.trim()?null:{token:e,expiry:null}},expiryOf=e=>3===e.split(".").length?getJWTExpiry(e)?.toISOString()??null:null,readLegacyRaw=async e=>{const r=await resolveBackend().getGenericPassword({service:e});return r?r.password:null},migrateLegacySession=()=>{if(!legacyKeys)return Promise.resolve();if(legacyCheck)return legacyCheck;const e=legacyKeys;return legacyCheck=(async()=>{try{const r=resolveBackend(),o=!await r.getGenericPassword({service:"accessToken"})&&!await r.getGenericPassword({service:"refreshToken"}),t=await readLegacyRaw(e.accessToken),n=await readLegacyRaw(e.refreshToken);if(o&&(null!==t||null!==n)){const o=null===t?null:readLegacyToken(t),s=null===n?null:readLegacyToken(n);if(o){const e=o.expiry??expiryOf(o.token)??(new Date).toISOString();await r.setGenericPassword("token",JSON.stringify({token:o.token,expiry:e}),{service:"accessToken"})}if(s){const e=s.expiry??expiryOf(s.token)??new Date(Date.now()+2592e6).toISOString();await r.setGenericPassword("token",JSON.stringify({token:s.token,expiry:e}),{service:"refreshToken"})}if(e.sessionUser){const r=await Storage.getItem(e.sessionUser);null!==r&&await Storage.setItem("sessionUser",r)}null===o&&null===s||(forget(),Logger.breadcrumb("[authStore] moved a session from the legacy token keys"))}for(const o of[e.accessToken,e.refreshToken])await r.resetGenericPassword({service:o}).catch(()=>{});e.sessionUser&&await Storage.removeItem(e.sessionUser)}catch(e){Logger.error("[authStore] could not move the session from the legacy token keys",e)}})(),legacyCheck},listeners=new Set,emitSessionEnded=(e,r)=>{for(const o of[...listeners])try{o(e,r)}catch(e){Logger.error("[authStore] a session-ended listener threw",e)}};export const addSessionEndedListener=e=>(listeners.add(e),()=>{listeners.delete(e)});const endSession=async(e,r,o=null,t=null)=>{const n=r||null!==await getToken("accessToken")||null!==await getToken("refreshToken");await removeToken("accessToken"),await removeToken("refreshToken"),await removeSessionUser(),n&&emitSessionEnded(e,{code:o,detail:t})};export const clearSession=async(e="signedOut",r={})=>endSession(e,!1,r.code??null,r.detail??null);
@@ -41,9 +41,11 @@ export declare const dispatchLogout: () => Promise<void>;
41
41
  * The session is over: clear it (tokens and session user removed, listeners told why, with
42
42
  * the server's session code), then dispatch the app's logout.
43
43
  */
44
- export declare const endSessionAndLogout: (reason: SessionEndReason, code: string | null) => Promise<void>;
44
+ export declare const endSessionAndLogout: (reason: SessionEndReason, code: string | null, detail?: string | null) => Promise<void>;
45
45
  /** The server's session code in a 401 body (`session_revoked`, `session_expired`), or null. */
46
46
  export declare const sessionCodeOf: (data: unknown) => string | null;
47
+ /** The server's sentence (the problem's `detail`) in a 401 body that carries a session code, or null. */
48
+ export declare const sessionDetailOf: (data: unknown) => string | null;
47
49
  export declare const isRefreshInFlight: () => boolean;
48
50
  /** Opens the replacement window for one flagged request; call the returned function once it settles. */
49
51
  export declare const beginSessionReplacement: () => (() => void);
@@ -36,6 +36,8 @@ export interface SessionEndDetail {
36
36
  * The server's session code (`session_revoked`, `session_expired`) when the server ended the session; null when the app did.
37
37
  */
38
38
  code: string | null;
39
+ /** The server's sentence for the user (the problem's detail) when the server ended the session; null otherwise. */
40
+ detail: string | null;
39
41
  }
40
42
  export type SessionEndedListener = (reason: SessionEndReason, detail: SessionEndDetail) => void;
41
43
  /**
@@ -41,9 +41,11 @@ export declare const dispatchLogout: () => Promise<void>;
41
41
  * The session is over: clear it (tokens and session user removed, listeners told why, with
42
42
  * the server's session code), then dispatch the app's logout.
43
43
  */
44
- export declare const endSessionAndLogout: (reason: SessionEndReason, code: string | null) => Promise<void>;
44
+ export declare const endSessionAndLogout: (reason: SessionEndReason, code: string | null, detail?: string | null) => Promise<void>;
45
45
  /** The server's session code in a 401 body (`session_revoked`, `session_expired`), or null. */
46
46
  export declare const sessionCodeOf: (data: unknown) => string | null;
47
+ /** The server's sentence (the problem's `detail`) in a 401 body that carries a session code, or null. */
48
+ export declare const sessionDetailOf: (data: unknown) => string | null;
47
49
  export declare const isRefreshInFlight: () => boolean;
48
50
  /** Opens the replacement window for one flagged request; call the returned function once it settles. */
49
51
  export declare const beginSessionReplacement: () => (() => void);
@@ -36,6 +36,8 @@ export interface SessionEndDetail {
36
36
  * The server's session code (`session_revoked`, `session_expired`) when the server ended the session; null when the app did.
37
37
  */
38
38
  code: string | null;
39
+ /** The server's sentence for the user (the problem's detail) when the server ended the session; null otherwise. */
40
+ detail: string | null;
39
41
  }
40
42
  export type SessionEndedListener = (reason: SessionEndReason, detail: SessionEndDetail) => void;
41
43
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webority/mobile-core",
3
- "version": "0.100.0",
3
+ "version": "0.101.0",
4
4
  "description": "Platform layer for Webority React Native apps: HTTP clients, auth/token storage, config, logging, formatters, validators, network status, permissions, storage, version checks and over-the-air updates. No UI.",
5
5
  "keywords": [
6
6
  "react-native",