@webority/mobile-core 0.111.0 → 0.113.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 +20 -1
- package/lib/commonjs/api/clientHeaders.js +1 -1
- package/lib/commonjs/appLock/index.js +1 -1
- package/lib/commonjs/attachments/index.js +1 -0
- package/lib/commonjs/initSDK.js +1 -1
- package/lib/commonjs/installation/index.js +1 -0
- package/lib/commonjs/redux/index.js +1 -1
- package/lib/commonjs/redux/sessionMiddleware.js +1 -0
- package/lib/module/api/clientHeaders.js +1 -1
- package/lib/module/appLock/index.js +1 -1
- package/lib/module/attachments/index.js +1 -0
- package/lib/module/initSDK.js +1 -1
- package/lib/module/installation/index.js +1 -0
- package/lib/module/redux/index.js +1 -1
- package/lib/module/redux/sessionMiddleware.js +1 -0
- package/lib/typescript/commonjs/api/clientHeaders.d.ts +3 -1
- package/lib/typescript/commonjs/appLock/index.d.ts +4 -0
- package/lib/typescript/commonjs/attachments/index.d.ts +37 -0
- package/lib/typescript/commonjs/index.d.ts +2 -0
- package/lib/typescript/commonjs/initSDK.d.ts +10 -4
- package/lib/typescript/commonjs/installation/index.d.ts +23 -0
- package/lib/typescript/commonjs/logger/crashlytics.d.ts +1 -1
- package/lib/typescript/commonjs/redux/index.d.ts +2 -0
- package/lib/typescript/commonjs/redux/sessionMiddleware.d.ts +14 -0
- package/lib/typescript/module/api/clientHeaders.d.ts +3 -1
- package/lib/typescript/module/appLock/index.d.ts +4 -0
- package/lib/typescript/module/attachments/index.d.ts +37 -0
- package/lib/typescript/module/index.d.ts +2 -0
- package/lib/typescript/module/initSDK.d.ts +10 -4
- package/lib/typescript/module/installation/index.d.ts +23 -0
- package/lib/typescript/module/logger/crashlytics.d.ts +1 -1
- package/lib/typescript/module/redux/index.d.ts +2 -0
- package/lib/typescript/module/redux/sessionMiddleware.d.ts +14 -0
- package/package.json +26 -1
package/README.md
CHANGED
|
@@ -29,6 +29,8 @@ 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 { fetchAttachment, fetchPreviewAttachment, previewKind } from '@webority/mobile-core/attachments';
|
|
33
|
+
import { getOrCreateInstallationId, clearTokensOnFirstLaunch } from '@webority/mobile-core/installation';
|
|
32
34
|
import { requestReviewAfterSuccess } from '@webority/mobile-core/review';
|
|
33
35
|
import { initPush, enablePush, usePush } from '@webority/mobile-core/push';
|
|
34
36
|
import { definePushActionTask } from '@webority/mobile-core/push/task';
|
|
@@ -206,6 +208,9 @@ and answers an older build with 426 `client_update_required`. The number comes f
|
|
|
206
208
|
`nativeBuildVersion`. Without a whole number the header is left off, and the server never refuses a
|
|
207
209
|
request without it. `Client-Build` (`<appVersion>+<commit>`) is unchanged.
|
|
208
210
|
|
|
211
|
+
Both HTTP clients also send `Client-App: <appId>` when the config has an app id (`initWebority({ appId })`).
|
|
212
|
+
It is the app's store identifier, so the API can tell apps of the same client kind apart.
|
|
213
|
+
|
|
209
214
|
The version floor comes only from the `Min-App-Version` response header. When a reply carries it,
|
|
210
215
|
`useVersionCheck` turns `isUpdateRequired` on for a build below it. A 426 `client_update_required`
|
|
211
216
|
does not move the floor, so a 426 without that header leaves the floor where it is. A 426 is never
|
|
@@ -229,7 +234,21 @@ await userClient.get('/status', { skipErrorLog: true });
|
|
|
229
234
|
JWT-shaped strings, and the values of keys named `token`, `accessToken`, `refreshToken`,
|
|
230
235
|
`password` and `authorization` (any case) in objects and JSON strings.
|
|
231
236
|
|
|
232
|
-
`configureLogger({ redactKeys })` adds app keys to the built-in masked list and never removes one. `Logger.getBreadcrumbs()` returns the last 50 breadcrumbs, oldest first, already redacted; `configureLogger({ breadcrumbLimit })` changes the cap. `initWebority` takes a required `remoteLogger`: the sink, or `null` for a build without crash reporting.
|
|
237
|
+
`configureLogger({ redactKeys })` adds app keys to the built-in masked list and never removes one. `Logger.getBreadcrumbs()` returns the last 50 breadcrumbs, oldest first, already redacted; `configureLogger({ breadcrumbLimit })` changes the cap. `initWebority` takes a required `remoteLogger`: the sink, or `null` for a build without crash reporting. It also takes a required `appId`, the compiled settings' app id, and refuses to start when it differs from the installed app's id: a bundle prebuilt for another environment stops at start-up with the prebuild step to run.
|
|
238
|
+
|
|
239
|
+
## App lock
|
|
240
|
+
|
|
241
|
+
`@webority/mobile-core/appLock` locks the app when a signed-in person returns after the lock delay (five minutes by default). `useAppLock` returns what a settings screen and the lock screen need:
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
import { useAppLock } from '@webority/mobile-core/appLock';
|
|
245
|
+
|
|
246
|
+
const { locked, enabled, biometricAvailable, availabilityKnown, setEnabled, unlock } = useAppLock({ active: isSignedIn });
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
- `availabilityKnown` is false until the first biometric check settles. Until then `biometricAvailable` is not an answer yet, so a screen should wait before it shows the lock as unavailable or disables its switch.
|
|
250
|
+
- `setEnabled(next)` and `unlock(reason)` return an `AppLockResult`: `{ ok: true }`, or `{ ok: false, reason }` with the reason `cancelled`, `lockout`, `not-enrolled`, `not-available` or `failed`.
|
|
251
|
+
- `policy` overrides the delay and the two storage keys; the defaults are used for any key left out.
|
|
233
252
|
|
|
234
253
|
## Metro bridge
|
|
235
254
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.stampAccountId=exports.readAccountId=exports.getClientBuild=exports.applyClientHeaders=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="Client-Build",APP_BUILD_HEADER=exports.APP_BUILD_HEADER="App-Build",CLIENT_KIND_HEADER=exports.CLIENT_KIND_HEADER="Client-Kind",ACCOUNT_ID_HEADER=exports.ACCOUNT_ID_HEADER="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 readAccountId=async e=>e&&await e()||null;exports.readAccountId=readAccountId;const stampAccountId=async(e,t)=>{if(void 0!==e._accountId)return void(e._accountId&&e.headers.set(ACCOUNT_ID_HEADER,e._accountId));const s=e.headers.get(ACCOUNT_ID_HEADER);if("string"==typeof s&&""!==s)return void(e._accountId=s);const n=await readAccountId(t);e._accountId=n,n&&e.headers.set(ACCOUNT_ID_HEADER,n)};exports.stampAccountId=stampAccountId;
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.stampAccountId=exports.readAccountId=exports.getClientBuild=exports.applyClientHeaders=exports.CLIENT_KIND_HEADER=exports.CLIENT_KIND=exports.CLIENT_BUILD_HEADER=exports.CLIENT_APP_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="Client-Build",APP_BUILD_HEADER=exports.APP_BUILD_HEADER="App-Build",CLIENT_KIND_HEADER=exports.CLIENT_KIND_HEADER="Client-Kind",CLIENT_APP_HEADER=exports.CLIENT_APP_HEADER="Client-App",ACCOUNT_ID_HEADER=exports.ACCOUNT_ID_HEADER="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_APP_HEADER,(0,_index.getConfigValue)("appId")),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 readAccountId=async e=>e&&await e()||null;exports.readAccountId=readAccountId;const stampAccountId=async(e,t)=>{if(void 0!==e._accountId)return void(e._accountId&&e.headers.set(ACCOUNT_ID_HEADER,e._accountId));const s=e.headers.get(ACCOUNT_ID_HEADER);if("string"==typeof s&&""!==s)return void(e._accountId=s);const n=await readAccountId(t);e._accountId=n,n&&e.headers.set(ACCOUNT_ID_HEADER,n)};exports.stampAccountId=stampAccountId;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.authenticateBiometric=authenticateBiometric,exports.isBiometricAvailable=isBiometricAvailable,exports.useAppLock=useAppLock;var cache,LocalAuthentication=_interopRequireWildcard(require("expo-local-authentication")),_react=require("react"),_reactNative=require("react-native"),_index=require("../logger/index.js"),_index2=require("../storage/index.js");function _interopRequireWildcard(e,t){if(!t&&e&&e.__esModule)return e;var a,r,c=Object.defineProperty,n={__proto__:null,default:e};if(Object(e)!==e)return n;if(cache||"function"!=typeof WeakMap||(cache=new WeakMap),cache){if(cache.has(e))return cache.get(e);cache.set(e,n)}for(r in e)"default"!==r&&{}.hasOwnProperty.call(e,r)&&((a=c&&Object.getOwnPropertyDescriptor(e,r))&&(a.get||a.set)?c(n,r,a):n[r]=e[r]);return n}const DEFAULT_POLICY={lockAfterMs:3e5,enabledKey:"appLockEnabled",backgroundedAtKey:"appLockBackgroundedAt"};let pendingPrompt=null;async function isBiometricAvailable(){try{const[e,t]=await Promise.all([LocalAuthentication.hasHardwareAsync(),LocalAuthentication.isEnrolledAsync()]);return e&&t}catch(e){return _index.Logger.error("[AppLock] biometric availability check failed:",e),!1}}const failureFor=e=>{switch(e){case"user_cancel":case"app_cancel":case"system_cancel":case"user_fallback":return"cancelled";case"lockout":return"lockout";case"not_enrolled":case"passcode_not_set":return"not-enrolled";case"not_available":return"not-available";default:return"failed"}},prompt=async e=>{try{if(await LocalAuthentication.getEnrolledLevelAsync()===LocalAuthentication.SecurityLevel.NONE)return{ok:!1,reason:"not-enrolled"};const t=await LocalAuthentication.authenticateAsync({promptMessage:e,cancelLabel:"Cancel",fallbackLabel:"Use passcode",disableDeviceFallback:!1});return t.success?{ok:!0}:(_index.Logger.warn("[AppLock] check not passed:",t.error),{ok:!1,reason:failureFor(t.error)})}catch(e){return _index.Logger.error("[AppLock] check failed:",e),{ok:!1,reason:"failed"}}};function authenticateBiometric(e){return pendingPrompt||(pendingPrompt=prompt(e).finally(()=>{pendingPrompt=null})),pendingPrompt}function useAppLock({active:e,policy:t}){const a=t?.lockAfterMs??DEFAULT_POLICY.lockAfterMs,r=t?.enabledKey??DEFAULT_POLICY.enabledKey,c=t?.backgroundedAtKey??DEFAULT_POLICY.backgroundedAtKey,[n,o]=(0,_react.useState)(!1),[i,l]=(0,_react.useState)(!1),[s,u]=(0,_react.useState)(!1),d=(0,_react.useCallback)(async()=>{const e=await _index2.Storage.getItem(r);return"true"===e||"false"===e?"true"===e:isBiometricAvailable()},[r]),
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.authenticateBiometric=authenticateBiometric,exports.isBiometricAvailable=isBiometricAvailable,exports.useAppLock=useAppLock;var cache,LocalAuthentication=_interopRequireWildcard(require("expo-local-authentication")),_react=require("react"),_reactNative=require("react-native"),_index=require("../logger/index.js"),_index2=require("../storage/index.js");function _interopRequireWildcard(e,t){if(!t&&e&&e.__esModule)return e;var a,r,c=Object.defineProperty,n={__proto__:null,default:e};if(Object(e)!==e)return n;if(cache||"function"!=typeof WeakMap||(cache=new WeakMap),cache){if(cache.has(e))return cache.get(e);cache.set(e,n)}for(r in e)"default"!==r&&{}.hasOwnProperty.call(e,r)&&((a=c&&Object.getOwnPropertyDescriptor(e,r))&&(a.get||a.set)?c(n,r,a):n[r]=e[r]);return n}const DEFAULT_POLICY={lockAfterMs:3e5,enabledKey:"appLockEnabled",backgroundedAtKey:"appLockBackgroundedAt"};let pendingPrompt=null;async function isBiometricAvailable(){try{const[e,t]=await Promise.all([LocalAuthentication.hasHardwareAsync(),LocalAuthentication.isEnrolledAsync()]);return e&&t}catch(e){return _index.Logger.error("[AppLock] biometric availability check failed:",e),!1}}const failureFor=e=>{switch(e){case"user_cancel":case"app_cancel":case"system_cancel":case"user_fallback":return"cancelled";case"lockout":return"lockout";case"not_enrolled":case"passcode_not_set":return"not-enrolled";case"not_available":return"not-available";default:return"failed"}},prompt=async e=>{try{if(await LocalAuthentication.getEnrolledLevelAsync()===LocalAuthentication.SecurityLevel.NONE)return{ok:!1,reason:"not-enrolled"};const t=await LocalAuthentication.authenticateAsync({promptMessage:e,cancelLabel:"Cancel",fallbackLabel:"Use passcode",disableDeviceFallback:!1});return t.success?{ok:!0}:(_index.Logger.warn("[AppLock] check not passed:",t.error),{ok:!1,reason:failureFor(t.error)})}catch(e){return _index.Logger.error("[AppLock] check failed:",e),{ok:!1,reason:"failed"}}};function authenticateBiometric(e){return pendingPrompt||(pendingPrompt=prompt(e).finally(()=>{pendingPrompt=null})),pendingPrompt}function useAppLock({active:e,policy:t}){const a=t?.lockAfterMs??DEFAULT_POLICY.lockAfterMs,r=t?.enabledKey??DEFAULT_POLICY.enabledKey,c=t?.backgroundedAtKey??DEFAULT_POLICY.backgroundedAtKey,[n,o]=(0,_react.useState)(!1),[i,l]=(0,_react.useState)(!1),[s,u]=(0,_react.useState)(!1),[d,p]=(0,_react.useState)(!1),_=(0,_react.useCallback)(async()=>{const e=await _index2.Storage.getItem(r);return"true"===e||"false"===e?"true"===e:isBiometricAvailable()},[r]),k=(0,_react.useCallback)(async()=>{const t=await _index2.Storage.getItem(c);null!==t&&(await _index2.Storage.removeItem(c),e&&Date.now()-Number(t)>=a&&await _()&&o(!0))},[e,c,a,_]);(0,_react.useEffect)(()=>{e||o(!1),k()},[e,k]),(0,_react.useEffect)(()=>{const t=_reactNative.AppState.addEventListener("change",t=>{"background"===t&&e?_index2.Storage.setItem(c,String(Date.now())):"active"===t&&k()});return()=>t.remove()},[e,c,k]),(0,_react.useEffect)(()=>{let e=!1;return isBiometricAvailable().then(t=>{e||(u(t),p(!0))}),_().then(t=>{e||l(t)}),()=>{e=!0}},[_]);return{locked:n,enabled:i,biometricAvailable:s,availabilityKnown:d,setEnabled:(0,_react.useCallback)(async e=>await _index2.Storage.setItem(r,e?"true":"false")?(l(e),{ok:!0}):{ok:!1,reason:"failed"},[r]),unlock:(0,_react.useCallback)(async e=>{const t=await authenticateBiometric(e);return t.ok&&o(!1),t},[])}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.previewKind=exports.mimeTypeFromFileName=exports.fetchPreviewAttachment=exports.fetchAttachment=void 0;var _expoFileSystem=require("expo-file-system"),_userClient=_interopRequireDefault(require("../api/userClient.js")),_index=require("../download/index.js"),_index2=require("../logger/index.js");function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}const EXTENSION_TYPES={pdf:"application/pdf",doc:"application/msword",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",xls:"application/vnd.ms-excel",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",ppt:"application/vnd.ms-powerpoint",pptx:"application/vnd.openxmlformats-officedocument.presentationml.presentation",jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",webp:"image/webp",gif:"image/gif"},mimeTypeFromFileName=e=>{const t=(e??"").split(".").pop()?.toLowerCase()??"";return Object.hasOwn(EXTENSION_TYPES,t)?EXTENSION_TYPES[t]:void 0};exports.mimeTypeFromFileName=mimeTypeFromFileName;const fetchAttachment=({url:e,fileName:t,mimeType:i})=>(0,_index.downloadFile)({client:_userClient.default,url:e,fileName:t,mimeType:i??mimeTypeFromFileName(t),destination:"cache",overwrite:!0});exports.fetchAttachment=fetchAttachment;const PREVIEW_LIMIT=20,previewCache=new Map,previewFolderName=e=>e.replace(/[^A-Za-z0-9_-]/g,"_"),forgetPreview=e=>{try{new _expoFileSystem.Directory(e.folder).delete()}catch(e){_index2.Logger.warn("Removing an old preview failed:",e)}},fetchPreviewAttachment=async({cacheId:e,version:t,url:i,fileName:r,mimeType:p})=>{if(!t)return fetchAttachment({url:i,fileName:r,mimeType:p});const n=`${e}@${t}`,o=previewCache.get(n);if(o&&new _expoFileSystem.File(o.path).exists)return previewCache.delete(n),previewCache.set(n,o),o;previewCache.delete(n);const a=await fetchAttachment({url:i,fileName:r,mimeType:p}),m=new _expoFileSystem.File(a.path),c=new _expoFileSystem.Directory(_expoFileSystem.Paths.cache,"previews",previewFolderName(n));c.exists&&c.delete(),c.create({intermediates:!0,idempotent:!0}),await m.move(new _expoFileSystem.File(c,m.name));const l={path:new _expoFileSystem.File(c,m.name).uri,mimeType:a.mimeType,folder:c.uri};if(previewCache.set(n,l),previewCache.size>20){const e=previewCache.entries().next().value;e&&(previewCache.delete(e[0]),forgetPreview(e[1]))}return l};exports.fetchPreviewAttachment=fetchPreviewAttachment;const previewKind=e=>{const{contentType:t,name:i}=e,r=(i??"").split(".").pop()?.toLowerCase()??"",p=(t??r).toLowerCase();return p.includes("pdf")?"pdf":["png","jpeg","jpg","webp","gif"].some(e=>p.includes(e))?"image":null};exports.previewKind=previewKind;
|
package/lib/commonjs/initSDK.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.initWebority=void 0;var _authStore=require("./auth/authStore.js"),_index=require("./config/index.js"),_index2=require("./logger/index.js"),_networkStatus=require("./network/networkStatus.js"),_index3=require("./storage/index.js");const initWebority=e=>{if(!e.baseUrl)throw new Error("baseUrl is required for Webority packages
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.initWebority=void 0;var cache,Application=_interopRequireWildcard(require("expo-application")),_authStore=require("./auth/authStore.js"),_index=require("./config/index.js"),_index2=require("./logger/index.js"),_networkStatus=require("./network/networkStatus.js"),_index3=require("./storage/index.js");function _interopRequireWildcard(e,r){if(!r&&e&&e.__esModule)return e;var t,i,o=Object.defineProperty,n={__proto__:null,default:e};if(Object(e)!==e)return n;if(cache||"function"!=typeof WeakMap||(cache=new WeakMap),cache){if(cache.has(e))return cache.get(e);cache.set(e,n)}for(i in e)"default"!==i&&{}.hasOwnProperty.call(e,i)&&((t=o&&Object.getOwnPropertyDescriptor(e,i))&&(t.get||t.set)?o(n,i,t):n[i]=e[i]);return n}const initWebority=e=>{if(!e.baseUrl)throw new Error("baseUrl is required for Webority packages initialisation");if(void 0===e.remoteLogger)throw new Error("remoteLogger is required for Webority packages initialisation: pass a RemoteLogger, or null for a build without crash reporting");if(!e.appId)throw new Error("appId is required for Webority packages initialisation");const r=Application.applicationId;if(null!==r&&e.appId!==r)throw new Error(`This build's settings are for ${e.appId} but the installed app is ${r}. Run mobile-tools prebuild for the right environment, then rebuild the bundle.`);(0,_index2.setRemoteLogger)(e.remoteLogger),(0,_index.setConfig)({baseUrl:e.baseUrl,loggingEnabled:e.loggingEnabled??!0,logLevel:e.logLevel||_index.DEFAULT_LOG_LEVEL,environment:e.environment||"Development",country:(e.country||"in").toLowerCase(),appId:e.appId,appVersion:e.appVersion??null,commit:e.commit??null,appBuild:e.appBuild??null,appStoreId:e.appStoreId??null,logCurl:e.logCurl??!1,timeoutMs:e.timeoutMs??_index.DEFAULT_TIMEOUT_MS,retries:e.retries??_index.DEFAULT_RETRIES});const t=e.backends;t?.storage&&(0,_index3.setStorageImplementation)(t.storage),t?.keychain&&(0,_authStore.setKeychainImplementation)(t.keychain),t?.network&&(0,_networkStatus.setNetworkStatusImplementation)(t.network),_index2.Logger.info("[@webority/mobile-core] initialized")};exports.initWebority=initWebority;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.getOrCreateInstallationId=exports.clearTokensOnFirstLaunch=exports.INSTALLATION_STORAGE_KEYS=void 0;var _expoCrypto=require("expo-crypto"),_reactNative=require("react-native"),_authStore=require("../auth/authStore.js"),_index=require("../logger/index.js"),_index2=require("../storage/index.js");const INSTALLATION_STORAGE_KEYS=exports.INSTALLATION_STORAGE_KEYS={HAS_LAUNCHED_BEFORE:"hasLaunchedBefore",INSTALLATION_ID:"installationId"},getOrCreateInstallationId=async()=>{const e=await _index2.Storage.getItem(INSTALLATION_STORAGE_KEYS.INSTALLATION_ID);if(e)return e;const t=(0,_expoCrypto.randomUUID)();if(!await _index2.Storage.setItem(INSTALLATION_STORAGE_KEYS.INSTALLATION_ID,t))throw new Error("Could not persist the installation id");return t};exports.getOrCreateInstallationId=getOrCreateInstallationId;const clearTokensOnFirstLaunch=async()=>{if("ios"!==_reactNative.Platform.OS)return;null===await _index2.Storage.getItem(INSTALLATION_STORAGE_KEYS.HAS_LAUNCHED_BEFORE)&&(_index.Logger.info("First launch after install: clearing stored tokens"),await(0,_authStore.removeToken)(_authStore.ACCESS_TOKEN_KEY),await(0,_authStore.removeToken)(_authStore.REFRESH_TOKEN_KEY),await _index2.Storage.setItem(INSTALLATION_STORAGE_KEYS.HAS_LAUNCHED_BEFORE,"true"))};exports.clearTokensOnFirstLaunch=clearTokensOnFirstLaunch;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),Object.defineProperty(exports,"SERIALIZABLE_IGNORED_ACTION_PATHS",{enumerable:!0,get:function(){return _serializableCheck.SERIALIZABLE_IGNORED_ACTION_PATHS}}),Object.defineProperty(exports,"createRootReducer",{enumerable:!0,get:function(){return _rootReducer.createRootReducer}}),Object.defineProperty(exports,"errorToastMiddleware",{enumerable:!0,get:function(){return _errorToastMiddleware.errorToastMiddleware}}),Object.defineProperty(exports,"isSilentRejection",{enumerable:!0,get:function(){return _errorToastMiddleware.isSilentRejection}}),Object.defineProperty(exports,"resetState",{enumerable:!0,get:function(){return _rootReducer.resetState}});var _errorToastMiddleware=require("./errorToastMiddleware.js"),_rootReducer=require("./rootReducer.js"),_serializableCheck=require("./serializableCheck.js");
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),Object.defineProperty(exports,"SERIALIZABLE_IGNORED_ACTION_PATHS",{enumerable:!0,get:function(){return _serializableCheck.SERIALIZABLE_IGNORED_ACTION_PATHS}}),Object.defineProperty(exports,"createRootReducer",{enumerable:!0,get:function(){return _rootReducer.createRootReducer}}),Object.defineProperty(exports,"createSessionMiddleware",{enumerable:!0,get:function(){return _sessionMiddleware.createSessionMiddleware}}),Object.defineProperty(exports,"errorToastMiddleware",{enumerable:!0,get:function(){return _errorToastMiddleware.errorToastMiddleware}}),Object.defineProperty(exports,"isSilentRejection",{enumerable:!0,get:function(){return _errorToastMiddleware.isSilentRejection}}),Object.defineProperty(exports,"resetState",{enumerable:!0,get:function(){return _rootReducer.resetState}});var _errorToastMiddleware=require("./errorToastMiddleware.js"),_rootReducer=require("./rootReducer.js"),_serializableCheck=require("./serializableCheck.js"),_sessionMiddleware=require("./sessionMiddleware.js");
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.createSessionMiddleware=void 0;const isAction=e=>"object"==typeof e&&null!==e&&"type"in e,thunkMeta=e=>isAction(e)&&"object"==typeof e.meta&&null!==e.meta?e.meta:{},createSessionMiddleware=e=>{const t=new Set,n=new Set;return()=>r=>s=>{const{requestId:i,requestStatus:o}=thunkMeta(s);if(!i||!o)return r(s);if("pending"===o){if(isAction(s)&&e.isSignOut(s)){for(const e of t)n.add(e);t.clear()}else t.add(i);return r(s)}return t.delete(i),n.delete(i)?s:r(s)}};exports.createSessionMiddleware=createSessionMiddleware;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{getConfigValue}from"../config/index.js";import{readAppBuild}from"../versionCheck/appBuild.js";export const CLIENT_BUILD_HEADER="Client-Build";export const APP_BUILD_HEADER="App-Build";export const CLIENT_KIND_HEADER="Client-Kind";export const ACCOUNT_ID_HEADER="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,"Client-Kind","MobileApp"),setIfAbsent(t.headers,"Client-Build",getClientBuild());const e=readAppBuild();return setIfAbsent(t.headers,"App-Build",null===e?null:String(e)),t})};export const readAccountId=async t=>t&&await t()||null;export const stampAccountId=async(t,e)=>{if(void 0!==t._accountId)return void(t._accountId&&t.headers.set("Account-Id",t._accountId));const n=t.headers.get("Account-Id");if("string"==typeof n&&""!==n)return void(t._accountId=n);const o=await readAccountId(e);t._accountId=o,o&&t.headers.set("Account-Id",o)};
|
|
1
|
+
"use strict";import{getConfigValue}from"../config/index.js";import{readAppBuild}from"../versionCheck/appBuild.js";export const CLIENT_BUILD_HEADER="Client-Build";export const APP_BUILD_HEADER="App-Build";export const CLIENT_KIND_HEADER="Client-Kind";export const CLIENT_APP_HEADER="Client-App";export const ACCOUNT_ID_HEADER="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,"Client-Kind","MobileApp"),setIfAbsent(t.headers,"Client-App",getConfigValue("appId")),setIfAbsent(t.headers,"Client-Build",getClientBuild());const e=readAppBuild();return setIfAbsent(t.headers,"App-Build",null===e?null:String(e)),t})};export const readAccountId=async t=>t&&await t()||null;export const stampAccountId=async(t,e)=>{if(void 0!==t._accountId)return void(t._accountId&&t.headers.set("Account-Id",t._accountId));const n=t.headers.get("Account-Id");if("string"==typeof n&&""!==n)return void(t._accountId=n);const o=await readAccountId(e);t._accountId=o,o&&t.headers.set("Account-Id",o)};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import*as LocalAuthentication from"expo-local-authentication";import{useCallback,useEffect,useState}from"react";import{AppState}from"react-native";import{Logger}from"../logger/index.js";import{Storage}from"../storage/index.js";const DEFAULT_POLICY={lockAfterMs:3e5,enabledKey:"appLockEnabled",backgroundedAtKey:"appLockBackgroundedAt"};let pendingPrompt=null;export async function isBiometricAvailable(){try{const[e,t]=await Promise.all([LocalAuthentication.hasHardwareAsync(),LocalAuthentication.isEnrolledAsync()]);return e&&t}catch(e){return Logger.error("[AppLock] biometric availability check failed:",e),!1}}const failureFor=e=>{switch(e){case"user_cancel":case"app_cancel":case"system_cancel":case"user_fallback":return"cancelled";case"lockout":return"lockout";case"not_enrolled":case"passcode_not_set":return"not-enrolled";case"not_available":return"not-available";default:return"failed"}},prompt=async e=>{try{if(await LocalAuthentication.getEnrolledLevelAsync()===LocalAuthentication.SecurityLevel.NONE)return{ok:!1,reason:"not-enrolled"};const t=await LocalAuthentication.authenticateAsync({promptMessage:e,cancelLabel:"Cancel",fallbackLabel:"Use passcode",disableDeviceFallback:!1});return t.success?{ok:!0}:(Logger.warn("[AppLock] check not passed:",t.error),{ok:!1,reason:failureFor(t.error)})}catch(e){return Logger.error("[AppLock] check failed:",e),{ok:!1,reason:"failed"}}};export function authenticateBiometric(e){return pendingPrompt||(pendingPrompt=prompt(e).finally(()=>{pendingPrompt=null})),pendingPrompt}export function useAppLock({active:e,policy:t}){const a=t?.lockAfterMs??DEFAULT_POLICY.lockAfterMs,
|
|
1
|
+
"use strict";import*as LocalAuthentication from"expo-local-authentication";import{useCallback,useEffect,useState}from"react";import{AppState}from"react-native";import{Logger}from"../logger/index.js";import{Storage}from"../storage/index.js";const DEFAULT_POLICY={lockAfterMs:3e5,enabledKey:"appLockEnabled",backgroundedAtKey:"appLockBackgroundedAt"};let pendingPrompt=null;export async function isBiometricAvailable(){try{const[e,t]=await Promise.all([LocalAuthentication.hasHardwareAsync(),LocalAuthentication.isEnrolledAsync()]);return e&&t}catch(e){return Logger.error("[AppLock] biometric availability check failed:",e),!1}}const failureFor=e=>{switch(e){case"user_cancel":case"app_cancel":case"system_cancel":case"user_fallback":return"cancelled";case"lockout":return"lockout";case"not_enrolled":case"passcode_not_set":return"not-enrolled";case"not_available":return"not-available";default:return"failed"}},prompt=async e=>{try{if(await LocalAuthentication.getEnrolledLevelAsync()===LocalAuthentication.SecurityLevel.NONE)return{ok:!1,reason:"not-enrolled"};const t=await LocalAuthentication.authenticateAsync({promptMessage:e,cancelLabel:"Cancel",fallbackLabel:"Use passcode",disableDeviceFallback:!1});return t.success?{ok:!0}:(Logger.warn("[AppLock] check not passed:",t.error),{ok:!1,reason:failureFor(t.error)})}catch(e){return Logger.error("[AppLock] check failed:",e),{ok:!1,reason:"failed"}}};export function authenticateBiometric(e){return pendingPrompt||(pendingPrompt=prompt(e).finally(()=>{pendingPrompt=null})),pendingPrompt}export function useAppLock({active:e,policy:t}){const a=t?.lockAfterMs??DEFAULT_POLICY.lockAfterMs,o=t?.enabledKey??DEFAULT_POLICY.enabledKey,r=t?.backgroundedAtKey??DEFAULT_POLICY.backgroundedAtKey,[n,c]=useState(!1),[l,i]=useState(!1),[s,u]=useState(!1),[p,d]=useState(!1),k=useCallback(async()=>{const e=await Storage.getItem(o);return"true"===e||"false"===e?"true"===e:isBiometricAvailable()},[o]),m=useCallback(async()=>{const t=await Storage.getItem(r);null!==t&&(await Storage.removeItem(r),e&&Date.now()-Number(t)>=a&&await k()&&c(!0))},[e,r,a,k]);useEffect(()=>{e||c(!1),m()},[e,m]),useEffect(()=>{const t=AppState.addEventListener("change",t=>{"background"===t&&e?Storage.setItem(r,String(Date.now())):"active"===t&&m()});return()=>t.remove()},[e,r,m]),useEffect(()=>{let e=!1;return isBiometricAvailable().then(t=>{e||(u(t),d(!0))}),k().then(t=>{e||i(t)}),()=>{e=!0}},[k]);return{locked:n,enabled:l,biometricAvailable:s,availabilityKnown:p,setEnabled:useCallback(async e=>await Storage.setItem(o,e?"true":"false")?(i(e),{ok:!0}):{ok:!1,reason:"failed"},[o]),unlock:useCallback(async e=>{const t=await authenticateBiometric(e);return t.ok&&c(!1),t},[])}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";import{Directory,File,Paths}from"expo-file-system";import userClient from"../api/userClient.js";import{downloadFile}from"../download/index.js";import{Logger}from"../logger/index.js";const EXTENSION_TYPES={pdf:"application/pdf",doc:"application/msword",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",xls:"application/vnd.ms-excel",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",ppt:"application/vnd.ms-powerpoint",pptx:"application/vnd.openxmlformats-officedocument.presentationml.presentation",jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",webp:"image/webp",gif:"image/gif"};export const mimeTypeFromFileName=e=>{const t=(e??"").split(".").pop()?.toLowerCase()??"";return Object.hasOwn(EXTENSION_TYPES,t)?EXTENSION_TYPES[t]:void 0};export const fetchAttachment=({url:e,fileName:t,mimeType:i})=>downloadFile({client:userClient,url:e,fileName:t,mimeType:i??mimeTypeFromFileName(t),destination:"cache",overwrite:!0});const PREVIEW_LIMIT=20,previewCache=new Map,previewFolderName=e=>e.replace(/[^A-Za-z0-9_-]/g,"_"),forgetPreview=e=>{try{new Directory(e.folder).delete()}catch(e){Logger.warn("Removing an old preview failed:",e)}};export const fetchPreviewAttachment=async({cacheId:e,version:t,url:i,fileName:o,mimeType:p})=>{if(!t)return fetchAttachment({url:i,fileName:o,mimeType:p});const r=`${e}@${t}`,n=previewCache.get(r);if(n&&new File(n.path).exists)return previewCache.delete(r),previewCache.set(r,n),n;previewCache.delete(r);const a=await fetchAttachment({url:i,fileName:o,mimeType:p}),m=new File(a.path),c=new Directory(Paths.cache,"previews",previewFolderName(r));c.exists&&c.delete(),c.create({intermediates:!0,idempotent:!0}),await m.move(new File(c,m.name));const l={path:new File(c,m.name).uri,mimeType:a.mimeType,folder:c.uri};if(previewCache.set(r,l),previewCache.size>20){const e=previewCache.entries().next().value;e&&(previewCache.delete(e[0]),forgetPreview(e[1]))}return l};export const previewKind=e=>{const{contentType:t,name:i}=e,o=(i??"").split(".").pop()?.toLowerCase()??"",p=(t??o).toLowerCase();return p.includes("pdf")?"pdf":["png","jpeg","jpg","webp","gif"].some(e=>p.includes(e))?"image":null};
|
package/lib/module/initSDK.js
CHANGED
|
@@ -1 +1 @@
|
|
|
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
|
|
1
|
+
"use strict";import*as Application from"expo-application";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 initialisation");if(void 0===e.remoteLogger)throw new Error("remoteLogger is required for Webority packages initialisation: pass a RemoteLogger, or null for a build without crash reporting");if(!e.appId)throw new Error("appId is required for Webority packages initialisation");const o=Application.applicationId;if(null!==o&&e.appId!==o)throw new Error(`This build's settings are for ${e.appId} but the installed app is ${o}. Run mobile-tools prebuild for the right environment, then rebuild the bundle.`);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,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 t=e.backends;t?.storage&&setStorageImplementation(t.storage),t?.keychain&&setKeychainImplementation(t.keychain),t?.network&&setNetworkStatusImplementation(t.network),Logger.info("[@webority/mobile-core] initialized")};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";import{randomUUID}from"expo-crypto";import{Platform}from"react-native";import{ACCESS_TOKEN_KEY,REFRESH_TOKEN_KEY,removeToken}from"../auth/authStore.js";import{Logger}from"../logger/index.js";import{Storage}from"../storage/index.js";export const INSTALLATION_STORAGE_KEYS={HAS_LAUNCHED_BEFORE:"hasLaunchedBefore",INSTALLATION_ID:"installationId"};export const getOrCreateInstallationId=async()=>{const t=await Storage.getItem(INSTALLATION_STORAGE_KEYS.INSTALLATION_ID);if(t)return t;const e=randomUUID();if(!await Storage.setItem(INSTALLATION_STORAGE_KEYS.INSTALLATION_ID,e))throw new Error("Could not persist the installation id");return e};export const clearTokensOnFirstLaunch=async()=>{if("ios"!==Platform.OS)return;null===await Storage.getItem(INSTALLATION_STORAGE_KEYS.HAS_LAUNCHED_BEFORE)&&(Logger.info("First launch after install: clearing stored tokens"),await removeToken(ACCESS_TOKEN_KEY),await removeToken(REFRESH_TOKEN_KEY),await Storage.setItem(INSTALLATION_STORAGE_KEYS.HAS_LAUNCHED_BEFORE,"true"))};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";export{errorToastMiddleware,isSilentRejection}from"./errorToastMiddleware.js";export{createRootReducer,resetState}from"./rootReducer.js";export{SERIALIZABLE_IGNORED_ACTION_PATHS}from"./serializableCheck.js";
|
|
1
|
+
"use strict";export{errorToastMiddleware,isSilentRejection}from"./errorToastMiddleware.js";export{createRootReducer,resetState}from"./rootReducer.js";export{SERIALIZABLE_IGNORED_ACTION_PATHS}from"./serializableCheck.js";export{createSessionMiddleware}from"./sessionMiddleware.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";const isAction=e=>"object"==typeof e&&null!==e&&"type"in e,thunkMeta=e=>isAction(e)&&"object"==typeof e.meta&&null!==e.meta?e.meta:{};export const createSessionMiddleware=e=>{const t=new Set,n=new Set;return()=>s=>i=>{const{requestId:o,requestStatus:r}=thunkMeta(i);if(!o||!r)return s(i);if("pending"===r){if(isAction(i)&&e.isSignOut(i)){for(const e of t)n.add(e);t.clear()}else t.add(o);return s(i)}return t.delete(o),n.delete(o)?i:s(i)}};
|
|
@@ -2,6 +2,7 @@ import type { AxiosInstance } from "axios";
|
|
|
2
2
|
export declare const CLIENT_BUILD_HEADER = "Client-Build";
|
|
3
3
|
export declare const APP_BUILD_HEADER = "App-Build";
|
|
4
4
|
export declare const CLIENT_KIND_HEADER = "Client-Kind";
|
|
5
|
+
export declare const CLIENT_APP_HEADER = "Client-App";
|
|
5
6
|
export declare const ACCOUNT_ID_HEADER = "Account-Id";
|
|
6
7
|
/** The server picks session rules and the minimum version per kind; the names match its enum. */
|
|
7
8
|
export type ClientKind = "Browser" | "MobileApp";
|
|
@@ -9,7 +10,8 @@ export declare const CLIENT_KIND: ClientKind;
|
|
|
9
10
|
/** `<appVersion>+<commit>`, `<appVersion>` when no commit was stamped, `null` before init. */
|
|
10
11
|
export declare const getClientBuild: () => string | null;
|
|
11
12
|
/**
|
|
12
|
-
* Stamps Client-Kind, Client-Build and App-Build on every request
|
|
13
|
+
* Stamps Client-Kind, Client-Build and App-Build on every request, and Client-App with the
|
|
14
|
+
* config's app id when one is set, so the server can tell the apps of one kind apart.
|
|
13
15
|
* App-Build is the whole build number the server compares with its minimum;
|
|
14
16
|
* it is left off when the build is unknown, because the server never refuses a
|
|
15
17
|
* request without it and a guessed number could strand a current build. A value the app
|
|
@@ -14,6 +14,8 @@ export interface AppLockState {
|
|
|
14
14
|
locked: boolean;
|
|
15
15
|
enabled: boolean;
|
|
16
16
|
biometricAvailable: boolean;
|
|
17
|
+
/** False until the first biometric availability check settles, so `biometricAvailable` is not yet a real answer. */
|
|
18
|
+
availabilityKnown: boolean;
|
|
17
19
|
setEnabled: (enabled: boolean) => Promise<AppLockResult>;
|
|
18
20
|
unlock: (reason: string) => Promise<AppLockResult>;
|
|
19
21
|
}
|
|
@@ -25,6 +27,8 @@ export declare function authenticateBiometric(reason: string): Promise<AppLockRe
|
|
|
25
27
|
* The time it left the screen is saved, not held in memory, so a phone that closed the app in the background still
|
|
26
28
|
* locks on the next launch. Only "background" counts as away: iOS reports "inactive" while the Face ID prompt is up.
|
|
27
29
|
* The person's choice is stored under `enabledKey`; with none saved it is on when the phone has a fingerprint or face enrolled.
|
|
30
|
+
* `availabilityKnown` stays false until the first availability check settles, so a screen can wait before it reads
|
|
31
|
+
* `biometricAvailable` as "this phone has no biometrics".
|
|
28
32
|
*/
|
|
29
33
|
export declare function useAppLock({ active, policy }: {
|
|
30
34
|
active: boolean;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type DownloadResult } from "../download";
|
|
2
|
+
/**
|
|
3
|
+
* The content type a file name's extension stands for. Files that arrive with a name only need a type to be saved
|
|
4
|
+
* properly and to offer the right apps for them.
|
|
5
|
+
*/
|
|
6
|
+
export declare const mimeTypeFromFileName: (fileName: string | null | undefined) => string | undefined;
|
|
7
|
+
export interface AttachmentRequest {
|
|
8
|
+
url: string;
|
|
9
|
+
fileName: string;
|
|
10
|
+
mimeType?: string | null;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Fetches a file through the signed-in client into the app's cache, where a viewer or a save reads it. Answers with
|
|
14
|
+
* its local `path` and `mimeType`; a file with no content type of its own takes the one its name stands for.
|
|
15
|
+
*/
|
|
16
|
+
export declare const fetchAttachment: ({ url, fileName, mimeType }: AttachmentRequest) => Promise<DownloadResult>;
|
|
17
|
+
export interface PreviewAttachment {
|
|
18
|
+
path: string;
|
|
19
|
+
mimeType?: string;
|
|
20
|
+
folder: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* fetchAttachment for a preview: a document already downloaded this session opens from the phone with no second
|
|
24
|
+
* download. The document's `version` (its updated time) is part of the key, so a replaced document is fetched again;
|
|
25
|
+
* without a version the file is not kept at all.
|
|
26
|
+
*/
|
|
27
|
+
export declare const fetchPreviewAttachment: ({ cacheId, version, url, fileName, mimeType, }: AttachmentRequest & {
|
|
28
|
+
cacheId: string;
|
|
29
|
+
version?: string | null;
|
|
30
|
+
}) => Promise<PreviewAttachment | DownloadResult>;
|
|
31
|
+
export type PreviewKind = "image" | "pdf";
|
|
32
|
+
/** What the app can show itself, from a content type or a file name: "image", "pdf", or null for anything else. */
|
|
33
|
+
export declare const previewKind: (file: {
|
|
34
|
+
contentType?: string | null;
|
|
35
|
+
name?: string | null;
|
|
36
|
+
}) => PreviewKind | null;
|
|
37
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
* @webority/mobile-core/geolocation a geolocation provider
|
|
11
11
|
* @webority/mobile-core/sqlite expo-sqlite
|
|
12
12
|
* @webority/mobile-core/download a filesystem module
|
|
13
|
+
* @webority/mobile-core/installation installation id (expo-crypto)
|
|
14
|
+
* @webority/mobile-core/attachments fetch, preview cache and preview kind (expo-file-system)
|
|
13
15
|
* @webority/mobile-core/share expo-sharing
|
|
14
16
|
* @webority/mobile-core/review expo-store-review
|
|
15
17
|
* @webority/mobile-core/secureScreen expo-screen-capture
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Simple
|
|
2
|
+
* Simple initialisation API for Webority packages
|
|
3
3
|
* One function to configure everything needed
|
|
4
4
|
*/
|
|
5
5
|
import { type KeychainLike } from "./auth/authStore";
|
|
@@ -27,13 +27,17 @@ export interface InitConfig {
|
|
|
27
27
|
*/
|
|
28
28
|
country?: string;
|
|
29
29
|
/**
|
|
30
|
-
* The app's store identifier (Android package name / iOS bundle id),
|
|
31
|
-
*
|
|
30
|
+
* The app's store identifier (Android package name / iOS bundle id), from `APP_ID` in
|
|
31
|
+
* `appsettings.Compiled.json`. Required: it must equal the bundle id the installed app runs,
|
|
32
|
+
* so a bundle compiled for another environment stops at start-up.
|
|
33
|
+
*/
|
|
34
|
+
appId: string;
|
|
35
|
+
/**
|
|
36
|
+
* The app's user-visible version and its internal build number (`VERSION_INTERNAL`).
|
|
32
37
|
* Every Webority app already has them in `appsettings.Compiled.json`, which
|
|
33
38
|
* is why they come from config rather than across the native bridge. The
|
|
34
39
|
* version check compares `appBuild` with the API's `Min-App-Version`.
|
|
35
40
|
*/
|
|
36
|
-
appId?: string;
|
|
37
41
|
appVersion?: string;
|
|
38
42
|
appBuild?: number;
|
|
39
43
|
/** The numeric Apple App Store id; the iOS update button needs it. */
|
|
@@ -73,6 +77,7 @@ export interface InitConfig {
|
|
|
73
77
|
* // Basic setup
|
|
74
78
|
* initWebority({
|
|
75
79
|
* baseUrl: 'https://api.myapp.com',
|
|
80
|
+
* appId: 'com.myapp',
|
|
76
81
|
* remoteLogger: null
|
|
77
82
|
* });
|
|
78
83
|
*
|
|
@@ -80,6 +85,7 @@ export interface InitConfig {
|
|
|
80
85
|
* // Full configuration
|
|
81
86
|
* initWebority({
|
|
82
87
|
* baseUrl: 'https://api.myapp.com',
|
|
88
|
+
* appId: 'com.myapp',
|
|
83
89
|
* remoteLogger: crashlyticsRemoteLogger(crashlytics),
|
|
84
90
|
* loggingEnabled: __DEV__,
|
|
85
91
|
* logLevel: 'warn',
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The stored names the existing apps already use. Keeping them means an
|
|
3
|
+
* installation that already has an id keeps it after the app adopts this module.
|
|
4
|
+
*/
|
|
5
|
+
export declare const INSTALLATION_STORAGE_KEYS: {
|
|
6
|
+
readonly HAS_LAUNCHED_BEFORE: "hasLaunchedBefore";
|
|
7
|
+
readonly INSTALLATION_ID: "installationId";
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Returns this installation's id, creating and persisting one on first use. A
|
|
11
|
+
* stored id is never replaced, whatever its format. Throws when the new id cannot
|
|
12
|
+
* be persisted: every account call is keyed on the id, and a fresh id per call
|
|
13
|
+
* would register a new installation on each sign-in.
|
|
14
|
+
*/
|
|
15
|
+
export declare const getOrCreateInstallationId: () => Promise<string>;
|
|
16
|
+
/**
|
|
17
|
+
* The iOS keychain keeps tokens across an uninstall, so a reinstalled app would
|
|
18
|
+
* start signed in as the previous person. This clears them on the first launch
|
|
19
|
+
* after install. It never calls clearSession: no session-ended listener may fire
|
|
20
|
+
* before the app has started.
|
|
21
|
+
*/
|
|
22
|
+
export declare const clearTokensOnFirstLaunch: () => Promise<void>;
|
|
23
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -18,7 +18,7 @@ export interface CrashlyticsModule<Instance = unknown> {
|
|
|
18
18
|
* import * as crashlytics from '@react-native-firebase/crashlytics';
|
|
19
19
|
* import { crashlyticsRemoteLogger, initWebority } from '@webority/mobile-core';
|
|
20
20
|
*
|
|
21
|
-
* initWebority({ baseUrl, remoteLogger: crashlyticsRemoteLogger(crashlytics) });
|
|
21
|
+
* initWebority({ baseUrl, appId, remoteLogger: crashlyticsRemoteLogger(crashlytics) });
|
|
22
22
|
* ```
|
|
23
23
|
*
|
|
24
24
|
* Breadcrumbs and log lines become Crashlytics log lines, errors become
|
|
@@ -3,4 +3,6 @@ export { errorToastMiddleware, isSilentRejection } from "./errorToastMiddleware"
|
|
|
3
3
|
export type { RootReducerOptions } from "./rootReducer";
|
|
4
4
|
export { createRootReducer, resetState } from "./rootReducer";
|
|
5
5
|
export { SERIALIZABLE_IGNORED_ACTION_PATHS } from "./serializableCheck";
|
|
6
|
+
export type { SessionMiddlewareOptions } from "./sessionMiddleware";
|
|
7
|
+
export { createSessionMiddleware } from "./sessionMiddleware";
|
|
6
8
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Middleware, UnknownAction } from "@reduxjs/toolkit";
|
|
2
|
+
export interface SessionMiddlewareOptions {
|
|
3
|
+
/** Answers true for the pending action of the app's own sign-out thunk, for example `logout.pending.match`. */
|
|
4
|
+
isSignOut: (action: UnknownAction) => boolean;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Drops the outcome of every call still out when the person signs out, so an answer for the ended
|
|
8
|
+
* session never refills the store the sign-out just cleared, or shows to the next person on this
|
|
9
|
+
* phone. Each async thunk's actions carry a `requestId`; the ones in flight at the sign-out's
|
|
10
|
+
* pending action are stale. The thunk's own promise still settles, so a caller awaiting it is not
|
|
11
|
+
* left hanging.
|
|
12
|
+
*/
|
|
13
|
+
export declare const createSessionMiddleware: (options: SessionMiddlewareOptions) => Middleware;
|
|
14
|
+
//# sourceMappingURL=sessionMiddleware.d.ts.map
|
|
@@ -2,6 +2,7 @@ import type { AxiosInstance } from "axios";
|
|
|
2
2
|
export declare const CLIENT_BUILD_HEADER = "Client-Build";
|
|
3
3
|
export declare const APP_BUILD_HEADER = "App-Build";
|
|
4
4
|
export declare const CLIENT_KIND_HEADER = "Client-Kind";
|
|
5
|
+
export declare const CLIENT_APP_HEADER = "Client-App";
|
|
5
6
|
export declare const ACCOUNT_ID_HEADER = "Account-Id";
|
|
6
7
|
/** The server picks session rules and the minimum version per kind; the names match its enum. */
|
|
7
8
|
export type ClientKind = "Browser" | "MobileApp";
|
|
@@ -9,7 +10,8 @@ export declare const CLIENT_KIND: ClientKind;
|
|
|
9
10
|
/** `<appVersion>+<commit>`, `<appVersion>` when no commit was stamped, `null` before init. */
|
|
10
11
|
export declare const getClientBuild: () => string | null;
|
|
11
12
|
/**
|
|
12
|
-
* Stamps Client-Kind, Client-Build and App-Build on every request
|
|
13
|
+
* Stamps Client-Kind, Client-Build and App-Build on every request, and Client-App with the
|
|
14
|
+
* config's app id when one is set, so the server can tell the apps of one kind apart.
|
|
13
15
|
* App-Build is the whole build number the server compares with its minimum;
|
|
14
16
|
* it is left off when the build is unknown, because the server never refuses a
|
|
15
17
|
* request without it and a guessed number could strand a current build. A value the app
|
|
@@ -14,6 +14,8 @@ export interface AppLockState {
|
|
|
14
14
|
locked: boolean;
|
|
15
15
|
enabled: boolean;
|
|
16
16
|
biometricAvailable: boolean;
|
|
17
|
+
/** False until the first biometric availability check settles, so `biometricAvailable` is not yet a real answer. */
|
|
18
|
+
availabilityKnown: boolean;
|
|
17
19
|
setEnabled: (enabled: boolean) => Promise<AppLockResult>;
|
|
18
20
|
unlock: (reason: string) => Promise<AppLockResult>;
|
|
19
21
|
}
|
|
@@ -25,6 +27,8 @@ export declare function authenticateBiometric(reason: string): Promise<AppLockRe
|
|
|
25
27
|
* The time it left the screen is saved, not held in memory, so a phone that closed the app in the background still
|
|
26
28
|
* locks on the next launch. Only "background" counts as away: iOS reports "inactive" while the Face ID prompt is up.
|
|
27
29
|
* The person's choice is stored under `enabledKey`; with none saved it is on when the phone has a fingerprint or face enrolled.
|
|
30
|
+
* `availabilityKnown` stays false until the first availability check settles, so a screen can wait before it reads
|
|
31
|
+
* `biometricAvailable` as "this phone has no biometrics".
|
|
28
32
|
*/
|
|
29
33
|
export declare function useAppLock({ active, policy }: {
|
|
30
34
|
active: boolean;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type DownloadResult } from "../download/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* The content type a file name's extension stands for. Files that arrive with a name only need a type to be saved
|
|
4
|
+
* properly and to offer the right apps for them.
|
|
5
|
+
*/
|
|
6
|
+
export declare const mimeTypeFromFileName: (fileName: string | null | undefined) => string | undefined;
|
|
7
|
+
export interface AttachmentRequest {
|
|
8
|
+
url: string;
|
|
9
|
+
fileName: string;
|
|
10
|
+
mimeType?: string | null;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Fetches a file through the signed-in client into the app's cache, where a viewer or a save reads it. Answers with
|
|
14
|
+
* its local `path` and `mimeType`; a file with no content type of its own takes the one its name stands for.
|
|
15
|
+
*/
|
|
16
|
+
export declare const fetchAttachment: ({ url, fileName, mimeType }: AttachmentRequest) => Promise<DownloadResult>;
|
|
17
|
+
export interface PreviewAttachment {
|
|
18
|
+
path: string;
|
|
19
|
+
mimeType?: string;
|
|
20
|
+
folder: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* fetchAttachment for a preview: a document already downloaded this session opens from the phone with no second
|
|
24
|
+
* download. The document's `version` (its updated time) is part of the key, so a replaced document is fetched again;
|
|
25
|
+
* without a version the file is not kept at all.
|
|
26
|
+
*/
|
|
27
|
+
export declare const fetchPreviewAttachment: ({ cacheId, version, url, fileName, mimeType, }: AttachmentRequest & {
|
|
28
|
+
cacheId: string;
|
|
29
|
+
version?: string | null;
|
|
30
|
+
}) => Promise<PreviewAttachment | DownloadResult>;
|
|
31
|
+
export type PreviewKind = "image" | "pdf";
|
|
32
|
+
/** What the app can show itself, from a content type or a file name: "image", "pdf", or null for anything else. */
|
|
33
|
+
export declare const previewKind: (file: {
|
|
34
|
+
contentType?: string | null;
|
|
35
|
+
name?: string | null;
|
|
36
|
+
}) => PreviewKind | null;
|
|
37
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
* @webority/mobile-core/geolocation a geolocation provider
|
|
11
11
|
* @webority/mobile-core/sqlite expo-sqlite
|
|
12
12
|
* @webority/mobile-core/download a filesystem module
|
|
13
|
+
* @webority/mobile-core/installation installation id (expo-crypto)
|
|
14
|
+
* @webority/mobile-core/attachments fetch, preview cache and preview kind (expo-file-system)
|
|
13
15
|
* @webority/mobile-core/share expo-sharing
|
|
14
16
|
* @webority/mobile-core/review expo-store-review
|
|
15
17
|
* @webority/mobile-core/secureScreen expo-screen-capture
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Simple
|
|
2
|
+
* Simple initialisation API for Webority packages
|
|
3
3
|
* One function to configure everything needed
|
|
4
4
|
*/
|
|
5
5
|
import { type KeychainLike } from "./auth/authStore.js";
|
|
@@ -27,13 +27,17 @@ export interface InitConfig {
|
|
|
27
27
|
*/
|
|
28
28
|
country?: string;
|
|
29
29
|
/**
|
|
30
|
-
* The app's store identifier (Android package name / iOS bundle id),
|
|
31
|
-
*
|
|
30
|
+
* The app's store identifier (Android package name / iOS bundle id), from `APP_ID` in
|
|
31
|
+
* `appsettings.Compiled.json`. Required: it must equal the bundle id the installed app runs,
|
|
32
|
+
* so a bundle compiled for another environment stops at start-up.
|
|
33
|
+
*/
|
|
34
|
+
appId: string;
|
|
35
|
+
/**
|
|
36
|
+
* The app's user-visible version and its internal build number (`VERSION_INTERNAL`).
|
|
32
37
|
* Every Webority app already has them in `appsettings.Compiled.json`, which
|
|
33
38
|
* is why they come from config rather than across the native bridge. The
|
|
34
39
|
* version check compares `appBuild` with the API's `Min-App-Version`.
|
|
35
40
|
*/
|
|
36
|
-
appId?: string;
|
|
37
41
|
appVersion?: string;
|
|
38
42
|
appBuild?: number;
|
|
39
43
|
/** The numeric Apple App Store id; the iOS update button needs it. */
|
|
@@ -73,6 +77,7 @@ export interface InitConfig {
|
|
|
73
77
|
* // Basic setup
|
|
74
78
|
* initWebority({
|
|
75
79
|
* baseUrl: 'https://api.myapp.com',
|
|
80
|
+
* appId: 'com.myapp',
|
|
76
81
|
* remoteLogger: null
|
|
77
82
|
* });
|
|
78
83
|
*
|
|
@@ -80,6 +85,7 @@ export interface InitConfig {
|
|
|
80
85
|
* // Full configuration
|
|
81
86
|
* initWebority({
|
|
82
87
|
* baseUrl: 'https://api.myapp.com',
|
|
88
|
+
* appId: 'com.myapp',
|
|
83
89
|
* remoteLogger: crashlyticsRemoteLogger(crashlytics),
|
|
84
90
|
* loggingEnabled: __DEV__,
|
|
85
91
|
* logLevel: 'warn',
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The stored names the existing apps already use. Keeping them means an
|
|
3
|
+
* installation that already has an id keeps it after the app adopts this module.
|
|
4
|
+
*/
|
|
5
|
+
export declare const INSTALLATION_STORAGE_KEYS: {
|
|
6
|
+
readonly HAS_LAUNCHED_BEFORE: "hasLaunchedBefore";
|
|
7
|
+
readonly INSTALLATION_ID: "installationId";
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Returns this installation's id, creating and persisting one on first use. A
|
|
11
|
+
* stored id is never replaced, whatever its format. Throws when the new id cannot
|
|
12
|
+
* be persisted: every account call is keyed on the id, and a fresh id per call
|
|
13
|
+
* would register a new installation on each sign-in.
|
|
14
|
+
*/
|
|
15
|
+
export declare const getOrCreateInstallationId: () => Promise<string>;
|
|
16
|
+
/**
|
|
17
|
+
* The iOS keychain keeps tokens across an uninstall, so a reinstalled app would
|
|
18
|
+
* start signed in as the previous person. This clears them on the first launch
|
|
19
|
+
* after install. It never calls clearSession: no session-ended listener may fire
|
|
20
|
+
* before the app has started.
|
|
21
|
+
*/
|
|
22
|
+
export declare const clearTokensOnFirstLaunch: () => Promise<void>;
|
|
23
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -18,7 +18,7 @@ export interface CrashlyticsModule<Instance = unknown> {
|
|
|
18
18
|
* import * as crashlytics from '@react-native-firebase/crashlytics';
|
|
19
19
|
* import { crashlyticsRemoteLogger, initWebority } from '@webority/mobile-core';
|
|
20
20
|
*
|
|
21
|
-
* initWebority({ baseUrl, remoteLogger: crashlyticsRemoteLogger(crashlytics) });
|
|
21
|
+
* initWebority({ baseUrl, appId, remoteLogger: crashlyticsRemoteLogger(crashlytics) });
|
|
22
22
|
* ```
|
|
23
23
|
*
|
|
24
24
|
* Breadcrumbs and log lines become Crashlytics log lines, errors become
|
|
@@ -3,4 +3,6 @@ export { errorToastMiddleware, isSilentRejection } from "./errorToastMiddleware.
|
|
|
3
3
|
export type { RootReducerOptions } from "./rootReducer.js";
|
|
4
4
|
export { createRootReducer, resetState } from "./rootReducer.js";
|
|
5
5
|
export { SERIALIZABLE_IGNORED_ACTION_PATHS } from "./serializableCheck.js";
|
|
6
|
+
export type { SessionMiddlewareOptions } from "./sessionMiddleware.js";
|
|
7
|
+
export { createSessionMiddleware } from "./sessionMiddleware.js";
|
|
6
8
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Middleware, UnknownAction } from "@reduxjs/toolkit";
|
|
2
|
+
export interface SessionMiddlewareOptions {
|
|
3
|
+
/** Answers true for the pending action of the app's own sign-out thunk, for example `logout.pending.match`. */
|
|
4
|
+
isSignOut: (action: UnknownAction) => boolean;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Drops the outcome of every call still out when the person signs out, so an answer for the ended
|
|
8
|
+
* session never refills the store the sign-out just cleared, or shows to the next person on this
|
|
9
|
+
* phone. Each async thunk's actions carry a `requestId`; the ones in flight at the sign-out's
|
|
10
|
+
* pending action are stale. The thunk's own promise still settles, so a caller awaiting it is not
|
|
11
|
+
* left hanging.
|
|
12
|
+
*/
|
|
13
|
+
export declare const createSessionMiddleware: (options: SessionMiddlewareOptions) => Middleware;
|
|
14
|
+
//# sourceMappingURL=sessionMiddleware.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webority/mobile-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.113.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",
|
|
@@ -152,6 +152,26 @@
|
|
|
152
152
|
"default": "./lib/commonjs/download/index.js"
|
|
153
153
|
}
|
|
154
154
|
},
|
|
155
|
+
"./installation": {
|
|
156
|
+
"import": {
|
|
157
|
+
"types": "./lib/typescript/module/installation/index.d.ts",
|
|
158
|
+
"default": "./lib/module/installation/index.js"
|
|
159
|
+
},
|
|
160
|
+
"require": {
|
|
161
|
+
"types": "./lib/typescript/commonjs/installation/index.d.ts",
|
|
162
|
+
"default": "./lib/commonjs/installation/index.js"
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
"./attachments": {
|
|
166
|
+
"import": {
|
|
167
|
+
"types": "./lib/typescript/module/attachments/index.d.ts",
|
|
168
|
+
"default": "./lib/module/attachments/index.js"
|
|
169
|
+
},
|
|
170
|
+
"require": {
|
|
171
|
+
"types": "./lib/typescript/commonjs/attachments/index.d.ts",
|
|
172
|
+
"default": "./lib/commonjs/attachments/index.js"
|
|
173
|
+
}
|
|
174
|
+
},
|
|
155
175
|
"./share": {
|
|
156
176
|
"import": {
|
|
157
177
|
"types": "./lib/typescript/module/share/index.d.ts",
|
|
@@ -329,6 +349,7 @@
|
|
|
329
349
|
"expo-calendar": ">=57.0.0",
|
|
330
350
|
"expo-camera": ">=57.0.0",
|
|
331
351
|
"expo-contacts": ">=57.0.0",
|
|
352
|
+
"expo-crypto": ">=57.0.0",
|
|
332
353
|
"expo-document-picker": ">=57.0.0",
|
|
333
354
|
"expo-file-system": ">=57.0.0",
|
|
334
355
|
"expo-image-manipulator": ">=57.0.0",
|
|
@@ -362,6 +383,9 @@
|
|
|
362
383
|
"expo-contacts": {
|
|
363
384
|
"optional": true
|
|
364
385
|
},
|
|
386
|
+
"expo-crypto": {
|
|
387
|
+
"optional": true
|
|
388
|
+
},
|
|
365
389
|
"expo-document-picker": {
|
|
366
390
|
"optional": true
|
|
367
391
|
},
|
|
@@ -443,6 +467,7 @@
|
|
|
443
467
|
"@types/react": "19.2.18",
|
|
444
468
|
"expo": "57.0.17",
|
|
445
469
|
"expo-application": "57.0.2",
|
|
470
|
+
"expo-crypto": "57.0.4",
|
|
446
471
|
"expo-document-picker": "57.0.2",
|
|
447
472
|
"expo-file-system": "57.0.6",
|
|
448
473
|
"expo-image-manipulator": "57.0.14",
|