@webority/mobile-core 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -29,6 +29,7 @@ import { Geolocation } from '@webority/mobile-core/geolocation';
29
29
  import { openDatabase } from '@webority/mobile-core/sqlite';
30
30
  import { downloadFile } from '@webority/mobile-core/download';
31
31
  import { shareFile } from '@webority/mobile-core/share';
32
+ import { requestReviewAfterSuccess } from '@webority/mobile-core/review';
32
33
  import { initPush, enablePush, usePush } from '@webority/mobile-core/push';
33
34
  import { definePushActionTask } from '@webority/mobile-core/push/task';
34
35
  import { compressImage } from '@webority/mobile-core/compressor';
@@ -164,6 +165,19 @@ the listener and the Android task can each report it) runs once.
164
165
  Storage and network status stay on the main barrel but load no package on their own. Wire them at
165
166
  startup with `setStorageImplementation` and `setNetworkStatusImplementation` if you want them.
166
167
 
168
+ ## Build number header and forced updates
169
+
170
+ Both HTTP clients send `X-App-Build: <integer>` on every request: the app's whole build number
171
+ (`VERSION_INTERNAL`, Android versionCode, iOS CFBundleVersion). The API compares it with its minimum
172
+ and answers an older build with 426 `client_update_required`. The number comes from
173
+ `initWebority({ appBuild })` (`setConfig({ appBuild })`), else from expo-application's
174
+ `nativeBuildVersion`. Without a whole number the header is left off, and the server never refuses a
175
+ request without it. `X-Client-Build` (`<appVersion>+<commit>`) is unchanged.
176
+
177
+ A 426 `client_update_required` opens the same force-update flow as the `X-Min-App-Version` response
178
+ header: `useVersionCheck` turns `isUpdateRequired` on. The floor is the header when the reply has
179
+ one, else the body's `minVersion`, else one above this build. A 426 is never retried.
180
+
167
181
  ## Quiet requests and log redaction
168
182
 
