@webority/mobile-core 0.109.8 → 0.110.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 +24 -0
- package/lib/commonjs/metro/createMetroBridge.js +1 -0
- package/lib/commonjs/metro/index.js +1 -0
- package/lib/commonjs/metro/types.js +1 -0
- package/lib/commonjs/push/index.js +1 -1
- package/lib/commonjs/push/push.js +1 -1
- package/lib/commonjs/updates/index.js +1 -1
- package/lib/commonjs/updates/updates.js +1 -1
- package/lib/module/metro/createMetroBridge.js +1 -0
- package/lib/module/metro/index.js +1 -0
- package/lib/module/metro/types.js +1 -0
- package/lib/module/push/index.js +1 -1
- package/lib/module/push/push.js +1 -1
- package/lib/module/updates/index.js +1 -1
- package/lib/module/updates/updates.js +1 -1
- package/lib/typescript/commonjs/metro/createMetroBridge.d.ts +3 -0
- package/lib/typescript/commonjs/metro/index.d.ts +3 -0
- package/lib/typescript/commonjs/metro/types.d.ts +65 -0
- package/lib/typescript/commonjs/push/index.d.ts +1 -1
- package/lib/typescript/commonjs/push/push.d.ts +5 -0
- package/lib/typescript/commonjs/updates/index.d.ts +2 -2
- package/lib/typescript/commonjs/updates/updates.d.ts +15 -0
- package/lib/typescript/module/metro/createMetroBridge.d.ts +3 -0
- package/lib/typescript/module/metro/index.d.ts +3 -0
- package/lib/typescript/module/metro/types.d.ts +65 -0
- package/lib/typescript/module/push/index.d.ts +1 -1
- package/lib/typescript/module/push/push.d.ts +5 -0
- package/lib/typescript/module/updates/index.d.ts +2 -2
- package/lib/typescript/module/updates/updates.d.ts +15 -0
- package/package.json +11 -1
package/README.md
CHANGED
|
@@ -230,3 +230,27 @@ JWT-shaped strings, and the values of keys named `token`, `accessToken`, `refres
|
|
|
230
230
|
`password` and `authorization` (any case) in objects and JSON strings.
|
|
231
231
|
|
|
232
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.
|
|
233
|
+
|
|
234
|
+
## Metro bridge
|
|
235
|
+
|
|
236
|
+
`@webority/mobile-core/metro` builds the development bridge that a Metro dev tool reads as
|
|
237
|
+
`globalThis.__METRO_BRIDGE__`. The app assigns it under `__DEV__`; the builder adds no check of its own.
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
import { createMetroBridge } from '@webority/mobile-core/metro';
|
|
241
|
+
|
|
242
|
+
if (__DEV__) {
|
|
243
|
+
globalThis.__METRO_BRIDGE__ = createMetroBridge({ store, navigationRef, signIn, signOut, isSignedIn });
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
- `store`: the Redux store, read through `getState` and written through `dispatch`.
|
|
248
|
+
- `navigationRef`: the navigation container's ref (`isReady`, `getRootState`, `canGoBack`, `goBack`, `navigate`).
|
|
249
|
+
- `signIn(request)` resolves `{ codeRequired }`; `signOut()` resolves once the session is cleared;
|
|
250
|
+
`isSignedIn()` returns a boolean.
|
|
251
|
+
- `commands` (optional): extra named commands. A name that is `signIn`, `signOut` or `navigate` throws
|
|
252
|
+
when the bridge is created.
|
|
253
|
+
|
|
254
|
+
The bridge exposes `commands` (`signIn`, `signOut`, `navigate`, and the extras), `navigation`
|
|
255
|
+
(`getState`, which is null until the navigator is ready, and `goBack`, which returns `{ wentBack }`),
|
|
256
|
+
`redux`, `auth` (`isSignedIn`) and `push` (`notifyLocal` and `deviceToken`, the device's push token).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.createMetroBridge=void 0;var _index=require("../push/index.js");const RESERVED_COMMANDS=new Set(["signIn","signOut","navigate"]),createMetroBridge=e=>{const{store:t,navigationRef:a,signIn:n,signOut:i,isSignedIn:o}=e;for(const t of Object.keys(e.commands??{}))if(RESERVED_COMMANDS.has(t))throw new Error(`createMetroBridge: the command ${t} is already defined`);return{commands:{...e.commands,signIn:n,signOut:async()=>(await i(),{signedOut:!0}),navigate:({name:e,params:t})=>(a.navigate(e,t),{route:e})},navigation:{getState:()=>a.isReady()?a.getRootState():null,goBack:()=>a.isReady()&&a.canGoBack()?(a.goBack(),{wentBack:!0}):{wentBack:!1}},redux:{getState:()=>t.getState(),dispatch:e=>t.dispatch(e)},auth:{isSignedIn:o},push:{notifyLocal:_index.notifyLocal,deviceToken:_index.readDevicePushToken}}};exports.createMetroBridge=createMetroBridge;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),Object.defineProperty(exports,"createMetroBridge",{enumerable:!0,get:function(){return _createMetroBridge.createMetroBridge}});var _createMetroBridge=require("./createMetroBridge.js");
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),Object.defineProperty(exports,"PUSH_STATUS",{enumerable:!0,get:function(){return _status.PUSH_STATUS}}),Object.defineProperty(exports,"disablePush",{enumerable:!0,get:function(){return _push.disablePush}}),Object.defineProperty(exports,"dismissAllNotifications",{enumerable:!0,get:function(){return _push.dismissAllNotifications}}),Object.defineProperty(exports,"enablePush",{enumerable:!0,get:function(){return _push.enablePush}}),Object.defineProperty(exports,"initPush",{enumerable:!0,get:function(){return _push.initPush}}),Object.defineProperty(exports,"notifyLocal",{enumerable:!0,get:function(){return _push.notifyLocal}}),Object.defineProperty(exports,"onAction",{enumerable:!0,get:function(){return _push.onAction}}),Object.defineProperty(exports,"onReceive",{enumerable:!0,get:function(){return _push.onReceive}}),Object.defineProperty(exports,"resetPush",{enumerable:!0,get:function(){return _push.resetPush}}),Object.defineProperty(exports,"setBadgeCount",{enumerable:!0,get:function(){return _push.setBadgeCount}}),Object.defineProperty(exports,"setCategories",{enumerable:!0,get:function(){return _push.setCategories}}),Object.defineProperty(exports,"setPushImplementation",{enumerable:!0,get:function(){return _push.setPushImplementation}}),Object.defineProperty(exports,"usePush",{enumerable:!0,get:function(){return _push.usePush}});var _push=require("./push.js"),_status=require("./status.js");
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),Object.defineProperty(exports,"PUSH_STATUS",{enumerable:!0,get:function(){return _status.PUSH_STATUS}}),Object.defineProperty(exports,"disablePush",{enumerable:!0,get:function(){return _push.disablePush}}),Object.defineProperty(exports,"dismissAllNotifications",{enumerable:!0,get:function(){return _push.dismissAllNotifications}}),Object.defineProperty(exports,"enablePush",{enumerable:!0,get:function(){return _push.enablePush}}),Object.defineProperty(exports,"initPush",{enumerable:!0,get:function(){return _push.initPush}}),Object.defineProperty(exports,"notifyLocal",{enumerable:!0,get:function(){return _push.notifyLocal}}),Object.defineProperty(exports,"onAction",{enumerable:!0,get:function(){return _push.onAction}}),Object.defineProperty(exports,"onReceive",{enumerable:!0,get:function(){return _push.onReceive}}),Object.defineProperty(exports,"readDevicePushToken",{enumerable:!0,get:function(){return _push.readDevicePushToken}}),Object.defineProperty(exports,"resetPush",{enumerable:!0,get:function(){return _push.resetPush}}),Object.defineProperty(exports,"setBadgeCount",{enumerable:!0,get:function(){return _push.setBadgeCount}}),Object.defineProperty(exports,"setCategories",{enumerable:!0,get:function(){return _push.setCategories}}),Object.defineProperty(exports,"setPushImplementation",{enumerable:!0,get:function(){return _push.setPushImplementation}}),Object.defineProperty(exports,"usePush",{enumerable:!0,get:function(){return _push.usePush}});var _push=require("./push.js"),_status=require("./status.js");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.usePush=exports.toPushReceived=exports.toPushAction=exports.toNotificationActions=exports.setPushImplementation=exports.setCategories=exports.setBadgeCount=exports.routeTaskPayload=exports.resolveModules=exports.resetPush=exports.onReceive=exports.onAction=exports.notifyLocal=exports.initPush=exports.enablePush=exports.dismissAllNotifications=exports.disablePush=void 0;var _react=require("react"),_reactNative=require("react-native"),_index=require("../logger/index.js");let resolved,override=null;const setPushImplementation=e=>{override=e};exports.setPushImplementation=setPushImplementation;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){_index.Logger.warn("[push] expo-notifications is not available; push is off",e),resolved=null}return resolved};exports.resolveModules=resolveModules;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){_index.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),toPushReceived=e=>({notificationId:e.request.identifier,title:e.request.content.title??null,body:e.request.content.body??null,data:notificationData(e)});exports.toPushReceived=toPushReceived;const toPushAction=e=>({actionId:e.actionIdentifier,...e.userText?{userText:e.userText}:{},data:responseData(e),notificationId:e.notification.request.identifier});exports.toPushAction=toPushAction;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}}));exports.toNotificationActions=toNotificationActions;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){_index.Logger.error("[push] onAction threw",e)}}else if(config){routedAt.set(n,Date.now());try{config.onOpen(responseData(t))}catch(e){_index.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=>_index.Logger.error("[push] reading the launch notification failed",e)))},routeTaskPayload=async e=>{const t=resolveModules(),n=e;t&&"string"==typeof n?.actionIdentifier&&"string"==typeof n.notification?.request?.identifier&&await routeResponse(t.notifications,n)};exports.routeTaskPayload=routeTaskPayload;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){_index.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 _index.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()}},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"===_reactNative.Platform.OS&&e.androidChannels?.length&&(channelsReady=createChannels(o,e.androidChannels)),listenForResponses(o),drainPending(o),o.getPermissionsAsync().then(e=>setState({status:toPermissionStatus(e)})).catch(e=>_index.Logger.error("[push] permission check failed",e))};exports.initPush=initPush;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},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=>_index.Logger.error("[push] re-registering a new token failed",e))}),setState({status:"granted",enabled:!0}),"granted"):(setState({status:"unavailable"}),"unavailable")};exports.enablePush=enablePush;const stopFollowingToken=()=>{tokenSubscription?.remove(),tokenSubscription=null,registeredKey=null,setState({enabled:!1})},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()}};exports.disablePush=disablePush;const resetPush=async()=>{stopFollowingToken();const e=resolveModules();if(!e)return;const t=e.notifications.unregisterForNotificationsAsync();tokenDrop=t.catch(()=>{}),await t};exports.resetPush=resetPush;const setBadgeCount=async e=>{const t=resolveModules();t&&await t.notifications.setBadgeCountAsync(e)};exports.setBadgeCount=setBadgeCount;const dismissAllNotifications=async()=>{const e=resolveModules();if(e)try{await e.notifications.dismissAllNotificationsAsync()}catch(e){_index.Logger.warn("[push] dismissing the notifications failed",e)}};exports.dismissAllNotifications=dismissAllNotifications;const setCategories=async e=>{const t=resolveModules();if(t)for(const n of e)await t.notifications.setNotificationCategoryAsync(n.id,toNotificationActions(n.actions))};exports.setCategories=setCategories;const onAction=e=>{actionHandler=e;const t=resolveModules();return t&&(listenForResponses(t.notifications),drainPending(t.notifications)),()=>{actionHandler===e&&(actionHandler=null)}};exports.onAction=onAction;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){_index.Logger.error("[push] onReceive threw",e)}})),()=>{receiveHandlers.delete(e)}};exports.onReceive=onReceive;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})};exports.notifyLocal=notifyLocal;const subscribe=e=>(subscribers.add(e),()=>{subscribers.delete(e)}),getSnapshot=()=>state,enable=async()=>{await enablePush()},usePush=()=>({...(0,_react.useSyncExternalStore)(subscribe,getSnapshot,getSnapshot),enable:enable,disable:disablePush});exports.usePush=usePush;
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.usePush=exports.toPushReceived=exports.toPushAction=exports.toNotificationActions=exports.setPushImplementation=exports.setCategories=exports.setBadgeCount=exports.routeTaskPayload=exports.resolveModules=exports.resetPush=exports.readDevicePushToken=exports.onReceive=exports.onAction=exports.notifyLocal=exports.initPush=exports.enablePush=exports.dismissAllNotifications=exports.disablePush=void 0;var _react=require("react"),_reactNative=require("react-native"),_index=require("../logger/index.js");let resolved,override=null;const setPushImplementation=e=>{override=e};exports.setPushImplementation=setPushImplementation;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){_index.Logger.warn("[push] expo-notifications is not available; push is off",e),resolved=null}return resolved};exports.resolveModules=resolveModules;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){_index.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),toPushReceived=e=>({notificationId:e.request.identifier,title:e.request.content.title??null,body:e.request.content.body??null,data:notificationData(e)});exports.toPushReceived=toPushReceived;const toPushAction=e=>({actionId:e.actionIdentifier,...e.userText?{userText:e.userText}:{},data:responseData(e),notificationId:e.notification.request.identifier});exports.toPushAction=toPushAction;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}}));exports.toNotificationActions=toNotificationActions;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){_index.Logger.error("[push] onAction threw",e)}}else if(config){routedAt.set(n,Date.now());try{config.onOpen(responseData(t))}catch(e){_index.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=>_index.Logger.error("[push] reading the launch notification failed",e)))},routeTaskPayload=async e=>{const t=resolveModules(),n=e;t&&"string"==typeof n?.actionIdentifier&&"string"==typeof n.notification?.request?.identifier&&await routeResponse(t.notifications,n)};exports.routeTaskPayload=routeTaskPayload;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){_index.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 _index.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()}},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"===_reactNative.Platform.OS&&e.androidChannels?.length&&(channelsReady=createChannels(o,e.androidChannels)),listenForResponses(o),drainPending(o),o.getPermissionsAsync().then(e=>setState({status:toPermissionStatus(e)})).catch(e=>_index.Logger.error("[push] permission check failed",e))};exports.initPush=initPush;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},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=>_index.Logger.error("[push] re-registering a new token failed",e))}),setState({status:"granted",enabled:!0}),"granted"):(setState({status:"unavailable"}),"unavailable")};exports.enablePush=enablePush;const readDevicePushToken=async()=>{const e=resolveModules();if(!e)return null;await tokenDrop;if(!(await e.notifications.getPermissionsAsync()).granted)return null;const t=await e.notifications.getDevicePushTokenAsync(),n="android"===t.type?"android":"ios"===t.type?"ios":null;return n&&"string"==typeof t.data?{platform:n,token:t.data}:null};exports.readDevicePushToken=readDevicePushToken;const stopFollowingToken=()=>{tokenSubscription?.remove(),tokenSubscription=null,registeredKey=null,setState({enabled:!1})},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()}};exports.disablePush=disablePush;const resetPush=async()=>{stopFollowingToken();const e=resolveModules();if(!e)return;const t=e.notifications.unregisterForNotificationsAsync();tokenDrop=t.catch(()=>{}),await t};exports.resetPush=resetPush;const setBadgeCount=async e=>{const t=resolveModules();t&&await t.notifications.setBadgeCountAsync(e)};exports.setBadgeCount=setBadgeCount;const dismissAllNotifications=async()=>{const e=resolveModules();if(e)try{await e.notifications.dismissAllNotificationsAsync()}catch(e){_index.Logger.warn("[push] dismissing the notifications failed",e)}};exports.dismissAllNotifications=dismissAllNotifications;const setCategories=async e=>{const t=resolveModules();if(t)for(const n of e)await t.notifications.setNotificationCategoryAsync(n.id,toNotificationActions(n.actions))};exports.setCategories=setCategories;const onAction=e=>{actionHandler=e;const t=resolveModules();return t&&(listenForResponses(t.notifications),drainPending(t.notifications)),()=>{actionHandler===e&&(actionHandler=null)}};exports.onAction=onAction;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){_index.Logger.error("[push] onReceive threw",e)}})),()=>{receiveHandlers.delete(e)}};exports.onReceive=onReceive;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})};exports.notifyLocal=notifyLocal;const subscribe=e=>(subscribers.add(e),()=>{subscribers.delete(e)}),getSnapshot=()=>state,enable=async()=>{await enablePush()},usePush=()=>({...(0,_react.useSyncExternalStore)(subscribe,getSnapshot,getSnapshot),enable:enable,disable:disablePush});exports.usePush=usePush;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),Object.defineProperty(exports,"DEFAULT_MIN_CHECK_INTERVAL_MS",{enumerable:!0,get:function(){return _updates.DEFAULT_MIN_CHECK_INTERVAL_MS}}),Object.defineProperty(exports,"applyOtaUpdate",{enumerable:!0,get:function(){return _updates.applyOtaUpdate}}),Object.defineProperty(exports,"checkForOtaUpdate",{enumerable:!0,get:function(){return _updates.checkForOtaUpdate}}),Object.defineProperty(exports,"initOtaUpdates",{enumerable:!0,get:function(){return _updates.initOtaUpdates}}),Object.defineProperty(exports,"normalizeAppIdentity",{enumerable:!0,get:function(){return _updates.normalizeAppIdentity}}),Object.defineProperty(exports,"setOtaUpdatesImplementation",{enumerable:!0,get:function(){return _updates.setOtaUpdatesImplementation}}),Object.defineProperty(exports,"stopOtaUpdates",{enumerable:!0,get:function(){return _updates.stopOtaUpdates}}),Object.defineProperty(exports,"useOtaUpdates",{enumerable:!0,get:function(){return _updates.useOtaUpdates}});var _updates=require("./updates.js");
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),Object.defineProperty(exports,"DEFAULT_MIN_CHECK_INTERVAL_MS",{enumerable:!0,get:function(){return _updates.DEFAULT_MIN_CHECK_INTERVAL_MS}}),Object.defineProperty(exports,"OTA_LAST_LAUNCH_KEY",{enumerable:!0,get:function(){return _updates.OTA_LAST_LAUNCH_KEY}}),Object.defineProperty(exports,"applyOtaUpdate",{enumerable:!0,get:function(){return _updates.applyOtaUpdate}}),Object.defineProperty(exports,"checkForOtaUpdate",{enumerable:!0,get:function(){return _updates.checkForOtaUpdate}}),Object.defineProperty(exports,"initOtaUpdates",{enumerable:!0,get:function(){return _updates.initOtaUpdates}}),Object.defineProperty(exports,"normalizeAppIdentity",{enumerable:!0,get:function(){return _updates.normalizeAppIdentity}}),Object.defineProperty(exports,"setOtaUpdatesImplementation",{enumerable:!0,get:function(){return _updates.setOtaUpdatesImplementation}}),Object.defineProperty(exports,"stopOtaUpdates",{enumerable:!0,get:function(){return _updates.stopOtaUpdates}}),Object.defineProperty(exports,"useOtaUpdates",{enumerable:!0,get:function(){return _updates.useOtaUpdates}});var _updates=require("./updates.js");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.useOtaUpdates=exports.stopOtaUpdates=exports.setOtaUpdatesImplementation=exports.normalizeAppIdentity=exports.initOtaUpdates=exports.checkForOtaUpdate=exports.applyOtaUpdate=exports.DEFAULT_MIN_CHECK_INTERVAL_MS=void 0;var _react=require("react"),_reactNative=require("react-native"),_index=require("../config/index.js"),_index2=require("../logger/index.js"),_index3=require("../storage/index.js");const PREFIX="[@webority/mobile-core] OTA updates:",REPORTED_KEY="@webority/ota/last-reported-update-id",ENV_IDENTITY_SUFFIXES=[".staging",".development"],DEFAULT_MIN_CHECK_INTERVAL_MS=exports.DEFAULT_MIN_CHECK_INTERVAL_MS=9e5,EVENT_TIMEOUT_MS=1e4;let resolved,override=null;const setOtaUpdatesImplementation=e=>{override=e};exports.setOtaUpdatesImplementation=setOtaUpdatesImplementation;const resolveUpdates=()=>{if(override)return override;if(void 0!==resolved)return resolved;try{resolved=require("expo-updates")}catch{resolved=null}return resolved},INITIAL_STATE={status:"unavailable",runningUpdateId:null,error:null,lastCheckedAt:null};let state=INITIAL_STATE;const subscribers=new Set,setState=e=>{state={...state,...e};for(const e of subscribers)e()};let config=null,stateSubscription=null,appStateSubscription=null,checking=null,lastCheckStartedAt=0,lastReportedError=null;const messageOf=e=>e instanceof Error?e.message:String(e),normalizeAppIdentity=e=>{const t=String(e??"").trim().toLowerCase(),a=ENV_IDENTITY_SUFFIXES.find(e=>t.endsWith(e));return a?t.slice(0,-a.length):t};exports.normalizeAppIdentity=normalizeAppIdentity;const ownIdentity=()=>config?.appIdentifier??(0,_index.getConfigValue)("appId",null)??null,identityMismatch=e=>{const t=e?.extra?.webority?.appIdentifier;if(!t)return null;const a=ownIdentity();return a&&normalizeAppIdentity(t)===normalizeAppIdentity(a)?null:`identity mismatch: the update is for '${t}' but this app is '${a??"unset"}'`},postEvent=async(e,t,a)=>{const r=config?.serverUrl;if(!r||!e)return;const
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.useOtaUpdates=exports.stopOtaUpdates=exports.setOtaUpdatesImplementation=exports.normalizeAppIdentity=exports.initOtaUpdates=exports.checkForOtaUpdate=exports.applyOtaUpdate=exports.OTA_LAST_LAUNCH_KEY=exports.DEFAULT_MIN_CHECK_INTERVAL_MS=void 0;var _react=require("react"),_reactNative=require("react-native"),_index=require("../config/index.js"),_index2=require("../logger/index.js"),_index3=require("../storage/index.js");const PREFIX="[@webority/mobile-core] OTA updates:",REPORTED_KEY="@webority/ota/last-reported-update-id",OTA_LAST_LAUNCH_KEY=exports.OTA_LAST_LAUNCH_KEY="@webority/ota/last-launch",ENV_IDENTITY_SUFFIXES=[".staging",".development"],DEFAULT_MIN_CHECK_INTERVAL_MS=exports.DEFAULT_MIN_CHECK_INTERVAL_MS=9e5,EVENT_TIMEOUT_MS=1e4;let resolved,override=null;const setOtaUpdatesImplementation=e=>{override=e};exports.setOtaUpdatesImplementation=setOtaUpdatesImplementation;const resolveUpdates=()=>{if(override)return override;if(void 0!==resolved)return resolved;try{resolved=require("expo-updates")}catch{resolved=null}return resolved},INITIAL_STATE={status:"unavailable",runningUpdateId:null,error:null,lastCheckedAt:null};let state=INITIAL_STATE;const subscribers=new Set,setState=e=>{state={...state,...e};for(const e of subscribers)e()};let config=null,stateSubscription=null,appStateSubscription=null,checking=null,lastCheckStartedAt=0,lastReportedError=null;const messageOf=e=>e instanceof Error?e.message:String(e),normalizeAppIdentity=e=>{const t=String(e??"").trim().toLowerCase(),a=ENV_IDENTITY_SUFFIXES.find(e=>t.endsWith(e));return a?t.slice(0,-a.length):t};exports.normalizeAppIdentity=normalizeAppIdentity;const ownIdentity=()=>config?.appIdentifier??(0,_index.getConfigValue)("appId",null)??null,identityMismatch=e=>{const t=e?.extra?.webority?.appIdentifier;if(!t)return null;const a=ownIdentity();return a&&normalizeAppIdentity(t)===normalizeAppIdentity(a)?null:`identity mismatch: the update is for '${t}' but this app is '${a??"unset"}'`},postEvent=async(e,t,a)=>{const r=config?.serverUrl;if(!r||!e)return;const n=new AbortController,s=setTimeout(()=>n.abort(),1e4);try{await fetch(`${r.replace(/\/+$/,"")}/ota/updates/${encodeURIComponent(e)}/events`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({status:t,platform:_reactNative.Platform.OS,runtimeVersion:resolveUpdates()?.runtimeVersion??null,message:a}),signal:n.signal})}catch(e){_index2.Logger.info(`${PREFIX} the ${t} event was not reported: ${messageOf(e)}`)}finally{clearTimeout(s)}},reportError=(e,t)=>{setState({status:"error",error:t});const a=`${e??""}|${t}`;a!==lastReportedError&&(lastReportedError=a,_index2.Logger.error(`${PREFIX} ${t}`),postEvent(e,"error",t))},recordLaunch=async e=>{const t={updateId:e.updateId,channel:e.channel??null,runtimeVersion:e.runtimeVersion,isEmbeddedLaunch:e.isEmbeddedLaunch,isEmergencyLaunch:e.isEmergencyLaunch??!1,emergencyLaunchReason:e.emergencyLaunchReason??null,launchedAtUtc:(new Date).toISOString()};await _index3.Storage.setItem(OTA_LAST_LAUNCH_KEY,JSON.stringify(t))||_index2.Logger.error(`${PREFIX} the launch record was not saved`)},reportRunningUpdate=async e=>{if(await recordLaunch(e),e.isEmbeddedLaunch||!e.updateId)return;const t=identityMismatch(e.manifest);t?reportError(e.updateId,t):await _index3.Storage.getItem(REPORTED_KEY)!==e.updateId&&(await postEvent(e.updateId,"success"),await _index3.Storage.setItem(REPORTED_KEY,e.updateId),_index2.Logger.info(`${PREFIX} running update ${e.updateId}`))},onStateChange=e=>{const t=e.downloadError??e.checkError;if(t){const a=e.latestManifest?.id??resolveUpdates()?.updateId;return void reportError(a,t.message)}e.isChecking?setState({status:"checking"}):e.isDownloading?setState({status:"downloading"}):e.isUpdatePending&&setState({status:"ready",error:null})},checkForOtaUpdate=()=>{const e=resolveUpdates();if(!config||!e?.isEnabled)return Promise.resolve("unavailable");if(checking)return checking;lastCheckStartedAt=Date.now();return checking=(async()=>{try{setState({status:"checking"});const t=await e.checkForUpdateAsync();if(t.isAvailable){const e=identityMismatch(t.manifest);if(e)return reportError(t.manifest?.id,e),"error"}return t.isAvailable||t.isRollBackToEmbedded?(setState({status:"downloading"}),await e.fetchUpdateAsync(),setState({status:"ready",error:null}),"ready"):(setState({status:"ready"===state.status?"ready":"idle",error:null}),state.status)}catch(t){return reportError(e.updateId,messageOf(t)),"error"}finally{setState({lastCheckedAt:Date.now()}),checking=null}})(),checking};exports.checkForOtaUpdate=checkForOtaUpdate;const onAppStateChange=e=>{if("active"!==e||!1===config?.checkOnForeground)return;const t=config?.minCheckIntervalMs??DEFAULT_MIN_CHECK_INTERVAL_MS;Date.now()-lastCheckStartedAt<t||checkForOtaUpdate()},initOtaUpdates=e=>{const t=null===config;if(config=e,!t)return;const a=resolveUpdates();if(!a?.isEnabled)return void setState({status:"unavailable"});setState({status:"idle",runningUpdateId:a.isEmbeddedLaunch?null:a.updateId}),reportRunningUpdate(a).catch(e=>{_index2.Logger.info(`${PREFIX} start-up report failed: ${messageOf(e)}`)}),"function"==typeof a.addUpdatesStateChangeListener&&(stateSubscription=a.addUpdatesStateChangeListener(e=>onStateChange(e.context))),appStateSubscription=_reactNative.AppState.addEventListener("change",onAppStateChange);"ON_LOAD"===a.checkAutomatically||"WIFI_ONLY"===a.checkAutomatically||checkForOtaUpdate()};exports.initOtaUpdates=initOtaUpdates;const applyOtaUpdate=async()=>{const e=resolveUpdates();e?.isEnabled&&"ready"===state.status&&await e.reloadAsync()};exports.applyOtaUpdate=applyOtaUpdate;const stopOtaUpdates=()=>{stateSubscription?.remove(),appStateSubscription?.remove(),stateSubscription=null,appStateSubscription=null,config=null,checking=null,lastCheckStartedAt=0,lastReportedError=null,resolved=void 0,setState(INITIAL_STATE)};exports.stopOtaUpdates=stopOtaUpdates;const subscribe=e=>(subscribers.add(e),()=>{subscribers.delete(e)}),getSnapshot=()=>state,useOtaUpdates=()=>({...(0,_react.useSyncExternalStore)(subscribe,getSnapshot,getSnapshot),check:checkForOtaUpdate,apply:applyOtaUpdate});exports.useOtaUpdates=useOtaUpdates;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";import{notifyLocal,readDevicePushToken}from"../push/index.js";const RESERVED_COMMANDS=new Set(["signIn","signOut","navigate"]);export const createMetroBridge=e=>{const{store:t,navigationRef:n,signIn:a,signOut:i,isSignedIn:o}=e;for(const t of Object.keys(e.commands??{}))if(RESERVED_COMMANDS.has(t))throw new Error(`createMetroBridge: the command ${t} is already defined`);return{commands:{...e.commands,signIn:a,signOut:async()=>(await i(),{signedOut:!0}),navigate:({name:e,params:t})=>(n.navigate(e,t),{route:e})},navigation:{getState:()=>n.isReady()?n.getRootState():null,goBack:()=>n.isReady()&&n.canGoBack()?(n.goBack(),{wentBack:!0}):{wentBack:!1}},redux:{getState:()=>t.getState(),dispatch:e=>t.dispatch(e)},auth:{isSignedIn:o},push:{notifyLocal:notifyLocal,deviceToken:readDevicePushToken}}};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";export{createMetroBridge}from"./createMetroBridge.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
package/lib/module/push/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";export{disablePush,dismissAllNotifications,enablePush,initPush,notifyLocal,onAction,onReceive,resetPush,setBadgeCount,setCategories,setPushImplementation,usePush}from"./push.js";export{PUSH_STATUS}from"./status.js";
|
|
1
|
+
"use strict";export{disablePush,dismissAllNotifications,enablePush,initPush,notifyLocal,onAction,onReceive,readDevicePushToken,resetPush,setBadgeCount,setCategories,setPushImplementation,usePush}from"./push.js";export{PUSH_STATUS}from"./status.js";
|
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}},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
|
+
"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")};export const readDevicePushToken=async()=>{const e=resolveModules();if(!e)return null;await tokenDrop;if(!(await e.notifications.getPermissionsAsync()).granted)return null;const t=await e.notifications.getDevicePushTokenAsync(),n="android"===t.type?"android":"ios"===t.type?"ios":null;return n&&"string"==typeof t.data?{platform:n,token:t.data}:null};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";export{applyOtaUpdate,checkForOtaUpdate,DEFAULT_MIN_CHECK_INTERVAL_MS,initOtaUpdates,normalizeAppIdentity,setOtaUpdatesImplementation,stopOtaUpdates,useOtaUpdates}from"./updates.js";
|
|
1
|
+
"use strict";export{applyOtaUpdate,checkForOtaUpdate,DEFAULT_MIN_CHECK_INTERVAL_MS,initOtaUpdates,normalizeAppIdentity,OTA_LAST_LAUNCH_KEY,setOtaUpdatesImplementation,stopOtaUpdates,useOtaUpdates}from"./updates.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{useSyncExternalStore}from"react";import{AppState,Platform}from"react-native";import{getConfigValue}from"../config/index.js";import{Logger}from"../logger/index.js";import{Storage}from"../storage/index.js";const PREFIX="[@webority/mobile-core] OTA updates:",REPORTED_KEY="@webority/ota/last-reported-update-id"
|
|
1
|
+
"use strict";import{useSyncExternalStore}from"react";import{AppState,Platform}from"react-native";import{getConfigValue}from"../config/index.js";import{Logger}from"../logger/index.js";import{Storage}from"../storage/index.js";const PREFIX="[@webority/mobile-core] OTA updates:",REPORTED_KEY="@webority/ota/last-reported-update-id";export const OTA_LAST_LAUNCH_KEY="@webority/ota/last-launch";const ENV_IDENTITY_SUFFIXES=[".staging",".development"];export const DEFAULT_MIN_CHECK_INTERVAL_MS=9e5;const EVENT_TIMEOUT_MS=1e4;let resolved,override=null;export const setOtaUpdatesImplementation=e=>{override=e};const resolveUpdates=()=>{if(override)return override;if(void 0!==resolved)return resolved;try{resolved=require("expo-updates")}catch{resolved=null}return resolved},INITIAL_STATE={status:"unavailable",runningUpdateId:null,error:null,lastCheckedAt:null};let state=INITIAL_STATE;const subscribers=new Set,setState=e=>{state={...state,...e};for(const e of subscribers)e()};let config=null,stateSubscription=null,appStateSubscription=null,checking=null,lastCheckStartedAt=0,lastReportedError=null;const messageOf=e=>e instanceof Error?e.message:String(e);export const normalizeAppIdentity=e=>{const t=String(e??"").trim().toLowerCase(),r=ENV_IDENTITY_SUFFIXES.find(e=>t.endsWith(e));return r?t.slice(0,-r.length):t};const ownIdentity=()=>config?.appIdentifier??getConfigValue("appId",null)??null,identityMismatch=e=>{const t=e?.extra?.webority?.appIdentifier;if(!t)return null;const r=ownIdentity();return r&&normalizeAppIdentity(t)===normalizeAppIdentity(r)?null:`identity mismatch: the update is for '${t}' but this app is '${r??"unset"}'`},postEvent=async(e,t,r)=>{const a=config?.serverUrl;if(!a||!e)return;const n=new AbortController,s=setTimeout(()=>n.abort(),1e4);try{await fetch(`${a.replace(/\/+$/,"")}/ota/updates/${encodeURIComponent(e)}/events`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({status:t,platform:Platform.OS,runtimeVersion:resolveUpdates()?.runtimeVersion??null,message:r}),signal:n.signal})}catch(e){Logger.info(`${PREFIX} the ${t} event was not reported: ${messageOf(e)}`)}finally{clearTimeout(s)}},reportError=(e,t)=>{setState({status:"error",error:t});const r=`${e??""}|${t}`;r!==lastReportedError&&(lastReportedError=r,Logger.error(`${PREFIX} ${t}`),postEvent(e,"error",t))},recordLaunch=async e=>{const t={updateId:e.updateId,channel:e.channel??null,runtimeVersion:e.runtimeVersion,isEmbeddedLaunch:e.isEmbeddedLaunch,isEmergencyLaunch:e.isEmergencyLaunch??!1,emergencyLaunchReason:e.emergencyLaunchReason??null,launchedAtUtc:(new Date).toISOString()};await Storage.setItem(OTA_LAST_LAUNCH_KEY,JSON.stringify(t))||Logger.error(`${PREFIX} the launch record was not saved`)},reportRunningUpdate=async e=>{if(await recordLaunch(e),e.isEmbeddedLaunch||!e.updateId)return;const t=identityMismatch(e.manifest);t?reportError(e.updateId,t):await Storage.getItem(REPORTED_KEY)!==e.updateId&&(await postEvent(e.updateId,"success"),await Storage.setItem(REPORTED_KEY,e.updateId),Logger.info(`${PREFIX} running update ${e.updateId}`))},onStateChange=e=>{const t=e.downloadError??e.checkError;if(t){const r=e.latestManifest?.id??resolveUpdates()?.updateId;return void reportError(r,t.message)}e.isChecking?setState({status:"checking"}):e.isDownloading?setState({status:"downloading"}):e.isUpdatePending&&setState({status:"ready",error:null})};export const checkForOtaUpdate=()=>{const e=resolveUpdates();if(!config||!e?.isEnabled)return Promise.resolve("unavailable");if(checking)return checking;lastCheckStartedAt=Date.now();return checking=(async()=>{try{setState({status:"checking"});const t=await e.checkForUpdateAsync();if(t.isAvailable){const e=identityMismatch(t.manifest);if(e)return reportError(t.manifest?.id,e),"error"}return t.isAvailable||t.isRollBackToEmbedded?(setState({status:"downloading"}),await e.fetchUpdateAsync(),setState({status:"ready",error:null}),"ready"):(setState({status:"ready"===state.status?"ready":"idle",error:null}),state.status)}catch(t){return reportError(e.updateId,messageOf(t)),"error"}finally{setState({lastCheckedAt:Date.now()}),checking=null}})(),checking};const onAppStateChange=e=>{if("active"!==e||!1===config?.checkOnForeground)return;const t=config?.minCheckIntervalMs??9e5;Date.now()-lastCheckStartedAt<t||checkForOtaUpdate()};export const initOtaUpdates=e=>{const t=null===config;if(config=e,!t)return;const r=resolveUpdates();if(!r?.isEnabled)return void setState({status:"unavailable"});setState({status:"idle",runningUpdateId:r.isEmbeddedLaunch?null:r.updateId}),reportRunningUpdate(r).catch(e=>{Logger.info(`${PREFIX} start-up report failed: ${messageOf(e)}`)}),"function"==typeof r.addUpdatesStateChangeListener&&(stateSubscription=r.addUpdatesStateChangeListener(e=>onStateChange(e.context))),appStateSubscription=AppState.addEventListener("change",onAppStateChange);"ON_LOAD"===r.checkAutomatically||"WIFI_ONLY"===r.checkAutomatically||checkForOtaUpdate()};export const applyOtaUpdate=async()=>{const e=resolveUpdates();e?.isEnabled&&"ready"===state.status&&await e.reloadAsync()};export const stopOtaUpdates=()=>{stateSubscription?.remove(),appStateSubscription?.remove(),stateSubscription=null,appStateSubscription=null,config=null,checking=null,lastCheckStartedAt=0,lastReportedError=null,resolved=void 0,setState(INITIAL_STATE)};const subscribe=e=>(subscribers.add(e),()=>{subscribers.delete(e)}),getSnapshot=()=>state;export const useOtaUpdates=()=>({...useSyncExternalStore(subscribe,getSnapshot,getSnapshot),check:checkForOtaUpdate,apply:applyOtaUpdate});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/** The development bridge a Metro dev tool reads as globalThis.__METRO_BRIDGE__. */
|
|
2
|
+
export interface MetroNavigationRef {
|
|
3
|
+
isReady(): boolean;
|
|
4
|
+
getRootState(): unknown;
|
|
5
|
+
canGoBack(): boolean;
|
|
6
|
+
goBack(): void;
|
|
7
|
+
navigate(name: string, params?: object): void;
|
|
8
|
+
}
|
|
9
|
+
export interface MetroStore {
|
|
10
|
+
getState(): unknown;
|
|
11
|
+
dispatch(action: unknown): unknown;
|
|
12
|
+
}
|
|
13
|
+
export interface MetroSignInRequest {
|
|
14
|
+
email: string;
|
|
15
|
+
/** Absent for an account that signs in with an emailed code only. */
|
|
16
|
+
password?: string;
|
|
17
|
+
code?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface MetroSignInResult {
|
|
20
|
+
/** True when the app now waits for the emailed or authenticator code. */
|
|
21
|
+
codeRequired: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface MetroBridgeOptions {
|
|
24
|
+
store: MetroStore;
|
|
25
|
+
navigationRef: MetroNavigationRef;
|
|
26
|
+
signIn: (request: MetroSignInRequest) => Promise<MetroSignInResult>;
|
|
27
|
+
signOut: () => Promise<void>;
|
|
28
|
+
isSignedIn: () => boolean;
|
|
29
|
+
/** App commands beyond signIn, signOut and navigate; a name already taken throws. */
|
|
30
|
+
commands?: Record<string, (args?: unknown) => unknown>;
|
|
31
|
+
}
|
|
32
|
+
export interface MetroBridge {
|
|
33
|
+
commands: {
|
|
34
|
+
signIn: (request: MetroSignInRequest) => Promise<MetroSignInResult>;
|
|
35
|
+
signOut: () => Promise<{
|
|
36
|
+
signedOut: true;
|
|
37
|
+
}>;
|
|
38
|
+
navigate: (args: {
|
|
39
|
+
name: string;
|
|
40
|
+
params?: object;
|
|
41
|
+
}) => {
|
|
42
|
+
route: string;
|
|
43
|
+
};
|
|
44
|
+
[name: string]: (args: never) => unknown;
|
|
45
|
+
};
|
|
46
|
+
navigation: {
|
|
47
|
+
/** Null until the navigation container is ready. */
|
|
48
|
+
getState: () => unknown;
|
|
49
|
+
goBack: () => {
|
|
50
|
+
wentBack: boolean;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
redux: {
|
|
54
|
+
getState: () => unknown;
|
|
55
|
+
dispatch: (action: unknown) => unknown;
|
|
56
|
+
};
|
|
57
|
+
auth: {
|
|
58
|
+
isSignedIn: () => boolean;
|
|
59
|
+
};
|
|
60
|
+
push: {
|
|
61
|
+
notifyLocal: typeof import("../push").notifyLocal;
|
|
62
|
+
deviceToken: typeof import("../push").readDevicePushToken;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type { EnablePushOptions, PushAction, PushActionHandler, PushAndroidChannel, PushApnsEnvironment, PushCategory, PushCategoryAction, PushConfig, PushDevice, PushLocalNotification, PushModules, PushNotificationsLike, PushPlatform, PushReceived, PushReceiveHandler, PushState, } from "./push";
|
|
2
|
-
export { disablePush, dismissAllNotifications, enablePush, initPush, notifyLocal, onAction, onReceive, resetPush, setBadgeCount, setCategories, setPushImplementation, usePush, } from "./push";
|
|
2
|
+
export { disablePush, dismissAllNotifications, enablePush, initPush, notifyLocal, onAction, onReceive, readDevicePushToken, resetPush, setBadgeCount, setCategories, setPushImplementation, usePush, } from "./push";
|
|
3
3
|
export type { PushStatus } from "./status";
|
|
4
4
|
export { PUSH_STATUS } from "./status";
|
|
5
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -227,6 +227,11 @@ export declare const initPush: (next: PushConfig) => void;
|
|
|
227
227
|
* and the prompt can still show, `blocked` when only Settings can turn it on).
|
|
228
228
|
*/
|
|
229
229
|
export declare const enablePush: ({ prompt }?: EnablePushOptions) => Promise<PushStatus>;
|
|
230
|
+
/** Null when push is unavailable in this build or permission is not granted. Never prompts or registers. */
|
|
231
|
+
export declare const readDevicePushToken: () => Promise<{
|
|
232
|
+
platform: "android" | "ios";
|
|
233
|
+
token: string;
|
|
234
|
+
} | null>;
|
|
230
235
|
/**
|
|
231
236
|
* Unregisters the device through `config.unregister`, then stops following
|
|
232
237
|
* token changes. When the server call fails it rejects and push stays on, the
|
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
* point because it names expo-updates, an optional peer: Metro resolves every require reachable
|
|
4
4
|
* from an import, so keeping it off the main barrel lets an app without OTA skip the package.
|
|
5
5
|
*/
|
|
6
|
-
export type { OtaUpdatesConfig, OtaUpdatesState, OtaUpdatesStatus, UpdatesModuleLike, } from "./updates";
|
|
7
|
-
export { applyOtaUpdate, checkForOtaUpdate, DEFAULT_MIN_CHECK_INTERVAL_MS, initOtaUpdates, normalizeAppIdentity, setOtaUpdatesImplementation, stopOtaUpdates, useOtaUpdates, } from "./updates";
|
|
6
|
+
export type { OtaLaunchRecord, OtaUpdatesConfig, OtaUpdatesState, OtaUpdatesStatus, UpdatesModuleLike, } from "./updates";
|
|
7
|
+
export { applyOtaUpdate, checkForOtaUpdate, DEFAULT_MIN_CHECK_INTERVAL_MS, initOtaUpdates, normalizeAppIdentity, OTA_LAST_LAUNCH_KEY, setOtaUpdatesImplementation, stopOtaUpdates, useOtaUpdates, } from "./updates";
|
|
8
8
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** The record of the most recent start: which update ran, or the built-in bundle after a fallback. */
|
|
2
|
+
export declare const OTA_LAST_LAUNCH_KEY = "@webority/ota/last-launch";
|
|
1
3
|
export declare const DEFAULT_MIN_CHECK_INTERVAL_MS: number;
|
|
2
4
|
interface ManifestLike {
|
|
3
5
|
id?: string;
|
|
@@ -22,6 +24,9 @@ export interface UpdatesModuleLike {
|
|
|
22
24
|
isEmbeddedLaunch: boolean;
|
|
23
25
|
updateId: string | null;
|
|
24
26
|
runtimeVersion: string | null;
|
|
27
|
+
channel?: string | null;
|
|
28
|
+
isEmergencyLaunch?: boolean;
|
|
29
|
+
emergencyLaunchReason?: string | null;
|
|
25
30
|
checkAutomatically: string | null;
|
|
26
31
|
manifest: ManifestLike | null | undefined;
|
|
27
32
|
checkForUpdateAsync: () => Promise<{
|
|
@@ -70,6 +75,16 @@ export interface OtaUpdatesState {
|
|
|
70
75
|
/** When this module last finished a check, in epoch milliseconds. */
|
|
71
76
|
lastCheckedAt: number | null;
|
|
72
77
|
}
|
|
78
|
+
/** Written on every start, to OTA_LAST_LAUNCH_KEY as JSON. */
|
|
79
|
+
export type OtaLaunchRecord = {
|
|
80
|
+
updateId: string | null;
|
|
81
|
+
channel: string | null;
|
|
82
|
+
runtimeVersion: string | null;
|
|
83
|
+
isEmbeddedLaunch: boolean;
|
|
84
|
+
isEmergencyLaunch: boolean;
|
|
85
|
+
emergencyLaunchReason: string | null;
|
|
86
|
+
launchedAtUtc: string;
|
|
87
|
+
};
|
|
73
88
|
/**
|
|
74
89
|
* Replace expo-updates. For tests and the showcase; pass `null` to restore the real module.
|
|
75
90
|
*/
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/** The development bridge a Metro dev tool reads as globalThis.__METRO_BRIDGE__. */
|
|
2
|
+
export interface MetroNavigationRef {
|
|
3
|
+
isReady(): boolean;
|
|
4
|
+
getRootState(): unknown;
|
|
5
|
+
canGoBack(): boolean;
|
|
6
|
+
goBack(): void;
|
|
7
|
+
navigate(name: string, params?: object): void;
|
|
8
|
+
}
|
|
9
|
+
export interface MetroStore {
|
|
10
|
+
getState(): unknown;
|
|
11
|
+
dispatch(action: unknown): unknown;
|
|
12
|
+
}
|
|
13
|
+
export interface MetroSignInRequest {
|
|
14
|
+
email: string;
|
|
15
|
+
/** Absent for an account that signs in with an emailed code only. */
|
|
16
|
+
password?: string;
|
|
17
|
+
code?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface MetroSignInResult {
|
|
20
|
+
/** True when the app now waits for the emailed or authenticator code. */
|
|
21
|
+
codeRequired: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface MetroBridgeOptions {
|
|
24
|
+
store: MetroStore;
|
|
25
|
+
navigationRef: MetroNavigationRef;
|
|
26
|
+
signIn: (request: MetroSignInRequest) => Promise<MetroSignInResult>;
|
|
27
|
+
signOut: () => Promise<void>;
|
|
28
|
+
isSignedIn: () => boolean;
|
|
29
|
+
/** App commands beyond signIn, signOut and navigate; a name already taken throws. */
|
|
30
|
+
commands?: Record<string, (args?: unknown) => unknown>;
|
|
31
|
+
}
|
|
32
|
+
export interface MetroBridge {
|
|
33
|
+
commands: {
|
|
34
|
+
signIn: (request: MetroSignInRequest) => Promise<MetroSignInResult>;
|
|
35
|
+
signOut: () => Promise<{
|
|
36
|
+
signedOut: true;
|
|
37
|
+
}>;
|
|
38
|
+
navigate: (args: {
|
|
39
|
+
name: string;
|
|
40
|
+
params?: object;
|
|
41
|
+
}) => {
|
|
42
|
+
route: string;
|
|
43
|
+
};
|
|
44
|
+
[name: string]: (args: never) => unknown;
|
|
45
|
+
};
|
|
46
|
+
navigation: {
|
|
47
|
+
/** Null until the navigation container is ready. */
|
|
48
|
+
getState: () => unknown;
|
|
49
|
+
goBack: () => {
|
|
50
|
+
wentBack: boolean;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
redux: {
|
|
54
|
+
getState: () => unknown;
|
|
55
|
+
dispatch: (action: unknown) => unknown;
|
|
56
|
+
};
|
|
57
|
+
auth: {
|
|
58
|
+
isSignedIn: () => boolean;
|
|
59
|
+
};
|
|
60
|
+
push: {
|
|
61
|
+
notifyLocal: typeof import("../push/index.js").notifyLocal;
|
|
62
|
+
deviceToken: typeof import("../push/index.js").readDevicePushToken;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type { EnablePushOptions, PushAction, PushActionHandler, PushAndroidChannel, PushApnsEnvironment, PushCategory, PushCategoryAction, PushConfig, PushDevice, PushLocalNotification, PushModules, PushNotificationsLike, PushPlatform, PushReceived, PushReceiveHandler, PushState, } from "./push.js";
|
|
2
|
-
export { disablePush, dismissAllNotifications, enablePush, initPush, notifyLocal, onAction, onReceive, resetPush, setBadgeCount, setCategories, setPushImplementation, usePush, } from "./push.js";
|
|
2
|
+
export { disablePush, dismissAllNotifications, enablePush, initPush, notifyLocal, onAction, onReceive, readDevicePushToken, resetPush, setBadgeCount, setCategories, setPushImplementation, usePush, } from "./push.js";
|
|
3
3
|
export type { PushStatus } from "./status.js";
|
|
4
4
|
export { PUSH_STATUS } from "./status.js";
|
|
5
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -227,6 +227,11 @@ export declare const initPush: (next: PushConfig) => void;
|
|
|
227
227
|
* and the prompt can still show, `blocked` when only Settings can turn it on).
|
|
228
228
|
*/
|
|
229
229
|
export declare const enablePush: ({ prompt }?: EnablePushOptions) => Promise<PushStatus>;
|
|
230
|
+
/** Null when push is unavailable in this build or permission is not granted. Never prompts or registers. */
|
|
231
|
+
export declare const readDevicePushToken: () => Promise<{
|
|
232
|
+
platform: "android" | "ios";
|
|
233
|
+
token: string;
|
|
234
|
+
} | null>;
|
|
230
235
|
/**
|
|
231
236
|
* Unregisters the device through `config.unregister`, then stops following
|
|
232
237
|
* token changes. When the server call fails it rejects and push stays on, the
|
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
* point because it names expo-updates, an optional peer: Metro resolves every require reachable
|
|
4
4
|
* from an import, so keeping it off the main barrel lets an app without OTA skip the package.
|
|
5
5
|
*/
|
|
6
|
-
export type { OtaUpdatesConfig, OtaUpdatesState, OtaUpdatesStatus, UpdatesModuleLike, } from "./updates.js";
|
|
7
|
-
export { applyOtaUpdate, checkForOtaUpdate, DEFAULT_MIN_CHECK_INTERVAL_MS, initOtaUpdates, normalizeAppIdentity, setOtaUpdatesImplementation, stopOtaUpdates, useOtaUpdates, } from "./updates.js";
|
|
6
|
+
export type { OtaLaunchRecord, OtaUpdatesConfig, OtaUpdatesState, OtaUpdatesStatus, UpdatesModuleLike, } from "./updates.js";
|
|
7
|
+
export { applyOtaUpdate, checkForOtaUpdate, DEFAULT_MIN_CHECK_INTERVAL_MS, initOtaUpdates, normalizeAppIdentity, OTA_LAST_LAUNCH_KEY, setOtaUpdatesImplementation, stopOtaUpdates, useOtaUpdates, } from "./updates.js";
|
|
8
8
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** The record of the most recent start: which update ran, or the built-in bundle after a fallback. */
|
|
2
|
+
export declare const OTA_LAST_LAUNCH_KEY = "@webority/ota/last-launch";
|
|
1
3
|
export declare const DEFAULT_MIN_CHECK_INTERVAL_MS: number;
|
|
2
4
|
interface ManifestLike {
|
|
3
5
|
id?: string;
|
|
@@ -22,6 +24,9 @@ export interface UpdatesModuleLike {
|
|
|
22
24
|
isEmbeddedLaunch: boolean;
|
|
23
25
|
updateId: string | null;
|
|
24
26
|
runtimeVersion: string | null;
|
|
27
|
+
channel?: string | null;
|
|
28
|
+
isEmergencyLaunch?: boolean;
|
|
29
|
+
emergencyLaunchReason?: string | null;
|
|
25
30
|
checkAutomatically: string | null;
|
|
26
31
|
manifest: ManifestLike | null | undefined;
|
|
27
32
|
checkForUpdateAsync: () => Promise<{
|
|
@@ -70,6 +75,16 @@ export interface OtaUpdatesState {
|
|
|
70
75
|
/** When this module last finished a check, in epoch milliseconds. */
|
|
71
76
|
lastCheckedAt: number | null;
|
|
72
77
|
}
|
|
78
|
+
/** Written on every start, to OTA_LAST_LAUNCH_KEY as JSON. */
|
|
79
|
+
export type OtaLaunchRecord = {
|
|
80
|
+
updateId: string | null;
|
|
81
|
+
channel: string | null;
|
|
82
|
+
runtimeVersion: string | null;
|
|
83
|
+
isEmbeddedLaunch: boolean;
|
|
84
|
+
isEmergencyLaunch: boolean;
|
|
85
|
+
emergencyLaunchReason: string | null;
|
|
86
|
+
launchedAtUtc: string;
|
|
87
|
+
};
|
|
73
88
|
/**
|
|
74
89
|
* Replace expo-updates. For tests and the showcase; pass `null` to restore the real module.
|
|
75
90
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webority/mobile-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.110.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",
|
|
@@ -202,6 +202,16 @@
|
|
|
202
202
|
"default": "./lib/commonjs/push/task.js"
|
|
203
203
|
}
|
|
204
204
|
},
|
|
205
|
+
"./metro": {
|
|
206
|
+
"import": {
|
|
207
|
+
"types": "./lib/typescript/module/metro/index.d.ts",
|
|
208
|
+
"default": "./lib/module/metro/index.js"
|
|
209
|
+
},
|
|
210
|
+
"require": {
|
|
211
|
+
"types": "./lib/typescript/commonjs/metro/index.d.ts",
|
|
212
|
+
"default": "./lib/commonjs/metro/index.js"
|
|
213
|
+
}
|
|
214
|
+
},
|
|
205
215
|
"./push/broadcasts": {
|
|
206
216
|
"import": {
|
|
207
217
|
"types": "./lib/typescript/module/push/broadcasts.d.ts",
|