@webority/mobile-core 0.15.0 → 0.16.1
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 +4 -0
- package/lib/commonjs/errors/extractApiError.js +1 -1
- package/lib/commonjs/errors/index.js +1 -1
- package/lib/commonjs/formatters/currency.js +1 -1
- package/lib/commonjs/formatters/house.js +1 -1
- package/lib/commonjs/index.js +1 -1
- package/lib/commonjs/initSDK.js +1 -1
- package/lib/commonjs/logger/index.js +1 -1
- package/lib/commonjs/logger/redact.js +1 -1
- package/lib/commonjs/push/push.js +1 -1
- package/lib/commonjs/utils/imageCompression.js +1 -1
- package/lib/module/errors/extractApiError.js +1 -1
- package/lib/module/errors/index.js +1 -1
- package/lib/module/formatters/currency.js +1 -1
- package/lib/module/formatters/house.js +1 -1
- package/lib/module/index.js +1 -1
- package/lib/module/initSDK.js +1 -1
- package/lib/module/logger/index.js +1 -1
- package/lib/module/logger/redact.js +1 -1
- package/lib/module/push/push.js +1 -1
- package/lib/module/utils/imageCompression.js +1 -1
- package/lib/typescript/commonjs/errors/extractApiError.d.ts +9 -6
- package/lib/typescript/commonjs/errors/index.d.ts +1 -1
- package/lib/typescript/commonjs/formatters/currency.d.ts +10 -9
- package/lib/typescript/commonjs/formatters/house.d.ts +2 -36
- package/lib/typescript/commonjs/index.d.ts +3 -3
- package/lib/typescript/commonjs/initSDK.d.ts +9 -1
- package/lib/typescript/commonjs/logger/crashlytics.d.ts +3 -3
- package/lib/typescript/commonjs/logger/index.d.ts +20 -8
- package/lib/typescript/commonjs/logger/redact.d.ts +2 -0
- package/lib/typescript/commonjs/push/push.d.ts +2 -0
- package/lib/typescript/module/errors/extractApiError.d.ts +9 -6
- package/lib/typescript/module/errors/index.d.ts +1 -1
- package/lib/typescript/module/formatters/currency.d.ts +10 -9
- package/lib/typescript/module/formatters/house.d.ts +2 -36
- package/lib/typescript/module/index.d.ts +3 -3
- package/lib/typescript/module/initSDK.d.ts +9 -1
- package/lib/typescript/module/logger/crashlytics.d.ts +3 -3
- package/lib/typescript/module/logger/index.d.ts +20 -8
- package/lib/typescript/module/logger/redact.d.ts +2 -0
- package/lib/typescript/module/push/push.d.ts +2 -0
- package/package.json +1 -1
package/lib/module/push/push.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{useSyncExternalStore}from"react";import{Platform}from"react-native";import{Logger}from"../logger/index.js";let resolved,override=null;export const setPushImplementation=e=>{override=e};export const resolveModules=()=>{if(override)return override;if(void 0!==resolved)return resolved;try{if(require("expo").isRunningInExpoGo())return resolved=null,resolved;const e=require("expo-notifications"),t=require("expo-application");resolved={notifications:e,applicationId:t.applicationId,getApnsEnvironment:()=>t.getIosPushNotificationServiceEnvironmentAsync()}}catch(e){Logger.warn("[push] expo-notifications is not available; push is off",e),resolved=null}return resolved};let state={status:"unknown",enabled:!1};const subscribers=new Set,setState=e=>{state={...state,...e};for(const e of subscribers)e()};let config=null,channelsReady=Promise.resolve(),tokenSubscription=null,actionHandler=null;const receiveHandlers=new Set;let listening=!1,listeningForReceived=!1;const pendingResponses=[],routedAt=new Map,REPEAT_WINDOW_MS=1e4,requireConfig=()=>{if(!config)throw new Error("[@webority/mobile-core] call initPush before enablePush or disablePush.");return config},toPermissionStatus=e=>e.granted?"granted":"denied"===e.status?e.canAskAgain?"denied":"blocked":"undetermined",toStringData=e=>{const t={};for(const[n,o]of Object.entries(e??{}))null!=o&&(t[n]="object"==typeof o?JSON.stringify(o):String(o));return t},contentData=e=>{const t=e.request.content;if(t.data)return toStringData(t.data);if("string"==typeof t.dataString)try{return toStringData(JSON.parse(t.dataString))}catch(e){Logger.error("[push] notification data is not JSON",e)}return{}},triggerData=e=>{const t=e.request.trigger,n={...t?.remoteMessage?.data??{}};for(const[e,o]of Object.entries(t?.payload??{}))"aps"===e||"body"===e&&"object"==typeof o||(n[e]=o);return toStringData(n)},notificationData=e=>({...triggerData(e),...contentData(e)}),responseData=e=>notificationData(e.notification);export const toPushReceived=e=>({notificationId:e.request.identifier,title:e.request.content.title??null,body:e.request.content.body??null,data:notificationData(e)});export const toPushAction=e=>({actionId:e.actionIdentifier,...e.userText?{userText:e.userText}:{},data:responseData(e),notificationId:e.notification.request.identifier});export const toNotificationActions=e=>e.map(e=>({identifier:e.id,buttonTitle:e.title,...e.textInput?{textInput:{submitButtonTitle:e.textInput.submitTitle,placeholder:e.textInput.placeholder}}:{},options:{opensAppToForeground:e.opensApp??!0,isDestructive:e.destructive??!1,isAuthenticationRequired:e.authenticationRequired??!1}}));const routeResponse=async(e,t)=>{const n=`${t.notification.request.identifier}:${t.actionIdentifier}`,o=routedAt.get(n);if(void 0!==o&&Date.now()-o<1e4)return;if(t.actionIdentifier!==e.DEFAULT_ACTION_IDENTIFIER&&actionHandler){routedAt.set(n,Date.now());try{await actionHandler(toPushAction(t))}catch(e){Logger.error("[push] onAction threw",e)}}else if(config){routedAt.set(n,Date.now());try{config.onOpen(responseData(t))}catch(e){Logger.error("[push] onOpen threw",e)}}else pendingResponses.push(t)},drainPending=async e=>{const t=pendingResponses.splice(0);for(const n of t)await routeResponse(e,n)},listenForResponses=e=>{listening||(listening=!0,e.addNotificationResponseReceivedListener(t=>{routeResponse(e,t)}),e.getLastNotificationResponseAsync().then(async t=>(t&&await routeResponse(e,t),e.clearLastNotificationResponseAsync?.())).catch(e=>Logger.error("[push] reading the launch notification failed",e)))};export const routeTaskPayload=async e=>{const t=resolveModules(),n=e;t&&"string"==typeof n?.actionIdentifier&&"string"==typeof n.notification?.request?.identifier&&await routeResponse(t.notifications,n)};const createChannels=async(e,t)=>{for(const n of t)try{await e.setNotificationChannelAsync(n.id,{name:n.name,importance:"high"===n.importance?e.AndroidImportance.HIGH:e.AndroidImportance.DEFAULT})}catch(e){Logger.error(`[push] creating channel ${n.id} failed`,e)}},APNS_ENVIRONMENTS={development:"Development",production:"Production"},readApnsEnvironment=async e=>{try{return APNS_ENVIRONMENTS[await e.getApnsEnvironment()??""]??null}catch(e){return Logger.warn("[push] reading the APNs environment failed",e),null}},toDevice=async(e,t)=>{const n="android"===e.type?"Fcm":"ios"===e.type?"Apns":null;if(!n||"string"!=typeof e.data)return null;if(!t.applicationId)throw new Error("[@webority/mobile-core] push needs the app bundle id from expo-application.");return{installationId:await requireConfig().getInstallationId(),platform:n,token:e.data,appId:t.applicationId,apnsEnvironment:"Apns"===n?await readApnsEnvironment(t):null}};export const initPush=e=>{const t=null===config;if(config=e,!t)return;const n=resolveModules();if(!n)return void setState({status:"unavailable"});const o=n.notifications;o.setNotificationHandler({handleNotification:async()=>{const e=config?.showInForeground?.()??!0;return{shouldShowBanner:e,shouldShowList:e,shouldPlaySound:e&&(config?.playSoundInForeground?.()??!0),shouldSetBadge:!0}}}),"android"===Platform.OS&&e.androidChannels?.length&&(channelsReady=createChannels(o,e.androidChannels)),listenForResponses(o),drainPending(o),o.getPermissionsAsync().then(e=>setState({status:toPermissionStatus(e)})).catch(e=>Logger.error("[push] permission check failed",e))};let registering=null,registeredKey=null,unregistering=null,tokenDrop=Promise.resolve();const registerOnce=e=>{const t=JSON.stringify(e);if(registering?.key===t)return registering.promise;const n=requireConfig().register(e).then(()=>{registeredKey=t}).finally(()=>{registering?.promise===n&&(registering=null)});return registering={key:t,promise:n},n};export const enablePush=async({prompt:e=!0}={})=>{requireConfig();const t=resolveModules();if(!t)return setState({status:"unavailable"}),"unavailable";const n=t.notifications;await channelsReady,await tokenDrop;let o=await n.getPermissionsAsync();if(!o.granted&&e&&(o=await n.requestPermissionsAsync()),!o.granted){const e=toPermissionStatus(o);return setState({status:e}),e}const i=await toDevice(await n.getDevicePushTokenAsync(),t);return i?(await registerOnce(i),tokenSubscription?.remove(),tokenSubscription=n.addPushTokenListener(e=>{(unregistering??Promise.resolve()).then(()=>state.enabled?toDevice(e,t):null).then(e=>e&&JSON.stringify(e)!==registeredKey?registerOnce(e):void 0).catch(e=>Logger.error("[push] re-registering a new token failed",e))}),setState({status:"granted",enabled:!0}),"granted"):(setState({status:"unavailable"}),"unavailable")};const stopFollowingToken=()=>{tokenSubscription?.remove(),tokenSubscription=null,registeredKey=null,setState({enabled:!1})};export const disablePush=async()=>{const e=requireConfig(),t=await e.getInstallationId();await(registering?.promise.catch(()=>{}));let settle=()=>{};const n=new Promise(e=>{settle=e});unregistering=n;try{await e.unregister(t),stopFollowingToken()}finally{unregistering===n&&(unregistering=null),settle()}};export const resetPush=async()=>{stopFollowingToken();const e=resolveModules();if(!e)return;const t=e.notifications.unregisterForNotificationsAsync();tokenDrop=t.catch(()=>{}),await t};export const setBadgeCount=async e=>{const t=resolveModules();t&&await t.notifications.setBadgeCountAsync(e)};export const dismissAllNotifications=async()=>{const e=resolveModules();if(e)try{await e.notifications.dismissAllNotificationsAsync()}catch(e){Logger.warn("[push] dismissing the notifications failed",e)}};export const setCategories=async e=>{const t=resolveModules();if(t)for(const n of e)await t.notifications.setNotificationCategoryAsync(n.id,toNotificationActions(n.actions))};export const onAction=e=>{actionHandler=e;const t=resolveModules();return t&&(listenForResponses(t.notifications),drainPending(t.notifications)),()=>{actionHandler===e&&(actionHandler=null)}};export const onReceive=e=>{receiveHandlers.add(e);const t=resolveModules();return t&&!listeningForReceived&&(listeningForReceived=!0,t.notifications.addNotificationReceivedListener(e=>{const t=toPushReceived(e);for(const e of[...receiveHandlers])try{e(t)}catch(e){Logger.error("[push] onReceive threw",e)}})),()=>{receiveHandlers.delete(e)}};export const notifyLocal=async({title:e,body:t,data:n})=>{const o=resolveModules();o&&await o.notifications.scheduleNotificationAsync({content:{title:e,body:t,data:n??{}},trigger:null})};const subscribe=e=>(subscribers.add(e),()=>{subscribers.delete(e)}),getSnapshot=()=>state,enable=async()=>{await enablePush()};export const usePush=()=>({...useSyncExternalStore(subscribe,getSnapshot,getSnapshot),enable:enable,disable:disablePush});
|
|
1
|
+
"use strict";import{useSyncExternalStore}from"react";import{Platform}from"react-native";import{Logger}from"../logger/index.js";let resolved,override=null;export const setPushImplementation=e=>{override=e};export const resolveModules=()=>{if(override)return override;if(void 0!==resolved)return resolved;try{if(require("expo").isRunningInExpoGo())return resolved=null,resolved;const e=require("expo-notifications"),t=require("expo-application");resolved={notifications:e,applicationId:t.applicationId,getApnsEnvironment:()=>t.getIosPushNotificationServiceEnvironmentAsync()}}catch(e){Logger.warn("[push] expo-notifications is not available; push is off",e),resolved=null}return resolved};let state={status:"unknown",enabled:!1};const subscribers=new Set,setState=e=>{state={...state,...e};for(const e of subscribers)e()};let config=null,channelsReady=Promise.resolve(),tokenSubscription=null,actionHandler=null;const receiveHandlers=new Set;let listening=!1,listeningForReceived=!1;const pendingResponses=[],routedAt=new Map,REPEAT_WINDOW_MS=1e4,requireConfig=()=>{if(!config)throw new Error("[@webority/mobile-core] call initPush before enablePush or disablePush.");return config},toPermissionStatus=e=>e.granted?"granted":"denied"===e.status?e.canAskAgain?"denied":"blocked":"undetermined",toStringData=e=>{const t={};for(const[n,o]of Object.entries(e??{}))null!=o&&(t[n]="object"==typeof o?JSON.stringify(o):String(o));return t},contentData=e=>{const t=e.request.content;if(t.data)return toStringData(t.data);if("string"==typeof t.dataString)try{return toStringData(JSON.parse(t.dataString))}catch(e){Logger.error("[push] notification data is not JSON",e)}return{}},triggerData=e=>{const t=e.request.trigger,n={...t?.remoteMessage?.data??{}};for(const[e,o]of Object.entries(t?.payload??{}))"aps"===e||"body"===e&&"object"==typeof o||(n[e]=o);return toStringData(n)},notificationData=e=>({...triggerData(e),...contentData(e)}),responseData=e=>notificationData(e.notification);export const toPushReceived=e=>({notificationId:e.request.identifier,title:e.request.content.title??null,body:e.request.content.body??null,data:notificationData(e)});export const toPushAction=e=>({actionId:e.actionIdentifier,...e.userText?{userText:e.userText}:{},data:responseData(e),notificationId:e.notification.request.identifier});export const toNotificationActions=e=>e.map(e=>({identifier:e.id,buttonTitle:e.title,...e.textInput?{textInput:{submitButtonTitle:e.textInput.submitTitle,placeholder:e.textInput.placeholder}}:{},options:{opensAppToForeground:e.opensApp??!0,isDestructive:e.destructive??!1,isAuthenticationRequired:e.authenticationRequired??!1}}));const routeResponse=async(e,t)=>{const n=`${t.notification.request.identifier}:${t.actionIdentifier}`,o=routedAt.get(n);if(void 0!==o&&Date.now()-o<1e4)return;if(t.actionIdentifier!==e.DEFAULT_ACTION_IDENTIFIER&&actionHandler){routedAt.set(n,Date.now());try{await actionHandler(toPushAction(t))}catch(e){Logger.error("[push] onAction threw",e)}}else if(config){routedAt.set(n,Date.now());try{config.onOpen(responseData(t))}catch(e){Logger.error("[push] onOpen threw",e)}}else pendingResponses.push(t)},drainPending=async e=>{const t=pendingResponses.splice(0);for(const n of t)await routeResponse(e,n)},listenForResponses=e=>{listening||(listening=!0,e.addNotificationResponseReceivedListener(t=>{routeResponse(e,t)}),e.getLastNotificationResponseAsync().then(async t=>(t&&await routeResponse(e,t),e.clearLastNotificationResponseAsync?.())).catch(e=>Logger.error("[push] reading the launch notification failed",e)))};export const routeTaskPayload=async e=>{const t=resolveModules(),n=e;t&&"string"==typeof n?.actionIdentifier&&"string"==typeof n.notification?.request?.identifier&&await routeResponse(t.notifications,n)};const createChannels=async(e,t)=>{for(const n of t)try{await e.setNotificationChannelAsync(n.id,{name:n.name,importance:"high"===n.importance?e.AndroidImportance.HIGH:e.AndroidImportance.DEFAULT})}catch(e){Logger.error(`[push] creating channel ${n.id} failed`,e)}},APNS_ENVIRONMENTS={development:"Development",production:"Production"},readApnsEnvironment=async e=>{try{return APNS_ENVIRONMENTS[await e.getApnsEnvironment()??""]??null}catch(e){return Logger.warn("[push] reading the APNs environment failed",e),null}},deviceTimeZone=()=>Intl.DateTimeFormat().resolvedOptions().timeZone||null,toDevice=async(e,t)=>{const n="android"===e.type?"Fcm":"ios"===e.type?"Apns":null;if(!n||"string"!=typeof e.data)return null;if(!t.applicationId)throw new Error("[@webority/mobile-core] push needs the app bundle id from expo-application.");return{installationId:await requireConfig().getInstallationId(),platform:n,token:e.data,appId:t.applicationId,apnsEnvironment:"Apns"===n?await readApnsEnvironment(t):null,timeZone:deviceTimeZone()}};export const initPush=e=>{const t=null===config;if(config=e,!t)return;const n=resolveModules();if(!n)return void setState({status:"unavailable"});const o=n.notifications;o.setNotificationHandler({handleNotification:async()=>{const e=config?.showInForeground?.()??!0;return{shouldShowBanner:e,shouldShowList:e,shouldPlaySound:e&&(config?.playSoundInForeground?.()??!0),shouldSetBadge:!0}}}),"android"===Platform.OS&&e.androidChannels?.length&&(channelsReady=createChannels(o,e.androidChannels)),listenForResponses(o),drainPending(o),o.getPermissionsAsync().then(e=>setState({status:toPermissionStatus(e)})).catch(e=>Logger.error("[push] permission check failed",e))};let registering=null,registeredKey=null,unregistering=null,tokenDrop=Promise.resolve();const registerOnce=e=>{const t=JSON.stringify(e);if(registering?.key===t)return registering.promise;const n=requireConfig().register(e).then(()=>{registeredKey=t}).finally(()=>{registering?.promise===n&&(registering=null)});return registering={key:t,promise:n},n};export const enablePush=async({prompt:e=!0}={})=>{requireConfig();const t=resolveModules();if(!t)return setState({status:"unavailable"}),"unavailable";const n=t.notifications;await channelsReady,await tokenDrop;let o=await n.getPermissionsAsync();if(!o.granted&&e&&(o=await n.requestPermissionsAsync()),!o.granted){const e=toPermissionStatus(o);return setState({status:e}),e}const i=await toDevice(await n.getDevicePushTokenAsync(),t);return i?(await registerOnce(i),tokenSubscription?.remove(),tokenSubscription=n.addPushTokenListener(e=>{(unregistering??Promise.resolve()).then(()=>state.enabled?toDevice(e,t):null).then(e=>e&&JSON.stringify(e)!==registeredKey?registerOnce(e):void 0).catch(e=>Logger.error("[push] re-registering a new token failed",e))}),setState({status:"granted",enabled:!0}),"granted"):(setState({status:"unavailable"}),"unavailable")};const stopFollowingToken=()=>{tokenSubscription?.remove(),tokenSubscription=null,registeredKey=null,setState({enabled:!1})};export const disablePush=async()=>{const e=requireConfig(),t=await e.getInstallationId();await(registering?.promise.catch(()=>{}));let settle=()=>{};const n=new Promise(e=>{settle=e});unregistering=n;try{await e.unregister(t),stopFollowingToken()}finally{unregistering===n&&(unregistering=null),settle()}};export const resetPush=async()=>{stopFollowingToken();const e=resolveModules();if(!e)return;const t=e.notifications.unregisterForNotificationsAsync();tokenDrop=t.catch(()=>{}),await t};export const setBadgeCount=async e=>{const t=resolveModules();t&&await t.notifications.setBadgeCountAsync(e)};export const dismissAllNotifications=async()=>{const e=resolveModules();if(e)try{await e.notifications.dismissAllNotificationsAsync()}catch(e){Logger.warn("[push] dismissing the notifications failed",e)}};export const setCategories=async e=>{const t=resolveModules();if(t)for(const n of e)await t.notifications.setNotificationCategoryAsync(n.id,toNotificationActions(n.actions))};export const onAction=e=>{actionHandler=e;const t=resolveModules();return t&&(listenForResponses(t.notifications),drainPending(t.notifications)),()=>{actionHandler===e&&(actionHandler=null)}};export const onReceive=e=>{receiveHandlers.add(e);const t=resolveModules();return t&&!listeningForReceived&&(listeningForReceived=!0,t.notifications.addNotificationReceivedListener(e=>{const t=toPushReceived(e);for(const e of[...receiveHandlers])try{e(t)}catch(e){Logger.error("[push] onReceive threw",e)}})),()=>{receiveHandlers.delete(e)}};export const notifyLocal=async({title:e,body:t,data:n})=>{const o=resolveModules();o&&await o.notifications.scheduleNotificationAsync({content:{title:e,body:t,data:n??{}},trigger:null})};const subscribe=e=>(subscribers.add(e),()=>{subscribers.delete(e)}),getSnapshot=()=>state,enable=async()=>{await enablePush()};export const usePush=()=>({...useSyncExternalStore(subscribe,getSnapshot,getSnapshot),enable:enable,disable:disablePush});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{File}from"expo-file-system";import{ImageManipulator}from"expo-image-manipulator";import{Logger}from"../logger/index.js";export class ImageUnreadableError extends Error{constructor(e,t){super(`Image is unreadable (${t}): ${e||"<empty uri>"}`),this.name="ImageUnreadableError",this.uri=e}}const release=e=>{try{e?.release?.()}catch{}},expoBackend={stat:async e=>{const t=new File(e);return t.exists?null==t.size?null:{exists:!0,size:t.size}:{exists:!1,size:0}},remove:async e=>{new File(e).delete()},open:async e=>{const t=ImageManipulator.manipulate(e);let i;try{i=await t.renderAsync()}finally{release(t)}return{width:i.width,height:i.height,encode:async({width:e,height:t,quality:a,format:r})=>{const n=ImageManipulator.manipulate(i);let o=null;try{void 0===e&&void 0===t||n.resize({width:e??null,height:t??null}),o=await n.renderAsync();const i=await o.saveAsync({compress:a,format:r});return{uri:i.uri,width:i.width,height:i.height}}finally{release(o),release(n)}},close:()=>release(i)}}};let backend=null;export const setImageCompressorImplementation=e=>{backend=e};const resolveBackend=()=>backend??expoBackend,EIGHT_MB=8388608,THREE_MB=3145728;export const qualityForFileSize=e=>null==e?.7:e>8388608?.6:e>3145728?.7:.8;const DEFAULT_MAX_SIZE=1500,DEFAULT_MIN_QUALITY=.4,QUALITY_STEP=.1,SIZE_STEP=.75,MIN_SIDE=320,FORMAT={jpg:{encode:"jpeg",mimeType:"image/jpeg"},png:{encode:"png",mimeType:"image/png"},webp:{encode:"webp",mimeType:"image/webp"}},MIME_BY_EXTENSION={jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",webp:"image/webp",gif:"image/gif",heic:"image/heic",heif:"image/heif",bmp:"image/bmp",tif:"image/tiff",tiff:"image/tiff"},lastSegment=e=>{const t=e.split(/[?#]/)[0]??"";return decodeURIComponentSafe(t.slice(t.lastIndexOf("/")+1))},decodeURIComponentSafe=e=>{try{return decodeURIComponent(e)}catch{return e}},extensionOf=e=>{const t=e.lastIndexOf(".");return t>0?e.slice(t+1).toLowerCase():""},withoutExtension=e=>{const t=e.lastIndexOf(".");return t>0?e.slice(0,t):e},baseNameFor=(e,t)=>withoutExtension(t?.trim()||lastSegment(e))||"image",round1=e=>Math.round(10*e)/10,safeStat=async(e,t)=>{try{return await e.stat(t)}catch{return null}},safeRemove=async(e,t)=>{try{await e.remove(t)}catch{}},renderWithin=async(e,t,i,a,r)=>{const n=Math.max(t.width,t.height)>i?t.width>=t.height?{width:i}:{height:i}:{},o=await t.encode({...n,quality:a,format:r}),s=await safeStat(e,o.uri);if(s&&(!s.exists||0===s.size))throw await safeRemove(e,o.uri),new ImageUnreadableError(o.uri,"the encoder wrote an empty file");return{...o,size:s?.size??null}},originalResult=(e,t,i,a)=>{const r=extensionOf(lastSegment(e))||"jpg",n=MIME_BY_EXTENSION[r]??"image/jpeg",o=`${i}.${r}`;return{uri:e,width:a?.width??null,height:a?.height??null,size:t,mimeType:n,extension:r,fileName:o,compressed:!1,quality:null,part:{uri:e,name:o,type:n}}};export const compressImage=async(e,t={})=>{if(!e)throw new ImageUnreadableError(e,"no uri");const i=resolveBackend(),{maxSize:a=DEFAULT_MAX_SIZE,output:r="jpg",maxBytes:n,minQuality:o=DEFAULT_MIN_QUALITY,name:s}=t,l=FORMAT[r]??FORMAT.jpg,m=baseNameFor(e,s),g=await safeStat(i,e);if(g&&!g.exists)throw new ImageUnreadableError(e,"the file does not exist");if(g&&0===g.size)throw new ImageUnreadableError(e,"the file is empty");const h=g?.size??t.fileSize??null;let c;try{c=await i.open(e)}catch(t){throw Logger.error("[imageCompression] could not decode the image",t),new ImageUnreadableError(e,"the photo could not be decoded")}try{let e=t.quality??qualityForFileSize(h),s=a
|
|
1
|
+
"use strict";import{File}from"expo-file-system";import{ImageManipulator}from"expo-image-manipulator";import{Logger}from"../logger/index.js";export class ImageUnreadableError extends Error{constructor(e,t){super(`Image is unreadable (${t}): ${e||"<empty uri>"}`),this.name="ImageUnreadableError",this.uri=e}}const release=e=>{try{e?.release?.()}catch{}},expoBackend={stat:async e=>{const t=new File(e);return t.exists?null==t.size?null:{exists:!0,size:t.size}:{exists:!1,size:0}},remove:async e=>{new File(e).delete()},open:async e=>{const t=ImageManipulator.manipulate(e);let i;try{i=await t.renderAsync()}finally{release(t)}return{width:i.width,height:i.height,encode:async({width:e,height:t,quality:a,format:r})=>{const n=ImageManipulator.manipulate(i);let o=null;try{void 0===e&&void 0===t||n.resize({width:e??null,height:t??null}),o=await n.renderAsync();const i=await o.saveAsync({compress:a,format:r});return{uri:i.uri,width:i.width,height:i.height}}finally{release(o),release(n)}},close:()=>release(i)}}};let backend=null;export const setImageCompressorImplementation=e=>{backend=e};const resolveBackend=()=>backend??expoBackend,EIGHT_MB=8388608,THREE_MB=3145728;export const qualityForFileSize=e=>null==e?.7:e>8388608?.6:e>3145728?.7:.8;const DEFAULT_MAX_SIZE=1500,DEFAULT_MIN_QUALITY=.4,QUALITY_STEP=.1,SIZE_STEP=.75,MIN_SIDE=320,FORMAT={jpg:{encode:"jpeg",mimeType:"image/jpeg"},png:{encode:"png",mimeType:"image/png"},webp:{encode:"webp",mimeType:"image/webp"}},MIME_BY_EXTENSION={jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",webp:"image/webp",gif:"image/gif",heic:"image/heic",heif:"image/heif",bmp:"image/bmp",tif:"image/tiff",tiff:"image/tiff"},lastSegment=e=>{const t=e.split(/[?#]/)[0]??"";return decodeURIComponentSafe(t.slice(t.lastIndexOf("/")+1))},decodeURIComponentSafe=e=>{try{return decodeURIComponent(e)}catch{return e}},extensionOf=e=>{const t=e.lastIndexOf(".");return t>0?e.slice(t+1).toLowerCase():""},withoutExtension=e=>{const t=e.lastIndexOf(".");return t>0?e.slice(0,t):e},baseNameFor=(e,t)=>withoutExtension(t?.trim()||lastSegment(e))||"image",round1=e=>Math.round(10*e)/10,safeStat=async(e,t)=>{try{return await e.stat(t)}catch{return null}},safeRemove=async(e,t)=>{try{await e.remove(t)}catch{}},renderWithin=async(e,t,i,a,r)=>{const n=Math.max(t.width,t.height)>i?t.width>=t.height?{width:i}:{height:i}:{},o=await t.encode({...n,quality:a,format:r}),s=await safeStat(e,o.uri);if(s&&(!s.exists||0===s.size))throw await safeRemove(e,o.uri),new ImageUnreadableError(o.uri,"the encoder wrote an empty file");return{...o,size:s?.size??null}},originalResult=(e,t,i,a)=>{const r=extensionOf(lastSegment(e))||"jpg",n=MIME_BY_EXTENSION[r]??"image/jpeg",o=`${i}.${r}`;return{uri:e,width:a?.width??null,height:a?.height??null,size:t,mimeType:n,extension:r,fileName:o,compressed:!1,quality:null,part:{uri:e,name:o,type:n}}};export const compressImage=async(e,t={})=>{if(!e)throw new ImageUnreadableError(e,"no uri");const i=resolveBackend(),{maxSize:a=DEFAULT_MAX_SIZE,output:r="jpg",maxBytes:n,minQuality:o=DEFAULT_MIN_QUALITY,name:s}=t,l=FORMAT[r]??FORMAT.jpg,m=baseNameFor(e,s),g=await safeStat(i,e);if(g&&!g.exists)throw new ImageUnreadableError(e,"the file does not exist");if(g&&0===g.size)throw new ImageUnreadableError(e,"the file is empty");const h=g?.size??t.fileSize??null;let c,p;try{c=await i.open(e)}catch(t){throw Logger.error("[imageCompression] could not decode the image",t),new ImageUnreadableError(e,"the photo could not be decoded")}try{let e=t.quality??qualityForFileSize(h),s=a;if(p=await renderWithin(i,c,s,e,l.encode),void 0!==n)for(;null!==p.size&&p.size>n;){if("png"!==r&&round1(e-.1)>=o)e=round1(e-.1);else{const e=Math.max(p.width,p.height);if(e<=320)break;s=Math.max(320,Math.floor(.75*e))}const t=await renderWithin(i,c,s,e,l.encode);await safeRemove(i,p.uri),p=t}const g=`${m}.${r}`;return{uri:p.uri,width:p.width,height:p.height,size:p.size,mimeType:l.mimeType,extension:r,fileName:g,compressed:!0,quality:"png"===r?null:e,part:{uri:p.uri,name:g,type:l.mimeType}}}catch(t){if(p&&await safeRemove(i,p.uri),t instanceof ImageUnreadableError)throw t;Logger.error("[imageCompression] encoding failed; retrying once as jpeg at quality 0.7",t);try{const e=await renderWithin(i,c,a,.7,"jpeg"),t=`${m}.jpg`;return{uri:e.uri,width:e.width,height:e.height,size:e.size,mimeType:"image/jpeg",extension:"jpg",fileName:t,compressed:!0,quality:.7,part:{uri:e.uri,name:t,type:"image/jpeg"}}}catch(t){if(t instanceof ImageUnreadableError)throw t;Logger.error("[imageCompression] jpeg retry failed",t);if(Math.max(c.width,c.height)<=a&&(void 0===n||null!==h&&h<=n))return originalResult(e,h,m,{width:c.width,height:c.height});throw new ImageUnreadableError(e,"the photo could not be prepared for upload")}}finally{c.close()}};
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
* legacy `error`, `title`, then `Request failed (<status>)`. `title` is last because it is
|
|
13
13
|
* the ProblemDetails category ("Bad Request"), not the curated sentence.
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* the logger
|
|
15
|
+
* Never a `message`: anything that reads like an exception dump, and a 5xx body unless it is
|
|
16
|
+
* coded (not `internal_error`) and its `message` is user-safe. The server's text goes on
|
|
17
|
+
* `detail` for the logger either way.
|
|
18
18
|
*/
|
|
19
19
|
export interface ApiError {
|
|
20
20
|
/** Stable snake_case branch key from the server (`not_found`, `validation_failed`). Branch on it, never render it. */
|
|
@@ -36,14 +36,17 @@ export declare const SERVER_FAILURE_MESSAGE = "Something went wrong on our side.
|
|
|
36
36
|
export declare const OFFLINE_MESSAGE = "You appear to be offline. Check your connection and try again.";
|
|
37
37
|
export declare const TIMEOUT_MESSAGE = "That took too long to respond. Check your connection and try again.";
|
|
38
38
|
export declare const UNEXPECTED_MESSAGE = "Something went wrong. Please try again.";
|
|
39
|
+
export declare const UNREACHABLE_MESSAGE = "We couldn't reach the server. Check your connection and try again.";
|
|
39
40
|
/** `code` for a request the caller cancelled (axios `CanceledError`). Never toasted. */
|
|
40
41
|
export declare const REQUEST_CANCELED_CODE = "request_canceled";
|
|
41
42
|
/** `code` for a response that failed schema validation at the service boundary. */
|
|
42
43
|
export declare const CONTRACT_ERROR_CODE = "contract_error";
|
|
43
44
|
/** `code` for a request that got no answer in time (axios `ECONNABORTED` / `ETIMEDOUT`). `offline` is true. */
|
|
44
45
|
export declare const TIMEOUT_CODE = "timeout";
|
|
45
|
-
/** `code` for a request that
|
|
46
|
+
/** `code` for a request that got no answer from the server (DNS, CORS, a dropped connection, a server that is down). `offline` is true. */
|
|
46
47
|
export declare const NETWORK_CODE = "network";
|
|
48
|
+
/** `code` for a request refused because the device reports no connection. `offline` is true. */
|
|
49
|
+
export declare const OFFLINE_CODE = "offline";
|
|
47
50
|
/** `Items[0].UnitPrice` -> `items[0].unitPrice`; `$.email` (System.Text.Json path) -> `email`. */
|
|
48
51
|
export declare const toFieldPath: (key: string) => string;
|
|
49
52
|
export declare const isApiError: (value: unknown) => value is ApiError;
|
|
@@ -57,8 +60,8 @@ export declare const isNoResponse: (error: unknown) => boolean;
|
|
|
57
60
|
* The sentence a screen shows for a failure: the server's own curated message when it sent one,
|
|
58
61
|
* otherwise the screen's `fallback` ("We couldn't save the project."), otherwise the library's
|
|
59
62
|
* generic sentence. A lost answer always reads as the connection message, because "check your
|
|
60
|
-
* connection" is more accurate than any screen's fallback. A 5xx
|
|
61
|
-
* with no usable sentence count as "the server said nothing usable".
|
|
63
|
+
* connection" is more accurate than any screen's fallback. A 5xx without a curated sentence, a
|
|
64
|
+
* cancelled request and a body with no usable sentence count as "the server said nothing usable".
|
|
62
65
|
*
|
|
63
66
|
* `fallback: null` is the "say nothing" mode, for a screen that shows only what the server
|
|
64
67
|
* curated (a live panel that keeps its last result): it returns null when the server said
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export type { ApiError } from './extractApiError';
|
|
2
|
-
export { apiErrorMessage, CONTRACT_ERROR_CODE, extractApiError, isApiError, isNoResponse, NETWORK_CODE, OFFLINE_MESSAGE, REQUEST_CANCELED_CODE, SERVER_FAILURE_MESSAGE, TIMEOUT_CODE, TIMEOUT_MESSAGE, toFieldPath, UNEXPECTED_MESSAGE } from './extractApiError';
|
|
2
|
+
export { apiErrorMessage, CONTRACT_ERROR_CODE, extractApiError, isApiError, isNoResponse, NETWORK_CODE, OFFLINE_CODE, OFFLINE_MESSAGE, REQUEST_CANCELED_CODE, SERVER_FAILURE_MESSAGE, TIMEOUT_CODE, TIMEOUT_MESSAGE, toFieldPath, UNEXPECTED_MESSAGE, UNREACHABLE_MESSAGE } from './extractApiError';
|
|
3
3
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* `signDisplay`, ...), passed through to Intl.NumberFormat.
|
|
4
4
|
*/
|
|
5
5
|
export interface FormatCurrencyOptions extends Omit<Intl.NumberFormatOptions, 'style' | 'currency'> {
|
|
6
|
-
/** ISO 4217 currency code.
|
|
6
|
+
/** ISO 4217 currency code. A missing or empty one warns and prints INR. */
|
|
7
7
|
currency?: string;
|
|
8
8
|
/** BCP-47 locale tag. Defaults to the configured locale, else `'en-IN'` (Indian numbering system). */
|
|
9
9
|
locale?: string;
|
|
@@ -20,9 +20,10 @@ export interface FormatCurrencyOptions extends Omit<Intl.NumberFormatOptions, 's
|
|
|
20
20
|
empty?: string;
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
|
-
* Format an amount as currency.
|
|
24
|
-
*
|
|
25
|
-
*
|
|
23
|
+
* Format an amount as currency. Pass an ISO 4217 code: a missing or empty one warns and
|
|
24
|
+
* prints **INR** with Indian numbering (`en-IN`), the convention across Webority projects.
|
|
25
|
+
* With `{ currency: 'INR' }`: "₹1,50,000" for 150000, "₹1,234.50" for 1234.5, and `''` for a
|
|
26
|
+
* missing amount.
|
|
26
27
|
* Once the app configures a locale (`configureFormatters({ locale })`), the amount is
|
|
27
28
|
* written in that locale and a missing amount prints the configured placeholder ("-").
|
|
28
29
|
* A configured `numberStyle` sets the decimal mark and group separator; the locale keeps
|
|
@@ -31,11 +32,11 @@ export interface FormatCurrencyOptions extends Omit<Intl.NumberFormatOptions, 's
|
|
|
31
32
|
* use `formatMinor`.
|
|
32
33
|
*
|
|
33
34
|
* @example
|
|
34
|
-
* formatCurrency(150000)
|
|
35
|
-
* formatCurrency(1234.5)
|
|
36
|
-
* formatCurrency(99, { currency: 'USD', locale: 'en-US' })
|
|
37
|
-
* formatCurrency(150000, { stripSymbol: true })
|
|
38
|
-
* formatCurrency(1500000, { notation: 'compact' })
|
|
35
|
+
* formatCurrency(150000, { currency: 'INR' }) // ₹1,50,000
|
|
36
|
+
* formatCurrency(1234.5, { currency: 'INR' }) // ₹1,234.50
|
|
37
|
+
* formatCurrency(99, { currency: 'USD', locale: 'en-US' }) // $99
|
|
38
|
+
* formatCurrency(150000, { currency: 'INR', stripSymbol: true }) // 1,50,000
|
|
39
|
+
* formatCurrency(1500000, { currency: 'INR', notation: 'compact' }) // ₹15L
|
|
39
40
|
*/
|
|
40
41
|
export declare const formatCurrency: (amount: number | string | null | undefined, options?: FormatCurrencyOptions) => string;
|
|
41
42
|
//# sourceMappingURL=currency.d.ts.map
|
|
@@ -1,37 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The house formatters: DD-MMM-YYYY dates, IST by default or the viewer's own
|
|
3
|
-
* zone with a label, Indian digit grouping by default, money in minor units,
|
|
4
|
-
* and one empty placeholder. Byte-identical in @webority/mobile-core and
|
|
5
|
-
* @webority/web-core.
|
|
6
|
-
*
|
|
7
|
-
* Same output on Hermes and in every browser is the design constraint, so the
|
|
8
|
-
* text is assembled here rather than by Intl. Engines disagree on month
|
|
9
|
-
* abbreviations ("Sep" or "Sept"), on the space before AM/PM (a plain or a
|
|
10
|
-
* narrow no-break space), on zone names ("IST" or "GMT+5:30") and on which
|
|
11
|
-
* Intl options exist at all (Hermes has no RelativeTimeFormat and no
|
|
12
|
-
* trailingZeroDisplay). IST and the viewer's zone are therefore computed with
|
|
13
|
-
* plain Date arithmetic; Intl is used only to read the wall clock of an
|
|
14
|
-
* explicitly named IANA zone, through formatToParts with numeric fields, to
|
|
15
|
-
* read the locale's 12- or 24-hour clock, and where a caller asks for Intl
|
|
16
|
-
* output itself (Intl number options, `formatRelative({ intl: true })`).
|
|
17
|
-
*
|
|
18
|
-
* Called with no options (house defaults: zone IST, locale en-IN, empty "-"):
|
|
19
|
-
* - formatDate(v) "08-Apr-2026"; formatTime(v) "8:05 PM"; formatDateTime(v) "08-Apr-2026, 8:05 PM";
|
|
20
|
-
* - formatRelative(v) "5 minutes ago", "in 3 days", "just now";
|
|
21
|
-
* - formatNumber(v) "1,23,456.789" (Indian grouping, up to 3 decimals);
|
|
22
|
-
* - formatPercent(0.42) "42%"; formatMinor(15000000) "₹1,50,000.00";
|
|
23
|
-
* - a missing or unreadable value "-".
|
|
24
|
-
*
|
|
25
|
-
* With a configured locale (`configureFormatters({ locale })`), the words and separators come
|
|
26
|
-
* from Intl for that locale and the date order stays DD-MMM-YYYY. For en-IN in the viewer zone:
|
|
27
|
-
* "08-Sept-2026", "08:05 pm", "08-Sept-2026, 08:05 pm IST", "yesterday", "12,34,567.891",
|
|
28
|
-
* "₹1,50,000". For de-DE: "08-Sept-2026", "20:05", "1.234.567,891".
|
|
29
|
-
*
|
|
30
|
-
* A configured `numberStyle` puts its decimal mark and group separator in every number and amount,
|
|
31
|
-
* whichever path wrote it: "1.23.456,789" for `'CommaWithPointGroups'` with the house text. A
|
|
32
|
-
* configured `dateOrder` other than DD-MMM-YYYY writes the date as digits from the wall clock,
|
|
33
|
-
* in every locale: "08/04/2026", "04/08/2026", "2026-04-08".
|
|
34
|
-
*/
|
|
35
1
|
import { type NumberGrouping, type ZoneMode } from './formatConfig';
|
|
36
2
|
/** A date the house formatters accept; null, undefined and "" read as missing. */
|
|
37
3
|
export type DateValue = Date | string | number | null | undefined;
|
|
@@ -96,7 +62,7 @@ export interface FormatPercentOptions extends FormatNumberOptions {
|
|
|
96
62
|
scale?: 'ratio' | 'percent';
|
|
97
63
|
}
|
|
98
64
|
export interface FormatMinorOptions {
|
|
99
|
-
/** ISO 4217 code.
|
|
65
|
+
/** ISO 4217 code. A missing or empty one warns and prints INR. */
|
|
100
66
|
currency?: string;
|
|
101
67
|
/** Defaults to Indian grouping for INR and international grouping for every other currency. */
|
|
102
68
|
grouping?: NumberGrouping;
|
|
@@ -138,7 +104,7 @@ export declare const formatNumber: (value: number | string | null | undefined, o
|
|
|
138
104
|
export declare const formatPercent: (value: number | string | null | undefined, options?: FormatPercentOptions) => string;
|
|
139
105
|
/**
|
|
140
106
|
* Money held in minor units (paise, cents), which is how every API carries
|
|
141
|
-
* it: formatMinor(15000000) gives "₹1,50,000.00". The split into whole and
|
|
107
|
+
* it: formatMinor(15000000, { currency: 'INR' }) gives "₹1,50,000.00". The split into whole and
|
|
142
108
|
* fractional units is done on the integer's digits, so no floating-point
|
|
143
109
|
* division can shift a paisa. A fractional input is rounded to the nearest
|
|
144
110
|
* minor unit. A currency without a known symbol prints its code: "AED 1,000.00".
|
|
@@ -52,15 +52,15 @@ export { getConfig, getConfigValue, resetConfig, setConfig } from './config';
|
|
|
52
52
|
export type { DeepLinkConfig, DeepLinkRoute, ParsedLink } from './deepLink';
|
|
53
53
|
export { DeepLink, parseDeepLink, useDeepLink } from './deepLink';
|
|
54
54
|
export type { ApiError } from './errors';
|
|
55
|
-
export { apiErrorMessage, CONTRACT_ERROR_CODE, extractApiError, isApiError, isNoResponse, NETWORK_CODE, OFFLINE_MESSAGE, REQUEST_CANCELED_CODE, SERVER_FAILURE_MESSAGE, TIMEOUT_CODE, TIMEOUT_MESSAGE, toFieldPath, UNEXPECTED_MESSAGE } from './errors';
|
|
55
|
+
export { apiErrorMessage, CONTRACT_ERROR_CODE, extractApiError, isApiError, isNoResponse, NETWORK_CODE, OFFLINE_CODE, OFFLINE_MESSAGE, REQUEST_CANCELED_CODE, SERVER_FAILURE_MESSAGE, TIMEOUT_CODE, TIMEOUT_MESSAGE, toFieldPath, UNEXPECTED_MESSAGE, UNREACHABLE_MESSAGE } from './errors';
|
|
56
56
|
export type { DateInput, DateOrder, DateStyle, DateValue, FormatCurrencyOptions, FormatDateOptions, FormatDateTimeOptions, FormatMinorOptions, FormatNumberOptions, FormatPercentOptions, FormatPhoneOptions, FormatRelativeOptions, FormatTimeOptions, FormatterConfig, NumberGrouping, NumberStyle, PhoneCountry, PhoneCountryEntry, PhoneNumber, PhoneNumberType, PhoneParts, PhoneValidationKind, TimeStyle, ZoneMode, ZoneOption } from './formatters';
|
|
57
57
|
export { AsYouType, addDays, clampNationalNumber, configureFormatters, countryFlagEmoji, detectPhoneCountry, diffMs, formatCompactNumber, formatCurrency, formatDate, formatDateDDMMMYYYY, formatDateISO, formatDatePattern, formatDateTime, formatMinor, formatNumber, formatPercent, formatPhone, formatPhoneAsTyped, formatRelative, formatRelativeTime, formatTime, formatTime12h, formatTimeInWindowsZone, formatTimeInZone, getCountries, getCountryCallingCode, getFormatterConfig, getInitials, getPhoneCountries, isDateAfter, isDateBefore, isSameDay, isSupportedCountry, isValidNationalNumber, isValidPhoneNumber, maxNationalNumberLength, normalizePhone, parsePhoneNumber, parsePhoneNumberFromString, splitPhoneNumber, startOfDay, validatePhoneNumberLength } from './formatters';
|
|
58
58
|
export type { InitConfig } from './initSDK';
|
|
59
59
|
export { initWebority } from './initSDK';
|
|
60
60
|
export type { TokenRefreshConfig, TokenRefreshResult, UserAuthOptions } from './initUserAuth';
|
|
61
61
|
export { initUserAuth } from './initUserAuth';
|
|
62
|
-
export type { DeviceContext, LoggerOptions, LoggerType, RemoteLogger, RequestFailedContext } from './logger';
|
|
63
|
-
export { configureLogger, getDeviceContext, Logger, setDeviceContext
|
|
62
|
+
export type { DeviceContext, LoggerBreadcrumb, LoggerOptions, LoggerType, RemoteLogger, RequestFailedContext } from './logger';
|
|
63
|
+
export { configureLogger, getDeviceContext, Logger, setDeviceContext } from './logger';
|
|
64
64
|
export type { CrashlyticsModule } from './logger/crashlytics';
|
|
65
65
|
export { crashlyticsRemoteLogger } from './logger/crashlytics';
|
|
66
66
|
export type { NetworkConnectionType, NetworkStatus, NetworkStatusLike, NetworkStatusListener } from './network';
|
|
@@ -4,11 +4,17 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { type KeychainLike } from './auth/authStore';
|
|
6
6
|
import { type Environment, type LogLevel } from './config';
|
|
7
|
+
import { type RemoteLogger } from './logger';
|
|
7
8
|
import { type NetInfoLike } from './network/networkStatus';
|
|
8
9
|
import { setStorageImplementation } from './storage';
|
|
9
10
|
export interface InitConfig {
|
|
10
11
|
/** Your API base URL (required) */
|
|
11
12
|
baseUrl: string;
|
|
13
|
+
/**
|
|
14
|
+
* Where errors and breadcrumbs go: a RemoteLogger adapter (e.g. `crashlyticsRemoteLogger`), or
|
|
15
|
+
* `null` for a build without crash reporting. Required: omitting it throws at start-up.
|
|
16
|
+
*/
|
|
17
|
+
remoteLogger: RemoteLogger | null;
|
|
12
18
|
/** Enable/disable logging. Defaults to `true`. */
|
|
13
19
|
loggingEnabled?: boolean;
|
|
14
20
|
/** Log level. Defaults to `'info'` in development and `'warn'` in a release build. */
|
|
@@ -68,13 +74,15 @@ export interface InitConfig {
|
|
|
68
74
|
* @example
|
|
69
75
|
* // Basic setup
|
|
70
76
|
* initWebority({
|
|
71
|
-
* baseUrl: 'https://api.myapp.com'
|
|
77
|
+
* baseUrl: 'https://api.myapp.com',
|
|
78
|
+
* remoteLogger: null
|
|
72
79
|
* });
|
|
73
80
|
*
|
|
74
81
|
* @example
|
|
75
82
|
* // Full configuration
|
|
76
83
|
* initWebority({
|
|
77
84
|
* baseUrl: 'https://api.myapp.com',
|
|
85
|
+
* remoteLogger: crashlyticsRemoteLogger(crashlytics),
|
|
78
86
|
* loggingEnabled: __DEV__,
|
|
79
87
|
* logLevel: 'warn',
|
|
80
88
|
* environment: __DEV__ ? 'Development' : 'Production'
|
|
@@ -12,13 +12,13 @@ export interface CrashlyticsModule<Instance = unknown> {
|
|
|
12
12
|
setAttribute: (crashlytics: Instance, name: string, value: string) => Promise<unknown>;
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
|
-
* Builds the `
|
|
15
|
+
* Builds the `remoteLogger` adapter for Firebase Crashlytics, passed to `initWebority`:
|
|
16
16
|
*
|
|
17
17
|
* ```ts
|
|
18
18
|
* import * as crashlytics from '@react-native-firebase/crashlytics';
|
|
19
|
-
* import { crashlyticsRemoteLogger,
|
|
19
|
+
* import { crashlyticsRemoteLogger, initWebority } from '@webority/mobile-core';
|
|
20
20
|
*
|
|
21
|
-
*
|
|
21
|
+
* initWebority({ baseUrl, remoteLogger: crashlyticsRemoteLogger(crashlytics) });
|
|
22
22
|
* ```
|
|
23
23
|
*
|
|
24
24
|
* Breadcrumbs and log lines become Crashlytics log lines, errors become
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type LogLevel } from '../config';
|
|
2
2
|
type LogFn = (message: string, ...optionalParams: unknown[]) => void;
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
* and breadcrumbs to Crashlytics, Sentry, Bugsnag, etc
|
|
4
|
+
* Remote sink. Apps pass one to `initWebority({ remoteLogger })` to forward errors
|
|
5
|
+
* and breadcrumbs to Crashlytics, Sentry, Bugsnag, etc., without making the
|
|
6
6
|
* library depend on any of them.
|
|
7
7
|
*
|
|
8
8
|
* Every method is optional; the library calls only what the adapter implements.
|
|
@@ -20,10 +20,7 @@ export interface RemoteLogger {
|
|
|
20
20
|
/** Forward a leveled console message. */
|
|
21
21
|
log?: (level: LogLevel, message: string, optionalParams: unknown[]) => void;
|
|
22
22
|
}
|
|
23
|
-
/**
|
|
24
|
-
* Inject a remote logger adapter (e.g. Firebase Crashlytics, Sentry).
|
|
25
|
-
* Pass `null` to detach. Safe to call before or after `initWebority`.
|
|
26
|
-
*/
|
|
23
|
+
/** Set by `initWebority`; `null` detaches. Not part of the package's public API. */
|
|
27
24
|
export declare const setRemoteLogger: (impl: RemoteLogger | null) => void;
|
|
28
25
|
/**
|
|
29
26
|
* Device / app context attached once at boot (platform, OS version, app version,
|
|
@@ -47,6 +44,12 @@ export interface DeviceContext {
|
|
|
47
44
|
*/
|
|
48
45
|
export declare const setDeviceContext: (context: DeviceContext | null) => void;
|
|
49
46
|
export declare const getDeviceContext: () => DeviceContext | null;
|
|
47
|
+
export interface LoggerBreadcrumb {
|
|
48
|
+
message: string;
|
|
49
|
+
data?: Record<string, unknown>;
|
|
50
|
+
/** When it was recorded, ISO 8601 in UTC. */
|
|
51
|
+
timestampUtc: string;
|
|
52
|
+
}
|
|
50
53
|
/** Tuning knobs for screen history and duplicate-error suppression. */
|
|
51
54
|
export interface LoggerOptions {
|
|
52
55
|
/** Identical errors within this window are collapsed. Default 5000ms. 0 disables dedupe. */
|
|
@@ -55,8 +58,15 @@ export interface LoggerOptions {
|
|
|
55
58
|
dedupeMaxPerWindow?: number;
|
|
56
59
|
/** Cap on tracked screen history. Default 20. */
|
|
57
60
|
screenHistoryLimit?: number;
|
|
61
|
+
/** Extra keys masked in logs, added to the built-in list. */
|
|
62
|
+
redactKeys?: string[];
|
|
63
|
+
/** Breadcrumbs kept for Logger.getBreadcrumbs(). Default 50. */
|
|
64
|
+
breadcrumbLimit?: number;
|
|
58
65
|
}
|
|
59
|
-
/**
|
|
66
|
+
/**
|
|
67
|
+
* Configure dedupe, screen history, breadcrumbs and redaction. Merges with current options;
|
|
68
|
+
* a `redactKeys` list replaces the app keys given before, and omitting it keeps them.
|
|
69
|
+
*/
|
|
60
70
|
export declare const configureLogger: (options: LoggerOptions) => void;
|
|
61
71
|
/** What `Logger.requestFailed` is told about the call beside the error itself. */
|
|
62
72
|
export interface RequestFailedContext {
|
|
@@ -76,7 +86,7 @@ export interface LoggerType {
|
|
|
76
86
|
info: LogFn;
|
|
77
87
|
warn: LogFn;
|
|
78
88
|
error: LogFn;
|
|
79
|
-
/** Append a breadcrumb to the remote sink (and log at info level). */
|
|
89
|
+
/** Append a breadcrumb to the ring and the remote sink (and log at info level). Data is redacted before it is kept. */
|
|
80
90
|
breadcrumb: (message: string, data?: Record<string, unknown>) => void;
|
|
81
91
|
/** Record a non-fatal error with optional context. */
|
|
82
92
|
recordError: (error: unknown, context?: Record<string, unknown>) => void;
|
|
@@ -92,6 +102,8 @@ export interface LoggerType {
|
|
|
92
102
|
getCurrentScreen: () => string | null;
|
|
93
103
|
/** Bounded history of tracked screen names, oldest first. */
|
|
94
104
|
getScreenHistory: () => string[];
|
|
105
|
+
/** Copy of the breadcrumb ring, oldest first, capped at configureLogger({ breadcrumbLimit }). */
|
|
106
|
+
getBreadcrumbs: () => LoggerBreadcrumb[];
|
|
95
107
|
}
|
|
96
108
|
export declare const Logger: LoggerType;
|
|
97
109
|
export {};
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** Rebuilds the masking rules from the built-in list plus `extraKeys`. Replaces any earlier extras. */
|
|
2
|
+
export declare const configureRedaction: (extraKeys: readonly string[]) => void;
|
|
1
3
|
export declare const redactString: (input: string) => string;
|
|
2
4
|
/** Returns a copy with secrets masked; Errors are masked in place so identity and stack survive. */
|
|
3
5
|
export declare const redact: <T>(value: T) => T;
|
|
@@ -14,6 +14,8 @@ export interface PushDevice {
|
|
|
14
14
|
* (the simulator), so the server falls back to its own setting.
|
|
15
15
|
*/
|
|
16
16
|
apnsEnvironment: PushApnsEnvironment | null;
|
|
17
|
+
/** The device's IANA time zone, such as `Europe/London`. Null when the platform cannot name it. */
|
|
18
|
+
timeZone: string | null;
|
|
17
19
|
}
|
|
18
20
|
/** A notification that arrived while the app was open. */
|
|
19
21
|
export interface PushReceived {
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
* legacy `error`, `title`, then `Request failed (<status>)`. `title` is last because it is
|
|
13
13
|
* the ProblemDetails category ("Bad Request"), not the curated sentence.
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* the logger
|
|
15
|
+
* Never a `message`: anything that reads like an exception dump, and a 5xx body unless it is
|
|
16
|
+
* coded (not `internal_error`) and its `message` is user-safe. The server's text goes on
|
|
17
|
+
* `detail` for the logger either way.
|
|
18
18
|
*/
|
|
19
19
|
export interface ApiError {
|
|
20
20
|
/** Stable snake_case branch key from the server (`not_found`, `validation_failed`). Branch on it, never render it. */
|
|
@@ -36,14 +36,17 @@ export declare const SERVER_FAILURE_MESSAGE = "Something went wrong on our side.
|
|
|
36
36
|
export declare const OFFLINE_MESSAGE = "You appear to be offline. Check your connection and try again.";
|
|
37
37
|
export declare const TIMEOUT_MESSAGE = "That took too long to respond. Check your connection and try again.";
|
|
38
38
|
export declare const UNEXPECTED_MESSAGE = "Something went wrong. Please try again.";
|
|
39
|
+
export declare const UNREACHABLE_MESSAGE = "We couldn't reach the server. Check your connection and try again.";
|
|
39
40
|
/** `code` for a request the caller cancelled (axios `CanceledError`). Never toasted. */
|
|
40
41
|
export declare const REQUEST_CANCELED_CODE = "request_canceled";
|
|
41
42
|
/** `code` for a response that failed schema validation at the service boundary. */
|
|
42
43
|
export declare const CONTRACT_ERROR_CODE = "contract_error";
|
|
43
44
|
/** `code` for a request that got no answer in time (axios `ECONNABORTED` / `ETIMEDOUT`). `offline` is true. */
|
|
44
45
|
export declare const TIMEOUT_CODE = "timeout";
|
|
45
|
-
/** `code` for a request that
|
|
46
|
+
/** `code` for a request that got no answer from the server (DNS, CORS, a dropped connection, a server that is down). `offline` is true. */
|
|
46
47
|
export declare const NETWORK_CODE = "network";
|
|
48
|
+
/** `code` for a request refused because the device reports no connection. `offline` is true. */
|
|
49
|
+
export declare const OFFLINE_CODE = "offline";
|
|
47
50
|
/** `Items[0].UnitPrice` -> `items[0].unitPrice`; `$.email` (System.Text.Json path) -> `email`. */
|
|
48
51
|
export declare const toFieldPath: (key: string) => string;
|
|
49
52
|
export declare const isApiError: (value: unknown) => value is ApiError;
|
|
@@ -57,8 +60,8 @@ export declare const isNoResponse: (error: unknown) => boolean;
|
|
|
57
60
|
* The sentence a screen shows for a failure: the server's own curated message when it sent one,
|
|
58
61
|
* otherwise the screen's `fallback` ("We couldn't save the project."), otherwise the library's
|
|
59
62
|
* generic sentence. A lost answer always reads as the connection message, because "check your
|
|
60
|
-
* connection" is more accurate than any screen's fallback. A 5xx
|
|
61
|
-
* with no usable sentence count as "the server said nothing usable".
|
|
63
|
+
* connection" is more accurate than any screen's fallback. A 5xx without a curated sentence, a
|
|
64
|
+
* cancelled request and a body with no usable sentence count as "the server said nothing usable".
|
|
62
65
|
*
|
|
63
66
|
* `fallback: null` is the "say nothing" mode, for a screen that shows only what the server
|
|
64
67
|
* curated (a live panel that keeps its last result): it returns null when the server said
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export type { ApiError } from './extractApiError.js';
|
|
2
|
-
export { apiErrorMessage, CONTRACT_ERROR_CODE, extractApiError, isApiError, isNoResponse, NETWORK_CODE, OFFLINE_MESSAGE, REQUEST_CANCELED_CODE, SERVER_FAILURE_MESSAGE, TIMEOUT_CODE, TIMEOUT_MESSAGE, toFieldPath, UNEXPECTED_MESSAGE } from './extractApiError.js';
|
|
2
|
+
export { apiErrorMessage, CONTRACT_ERROR_CODE, extractApiError, isApiError, isNoResponse, NETWORK_CODE, OFFLINE_CODE, OFFLINE_MESSAGE, REQUEST_CANCELED_CODE, SERVER_FAILURE_MESSAGE, TIMEOUT_CODE, TIMEOUT_MESSAGE, toFieldPath, UNEXPECTED_MESSAGE, UNREACHABLE_MESSAGE } from './extractApiError.js';
|
|
3
3
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* `signDisplay`, ...), passed through to Intl.NumberFormat.
|
|
4
4
|
*/
|
|
5
5
|
export interface FormatCurrencyOptions extends Omit<Intl.NumberFormatOptions, 'style' | 'currency'> {
|
|
6
|
-
/** ISO 4217 currency code.
|
|
6
|
+
/** ISO 4217 currency code. A missing or empty one warns and prints INR. */
|
|
7
7
|
currency?: string;
|
|
8
8
|
/** BCP-47 locale tag. Defaults to the configured locale, else `'en-IN'` (Indian numbering system). */
|
|
9
9
|
locale?: string;
|
|
@@ -20,9 +20,10 @@ export interface FormatCurrencyOptions extends Omit<Intl.NumberFormatOptions, 's
|
|
|
20
20
|
empty?: string;
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
|
-
* Format an amount as currency.
|
|
24
|
-
*
|
|
25
|
-
*
|
|
23
|
+
* Format an amount as currency. Pass an ISO 4217 code: a missing or empty one warns and
|
|
24
|
+
* prints **INR** with Indian numbering (`en-IN`), the convention across Webority projects.
|
|
25
|
+
* With `{ currency: 'INR' }`: "₹1,50,000" for 150000, "₹1,234.50" for 1234.5, and `''` for a
|
|
26
|
+
* missing amount.
|
|
26
27
|
* Once the app configures a locale (`configureFormatters({ locale })`), the amount is
|
|
27
28
|
* written in that locale and a missing amount prints the configured placeholder ("-").
|
|
28
29
|
* A configured `numberStyle` sets the decimal mark and group separator; the locale keeps
|
|
@@ -31,11 +32,11 @@ export interface FormatCurrencyOptions extends Omit<Intl.NumberFormatOptions, 's
|
|
|
31
32
|
* use `formatMinor`.
|
|
32
33
|
*
|
|
33
34
|
* @example
|
|
34
|
-
* formatCurrency(150000)
|
|
35
|
-
* formatCurrency(1234.5)
|
|
36
|
-
* formatCurrency(99, { currency: 'USD', locale: 'en-US' })
|
|
37
|
-
* formatCurrency(150000, { stripSymbol: true })
|
|
38
|
-
* formatCurrency(1500000, { notation: 'compact' })
|
|
35
|
+
* formatCurrency(150000, { currency: 'INR' }) // ₹1,50,000
|
|
36
|
+
* formatCurrency(1234.5, { currency: 'INR' }) // ₹1,234.50
|
|
37
|
+
* formatCurrency(99, { currency: 'USD', locale: 'en-US' }) // $99
|
|
38
|
+
* formatCurrency(150000, { currency: 'INR', stripSymbol: true }) // 1,50,000
|
|
39
|
+
* formatCurrency(1500000, { currency: 'INR', notation: 'compact' }) // ₹15L
|
|
39
40
|
*/
|
|
40
41
|
export declare const formatCurrency: (amount: number | string | null | undefined, options?: FormatCurrencyOptions) => string;
|
|
41
42
|
//# sourceMappingURL=currency.d.ts.map
|
|
@@ -1,37 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The house formatters: DD-MMM-YYYY dates, IST by default or the viewer's own
|
|
3
|
-
* zone with a label, Indian digit grouping by default, money in minor units,
|
|
4
|
-
* and one empty placeholder. Byte-identical in @webority/mobile-core and
|
|
5
|
-
* @webority/web-core.
|
|
6
|
-
*
|
|
7
|
-
* Same output on Hermes and in every browser is the design constraint, so the
|
|
8
|
-
* text is assembled here rather than by Intl. Engines disagree on month
|
|
9
|
-
* abbreviations ("Sep" or "Sept"), on the space before AM/PM (a plain or a
|
|
10
|
-
* narrow no-break space), on zone names ("IST" or "GMT+5:30") and on which
|
|
11
|
-
* Intl options exist at all (Hermes has no RelativeTimeFormat and no
|
|
12
|
-
* trailingZeroDisplay). IST and the viewer's zone are therefore computed with
|
|
13
|
-
* plain Date arithmetic; Intl is used only to read the wall clock of an
|
|
14
|
-
* explicitly named IANA zone, through formatToParts with numeric fields, to
|
|
15
|
-
* read the locale's 12- or 24-hour clock, and where a caller asks for Intl
|
|
16
|
-
* output itself (Intl number options, `formatRelative({ intl: true })`).
|
|
17
|
-
*
|
|
18
|
-
* Called with no options (house defaults: zone IST, locale en-IN, empty "-"):
|
|
19
|
-
* - formatDate(v) "08-Apr-2026"; formatTime(v) "8:05 PM"; formatDateTime(v) "08-Apr-2026, 8:05 PM";
|
|
20
|
-
* - formatRelative(v) "5 minutes ago", "in 3 days", "just now";
|
|
21
|
-
* - formatNumber(v) "1,23,456.789" (Indian grouping, up to 3 decimals);
|
|
22
|
-
* - formatPercent(0.42) "42%"; formatMinor(15000000) "₹1,50,000.00";
|
|
23
|
-
* - a missing or unreadable value "-".
|
|
24
|
-
*
|
|
25
|
-
* With a configured locale (`configureFormatters({ locale })`), the words and separators come
|
|
26
|
-
* from Intl for that locale and the date order stays DD-MMM-YYYY. For en-IN in the viewer zone:
|
|
27
|
-
* "08-Sept-2026", "08:05 pm", "08-Sept-2026, 08:05 pm IST", "yesterday", "12,34,567.891",
|
|
28
|
-
* "₹1,50,000". For de-DE: "08-Sept-2026", "20:05", "1.234.567,891".
|
|
29
|
-
*
|
|
30
|
-
* A configured `numberStyle` puts its decimal mark and group separator in every number and amount,
|
|
31
|
-
* whichever path wrote it: "1.23.456,789" for `'CommaWithPointGroups'` with the house text. A
|
|
32
|
-
* configured `dateOrder` other than DD-MMM-YYYY writes the date as digits from the wall clock,
|
|
33
|
-
* in every locale: "08/04/2026", "04/08/2026", "2026-04-08".
|
|
34
|
-
*/
|
|
35
1
|
import { type NumberGrouping, type ZoneMode } from './formatConfig.js';
|
|
36
2
|
/** A date the house formatters accept; null, undefined and "" read as missing. */
|
|
37
3
|
export type DateValue = Date | string | number | null | undefined;
|
|
@@ -96,7 +62,7 @@ export interface FormatPercentOptions extends FormatNumberOptions {
|
|
|
96
62
|
scale?: 'ratio' | 'percent';
|
|
97
63
|
}
|
|
98
64
|
export interface FormatMinorOptions {
|
|
99
|
-
/** ISO 4217 code.
|
|
65
|
+
/** ISO 4217 code. A missing or empty one warns and prints INR. */
|
|
100
66
|
currency?: string;
|
|
101
67
|
/** Defaults to Indian grouping for INR and international grouping for every other currency. */
|
|
102
68
|
grouping?: NumberGrouping;
|
|
@@ -138,7 +104,7 @@ export declare const formatNumber: (value: number | string | null | undefined, o
|
|
|
138
104
|
export declare const formatPercent: (value: number | string | null | undefined, options?: FormatPercentOptions) => string;
|
|
139
105
|
/**
|
|
140
106
|
* Money held in minor units (paise, cents), which is how every API carries
|
|
141
|
-
* it: formatMinor(15000000) gives "₹1,50,000.00". The split into whole and
|
|
107
|
+
* it: formatMinor(15000000, { currency: 'INR' }) gives "₹1,50,000.00". The split into whole and
|
|
142
108
|
* fractional units is done on the integer's digits, so no floating-point
|
|
143
109
|
* division can shift a paisa. A fractional input is rounded to the nearest
|
|
144
110
|
* minor unit. A currency without a known symbol prints its code: "AED 1,000.00".
|
|
@@ -52,15 +52,15 @@ export { getConfig, getConfigValue, resetConfig, setConfig } from './config/inde
|
|
|
52
52
|
export type { DeepLinkConfig, DeepLinkRoute, ParsedLink } from './deepLink/index.js';
|
|
53
53
|
export { DeepLink, parseDeepLink, useDeepLink } from './deepLink/index.js';
|
|
54
54
|
export type { ApiError } from './errors/index.js';
|
|
55
|
-
export { apiErrorMessage, CONTRACT_ERROR_CODE, extractApiError, isApiError, isNoResponse, NETWORK_CODE, OFFLINE_MESSAGE, REQUEST_CANCELED_CODE, SERVER_FAILURE_MESSAGE, TIMEOUT_CODE, TIMEOUT_MESSAGE, toFieldPath, UNEXPECTED_MESSAGE } from './errors/index.js';
|
|
55
|
+
export { apiErrorMessage, CONTRACT_ERROR_CODE, extractApiError, isApiError, isNoResponse, NETWORK_CODE, OFFLINE_CODE, OFFLINE_MESSAGE, REQUEST_CANCELED_CODE, SERVER_FAILURE_MESSAGE, TIMEOUT_CODE, TIMEOUT_MESSAGE, toFieldPath, UNEXPECTED_MESSAGE, UNREACHABLE_MESSAGE } from './errors/index.js';
|
|
56
56
|
export type { DateInput, DateOrder, DateStyle, DateValue, FormatCurrencyOptions, FormatDateOptions, FormatDateTimeOptions, FormatMinorOptions, FormatNumberOptions, FormatPercentOptions, FormatPhoneOptions, FormatRelativeOptions, FormatTimeOptions, FormatterConfig, NumberGrouping, NumberStyle, PhoneCountry, PhoneCountryEntry, PhoneNumber, PhoneNumberType, PhoneParts, PhoneValidationKind, TimeStyle, ZoneMode, ZoneOption } from './formatters/index.js';
|
|
57
57
|
export { AsYouType, addDays, clampNationalNumber, configureFormatters, countryFlagEmoji, detectPhoneCountry, diffMs, formatCompactNumber, formatCurrency, formatDate, formatDateDDMMMYYYY, formatDateISO, formatDatePattern, formatDateTime, formatMinor, formatNumber, formatPercent, formatPhone, formatPhoneAsTyped, formatRelative, formatRelativeTime, formatTime, formatTime12h, formatTimeInWindowsZone, formatTimeInZone, getCountries, getCountryCallingCode, getFormatterConfig, getInitials, getPhoneCountries, isDateAfter, isDateBefore, isSameDay, isSupportedCountry, isValidNationalNumber, isValidPhoneNumber, maxNationalNumberLength, normalizePhone, parsePhoneNumber, parsePhoneNumberFromString, splitPhoneNumber, startOfDay, validatePhoneNumberLength } from './formatters/index.js';
|
|
58
58
|
export type { InitConfig } from './initSDK.js';
|
|
59
59
|
export { initWebority } from './initSDK.js';
|
|
60
60
|
export type { TokenRefreshConfig, TokenRefreshResult, UserAuthOptions } from './initUserAuth.js';
|
|
61
61
|
export { initUserAuth } from './initUserAuth.js';
|
|
62
|
-
export type { DeviceContext, LoggerOptions, LoggerType, RemoteLogger, RequestFailedContext } from './logger/index.js';
|
|
63
|
-
export { configureLogger, getDeviceContext, Logger, setDeviceContext
|
|
62
|
+
export type { DeviceContext, LoggerBreadcrumb, LoggerOptions, LoggerType, RemoteLogger, RequestFailedContext } from './logger/index.js';
|
|
63
|
+
export { configureLogger, getDeviceContext, Logger, setDeviceContext } from './logger/index.js';
|
|
64
64
|
export type { CrashlyticsModule } from './logger/crashlytics.js';
|
|
65
65
|
export { crashlyticsRemoteLogger } from './logger/crashlytics.js';
|
|
66
66
|
export type { NetworkConnectionType, NetworkStatus, NetworkStatusLike, NetworkStatusListener } from './network/index.js';
|
|
@@ -4,11 +4,17 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { type KeychainLike } from './auth/authStore.js';
|
|
6
6
|
import { type Environment, type LogLevel } from './config/index.js';
|
|
7
|
+
import { type RemoteLogger } from './logger/index.js';
|
|
7
8
|
import { type NetInfoLike } from './network/networkStatus.js';
|
|
8
9
|
import { setStorageImplementation } from './storage/index.js';
|
|
9
10
|
export interface InitConfig {
|
|
10
11
|
/** Your API base URL (required) */
|
|
11
12
|
baseUrl: string;
|
|
13
|
+
/**
|
|
14
|
+
* Where errors and breadcrumbs go: a RemoteLogger adapter (e.g. `crashlyticsRemoteLogger`), or
|
|
15
|
+
* `null` for a build without crash reporting. Required: omitting it throws at start-up.
|
|
16
|
+
*/
|
|
17
|
+
remoteLogger: RemoteLogger | null;
|
|
12
18
|
/** Enable/disable logging. Defaults to `true`. */
|
|
13
19
|
loggingEnabled?: boolean;
|
|
14
20
|
/** Log level. Defaults to `'info'` in development and `'warn'` in a release build. */
|
|
@@ -68,13 +74,15 @@ export interface InitConfig {
|
|
|
68
74
|
* @example
|
|
69
75
|
* // Basic setup
|
|
70
76
|
* initWebority({
|
|
71
|
-
* baseUrl: 'https://api.myapp.com'
|
|
77
|
+
* baseUrl: 'https://api.myapp.com',
|
|
78
|
+
* remoteLogger: null
|
|
72
79
|
* });
|
|
73
80
|
*
|
|
74
81
|
* @example
|
|
75
82
|
* // Full configuration
|
|
76
83
|
* initWebority({
|
|
77
84
|
* baseUrl: 'https://api.myapp.com',
|
|
85
|
+
* remoteLogger: crashlyticsRemoteLogger(crashlytics),
|
|
78
86
|
* loggingEnabled: __DEV__,
|
|
79
87
|
* logLevel: 'warn',
|
|
80
88
|
* environment: __DEV__ ? 'Development' : 'Production'
|
|
@@ -12,13 +12,13 @@ export interface CrashlyticsModule<Instance = unknown> {
|
|
|
12
12
|
setAttribute: (crashlytics: Instance, name: string, value: string) => Promise<unknown>;
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
|
-
* Builds the `
|
|
15
|
+
* Builds the `remoteLogger` adapter for Firebase Crashlytics, passed to `initWebority`:
|
|
16
16
|
*
|
|
17
17
|
* ```ts
|
|
18
18
|
* import * as crashlytics from '@react-native-firebase/crashlytics';
|
|
19
|
-
* import { crashlyticsRemoteLogger,
|
|
19
|
+
* import { crashlyticsRemoteLogger, initWebority } from '@webority/mobile-core';
|
|
20
20
|
*
|
|
21
|
-
*
|
|
21
|
+
* initWebority({ baseUrl, remoteLogger: crashlyticsRemoteLogger(crashlytics) });
|
|
22
22
|
* ```
|
|
23
23
|
*
|
|
24
24
|
* Breadcrumbs and log lines become Crashlytics log lines, errors become
|