169
183
  Both HTTP clients log a failed response through `Logger.error`. For a call whose failure the app
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.getClientBuild=exports.applyClientHeaders=exports.applyAccountIdHeader=exports.CLIENT_KIND_HEADER=exports.CLIENT_KIND=exports.CLIENT_BUILD_HEADER=exports.ACCOUNT_ID_HEADER=void 0;var _index=require("../config/index.js");const CLIENT_BUILD_HEADER=exports.CLIENT_BUILD_HEADER="X-Client-Build",CLIENT_KIND_HEADER=exports.CLIENT_KIND_HEADER="X-Client-Kind",ACCOUNT_ID_HEADER=exports.ACCOUNT_ID_HEADER="X-Account-Id",CLIENT_KIND=exports.CLIENT_KIND="MobileApp",getClientBuild=()=>{const e=(0,_index.getConfigValue)("appVersion");if(!e)return null;const t=(0,_index.getConfigValue)("commit");return t?`${e}+${t}`:e};exports.getClientBuild=getClientBuild;const setIfAbsent=(e,t,s)=>{s&&!e.has(t)&&e.set(t,s)},applyClientHeaders=e=>{e.interceptors.request.use(e=>(setIfAbsent(e.headers,CLIENT_KIND_HEADER,CLIENT_KIND),setIfAbsent(e.headers,CLIENT_BUILD_HEADER,getClientBuild()),e))};exports.applyClientHeaders=applyClientHeaders;const applyAccountIdHeader=async(e,t)=>{const s=t?await t():null;s&&e.set(ACCOUNT_ID_HEADER,s)};exports.applyAccountIdHeader=applyAccountIdHeader;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.getClientBuild=exports.applyClientHeaders=exports.applyAccountIdHeader=exports.CLIENT_KIND_HEADER=exports.CLIENT_KIND=exports.CLIENT_BUILD_HEADER=exports.APP_BUILD_HEADER=exports.ACCOUNT_ID_HEADER=void 0;var _index=require("../config/index.js"),_appBuild=require("../versionCheck/appBuild.js");const CLIENT_BUILD_HEADER=exports.CLIENT_BUILD_HEADER="X-Client-Build",APP_BUILD_HEADER=exports.APP_BUILD_HEADER="X-App-Build",CLIENT_KIND_HEADER=exports.CLIENT_KIND_HEADER="X-Client-Kind",ACCOUNT_ID_HEADER=exports.ACCOUNT_ID_HEADER="X-Account-Id",CLIENT_KIND=exports.CLIENT_KIND="MobileApp",getClientBuild=()=>{const e=(0,_index.getConfigValue)("appVersion");if(!e)return null;const t=(0,_index.getConfigValue)("commit");return t?`${e}+${t}`:e};exports.getClientBuild=getClientBuild;const setIfAbsent=(e,t,s)=>{s&&!e.has(t)&&e.set(t,s)},applyClientHeaders=e=>{e.interceptors.request.use(e=>{setIfAbsent(e.headers,CLIENT_KIND_HEADER,CLIENT_KIND),setIfAbsent(e.headers,CLIENT_BUILD_HEADER,getClientBuild());const t=(0,_appBuild.readAppBuild)();return setIfAbsent(e.headers,APP_BUILD_HEADER,null===t?null:String(t)),e})};exports.applyClientHeaders=applyClientHeaders;const applyAccountIdHeader=async(e,t)=>{const s=t?await t():null;s&&e.set(ACCOUNT_ID_HEADER,s)};exports.applyAccountIdHeader=applyAccountIdHeader;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.readMinAppVersion=exports.getMinAppVersion=exports.addMinAppVersionListener=exports.MIN_APP_VERSION_HEADER=void 0;var _index=require("../logger/index.js");const MIN_APP_VERSION_HEADER=exports.MIN_APP_VERSION_HEADER="X-Min-App-Version";let current=null;const listeners=new Set,getMinAppVersion=()=>current;exports.getMinAppVersion=getMinAppVersion;const addMinAppVersionListener=e=>(listeners.add(e),null!==current&&e(current),()=>{listeners.delete(e)});exports.addMinAppVersionListener=addMinAppVersionListener;const readHeader=e=>{if(!e||"object"!=typeof e)return;const r=e.get;return"function"==typeof r?r.call(e,MIN_APP_VERSION_HEADER):e[MIN_APP_VERSION_HEADER.toLowerCase()]},record=e=>{const r=readHeader(e?.headers);if(null==r||""===r)return;const n=String(r).trim();if(!/^\d+$/.test(n))return void _index.Logger.warn(`[minAppVersion] ignored a non-integer ${MIN_APP_VERSION_HEADER}: ${n}`);const t=Number.parseInt(n,10);if(t!==current){current=t;for(const e of[...listeners])try{e(t)}catch(e){_index.Logger.error("[minAppVersion] a listener threw",e)}}},readMinAppVersion=e=>{e.interceptors.response.use(e=>(record(e),e),e=>(record(e?.response),Promise.reject(e)))};exports.readMinAppVersion=readMinAppVersion;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.readMinAppVersion=exports.getMinAppVersion=exports.addMinAppVersionListener=exports.MIN_APP_VERSION_HEADER=void 0;var _index=require("../logger/index.js"),_appBuild=require("../versionCheck/appBuild.js"),_buildNumber=require("../versionCheck/buildNumber.js");const MIN_APP_VERSION_HEADER=exports.MIN_APP_VERSION_HEADER="X-Min-App-Version";let current=null;const listeners=new Set,getMinAppVersion=()=>current;exports.getMinAppVersion=getMinAppVersion;const addMinAppVersionListener=e=>(listeners.add(e),null!==current&&e(current),()=>{listeners.delete(e)});exports.addMinAppVersionListener=addMinAppVersionListener;const readHeader=e=>{if(!e||"object"!=typeof e)return;const r=e.get;return"function"==typeof r?r.call(e,MIN_APP_VERSION_HEADER):e[MIN_APP_VERSION_HEADER.toLowerCase()]},setFloor=e=>{if(e!==current){current=e;for(const r of[...listeners])try{r(e)}catch(e){_index.Logger.error("[minAppVersion] a listener threw",e)}}},record=e=>{const r=readHeader(e?.headers);if(null==r||""===r)return;const n=String(r).trim();/^\d+$/.test(n)?setFloor(Number.parseInt(n,10)):_index.Logger.warn(`[minAppVersion] ignored a non-integer ${MIN_APP_VERSION_HEADER}: ${n}`)},bodyField=(e,r)=>e&&"object"==typeof e?e[r]:void 0,recordUpdateRequired=e=>{if(426!==e?.status||"client_update_required"!==bodyField(e.data,"code"))return;const r=(0,_appBuild.readAppBuild)(),n=(0,_buildNumber.parseBuildNumber)(bodyField(e.data,"minVersion"))??(null===r?null:r+1);null!==n&&setFloor(Math.max(current??0,n))},readMinAppVersion=e=>{e.interceptors.response.use(e=>(record(e),e),e=>{const r=e?.response;return record(r),recordUpdateRequired(r),Promise.reject(e)})};exports.readMinAppVersion=readMinAppVersion;
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.requestReviewAfterSuccess=exports.configureReviewPrompt=exports.REVIEW_PROMPT_STORAGE_KEY=exports.DEFAULT_REVIEW_COOL_DOWN_DAYS=void 0;var _index=require("../logger/index.js"),_index2=require("../storage/index.js");const TAG="[reviewPrompt]",DAY_MS=864e5,DEFAULT_REVIEW_COOL_DOWN_DAYS=exports.DEFAULT_REVIEW_COOL_DOWN_DAYS=90,REVIEW_PROMPT_STORAGE_KEY=exports.REVIEW_PROMPT_STORAGE_KEY="@webority/review-prompt",storeReview=()=>require("expo-store-review");let coolDownDays=DEFAULT_REVIEW_COOL_DOWN_DAYS,inFlight=!1;const configureReviewPrompt=(e={})=>{const o=e.coolDownDays??DEFAULT_REVIEW_COOL_DOWN_DAYS;if(!Number.isFinite(o)||o<0)throw new Error(`[@webority/mobile-core] configureReviewPrompt: coolDownDays must be a number of days, 0 or more; got ${String(o)}.`);o<DEFAULT_REVIEW_COOL_DOWN_DAYS&&_index.Logger.warn(`${TAG} coolDownDays ${o} is under the ${DEFAULT_REVIEW_COOL_DOWN_DAYS}-day minimum for store builds.`),coolDownDays=o};exports.configureReviewPrompt=configureReviewPrompt;const withinCoolDown=(e,o)=>{if(!e)return!1;const r=Date.parse(e.lastAskedUtc);return!Number.isNaN(r)&&o-r<coolDownDays*DAY_MS},storeCanPrompt=async()=>{try{const e=storeReview();return await e.isAvailableAsync()&&await e.hasAction()}catch(e){return _index.Logger.warn(`${TAG} the store review module is not available`,e),!1}},requestReviewAfterSuccess=async e=>{if(inFlight)return"skipped-cooldown";inFlight=!0;try{const o=await _index2.Storage.getJson(REVIEW_PROMPT_STORAGE_KEY),r=Date.now();if(withinCoolDown(o,r))return"skipped-cooldown";if(!await storeCanPrompt())return"skipped-unavailable";try{await storeReview().requestReview()}catch(o){return _index.Logger.warn(`${TAG} the store review request failed after "${e}"`,o),"skipped-unavailable"}const t={lastAskedUtc:new Date(r).toISOString(),moment:e};return await _index2.Storage.setJson(REVIEW_PROMPT_STORAGE_KEY,t)||_index.Logger.warn(`${TAG} the prompt time could not be stored, so the cool-down may not hold`),_index.Logger.info(`${TAG} review prompt requested after "${e}"`),"shown"}finally{inFlight=!1}};exports.requestReviewAfterSuccess=requestReviewAfterSuccess;
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.setDeviceInfo=exports.readAppBuild=exports.getDeviceInfo=exports.expoApplicationBackend=void 0;var Application=_interopRequireWildcard(require("expo-application")),_index=require("../config/index.js"),_buildNumber=require("./buildNumber.js");function _interopRequireWildcard(e,i){if("function"==typeof WeakMap)var t=new WeakMap,n=new WeakMap;return(_interopRequireWildcard=function(e,i){if(!i&&e&&e.__esModule)return e;var r,o,p={__proto__:null,default:e};if(null===e||"object"!=typeof e&&"function"!=typeof e)return p;if(r=i?n:t){if(r.has(e))return r.get(e);r.set(e,p)}for(const i in e)"default"!==i&&{}.hasOwnProperty.call(e,i)&&((o=(r=Object.defineProperty)&&Object.getOwnPropertyDescriptor(e,i))&&(o.get||o.set)?r(p,i,o):p[i]=e[i]);return p})(e,i)}const expoApplicationBackend=exports.expoApplicationBackend={getBundleId:()=>Application.applicationId??"",getVersion:()=>Application.nativeApplicationVersion??"",getBuildNumber:()=>Application.nativeBuildVersion??""};let deviceInfo=null;const setDeviceInfo=e=>{deviceInfo=e};exports.setDeviceInfo=setDeviceInfo;const getDeviceInfo=()=>deviceInfo;exports.getDeviceInfo=getDeviceInfo;const readAppBuild=()=>{const e=(0,_buildNumber.parseBuildNumber)((0,_index.getConfigValue)("appBuild"));if(null!==e)return e;try{return(0,_buildNumber.parseBuildNumber)(deviceInfo?.getBuildNumber?.()??expoApplicationBackend.getBuildNumber())}catch{return null}};exports.readAppBuild=readAppBuild;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=exports.VersionCheck=void 0,Object.defineProperty(exports,"isBuildBelowMinimum",{enumerable:!0,get:function(){return _buildNumber.isBuildBelowMinimum}}),Object.defineProperty(exports,"parseBuildNumber",{enumerable:!0,get:function(){return _buildNumber.parseBuildNumber}}),exports.useVersionCheck=exports.setDeviceInfoImplementation=void 0;var Application=_interopRequireWildcard(require("expo-application")),_react=require("react"),_minAppVersion=require("../api/minAppVersion.js"),_index=require("../config/index.js"),_index2=require("../logger/index.js"),_buildNumber=require("./buildNumber.js");function _interopRequireWildcard(e,r){if("function"==typeof WeakMap)var i=new WeakMap,t=new WeakMap;return(_interopRequireWildcard=function(e,r){if(!r&&e&&e.__esModule)return e;var n,o,l={__proto__:null,default:e};if(null===e||"object"!=typeof e&&"function"!=typeof e)return l;if(n=r?t:i){if(n.has(e))return n.get(e);n.set(e,l)}for(const r in e)"default"!==r&&{}.hasOwnProperty.call(e,r)&&((o=(n=Object.defineProperty)&&Object.getOwnPropertyDescriptor(e,r))&&(o.get||o.set)?n(l,r,o):l[r]=e[r]);return l})(e,r)}const expoApplicationBackend={getBundleId:()=>Application.applicationId??"",getVersion:()=>Application.nativeApplicationVersion??"",getBuildNumber:()=>Application.nativeBuildVersion??""};let deviceInfo=null;const setDeviceInfoImplementation=e=>{deviceInfo=e};exports.setDeviceInfoImplementation=setDeviceInfoImplementation;const missing=(e,r)=>{const i=new Error(`[VersionCheck] the app's ${e} is not available. Call setConfig({ ${r}: ... }) — every Webority app already has this value in appsettings.Compiled.json — or setDeviceInfoImplementation() with a native provider.`);return _index2.Logger.error(i.message),i},getSystemName=()=>{const{Platform:e}=require("react-native");return e.OS},getBundleId=()=>{const e=(0,_index.getConfigValue)("appId");if(e)return e;const r=(deviceInfo??expoApplicationBackend).getBundleId();if(r)return r;throw missing("store identifier","appId")},getBuildNumber=()=>{const e=(0,_buildNumber.parseBuildNumber)((0,_index.getConfigValue)("appBuild"));if(null!==e)return e;const r=(0,_buildNumber.parseBuildNumber)(deviceInfo?.getBuildNumber?.()??expoApplicationBackend.getBuildNumber());if(null!==r)return r;throw missing("build number","appBuild")},getCountry=()=>(0,_index.getConfigValue)("country","in").toLowerCase(),subscribeMinimum=e=>(0,_minAppVersion.addMinAppVersionListener)(()=>e()),getFallbackStoreUrl=()=>{const e=getSystemName().toLowerCase(),r=getCountry();if("ios"===e){const e=(0,_index.getConfigValue)("appStoreId");return e?`https://apps.apple.com/${r}/app/id${e}`:`https://apps.apple.com/${r}/app/${getBundleId()}`}return`https://play.google.com/store/apps/details?id=${getBundleId()}&hl=${r}`},storeUrlOrNull=()=>{try{return getFallbackStoreUrl()}catch{return null}},checkForUpdates=()=>{const e=(0,_buildNumber.isBuildBelowMinimum)(getBuildNumber(),(0,_minAppVersion.getMinAppVersion)());return{isNewVersionAvailable:e,updateType:e?"major":null,url:e?storeUrlOrNull():null}},VersionCheck=exports.VersionCheck={isNewVersionAvailable:async()=>checkForUpdates(),openUpdateUrl:async e=>{const{Linking:r}=require("react-native");try{if(e)_index2.Logger.info(`Opening app store URL: ${e}`),await r.openURL(e);else{const e=getFallbackStoreUrl();_index2.Logger.info(`Opening fallback store URL: ${e}`),await r.openURL(e)}return!0}catch(i){const t=i instanceof Error?i.message:String(i);_index2.Logger.error(`Failed to open store URL: ${t}`);try{if(e){const e=getFallbackStoreUrl();return _index2.Logger.info(`Trying fallback store URL: ${e}`),await r.openURL(e),!0}}catch(e){const r=e instanceof Error?e.message:String(e);_index2.Logger.error(`Failed to open fallback URL: ${r}`)}return!1}},getFallbackStoreUrl:getFallbackStoreUrl},NOT_REQUIRED={isUpdateRequired:!1,updateUrl:null},useVersionCheck=(e,r=(0,_index.getConfigValue)("environment","Development"))=>{const i=(0,_react.useSyncExternalStore)(subscribeMinimum,_minAppVersion.getMinAppVersion,_minAppVersion.getMinAppVersion);return(0,_react.useMemo)(()=>{if(!e||"Production"!==r||null===i)return NOT_REQUIRED;try{if(!(0,_buildNumber.isBuildBelowMinimum)(getBuildNumber(),i))return NOT_REQUIRED}catch{return NOT_REQUIRED}return{isUpdateRequired:!0,updateUrl:storeUrlOrNull()}},[e,r,i])};exports.useVersionCheck=useVersionCheck;var _default=exports.default=VersionCheck;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=exports.VersionCheck=void 0,Object.defineProperty(exports,"isBuildBelowMinimum",{enumerable:!0,get:function(){return _buildNumber.isBuildBelowMinimum}}),Object.defineProperty(exports,"parseBuildNumber",{enumerable:!0,get:function(){return _buildNumber.parseBuildNumber}}),Object.defineProperty(exports,"readAppBuild",{enumerable:!0,get:function(){return _appBuild.readAppBuild}}),exports.useVersionCheck=exports.setDeviceInfoImplementation=void 0;var _react=require("react"),_minAppVersion=require("../api/minAppVersion.js"),_index=require("../config/index.js"),_index2=require("../logger/index.js"),_appBuild=require("./appBuild.js"),_buildNumber=require("./buildNumber.js");const setDeviceInfoImplementation=e=>{(0,_appBuild.setDeviceInfo)(e)};exports.setDeviceInfoImplementation=setDeviceInfoImplementation;const missing=(e,r)=>{const t=new Error(`[VersionCheck] the app's ${e} is not available. Call setConfig({ ${r}: ... }) — every Webority app already has this value in appsettings.Compiled.json — or setDeviceInfoImplementation() with a native provider.`);return _index2.Logger.error(t.message),t},getSystemName=()=>{const{Platform:e}=require("react-native");return e.OS},getBundleId=()=>{const e=(0,_index.getConfigValue)("appId");if(e)return e;const r=((0,_appBuild.getDeviceInfo)()??_appBuild.expoApplicationBackend).getBundleId();if(r)return r;throw missing("store identifier","appId")},getBuildNumber=()=>{const e=(0,_appBuild.readAppBuild)();if(null!==e)return e;throw missing("build number","appBuild")},getCountry=()=>(0,_index.getConfigValue)("country","in").toLowerCase(),subscribeMinimum=e=>(0,_minAppVersion.addMinAppVersionListener)(()=>e()),getFallbackStoreUrl=()=>{const e=getSystemName().toLowerCase(),r=getCountry();if("ios"===e){const e=(0,_index.getConfigValue)("appStoreId");return e?`https://apps.apple.com/${r}/app/id${e}`:`https://apps.apple.com/${r}/app/${getBundleId()}`}return`https://play.google.com/store/apps/details?id=${getBundleId()}&hl=${r}`},storeUrlOrNull=()=>{try{return getFallbackStoreUrl()}catch{return null}},checkForUpdates=()=>{const e=(0,_buildNumber.isBuildBelowMinimum)(getBuildNumber(),(0,_minAppVersion.getMinAppVersion)());return{isNewVersionAvailable:e,updateType:e?"major":null,url:e?storeUrlOrNull():null}},VersionCheck=exports.VersionCheck={isNewVersionAvailable:async()=>checkForUpdates(),openUpdateUrl:async e=>{const{Linking:r}=require("react-native");try{if(e)_index2.Logger.info(`Opening app store URL: ${e}`),await r.openURL(e);else{const e=getFallbackStoreUrl();_index2.Logger.info(`Opening fallback store URL: ${e}`),await r.openURL(e)}return!0}catch(t){const i=t instanceof Error?t.message:String(t);_index2.Logger.error(`Failed to open store URL: ${i}`);try{if(e){const e=getFallbackStoreUrl();return _index2.Logger.info(`Trying fallback store URL: ${e}`),await r.openURL(e),!0}}catch(e){const r=e instanceof Error?e.message:String(e);_index2.Logger.error(`Failed to open fallback URL: ${r}`)}return!1}},getFallbackStoreUrl:getFallbackStoreUrl},NOT_REQUIRED={isUpdateRequired:!1,updateUrl:null},useVersionCheck=(e,r=(0,_index.getConfigValue)("environment","Development"))=>{const t=(0,_react.useSyncExternalStore)(subscribeMinimum,_minAppVersion.getMinAppVersion,_minAppVersion.getMinAppVersion);return(0,_react.useMemo)(()=>{if(!e||"Production"!==r||null===t)return NOT_REQUIRED;try{if(!(0,_buildNumber.isBuildBelowMinimum)(getBuildNumber(),t))return NOT_REQUIRED}catch{return NOT_REQUIRED}return{isUpdateRequired:!0,updateUrl:storeUrlOrNull()}},[e,r,t])};exports.useVersionCheck=useVersionCheck;var _default=exports.default=VersionCheck;
@@ -1 +1 @@
1
- "use strict";import{getConfigValue}from"../config/index.js";export const CLIENT_BUILD_HEADER="X-Client-Build";export const CLIENT_KIND_HEADER="X-Client-Kind";export const ACCOUNT_ID_HEADER="X-Account-Id";export const CLIENT_KIND="MobileApp";export const getClientBuild=()=>{const t=getConfigValue("appVersion");if(!t)return null;const e=getConfigValue("commit");return e?`${t}+${e}`:t};const setIfAbsent=(t,e,n)=>{n&&!t.has(e)&&t.set(e,n)};export const applyClientHeaders=t=>{t.interceptors.request.use(t=>(setIfAbsent(t.headers,"X-Client-Kind","MobileApp"),setIfAbsent(t.headers,"X-Client-Build",getClientBuild()),t))};export const applyAccountIdHeader=async(t,e)=>{const n=e?await e():null;n&&t.set("X-Account-Id",n)};
1
+ "use strict";import{getConfigValue}from"../config/index.js";import{readAppBuild}from"../versionCheck/appBuild.js";export const CLIENT_BUILD_HEADER="X-Client-Build";export const APP_BUILD_HEADER="X-App-Build";export const CLIENT_KIND_HEADER="X-Client-Kind";export const ACCOUNT_ID_HEADER="X-Account-Id";export const CLIENT_KIND="MobileApp";export const getClientBuild=()=>{const e=getConfigValue("appVersion");if(!e)return null;const t=getConfigValue("commit");return t?`${e}+${t}`:e};const setIfAbsent=(e,t,n)=>{n&&!e.has(t)&&e.set(t,n)};export const applyClientHeaders=e=>{e.interceptors.request.use(e=>{setIfAbsent(e.headers,"X-Client-Kind","MobileApp"),setIfAbsent(e.headers,"X-Client-Build",getClientBuild());const t=readAppBuild();return setIfAbsent(e.headers,"X-App-Build",null===t?null:String(t)),e})};export const applyAccountIdHeader=async(e,t)=>{const n=t?await t():null;n&&e.set("X-Account-Id",n)};
@@ -1 +1 @@
1
- "use strict";import{Logger}from"../logger/index.js";export const MIN_APP_VERSION_HEADER="X-Min-App-Version";let current=null;const listeners=new Set;export const getMinAppVersion=()=>current;export const addMinAppVersionListener=e=>(listeners.add(e),null!==current&&e(current),()=>{listeners.delete(e)});const readHeader=e=>{if(!e||"object"!=typeof e)return;const r=e.get;return"function"==typeof r?r.call(e,"X-Min-App-Version"):e["X-Min-App-Version".toLowerCase()]},record=e=>{const r=readHeader(e?.headers);if(null==r||""===r)return;const n=String(r).trim();if(!/^\d+$/.test(n))return void Logger.warn(`[minAppVersion] ignored a non-integer X-Min-App-Version: ${n}`);const t=Number.parseInt(n,10);if(t!==current){current=t;for(const e of[...listeners])try{e(t)}catch(e){Logger.error("[minAppVersion] a listener threw",e)}}};export const readMinAppVersion=e=>{e.interceptors.response.use(e=>(record(e),e),e=>(record(e?.response),Promise.reject(e)))};
1
+ "use strict";import{Logger}from"../logger/index.js";import{readAppBuild}from"../versionCheck/appBuild.js";import{parseBuildNumber}from"../versionCheck/buildNumber.js";export const MIN_APP_VERSION_HEADER="X-Min-App-Version";let current=null;const listeners=new Set;export const getMinAppVersion=()=>current;export const addMinAppVersionListener=e=>(listeners.add(e),null!==current&&e(current),()=>{listeners.delete(e)});const readHeader=e=>{if(!e||"object"!=typeof e)return;const r=e.get;return"function"==typeof r?r.call(e,"X-Min-App-Version"):e["X-Min-App-Version".toLowerCase()]},setFloor=e=>{if(e!==current){current=e;for(const r of[...listeners])try{r(e)}catch(e){Logger.error("[minAppVersion] a listener threw",e)}}},record=e=>{const r=readHeader(e?.headers);if(null==r||""===r)return;const t=String(r).trim();/^\d+$/.test(t)?setFloor(Number.parseInt(t,10)):Logger.warn(`[minAppVersion] ignored a non-integer X-Min-App-Version: ${t}`)},bodyField=(e,r)=>e&&"object"==typeof e?e[r]:void 0,recordUpdateRequired=e=>{if(426!==e?.status||"client_update_required"!==bodyField(e.data,"code"))return;const r=readAppBuild(),t=parseBuildNumber(bodyField(e.data,"minVersion"))??(null===r?null:r+1);null!==t&&setFloor(Math.max(current??0,t))};export const readMinAppVersion=e=>{e.interceptors.response.use(e=>(record(e),e),e=>{const r=e?.response;return record(r),recordUpdateRequired(r),Promise.reject(e)})};
@@ -0,0 +1 @@
1
+ "use strict";import{Logger}from"../logger/index.js";import{Storage}from"../storage/index.js";const TAG="[reviewPrompt]",DAY_MS=864e5;export const DEFAULT_REVIEW_COOL_DOWN_DAYS=90;export const REVIEW_PROMPT_STORAGE_KEY="@webority/review-prompt";const storeReview=()=>require("expo-store-review");let coolDownDays=90,inFlight=!1;export const configureReviewPrompt=(e={})=>{const o=e.coolDownDays??90;if(!Number.isFinite(o)||o<0)throw new Error(`[@webority/mobile-core] configureReviewPrompt: coolDownDays must be a number of days, 0 or more; got ${String(o)}.`);o<90&&Logger.warn(`${TAG} coolDownDays ${o} is under the 90-day minimum for store builds.`),coolDownDays=o};const withinCoolDown=(e,o)=>{if(!e)return!1;const t=Date.parse(e.lastAskedUtc);return!Number.isNaN(t)&&o-t<coolDownDays*DAY_MS},storeCanPrompt=async()=>{try{const e=storeReview();return await e.isAvailableAsync()&&await e.hasAction()}catch(e){return Logger.warn(`${TAG} the store review module is not available`,e),!1}};export const requestReviewAfterSuccess=async e=>{if(inFlight)return"skipped-cooldown";inFlight=!0;try{const o=await Storage.getJson("@webority/review-prompt"),t=Date.now();if(withinCoolDown(o,t))return"skipped-cooldown";if(!await storeCanPrompt())return"skipped-unavailable";try{await storeReview().requestReview()}catch(o){return Logger.warn(`${TAG} the store review request failed after "${e}"`,o),"skipped-unavailable"}const r={lastAskedUtc:new Date(t).toISOString(),moment:e};return await Storage.setJson("@webority/review-prompt",r)||Logger.warn(`${TAG} the prompt time could not be stored, so the cool-down may not hold`),Logger.info(`${TAG} review prompt requested after "${e}"`),"shown"}finally{inFlight=!1}};
@@ -0,0 +1 @@
1
+ "use strict";import*as Application from"expo-application";import{getConfigValue}from"../config/index.js";import{parseBuildNumber}from"./buildNumber.js";export const expoApplicationBackend={getBundleId:()=>Application.applicationId??"",getVersion:()=>Application.nativeApplicationVersion??"",getBuildNumber:()=>Application.nativeBuildVersion??""};let deviceInfo=null;export const setDeviceInfo=e=>{deviceInfo=e};export const getDeviceInfo=()=>deviceInfo;export const readAppBuild=()=>{const e=parseBuildNumber(getConfigValue("appBuild"));if(null!==e)return e;try{return parseBuildNumber(deviceInfo?.getBuildNumber?.()??expoApplicationBackend.getBuildNumber())}catch{return null}};
@@ -1 +1 @@
1
- "use strict";import*as Application from"expo-application";import{useMemo,useSyncExternalStore}from"react";import{addMinAppVersionListener,getMinAppVersion}from"../api/minAppVersion.js";import{getConfigValue}from"../config/index.js";import{Logger}from"../logger/index.js";import{isBuildBelowMinimum,parseBuildNumber}from"./buildNumber.js";const expoApplicationBackend={getBundleId:()=>Application.applicationId??"",getVersion:()=>Application.nativeApplicationVersion??"",getBuildNumber:()=>Application.nativeBuildVersion??""};let deviceInfo=null;export const setDeviceInfoImplementation=e=>{deviceInfo=e};const missing=(e,r)=>{const t=new Error(`[VersionCheck] the app's ${e} is not available. Call setConfig({ ${r}: ... }) — every Webority app already has this value in appsettings.Compiled.json — or setDeviceInfoImplementation() with a native provider.`);return Logger.error(t.message),t},getSystemName=()=>{const{Platform:e}=require("react-native");return e.OS},getBundleId=()=>{const e=getConfigValue("appId");if(e)return e;const r=(deviceInfo??expoApplicationBackend).getBundleId();if(r)return r;throw missing("store identifier","appId")},getBuildNumber=()=>{const e=parseBuildNumber(getConfigValue("appBuild"));if(null!==e)return e;const r=parseBuildNumber(deviceInfo?.getBuildNumber?.()??expoApplicationBackend.getBuildNumber());if(null!==r)return r;throw missing("build number","appBuild")},getCountry=()=>getConfigValue("country","in").toLowerCase(),subscribeMinimum=e=>addMinAppVersionListener(()=>e()),getFallbackStoreUrl=()=>{const e=getSystemName().toLowerCase(),r=getCountry();if("ios"===e){const e=getConfigValue("appStoreId");return e?`https://apps.apple.com/${r}/app/id${e}`:`https://apps.apple.com/${r}/app/${getBundleId()}`}return`https://play.google.com/store/apps/details?id=${getBundleId()}&hl=${r}`},storeUrlOrNull=()=>{try{return getFallbackStoreUrl()}catch{return null}},checkForUpdates=()=>{const e=isBuildBelowMinimum(getBuildNumber(),getMinAppVersion());return{isNewVersionAvailable:e,updateType:e?"major":null,url:e?storeUrlOrNull():null}};export const VersionCheck={isNewVersionAvailable:async()=>checkForUpdates(),openUpdateUrl:async e=>{const{Linking:r}=require("react-native");try{if(e)Logger.info(`Opening app store URL: ${e}`),await r.openURL(e);else{const e=getFallbackStoreUrl();Logger.info(`Opening fallback store URL: ${e}`),await r.openURL(e)}return!0}catch(t){const i=t instanceof Error?t.message:String(t);Logger.error(`Failed to open store URL: ${i}`);try{if(e){const e=getFallbackStoreUrl();return Logger.info(`Trying fallback store URL: ${e}`),await r.openURL(e),!0}}catch(e){const r=e instanceof Error?e.message:String(e);Logger.error(`Failed to open fallback URL: ${r}`)}return!1}},getFallbackStoreUrl:getFallbackStoreUrl};const NOT_REQUIRED={isUpdateRequired:!1,updateUrl:null};export const useVersionCheck=(e,r=getConfigValue("environment","Development"))=>{const t=useSyncExternalStore(subscribeMinimum,getMinAppVersion,getMinAppVersion);return useMemo(()=>{if(!e||"Production"!==r||null===t)return NOT_REQUIRED;try{if(!isBuildBelowMinimum(getBuildNumber(),t))return NOT_REQUIRED}catch{return NOT_REQUIRED}return{isUpdateRequired:!0,updateUrl:storeUrlOrNull()}},[e,r,t])};export default VersionCheck;export{isBuildBelowMinimum,parseBuildNumber}from"./buildNumber.js";
1
+ "use strict";import{useMemo,useSyncExternalStore}from"react";import{addMinAppVersionListener,getMinAppVersion}from"../api/minAppVersion.js";import{getConfigValue}from"../config/index.js";import{Logger}from"../logger/index.js";import{expoApplicationBackend,getDeviceInfo,readAppBuild,setDeviceInfo}from"./appBuild.js";import{isBuildBelowMinimum}from"./buildNumber.js";export const setDeviceInfoImplementation=e=>{setDeviceInfo(e)};const missing=(e,r)=>{const t=new Error(`[VersionCheck] the app's ${e} is not available. Call setConfig({ ${r}: ... }) — every Webority app already has this value in appsettings.Compiled.json — or setDeviceInfoImplementation() with a native provider.`);return Logger.error(t.message),t},getSystemName=()=>{const{Platform:e}=require("react-native");return e.OS},getBundleId=()=>{const e=getConfigValue("appId");if(e)return e;const r=(getDeviceInfo()??expoApplicationBackend).getBundleId();if(r)return r;throw missing("store identifier","appId")},getBuildNumber=()=>{const e=readAppBuild();if(null!==e)return e;throw missing("build number","appBuild")},getCountry=()=>getConfigValue("country","in").toLowerCase(),subscribeMinimum=e=>addMinAppVersionListener(()=>e()),getFallbackStoreUrl=()=>{const e=getSystemName().toLowerCase(),r=getCountry();if("ios"===e){const e=getConfigValue("appStoreId");return e?`https://apps.apple.com/${r}/app/id${e}`:`https://apps.apple.com/${r}/app/${getBundleId()}`}return`https://play.google.com/store/apps/details?id=${getBundleId()}&hl=${r}`},storeUrlOrNull=()=>{try{return getFallbackStoreUrl()}catch{return null}},checkForUpdates=()=>{const e=isBuildBelowMinimum(getBuildNumber(),getMinAppVersion());return{isNewVersionAvailable:e,updateType:e?"major":null,url:e?storeUrlOrNull():null}};export const VersionCheck={isNewVersionAvailable:async()=>checkForUpdates(),openUpdateUrl:async e=>{const{Linking:r}=require("react-native");try{if(e)Logger.info(`Opening app store URL: ${e}`),await r.openURL(e);else{const e=getFallbackStoreUrl();Logger.info(`Opening fallback store URL: ${e}`),await r.openURL(e)}return!0}catch(t){const o=t instanceof Error?t.message:String(t);Logger.error(`Failed to open store URL: ${o}`);try{if(e){const e=getFallbackStoreUrl();return Logger.info(`Trying fallback store URL: ${e}`),await r.openURL(e),!0}}catch(e){const r=e instanceof Error?e.message:String(e);Logger.error(`Failed to open fallback URL: ${r}`)}return!1}},getFallbackStoreUrl:getFallbackStoreUrl};const NOT_REQUIRED={isUpdateRequired:!1,updateUrl:null};export const useVersionCheck=(e,r=getConfigValue("environment","Development"))=>{const t=useSyncExternalStore(subscribeMinimum,getMinAppVersion,getMinAppVersion);return useMemo(()=>{if(!e||"Production"!==r||null===t)return NOT_REQUIRED;try{if(!isBuildBelowMinimum(getBuildNumber(),t))return NOT_REQUIRED}catch{return NOT_REQUIRED}return{isUpdateRequired:!0,updateUrl:storeUrlOrNull()}},[e,r,t])};export default VersionCheck;export{readAppBuild}from"./appBuild.js";export{isBuildBelowMinimum,parseBuildNumber}from"./buildNumber.js";
@@ -1,5 +1,6 @@
1
1
  import type { AxiosInstance } from 'axios';
