@webority/mobile-core 0.91.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 +11 -5
- package/lib/commonjs/api/clientHeaders.js +1 -1
- package/lib/commonjs/api/minAppVersion.js +1 -1
- package/lib/commonjs/api/requestDefaults.js +1 -1
- package/lib/commonjs/api/retry.js +1 -1
- package/lib/commonjs/api/tokenRefresh.js +1 -1
- package/lib/commonjs/api/userClient.js +1 -1
- package/lib/commonjs/api/workspace.js +1 -1
- package/lib/commonjs/auth/authStore.js +1 -1
- package/lib/commonjs/config/index.js +1 -1
- package/lib/commonjs/errors/extractApiError.js +1 -1
- package/lib/commonjs/index.js +1 -1
- package/lib/commonjs/initSDK.js +1 -1
- package/lib/commonjs/logger/index.js +1 -1
- package/lib/module/api/clientHeaders.js +1 -1
- package/lib/module/api/minAppVersion.js +1 -1
- package/lib/module/api/requestDefaults.js +1 -1
- package/lib/module/api/retry.js +1 -1
- package/lib/module/api/tokenRefresh.js +1 -1
- package/lib/module/api/userClient.js +1 -1
- package/lib/module/api/workspace.js +1 -1
- package/lib/module/auth/authStore.js +1 -1
- package/lib/module/config/index.js +1 -1
- package/lib/module/errors/extractApiError.js +1 -1
- package/lib/module/index.js +1 -1
- package/lib/module/initSDK.js +1 -1
- package/lib/module/logger/index.js +1 -1
- package/lib/typescript/commonjs/api/clientHeaders.d.ts +7 -7
- package/lib/typescript/commonjs/api/minAppVersion.d.ts +2 -3
- package/lib/typescript/commonjs/api/requestDefaults.d.ts +10 -7
- package/lib/typescript/commonjs/api/retry.d.ts +3 -3
- package/lib/typescript/commonjs/api/tokenRefresh.d.ts +8 -5
- package/lib/typescript/commonjs/api/userClient.d.ts +1 -23
- package/lib/typescript/commonjs/auth/authStore.d.ts +3 -6
- package/lib/typescript/commonjs/config/index.d.ts +2 -9
- package/lib/typescript/commonjs/errors/extractApiError.d.ts +7 -13
- package/lib/typescript/commonjs/index.d.ts +3 -3
- package/lib/typescript/commonjs/initSDK.d.ts +2 -4
- package/lib/typescript/commonjs/initUserAuth.d.ts +1 -8
- package/lib/typescript/commonjs/versionCheck/buildNumber.d.ts +1 -1
- package/lib/typescript/commonjs/versionCheck/index.d.ts +4 -4
- package/lib/typescript/module/api/clientHeaders.d.ts +7 -7
- package/lib/typescript/module/api/minAppVersion.d.ts +2 -3
- package/lib/typescript/module/api/requestDefaults.d.ts +10 -7
- package/lib/typescript/module/api/retry.d.ts +3 -3
- package/lib/typescript/module/api/tokenRefresh.d.ts +8 -5
- package/lib/typescript/module/api/userClient.d.ts +1 -23
- package/lib/typescript/module/auth/authStore.d.ts +3 -6
- package/lib/typescript/module/config/index.d.ts +2 -9
- package/lib/typescript/module/errors/extractApiError.d.ts +7 -13
- package/lib/typescript/module/index.d.ts +3 -3
- package/lib/typescript/module/initSDK.d.ts +2 -4
- package/lib/typescript/module/initUserAuth.d.ts +1 -8
- package/lib/typescript/module/versionCheck/buildNumber.d.ts +1 -1
- package/lib/typescript/module/versionCheck/index.d.ts +4 -4
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";export const SERVER_FAILURE_MESSAGE="Something went wrong on our side. Please try again in a moment.";export const OFFLINE_MESSAGE="You appear to be offline. Check your connection and try again.";export const TIMEOUT_MESSAGE="That took too long to respond. Check your connection and try again.";export const UNEXPECTED_MESSAGE="Something went wrong. Please try again.";export const UNREACHABLE_MESSAGE="We couldn't reach the server. Check your connection and try again.";export const REQUEST_CANCELED_CODE="request_canceled";export const CONTRACT_ERROR_CODE="contract_error";export const TIMEOUT_CODE="timeout";export const NETWORK_CODE="network";export const OFFLINE_CODE="offline";const INTERNAL_ERROR_CODE="internal_error",MAX_MESSAGE_LENGTH=300,requestFailedMessage=e=>`Request failed (${e})`,isDict=e=>"object"==typeof e&&null!==e&&!Array.isArray(e),nonEmptyString=e=>"string"==typeof e&&e.trim().length>0?e:null,looksLikeDiagnostics=e=>e.length>300||/^\s*</.test(e)||e.includes("\n at ")||/(Exception|StackTrace):/.test(e)||/\b(System|Microsoft)\.[A-Za-z]+\.[A-Za-z]/.test(e),userSafe=e=>{const t=nonEmptyString(e);return null===t||looksLikeDiagnostics(t)?null:t};export const toFieldPath=e=>e.replace(/^\$\.?/,"").split(".").map(e=>e.charAt(0).toLowerCase()+e.slice(1)).join(".");const firstMessage=e=>{if(Array.isArray(e)){for(const t of e){const e=nonEmptyString(t);if(null!==e)return e}return null}return nonEmptyString(e)},addError=(e,t,r)=>{const n=toFieldPath(t);""===n?e.form??=r:n in e.fields||(e.fields[n]=r)},readErrors=e=>{const t={fields:{},form:null};if(Array.isArray(e)){for(const r of e){if(!isDict(r))continue;const e=nonEmptyString(r.message);null!==e&&addError(t,"string"==typeof r.field?r.field:"",e)}return t}if(!isDict(e))return t;for(const[r,n]of Object.entries(e)){const e=firstMessage(n);null!==e&&addError(t,r,e)}return t}
|
|
1
|
+
"use strict";export const SERVER_FAILURE_MESSAGE="Something went wrong on our side. Please try again in a moment.";export const OFFLINE_MESSAGE="You appear to be offline. Check your connection and try again.";export const TIMEOUT_MESSAGE="That took too long to respond. Check your connection and try again.";export const UNEXPECTED_MESSAGE="Something went wrong. Please try again.";export const UNREACHABLE_MESSAGE="We couldn't reach the server. Check your connection and try again.";export const REQUEST_CANCELED_CODE="request_canceled";export const CONTRACT_ERROR_CODE="contract_error";export const TIMEOUT_CODE="timeout";export const NETWORK_CODE="network";export const OFFLINE_CODE="offline";const INTERNAL_ERROR_CODE="internal_error",MAX_MESSAGE_LENGTH=300,requestFailedMessage=e=>`Request failed (${e})`,isDict=e=>"object"==typeof e&&null!==e&&!Array.isArray(e),nonEmptyString=e=>"string"==typeof e&&e.trim().length>0?e:null,looksLikeDiagnostics=e=>e.length>300||/^\s*</.test(e)||e.includes("\n at ")||/(Exception|StackTrace):/.test(e)||/\b(System|Microsoft)\.[A-Za-z]+\.[A-Za-z]/.test(e),userSafe=e=>{const t=nonEmptyString(e);return null===t||looksLikeDiagnostics(t)?null:t};export const toFieldPath=e=>e.replace(/^\$\.?/,"").split(".").map(e=>e.charAt(0).toLowerCase()+e.slice(1)).join(".");const firstMessage=e=>{if(Array.isArray(e)){for(const t of e){const e=nonEmptyString(t);if(null!==e)return e}return null}return nonEmptyString(e)},addError=(e,t,r)=>{const n=toFieldPath(t);""===n?e.form??=r:n in e.fields||(e.fields[n]=r)},readErrors=e=>{const t={fields:{},form:null};if(Array.isArray(e)){for(const r of e){if(!isDict(r))continue;const e=nonEmptyString(r.message);null!==e&&addError(t,"string"==typeof r.field?r.field:"",e)}return t}if(!isDict(e))return t;for(const[r,n]of Object.entries(e)){const e=firstMessage(n);null!==e&&addError(t,r,e)}return t};export const isApiError=e=>isDict(e)&&"string"==typeof e.message&&"number"==typeof e.status&&"boolean"==typeof e.offline&&isDict(e.fieldErrors);const build=e=>({code:null,fieldErrors:{},status:0,offline:!1,traceId:null,diagnostic:null,...e}),requestTraceId=e=>{const t=isDict(e.config)?e.config.headers:void 0;if(!isDict(t))return null;const r="function"==typeof t.get?t.get("traceparent"):t.traceparent??t.Traceparent,n="string"==typeof r?/^00-([0-9a-f]{32})-[0-9a-f]{16}-[0-9a-f]{2}$/.exec(r):null;return n?n[1]:null},fromNoResponse=e=>{const t=nonEmptyString(e.code),r=nonEmptyString(e.name),n=nonEmptyString(e.message),o=requestTraceId(e);return build("CanceledError"===r||"ERR_CANCELED"===t?{code:"request_canceled",message:UNEXPECTED_MESSAGE,traceId:o,diagnostic:n}:"ECONNABORTED"===t||"ETIMEDOUT"===t?{code:"timeout",message:TIMEOUT_MESSAGE,offline:!0,traceId:o,diagnostic:n}:"RequestOfflineError"===r?{code:"offline",message:OFFLINE_MESSAGE,offline:!0,traceId:o,diagnostic:n}:"ERR_NETWORK"===t||"Network Error"===n||"Network request failed"===n?{code:"network",message:UNREACHABLE_MESSAGE,offline:!0,traceId:o,diagnostic:n}:{message:UNEXPECTED_MESSAGE,traceId:o,diagnostic:n})};export function extractApiError(e){if(isApiError(e))return e;if("string"==typeof e)return build({message:userSafe(e)??UNEXPECTED_MESSAGE,diagnostic:e||null});if(!isDict(e))return build({message:UNEXPECTED_MESSAGE,diagnostic:null==e?null:String(e)});if(!0===e.isContractError)return build({code:"contract_error",message:userSafe(e.message)??UNEXPECTED_MESSAGE,diagnostic:nonEmptyString(e.message)});const t=isDict(e.response)?e.response:null,r="number"==typeof t?.status?t.status:"number"==typeof e.status?e.status:0,n=t?t.data:e.body;if(0===r&&null==n)return fromNoResponse(e);const o=isDict(n)?n:null,{fields:s,form:i}=readErrors(o?.errors),a=nonEmptyString(o?.code),E=nonEmptyString(o?.traceId)??requestTraceId(e),c=nonEmptyString(o?.detail)??nonEmptyString(o?.title)??nonEmptyString(n)??nonEmptyString(e.message);let l;if(r>=500){l=(null!==a&&"internal_error"!==a?userSafe(o?.detail):null)??SERVER_FAILURE_MESSAGE}else l=userSafe(Object.values(s)[0])??userSafe(i)??userSafe(o?.detail)??userSafe(o?.title)??userSafe(n)??(r?requestFailedMessage(r):UNEXPECTED_MESSAGE);return build({code:a,message:l,fieldErrors:s,status:r,traceId:E,diagnostic:c})}export const isNoResponse=e=>extractApiError(e).offline;export function apiErrorMessage(e,t){const r=extractApiError(e);if(r.offline||null!==t&&!t)return r.message;return r.message===SERVER_FAILURE_MESSAGE||"request_canceled"===r.code||r.message===UNEXPECTED_MESSAGE||r.message===requestFailedMessage(r.status)?t??null:r.message}
|
package/lib/module/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";export{ACCOUNT_ID_HEADER,CLIENT_BUILD_HEADER,CLIENT_KIND,CLIENT_KIND_HEADER,getClientBuild}from"./api/clientHeaders.js";export{buildCurl,logCurl}from"./api/curl.js";export{addMinAppVersionListener,getMinAppVersion,MIN_APP_VERSION_HEADER}from"./api/minAppVersion.js";export{default as publicClient}from"./api/publicClient.js";export{applyRequestDefaults,
|
|
1
|
+
"use strict";export{ACCOUNT_ID_HEADER,CLIENT_BUILD_HEADER,CLIENT_KIND,CLIENT_KIND_HEADER,getClientBuild}from"./api/clientHeaders.js";export{buildCurl,logCurl}from"./api/curl.js";export{addMinAppVersionListener,getMinAppVersion,MIN_APP_VERSION_HEADER}from"./api/minAppVersion.js";export{default as publicClient}from"./api/publicClient.js";export{applyRequestDefaults,newTraceparent,TRACEPARENT_HEADER}from"./api/requestDefaults.js";export{attachRetry,IDEMPOTENCY_KEY_HEADER,isNeverSentError,isRetryableFailure,isRetryableStatus,retryAfterMs}from"./api/retry.js";export{refreshAccessToken}from"./api/tokenRefresh.js";export{default as userClient,injectStore,injectUserAuth,RequestOfflineError,resetRefreshState,SESSION_EXPIRED_CODE,SESSION_REVOKED_CODE}from"./api/userClient.js";export{isWorkspaceChangedError,WORKSPACE_CHANGED_CODE,WorkspaceChangedError}from"./api/workspace.js";export{ACCESS_TOKEN_KEY,addSessionEndedListener,clearSession,getSessionUser,getToken,LEGACY_REFRESH_TOKEN_LIFETIME_MS,REFRESH_TOKEN_KEY,removeSessionUser,removeToken,SESSION_USER_KEY,setKeychainImplementation,setLegacyTokenKeys,storeSessionUser,storeToken}from"./auth/authStore.js";export{decodeJWT,getJWTExpiry,isJWTExpired}from"./auth/jwt.js";export{getConfig,getConfigValue,resetConfig,setConfig}from"./config/index.js";export{DeepLink,parseDeepLink,useDeepLink}from"./deepLink/index.js";export{apiErrorMessage,CONTRACT_ERROR_CODE,extractApiError,isApiError,isNoResponse,NETWORK_CODE,OFFLINE_CODE,OFFLINE_MESSAGE,REQUEST_CANCELED_CODE,SERVER_FAILURE_MESSAGE,TIMEOUT_CODE,TIMEOUT_MESSAGE,toFieldPath,UNEXPECTED_MESSAGE,UNREACHABLE_MESSAGE}from"./errors/index.js";export{AsYouType,addDays,clampNationalNumber,configureFormatters,countryFlagEmoji,detectPhoneCountry,diffMs,formatCompactNumber,formatCurrency,formatDate,formatDateDDMMMYYYY,formatDateISO,formatDatePattern,formatDateTime,formatMinor,formatNumber,formatPercent,formatPhone,formatPhoneAsTyped,formatRelative,formatRelativeTime,formatTime,formatTime12h,formatTimeInWindowsZone,formatTimeInZone,getCountries,getCountryCallingCode,getFormatterConfig,getInitials,getPhoneCountries,isDateAfter,isDateBefore,isSameDay,isSupportedCountry,isValidNationalNumber,isValidPhoneNumber,maxNationalNumberLength,normalizePhone,parsePhoneNumber,parsePhoneNumberFromString,splitPhoneNumber,startOfDay,validatePhoneNumberLength}from"./formatters/index.js";export{initWebority}from"./initSDK.js";export{initUserAuth}from"./initUserAuth.js";export{configureLogger,getDeviceContext,Logger,setDeviceContext}from"./logger/index.js";export{crashlyticsRemoteLogger}from"./logger/crashlytics.js";export{addNetworkStatusListener,getNetworkStatus,setNetworkStatusImplementation,useNetworkStatus}from"./network/index.js";export{PUSH_STATUS}from"./push/status.js";export{isStorageAvailable,Storage,setStorageImplementation}from"./storage/index.js";export{encodeBase64}from"./utils/base64.js";export{isAadhaar,isAlphanumeric,isBetween,isEmail,isGstin,isHexColor,isIfsc,isIndianMobile,isIndianPincode,isInteger,isNonEmpty,isNumeric,isPan,isPhone,isStrongPassword,isUrl,maxLength,minLength}from"./validators/index.js";export{isBuildBelowMinimum,parseBuildNumber,setDeviceInfoImplementation,useVersionCheck,VersionCheck}from"./versionCheck/index.js";
|
package/lib/module/initSDK.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{setKeychainImplementation}from"./auth/authStore.js";import{
|
|
1
|
+
"use strict";import{setKeychainImplementation}from"./auth/authStore.js";import{DEFAULT_LOG_LEVEL,DEFAULT_RETRIES,DEFAULT_TIMEOUT_MS,setConfig}from"./config/index.js";import{Logger,setRemoteLogger}from"./logger/index.js";import{setNetworkStatusImplementation}from"./network/networkStatus.js";import{setStorageImplementation}from"./storage/index.js";export const initWebority=e=>{if(!e.baseUrl)throw new Error("baseUrl is required for Webority packages initialization");if(void 0===e.remoteLogger)throw new Error("remoteLogger is required for Webority packages initialization: pass a RemoteLogger, or null for a build without crash reporting");setRemoteLogger(e.remoteLogger),setConfig({baseUrl:e.baseUrl,loggingEnabled:e.loggingEnabled??!0,logLevel:e.logLevel||DEFAULT_LOG_LEVEL,environment:e.environment||"Development",country:(e.country||"in").toLowerCase(),appId:e.appId??null,appVersion:e.appVersion??null,commit:e.commit??null,appBuild:e.appBuild??null,appStoreId:e.appStoreId??null,logCurl:e.logCurl??!1,timeoutMs:e.timeoutMs??DEFAULT_TIMEOUT_MS,retries:e.retries??DEFAULT_RETRIES});const o=e.backends;o?.storage&&setStorageImplementation(o.storage),o?.keychain&&setKeychainImplementation(o.keychain),o?.network&&setNetworkStatusImplementation(o.network),Logger.info("[@webority/mobile-core] initialized")};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{getConfigValue}from"../config/index.js";import{isNoResponse}from"../errors/extractApiError.js";import{configureRedaction,redact,redactError,redactString}from"./redact.js";const Colors={RESET:"[0m",GRAY:"[90m",BLUE:"[34m",YELLOW:"[33m",RED_BOLD:"[31;1m"},LogLevels={DEBUG:{level:"debug",color:Colors.GRAY},INFO:{level:"info",color:Colors.BLUE},WARN:{level:"warn",color:Colors.YELLOW},ERROR:{level:"error",color:Colors.RED_BOLD}},levels=["debug","info","warn","error"],isLogLevelAllowed=e=>{const r=getConfigValue("logLevel","info");return levels.indexOf(e)>=levels.indexOf(r)},isEnabled=()=>getConfigValue("loggingEnabled",!0);let remote=null;export const setRemoteLogger=e=>{remote=e};const safe=e=>{if(e)try{e()}catch{}};let deviceContext=null;const normalizeAttributeValue=e=>null==e?null:"string"==typeof e||"number"==typeof e||"boolean"==typeof e?e:String(e);export const setDeviceContext=e=>{if(deviceContext=e,e)for(const[r,o]of Object.entries(e))safe(()=>remote?.setAttribute?.(r,normalizeAttributeValue(o)))};export const getDeviceContext=()=>deviceContext;const DEFAULT_LOGGER_OPTIONS={dedupeWindowMs:5e3,dedupeMaxPerWindow:1,screenHistoryLimit:20,breadcrumbLimit:50};let loggerOptions={...DEFAULT_LOGGER_OPTIONS};export const configureLogger=e=>{const{redactKeys:r,...o}=e;void 0!==r&&configureRedaction(r),loggerOptions={...loggerOptions,...o},screenHistory.length>loggerOptions.screenHistoryLimit&&(screenHistory=screenHistory.slice(screenHistory.length-loggerOptions.screenHistoryLimit)),trimBreadcrumbs()};let currentScreen=null,screenHistory=[];const breadcrumbs=[],trimBreadcrumbs=()=>{const e=breadcrumbs.length-loggerOptions.breadcrumbLimit;e>0&&breadcrumbs.splice(0,e)},dedupeMap=new Map,MAX_DEDUPE_ENTRIES=200,dedupeKeyFor=e=>{const r=e.stack?.split("\n")[1]?.trim();return r?`${e.message}::${r}`:e.message},capDedupeMap=()=>{for(;dedupeMap.size>200;){const e=dedupeMap.keys().next().value;if(void 0===e)return;dedupeMap.delete(e)}},shouldForward=e=>{if(loggerOptions.dedupeWindowMs<=0)return{forward:!0};const r=Date.now(),o=dedupeMap.get(e);if(!o||r-o.windowStart>=loggerOptions.dedupeWindowMs){const t=o?.suppressedCount??0;return dedupeMap.set(e,{windowStart:r,count:1,suppressedCount:0}),capDedupeMap(),t>0?{forward:!0,suppressedCount:t}:{forward:!0}}return o.count+=1,o.count>loggerOptions.dedupeMaxPerWindow?(o.suppressedCount+=1,{forward:!1}):{forward:!0}},buildErrorContext=(e,r)=>{const o={...deviceContext};return null!==currentScreen&&(o.screen=currentScreen),r&&(o.suppressedCount=r),{...o,...e}},forwardErrorToRemote=(e,r)=>{const o=dedupeKeyFor(e),{forward:t,suppressedCount:s}=shouldForward(o);t&&safe(()=>remote?.recordError?.(e,buildErrorContext(r,s)))},logDebug=(e,...r)=>{isEnabled()&&isLogLevelAllowed(LogLevels.DEBUG.level)&&console.debug(`${LogLevels.DEBUG.color}[DEBUG]: ${redactString(e)}${Colors.RESET}`,...redact(r))},logInfo=(e,...r)=>{if(!isEnabled()||!isLogLevelAllowed(LogLevels.INFO.level))return;const o=redactString(e),t=redact(r);console.info(`${LogLevels.INFO.color}[INFO]: ${o}${Colors.RESET}`,...t),safe(()=>remote?.log?.("info",o,t))},logWarning=(e,...r)=>{if(!isEnabled()||!isLogLevelAllowed(LogLevels.WARN.level))return;const o=redactString(e),t=redact(r);console.warn(`${LogLevels.WARN.color}[WARNING]: ${o}${Colors.RESET}`,...t),safe(()=>remote?.log?.("warn",o,t))},logError=(e,...r)=>{const o=redactString(e),t=redact(r);isEnabled()&&isLogLevelAllowed(LogLevels.ERROR.level)&&console.error(`${LogLevels.ERROR.color}[ERROR]: ${o}${Colors.RESET}`,...t),safe(()=>remote?.log?.("error",o,t));const s=r[0];void 0===s||isNoResponse(s)||forwardErrorToRemote(redactError(s),{source:"Logger.error",message:o})},breadcrumb=(e,r)=>{const o=redactString(e),t=redact(r);breadcrumbs.push({message:o,data:t,timestampUtc:(new Date).toISOString()}),trimBreadcrumbs(),isEnabled()&&isLogLevelAllowed("info")&&console.info(`${Colors.BLUE}[BREADCRUMB]: ${o}${Colors.RESET}`,t??""),safe(()=>remote?.breadcrumb?.(o,t))},recordError=(e,r)=>{const o=redactError(e),t=redact(r);isNoResponse(e)?logInfo(`No response: ${o.message}`):(isEnabled()&&isLogLevelAllowed("error")&&console.error(`${Colors.RED_BOLD}[RECORDED]: ${o.message}${Colors.RESET}`,t??""),forwardErrorToRemote(o,t))},asDict=e=>"object"==typeof e&&null!==e?e:null,headerValue=(e,r)=>{const o=asDict(e);if(!o)return null;const t="function"==typeof o.get?o.get.call(e,r):o[r]??o[r.toLowerCase()];return"string"==typeof t&&""!==t?t:null},requestFailed=(e,r={})=>{const{label:o,expectedStatuses:t=[],...s}=r,n=asDict(e),l=asDict(n?.config),c=asDict(n?.response),i=asDict(c?.data),a="number"==typeof c?.status?c.status:null,d="string"==typeof l?.method?l.method.toUpperCase():null,u="string"==typeof l?.url?l.url.split("?")[0]??null:null,g=o??([d,u].filter(Boolean).join(" ")||"Request");if("CanceledError"===n?.name||"ERR_CANCELED"===n?.code||!0===n?.__CANCEL__)return void logInfo(`${g} canceled`);if("RequestOfflineError"===n?.name)return void logInfo(`${g} not sent (offline)`);if(isNoResponse(e))return void logInfo(`${g} got no response`);if("WorkspaceChangedError"===n?.name||"workspace_changed"===i?.code)return void logInfo(`${g} refused (another workspace)`);if(null!==a&&t.includes(a))return void logInfo(`${g} refused (${a})`);const p="string"==typeof n?.message?n.message:e instanceof Error?e.message:null,f=redactString(null===a?`${g} failed`:`${g} failed (${a})`);isEnabled()&&isLogLevelAllowed(LogLevels.ERROR.level)&&console.error(`${LogLevels.ERROR.color}[ERROR]: ${f}${Colors.RESET}`,redactString(p??"")),safe(()=>remote?.log?.("error",f,[]));const E=
|
|
1
|
+
"use strict";import{TRACEPARENT_HEADER}from"../api/requestDefaults.js";import{getConfigValue}from"../config/index.js";import{isNoResponse}from"../errors/extractApiError.js";import{configureRedaction,redact,redactError,redactString}from"./redact.js";const Colors={RESET:"[0m",GRAY:"[90m",BLUE:"[34m",YELLOW:"[33m",RED_BOLD:"[31;1m"},LogLevels={DEBUG:{level:"debug",color:Colors.GRAY},INFO:{level:"info",color:Colors.BLUE},WARN:{level:"warn",color:Colors.YELLOW},ERROR:{level:"error",color:Colors.RED_BOLD}},levels=["debug","info","warn","error"],isLogLevelAllowed=e=>{const r=getConfigValue("logLevel","info");return levels.indexOf(e)>=levels.indexOf(r)},isEnabled=()=>getConfigValue("loggingEnabled",!0);let remote=null;export const setRemoteLogger=e=>{remote=e};const safe=e=>{if(e)try{e()}catch{}};let deviceContext=null;const normalizeAttributeValue=e=>null==e?null:"string"==typeof e||"number"==typeof e||"boolean"==typeof e?e:String(e);export const setDeviceContext=e=>{if(deviceContext=e,e)for(const[r,o]of Object.entries(e))safe(()=>remote?.setAttribute?.(r,normalizeAttributeValue(o)))};export const getDeviceContext=()=>deviceContext;const DEFAULT_LOGGER_OPTIONS={dedupeWindowMs:5e3,dedupeMaxPerWindow:1,screenHistoryLimit:20,breadcrumbLimit:50};let loggerOptions={...DEFAULT_LOGGER_OPTIONS};export const configureLogger=e=>{const{redactKeys:r,...o}=e;void 0!==r&&configureRedaction(r),loggerOptions={...loggerOptions,...o},screenHistory.length>loggerOptions.screenHistoryLimit&&(screenHistory=screenHistory.slice(screenHistory.length-loggerOptions.screenHistoryLimit)),trimBreadcrumbs()};let currentScreen=null,screenHistory=[];const breadcrumbs=[],trimBreadcrumbs=()=>{const e=breadcrumbs.length-loggerOptions.breadcrumbLimit;e>0&&breadcrumbs.splice(0,e)},dedupeMap=new Map,MAX_DEDUPE_ENTRIES=200,dedupeKeyFor=e=>{const r=e.stack?.split("\n")[1]?.trim();return r?`${e.message}::${r}`:e.message},capDedupeMap=()=>{for(;dedupeMap.size>200;){const e=dedupeMap.keys().next().value;if(void 0===e)return;dedupeMap.delete(e)}},shouldForward=e=>{if(loggerOptions.dedupeWindowMs<=0)return{forward:!0};const r=Date.now(),o=dedupeMap.get(e);if(!o||r-o.windowStart>=loggerOptions.dedupeWindowMs){const t=o?.suppressedCount??0;return dedupeMap.set(e,{windowStart:r,count:1,suppressedCount:0}),capDedupeMap(),t>0?{forward:!0,suppressedCount:t}:{forward:!0}}return o.count+=1,o.count>loggerOptions.dedupeMaxPerWindow?(o.suppressedCount+=1,{forward:!1}):{forward:!0}},buildErrorContext=(e,r)=>{const o={...deviceContext};return null!==currentScreen&&(o.screen=currentScreen),r&&(o.suppressedCount=r),{...o,...e}},forwardErrorToRemote=(e,r)=>{const o=dedupeKeyFor(e),{forward:t,suppressedCount:s}=shouldForward(o);t&&safe(()=>remote?.recordError?.(e,buildErrorContext(r,s)))},logDebug=(e,...r)=>{isEnabled()&&isLogLevelAllowed(LogLevels.DEBUG.level)&&console.debug(`${LogLevels.DEBUG.color}[DEBUG]: ${redactString(e)}${Colors.RESET}`,...redact(r))},logInfo=(e,...r)=>{if(!isEnabled()||!isLogLevelAllowed(LogLevels.INFO.level))return;const o=redactString(e),t=redact(r);console.info(`${LogLevels.INFO.color}[INFO]: ${o}${Colors.RESET}`,...t),safe(()=>remote?.log?.("info",o,t))},logWarning=(e,...r)=>{if(!isEnabled()||!isLogLevelAllowed(LogLevels.WARN.level))return;const o=redactString(e),t=redact(r);console.warn(`${LogLevels.WARN.color}[WARNING]: ${o}${Colors.RESET}`,...t),safe(()=>remote?.log?.("warn",o,t))},logError=(e,...r)=>{const o=redactString(e),t=redact(r);isEnabled()&&isLogLevelAllowed(LogLevels.ERROR.level)&&console.error(`${LogLevels.ERROR.color}[ERROR]: ${o}${Colors.RESET}`,...t),safe(()=>remote?.log?.("error",o,t));const s=r[0];void 0===s||isNoResponse(s)||forwardErrorToRemote(redactError(s),{source:"Logger.error",message:o})},breadcrumb=(e,r)=>{const o=redactString(e),t=redact(r);breadcrumbs.push({message:o,data:t,timestampUtc:(new Date).toISOString()}),trimBreadcrumbs(),isEnabled()&&isLogLevelAllowed("info")&&console.info(`${Colors.BLUE}[BREADCRUMB]: ${o}${Colors.RESET}`,t??""),safe(()=>remote?.breadcrumb?.(o,t))},recordError=(e,r)=>{const o=redactError(e),t=redact(r);isNoResponse(e)?logInfo(`No response: ${o.message}`):(isEnabled()&&isLogLevelAllowed("error")&&console.error(`${Colors.RED_BOLD}[RECORDED]: ${o.message}${Colors.RESET}`,t??""),forwardErrorToRemote(o,t))},asDict=e=>"object"==typeof e&&null!==e?e:null,traceIdOfTraceparent=e=>headerValue(e,TRACEPARENT_HEADER)?.split("-")[1]??null,headerValue=(e,r)=>{const o=asDict(e);if(!o)return null;const t="function"==typeof o.get?o.get.call(e,r):o[r]??o[r.toLowerCase()];return"string"==typeof t&&""!==t?t:null},requestFailed=(e,r={})=>{const{label:o,expectedStatuses:t=[],...s}=r,n=asDict(e),l=asDict(n?.config),c=asDict(n?.response),i=asDict(c?.data),a="number"==typeof c?.status?c.status:null,d="string"==typeof l?.method?l.method.toUpperCase():null,u="string"==typeof l?.url?l.url.split("?")[0]??null:null,g=o??([d,u].filter(Boolean).join(" ")||"Request");if("CanceledError"===n?.name||"ERR_CANCELED"===n?.code||!0===n?.__CANCEL__)return void logInfo(`${g} canceled`);if("RequestOfflineError"===n?.name)return void logInfo(`${g} not sent (offline)`);if(isNoResponse(e))return void logInfo(`${g} got no response`);if("WorkspaceChangedError"===n?.name||"workspace_changed"===i?.code)return void logInfo(`${g} refused (another workspace)`);if(null!==a&&t.includes(a))return void logInfo(`${g} refused (${a})`);const p="string"==typeof n?.message?n.message:e instanceof Error?e.message:null,f=redactString(null===a?`${g} failed`:`${g} failed (${a})`);isEnabled()&&isLogLevelAllowed(LogLevels.ERROR.level)&&console.error(`${LogLevels.ERROR.color}[ERROR]: ${f}${Colors.RESET}`,redactString(p??"")),safe(()=>remote?.log?.("error",f,[]));const E=redact({source:"Logger.requestFailed",label:g,status:a,method:d,path:u,code:"string"==typeof i?.code?i.code:null,traceId:("string"==typeof i?.traceId?i.traceId:null)??traceIdOfTraceparent(l?.headers),detail:p,...s});forwardErrorToRemote(new Error(f),E)},setUser=(e,r)=>{safe(()=>remote?.setUser?.(e,r))},setAttribute=(e,r)=>{safe(()=>remote?.setAttribute?.(e,r))},trackScreen=(e,r)=>{e!==currentScreen&&(currentScreen=e,screenHistory.push(e),screenHistory.length>loggerOptions.screenHistoryLimit&&(screenHistory=screenHistory.slice(screenHistory.length-loggerOptions.screenHistoryLimit)),breadcrumb(`Screen: ${e}`,r))},getCurrentScreen=()=>currentScreen,getScreenHistory=()=>[...screenHistory],getBreadcrumbs=()=>[...breadcrumbs];export const Logger={debug:logDebug,info:logInfo,warn:logWarning,error:logError,breadcrumb:breadcrumb,recordError:recordError,requestFailed:requestFailed,setUser:setUser,setAttribute:setAttribute,trackScreen:trackScreen,getCurrentScreen:getCurrentScreen,getScreenHistory:getScreenHistory,getBreadcrumbs:getBreadcrumbs};
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import type { AxiosInstance } from "axios";
|
|
2
|
-
export declare const CLIENT_BUILD_HEADER = "
|
|
3
|
-
export declare const APP_BUILD_HEADER = "
|
|
4
|
-
export declare const CLIENT_KIND_HEADER = "
|
|
5
|
-
export declare const ACCOUNT_ID_HEADER = "
|
|
2
|
+
export declare const CLIENT_BUILD_HEADER = "Client-Build";
|
|
3
|
+
export declare const APP_BUILD_HEADER = "App-Build";
|
|
4
|
+
export declare const CLIENT_KIND_HEADER = "Client-Kind";
|
|
5
|
+
export declare const ACCOUNT_ID_HEADER = "Account-Id";
|
|
6
6
|
/** The server picks session rules and the minimum version per kind; the names match its enum. */
|
|
7
7
|
export type ClientKind = "Browser" | "MobileApp";
|
|
8
8
|
export declare const CLIENT_KIND: ClientKind;
|
|
9
9
|
/** `<appVersion>+<commit>`, `<appVersion>` when no commit was stamped, `null` before init. */
|
|
10
10
|
export declare const getClientBuild: () => string | null;
|
|
11
11
|
/**
|
|
12
|
-
* Stamps
|
|
13
|
-
*
|
|
12
|
+
* Stamps Client-Kind, Client-Build and App-Build on every request.
|
|
13
|
+
* App-Build is the whole build number the server compares with its minimum;
|
|
14
14
|
* it is left off when the build is unknown, because the server never refuses a
|
|
15
15
|
* request without it and a guessed number could strand a current build. A value the app
|
|
16
16
|
* already set (a default header, or a per-request one) wins, so an app that
|
|
@@ -29,7 +29,7 @@ export interface AccountStampedConfig {
|
|
|
29
29
|
_accountId?: string | null;
|
|
30
30
|
}
|
|
31
31
|
/**
|
|
32
|
-
* Stamps
|
|
32
|
+
* Stamps Account-Id once, on the first send, and records it on the config. A request must
|
|
33
33
|
* never run in a workspace other than the one it was made in, so every resend (the retry
|
|
34
34
|
* policy, a 401 replay, a refresh replay) keeps the first stamp even after the user switched.
|
|
35
35
|
* A header the caller set itself (an outbox item naming its own workspace) wins over the provider.
|
|
@@ -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;
|
|
@@ -17,8 +17,7 @@ export declare const getMinAppVersion: () => number | null;
|
|
|
17
17
|
*/
|
|
18
18
|
export declare const addMinAppVersionListener: (listener: MinAppVersionListener) => (() => void);
|
|
19
19
|
/**
|
|
20
|
-
* Reads the floor off every response the client receives, a rejected one too
|
|
21
|
-
* a 426 Upgrade Required is exactly the case the floor exists for.
|
|
20
|
+
* Reads the floor off every response the client receives, a rejected one too.
|
|
22
21
|
*/
|
|
23
22
|
export declare const readMinAppVersion: (client: AxiosInstance) => void;
|
|
24
23
|
//# sourceMappingURL=minAppVersion.d.ts.map
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import type { AxiosInstance } from "axios";
|
|
2
|
+
export declare const TRACEPARENT_HEADER = "traceparent";
|
|
2
3
|
/**
|
|
3
|
-
* A fresh
|
|
4
|
-
*
|
|
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
|
|
5
|
+
* only have to be unique enough to find one request in the logs; they are never secrets.
|
|
6
6
|
*/
|
|
7
|
-
export declare const
|
|
7
|
+
export declare const newTraceparent: () => {
|
|
8
|
+
header: string;
|
|
9
|
+
traceId: string;
|
|
10
|
+
};
|
|
8
11
|
/**
|
|
9
|
-
* Applies the configured timeout and the
|
|
12
|
+
* Applies the configured timeout and the traceparent per request, read on every send so a
|
|
10
13
|
* value set after import still applies (the same reason `applyConfiguredBaseUrl` exists).
|
|
11
14
|
* - `timeoutMs`: used when the request carries no timeout of its own; a request's own axios
|
|
12
15
|
* `timeout` wins. 0, either way, means no timeout (axios's own meaning).
|
|
13
|
-
* - `
|
|
14
|
-
* resends the same config, so it keeps the
|
|
16
|
+
* - `traceparent`: a fresh one unless the request already carries one. A retry
|
|
17
|
+
* resends the same config, so it keeps the value the first attempt went out with.
|
|
15
18
|
*
|
|
16
19
|
* axios fills a missing `timeout` with its own default of 0, which would make "this request
|
|
17
20
|
* 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 401 `code` the server sends when it revoked the session (a revoked token or a rejected cookie stamp). */
|
|
4
|
+
export declare const SESSION_REVOKED_CODE = "session_revoked";
|
|
5
|
+
/** The 401 `code` the server sends when the 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
|
|
@@ -33,14 +37,13 @@ export declare const getAccountIdProvider: () => UserAuthOptions["accountId"];
|
|
|
33
37
|
/** The refresh endpoint's path, once initUserAuth has run. */
|
|
34
38
|
export declare const getRefreshPath: () => string | undefined;
|
|
35
39
|
export declare const dispatchLogout: () => Promise<void>;
|
|
36
|
-
export declare const SIGN_OUT_REASON_HEADER = "X-Sign-Out-Reason";
|
|
37
|
-
/** The server's sign-out reason from response headers (AxiosHeaders or a plain object), or null. */
|
|
38
|
-
export declare const readSignOutReason: (headers: unknown) => string | null;
|
|
39
40
|
/**
|
|
40
41
|
* The session is over: clear it (tokens and session user removed, listeners told why, with
|
|
41
|
-
* the server's
|
|
42
|
+
* the server's session code), then dispatch the app's logout.
|
|
42
43
|
*/
|
|
43
|
-
export declare const endSessionAndLogout: (reason: SessionEndReason,
|
|
44
|
+
export declare const endSessionAndLogout: (reason: SessionEndReason, code: string | null) => Promise<void>;
|
|
45
|
+
/** The server's session code in a 401 body (`session_revoked`, `session_expired`), or null. */
|
|
46
|
+
export declare const sessionCodeOf: (data: unknown) => string | null;
|
|
44
47
|
export declare const isRefreshInFlight: () => boolean;
|
|
45
48
|
/** Opens the replacement window for one flagged request; call the returned function once it settles. */
|
|
46
49
|
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,28 +11,6 @@ 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;
|
|
38
16
|
//# sourceMappingURL=userClient.d.ts.map
|
|
@@ -33,12 +33,9 @@ export type SessionEndReason = "signedOut" | "rejected" | "terminated" | "corrup
|
|
|
33
33
|
/** What else is known about a session end, beside its reason. */
|
|
34
34
|
export interface SessionEndDetail {
|
|
35
35
|
/**
|
|
36
|
-
* The server's
|
|
37
|
-
* header (`X-Sign-Out-Reason` by default, `initUserAuth({ signOutReasonHeader })`), on a
|
|
38
|
-
* terminated session or a refused refresh. `null` when the server named none, and always
|
|
39
|
-
* `null` for `signedOut` and `corrupt` unless the app passes one.
|
|
36
|
+
* The server's session code (`session_revoked`, `session_expired`) when the server ended the session; null when the app did.
|
|
40
37
|
*/
|
|
41
|
-
|
|
38
|
+
code: string | null;
|
|
42
39
|
}
|
|
43
40
|
export type SessionEndedListener = (reason: SessionEndReason, detail: SessionEndDetail) => void;
|
|
44
41
|
/**
|
|
@@ -148,7 +145,7 @@ export declare const addSessionEndedListener: (listener: SessionEndedListener) =
|
|
|
148
145
|
* flow and the app's own sign-out can both call it without a double event.
|
|
149
146
|
* The library calls it itself with `rejected` when the refresh endpoint refuses
|
|
150
147
|
* the refresh token and `terminated` when the API reports the session dead,
|
|
151
|
-
* with the server's
|
|
148
|
+
* with the server's session code in `detail`.
|
|
152
149
|
*/
|
|
153
150
|
export declare const clearSession: (reason?: SessionEndReason, detail?: Partial<SessionEndDetail>) => Promise<void>;
|
|
154
151
|
export type { KeychainLike, SecureCredentials };
|
|
@@ -27,12 +27,12 @@ export interface Config {
|
|
|
27
27
|
appId: string | null;
|
|
28
28
|
/** The app's user-visible version string, e.g. `'1.4.2'`. Same rationale as `appId`. */
|
|
29
29
|
appVersion: string | null;
|
|
30
|
-
/** The short git sha stamped into the build; with `appVersion` it makes the
|
|
30
|
+
/** The short git sha stamped into the build; with `appVersion` it makes the Client-Build header. */
|
|
31
31
|
commit: string | null;
|
|
32
32
|
/**
|
|
33
33
|
* The app's internal build number (`VERSION_INTERNAL`: Android versionCode,
|
|
34
34
|
* iOS CFBundleVersion). The version check compares it with the API's
|
|
35
|
-
* `
|
|
35
|
+
* `Min-App-Version`. Falls back to expo-application when unset.
|
|
36
36
|
*/
|
|
37
37
|
appBuild: number | null;
|
|
38
38
|
/**
|
|
@@ -53,12 +53,6 @@ export interface Config {
|
|
|
53
53
|
timeoutMs: number;
|
|
54
54
|
/** How many times a failed request is resent under the retry rule. 0 turns retrying off. Default 3. */
|
|
55
55
|
retries: number;
|
|
56
|
-
/**
|
|
57
|
-
* Header that carries a fresh id (a uuid) on every request, for tracing one request across
|
|
58
|
-
* the app, the API and its logs. A retry keeps the id it first went out with. `null`
|
|
59
|
-
* sends none. Default `'X-Correlation-Id'`.
|
|
60
|
-
*/
|
|
61
|
-
correlationIdHeader: string | null;
|
|
62
56
|
}
|
|
63
57
|
/**
|
|
64
58
|
* Info lines (each token refresh, each unauthorised URL) reach the device log
|
|
@@ -68,7 +62,6 @@ export interface Config {
|
|
|
68
62
|
export declare const DEFAULT_LOG_LEVEL: LogLevel;
|
|
69
63
|
export declare const DEFAULT_TIMEOUT_MS = 30000;
|
|
70
64
|
export declare const DEFAULT_RETRIES = 3;
|
|
71
|
-
export declare const DEFAULT_CORRELATION_ID_HEADER = "X-Correlation-Id";
|
|
72
65
|
/**
|
|
73
66
|
* Set global configuration for all Webority packages
|
|
74
67
|
*/
|
|
@@ -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
|
-
* Never a `message`: anything that reads like an exception dump, and a 5xx body unless it is
|
|
16
|
-
* coded (not `internal_error`) and its `message` is user-safe. The server's text goes on
|
|
17
|
-
* `detail` for the logger either way.
|
|
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.";
|
|
@@ -36,12 +36,12 @@ export { buildCurl, logCurl } from "./api/curl";
|
|
|
36
36
|
export type { MinAppVersionListener } from "./api/minAppVersion";
|
|
37
37
|
export { addMinAppVersionListener, getMinAppVersion, MIN_APP_VERSION_HEADER, } from "./api/minAppVersion";
|
|
38
38
|
export { default as publicClient } from "./api/publicClient";
|
|
39
|
-
export { applyRequestDefaults,
|
|
39
|
+
export { applyRequestDefaults, newTraceparent, TRACEPARENT_HEADER } from "./api/requestDefaults";
|
|
40
40
|
export type { RetryPolicy } from "./api/retry";
|
|
41
41
|
export { attachRetry, IDEMPOTENCY_KEY_HEADER, isNeverSentError, isRetryableFailure, isRetryableStatus, retryAfterMs, } from "./api/retry";
|
|
42
42
|
export { refreshAccessToken } from "./api/tokenRefresh";
|
|
43
|
-
export type { RefreshableStore
|
|
44
|
-
export { default as userClient, injectStore, injectUserAuth, RequestOfflineError, resetRefreshState,
|
|
43
|
+
export type { RefreshableStore } from "./api/userClient";
|
|
44
|
+
export { default as userClient, injectStore, injectUserAuth, RequestOfflineError, resetRefreshState, SESSION_EXPIRED_CODE, SESSION_REVOKED_CODE, } from "./api/userClient";
|
|
45
45
|
export { isWorkspaceChangedError, WORKSPACE_CHANGED_CODE, WorkspaceChangedError, } from "./api/workspace";
|
|
46
46
|
export type { KeychainLike, LegacyTokenKeys, SecureCredentials, SessionEndDetail, SessionEndedListener, SessionEndReason, StoredToken, } from "./auth/authStore";
|
|
47
47
|
export { ACCESS_TOKEN_KEY, addSessionEndedListener, clearSession, getSessionUser, getToken, LEGACY_REFRESH_TOKEN_LIFETIME_MS, REFRESH_TOKEN_KEY, removeSessionUser, removeToken, SESSION_USER_KEY, setKeychainImplementation, setLegacyTokenKeys, storeSessionUser, storeToken, } from "./auth/authStore";
|
|
@@ -31,14 +31,14 @@ export interface InitConfig {
|
|
|
31
31
|
* user-visible version and its internal build number (`VERSION_INTERNAL`).
|
|
32
32
|
* Every Webority app already has them in `appsettings.Compiled.json`, which
|
|
33
33
|
* is why they come from config rather than across the native bridge. The
|
|
34
|
-
* version check compares `appBuild` with the API's `
|
|
34
|
+
* version check compares `appBuild` with the API's `Min-App-Version`.
|
|
35
35
|
*/
|
|
36
36
|
appId?: string;
|
|
37
37
|
appVersion?: string;
|
|
38
38
|
appBuild?: number;
|
|
39
39
|
/** The numeric Apple App Store id; the iOS update button needs it. */
|
|
40
40
|
appStoreId?: string;
|
|
41
|
-
/** The build's short git sha (`COMMIT` in appsettings); sent as `
|
|
41
|
+
/** The build's short git sha (`COMMIT` in appsettings); sent as `Client-Build: <appVersion>+<commit>`. */
|
|
42
42
|
commit?: string;
|
|
43
43
|
/**
|
|
44
44
|
* Log a redacted curl line per request. Off by default; it is a debugging
|
|
@@ -49,8 +49,6 @@ export interface InitConfig {
|
|
|
49
49
|
timeoutMs?: number;
|
|
50
50
|
/** Retries under the retry rule. Defaults to 3; 0 turns retrying off. */
|
|
51
51
|
retries?: number;
|
|
52
|
-
/** Header for the per-request correlation id. Defaults to `'X-Correlation-Id'`; `null` sends none. */
|
|
53
|
-
correlationIdHeader?: string | null;
|
|
54
52
|
/**
|
|
55
53
|
* The native modules that fill the seams the library does not import.
|
|
56
54
|
*
|
|
@@ -36,20 +36,13 @@ 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 when a request is first sent and kept for its
|
|
41
41
|
* resends, so a request never runs in a workspace the user switched to after
|
|
42
42
|
* making it; a request that sets the header itself keeps its own value.
|
|
43
43
|
* Omit it in a product whose API takes the account from the token alone.
|
|
44
44
|
*/
|
|
45
45
|
accountId?: AccountIdProvider;
|
|
46
|
-
/**
|
|
47
|
-
* The response header in which the API names why it ended a session. A 401 carrying it is a
|
|
48
|
-
* revoked session: no refresh is tried, the session is cleared as `terminated`, and the
|
|
49
|
-
* value reaches every `addSessionEndedListener` listener as `detail.signOutReason`. It is
|
|
50
|
-
* read from a refused refresh too. Default `'X-Sign-Out-Reason'`; `null` turns it off.
|
|
51
|
-
*/
|
|
52
|
-
signOutReasonHeader?: string | null;
|
|
53
46
|
/**
|
|
54
47
|
* The secure-store keys the app kept its session under before this library. The first token
|
|
55
48
|
* read that finds the library's keys empty moves a session found there and deletes the old
|
|
@@ -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.
|
|
@@ -20,7 +20,7 @@ export interface ParsedVersion {
|
|
|
20
20
|
patch: number;
|
|
21
21
|
prerelease: string;
|
|
22
22
|
}
|
|
23
|
-
/** @deprecated The store is no longer read; the minimum comes from `
|
|
23
|
+
/** @deprecated The store is no longer read; the minimum comes from `Min-App-Version`. */
|
|
24
24
|
export interface VersionData {
|
|
25
25
|
version: string | null;
|
|
26
26
|
released?: string | null;
|
|
@@ -30,7 +30,7 @@ export interface VersionData {
|
|
|
30
30
|
country?: string;
|
|
31
31
|
lastChecked: string;
|
|
32
32
|
}
|
|
33
|
-
/** @deprecated The store is no longer read; the minimum comes from `
|
|
33
|
+
/** @deprecated The store is no longer read; the minimum comes from `Min-App-Version`. */
|
|
34
34
|
export interface VersionComparison {
|
|
35
35
|
needsUpdate: boolean;
|
|
36
36
|
updateType: UpdateType;
|
|
@@ -48,7 +48,7 @@ export interface UpdateInfo {
|
|
|
48
48
|
export interface VersionCheckType {
|
|
49
49
|
/**
|
|
50
50
|
* Whether this build is below the minimum the API last sent in
|
|
51
|
-
* `
|
|
51
|
+
* `Min-App-Version`. Resolves "no update" until a reply carried one, and
|
|
52
52
|
* rejects only when the app's build number is unknown.
|
|
53
53
|
*/
|
|
54
54
|
isNewVersionAvailable: () => Promise<VersionCheckResult>;
|
|
@@ -63,7 +63,7 @@ export interface VersionCheckType {
|
|
|
63
63
|
export declare const VersionCheck: VersionCheckType;
|
|
64
64
|
/**
|
|
65
65
|
* Whether this build must update, from the minimum the API sends in
|
|
66
|
-
* `
|
|
66
|
+
* `Min-App-Version`. Re-renders the moment a reply raises the minimum, so a
|
|
67
67
|
* minimum raised mid-session opens the ForceUpdateDialog without a restart.
|
|
68
68
|
*
|
|
69
69
|
* @param isAppReady - Whether the app is ready to show the prompt
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import type { AxiosInstance } from "axios";
|
|
2
|
-
export declare const CLIENT_BUILD_HEADER = "
|
|
3
|
-
export declare const APP_BUILD_HEADER = "
|
|
4
|
-
export declare const CLIENT_KIND_HEADER = "
|
|
5
|
-
export declare const ACCOUNT_ID_HEADER = "
|
|
2
|
+
export declare const CLIENT_BUILD_HEADER = "Client-Build";
|
|
3
|
+
export declare const APP_BUILD_HEADER = "App-Build";
|
|
4
|
+
export declare const CLIENT_KIND_HEADER = "Client-Kind";
|
|
5
|
+
export declare const ACCOUNT_ID_HEADER = "Account-Id";
|
|
6
6
|
/** The server picks session rules and the minimum version per kind; the names match its enum. */
|
|
7
7
|
export type ClientKind = "Browser" | "MobileApp";
|
|
8
8
|
export declare const CLIENT_KIND: ClientKind;
|
|
9
9
|
/** `<appVersion>+<commit>`, `<appVersion>` when no commit was stamped, `null` before init. */
|
|
10
10
|
export declare const getClientBuild: () => string | null;
|
|
11
11
|
/**
|
|
12
|
-
* Stamps
|
|
13
|
-
*
|
|
12
|
+
* Stamps Client-Kind, Client-Build and App-Build on every request.
|
|
13
|
+
* App-Build is the whole build number the server compares with its minimum;
|
|
14
14
|
* it is left off when the build is unknown, because the server never refuses a
|
|
15
15
|
* request without it and a guessed number could strand a current build. A value the app
|
|
16
16
|
* already set (a default header, or a per-request one) wins, so an app that
|
|
@@ -29,7 +29,7 @@ export interface AccountStampedConfig {
|
|
|
29
29
|
_accountId?: string | null;
|
|
30
30
|
}
|
|
31
31
|
/**
|
|
32
|
-
* Stamps
|
|
32
|
+
* Stamps Account-Id once, on the first send, and records it on the config. A request must
|
|
33
33
|
* never run in a workspace other than the one it was made in, so every resend (the retry
|
|
34
34
|
* policy, a 401 replay, a refresh replay) keeps the first stamp even after the user switched.
|
|
35
35
|
* A header the caller set itself (an outbox item naming its own workspace) wins over the provider.
|
|
@@ -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;
|
|
@@ -17,8 +17,7 @@ export declare const getMinAppVersion: () => number | null;
|
|
|
17
17
|
*/
|
|
18
18
|
export declare const addMinAppVersionListener: (listener: MinAppVersionListener) => (() => void);
|
|
19
19
|
/**
|
|
20
|
-
* Reads the floor off every response the client receives, a rejected one too
|
|
21
|
-
* a 426 Upgrade Required is exactly the case the floor exists for.
|
|
20
|
+
* Reads the floor off every response the client receives, a rejected one too.
|
|
22
21
|
*/
|
|
23
22
|
export declare const readMinAppVersion: (client: AxiosInstance) => void;
|
|
24
23
|
//# sourceMappingURL=minAppVersion.d.ts.map
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import type { AxiosInstance } from "axios";
|
|
2
|
+
export declare const TRACEPARENT_HEADER = "traceparent";
|
|
2
3
|
/**
|
|
3
|
-
* A fresh
|
|
4
|
-
*
|
|
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
|
|
5
|
+
* only have to be unique enough to find one request in the logs; they are never secrets.
|
|
6
6
|
*/
|
|
7
|
-
export declare const
|
|
7
|
+
export declare const newTraceparent: () => {
|
|
8
|
+
header: string;
|
|
9
|
+
traceId: string;
|
|
10
|
+
};
|
|
8
11
|
/**
|
|
9
|
-
* Applies the configured timeout and the
|
|
12
|
+
* Applies the configured timeout and the traceparent per request, read on every send so a
|
|
10
13
|
* value set after import still applies (the same reason `applyConfiguredBaseUrl` exists).
|
|
11
14
|
* - `timeoutMs`: used when the request carries no timeout of its own; a request's own axios
|
|
12
15
|
* `timeout` wins. 0, either way, means no timeout (axios's own meaning).
|
|
13
|
-
* - `
|
|
14
|
-
* resends the same config, so it keeps the
|
|
16
|
+
* - `traceparent`: a fresh one unless the request already carries one. A retry
|
|
17
|
+
* resends the same config, so it keeps the value the first attempt went out with.
|
|
15
18
|
*
|
|
16
19
|
* axios fills a missing `timeout` with its own default of 0, which would make "this request
|
|
17
20
|
* 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.js";
|
|
2
2
|
import type { UserAuthOptions } from "../initUserAuth.js";
|
|
3
|
+
/** The 401 `code` the server sends when it revoked the session (a revoked token or a rejected cookie stamp). */
|
|
4
|
+
export declare const SESSION_REVOKED_CODE = "session_revoked";
|
|
5
|
+
/** The 401 `code` the server sends when the 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
|
|
@@ -33,14 +37,13 @@ export declare const getAccountIdProvider: () => UserAuthOptions["accountId"];
|
|
|
33
37
|
/** The refresh endpoint's path, once initUserAuth has run. */
|
|
34
38
|
export declare const getRefreshPath: () => string | undefined;
|
|
35
39
|
export declare const dispatchLogout: () => Promise<void>;
|
|
36
|
-
export declare const SIGN_OUT_REASON_HEADER = "X-Sign-Out-Reason";
|
|
37
|
-
/** The server's sign-out reason from response headers (AxiosHeaders or a plain object), or null. */
|
|
38
|
-
export declare const readSignOutReason: (headers: unknown) => string | null;
|
|
39
40
|
/**
|
|
40
41
|
* The session is over: clear it (tokens and session user removed, listeners told why, with
|
|
41
|
-
* the server's
|
|
42
|
+
* the server's session code), then dispatch the app's logout.
|
|
42
43
|
*/
|
|
43
|
-
export declare const endSessionAndLogout: (reason: SessionEndReason,
|
|
44
|
+
export declare const endSessionAndLogout: (reason: SessionEndReason, code: string | null) => Promise<void>;
|
|
45
|
+
/** The server's session code in a 401 body (`session_revoked`, `session_expired`), or null. */
|
|
46
|
+
export declare const sessionCodeOf: (data: unknown) => string | null;
|
|
44
47
|
export declare const isRefreshInFlight: () => boolean;
|
|
45
48
|
/** Opens the replacement window for one flagged request; call the returned function once it settles. */
|
|
46
49
|
export declare const beginSessionReplacement: () => (() => void);
|