2
2
  export declare const CLIENT_BUILD_HEADER = "X-Client-Build";
3
+ export declare const APP_BUILD_HEADER = "X-App-Build";
3
4
  export declare const CLIENT_KIND_HEADER = "X-Client-Kind";
4
5
  export declare const ACCOUNT_ID_HEADER = "X-Account-Id";
5
6
  /** The server picks session rules and the minimum version per kind; the names match its enum. */
@@ -8,7 +9,10 @@ export declare const CLIENT_KIND: ClientKind;
8
9
  /** `<appVersion>+<commit>`, `<appVersion>` when no commit was stamped, `null` before init. */
9
10
  export declare const getClientBuild: () => string | null;
10
11
  /**
11
- * Stamps X-Client-Kind and X-Client-Build on every request. A value the app
12
+ * Stamps X-Client-Kind, X-Client-Build and X-App-Build on every request.
13
+ * X-App-Build is the whole build number the server compares with its minimum;
14
+ * it is left off when the build is unknown, because the server never refuses a
15
+ * request without it and a guessed number could strand a current build. A value the app
12
16
  * already set (a default header, or a per-request one) wins, so an app that
13
17
  * wired them by hand keeps working unchanged.
14
18
  */
@@ -11,6 +11,7 @@
11
11
  * @webority/mobile-core/sqlite expo-sqlite
12
12
  * @webority/mobile-core/download a filesystem module
13
13
  * @webority/mobile-core/share expo-sharing
14
+ * @webority/mobile-core/review expo-store-review
14
15
  * @webority/mobile-core/secureScreen expo-screen-capture
15
16
  * @webority/mobile-core/updates expo-updates
16
17
  * @webority/mobile-core/smsRetriever Android only, own TurboModule
@@ -0,0 +1,42 @@
1
+ /** The fleet's minimum: no more than one prompt in 90 days per user. */
2
+ export declare const DEFAULT_REVIEW_COOL_DOWN_DAYS = 90;
3
+ /** Where the last prompt is remembered, through mobile-core's `Storage`. */
4
+ export declare const REVIEW_PROMPT_STORAGE_KEY = "@webority/review-prompt";
5
+ export interface ReviewPromptOptions {
6
+ /** Days to wait after a prompt before the next one. Defaults to 90, the fleet minimum. */
7
+ coolDownDays?: number;
8
+ }
9
+ /** What was stored when the prompt last showed. */
10
+ export interface ReviewPromptRecord {
11
+ /** When the prompt was last requested, as an ISO 8601 UTC string. */
12
+ lastAskedUtc: string;
13
+ /** The success moment that asked. */
14
+ moment: string;
15
+ }
16
+ export type ReviewPromptResult = 'shown' | 'skipped-cooldown' | 'skipped-unavailable';
17
+ /**
18
+ * Sets the cool-down between prompts. Each call replaces the previous options, so
19
+ * `configureReviewPrompt({})` restores the default. Throws on a negative or
20
+ * non-finite number of days, which is a programming error.
21
+ */
22
+ export declare const configureReviewPrompt: (options?: ReviewPromptOptions) => void;
23
+ /**
24
+ * Shows the platform's rating prompt after a success moment, at most once per
25
+ * cool-down (90 days by default, see `configureReviewPrompt`).
26
+ *
27
+ * Call it only after a success moment and once for that moment; never after an
28
+ * error and never behind a "Do you like the app?" question (App Store guideline
29
+ * 5.6.1, Google Play's review policy).
30
+ *
31
+ * Resolves, never rejects:
32
+ * - `shown`: the prompt was requested and the time and `moment` were stored. The
33
+ * platform still decides whether it appears, and caps how often it does.
34
+ * - `skipped-cooldown`: the last prompt is inside the cool-down, or another call
35
+ * is still running.
36
+ * - `skipped-unavailable`: the device or build has no in-app review (no store,
37
+ * a simulator, the native module missing), or the request failed. Logged.
38
+ *
39
+ * @param moment a short name for the success moment, such as `survey-submitted`.
40
+ */
41
+ export declare const requestReviewAfterSuccess: (moment: string) => Promise<ReviewPromptResult>;
42
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Minimal device-info shape we depend on.
3
+ *
4
+ * This is a LAST RESORT, not the primary path. Every Webority app carries its
5
+ * store id and build number in `appsettings.Compiled.json`, so
6
+ * `setConfig({ appId, appBuild })` covers both without a native module, and
7
+ * expo-application covers an app that wires neither. `getBuildNumber` is
8
+ * optional so an injector written before the server-driven check still fits;
9
+ * without it the build number comes from expo-application.
10
+ */
11
+ export interface DeviceInfoLike {
12
+ getBundleId: () => string;
13
+ getVersion: () => string;
14
+ getBuildNumber?: () => string;
15
+ }
16
+ /**
17
+ * expo-application, read at call time: its values are module CONSTANTS, and
18
+ * capturing them here would snapshot them before the native module has
19
+ * initialised. Config still wins over this.
20
+ */
21
+ export declare const expoApplicationBackend: Required<DeviceInfoLike>;
22
+ export declare const setDeviceInfo: (impl: DeviceInfoLike | null) => void;
23
+ export declare const getDeviceInfo: () => DeviceInfoLike | null;
24
+ /**
25
+ * The app's internal build number (`VERSION_INTERNAL`: Android versionCode, iOS
26
+ * CFBundleVersion) from `setConfig({ appBuild })`, else the device info, else
27
+ * expo-application; `null` when none of them holds an integer. Never throws.
28
+ */
29
+ export declare const readAppBuild: () => number | null;
30
+ //# sourceMappingURL=appBuild.d.ts.map
@@ -1,19 +1,5 @@
1
1
  import { type Environment } from '../config';
2
- /**
3
- * Minimal device-info shape we depend on.
4
- *
5
- * This is a LAST RESORT, not the primary path. Every Webority app carries its
6
- * store id and build number in `appsettings.Compiled.json`, so
7
- * `setConfig({ appId, appBuild })` covers both without a native module, and
8
- * expo-application covers an app that wires neither. `getBuildNumber` is
9
- * optional so an injector written before the server-driven check still fits;
10
- * without it the build number comes from expo-application.
11
- */
12
- interface DeviceInfoLike {
13
- getBundleId: () => string;
14
- getVersion: () => string;
15
- getBuildNumber?: () => string;
16
- }
2
+ import { type DeviceInfoLike } from './appBuild';
17
3
  /**
18
4
  * Supply the app identity from a native module instead of config.
19
5
  * Prefer `setConfig({ appId, appBuild })` — it needs no native dependency.
@@ -86,6 +72,7 @@ export declare const VersionCheck: VersionCheckType;
86
72
  */
87
73
  export declare const useVersionCheck: (isAppReady: boolean, environment?: Environment) => UpdateInfo;
88
74
  export default VersionCheck;
75
+ export { readAppBuild } from './appBuild';
89
76
  export { isBuildBelowMinimum, parseBuildNumber } from './buildNumber';
90
77
  export type { DeviceInfoLike };
91
78
  //# sourceMappingURL=index.d.ts.map
@@ -1,5 +1,6 @@
1
1
  import type { AxiosInstance } from 'axios';
2
2
  export declare const CLIENT_BUILD_HEADER = "X-Client-Build";
3
+ export declare const APP_BUILD_HEADER = "X-App-Build";
3
4
  export declare const CLIENT_KIND_HEADER = "X-Client-Kind";
4
5
  export declare const ACCOUNT_ID_HEADER = "X-Account-Id";
5
6
  /** The server picks session rules and the minimum version per kind; the names match its enum. */
@@ -8,7 +9,10 @@ export declare const CLIENT_KIND: ClientKind;
8
9
  /** `<appVersion>+<commit>`, `<appVersion>` when no commit was stamped, `null` before init. */
9
10
  export declare const getClientBuild: () => string | null;
10
11
  /**
11
- * Stamps X-Client-Kind and X-Client-Build on every request. A value the app
12
+ * Stamps X-Client-Kind, X-Client-Build and X-App-Build on every request.
13
+ * X-App-Build is the whole build number the server compares with its minimum;
14
+ * it is left off when the build is unknown, because the server never refuses a
15
+ * request without it and a guessed number could strand a current build. A value the app
12
16
  * already set (a default header, or a per-request one) wins, so an app that
13
17
  * wired them by hand keeps working unchanged.
14
18
  */
@@ -11,6 +11,7 @@
11
11
  * @webority/mobile-core/sqlite expo-sqlite
12
12
  * @webority/mobile-core/download a filesystem module
13
13
  * @webority/mobile-core/share expo-sharing
14
+ * @webority/mobile-core/review expo-store-review
14
15
  * @webority/mobile-core/secureScreen expo-screen-capture
15
16
  * @webority/mobile-core/updates expo-updates
16
17
  * @webority/mobile-core/smsRetriever Android only, own TurboModule
@@ -0,0 +1,42 @@
1
+ /** The fleet's minimum: no more than one prompt in 90 days per user. */
2
+ export declare const DEFAULT_REVIEW_COOL_DOWN_DAYS = 90;
3
+ /** Where the last prompt is remembered, through mobile-core's `Storage`. */
4
+ export declare const REVIEW_PROMPT_STORAGE_KEY = "@webority/review-prompt";
5
+ export interface ReviewPromptOptions {
6
+ /** Days to wait after a prompt before the next one. Defaults to 90, the fleet minimum. */
7
+ coolDownDays?: number;
8
+ }
9
+ /** What was stored when the prompt last showed. */
10
+ export interface ReviewPromptRecord {
11
+ /** When the prompt was last requested, as an ISO 8601 UTC string. */
12
+ lastAskedUtc: string;
13
+ /** The success moment that asked. */
14
+ moment: string;
15
+ }
16
+ export type ReviewPromptResult = 'shown' | 'skipped-cooldown' | 'skipped-unavailable';
17
+ /**
18
+ * Sets the cool-down between prompts. Each call replaces the previous options, so
19
+ * `configureReviewPrompt({})` restores the default. Throws on a negative or
20
+ * non-finite number of days, which is a programming error.
21
+ */
22
+ export declare const configureReviewPrompt: (options?: ReviewPromptOptions) => void;
23
+ /**
24
+ * Shows the platform's rating prompt after a success moment, at most once per
25
+ * cool-down (90 days by default, see `configureReviewPrompt`).
26
+ *
27
+ * Call it only after a success moment and once for that moment; never after an
28
+ * error and never behind a "Do you like the app?" question (App Store guideline
29
+ * 5.6.1, Google Play's review policy).
30
+ *
31
+ * Resolves, never rejects:
32
+ * - `shown`: the prompt was requested and the time and `moment` were stored. The
33
+ * platform still decides whether it appears, and caps how often it does.
34
+ * - `skipped-cooldown`: the last prompt is inside the cool-down, or another call
35
+ * is still running.
36
+ * - `skipped-unavailable`: the device or build has no in-app review (no store,
37
+ * a simulator, the native module missing), or the request failed. Logged.
38
+ *
39
+ * @param moment a short name for the success moment, such as `survey-submitted`.
40
+ */
41
+ export declare const requestReviewAfterSuccess: (moment: string) => Promise<ReviewPromptResult>;
42
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Minimal device-info shape we depend on.
3
+ *
4
+ * This is a LAST RESORT, not the primary path. Every Webority app carries its
5
+ * store id and build number in `appsettings.Compiled.json`, so
6
+ * `setConfig({ appId, appBuild })` covers both without a native module, and
7
+ * expo-application covers an app that wires neither. `getBuildNumber` is
8
+ * optional so an injector written before the server-driven check still fits;
9
+ * without it the build number comes from expo-application.
10
+ */
11
+ export interface DeviceInfoLike {
12
+ getBundleId: () => string;
13
+ getVersion: () => string;
14
+ getBuildNumber?: () => string;
15
+ }
16
+ /**
17
+ * expo-application, read at call time: its values are module CONSTANTS, and
18
+ * capturing them here would snapshot them before the native module has
19
+ * initialised. Config still wins over this.
20
+ */
21
+ export declare const expoApplicationBackend: Required<DeviceInfoLike>;
22
+ export declare const setDeviceInfo: (impl: DeviceInfoLike | null) => void;
23
+ export declare const getDeviceInfo: () => DeviceInfoLike | null;
24
+ /**
25
+ * The app's internal build number (`VERSION_INTERNAL`: Android versionCode, iOS
26
+ * CFBundleVersion) from `setConfig({ appBuild })`, else the device info, else
27
+ * expo-application; `null` when none of them holds an integer. Never throws.
28
+ */
29
+ export declare const readAppBuild: () => number | null;
30
+ //# sourceMappingURL=appBuild.d.ts.map
@@ -1,19 +1,5 @@
1
1
  import { type Environment } from '../config/index.js';
2
- /**
3
- * Minimal device-info shape we depend on.
4
- *
5
- * This is a LAST RESORT, not the primary path. Every Webority app carries its
6
- * store id and build number in `appsettings.Compiled.json`, so
7
- * `setConfig({ appId, appBuild })` covers both without a native module, and
8
- * expo-application covers an app that wires neither. `getBuildNumber` is
9
- * optional so an injector written before the server-driven check still fits;
10
- * without it the build number comes from expo-application.
11
- */
12
- interface DeviceInfoLike {
13
- getBundleId: () => string;
14
- getVersion: () => string;
15
- getBuildNumber?: () => string;
16
- }
2
+ import { type DeviceInfoLike } from './appBuild.js';
17
3
  /**
18
4
  * Supply the app identity from a native module instead of config.
19
5
  * Prefer `setConfig({ appId, appBuild })` — it needs no native dependency.
@@ -86,6 +72,7 @@ export declare const VersionCheck: VersionCheckType;
86
72
  */
87
73
  export declare const useVersionCheck: (isAppReady: boolean, environment?: Environment) => UpdateInfo;
88
74
  export default VersionCheck;
75
+ export { readAppBuild } from './appBuild.js';
89
76
  export { isBuildBelowMinimum, parseBuildNumber } from './buildNumber.js';
90
77
  export type { DeviceInfoLike };
91
78
  //# sourceMappingURL=index.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webority/mobile-core",
3
- "version": "0.5.0",
3
+ "version": "0.6.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",
@@ -162,6 +162,16 @@
162
162
  "default": "./lib/commonjs/share/index.js"
163
163
  }
164
164
  },
165
+ "./review": {
166
+ "import": {
167
+ "types": "./lib/typescript/module/review/index.d.ts",
168
+ "default": "./lib/module/review/index.js"
169
+ },
170
+ "require": {
171
+ "types": "./lib/typescript/commonjs/review/index.d.ts",
172
+ "default": "./lib/commonjs/review/index.js"
173
+ }
174
+ },
165
175
  "./secureScreen": {
166
176
  "import": {
167
177
  "types": "./lib/typescript/module/secureScreen/index.d.ts",
@@ -299,6 +309,7 @@
299
309
  "expo-secure-store": ">=57.0.0",
300
310
  "expo-sharing": ">=57.0.0",
301
311
  "expo-sqlite": ">=57.0.0",
312
+ "expo-store-review": ">=57.0.0",
302
313
  "expo-task-manager": ">=57.0.0",
303
314
  "expo-updates": ">=57.0.0",
304
315
  "react": "^19.1.0",
@@ -342,6 +353,9 @@
342
353
  "expo-sharing": {
343
354
  "optional": true
344
355
  },
356
+ "expo-store-review": {
357
+ "optional": true
358
+ },
345
359
  "expo-task-manager": {
346
360
  "optional": true
347
361
  },